Java 8 date-time: get start of day from ZonedDateTime

JavaDatetimeJava 8Java Time

Java Problem Overview


Is there any difference between these:

zonedDateTime.truncatedTo(ChronoUnit.DAYS);

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

Any reason to prefer one against the other?

Thanks

Java Solutions


Solution 1 - Java

Updated for sake of correction:

In most cases yes the same, see following example for Brazil when switching from winter to summer time:

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]

Truncating happens on the local timeline. If you choose DAYS then you opt for midnight. According to javadoc the truncate()-method finally converts back to the new ZonedDateTime and shifts the time forward by the size of the gap (1 hour).

Converting the zdt first to LocalDate (cutting off the time part) and then looking for its ZonedDateTime-part in given timezone is effectively the same for this situation.

However, for the reverse case of switching back from summer time to winter time there is one exception (thanks very much to @Austin who gave a counter example). The problem is during overlap when to decide which offset to be used. Usually the class ZonedDateTime is designed/specified to use the previous offset, see also this excerpt from Javadoc:

> For Overlaps, the general strategy is that if the local date-time > falls in the middle of an Overlap, then the previous offset will be > retained. If there is no previous offset, or the previous offset is > invalid, then the earlier offset is used, typically "summer" time.

If the class ZonedDateTime would consequently follow its own specification then both procedures would still be equivalent meaning:

zdt.truncatedTo(ChronoUnit.DAYS);

should be equivalent to

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

But the real behaviour according to the example of @Austin and confirmed by me in own testing is:

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

Looks like a hidden inconsistency in the class ZonedDateTime, mildly spoken. If you ask me which method to be preferred then I would rather advocate the second method although it is much longer and requires more keystrokes. But it has the big advantage to be more transparent about what it does. Another reason to prefer the second approach is:

It really obtains the FIRST instant at which the local time is equal to the start of day. Otherwise, when using first method, you have to write:

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

Solution 2 - Java

They are slightly different. According to the javadocs, truncatedTo() will try to preserve the time zone in the case of overlap, but atStartOfDay() will find the first occurrence of midnight.

For example, Cuba reverts daylight savings at 1am, falling back to 12am. If you begin with a time after that transition, atStartOfDay() will return the first occurence of 12am, while truncatedTo() will return the second occurence.

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());
ZonedDateTime zdt3 = zdt.with(LocalTime.MIN);

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]
System.out.println(zdt3); // 2016-11-06T00:00-05:00[America/Havana]

Solution 3 - Java

Note there's also another way of doing it:

zonedDateTime.with(LocalTime.MIN);

which produces the same result as truncatedTo

Solution 4 - Java

zonedDateTime.truncatedTo(ChronoUnit.DAYS) is the superior option.

As Austin pointed out, toLocalDate() loses the zone information. From LocalDate documentation:

> A date without a time-zone in the ISO-8601 calendar system, such as > 2007-12-03. LocalDate is an immutable date-time object that represents > a date, often viewed as year-month-day. Other date fields, such as > day-of-year, day-of-week and week-of-year, can also be accessed. For > example, the value "2nd October 2007" can be stored in a LocalDate.

In addition to the example given by Austin, this can cause issues in the common case where development and deployment are on different machines.

As a concrete example, consider processing the orders received by a restaurant in New York from 11 AM EDT to 11 PM EDT. If the code is developed on machine on New York time, toLocalDate() will not show any errors. However, when the code is deployed on a server in the UTC time zone, it will be off after 8 PM (when EDT is 4 hours behind UTC).

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionNazaret K.View Question on Stackoverflow
Solution 1 - JavaMeno HochschildView Answer on Stackoverflow
Solution 2 - JavaAustinView Answer on Stackoverflow
Solution 3 - JavanevsterView Answer on Stackoverflow
Solution 4 - JavaDatanova ScientificView Answer on Stackoverflow