0

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());
Stanislav Bashkyrtsev
  • 14,470
  • 7
  • 42
  • 45
  • 2
    That's because the rules for daylight savings time have changed many times in different countries over the years. The differences happen because the daylight savings correction is done relative to different moments (for example "now" vs. some date in the past). – Jesper May 28 '16 at 20:42
  • 3
    There is no bug, construct your `ZonedDateTime` with `ZonedDateTime.now(offset)` instead. See also http://stackoverflow.com/questions/33100130/getting-current-time-in-java-8/ – Tunaki May 28 '16 at 20:42
  • @Jesper this doesn't explain why Zoned and Local classes behave differently. I'd assume they both invoke the same mechanisms. – Stanislav Bashkyrtsev May 28 '16 at 20:50
  • @Tunaki With the offset for the Zoned date the picture differs, but the difference still exists. – Stanislav Bashkyrtsev May 28 '16 at 20:51
  • @StanislavBashkyrtsev Hmm the output of all your test cases are the same for me when making this change. Which difference is still there? – Tunaki May 28 '16 at 20:52
  • So let's take this one `ZonedDateTime.now(offset).minusYears(95).withZoneSameInstant(offset).toInstant())` it differs by 31 min to what `LocalDateTime` shows. – Stanislav Bashkyrtsev May 28 '16 at 20:53
  • What is your system locale? I cannot reproduce this 30 minutes difference with the snippet above. `LocalDateTime.now(offset).minusYears(95).toInstant(offset)` and `ZonedDateTime.now(offset).minusYears(95).toInstant()` both output the same value. – Tunaki May 28 '16 at 20:56
  • @Tunaki, it's `Europe/Moscow`, but should it matter? I pass the time zone offset to both of the dates. I updated the question with the example at the bottom to include the offset for the Zoned date. – Stanislav Bashkyrtsev May 28 '16 at 20:58
  • No, it shouldn't... But I can't reproduce your output. What is your Java version then? I'm on 1.8.0_74. – Tunaki May 28 '16 at 21:01
  • @Tunaki, my bad - didn't comment out another line. With your suggestion it started to work. Would you write a full-blow answer? I still have troubles understanding why it works this way since Zoned date has information about timezone anyway. – Stanislav Bashkyrtsev May 28 '16 at 21:16
  • Well, I think [this answer of mine](http://stackoverflow.com/a/33100290/1743880) covers the issue pretty well. Is there something in it that troubles you? – Tunaki May 28 '16 at 21:25
  • @Tunaki, I looked at your advice, but I also dug further and found that it can be a little bit incorrect. I posted a full answer below. Thanks for your help! – Stanislav Bashkyrtsev Jun 04 '16 at 07:06

2 Answers2

2

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.

Stanislav Bashkyrtsev
  • 14,470
  • 7
  • 42
  • 45
  • When doing this kind of work, and making examples, you should be specify an explicit time zone or offset rather than calling `systemDefault`. Others cannot verify your findings if you fail to disclose your parameters. – Basil Bourque Jun 04 '16 at 07:20
  • Usually you use `OffsetDateTime` with a `ZoneOffset`, and a `ZonedDateTime` with `ZoneId`. You seem to be expecting too much from these calculations: date-time work is tricky, time zones are messy, the [tz database](https://en.wikipedia.org/wiki/Tz_database) is [not fully detailed and accurate before 1970](https://en.wikipedia.org/wiki/Tz_database#Data_before_1970). I do not understand why you use the word "incorrect" for the difference between offset-only versus time zone calculations after you yourself explain they are not the same. – Basil Bourque Jun 04 '16 at 07:32
  • I say "incorrect" because in my case it shows the time that was actually different at that point of history. That's because I tried to use the same offset for now() and for 100 years ago. – Stanislav Bashkyrtsev Jun 04 '16 at 07:46
0

Since no one is answering this...

You have to send in offset when creating the ZonedDateTime as well. Otherwise it will use Clock.systemDefaultZone() and you get a difference in timezones.

ZonedDateTime.now(offset).minusYears(95).withZoneSameInstant(offset).toInstant()
softarn
  • 5,327
  • 3
  • 40
  • 54
  • No, as it appeared - you don't have to. Moreover - setting the offset for ZonedDateTime would erase information about the Time Zone which means it can potentially show the wrong information depending on the point in history and the place. – Stanislav Bashkyrtsev Jun 04 '16 at 07:08