Joda-Time, переход на летнее время и разбор даты - PullRequest
6 голосов
/ 23 октября 2010

У меня следующая проблема с использованием Joda-Time для анализа и создания даты и времени около Летнее время (DST) часов.Вот пример (обратите внимание, что 30 марта 2008 года - изменение летнего времени в Италии):

DateTimeFormatter dtf = DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss");
DateTime x = dtf.parseDateTime("30/03/2008 03:00:00");
int h = x.getHourOfDay();
System.out.println(h);
System.out.println(x.toString("dd/MM/yyyy HH:mm:ss"));
DateTime y = x.toDateMidnight().toDateTime().plusHours(h);
System.out.println(y.getHourOfDay());
System.out.println(y.toString("dd/MM/yyyy HH:mm:ss"));

Я получаю следующий вывод:

3
30/03/2008 03:00:00
4
30/03/2008 04:00:00

Когда я анализирую час, я получаючас - 3. В моей структуре данных я сохраняю день, сохраняющий полуночное время, и затем у меня есть некоторое значение для каждого часа дня (0-23).Затем, когда я записываю дату, я заново вычисляю время полной даты, делая полночь плюс час.Когда я делаю 3 часа до полуночи, я получаю 04:00:00!И если я проанализирую это снова, я получу 4 часа!

Где моя ошибка?Есть ли способ получить час 2, когда я анализирую, или час три, когда я распечатываю?

Я также пытался создать вывод вручную:

String.format("%s %02d:00:00", date.toString("dd/MM/yyyy"), h);

, но в этом случае для2-й час, я выдаю 30/03/2008 02:00:00, который не является действительной датой (поскольку 2-й час не существует) и больше не может быть проанализирован.

Заранее благодарю за помощь.Filippo

Ответы [ 3 ]

5 голосов
/ 23 октября 2010

Когда я складываю 3 часа до полуночи, я получаю 04:00:00!И если я проанализирую это снова, я получу 4 часа!Где моя ошибка?

Вы уже упоминали, что эта дата точно в то время, когда время меняется.Так что нет ошибки.30 марта 2010 г. 00:00 CEST (часовой пояс в Италии) точно говорит 29 марта 2010 г. 23:00 UTC.При добавлении 3 часов вы получите 30 марта 2010 года 02:00 UTC.Но это после того момента, когда мы меняем время (что происходит в 01:00 UTC), поэтому, когда вы переводите время в местный часовой пояс, вы получаете 30 марта 04:00.Это правильное поведение.

Есть ли способ получить час 2, когда я анализирую, или час три, когда я печатаю?

Нет, потому что 30 марта 2010 г. 02: 00 CEST не существует .Точно в 30 марта 2010 года в 01:00 UTC мы переключаем время с +1 часа на +2 часа по сравнению с UTC, поэтому 30 марта 2010 года 00:59 UTC - 30 марта 2010 года: 01:59 CEST, но 30 марта 2010 года 01: 00 UTC становится 30 марта 2010 года 03:00 CEST.Нет 02: хх час существует в эту конкретную дату.

Кстати.Через неделю можно ожидать еще одного «веселья».Можете ли вы сказать, к какой дате в UTC это относится:

31 октября 2010 г. 02:15 CEST?

Что ж, самое смешное, мы не знаем .Это может быть либо 31 октября 2010 г. 00:15 UTC (до фактического переключения времени), либо 31 октября 2010 г. 01:15 UTC (после переключения).

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

HTH.

4 голосов
/ 23 октября 2010

Структура данных, которую вы сохраняете, не очень оптимальна для дней с летним временем.Ваш день в этот конкретный день должен иметь только 23 часа.

Если вы сделаете:

    DateTimeFormatter dtf = DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss").withLocale(Locale.US);
    DateTime x = dtf.parseDateTime("30/03/2008 00:00:00");

    DateTimeFormatter parser = DateTimeFormat.fullDateTime();
    System.out.println("Start:"+parser.print(x));

    DateTime y = x.plusHours(4);

    System.out.println("After add of 4:"+parser.print(y));

Вы получите ожидаемый результат, время которого 05:00.

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

Вы можете сделать что-то вроде этого: В случае, когда мы перемещаем время на один час вперед, как в этом случае, вы должны сохранить 4 ине 5 как время для 5. И когда вы вычисляете время, вы должны использовать метод plusHours (), чтобы получить фактическое время.Я думаю, что вы могли бы сойти с рук что-то вроде:

public class DateTest {
    private static final int HOUR_TO_TEST = 2;

  public static void main(String[] args) {
    DateTimeFormatter dtf = DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss");
    DateTime startOfDay = dtf.parseDateTime("30/03/2008 00:00:00");

    /* Obtained from new DateTime() in code in practice */
    DateTime actualTimeWhenStoring = startOfDay.plusHours(HOUR_TO_TEST);

    int hourOfDay = actualTimeWhenStoring.getHourOfDay();
    int hourOffset = startOfDay.plusHours(hourOfDay).getHourOfDay();

    System.out.println("Hour of day:" + hourOfDay);
    System.out.println("Offset hour:" + hourOffset);

    int timeToSave = hourOfDay;
    if (hourOffset != hourOfDay) {
        timeToSave = (hourOfDay + (hourOfDay - hourOffset));
    }
    System.out.println("Time to save:" + timeToSave);

    /* When obtaining from db: */
    DateTime recalculatedTime = startOfDay.plusHours(timeToSave);

    System.out.println("Hour of time 'read' from db:" + recalculatedTime.getHourOfDay());
  }
}

