Java 8 date-time: получить начало дня из ZonedDateTime

Есть ли разница между ними:

zonedDateTime.truncatedTo(ChronoUnit.DAYS);

zonedDateTime.toLocalDate().atStartOfDay(zonedDateTime.getZone());

Любые причины предпочесть друг другу?

Спасибо

Ответ 1

Обновлено для исправления:

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

ZonedDateTime zdt = 
  ZonedDateTime.of(2015, 10, 18, 0, 30, 0, 0, 
    ZoneId.of("America/Sao_Paulo")); // switch to summer time
ZonedDateTime zdt1 = zdt.truncatedTo(ChronoUnit.DAYS);
ZonedDateTime zdt2 = zdt.toLocalDate().atStartOfDay(zdt.getZone());

System.out.println(zdt); // 2015-10-18T01:30-02:00[America/Sao_Paulo]
System.out.println(zdt1); // 2015-10-18T01:00-02:00[America/Sao_Paulo]
System.out.println(zdt2); // 2015-10-18T01:00-02:00[America/Sao_Paulo]

Усечение происходит на локальной временной шкале. Если вы выберете DAYS, тогда вы выбираете полночь. Согласно javadoc метод truncate() наконец преобразуется в новый ZonedDateTime и сдвигает время вперед на размер разрыва (1 час).

Преобразование zdt сначала в LocalDate (отключение временной части), а затем поиск его ZonedDateTime -part в заданном часовом поясе для этой ситуации фактически одинаково.

Однако для обратного случая перехода с летнего времени на зимнее время существует одно исключение (очень спасибо @Austin, который дал пример счетчика). Проблема заключается в перекрытии, когда нужно решить, какое смещение использовать. Обычно класс ZonedDateTime разработан/задан для использования предыдущего смещения, см. Также этот отрывок из Javadoc:

Для Overlaps общая стратегия заключается в том, что если локальная дата-время попадает в середину перекрытия, тогда предыдущее смещение будет сохраняется. Если нет предыдущего смещения, или предыдущее смещение недействительным, то используется более раннее смещение, обычно "летнее" время.

Если класс ZonedDateTime, следовательно, будет следовать своей собственной спецификации, то обе процедуры будут по-прежнему эквивалентны:

zdt.truncatedTo(ChronoUnit.DAYS);

должен быть эквивалентен

zdt.toLocalDate().atStartOfDay().atZone(zdt.getZone()).withEarlierOffsetAtOverlap();

Но реальное поведение в соответствии с примером @Austin и подтвержденное мной в собственном тестировании:

zdt.toLocalDate().atStartOfDay().atZone(zdt.getZone()).withLaterOffsetAtOverlap();

Похож на скрытую несогласованность в классе ZonedDateTime, мягко говоря. Если вы спросите меня, какой метод будет предпочтительнее, я предпочел бы защищать второй метод, хотя он намного длиннее и требует больше нажатий клавиш. Но у него есть большое преимущество, чтобы быть более прозрачным в отношении того, что он делает. Еще одна причина предпочесть второй подход:

Он действительно получает ПЕРВЫЙ момент, когда местное время равно началу дня. В противном случае при использовании первого метода вам необходимо написать:

zdt.truncatedTo(ChronoUnit.DAYS).withEarlierOffsetAtOverlap();

Ответ 2

Они немного разные. Согласно javadocs, truncatedTo() будет пытаться сохранить часовой пояс в случае перекрытия, но atStartOfDay() найдет первое появление в полночь.

Например, Куба восстанавливает дневное сбережение в 1 утра, возвращаясь к 12 часам утра. Если вы начинаете с момента после этого перехода, atStartOfDay() вернет первое появление 12am, а truncatedTo() вернет второе появление.

ZonedDateTime zdt = ZonedDateTime.of(2016, 11, 6, 2, 0, 0, 0, ZoneId.of("America/Havana"));
ZonedDateTime zdt1 = zdt.truncatedTo(ChronoUnit.DAYS);
ZonedDateTime zdt2 = zdt.toLocalDate().atStartOfDay(zdt.getZone());

System.out.println(zdt);  // 2016-11-06T02:00-05:00[America/Havana]
System.out.println(zdt1); // 2016-11-06T00:00-05:00[America/Havana]
System.out.println(zdt2); // 2016-11-06T00:00-04:00[America/Havana]

Ответ 3

Обратите внимание на другой способ сделать это:

zonedDateTime.with(LocalTime.MIN);

который дает тот же результат, что и truncatedTo