Як встановити часовий пояс java.util.Date?


225

Я розібрав a java.util.Dateз, Stringале він визначає локальний часовий пояс як часовий пояс dateоб'єкта.

Часовий пояс не вказується в тому, Stringз якого Dateрозбирається. Я хочу встановити конкретний часовий пояс dateоб’єкта.

Як я можу це зробити?


8
Хоча насправді не було відповіді на ваше запитання, я користувався Joda Time після того, як його кілька разів тут згадували. Мені це здається більш раціональним, ніж стандартні API, і зробити це можна досить легко.
clstrfsck

2
@msandiford В даний час використовуйте класи java.time, а не Joda-Time. Проект Joda-Time зараз перебуває в режимі технічного обслуговування , і команда радить перейти до класів java.time . Дивіться Підручник від Oracle .
Василь Бурк

Відповіді:


315

Використовуйте DateFormat. Наприклад,

SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
isoFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
Date date = isoFormat.parse("2010-05-23T09:01:02");

6
якщо дата створена з класу Календар, ви можете встановити часовий пояс для календаря.
lwpro2

19
@ lwpro2 це твердження вводить в оману; Ви можете встановити часовий пояс для об’єкта Calendar, але отримання об'єкта Date з нього методом getTime () поверне об’єкт Date в часовий пояс хост-комп'ютера.
BrDaHa

У мене виникають проблеми з розбором 20.02.15 14:44. Хтось може допомогти
MS

@BrDaHa вірно. Вам потрібно буде TimeZone.setDefault()подзвонити перед тим, як зателефонувати, getTime()щоб новий об’єкт дати опинився у потрібному вам часовому поясі. У JDK 1.8 Calendar.getTime()дзвінки return new Date(getTimeInMillis());.
jpllosa

182

Пам’ятайте, що java.util.Dateоб’єкти не містять ніякої інформації часового поясу - ви не можете встановити часовий пояс на Dateоб’єкті. Єдине, що Dateмістить об’єкт - це кількість мілісекунд з моменту «епохи» - 1 січня 1970 року, 00:00:00 UTC.

Як показує ZZ Coder, ви встановлюєте часовий пояс на DateFormatоб'єкт, щоб повідомити йому, в якому часовому поясі ви хочете відображати дату та час.


77
Після Googling, експерименту та роз'яснення, я зрозумів, що це точне і корисне доповнення до відповіді - і варто підкреслити: Дата містить лише значення мілісекунди . Якщо ви подивитесь на джерело, тут є майже все, що longназивається полем fastTime. Date.toString()насправді використовує a Calendarдля інтерпретації цього мілісекундного часу. Таким чином, при друкуванні на екрані з'являється часовий пояс (за замовчуванням), що Dateспричиняє зрозумілі запитання щодо встановлення цього часового поясу.
Девід Карбоні

3
Є інформація про часовий пояс усередині об'єктів Date. Але це може бути правдою, ви не можете змінити це.
lwpro2

3
@ Iwpro2, Jesper стверджує (і я згоден), що об’єкт Date не зберігає часовий пояс. Якщо ви заявляєте, що часовий пояс зберігається всередині java.util.Date, надайте довідку.
Джим

6
@Jim, це вихідний код для java.util.Date. Він містить fastTime як unix epoch, а також BaseCalendar.Date cdate, який використовується на користь fastTime, якщо він визначений. Цей містить інформацію про часовий пояс. Я розумію це так, що екземпляр дати може містити інформацію про часовий пояс, але він може не робити.
eis

2
@Jim Я не сказав, що ти можеш її встановити, я говорив, що вона містить цю інформацію. Я не бачу жодного способу встановити його як користувача. Я думаю, що ОП є правильним, оскільки він, як видається, завжди є тимчасовим поясом користувача / системи за замовчуванням.
eis

95

тл; д-р

… Проаналізовано… з рядка… часовий пояс не вказано… Я хочу встановити конкретний часовий пояс

