具有可选模式的Java DateTimeFormatterBuilder导致DateTimeParseException [英] Java DateTimeFormatterBuilder with optional pattern results in DateTimeParseException

查看:192
本文介绍了具有可选模式的Java DateTimeFormatterBuilder导致DateTimeParseException的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为LocalDate实例提供灵活的解析器,可以处理以下格式之一的输入:

Provide a flexible parser for LocalDate instances that can handle input in one of the following formats:


  • yyyy

  • yyyyMM

  • yyyyMMdd

以下类尝试处理第一种和第二种模式。解析适用于年份输入,但年份+月份会导致以下异常。

The following class attempts to handle both the first and the second pattern. Parsing works for the year input, but year + month results in the exception outlined below.

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;

public class DateTest {

    public static void main(String[] args) {
        DateTimeFormatter parser = new DateTimeFormatterBuilder()
        .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
        .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
        .appendPattern("yyyy")
        .optionalStart().appendPattern("MM").optionalEnd().toFormatter();

        System.out.println(parser.parse("2014", LocalDate::from)); // Works
        System.out.println(parser.parse("201411", LocalDate::from)); // Fails
    }
}

第二次parse()尝试导致以下异常:

The second parse() attempt results in the following exception:

Exception in thread "main" java.time.format.DateTimeParseException: Text '201411' 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)

我认为我对可选部分模式的工作方式缺乏了解。我的目标是使用灵活的格式的解析器是否可以实现,还是需要检查输入长度并从解析器列表中进行选择?一如既往,我们将不胜感激。

I think my understanding of how optional partial patterns work is lacking. Is my goal of one parser with a flexible format even achievable, or do I need to check on input length and select from a list of parsers? As always, help is appreciated.

推荐答案

造成问题的真正原因是符号处理。您的输入没有符号,但是解析器元素 yyyy很乐意解析尽可能多的数字,并且期望正号,因为找到了四个以上的数字。

The real cause of your problem is sign-handling. Your input has no sign but the parser element "yyyy" is greedy to parse as many digits as possible and expects a positive sign because there are more than four digits found.

我的分析是通过两种不同的方式完成的:

My analysis was done in two different ways:


  • 调试(以查看不清楚的错误消息的真正含义)

  • debugging (in order to see what is really behind the unclear error message)

模拟另一个基于我的lib Time4J的解析引擎中的行为,以获得更好的错误消息:

simulating the behaviour in another parse engine based on my lib Time4J for getting a better error message:

ChronoFormatter<LocalDate> cf =
ChronoFormatter
    .ofPattern(
        "yyyy[MM]",
        PatternType.THREETEN,
        Locale.ROOT,
        PlainDate.axis(TemporalType.LOCAL_DATE)
    )
    .withDefault(PlainDate.MONTH_AS_NUMBER, 1)
    .withDefault(PlainDate.DAY_OF_MONTH, 1)
    .with(Leniency.STRICT);
System.out.println(cf.parse("201411")); 
// java.text.ParseException: Positive sign must be present for big number.


您可以通过以下方法解决问题指示构建器一年中始终仅使用四位数:

DateTimeFormatter parser =
    new DateTimeFormatterBuilder()
        .appendValue(ChronoField.YEAR, 4)
        .optionalStart()
        .appendPattern("MM[dd]")
        .optionalEnd()
        .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
        .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
        .toFormatter();

System.out.println(parser.parse("2014", LocalDate::from)); // 2014-01-01
System.out.println(parser.parse("201411", LocalDate::from)); // 2014-11-01
System.out.println(parser.parse("20141130", LocalDate::from)); // 2014-11-30

请注意默认元素在构建器中的位置。并不是在开始时调用它们,而是在结束时调用它们,因为不幸的是,默认元素的处理在 java.time 中是位置敏感的。另外,我还在第一个可选部分中添加了一个额外的可选部分。对于我来说,此解决方案似乎更干净,而不是使用Danila Zharenkov建议的使用3个可选节的序列,因为后者也可以解析具有更多数字的完全不同的输入(可能滥用可选节来替代or模式,尤其是宽大的情况)

Pay attention to the position of the defaulting elements in the builder. They are not called at the start but at the end because the processing of defaulting elements is unfortunately position-sensitive in java.time. And I have also added an extra optional section for the day of month inside the first optional section. This solution seems to be cleaner for me instead of using a sequence of 3 optional sections as suggested by Danila Zharenkov because latter one could also parse quite different inputs with many more digits (possible misuse of optional sections as replacement for or-patterns especially in lenient parsing).

关于默认元素的位置敏感行为,此处引自 API文档

About position-sensitive behaviour of defaulting elements here a citation from API-documentation:


在解析期间,将检查解析的当前状态。如果
指定字段没有关联值,因为此时尚未成功解析
,则指定值是将
注入到解析结果中。注入是立即进行的,因此
字段值对将对
格式化程序中的任何后续元素可见。因此,通常在
构建器的末尾调用此方法。

During parsing, the current state of the parse is inspected. If the specified field has no associated value, because it has not been parsed successfully at that point, then the specified value is injected into the parse result. Injection is immediate, thus the field-value pair will be visible to any subsequent elements in the formatter. As such, this method is normally called at the end of the builder.






顺便说一句:在我的lib Time4J中,我还可以使用符号 |定义实数或模式然后创建此格式化程序:


By the way: In my lib Time4J I can also define real or-patterns using the symbol "|" and then create this formatter:

ChronoFormatter<LocalDate> cf =
    ChronoFormatter
        .ofPattern(
            "yyyyMMdd|yyyyMM|yyyy",
            PatternType.CLDR,
            Locale.ROOT,
            PlainDate.axis(TemporalType.LOCAL_DATE)
        )
        .withDefault(PlainDate.MONTH_AS_NUMBER, 1)
        .withDefault(PlainDate.DAY_OF_MONTH, 1)
        .with(Leniency.STRICT);

这篇关于具有可选模式的Java DateTimeFormatterBuilder导致DateTimeParseException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