Java 8 DateTimeFormatter 解析可选部分

Java 8 DateTimeFormatter parsing optional sections

我需要将日期时间解析为两种不同格式的字符串:

以下 dateTimeFormatter 模式正确解析了第一种日期字符串

DateTimeFormatter.ofPattern ("uuuuMMddHHmmss[,S][.S]X")

但第二个失败,因为不需要破折号、冒号和 T。

我尝试使用如下可选部分:

DateTimeFormatter.ofPattern ("uuuu[-]MM[-]dd['T']HH[:]mm[:]ss[,S][.S]X")

出乎意料的是,这会解析第二种日期字符串(带破折号的),而不是第一种,抛出一个

java.time.format.DateTimeParseException: Text '19861221235959Z' could not be parsed at index 0

好像可选部分没有被评估为可选...

文档中并不清楚,但我的猜测是发生了以下情况。

当您在格式模式字符串中使用 uuuuMMddHHmmss 时,格式化程序可以很容易地看到有几个相邻的数字字段,因此使用字段宽度来分隔字段。取前4位表示年份,依此类推

当您改为使用 uuuu[-]MM[-]dd['T']HH[:]mm[:]ss 时,格式化程序不会将其视为相邻的数字字段。我同意 Peter Lawrey 的评论,因此它需要更长的 运行 数字作为年份,最后溢出最大年份 (999999999) 并抛出异常。

解决办法?请参考.

乍一看,您的第二种格式应该适用于这两种情况。不知道为什么没有。顺便说一句,我很好奇你为什么使用 'u' 而不是 'y' 一年。所以我也会尝试使用 'y' 看看它是否有所作为。但总的来说,您触及了有趣的一点——如何从未知格式解析日期(想象一下,您正在处理未知数量的格式,而不是 2 种可能的格式)。我实际上写过一次这样的解析器。我用来解决这个问题的想法在我的文章Java 8 java.time package: parsing any string to date中有描述。您可能会发现这个想法很有用。简而言之,我们的想法是拥有包含所有支持格式的外部文件,并尝试逐一应用每种格式,直到一种格式可用。

基于模式的 DateTimeFormatter 不够智能,无法同时处理可选部分和不分隔两个数字字段的可能性。当您确实需要数字字段没有分隔符时,毫无疑问,模式会理解模式字母从 u 到 M 的变化意味着它需要计算数字以了解哪个数字是哪些字段的一部分。但是当这不确定时,模式就不会尝试那样做。它看到一个数字字段被完整描述,并且没有立即跟随着另一个数字字段。因此,没有理由计算数字。所有数字都是应该在此处表示的字段的一部分。

为此,您不应尝试使用模式构建 DateTimeFormatter,而应使用构建器。从 DateTimeFormatter.BASIC_ISO_DATE 和附近的其他人那里获得灵感。

问题是您的模式将整个字符串视为年份。您可以使用 .appendValue(ChronoField.YEAR, 4) 将其限制为四个字符:

DateTimeFormatter formatter = new DateTimeFormatterBuilder()
    .appendValue(ChronoField.YEAR, 4)
    .appendPattern("[-]MM[-]dd['T']HH[:]mm[:]ss[,S][.S]X")
    .toFormatter();

您的两个示例都能正确解析。

如果你想更冗长,你可以这样做:

DateTimeFormatter formatter = new DateTimeFormatterBuilder()
    .appendValue(ChronoField.YEAR, 4)
    .optionalStart().appendLiteral('-').optionalEnd()
    .appendPattern("MM")
    .optionalStart().appendLiteral('-').optionalEnd()
    .appendPattern("dd")
    .optionalStart().appendLiteral('T').optionalEnd()
    .appendPattern("HH")
    .optionalStart().appendLiteral(':').optionalEnd()
    .appendPattern("mm")
    .optionalStart().appendLiteral(':').optionalEnd()
    .appendPattern("ss")
    .optionalStart().appendPattern("X").optionalEnd()
    .toFormatter();