LocalDateTime.parse( "2018-01-23T01:23:45.123456789" )  // Parse string, lacking an offset-from-UTC and lacking a time zone, as a `LocalDateTime`.
    .atZone( ZoneId.of( "Africa/Tunis" ) )              // Assign the time zone for which you are certain this date-time was intended. Instantiates a `ZonedDateTime` object.

Немає часової зони в juDate

Як зазначено в інших правильних відповідях, java.util.Date не має часового поясу . Він представляє UTC / GMT (не зміщено часовий пояс). Дуже заплутаний, оскільки його toStringметод застосовує часовий пояс JVM за замовчуванням під час створення представлення String.

Уникайте juDate

З цієї та багатьох інших причин вам слід уникати використання вбудованого java.util.Date & .Calendar & java.text.SimpleDateFormat. Вони сумно викликають занепокоєння.

Замість цього використовуйте пакет java.time в комплекті з Java 8 .

java.time

Класи java.time можуть представляти момент на шкалі трьома способами:

  • UTC ( Instant)
  • З компенсацією ( OffsetDateTimeз ZoneOffset)
  • З часовим поясом ( ZonedDateTimeз ZoneId)

Instant

У java.time основним будівельним блоком є Instantмомент на часовій лінії в UTC. Використовуйте Instantоб'єкти для більшої частини вашої бізнес-логіки.

Instant instant = Instant.now();

OffsetDateTime

Застосуйте зсув з-за UTC, щоб налаштувати час настінного годинника деяких місцевостей .

Застосуйте, ZoneOffsetщоб отримати OffsetDateTime.

ZoneOffset zoneOffset = ZoneOffset.of( "-04:00" );
OffsetDateTime odt = OffsetDateTime.ofInstant( instant , zoneOffset );

ZonedDateTime

Краще застосувати часовий пояс , зміщення плюс правила поводження з аномаліями, такими як літній час (DST) .

Застосуйте а ZoneId, Instantщоб отримати ZonedDateTime. Завжди вказуйте належну назву часового поясу . Ніколи не використовуйте 3-4 абревіатури, такі як ESTабо ISTякі не є ні унікальними, ні стандартизованими.

ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );

LocalDateTime

Якщо у вхідному рядку не було жодного індикатора зміщення чи зони, проаналізуйте як a LocalDateTime.

Якщо вам відомо про запланований часовий пояс, призначте команду ZoneIda ZonedDateTime. Дивіться приклад коду вище в розділі tl; dr вгорі.

Форматизовані рядки

Викличте toStringметод у будь-якому з цих трьох класів для створення рядка, що представляє значення дати та часу у стандартному форматі ISO 8601 . ZonedDateTimeКлас розширює стандартний формат шляхом додавання імені тимчасової зони в дужках.

String outputInstant = instant.toString(); // Ex: 2011-12-03T10:15:30Z
String outputOdt = odt.toString(); // Ex: 2007-12-03T10:15:30+01:00
String outputZdt = zdt.toString(); // Ex: 2007-12-03T10:15:30+01:00[Europe/Paris]

Для інших форматів використовуйте DateTimeFormatterклас. Як правило, краще, щоб цей клас генерував локалізовані формати, використовуючи очікувані користувачем людські мовні та культурні норми. Або ви можете вказати певний формат.


Таблиця всіх типів дат та часу на Java, як сучасних, так і застарілих


Про java.time

Java.time каркас вбудований в Java 8 і пізніших версій. Ці класи витісняти неприємні старі застарілі класи дати і часу , такі як java.util.Date, Calendar, і SimpleDateFormat.

Проект Joda-Time , який зараз знаходиться в режимі обслуговування , радить перейти до класів java.time .

Щоб дізнатися більше, дивіться навчальний посібник Oracle . І шукайте переповнення стека за багатьма прикладами та поясненнями. Специфікація - JSR 310 .

