无法将 ISO 8601 格式的字符串解析为 Java 8601 格式,偏移量中缺少冒号tl;博士详关于 java.time

2022-09-01 18:47:12

我对java 8日期格式/解析功能有点沮丧。我试图找到Jackson配置并将字符串解析为任何Java 8日期,但没有找到它。
这是一个工作正常的例子:DateTimeFormatter"2018-02-13T10:20:12.120+0000"java.util.Date

Date date = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSSZZZ")
                      .parse("2018-02-13T10:20:12.120+0000");

相同的格式不适用于新的日期时间 api

ZonedDateTime dateTime = ZonedDateTime.parse("2018-02-13T10:20:12.120+0000",
                   DateTimeFormatter.ofPattern("yyyy-MM-dd'T'hh:mm:ss.SSSZZZ"));

我们应该能够以适合 FE UI 应用程序的任何格式格式化/解析日期。也许我误解或误会了什么,但我认为它提供了更多的格式灵活性和更易于使用。java.util.Date


答案 1

tl;博士

直到 bug 修复:

OffsetDateTime.parse( 
    "2018-02-13T10:20:12.120+0000" , 
    DateTimeFormatter.ofPattern( "uuuu-MM-dd'T'HH:mm:ss.SSSX" )
)

当错误被修复时:

OffsetDateTime.parse( "2018-02-13T10:20:12.120+0000" )

您使用了错误的类。

避免麻烦的旧旧类,如 、 、 和 。现在被java.time类所取代。DateCalendarSimpleDateFormat

你使用的ZonedDateTime类很好,它是java.time的一部分。但它适用于完整时区。输入字符串只有一个与 UTC 的偏移量。相比之下,完整时区是某个区域在不同时间点、过去点、现在点和将来生效的偏移量的集合。例如,在北美大部分地区使用夏令时 (DST),偏移量每年变化两次,因为我们将时钟向前移动一小时,而在春季,当我们将时钟向后移一小时时,偏移量会变小,而在秋季恢复到更长的值。

OffsetDateTime

对于仅偏移量而不是时区,请使用 OffsetDateTime 类。

您的输入字符串符合 ISO 8601 标准。java.time 类在解析/生成字符串时默认使用标准格式。因此,无需指定格式设置模式。

OffsetDateTime odt = OffsetDateTime.parse( "2018-02-13T10:20:12.120+0000" );

好吧,这应该有效。不幸的是,Java 8 中存在一个错误(至少在 Java 8 Update 121 之前),该类无法解析偏移量,从而省略了小时和分钟之间的冒号。所以虫子咬人但不是。因此,在修复程序到达之前,您可以选择两种解决方法:(a)黑客攻击,操作输入字符串,或(b)定义显式格式化模式。+0000+00:00

技巧:操作输入字符串以插入冒号。

String input = "2018-02-13T10:20:12.120+0000".replace( "+0000" , "+00:00" );
OffsetDateTime odt = OffsetDateTime.parse( input );

DateTimeFormatter

更可靠的解决方法是在对象中定义并传递格式设置模式。DateTimeFormatter

String input = "2018-02-13T10:20:12.120+0000" ;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "uuuu-MM-dd'T'HH:mm:ss.SSSX" );
OffsetDateTime odt = OffsetDateTime.parse( input , f );

地址:2018-02-13T10:20:12.120Z

顺便说一句,这里有一个提示:我发现,对于许多协议和库,如果你的偏移量总是有冒号,总是有小时和分钟(即使分钟为零),并且总是使用填充零(而不是),你的生活会更容易。-05:00-5

DateTimeFormatterBuilder

有关通过DateTimeFormatterBuilder创建的更灵活的格式化程序,请参阅重复问题的出色答案

Instant

如果要使用始终采用 UTC 的值(并且应该),请提取对象。Instant

Instant instant = odt.toInstant();

ZonedDateTime

如果要通过某个区域的挂钟时间镜头查看该时刻,请应用时区。

ZoneId z = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = odt.atZoneSameInstant( z );

请参阅此代码在 IdeOne.com 实时运行

所有这些在许多问题的答案中已经多次涉及。请在发布前彻底搜索堆栈溢出。你会发现几十个,如果不是几百个,的例子。


关于 java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧日期时间类,如java.util.DateCalendarSimpleDateFormat

Joda-Time 项目现在处于维护模式,建议迁移到 java.time 类。

要了解更多信息,请参阅 Oracle 教程。搜索 Stack Overflow 以获取许多示例和解释。规格是JSR 310

您可以直接与数据库交换 java.time 对象。使用符合 JDBC 4.2 或更高版本的 JDBC 驱动程序。不需要字符串,不需要类。java.sql.*

从哪里获取 java.time 类?

ThreeTen-Extra 项目通过其他类扩展了 java.time。这个项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的课程,例如IntervalYearWeekYearQuarter


答案 2

简短:不是错误,只是你的模式是错误的。

请使用专为时区偏移量设计的类型,并按以下方式使用模式:OffsetDateTime

OffsetDateTime odt =
    OffsetDateTime.parse( 
        "2018-02-13T10:20:12.120+0000" , 
        DateTimeFormatter.ofPattern( "uuuu-MM-dd'T'HH:mm:ss.SSSZZZ" )
    )

问题详情:

a) 12 小时制与 24 小时制

“h”表示 12 小时制的 AM/PM 小时,但根据 ISO-8601 的要求,24 小时制显然需要“H”。

b) 零点偏移量的形式

如果要解析零偏移量,如“+0000”而不是“Z”(如ISO-paper中所述),则不应使用图案符号“X”,而应使用“ZZZ”。引用模式语法

偏移量 Z:根据图案字母的数量设置偏移量的格式。一个、两个或三个字母输出小时和分钟,不带冒号,例如“+0130”。当偏移量为零时,输出将为“+0000”。

c)您的输入与ISO-8601不兼容,因此在Java中没有错误

您认为“2018-02-13T10:20:12.120+0000”应为有效的 ISO 是错误的,因为您混合了 ISO 文件中明确禁止的基本格式(在偏移部分)和扩展格式(请参阅第 4.3.2 节(示例部分)和 4.3.3d)。引用 ISO-8601:

[...]表达式应完全采用基本格式,在这种情况下,将使用所需表达式所需的最小分隔符数,或者完全采用扩展格式[...]

B. Bourque的声明有一个错误是基于对ISO兼容性的相同错误期望。比方说的文档仅描述了对扩展ISO格式的支持。另请参阅相关的 JDK 问题。并非所有 ISO-8601 变体都直接受支持,因此以正确的方式对解析器进行基于模式的构造是可以的。java.timeISO_OFFSET_DATE_TIME