我在将Timestamp对象转换为joda的LocalTime时出现问题。
见下面的例子:
public static void main(String[] args) {
Timestamp t = Timestamp.valueOf("1111-11-11 00:00:00");
System.out.println(t); //-- prints '1111-11-11 00:00:00.0'
System.out.println(new LocalDate(t)); //-- prints '1111-11-17'
Calendar calendar = Calendar.getInstance();
calendar.setTime(t);
System.out.println(LocalDate.fromCalendarFields(calendar)); //-- prints '1111-11-11'
}
我无法确定为什么'new LocalDate(t)'会导致'1111-11-17'。谁能帮助我呢?
在使用joda-time-hibernate填充我的bean类型为LocalDate的属性时,我注意到这个“问题”。
答案 0 :(得分:5)
这确实与不同类型的日历有关。与java.sql.Timestamp
一样,java.util.Date
没有任何日历信息,因为它们仅作为数字存储,即1970年以来的毫秒数,隐含的假设是10月4日之间有一个跳跃1582年10月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有点聪明,它支持几个年表,包括一个持续的朱利安,一个proleptic格里高利,一个混合朱利安+格里高利,以及一个标准的ISO年代,几乎与格里高利相同。如果您阅读the JavaDoc of the LocalDate.fromCalendarFields
method,您会看到它提到:
此工厂方法忽略日历的类型,并始终使用ISO年表创建LocalDate。
混合Julian + Gregorian年表的行为类似于隐式Java日期,在两个不同的日历之间自动切换。纯粹的年代表假设他们的日历系统永远在使用,所以例如它假设从一开始就使用了公历。
让我们看看每个年表如何对待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和Gregorian)报告正确的日期如果它们从一开始就一直在使用,而使用Julian日历的两个报告日期,因为它当时已知,虽然事后我们知道它与真正的春分日期相比将会被关闭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日之前没有格里高利历,但这个日期在延长的,公历的格里高利历中是有效的。这就像在BCE纪念碑上找到一个BCE日期......在基督出生之前,没有人知道他们是在基督之前。
因此,问题的根源是日期字符串不明确,因为您不知道您正在测量哪个日历。未来一年是5772年,还是现在的希伯来年? Java假定混合Julian + Gregorian日历。 JodaTime为不同的日历提供广泛的支持,默认情况下它采用ISO8601年表。您的日期会自动从1111中使用的Julian日历转换为我们当前使用的ISO年表。如果您希望JodaTime增强的时间戳使用与java.sql.Timestamp
类相同的年表,则在构造JodaTime对象时显式选择GJChronology
。
答案 1 :(得分:0)
过去几个世纪以来发生这种情况的原因与(全球)从朱利安历法中切换到格里高利历有关。基本上,随着各国以零敲碎打的方式实施新的日历系统,它们将在此过程中损失数天或数周(在某些情况下,数月)。例如,在西班牙,1582年10月4日在朱利安历法中,紧接着是1582年10月15日格里高利历。 Wikipedia对该主题进行了不错的讨论。
只要您的申请过去没有处理过多的日期,您就不必处理与此相关的差异。正如您在评论中提到的,System.out.println(new LocalDate(Timestamp.valueOf("1999-11-11 00:00:00")))
正确输出了1999-11-11。如果您需要使用这些较旧的日期,我建议您查看Julian日历的替代日期。
(顺便说一句,我很想知道如果你在1582年10月5日在西班牙要求当地约会时会发生什么......从未发生过这一天。)