【问题标题】:Cannot parse String in ISO 8601 format, lacking colon in offset, to Java 8 Date无法将 ISO 8601 格式的字符串(偏移量中缺少冒号)解析为 Java 8 日期
【发布时间】:2020-05-10 14:37:32
【问题描述】:

我对 java 8 日期格式/解析功能有点失望。我试图找到 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"));

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

【问题讨论】:

  • 我认为它不喜欢没有上午/下午标记的事实,所以10 可以,尝试使用HH 而不是hh ...或添加@987654330 @标记

标签: java parsing datetime java-8 iso8601


【解决方案1】:

tl;博士

直到错误被修复:

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

bug 修复后:

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

详情

你使用了错误的类。

避免使用麻烦的旧遗留类,例如DateCalendarSimpleDateFormat。现在被java.time 类所取代。

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

OffsetDateTime

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

您的输入字符串符合ISO 8601 标准。 java.time 类在解析/生成字符串时默认使用标准格式。所以不需要指定格式模式。

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

嗯,那应该起作用了。不幸的是,Java 8 中存在一个错误(至少在 Java 8 更新 121 之前),该类无法解析在小时和分钟之间省略冒号的偏移量。所以这个错误会咬+0000 而不是+00:00。因此,在修复到来之前,您可以选择两种解决方法:(a) hack,操作输入字符串,或 (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 创建的更灵活的格式化程序,请参阅this excellent Answer 的重复问题。

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 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310

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

从哪里获得 java.time 类?

ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

【讨论】:

  • 是的,刚刚更新了我的答案。我忘记了偏移中可选冒号的格式解析错误,已在 Java 9 中修复。Try my revised code at IdeOne.com.
  • 这个答案应该链接到 JDK 错误。快速搜索得到bugs.java.com/view_bug.do?bug_id=JDK-8032051,但我不确定这是不是同一个问题。
  • @deworde 好问题;我投了赞成票。我不知道答案。提示:我已经看到其他库因缩写 offset-from-UTC 字符串而窒息。最好将数据源更改为始终使用全长,+08:00 而不是 +0800+8
  • 这听起来不像是一个错误:JDK-8176547 解释说 +0000 是“基本”格式,但 +00:00 是“扩展”格式。 java.timedocumented 使用扩展格式。听起来上面的DateTimeFormatter(或DateTimeFormatterBuilder)解决方案是可行的方法。
  • 这里没有bug,所以这个答案目前是错误的。 OPs 代码的问题是使用ZonedDateTime 而不是OffsetDateTimehh 而不是HH。因此,正确的模式是“uuuu-MM-dd'T'HH:mm:ss.SSSZZZ”,它使用“+0000”而不是“Z”作为偏移量。您正在查看的错误是关于允许只有几个小时的偏移量,这是一个单独的问题。查看全系列图案字母:docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/…
【解决方案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 论文中所述),则不应使用模式符号“X”而是“ZZZ”。引用pattern syntax

偏移 Z:这会根据图案的数量格式化偏移 字母。一个、两个或三个字母输出小时和分钟, 没有冒号,例如“+0130”。输出将是“+0000”时 偏移量为零。

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

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

[...]表达式应完全采用基本格式,其中 case 所需的最小分隔符数量 表达式被使用,或完全以扩展格式[...]

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

【讨论】:

  • JDK 问题的链接对于理解问题非常有帮助。对于大多数程序员来说,“扩展”与“基本”格式的细微差别不会立即显现出来。规范中必须完全采用扩展格式或根本不采用扩展格式的部分绝对是繁琐的,并且会导致传入日期的变通方法可能不严格遵守。也许这不是严格意义上的 JDK 错误,但如果 JDK 在这里可以更宽容一点,并且做正确的事情,那就太好了。不过很好的参考,谢谢。
【解决方案3】:

如果偏移量 +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);

【讨论】:

    猜你喜欢
    • 2017-09-07
    • 2018-03-11
    • 1970-01-01
    • 2013-04-26
    • 2020-12-28
    相关资源
    最近更新 更多