Java 8 日期和时间:解析不带冒号的 ISO 8601 字符串

我们尝试使用时区偏移量解析以下 ISO 8601 日期时间字符串:

final String input = "2022-03-17T23:00:00.000+0000";

OffsetDateTime.parse(input);
LocalDateTime.parse(input, DateTimeFormatter.ISO_OFFSET_DATE_TIME);

这两种方法都失败了(这也是有道理的,因为时区偏移量中的冒号。OffsetDateTimeDateTimeFormatter.ISO_OFFSET_DATE_TIME

java.time.format.DateTimeParseException:无法在索引 23 处解析文本 '2022-03-17T23:00:00.000+0000'

但根据维基百科,时区偏移量有4种有效格式:

<time>Z 
<time>±hh:mm 
<time>±hhmm 
<time>±hh

其他框架/语言可以毫无问题地解析此字符串,例如Javascript或Jacksons(他们在这里讨论这个问题Date()ISO8601Utils)

现在我们可以用一个复杂的正则表达式编写自己的正则表达式,但在我看来,默认情况下,库应该能够解析这个有效的ISO 8601字符串,因为它是一个有效的字符串。DateTimeFormatterjava.time

现在我们使用 Jacksons ,但我们更愿意使用官方库来使用。您如何解决这个问题?ISO8601DateFormatdate.time


答案 1

如果要解析偏移量 (, 和 ) 的所有有效格式,一种替代方法是使用带有可选模式的 a(不幸的是,似乎没有单个模式字母可以将它们全部匹配):Z±hh:mm±hhmm±hhjava.time.format.DateTimeFormatterBuilder

DateTimeFormatter formatter = new DateTimeFormatterBuilder()
    // date/time
    .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
    // offset (hh:mm - "+00:00" when it's zero)
    .optionalStart().appendOffset("+HH:MM", "+00:00").optionalEnd()
    // offset (hhmm - "+0000" when it's zero)
    .optionalStart().appendOffset("+HHMM", "+0000").optionalEnd()
    // offset (hh - "Z" when it's zero)
    .optionalStart().appendOffset("+HH", "Z").optionalEnd()
    // create formatter
    .toFormatter();
System.out.println(OffsetDateTime.parse("2022-03-17T23:00:00.000+0000", formatter));
System.out.println(OffsetDateTime.parse("2022-03-17T23:00:00.000+00", formatter));
System.out.println(OffsetDateTime.parse("2022-03-17T23:00:00.000+00:00", formatter));
System.out.println(OffsetDateTime.parse("2022-03-17T23:00:00.000Z", formatter));

上述所有四种情况都会将其解析为 。2022-03-17T23:00Z


如果需要,还可以定义单个字符串模式,用于分隔可选部分:[]

// formatter with all possible offset patterns
DateTimeFormatter formatter = DateTimeFormatter
    .ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS[xxx][xx][X]");

此格式化程序也适用于所有情况,就像上面的格式化程序一样。检查 javadoc 以获取有关每种模式的更多详细信息。


笔记:

  • 具有上述可选部分的格式化程序适用于解析,但不适用于格式化。设置格式时,它将打印所有可选部分,这意味着它将多次打印偏移量。因此,要格式化日期,只需使用另一个格式化程序即可。
  • 第二个格式化程序正好接受小数点后 3 位数字(因为 )。另一方面,它更灵活:秒和纳秒是可选的,它也接受小数点后0到9位数字。选择最适合您的输入数据的一个。.SSSISO_LOCAL_DATE_TIME

答案 2

你不需要编写一个复杂的正则表达式 - 你可以构建一个可以轻松使用该格式的正则表达式:DateTimeFormatter

DateTimeFormatter formatter =
    DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSX", Locale.ROOT);

OffsetDateTime odt = OffsetDateTime.parse(input, formatter);

这也将接受“Z”而不是“0000”。它不接受“+00:00”(带冒号或类似名称。考虑到文档,这很令人惊讶,但是如果您的值始终具有没有冒号的UTC偏移量,则应该没问题。