如何处理 java 中的负 ISO 8601 日期字符串?

How to handle negative ISO 8601 date string in java?

我正在尝试将 ISO 8601 日期字符串转换为纪元时间。我如何处理负日期?下面的代码是否正确?我应该使用其他东西而不是简单的日期格式库吗?负日期适用于 BC。

String formatString = "yyyy-MM-dd'T'hh:mm:ssX";
SimpleDateFormat formatter = new SimpleDateFormat(formatString);
Date date = formatter.parse("-2017-01-04T12:30:00+05:00");
System.out.println(date.getTime()/1000);

Answer: -125818806600L

TL;DR:不,您的代码不正确。是的,我建议使用现代 Java 日期和时间 API 而不是 SimpleDateFormat.

您的第一个问题是定义公元前几年(公元前,“基督之前”)的正确性。

据我了解 Wikipedia,ISO 8601 并未明确定义如何解释该范围内的日期。年份本身没有什么大问题:“0000 年等于公元前 1 年”,所以 -1 是公元前 2 年,-2017 是公元前 2018 年。您 可以 使用 proleptic Gregorian calendar,它是通过将 Gregorian 日历向后扩展到 1582 年正式引入之前的日期而产生的,但请注意,这与传统上使用的 Julian 日历不一致,所以当您的日期-time 字符串表示 1 月 4 日,这与历史书中的 1 月 4 日不同。 ISO 8601 也没有要求使用负年份和阳历,这只是双方之间的协议。预约:不知道历史书上有没有对公元前2018年1月4日的定义;我们也在引入儒略历之前(由朱利叶斯凯撒在公元前 46 年提出)。

The documentation of SimpleDateFormat 没有说明它如何处理引入公历之前的日期。它似乎依赖于与 date/time 格式化程序关联的 Calendar 对象。这样的 Calendar 对象在大多数计算机和 JVM 上都是 GregorianCalendar,但并非总是如此。所以我认为不能保证您的代码的输出在所有计算机上都相同。 GregorianCalendar 可以并且通常确实处理儒略历中教皇格雷戈尔之前的日期,所以我希望你得到的结果 确实 与历史书籍一致,但不与 ISO 8601 一起确定哪一天是公元前 2018 年 1 月 4 日。所以基于这些理由我怀疑你的结果不正确。

作为测试,我将您代码的输出与类似使用 Java 日期和时间 API 的输出进行了比较。 运行 你的代码我也收到了 -125818806600。所以我尝试了:

    System.out.println(OffsetDateTime.parse("-2017-01-04T12:30:00+05:00")
            .toInstant()
            .getEpochSecond());

这些 类 应该符合 ISO 8601 标准,所以我更喜欢这个代码而不是你的代码(它也更简单一些)。我得到了

-125817294600

不一样,所以另一个迹象表明你的代码没有给出正确的结果。相差1512000秒,相当于17天12小时。首先让我承认我不明白。我很容易认为儒略历和公历之间的差异可以解释 17 天或 18 天范围内的差异。但是这 12 个小时让我很困惑。

编辑:12 小时来自您在格式模式字符串中使用小写 hh。由于您没有 AM/PM 标记,因此您应该使用大写字母 HH。更正此错误,您的代码输出为

-125818763400

现在你的代码和我的代码之间的差异是 1468800 秒或正好 17 天。

hh 用于 1-12 范围内的 AM 或 PM 内的小时数。大写 HH 表示一天中的小时,0-23。这是 SimpleDateFormat 的一个非常常见的错误(不是现代的 类,他们发现了它所以你更正了)。它经常被忽视,因为在大多数时间里结果都是一样的; SimpleDateFormat 很乐意使用 AM 作为默认值并解析例如 14:30 并将其理解为 2:30 PM。但是由于您的字符串中的小时数恰好是 12,因此存在差异:12:30 AM 表示当天的 0:30,而在 ISO 12:30 中表示 12:30下午。因此出现 12 小时错误。