... или в основном что-то подобное.Я бы написал тест на это, если вы решите пойти по этому маршруту.Вы можете изменить HOUR_TO_TEST, чтобы увидеть, что он перемещается с переходом на летнее время.

1 голос
/ 01 ноября 2013

Основываясь на правильных ответах Павла Диды и Кнубо…

ISO 8601 Для формата строки

Вы никогда не должны хранить ( сериализовать ) дату-время какстрока в указанном вами формате: "30/03/2008 03:00:00".Проблемы:

  • Пропущенный часовой пояс.
  • День, месяц, год Порядок неоднозначен.
  • Должен быть переведен во время UTC.

Если вам необходимо сериализовать значение даты-времени в текст, используйте надежный формат.Очевидным выбором является стандартный формат ISO 8601 .Еще лучше преобразовать местное время в часовой пояс UTC (Зулу) и затем в формат ISO 8601.Например: 2013-11-01T04:48:53.044Z

Нет полуночи

Методы midnight в Joda-Time устарели в пользу метода Joda-Time withTimeAtStartOfDay() (см. doc ).В некоторые дни не имеют полуночи .

Пример кода в Joda-Time 2.3

Некоторые комментарии об этом исходном коде:

    // © 2013 Basil Bourque. This source code may be used freely forevery by anyone taking full responsibility for doing so.

    // Joda-Time - The popular alternative to Sun/Oracle's notoriously bad date, time, and calendar classes bundled with Java 7 and earlier.
    // http://www.joda.org/joda-time/

    // Joda-Time will become outmoded by the JSR 310 Date and Time API introduced in Java 8.
    // JSR 310 was inspired by Joda-Time but is not directly based on it.
    // http://jcp.org/en/jsr/detail?id=310

    // By default, Joda-Time produces strings in the standard ISO 8601 format.
    // https://en.wikipedia.org/wiki/ISO_8601

Примерпоказывает 23 часа в день летнего времени (летнее время) в Риме, Италия, в то время как на следующий день имеет 24 часа.Обратите внимание, что указан часовой пояс (для Рима).

    // Time Zone list: http://joda-time.sourceforge.net/timezones.html
    org.joda.time.DateTimeZone romeTimeZone = org.joda.time.DateTimeZone.forID("Europe/Rome");
    org.joda.time.DateTime dayOfDstChange = new org.joda.time.DateTime( 2008, 3, 30, 0, 0, romeTimeZone ) ; // Day when DST
    org.joda.time.DateTime dayAfter = dayOfDstChange.plusDays(1);

    // How many hours in this day? Should be 23 rather than 24 on day of Daylight Saving Time "springing ahead" to lose one hour.
    org.joda.time.Hours hoursObjectForDay = org.joda.time.Hours.hoursBetween(dayOfDstChange.withTimeAtStartOfDay(), dayAfter.withTimeAtStartOfDay());
    System.out.println( "Expect 23 hours, got: " + hoursObjectForDay.getHours() ); // Extract an int from object.

    // What time is 3 hours after midnight on day of DST change?
    org.joda.time.DateTime threeHoursAfterMidnightOnDayOfDst = dayOfDstChange.withTimeAtStartOfDay().plusHours(3);
    System.out.println( "Expect 4 AM (04:00) for threeHoursAfterMidnightOnDayOfDst: " + threeHoursAfterMidnightOnDayOfDst );

    // What time is 3 hours after midnight on day _after_ DST change?
    org.joda.time.DateTime threeHoursAfterMidnightOnDayAfterDst = dayAfter.withTimeAtStartOfDay().plusHours(3);
    System.out.println( "Expect 3 AM (03:00) for threeHoursAfterMidnightOnDayAfterDst: " + threeHoursAfterMidnightOnDayAfterDst );

Пример сохранения даты-времени при первом переводе в UTC.Затем, восстановив объект даты и времени, установите нужный часовой пояс.

    // Serialize DateTime object to text.
    org.joda.time.DateTimeZone romeTimeZone = org.joda.time.DateTimeZone.forID("Europe/Rome");
    org.joda.time.DateTime dayOfDstChangeAtThreeHoursAfterMidnight = new org.joda.time.DateTime( 2008, 3, 30, 0, 0, romeTimeZone ).withTimeAtStartOfDay().plusHours(3);
    System.out.println("dayOfDstChangeAtThreeHoursAfterMidnight: " + dayOfDstChangeAtThreeHoursAfterMidnight);
    // Usually best to first change to UTC (Zulu) time when serializing.
    String dateTimeSerialized = dayOfDstChangeAtThreeHoursAfterMidnight.toDateTime( org.joda.time.DateTimeZone.UTC ).toString();
    System.out.println( "dateTimeBeingSerialized: " + dateTimeSerialized );
    // Restore
    org.joda.time.DateTime restoredDateTime = org.joda.time.DateTime.parse( dateTimeSerialized );
    System.out.println( "restoredDateTime: " + restoredDateTime );
    // Adjust to Rome Italy time zone.
    org.joda.time.DateTime restoredDateTimeAdjustedToRomeItaly = restoredDateTime.toDateTime(romeTimeZone);
    System.out.println( "restoredDateTimeAdjustedToRomeItaly: " + restoredDateTimeAdjustedToRomeItaly );

При запуске:

dayOfDstChangeAtThreeHoursAfterMidnight: 2008-03-30T04:00:00.000+02:00
dateTimeBeingSerialized: 2008-03-30T02:00:00.000Z
restoredDateTime: 2008-03-30T02:00:00.000Z
restoredDateTimeAdjustedToRomeItaly: 2008-03-30T04:00:00.000+02:00
...