无法将 ISO 8601 格式的字符串解析为 Java 8 日期,偏移量中缺少冒号

Cannot parse String in ISO 8601 format, lacking colon in offset, to Java 8 Date

我对 java 8 日期 format/parse 功能有点失望。我试图找到 Jackson 配置和 DateTimeFormatter 以将 "2018-02-13T10:20:12.120+0000" 字符串解析为任何 Java 8 日期,但没有找到它。
这是工作正常的 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"));

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

tl;博士

错误修复前:

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" )

详情

您使用了错误的 classes。

避免麻烦的旧遗留 classes,例如 DateCalendarSimpleDateFormat。现在被 java.time classes.

取代

ZonedDateTime class you used is good, it is part of java.time. But it is intended for a full time zone. Your input string has merely an offset-from-UTC。相比之下,完整时区是一个地区在过去、现在和未来的不同时间点有效的偏移量的集合。例如,在北美大部分地区使用夏令时 (DST),偏移量每年变化两次,在 Spring 中随着我们将时钟向前移动一个小时而变得更小,并在秋季恢复到更长的值,当我们将时钟拨回一个小时。

OffsetDateTime

对于偏移量而不是时区,使用 OffsetDateTime class。

您输入的字符串符合 ISO 8601 标准。当 parsing/generating 字符串时,java.time classes 默认使用标准格式。因此无需指定格式模式。

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

嗯,应该有效。不幸的是,Java 8 中存在一个 错误(至少在 Java 8 Update 121 之前),其中 class 无法解析省略的偏移量小时和分钟之间的冒号。所以这个 bug 会攻击 +0000 但不会攻击 +00:00。因此,在出现修复之前,您可以选择两种解决方法:(a) 黑客攻击,操纵输入字符串,或 (b) 定义明确的格式化模式。

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

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 );

odt.toString(): 2018-02-13T10:20:12.120Z

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

DateTimeFormatterBuilder

对于更灵活的格式化程序,通过 DateTimeFormatterBuilder, see 在重复的问题上创建。

Instant

如果您想使用始终采用 UTC 的值(您应该这样做),请提取一个 Instant 对象。

Instant instant = odt.toInstant();

ZonedDateTime

如果您想通过某个地区 wall-clock time 的镜头观看那一刻,请应用时区。

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

看到这个code run live at IdeOne.com

所有这些已在许多问题的许多答案中多次提及。发帖前请彻底搜索 Stack Overflow。您会发现许多(如果不是数百个)示例。


关于java.time

java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

要了解更多信息,请参阅 Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310

您可以直接与数据库交换 java.time 对象。使用 JDBC driver compliant with JDBC 4.2 或更高版本。不需要字符串,不需要 java.sql.* classes.

从哪里获得java.time classes?

ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

简而言之:不是错误,只是你的模式不对。

请使用专为时区偏移设计的类型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 的小时,但您显然需要 "H" 作为 ISO-8601 要求的 24 小时制。

b)零偏的形式

如果你想解析像“+0000”这样的零偏移而不是"Z"(如ISO-paper中所述)你不应该使用模式符号"X"而是"ZZZ" .引用 pattern syntax:

Offset Z: This formats the offset based on the number of pattern letters. One, two or three letters outputs the hour and minute, without a colon, such as '+0130'. The output will be '+0000' when the offset is zero.

c) 您的输入与 ISO-8601 不兼容,因此 Java

中没有错误

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

[...]the expression shall either be completely in basic format, in which case the minimum number of separators necessary for the required expression is used, or completely in extended format[...]

B. Bourque 关于 java.time 存在错误的说法是基于对 ISO 兼容性的相同错误期望。比方说 ISO_OFFSET_DATE_TIME 的文档仅描述了对扩展 ISO 格式的支持。另见相关 JDK issue。并非所有 ISO-8601 变体都得到直接支持,因此以正确的方式构建基于模式的解析器是可以的。

如果偏移量 +0000 试试这个

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