Joda-Time, расчет летнего времени, независимые от часового пояса тесты

Мы используем фиксированные периоды времени в приложении. Когда пользователь добавляет новый период, он должен быть по умолчанию с 6:00 до 6:00 следующего дня.

Обычно это 24 часа, но есть одна проблема: когда выполняется переход на летнее время, продолжительность этого периода изменяется. Например:

27 октября с 6:00 до 28 октября в 6:00. В этот период выполняется смена часового пояса с CEST на CET. Таким образом, этот период содержит 25 часов:

From 27 October 6:00 AM to 28 October 3:00 AM - there are 21 hours
at 3:00 am the time is shifted back by 1 hour, so there are 4 hours until 28 October 6:00 AM.

Мы столкнулись с этой проблемой и попытались написать модульный тест, чтобы он не появлялся снова. Тест успешно проходит на наших машинах, но не проходит на сервере CI (он находится в другом часовом поясе).

Вопрос заключается в следующем: как мы можем разработать наш модульный тест независимо от часового пояса машины?

В настоящее время расчет часового промежутка рассчитывается с использованием Joda-Time:

    if ((aStartDate == null) || (aEndDate == null)) {
        return 0;
    }
    final DateTime startDate = new DateTime(aStartDate);
    final DateTime endDate = new DateTime(aEndDate);
    return Hours.hoursBetween(startDate, endDate).getHours();

Модульное тестирование, которое проходит на нашей стороне, но не проходит на сервере CI:

    Calendar calendar = Calendar.getInstance();
    calendar.set(2012, Calendar.OCTOBER, 27, 6, 0);
    startDate= calendar.getTime();
    calendar.set(2012, Calendar.OCTOBER, 28, 6, 0);
    endDate= calendar.getTime();

Мы постарались использовать часовые пояса для календаря:

    TimeZone.setDefault(TimeZone.getTimeZone(TimeZone.getTimeZone("GMT").getID()));
    Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone(TimeZone.getTimeZone("GMT").getID()));
    calendar.set(2012, Calendar.OCTOBER, 27, 6, 0, 0);
    startDate= calendar.getTime();
    calendar.set(2012, Calendar.OCTOBER, 28, 6, 0, 0);
    endDate= calendar.getTime();

но в этом случае результат startDate - 27 октября, 9:00 CEST, а endDate - 28 октября, 8:00 CET, поэтому тест не пройден даже с нашей стороны.

Заранее спасибо.

3 ответа

Решение

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

Часовой пояс, который вы использовали на сервере в своем примере

TimeZone zone = TimeZone.getTimeZone("GMT");

не использует дневное время: zone.useDaylightTime() возвращается false,

Попробуйте использовать определенный часовой пояс, например,

TimeZone zone = TimeZone.getTimeZone("Europe/Chisinau");

имеет смещения летнего времени.

Вы также можете использовать часовые пояса в классах DateTime Joda Time и избегать использования классов Java Calendar и Date.

Укажите часовой пояс

Как мы можем разработать наш модульный тест независимо от часового пояса машины?

Всегда указывайте часовой пояс.

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

Если вы опустите часовой пояс, будет использован часовой пояс JVM по умолчанию.

У Joda-Time есть различные места, где вы можете пройти DateTimeZone объект или вы называете withZone метод.

Избегайте 3-4 буквенных кодов часовых поясов

Избегайте использования кодов часовых поясов, таких как CEST а также CET, Они не стандартизированы и не уникальны. И они часто используются по ошибке, когда путают / игнорируют переход на летнее время (DST).

Вместо этого используйте правильные имена часовых поясов. Большинство имен представляют собой комбинацию континента и крупного города зоны часового пояса, написанные простыми символами ASCII (без диакритических знаков). Например, Europe/Chisinau,

В Joda-Time это было бы...

DateTimeZone timeZone = DateTimeZone.forID( "Europe/Chisinau" );

Думать / использовать UTC

Ваша настоящая проблема - думать с точки зрения локальных значений даты и времени или пытаться выполнить бизнес-логику с местным временем. Это очень сложно из-за летнего времени в целом и его частых изменений наряду с другими аномалиями.

Вместо этого, подумайте о временном графике Вселенной, а не о настенных часах. Сохраняйте и обрабатывайте значения даты и времени как значения UTC (GMT), когда это возможно. Переведите в локальное зонированное время для представления пользователю (как вы бы локализовали строки) и, где это необходимо, для бизнес-целей, таких как определение начала "дня", определенного определенным часовым поясом.

24 часа против 1 дня

Итак, если вы действительно хотите 24 часа спустя, то добавьте 24 часа и позвольте времени настенных часов падать, где оно может. Если вы хотите "следующий день", то есть пользователь ожидает увидеть то же время на часах, то добавьте 1 день (который может оказаться спустя 25 часов). Joda-Time поддерживает оба вида логики.

Обратите внимание на приведенный ниже пример кода, который Joda-Time поддерживает либо для подсчета дня на 24 часа, либо для настройки времени на то же время на настенных часах из-за перехода на летнее время или из-за другой аномалии.

Пример кода

Вот пример кода с использованием Joda-Time 2.3. Новый пакет java.time в Java 8 должен иметь аналогичные возможности, как это было вдохновлено Joda-Time.

DateTimeZone timeZone = DateTimeZone.forID( "Europe/Chisinau" );
DateTime start = new DateTime( 2012, 10, 27, 6, 0, 0, timeZone );
DateTime stop = start.plusHours( 24 ); // Time will be an hour off because of Daylight Saving Time (DST) anomaly.

DateTime nextDay = start.plusDays( 1 ); // A different behavior (25 hours).

Interval interval = new Interval( start, stop );
Period period = new Period( start, stop );
int hoursBetween = Hours.hoursBetween( start, stop ).getHours();

DateTime startUtc = start.withZone( DateTimeZone.UTC );
DateTime stopUtc = stop.withZone( DateTimeZone.UTC );

Дамп на консоль…

System.out.println( "start: " + start );
System.out.println( "stop: " + stop );
System.out.println( "nextDay: " + nextDay );
System.out.println( "interval: " + interval );
System.out.println( "period: " + period );
System.out.println( "hoursBetween: " + hoursBetween );
System.out.println( "startUtc: " + startUtc );
System.out.println( "stopUtc: " + stopUtc );

Когда беги…

start: 2012-10-27T06:00:00.000+03:00
stop: 2012-10-28T05:00:00.000+02:00
nextDay: 2012-10-28T06:00:00.000+02:00
interval: 2012-10-27T06:00:00.000+03:00/2012-10-28T05:00:00.000+02:00
period: PT24H
hoursBetween: 24
startUtc: 2012-10-27T03:00:00.000Z
stopUtc: 2012-10-28T03:00:00.000Z

Почему бы не использовать обычный java.util.Calendar?

    final Calendar startTime = new GregorianCalendar( 2012, Calendar.OCTOBER, 27, 2, 12, 0 );
    startTime.setTimeZone( TimeZone.getTimeZone( strTimeZone ) );
    System.out.println( startTime.getTimeInMillis() );
Другие вопросы по тегам