带有文字且没有分隔符的 DateTimeFormatter 模式不起作用

DateTimeFormatter pattern with liternal and no separator does not work

DateTimeFormatter.ofPattern 生成的解析器表现出以下有趣的行为,这使我无法编写模式来解析像 20150100 这样的字符串:

System.out.println(DateTimeFormatter.ofPattern("yyyyMM").parse("201501", YearMonth::from)); // works
System.out.println(DateTimeFormatter.ofPattern("yyyyMM'aa'").parse("201501aa", YearMonth::from)); // works
System.out.println(DateTimeFormatter.ofPattern("yyyyMM'00'").parse("20150100", YearMonth::from));
// java.time.format.DateTimeParseException: Text '20150100' could not be parsed at index 0

我调试了代码,问题似乎是年字段解析超出了字符串的末尾(三个 y 及以上的最大宽度始终为 19)。但是,我不明白它如何适用于末尾没有 '00' 文字的模式。

有什么方法可以解决这个问题而不必使用格式化程序生成器吗?

编辑:

由于下面的 Jarrod 确认它有问题,我进行了更多的谷歌搜索,最终找到了错误报告:

http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8031085

http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8032491

两者都仅在 Java 9 中得到修复......

DateTimePrinterParser 中存在错误:

我一路调试,显然你不能将数字作为文字。类似的测试代码证明了这一点,如果您一直逐步调试到 DateTimeFormatterBuilder.parse() 方法,您可以看到它做错了什么。

显然,Value(YearOfEra,4,19,EXCEEDS_PAD) 解析器会消耗 00 如果这些 不是 数字,则它们会停止,因为它正在寻找一个数字 419 位长。 DateTimeParseContext 中嵌入的 DateTimeFormatter 是错误的。

如果你输入像 xx 这样的非数字字符文字,它可以工作,数字文字则不行。

这两个都失败了:

final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM'00'");
System.out.println(sdf.parse("20150100"));

Exception in thread "main" java.text.ParseException: Unparseable date: "20150100" at java.text.DateFormat.parse(DateFormat.java:366)

final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMM'00'");
System.out.println(dateTimeFormatter.parse("20150100", YearMonth::from));

Exception in thread "main" java.time.format.DateTimeParseException: Text '20150100' could not be parsed at index 0 at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949) at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)

这两个都成功了:

final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM'xx'");
System.out.println(sdf.parse("201501xx"));

Thu Jan 01 00:00:00 EST 2015

final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMM'xx'");
System.out.println(dateTimeFormatter.parse("201501xx", YearMonth::from));

2015-01

如果您不介意使用第 3 方库,那么您可以试试我的库 Time4J,其最新版本 v4.18 可以满足您的需求:

import net.time4j.Month;
import net.time4j.range.CalendarMonth;
import net.time4j.format.expert.ChronoFormatter;
import net.time4j.format.expert.PatternType;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import java.text.ParseException;
import java.util.Locale;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

@RunWith(JUnit4.class)
public class CalendarMonthTest {
    @Test
    public void parse2() throws ParseException {
      assertThat(
        ChronoFormatter.ofPattern(
            "yyyyMM'00'",
            PatternType.CLDR,
            Locale.ROOT,
            CalendarMonth.chronology()
        ).parse("20150100"),
        is(CalendarMonth.of(2015, Month.JANUARY)));
    }
}

顺便说一句,指向 JDK-bug-log 的链接与您的问题并没有真正的关系。这些问题仅描述了在小数秒的上下文中应用相邻数字解析时的问题。虽然 Java-9 会解决该问题,但您的问题不会。也许您想在那里开一个新问题?但我怀疑 Oracle 会将其视为错误。这是一个新特性,直到现在 Oracle 分发的任何库都不支持它。在 JSR-310(又名 java.time 包)中,带有(前导)数字的文字预计不会参与相邻值解析(在 SimpleDateFormat 中也不会)。

旁注:Time4J 不仅是对这个细节(数字文字)的回答,而且通常在解析方面提供更好的性能,并且由于有很多转换方法,可以与 JSR-310 并行使用。例如:要实现YearMonth的实例,只需在解析结果上调用calendarMonth.toTemporalAccessor()即可。

作为 user177800 回答的补充,您可以改用这个表格:

var formatter = new DateTimeFormatterBuilder()
    .appendValue(ChronoField.YEAR, 4)
    .appendValue(ChronoField.MONTH_OF_YEAR, 2)
    .appendLiteral("00")
    .toFormatter();
YearMonth.parse("20220200", formatter);

java.time的所有部分。