9

Consider the following code:

ZoneId zoneId = ZoneId.of("America/Los_Angeles");
long currMillis = 2530778400000L;
Instant curr = Instant.ofEpochMilli(currMillis);
LocalDateTime dt = LocalDateTime.ofInstant(curr, zoneId); //the local one just for completeness
ZonedDateTime zdt = ZonedDateTime.ofInstant(curr, zoneId);
Calendar calendar = GregorianCalendar.from(zdt);

System.out.println(String.format("%-30s %s", "java-8 LocalDateTime hour:", dt.toLocalTime().getHour()));
System.out.println(String.format("%-30s %s", "java-8 ZonedDateTime hour:", zdt.toLocalTime().getHour()));
System.out.println(String.format("%-30s %s", "Calendar hour:", calendar.get(Calendar.HOUR_OF_DAY)));

Printed:

java-8 LocalDateTime hour:     3
java-8 ZonedDateTime hour:     3
Calendar hour:                 2

It seems that around this hour Calendar jumps from hour 2 to hour 4 (not necessarily a problem in general if it corresponds to DST change).

I am using AdoptOpenJDK 1.8.0_242, but I've also checked on HotSpot 1.8.0_181 - the same issue.

Why does Calendar report a different hour from ZonedDateTime?
Is this mismatch a known issue?
Whom should I trust more - ZonedDateTime or Calendar in this case?

Michael
  • 41,989
  • 11
  • 82
  • 128
Alexander
  • 2,761
  • 1
  • 28
  • 33

1 Answers1

8

Assuming that the rules (transition into DST happens on the first Sunday on or after 8 March at 02:00) don't change in 2050, that instant is one where a gap transition occurs (13 March), where the clocks jump from 01:59 to 03:00, so 02:00 doesn't actually exist. Calendar is quite wrong here.

You can further see how wrong Calendar is by checking what each of the timezone classes say about the instant in question. ZonedDateTime uses ZoneId, whereas Calendar uses TimeZone. I compared the outputs of various methods on ZoneId with that of the TimeZone counterparts, using this code:

ZoneId zoneId = ZoneId.of("America/Los_Angeles");
long currMillis = 2530778400000L;
Instant curr = Instant.ofEpochMilli(currMillis);
TimeZone tz = TimeZone.getTimeZone(zoneId);

// what's the actual offset at that instant?
System.out.println(zoneId.getRules().getOffset(curr).getTotalSeconds());
System.out.println(tz.getOffset(currMillis) / 1000);

// is DST observed at that instant?
System.out.println(zoneId.getRules().isDaylightSavings(curr));
System.out.println(tz.inDaylightTime(new Date(currMillis)));

// what's the standard offset at that instant?      
System.out.println(zoneId.getRules().getStandardOffset(curr).getTotalSeconds());
System.out.println(tz.getRawOffset() / 1000);

// how many seconds does DST add to the standard offset at that instant?
System.out.println(zoneId.getRules().getDaylightSavings(curr).getSeconds());
Calendar calendar = GregorianCalendar.from(ZonedDateTime.ofInstant(curr, zoneId));
System.out.println(calendar.get(Calendar.DST_OFFSET) / 1000);

The results are as follows:

-25200
-28800
true
true
-28800
-28800
3600
0

As you can see, both of them think that DST is observed, but TimeZone thinks DST adds 0 seconds to the standard offset, which makes it think that the actual offset is still -8 hours.

But who knows what happens in 30 years? Let's hope everyone gets rid of DST :)

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • 3
    Great Answer. But regarding your comment about getting rid of DST: as long as there are politicians, there *will* be changes to time zones. DST is only one reason for zone changes. History has shown other political, military, diplomatic, and cultural reasons for politicians to change their time zones. I strongly recommend all programmers learn to handle time zones in their code, and defensively program to always expect zone changes regardless of whether DST is currently an issue in their juridictions. – Basil Bourque Feb 07 '20 at 17:50
  • @BasilBourque Yeah, I know, right? Non-DST changes are much more messy than DST changes. That comment was only meant as a joke... – Sweeper Feb 07 '20 at 17:53
  • I am surprised, though. From 2038 `ZoneId` says that DST begins at 2 on the 2nd Sunday in March, whereas `TimeZone` says at 3 AM. I thought that they both relied on the same time zone database. @BasilBourque is correct, of course, it doesn’t matter since no one knows what the politicians end up deciding for 2038 and later anyway. – Ole V.V. Feb 07 '20 at 20:16
  • 2
    @OleV.V. I just looked at the `tzdata2019c` file for `northamerica`. I see no occurrence of `2038`. That you found a different that happens to start in the year 2038 makes me wonder if it might be a programming issue related to the 32-bit [Year 2038 problem](https://en.wikipedia.org/wiki/Year_2038_problem). – Basil Bourque Feb 08 '20 at 00:09
  • 1
    @BasilBourque I did notice the coincidence. [Meno Hochschield’s comment here](https://stackoverflow.com/questions/37551019/bug-in-jdk8-date-conversion#comment62610919_37554309) seems to tell us that it is more than a coincidence. – Ole V.V. Feb 08 '20 at 07:00
  • 2
    @OleV.V. So, yet *another* reason to avoid the legacy date-time classes? – Basil Bourque Feb 08 '20 at 07:16
  • 1
    @BasilBourque As if we didn’t have plenty already! Yes it is! – Ole V.V. Feb 08 '20 at 07:25