为什么Java 8 DateTimeFormatter在ResolverStyle.STRICT模式下允许不正确的月份值?

时间:2017-08-23 12:12:26

标签: java-8 java-time datetime-parsing

为什么这个测试通过,而月份值显然是无效的(13)?

@Test
public void test() {

    String format = "uuuuMM";

    String value = "201713";

     DateTimeFormatter.ofPattern(format).withResolverStyle(ResolverStyle.STRICT)
        .parse(value);

}

使用时态查询时,会抛出预期的DateTimeParseException

DateTimeFormatter.ofPattern(format).withResolverStyle(ResolverStyle.STRICT)
    .parse(value, YearMonth::from);

未指定TemporalQuery时会发生什么?

编辑:13值似乎是一个特殊的值,因为我学到了ΦXocę웃Пepeúpaツ的答案(见Undecimber)。

但即使使用其他值(例如50:

)也不会引发异常
@Test
public void test() {

    String format = "uuuuMM";

    String value = "201750";

     DateTimeFormatter.ofPattern(format).withResolverStyle(ResolverStyle.STRICT)
        .parse(value);

}

3 个答案:

答案 0 :(得分:5)

我在这里进行了一些调试,发现解析过程的一部分是根据格式化程序的年表来检查字段。

当您创建DateTimeFormatterby default时,它会使用IsoChronology,用于解析日期字段。在此解析阶段,调用the method java.time.chrono.AbstractChronology::resolveDate

如果查看source,您会看到以下逻辑:

if (fieldValues.containsKey(YEAR)) {
    if (fieldValues.containsKey(MONTH_OF_YEAR)) {
        if (fieldValues.containsKey(DAY_OF_MONTH)) {
            return resolveYMD(fieldValues, resolverStyle);
        }
....
return null;

由于输入只包含字段,fieldValues.containsKey(DAY_OF_MONTH)返回false,因此该方法返回null而不是您可以在Parsed class中看到其他检查。

因此,在没有201750的情况下解析201713TemporalQuery时,由于上述逻辑,不会进行额外检查,并且parse方法返回{{1通过以下代码可以看到对象:

java.time.format.Parsed

输出结果为:

  

class java.time.format.Parsed
  {Year = 2017,MonthOfYear = 50},ISO

请注意,返回的对象的类型为DateTimeFormatter fmt = DateTimeFormatter.ofPattern("uuuuMM").withResolverStyle(ResolverStyle.STRICT); TemporalAccessor parsed = fmt.parse("201750"); System.out.println(parsed.getClass()); System.out.println(parsed); ,并且打印它会显示已解析的字段(年份和月份)。

当您使用java.time.format.Parsed调用parse时,TemporalQuery对象会传递给查询并且其字段已经过验证(当然这取决于查询,但API内置的总是验证)。

如果是Parsed,它会使用相应的YearMonth::fromMONTH_OF_YEARYEAR)检查年份和月份是否有效,并且月份字段接受仅限值from 1 to 12

这就是为什么只调用ChronoField不会抛出异常,而是使用parse(value)调用。

只是在所有日期字段(年,月和日)出现时检查上面的逻辑:

TemporalQuery

这引发:

  

线程“main”中的异常java.time.format.DateTimeParseException:无法解析文本“20175010”: MonthOfYear的值无效(有效值1 - 12):50

由于所有日期字段都存在,DateTimeFormatter fmt = DateTimeFormatter.ofPattern("uuuuMMdd").withResolverStyle(ResolverStyle.STRICT); fmt.parse("20175010"); 会返回fieldValues.containsKey(DAY_OF_MONTH),现在它会检查它是否为有效日期(使用resolveYMD method)。

答案 1 :(得分:3)

第13个月被称为:Undecimber

我们许多人使用的 gregorian 日历仅允许 12 月,但java包含对日历的支持,这些日历允许13个月,因此它取决于您所谈论的日历系统

例如,MONTH字段的实际最大值在某些年份 12 希伯来日历中其他年份的 13 系统。所以第13个月是有效的

答案 2 :(得分:1)

在没有给定parse的情况下调用TemporalQuery时,不会抛出异常,这有点奇怪。单个参数parse方法的一些文档:

  

这解析生成临时对象的整个文本。使用parse(CharSequence,TemporalQuery)通常更有用。此方法的结果是TemporalAccessor已经解决,应用基本验证检查以帮助确保有效的日期时间。

请注意,它表示使用解析(CharSequence,TemporalQuery)"通常更有用。在您的示例中,parse返回了一个java.time.format.Parsed对象,除了创建不同的TemporalAccessor之外,该对象并未真正用于其他任何内容。

请注意,如果您尝试从返回的值创建YearMonth,则会引发异常:

YearMonth.from(DateTimeFormatter.ofPattern(format)
    .withResolverStyle(ResolverStyle.STRICT).parse(value));

引发

 Exception in thread "main" java.time.DateTimeException: Unable to obtain YearMonth from TemporalAccessor: {Year=2017, MonthOfYear=50},ISO of type java.time.format.Parsed
    at java.time.YearMonth.from(YearMonth.java:263)
    at anl.nfolds.Test.main(Test.java:21)
Caused by: java.time.DateTimeException: Invalid value for MonthOfYear (valid values 1 - 12): 50
    at java.time.temporal.TemporalAccessor.get(TemporalAccessor.java:224)
    at java.time.YearMonth.from(YearMonth.java:260)
... 1 more

Parsed的文档:

  

解析数据的存储。

     

在解析过程中使用此类来收集数据。解析过程的一部分涉及处理可选块,并创建数据的多个副本以支持必要的回溯。

     

解析完成后,此类可用作生成的TemporalAccessor。在大多数情况下,只有在字段解析后才会显示它。

     

自:1.8

     

@implSpec这个类是一个可变的上下文,旨在从单个线程使用。在标准解析中,类的用法是线程安全的,因为为每个解析自动创建此类的新实例,并且解析是单线程的