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

Vir*_*nie 8 datetime-parsing java-8 java-time

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

@Test
public void test() {

    String format = "uuuuMM";

    String value = "201713";

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

}
Run Code Online (Sandbox Code Playgroud)

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

DateTimeFormatter.ofPattern(format).withResolverStyle(ResolverStyle.STRICT)
    .parse(value, YearMonth::from);
Run Code Online (Sandbox Code Playgroud)

没有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);

}
Run Code Online (Sandbox Code Playgroud)

小智 5

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

创建时DateTimeFormatter,默认情况下它使用an IsoChronology,用于解析日期字段.在此解析阶段,调用该方法java.time.chrono.AbstractChronology::resolveDate.

如果查看源代码,您将看到以下逻辑:

if (fieldValues.containsKey(YEAR)) {
    if (fieldValues.containsKey(MONTH_OF_YEAR)) {
        if (fieldValues.containsKey(DAY_OF_MONTH)) {
            return resolveYMD(fieldValues, resolverStyle);
        }
....
return null;
Run Code Online (Sandbox Code Playgroud)

由于输入只有字段,fieldValues.containsKey(DAY_OF_MONTH)返回false,方法返回,null并且没有进行其他检查,如您在Parsed类中看到的那样.

因此,在解析201750201713不解析时TemporalQuery,由于上面的逻辑而没有进行额外的检查,并且该parse方法返回一个java.time.format.Parsed对象,如下面的代码所示:

DateTimeFormatter fmt = DateTimeFormatter.ofPattern("uuuuMM").withResolverStyle(ResolverStyle.STRICT);
TemporalAccessor parsed = fmt.parse("201750");
System.out.println(parsed.getClass());
System.out.println(parsed);
Run Code Online (Sandbox Code Playgroud)

输出是:

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

请注意,返回的对象的类型是,java.time.format.Parsed并且打印它显示已解析的字段(年和月).

但是,当您parse使用a 调用时TemporalQuery,Parsed会将对象传递给查询并验证其字段(当然,这取决于查询,但API内置函数始终会验证).

在这种情况下YearMonth::from,它使用相应ChronoField的(MONTH_OF_YEARYEAR)检查年份和月份是否有效,而月份字段仅接受1到12的值.

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


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

DateTimeFormatter fmt = DateTimeFormatter.ofPattern("uuuuMMdd").withResolverStyle(ResolverStyle.STRICT);
fmt.parse("20175010");
Run Code Online (Sandbox Code Playgroud)

抛出:

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

由于所有日期字段都存在,因此fieldValues.containsKey(DAY_OF_MONTH)返回true并现在检查它是否是有效日期(使用该resolveYMD方法).

  • 非常详细的答案! (3认同)