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

Ric*_* II 8 java datetime date java-8 java-time

考虑以下代码:

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
Run Code Online (Sandbox Code Playgroud)

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

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

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

所以似乎要么返回的毫秒getTime()是错误的(不太可能),要么与我预期的不同,我需要考虑差异.

Men*_*ild 16

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

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

相反,旧java.util.GregorianCalendar类(间接也用于toString() - 输出java.util.Date)使用可配置的格里高利截止值默认为1582-10-15作为朱利安和格里高利历法规则之间的分离日期.

所以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
Run Code Online (Sandbox Code Playgroud)

应该注意的-12219292800000L是,tCutOver由于America/New_York和之间的时区偏移差异,您之前评论中提到的值相差5小时UTC.所以在时区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版以来的一个预防性 julian日历.但它仍然是你努力处理旧朱利安日历和新格里高利历之间的切换.(并且由于许多其他原因,例如新年开始等,不要指望这些图书馆能够处理真实的历史日期)

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