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

1

5

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
Antitrades answered 27/1, 2023 at 5:20 Comment(4)
@Sweeper yes, correct. I fixed the spacing to make the comment more clearerAntitrades
Have you got a very good reason for needing an old-fashioned java.util.Date, like for a legacy API that you cannot upgrade? Otherwise you should simply avoid that old class. And TimeZone and SimpleDateFormat. java.time, the modern date and time API to which LocalDateTime belongs, gives you all the functionality you wish for.Similar
@OleV.V. It is for a bridge type approach for migrating from java.util to the new java.time, not all codes can be modified at once so as an intermediate solution.Antitrades
Then I know exactly what you are talking about. Thanks for context.Similar
H
6

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.

Hooge answered 27/1, 2023 at 6:11 Comment(4)
I think the offset may be odd but why does the error only happens on old dates like 1900-01-01 00:00:00 it is ok. I will add that point to the question. I was not able to pin point exactly what date it started happening, before I assumed it was because of Julian to Gregorian Calendar switch but it does not happen in UTC. I think your answer is still applicable but I had to dig deeper.Antitrades
See my update on the question, did you see diff between 1800 and 1900? so it is not always disagree, now wondering why it disagree at old date? not sure when it started, do you have some links like documentationAntitrades
@Antitrades it seems like Date does not know about local mean times, so they would start agreeing when Japan (or whatever timezone you’re working with) started to standardise their timezones. I guess in this case they did it before 1900, but after 1800.Hooge
@Antitrades I have added a bug report about TimeZone not supporting local mean time. They won't fix it because of compatibility.Hooge

© 2022 - 2024 — McMap. All rights reserved.