Nap
Nap

Reputation: 8286

Why does the LocalDateTime conversion to java.util.Date is shifting for very old date?

I am having trouble with converting old dates from java.time.LocalDateTime to java.util.Date

I tried a lot of variation and it still has the same shifted dates. I would assume that it is some weird calendar performed but it is failing.

Date to parse 1800-01-01 00:00:00

I used a very simple convert function.

Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());

TimeZone a.Converted via SimpleDateFormat b.Converted via DateFormatter to LocalDateTime to java.util.Date
Asia/Tokyo 1800-01-01 00:00:00 JST 1799-12-31 23:41:01 JST
Europe/Brussels 1800-01-01 00:00:00 CET 1800-01-01 00:04:30 CET
Australia/Sydney 1800-01-01 00:00:00 AEST 1799-12-31 23:55:08 AEST
UTC 1800-01-01 00:00:00 UTC 1800-01-01 00:00:00 UTC

a. Convert String to java.util.Date via SimpleDateFormat

b. Convert String to java.time.LocalDatetime via DateFormatter, then convert it to java.util.Date

Now I see it only works for the UTC timezone, I cannot just change the software timezone as it will mess with the others. Anyone know any other way to convert a java.time.LocalDateTime to java.util.Date for an old day?

TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

===== following is added after sweeper's answer to illustrate it is not for all ====

The curious thing is it only happens to really old dates, it does not happen to 1900-01-01 00:00:00 but have not check yet at which point the trouble started. I was thinking that maybe because of and adjustment / change at some point in 18XX year.

System.out.println(ZoneId.of("Asia/Tokyo").getRules().getOffset(LocalDateTime.parse("1800-01-01T00:00:00")));
SimpleDateFormat format1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
System.out.println(TimeZone.getTimeZone("Asia/Tokyo").getOffset(format1.parse("1800-01-01T00:00:00").getTime()));
        
System.out.println(ZoneId.of("Asia/Tokyo").getRules().getOffset(LocalDateTime.parse("1900-01-01T00:00:00")));
SimpleDateFormat format2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
System.out.println(TimeZone.getTimeZone("Asia/Tokyo").getOffset(format2.parse("1900-01-01T00:00:00").getTime()));

Results to

+09:18:59
32400000
+09:00
32400000

Upvotes: 5

Views: 1049

Answers (1)

Sweeper
Sweeper

Reputation: 274650

Similar to this question, the old API disagrees with ZoneId.systemDefault() about what offsets those locations should be at on the date 1800-01-01.

You can see this in action:

System.out.println(ZoneId.of("Asia/Tokyo").getRules().getOffset(LocalDateTime.parse("1800-01-01T00:00:00")));
var format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
System.out.println(TimeZone.getTimeZone("Asia/Tokyo").getOffset(format.parse("1800-01-01T00:00:00").getTime()));

Output:

+09:18:59
32400000

As said in the linked post, +09:18:59 is the Local Mean Time in Japan, and 32400000ms is exactly 9 hours. This is because the old APIs don't support Local Mean Time. Note that Japan standardised their timezones in 1888, which explains why the outputs from the two APIs are consistent for 1900-01-01.

So ZoneId thinks the offset of Asia/Tokyo is 18 minutes and 59 seconds more than what TimeZone (which is used by SimpleSateFormat and Date.toString and so on) thinks it is.

This is exactly why there is a "shift" in the output. ldt.atZone(...) generates

1800-01-01T00:00:00+09:18:59

and toInstant turns this into an instant.

Assuming you are using Date.toString or some other old API that uses TimeZone to generate the string, the old API would think this instant is at +09:00:00 instead! What is the above date at +09:00:00? Well, just subtract the 18 minutes and 59 seconds (just like how you would subtract 9 hours to convert a UTC+9 date to UTC+0)!

And that's how you get:

1799-12-31 23:41:01

As for solutions, there really is nothing wrong with the "shifted" Date that you got. If you just convert it back to a ZonedDateTime and format it with DateTimeFormatter for display, everything should work as normal. In fact, if you can, consider not converting to Date at all, and use Instants instead.

If you cannot do that, I would suggest that you should not mix the two APIs.

Upvotes: 6

Related Questions