Java 8 DateTimeFormatter 解析可选部分
Java 8 DateTimeFormatter parsing optional sections
我需要将日期时间解析为两种不同格式的字符串:
- 19861221235959Z
- 1986-12-21T23:59:59Z
以下 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();
我需要将日期时间解析为两种不同格式的字符串:
- 19861221235959Z
- 1986-12-21T23:59:59Z
以下 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();