Ви можете обмінюватися об'єктами java.time безпосередньо зі своєю базою даних. Використовуйте драйвер JDBC, сумісний з JDBC 4.2 або пізнішої версії. Немає потреби в струнах, немає потреби в java.sql.*заняттях.

Де отримати класи java.time?

Проект ThreeTen-Extra розширює java.time додатковими класами. Цей проект є передумовою для можливих майбутніх доповнень до java.time. Ви можете знайти деякі корисні класи тут , такі як Interval, YearWeek, YearQuarter, і більш .


Joda-Time

Поки Joda-Time все ще активно підтримується, його виробники сказали нам перейти на java.time, як тільки зручно. Цей розділ я залишаю недоторканим як посилання, але пропоную використовувати java.timeрозділ вище.

У Joda-Time об’єкт дати ( DateTime) дійсно знає призначений часовий пояс. Це означає зміщення від UTC та правил та історії літнього часу (DST) цього часового поясу та інших подібних аномалій.

String input = "2014-01-02T03:04:05";
DateTimeZone timeZone = DateTimeZone.forID( "Asia/Kolkata" );
DateTime dateTimeIndia = new DateTime( input, timeZone );
DateTime dateTimeUtcGmt = dateTimeIndia.withZone( DateTimeZone.UTC );

Викличте toStringметод для створення рядка у форматі ISO 8601 .

String output = dateTimeIndia.toString();

Joda-Time також пропонує багаті можливості для генерації всіх видів інших форматів String.

Якщо потрібно, ви можете конвертувати з Joda-Time DateTime в java.util.Date.

Java.util.Date date = dateTimeIndia.toDate();

Шукайте в StackOverflow "дату joda", щоб знайти ще багато прикладів, деякі досить детальні.


Насправді є часовий пояс, вбудований у java.util.Date, який використовується для деяких внутрішніх функцій (див. Коментарі до цього відповіді). Але цей внутрішній часовий пояс не виставляється як властивість і його не можна встановити. Цей внутрішній часовий пояс не є тим, який використовується toStringметодом для формування рядкового подання значення дати та часу; натомість застосовується поточний часовий пояс JVM за замовчуванням. Отже, як скорочено, ми часто кажемо, що "juDate не має часового поясу". Заплутано? Так. Ще одна причина уникати цих втомлених старих занять.


3
"Немає часової зони в juDate" помиляється. Інформація про часовий пояс у juDate зберігається у його BaseCalendar.Date cdateвластивості, якщо вона встановлена. Погляньте на вихідний код тут . Ви не можете встановити часовий пояс об’єкта juDate, за винятком зміни часового поясу JVM за замовчуванням, зателефонувавши TimeZone.setDefault(TimeZone.getTimeZone("NEW_TIME_ZONE"));. Таким чином, відбувається зміщення часового поясу, і ви можете отримати компенсацію, зателефонувавши зі застарілим методом juDate.getTimezoneOffset ()
Тайський Буй

3
@blquythai Правильно, ви зробили домашнє завдання. Як і я, побачивши цей вихідний код раніше. Там є часовий пояс похований там. Але з усіх практичних цілей цей часовий пояс ігнорується. Java.util.Date працює без жодного часового поясу, фактично знаходиться в UTC, ігноруючи цей похований часовий пояс. За винятком toStringметоду, який застосовує поточний часовий пояс JVM за замовчуванням; знову ігноруючи похований часовий пояс. Тому для стислості ми говоримо, що java.util.Date не має часового поясу. Як і мистецтво , це брехня, яка говорить правду.
Василь Бурк

@blquythai Що стосується виклику TimeZone.setDefault, ви не встановлюєте часовий пояс об’єкта java.util.Date - об’єкт Date все ще ігнорує похований часовий пояс, діє ефективно в UTC. Ви вплинете на toStringметод Дата . Встановлення за замовчуванням змінює часовий пояс за замовчуванням JVM, який зазвичай встановлюється в часовий пояс хост-операційної системи. Цей виклик не рекомендується, оскільки він впливає на весь код у всіх потоках усіх програм, що працюють у цьому JVM, і робить це в ході, коли вони виконуються. Будучи грубим і небезпечним, цей дзвінок слід розглядати лише в крайньому випадку.
Василь Бурк

