Обработка летнего времени на Java

Обработка летнего времени на Java

1. обзор

Daylight Saving Time, или летнее время, - это практика перевода часов в летние месяцы, чтобы использовать дополнительный час естественного света (экономия энергии отопления, освещения, улучшение настроения и т. д.).

Он используетсяseveral countries и должен учитываться при работе с датами и отметками времени.

В этом руководстве мы увидим, как правильно обрабатывать DST в Java в зависимости от местоположения.

2. Изменчивость JRE и DST

Во-первых, чрезвычайно важно понимать, что всемирные зоны летнего времениchange very often и нет центрального органа, координирующего это.

Страна или, в некоторых случаях, даже город могут решить, следует ли и как применять или отозвать его.

Каждый раз, когда это происходит, изменение записывается вIANA Time Zone Database, и обновление будет развернуто в будущемrelease of the JRE.

In case it’s not possible to wait, we can force the modified Time Zone data containing the new DST settings into the JRE через официальный инструмент Oracle под названиемJava Time Zone Updater Tool, доступный наJava SE download page.

3. Неправильный путь: трехбуквенный идентификатор часового пояса

Еще в дни JDK 1.1 API допускал трехбуквенные идентификаторы часовых поясов, но это привело к нескольким проблемам.

Во-первых, это потому, что один и тот же трехбуквенный идентификатор может относиться к нескольким часовым поясам. Например,CST может быть США. «Центральное стандартное время», а также «Стандартное китайское время». Тогда платформа Java могла распознавать только одну из них.

Другая проблема заключалась в том, что стандартные часовые пояса никогда не учитывают переход на летнее время. Для нескольких областей / регионов / городов местное летнее время может находиться в одном стандартном часовом поясе, поэтому стандартное время его не соблюдает.

Благодаря обратной совместимостиit’s still possible to instantiate a java.util.Timezone with a three-letter ID. However, this method is deprecated and shouldn’t be used anymore.

4. Правильный путь: идентификатор часового пояса TZDB

Правильный способ обработки DST в Java - создать экземплярTimezone с определенным идентификатором часового пояса TZDB, например. “Europe/Rome”.

Затем мы будем использовать это в сочетании с классами, зависящими от времени, такими какjava.util.Calendar, чтобы получить правильную конфигурацию необработанного смещенияTimeZone’s (относительно часового пояса GMT) и автоматических корректировок перехода на летнее время.

Давайте посмотрим, как автоматически выполняется переход отGMT+1 кGMT+2 (который происходит в Италии 25 марта 2018 г. в 02:00) при использовании правогоTimeZone:

TimeZone tz = TimeZone.getTimeZone("Europe/Rome");
TimeZone.setDefault(tz);
Calendar cal = Calendar.getInstance(tz, Locale.ITALIAN);
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ITALIAN);
Date dateBeforeDST = df.parse("2018-03-25 01:55");
cal.setTime(dateBeforeDST);

assertThat(cal.get(Calendar.ZONE_OFFSET)).isEqualTo(3600000);
assertThat(cal.get(Calendar.DST_OFFSET)).isEqualTo(0);

Как мы видим,ZONE_OFFSET составляет 60 минут (потому что Италия - этоGMT+1), аDST_OFFSET в это время равно 0.

Добавим десять минут кCalendar:

cal.add(Calendar.MINUTE, 10);

ТеперьDST_OFFSET тоже стало 60 минут, и страна перешла местное время сCET (центральноевропейское время) наCEST (центральноевропейское летнее время), что составляетGMT+2. :

Date dateAfterDST = cal.getTime();

assertThat(cal.get(Calendar.DST_OFFSET))
  .isEqualTo(3600000);
assertThat(dateAfterDST)
  .isEqualTo(df.parse("2018-03-25 03:05"));

Если мы отобразим две даты в консоли, мы также увидим изменение часового пояса:

Before DST (00:55 UTC - 01:55 GMT+1) = Sun Mar 25 01:55:00 CET 2018
After DST (01:05 UTC - 03:05 GMT+2) = Sun Mar 25 03:05:00 CEST 2018

В качестве финального теста мы можем измерить расстояние между двумяDates, 1:55 и 3:05:

Long deltaBetweenDatesInMillis = dateAfterDST.getTime() - dateBeforeDST.getTime();
Long tenMinutesInMillis = (1000L * 60 * 10);

assertThat(deltaBetweenDatesInMillis)
  .isEqualTo(tenMinutesInMillis);

Как и следовало ожидать, расстояние составит 10 минут вместо 70.

Мы узнали, как избежать распространенных ошибок, с которыми мы можем столкнуться при работе сDate, за счет правильного использованияTimeZone иLocale.

5. Лучший способ: Java 8 Date / Time API

Работа с этими небезопасными для потоков и не всегда удобными для пользователя классамиjava.util всегда была сложной, особенно из-за проблем совместимости, которые не позволяли их должным образом реорганизовать.

По этой причине в Java 8 был представлен новый пакетjava.time и совершенно новый набор APIDate/Time API.. Это ISO-ориентированный, полностью поточно-ориентированный и во многом вдохновленный знаменитой библиотекой Joda- Время.

Давайте подробнее рассмотрим эти новые классы, начиная с преемникаjava.util.Date,java.time.LocalDateTime:

LocalDateTime localDateTimeBeforeDST = LocalDateTime
  .of(2018, 3, 25, 1, 55);

assertThat(localDateTimeBeforeDST.toString())
  .isEqualTo("2018-03-25T01:55");

Мы можем наблюдать, какLocalDateTime соответствует профилюISO8601, стандартной и широко принятой нотации даты и времени.

Однако он совершенно не знаетZones иOffsets, поэтому нам нужно преобразовать его вa fully DST-aware java.time.ZonedDateTime:

ZoneId italianZoneId = ZoneId.of("Europe/Rome");
ZonedDateTime zonedDateTimeBeforeDST = localDateTimeBeforeDST
  .atZone(italianZoneId);

assertThat(zonedDateTimeBeforeDST.toString())
  .isEqualTo("2018-03-25T01:55+01:00[Europe/Rome]");

Как мы видим, теперь дата включает в себя две основные конечные части информации:+01:00 - этоZoneOffset, а[Europe/Rome] - этоZoneId.

Как и в предыдущем примере, давайте активируем летнее время, добавив десять минут:

ZonedDateTime zonedDateTimeAfterDST = zonedDateTimeBeforeDST
  .plus(10, ChronoUnit.MINUTES);

assertThat(zonedDateTimeAfterDST.toString())
  .isEqualTo("2018-03-25T03:05+02:00[Europe/Rome]");

Опять же, мы видим, как и время, и смещение зоны смещаются вперед и сохраняют одинаковое расстояние:

Long deltaBetweenDatesInMinutes = ChronoUnit.MINUTES
  .between(zonedDateTimeBeforeDST,zonedDateTimeAfterDST);
assertThat(deltaBetweenDatesInMinutes)
  .isEqualTo(10);

6. Заключение

Мы узнали, что такое летнее время и как с этим справиться, на некоторых практических примерах в различных версиях API ядра Java.

При работе с Java 8 и выше использование нового пакетаjava.time приветствуется благодаря простоте использования и его стандартному потокобезопасному характеру.

Как всегда, доступен полный исходный кодover on Github.