Stanislav Bashkyrtsev Stanislav Bashkyrtsev - 5 months ago 48
Java Question

ZonedDateTime & LocalDateTime are different for 100 years ago

The behaviour that I see is very strange - sometimes

LocalDateTime
would be equal to
ZonedDateTime
, other times it will differ by 1 hour or 2 and sometimes it's 30 minutes. All these strange differences depend on the year that I subtract. Can someone explain what's happening? Tried
jdk1.8.0_65
and
jdk1.8.0_91
,
MacOS 10.11.5
. I work with UTC:

ZoneOffset offset = ZoneOffset.UTC;


Here are some experiments. For 1919 values may differ by nano or milliseconds, which is expected:

assertEquals(
LocalDateTime.now(offset).minusYears(85).toInstant(offset),
ZonedDateTime.now().minusYears(85).withZoneSameInstant(offset).toInstant());


For 1919 it's 1 hour difference:

assertEquals(
LocalDateTime.now(offset).minusYears(86).toInstant(offset),
ZonedDateTime.now().minusYears(86).withZoneSameInstant(offset).toInstant());

Expected :<1930-05-28T20:19:10.383Z>
Actual :<1930-05-28T21:19:10.383Z>


For 1920 it's 2 hours difference:

assertEquals(
LocalDateTime.now(offset).minusYears(95).toInstant(offset),
ZonedDateTime.now().minusYears(95).withZoneSameInstant(offset).toInstant());

Expected :<1921-05-28T20:21:45.094Z>
Actual :<1921-05-28T18:21:45.094Z>


For 1921 again milli or nano seconds difference:

assertEquals(
LocalDateTime.now(offset).minusYears(96).toInstant(offset),
ZonedDateTime.now().minusYears(96).withZoneSameInstant(offset).toInstant());


And the weirdest of all - 1930 year with 30 mins difference:

assertEquals(
LocalDateTime.now(offset).minusYears(97).toInstant(offset),
ZonedDateTime.now().minusYears(97).withZoneSameInstant(offset).toInstant());

Expected :<1919-05-28T20:24:27.345Z>
Actual :<1919-05-28T19:53:08.346Z>


Update



As @Tunaki pointed I had to specify the offset for the
ZonedDateTime
:

assertEquals(
LocalDateTime.now(offset).minusYears(95).toInstant(offset),
ZonedDateTime.now(offset).minusYears(95).withZoneSameInstant(offset).toInstant());

Answer

The problem is that this doesn't know about Time Zones: LocalDateTime.now(offset).minusYears(97).toInstant(offset). There is only offset present. But this knows about the time zone: ZonedDateTime.now().minusYears(97).toInstant()

  • ZoneId contains information about the place and the time difference at that place. It knows that N years ago in that particular time zone the offset was 2 hours, not 3 as it is now.
  • ZoneOffset keeps track only about the hours/minutes shift. It doesn't know the history of time changes in a particular country. It just "adds hours".

The suggested (and a little bit incorrect) solution is to let ZonedDateTime to forget about zones and use offset instead: ZonedDateTime.now(offset).minusYears(97). This now would agree with LocalDateTime with the same offset - both would show same incorrect information. But they would agree since both "just add hours" instead of understanding the time difference at that point of history for that place:

ZoneOffset offset = ZoneId.of("Europe/Moscow").getRules().getOffset(Instant.now());
assertEquals(
    LocalDateTime.now(offset).minusYears(97).toInstant(offset),           
    ZonedDateTime.now(offset).minusYears(97).toInstant());

Alternatively we can set the LocalDateTime to show the correct value for that time for that place:

ZonedDateTime zoned = ZonedDateTime.now(ZoneId.of("Europe/Moscow")).minusYears(97);
ZoneOffset offset = zoned.getOffset();//agrees with history
assertEquals(
        LocalDateTime.now().minusYears(97).toInstant(offset),
        zoned.toInstant());

PS: This all shows that ZonedDateTime can work differently in different situations - sometimes it knows about time zones, other times it just "adds hours" as you would do with LocalDateTime by setting the offset manually. To me this is a weird implementation. JodaTime probably is still the best Java implementation. At least you don't have to learn it for several days to understand it.

Comments