5
В той час зона використовується дуже часто (використовується в equals, hashcode, getTime..) Якщо ви подивитеся на equalsметод, він викликає , getTime()який виклики getTimeImpl(), які виклики , normalize()якщо cdateвластивість не нормовано. У normalize()методі останній, якщо умова перераховує мілісекунди з 1/1/70 на основі збереженої інформації про часовий пояс, якщо часовий пояс cdateвідрізняється від часового поясу поточного середовища JVM, на якому він працює. (Погляньте sun.util.calendar.AbstractCalendar getCalendarDate(long millis, CalendarDate date))
Thai Bui

2
@MichalM За вашою порадою, деякий час тому я додав післямови про вбудовану зону.
Василь Бурк

75

Ви також можете встановити часовий пояс на рівні JVM

Date date1 = new Date();
System.out.println(date1);

TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
// or pass in a command line arg: -Duser.timezone="UTC"

Date date2 = new Date();
System.out.println(date2);

вихід:

Thu Sep 05 10:11:12 EDT 2013
Thu Sep 05 14:11:12 UTC 2013

Це мені допомогло. Налаштування часового поясу в SDF не робив ніяких відмінностей
Вішну Viswanath

Я вважаю його надійнішим. (+1)
foobar

23
Остерігайтеся: Виклик TimeZone.setDefaultє досить різким, оскільки впливає на весь JVM, впливає на всі інші об'єкти та потоки. Дивіться цю відповідь, щоб отримати подробиці, включаючи ще більше ускладнень, якщо ви працюєте з SecurityManager. Додавання ще більшого ускладнення: така поведінка змінилася в різних версіях Java, про що йдеться в цьому запитанні .
Василь Бурк

Це встановлює загальний часовий пояс у всіх потоках, породжених після цього твердження, правда?
Jaydev

Це краща відповідь на питання, ніж прийняте.
франкогрекс

10

Якщо вам потрібно працювати лише зі стандартними класами JDK, ви можете скористатися цим:

/**
 * Converts the given <code>date</code> from the <code>fromTimeZone</code> to the
 * <code>toTimeZone</code>.  Since java.util.Date has does not really store time zome
 * information, this actually converts the date to the date that it would be in the
 * other time zone.
 * @param date
 * @param fromTimeZone
 * @param toTimeZone
 * @return
 */
public static Date convertTimeZone(Date date, TimeZone fromTimeZone, TimeZone toTimeZone)
{
    long fromTimeZoneOffset = getTimeZoneUTCAndDSTOffset(date, fromTimeZone);
    long toTimeZoneOffset = getTimeZoneUTCAndDSTOffset(date, toTimeZone);

    return new Date(date.getTime() + (toTimeZoneOffset - fromTimeZoneOffset));
}

/**
 * Calculates the offset of the <code>timeZone</code> from UTC, factoring in any
 * additional offset due to the time zone being in daylight savings time as of
 * the given <code>date</code>.
 * @param date
 * @param timeZone
 * @return
 */
private static long getTimeZoneUTCAndDSTOffset(Date date, TimeZone timeZone)
{
    long timeZoneDSTOffset = 0;
    if(timeZone.inDaylightTime(date))
    {
        timeZoneDSTOffset = timeZone.getDSTSavings();
    }

    return timeZone.getRawOffset() + timeZoneDSTOffset;
}

Кредит йде на цю посаду .


1
Зсув часових поясів не завжди постійний. Насправді вони можуть змінюватися через геополітичні причини. TimeZone.getDSTSavings () не враховує це, і завжди повертає поточне зміщення. Це означає, що ви можете отримати неправильну конверсію в тому випадку, якщо ви маєте справу з історичною датою за допомогою timeToneZone / toTimeZone, яка змінила компенсацію з цієї дати.
andrerobot

