【问题标题】:DateTimeFormatter pattern with liternal and no separator does not work带有文字且没有分隔符的 DateTimeFormatter 模式不起作用
【发布时间】:2017-01-02 04:02:50
【问题描述】:

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 中修复......

【问题讨论】:

  • 为什么不想使用格式化程序生成器?
  • 你试过单步调试看看有什么区别吗?
  • @Tunaki 因为这是一个简单的案例,像 SimpleDateFormat 这样的旧工具支持,而且这种行为看起来更像是一个错误......
  • SimpleDateFormat 也以同样的方式失败,因为它们都依赖于DateTimeFormatter,这就是错误所在。
  • @Tunaki 事实证明这个案例在构建器中也有问题......请参阅第二个错误链接。

标签: java java-time


【解决方案1】:

如果您不介意使用 3rd-party-library,那么您可以试试我的库 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 分发的任何库都不支持。在 JSR-310(又名 java.time-package)中不期望带有(前导)数字的文字参与相邻值解析(在 SimpleDateFormat 中也不会)。

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

【讨论】:

    【解决方案2】:

    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"));
    

    线程“主”java.text.ParseException 中的异常:无法解析的日期: java.text.DateFormat.parse(DateFormat.java:366) 处的“20150100”

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

    线程“主”java.time.format.DateTimeParseException 中的异常: 无法在索引 0 处解析文本“20150100” java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949) 在 java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)

    这两个都成功了:

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

    2015 年 1 月 1 日星期四 00:00:00 EST

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

    2015-01

    【讨论】:

    • 感谢您的确认。
    • 通过我在答案中提到的类上的方法逐步调试,这对我来说太复杂的逻辑了,但我明白它在做什么。
    猜你喜欢
    • 2018-03-31
    • 2015-09-05
    • 1970-01-01
    • 1970-01-01
    • 2018-07-10
    • 2021-09-03
    • 2021-06-20
    • 2022-01-13
    • 1970-01-01
    相关资源
    最近更新 更多