这确实与不同类型的日历有关。java.sql.Timestamp
, 比如java.util.Date
, 没有任何日历信息, 因为它们仅作为数字存储, 自 1970 年以来的毫秒数, 隐含假设在 1582 年 10 月 4 日和 15 月 15 日之间有一个跳跃, 当公历正式成为采用,取代旧的儒略历。因此,您不可能有一个代表 1582 年 10 月 7 日的日期(或时间戳)对象。如果您尝试创建这样的日期,您将自动在 10 天后结束。例如:
Calendar c = Calendar.getInstance();
c.setTimeZone(TimeZone.getTimeZone("UTC"));
c.set(1582, Calendar.OCTOBER, 7, 0, 0, 0);
c.set(Calendar.MILLISECOND, 0);
d = c.getTime();
System.out.println("Date: " + d);
// Prints:
// Date: Sun Oct 17 00:00:00 GMT 1582
换句话说, Date 对象有一个隐含的 Julian+Gregorian 年表,在这两者之间自动切换。
JodaTime 更聪明一点,它支持多种年表,包括连续的儒略历、预测的公历、混合的儒略历+公历,以及几乎与公历相同的标准 ISO 年表。如果您阅读该LocalDate.fromCalendarFields
方法的 JavaDoc ,您会看到它提到:
此工厂方法忽略日历的类型并始终创建具有 ISO 年表的 LocalDate。
混合 Julian+Gregorian 年表的行为类似于隐式 Java 日期,在两个不同的日历之间自动切换。纯年表假设他们的日历系统永远在使用,例如它假设公历从时间开始就一直在使用。
让我们看看每个 Chronology 如何处理 1111-11-11 日期:
Calendar c = Calendar.getInstance();
c.setTimeZone(TimeZone.getTimeZone("UTC"));
c.set(1111, Calendar.NOVEMBER, 11, 0, 0, 0);
c.set(Calendar.MILLISECOND, 0);
Date d = c.getTime();
System.out.println("Date: " + d + " (" + d.getTime() + " milliseconds)");
System.out.println("ISO: " + new DateTime(d, ISOChronology.getInstance(DateTimeZone.forID("UTC"))));
System.out.println("Julian+Gregorian: " + new DateTime(d, GJChronology.getInstance(DateTimeZone.forID("UTC"))));
System.out.println("Julian: " + new DateTime(d, JulianChronology.getInstance(DateTimeZone.forID("UTC"))));
System.out.println("Gregorian: " + new DateTime(d, GregorianChronology.getInstance(DateTimeZone.forID("UTC"))));
结束为:
Date: Sat Nov 11 00:00:00 GMT 1111 (-27079747200000 milliseconds)
ISO: 1111-11-18T00:00:00.000Z
Julian+Gregorian: 1111-11-11T00:00:00.000Z
Julian: 1111-11-11T00:00:00.000Z
Gregorian: 1111-11-18T00:00:00.000Z
如您所见,两个现代年表(ISO 和公历)报告正确的日期,如果它们从一开始就一直在使用,而使用儒略历的两个报告当时已知的日期,尽管在事后看来,与真正的春分日期相比,我们知道它要提前 7 天。
让我们看看交换机周围发生了什么:
c.set(1582, Calendar.OCTOBER, 15, 0, 0, 0);
d = c.getTime();
System.out.println("Date: " + d + " (" + d.getTime() + " milliseconds)");
System.out.println("ISO: " + new DateTime(d, ISOChronology.getInstance(DateTimeZone.forID("UTC"))));
System.out.println("Julian+Gregorian: " + new DateTime(d, GJChronology.getInstance(DateTimeZone.forID("UTC"))));
System.out.println("Julian: " + new DateTime(d, JulianChronology.getInstance(DateTimeZone.forID("UTC"))));
System.out.println("Gregorian: " + new DateTime(d, GregorianChronology.getInstance(DateTimeZone.forID("UTC"))));
最终为:
Date: Fri Oct 15 00:00:00 GMT 1582 (-12219292800000 milliseconds)
ISO: 1582-10-15T00:00:00.000Z
Julian+Gregorian: 1582-10-15T00:00:00.000Z
Julian: 1582-10-05T00:00:00.000Z
Gregorian: 1582-10-15T00:00:00.000Z
所以唯一留下的就是儒略历。在所有尚未接受公历的国家中,这都是一个有效的日期,当时有很多国家。希腊在 1923 年做出了转变……
在那之前的一毫秒,日期是:
c.add(Calendar.MILLISECOND, -1);
d = c.getTime();
System.out.println("Date: " + d + " (" + d.getTime() + " milliseconds)");
System.out.println("ISO: " + new DateTime(d, ISOChronology.getInstance(DateTimeZone.forID("UTC"))));
System.out.println("Julian+Gregorian: " + new DateTime(d, GJChronology.getInstance(DateTimeZone.forID("UTC"))));
System.out.println("Julian: " + new DateTime(d, JulianChronology.getInstance(DateTimeZone.forID("UTC"))));
System.out.println("Gregorian: " + new DateTime(d, GregorianChronology.getInstance(DateTimeZone.forID("UTC"))));
意义:
Date: Thu Oct 04 23:59:59 GMT 1582 (-12219292800001 milliseconds)
ISO: 1582-10-14T23:59:59.999Z
Julian+Gregorian: 1582-10-04T23:59:59.999Z
Julian: 1582-10-04T23:59:59.999Z
Gregorian: 1582-10-14T23:59:59.999Z
ISO 和公历年表报告的日期实际上并不存在于公历中,因为在 10 月 15 日之前没有公历,但该日期在延长的、预知的公历中有效。这就像找到一个刻在公元前纪念碑上的公元前日期......甚至在基督出生之前,没有人知道他们在基督之前。
因此,问题的根源在于日期字符串不明确,因为您不知道您正在测量哪个日历。5772 年是未来的一年,还是现在的希伯来年?Java 采用混合儒略历+公历。JodaTime 为不同的日历提供广泛的支持,默认情况下它采用 ISO8601 年表。您的日期会自动从 1111 年使用的儒略历转换为我们当前使用的 ISO 年表。如果您希望您的 JodaTime 增强时间戳使用与类相同的年表,则在构造 JodaTime 对象时java.sql.Timestamp
显式选择。GJChronology