为什么在1582之前将Java日期转换为使用Instant的LocalDate给出不同的日期?

考虑以下代码:

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 

我知道1582年是Julian和Gregorian日历之间的截止点。 我不知道为什么会发生这种情况,或者如何调整它。

这是我到目前为止所发现的:

  • 日期对象的BaseCalender设置为JulianCalendar
  • date.toInstant()只返回Instant.ofEpochMilli(getTime())
  • date.getTime()返回-14830974000000
  • -14830974000000是Wed,10 Jan 1500 05:00:00 GMT Gregorian

所以似乎getTime()返回的millis是错误的(不太可能)或者只是与我预期的不同而且我需要考虑差异。

LocalDate仅处理预感格里高利历 。 从它的javadoc :

ISO-8601日历系统是当今世界大部分地区使用的现代民用日历系统。 它等同于公历的格里高利历法系统,其中今天的闰年规则一直适用。 对于今天编写的大多数应用程序,ISO-8601规则是完全合适的。 但是,任何使用历史日期并要求它们准确的应用程序都会发现ISO-8601方法不合适。

相比之下,旧的java.util.GregorianCalendar类(它间接也用于toString() – java.util.Date输出)使用可配置的格里高利截止默认值为1582-10-15作为julian和julian之间的分隔日期。格里高利历日规。

因此, LocalDate不适用于任何历史日期。

但请记住,即使配置了正确的区域相关截止日期,即使java.util.GregorianCalendar经常失败。 例如,英国在1752年之前的3月25日开始了这一年。许多国家还有更多的历史偏差。 在欧洲之外甚至朱利安历法在引入格里高利历之前是不可用的(或者只能从殖民主义的角度来看最好)。

由于评论中的问题更新:

要解释值-14830974000000让我们考虑以下代码及其输出:

 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 

应该注意的是,由于America/New_YorkUTC之间的时区偏移差异,您之前评论中提到的值-12219292800000LtCutOver相差5小时。 因此,在时区EST(America / New_York),我们有30228天的差异。 对于有问题的时间跨度,我们应用朱利安历法规则,即每四年一次是闰年。

在1500和1582之间,我们有82 * 365天+ 21个闰日。 然后我们还要在1582-01-01和1582-10-01之间添加273天,最后4天直到切换(记住10月4日之后是10月15日)。 总计:82 * 365 + 21 + 273 + 4 = 30228(有待证实)。

请向我解释为什么你预期的值不同于-14830974000000 ms。 它看起来对我来说是正确的,因为它处理你的系统的时区偏移,1562年之前的朱利安日历规则以及从1582年10月4日到1582-10-15的截止日期的跳跃。 所以对我来说你的问题是“如何告诉日期对象将ms返回到正确的格里高利日期?” 已经回答 – 无需更正。 请记住,这些复杂的东西在生产中使用相当长,并且可以在这么多年后正常工作。

如果你真的想使用JSR-310那些东西,我再说一遍,不支持格里高利切换日期。 最棒的是你可以做自己的解决方案。

例如,您可能会考虑外部库Threeten-Extra ,它包含自0.9版以来的一个保护性朱利安日历。 但它仍然是你努力处理旧朱利安日历和新格里高利历之间的切换。 (并且由于许多其他原因,如新年开始等,不要指望这些图书馆能够处理真实的历史日期)

2017年更新:另一个更强大的选择是使用我的库Time4J的 HistoricCalendar ,它不仅仅处理julian / gregorian-cutover。