Why does converting Java Dates before 1582 to Loca

2019-02-13 13:15发布

问题:

Consider this code:

Date date = new SimpleDateFormat("MMddyyyy").parse("01011500");

LocalDate localDateRight = LocalDate.parse(formatter.format(date), dateFormatter);
LocalDate localDateWrong = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()).toLocalDate();

System.out.println(date);           // Wed Jan 01 00:00:00 EST 1500
System.out.println(localDateRight); // 1500-01-01
System.out.println(localDateWrong); // 1500-01-10

I know that 1582 is the cutoff between the Julian and Gregorian calendars. What I don't know is why this happens, or how to adjust for it.

Here's what I've figured out so far:

  • The date Object has a BaseCalender set to JulianCalendar
  • date.toInstant() just returns Instant.ofEpochMilli(getTime())
  • date.getTime() returns -14830974000000
  • -14830974000000 is Wed, 10 Jan 1500 05:00:00 GMT Gregorian

So it seems like either the millis returned by getTime() is wrong (unlikely) or just different than I expect and I need to account for the difference.

回答1:

LocalDate handles the proleptic gregorian calendar only. From its javadoc:

The ISO-8601 calendar system is the modern civil calendar system used today in most of the world. It is equivalent to the proleptic Gregorian calendar system, in which today's rules for leap years are applied for all time. For most applications written today, the ISO-8601 rules are entirely suitable. However, any application that makes use of historical dates, and requires them to be accurate will find the ISO-8601 approach unsuitable.

In contrast, the old java.util.GregorianCalendar class (which is indirectly also used in toString()-output of java.util.Date) uses a configurable gregorian cut-off defaulting to 1582-10-15 as separation date between julian and gregorian calendar rules.

So LocalDate is not useable for any kind of historical dates.

But bear in mind that even java.util.GregorianCalendar often fails even when configured with correct region-dependent cut-off date. For example UK started the year on March 25th before 1752. And there are many more historical deviations in many countries. Outside of Europe even the julian calendar is not useable before introduction of gregorian calendar (or best useable only from a colonialist perspective).

UPDATE due to questions in comment:

To explain the value -14830974000000 let's consider following code and its output:

SimpleDateFormat format = new SimpleDateFormat("MMddyyyy", Locale.US);
format.setTimeZone(TimeZone.getTimeZone("America/New_York"));
Date d = format.parse("01011500");

long t1500 = d.getTime();
long tCutOver = format.parse("10151582").getTime(); 
System.out.println(t1500); // -14830974000000
System.out.println(tCutOver); // default gregorian cut off day in "epoch millis"
System.out.println((tCutOver - t1500) / 1000); // output: 2611699200 = 30228 * 86400

It should be noted that the value -12219292800000L mentioned in your earlier comment is different by 5 hours from tCutOver due to timezone offset difference between America/New_York and UTC. So in timezone EST (America/New_York) we have exactly 30228 days difference. For the timespan in question we apply the rules of julian calendar that is every fourth year is a leap year.

Between 1500 and 1582 we have 82 * 365 days + 21 leap days. Then we have also to add 273 days between 1582-01-01 and 1582-10-01, finally 4 days until cut-over (remember 4th of Oct is followed by 15th of Oct). At total: 82 * 365 + 21 + 273 + 4 = 30228 (what was to be proved).

Please explain to me why you have expected a value different from -14830974000000 ms. It looks correct for me since it handles the timezone offset of your system, the julian calendar rules before 1582 and the jump from 4th of Oct 1582 to cut-over date 1582-10-15. So for me your question "how do I tell the date object to return the ms to the correct Gregorian date?" is already answered - no correction needed. Keep in mind that this complex stuff is a pretty long time in production use and can be expected to work correctly after so many years.

If you really want to use JSR-310 for that stuff I repeat that there is no support for gregorian cut-over date. The best thing is that you might do your own work-around.

For example you might consider the external library Threeten-Extra which contains a proleptic julian calendar since release 0.9. But it will still be your effort to handle the cut-over between old julian calendar and new gregorian calendar. (And don't expect such libraries to be capable of handling REAL historic dates due to many other reasons like new year start etc.)

Update in year 2017: Another more powerful option would be using HistoricCalendar of my library Time4J which handles much more than just julian/gregorian-cutover.