从java.sql.Timestamp到joda的LocalDate的意外转换

时间:2012-06-21 20:32:04

标签: java calendar jodatime

我在将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的属性时,我注意到这个“问题”。

2 个答案:

答案 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日在西班牙要求当地约会时会发生什么......从未发生过这一天。)