DateTimeFormatter使用parseDefaulting抛出异常的原因排查
DateTimeFormatter解析毫秒时的冲突异常分析
问题场景
定义了如下DateTimeFormatter:
private static final DateTimeFormatter DATE_PATTERN = new DateTimeFormatterBuilder() .appendPattern("yyyy-MM-dd[ ]['T'][HH:mm:ss][.SSSSSS][.SSSSS][.SSSS][.SSS][.SS][.S][X]") .parseDefaulting(ChronoField.HOUR_OF_DAY, 0) .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0) .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0) .parseDefaulting(ChronoField.MILLI_OF_SECOND, 0) .toFormatter() .withZone(ZoneOffset.UTC); // assume incoming is UTC
该格式化器被用于以下方法:
public static Function<String, String> getFormattedDate() { return dateTime -> { try { ZonedDateTime timeRemoved = ZonedDateTime.parse(dateTime, DATE_PATTERN).truncatedTo(ChronoUnit.DAYS); return DateTimeFormatter.ofPattern(DISPLAY_DATE_PATTERN).format(timeRemoved); } catch (Exception e) { return null; } }; }
运行测试用例时抛出异常:
public void test_data_patter_with_ms(){ String formattedDate = DateTimeUtil.getFormattedDate().apply("2021-04-24T06:57:06.850"); assertThat(formattedDate, is("2021-04-24T00:00:00Z")); }
异常信息:
java.time.format.DateTimeParseException: Text '2021-04-24T06:57:06.850' could not be parsed: Conflict found: NanoOfSecond 850000000 differs from NanoOfSecond 0 while resolving MilliOfSecond
注释掉.parseDefaulting(ChronoField.MILLI_OF_SECOND, 0)后测试恢复正常,原因如下:
核心原因
问题出在parseDefaulting的工作逻辑与时间字段的强关联性上:
- 解析字符串
2021-04-24T06:57:06.850时,.SSS部分会读取到毫秒值850,对应纳秒值为850000000。 - 但你通过
parseDefaulting(ChronoField.MILLI_OF_SECOND, 0)预先给毫秒字段设置了默认值0,对应纳秒值为0。 - Java时间库在解析时会校验关联字段的一致性:毫秒和纳秒是绑定的字段,解析得到的毫秒对应纳秒,与预设默认值的纳秒产生冲突,因此抛出解析异常。
当注释掉这条默认设置后,解析器只会使用从字符串中读取到的毫秒值,没有预设值与之冲突,因此可以正常完成解析。
另外补充:你的代码最终会调用truncatedTo(ChronoUnit.DAYS)截断到日期维度,即使保留毫秒值也不影响最终输出,但解析阶段的字段冲突会直接导致解析失败,因此必须移除这个多余的默认配置。
内容的提问来源于stack exchange,提问作者Dan OConnell
相关产品推荐
相关产品推荐