6

java.util.Calendarє звичайним способом обробки часових поясів, використовуючи лише класи JDK. Apache Commons має ще деякі альтернативи / утиліти, які можуть бути корисними. Редагувати примітку Spong нагадав мені , що я чув дуже хороші речі про Joda-час (хоча я не використовував його сам).


+1 за Joda Time. Хоча він не забезпечує додаткових функціональних можливостей, які ви не змогли отримати зі стандартного Java API (який я виявив у будь-якому випадку - радий, що показали інакше), Joda Time робить деякі завдання простішими.
Джошуа Хатчісон

2
@JoshuaHutchison Joda час має тонни додаткової функціональності. Приклад: Подання прольоти часу з класами Period, Durationі Interval. Ці прольоти включають в себе методи порівняння , такі як contains, abuts, overlap, і gap. І PeriodFormatterBuilderможе будувати описові фрази, такі як "15 років і 8 місяців".
Василь Бурк

1

Перетворіть дату в String і зробіть це за допомогою SimpleDateFormat.

    SimpleDateFormat readFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    readFormat.setTimeZone(TimeZone.getTimeZone("GMT" + timezoneOffset));
    String dateStr = readFormat.format(date);
    SimpleDateFormat writeFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    Date date = writeFormat.parse(dateStr);

1
Очікується, що відповіді на переповнення стека матимуть певну дискусію чи пояснення. Цей веб-сайт повинен бути більше, ніж бібліотека фрагментів коду.
Василь Бурк

дякую @Basil Bourque за ваш коментар. Я редагую відповідь
avisper

0

Якщо комусь це колись потрібно, якщо вам потрібно перетворити XMLGregorianCalendarчасовий пояс у ваш поточний часовий пояс з UTC, то все, що вам потрібно зробити, - це встановити часовий пояс 0, а потім зателефонувати toGregorianCalendar()- він залишиться тим самим часовим поясом, але Dateзнає, як його перетворити ваш, тому ви можете отримати дані звідти.

XMLGregorianCalendar xmlStartTime = DatatypeFactory.newInstance()
    .newXMLGregorianCalendar(
        ((GregorianCalendar)GregorianCalendar.getInstance());
xmlStartTime.setTimezone(0);
GregorianCalendar startCalendar = xmlStartTime.toGregorianCalendar();
Date startDate = startCalendar.getTime();
XMLGregorianCalendar xmlStartTime = DatatypeFactory.newInstance()
    .newXMLGregorianCalendar(startCalendar);
xmlStartTime.setHour(startDate.getHours());
xmlStartTime.setDay(startDate.getDate());
xmlStartTime.setMinute(startDate.getMinutes());
xmlStartTime.setMonth(startDate.getMonth()+1);
xmlStartTime.setTimezone(-startDate.getTimezoneOffset());
xmlStartTime.setSecond(startDate.getSeconds());
xmlStartTime.setYear(startDate.getYear() + 1900);
System.out.println(xmlStartTime.toString());

Результат:

2015-08-26T12:02:27.183Z
2015-08-26T14:02:27.183+02:00

0

Цей код був корисним у додатку, над яким я працюю:

    Instant date = null;
    Date sdf = null;
    String formatTemplate = "EEE MMM dd yyyy HH:mm:ss";
    try {
        SimpleDateFormat isoFormat = new SimpleDateFormat("EEE MMM dd yyyy HH:mm:ss");
        isoFormat.setTimeZone(TimeZone.getTimeZone(ZoneId.of("US/Pacific")));
        sdf = isoFormat.parse(timeAtWhichToMakeAvailable);
        date = sdf.toInstant();

    } catch (Exception e) {
        System.out.println("did not parse: " + timeAtWhichToMakeAvailable);
    }

    LOGGER.info("timeAtWhichToMakeAvailable: " + timeAtWhichToMakeAvailable);
    LOGGER.info("sdf: " + sdf);
    LOGGER.info("parsed to: " + date);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.