Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do ZonedDateTime and Calendar disagree on the hour in year 2050? [duplicate]

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?

like image 278
Alexander Avatar asked Feb 07 '20 08:02

Alexander


1 Answers

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 :)

like image 68
Sweeper Avatar answered Sep 17 '22 15:09

Sweeper