java.time.ZonedDateTime.parse 和 iso8601?

java.time.ZonedDateTime.parse and iso8601?

为什么 JDK8 DateTime 库似乎无法解析有效的 iso8601 日期时间字符串?它在表示为“+01”而不是“+01:00”

的时区偏移量上窒息

这个有效:

java.time.ZonedDateTime.parse("2015-08-18T00:00+01:00")

这会抛出一个解析异常:

java.time.ZonedDateTime.parse("2015-08-18T00:00+01")

来自 iso8601 维基百科页面:

The offset from UTC is appended to the time in the same way that 'Z' was above, in the form ±[hh]:[mm], ±[hh][mm], or ±[hh]. So if the time being described is one hour ahead of UTC (such as the time in Berlin during the winter), the zone designator would be "+01:00", "+0100", or simply "+01".

编辑:这看起来像是 JDK.

中的实际合法错误

https://bugs.openjdk.java.net/browse/JDK-8032051

哇,在测试新的日期时间东西多年之后,我认为他们会发现如此明显的东西。我还认为 JDK 作者类型足够严格,可以使用更好的自动化测试套件。

更新:这在当前的 jdk-9 版本中已完全修复。我刚刚确认。上面显示的完全相同的解析命令在当前 jdk-8 构建中失败,但在 jdk-9.

中完美运行

ADDENDUM:FWIW,基于 ISO-8601 的 RFC 3339,不允许这种简写。您必须在时区偏移量中指定分钟数。

使用的代码由 DateTimeFormatterBuilder.appendZoneId() 添加,允许时区格式为

For example, the following will parse:

"Europe/London"           -- ZoneId.of("Europe/London")
"Z"                       -- ZoneOffset.UTC
"UT"                      -- ZoneId.of("UT")
"UTC"                     -- ZoneId.of("UTC")
"GMT"                     -- ZoneId.of("GMT")
"+01:30"                  -- ZoneOffset.of("+01:30")
"UT+01:30"                -- ZoneOffset.of("+01:30")
"UTC+01:30"               -- ZoneOffset.of("+01:30")
"GMT+01:30"               -- ZoneOffset.of("+01:30")

您可以定义自己的日期时间格式以允许小时偏移,但世界上许多地方都有小时的小数部分,例如尼泊尔 +05:45 和朝鲜最近更改为 +08:30

use this default formatter: ISO_OFFSET_DATE_TIME(因为解析2015-08-18T00:00+01:00)。

在文档中:

This returns an immutable formatter capable of formatting and parsing the ISO-8601 extended offset date-time format. [...]

The offset ID. If the offset has seconds then they will be handled even though this is not part of the ISO-8601 standard. Parsing is case insensitive.

It's(您仅将此用于此默认格式化程序):

The ID is minor variation to the standard ISO-8601 formatted string for the offset. There are three formats:

  • Z - for UTC (ISO-8601)
  • +hh:mm or -hh:mm - if the seconds are zero (ISO-8601)
  • +hh:mm:ss or -hh:mm:ss - if the seconds are non-zero (not ISO-8601) (don't +hh like ISO-8601).

似乎 java.time (JDK 8) 没有完全实现 ISO-8601。


这个:

java.time.ZonedDateTime.parse("2015-08-18T00:00+01:00"); // works

对应于(大致来自来源JDK):

DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();
DateTimeFormatter formatter = builder
        .parseCaseInsensitive()
        .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
        .appendOffsetId()
        .toFormatter();

java.time.ZonedDateTime.parse("2015-08-18T00:00+01:00", formatter); // it's same

您可以创建自己的DataTimeFormatter with DateTimeFormatterBuilder

DateTimeFormatterBuilder builder2 = new DateTimeFormatterBuilder();
DateTimeFormatter formatter2 = builder2.parseCaseInsensitive()
        .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
        .appendPattern("X") // eg.:
        .toFormatter();

java.time.ZonedDateTime.parse("2015-08-18T00:00+01", formatter2); // here you set +01

而不是 appendOffsetId() 使用 appendPattern(String pattern) 并设置 'X' 或 'x'.

现在,您可以使用数据时间 2015-08-18T00:00+01


或者...使用默认 ISO_OFFSET_DATE_TIME 并添加后缀 :00.

java.time.ZonedDateTime.parse("2015-08-18T00:00+01" + ":00");

但最后这个解决方案很糟糕。

这在当前的 jdk-9 版本中已完全修复。我刚刚确认。上面显示的完全相同的解析命令在当前 jdk-8 构建中失败,但在 jdk-9.

中完美运行

使用新的 jdk-9 shell:

➜  jdk-9 bin/jshell
|  Welcome to JShell -- Version 9-ea
|  For an introduction type: /help intro

jshell> java.time.ZonedDateTime.parse("2015-08-18T00:00+01")
 ==> 2015-08-18T00:00+01:00

谢谢@mkczyk 说我为这项工作制作了自己的格式化程序。 我需要一个日期解析器,比如

  1. “2016-02-14T18:32:04.150Z”
  2. "2016-02-14T21:32:04.150+04"
  3. "2016-02-14T21:32:04.150+04:00"

private static final DateTimeFormatter DATE_TIME_NANOSECONDS_OFFSET_FORMATTER = new DateTimeFormatterBuilder().parseCaseInsensitive().append(ISO_LOCAL_DATE_TIME) .appendFraction(ChronoField.NANO_OF_SECOND, 0, 3, true) .appendOffset("+HH:mm", "Z") .toFormatter();

appendfraction(..,..,..,true) 将纳秒视为秒的小数点。

ZonedDateTime zdt = ZonedDateTime.parse(textToParse, DATE_TIME_NANOSECONDS_OFFSET_FORMATTER);

之后用于从中获取 UTC 时间。

zdt = ZonedDateTime.ofInstant(zdt.toInstant(), ZoneId.of("UTC"));