1

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

4

2 回答 2

5

这确实与不同类型的日历有关。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

于 2012-08-24T22:43:30.783 回答
0

过去几个世纪以来发生这种情况的原因与(全球)从儒略历转换到公历有关。基本上,随着各国以零碎的方式实施新的日历系统,他们将在此过程中损失数天或数周(在某些情况下,甚至数月)。例如,在西班牙,儒略历中的 1582 年 10 月 4 日紧随其后的是公历中的 1582 年 10 月 15 日。维基百科对该主题进行了不错的讨论。

只要您的应用程序没有处理过早的日期,您就不必处理与此相关的差异。正如您在评论中提到的,System.out.println(new LocalDate(Timestamp.valueOf("1999-11-11 00:00:00")))正确输出 1999-11-11。如果您需要使用这些较旧的日期,我建议您查看儒略历的替代日期。

(顺便说一句,我很想知道如果你询问当地的日期,在西班牙,1582 年 10 月 5 日……那一天会发生什么从未发生过。)

于 2012-06-22T18:54:54.987 回答