Java datetimeformatterbuilder with optional mode causes datetimeparseexception
target
Provide a flexible parser for localdate instances, which can process input in one of the following formats:
> yyyy > yyyyMM > yyyyMMdd
Implementation attempt
The following class attempts to handle the first and second patterns The work year input is parsed, but the month and year result in the exceptions listed 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 } }
The second parse() attempt resulted 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 the optional partial pattern works is lacking My goal is a flexible parser that can even be implemented, or do I need to check the input length and choose from a list of parsers? As always, thank you for your help
Solution
The real cause of the problem is signature processing Your input doesn't have any symbols, but the parser element "yyyy" greedily parses as many numbers as possible and expects a positive sign because more than four digits were found
My analysis is done in two different ways:
>Debug (to see the real situation behind ambiguous error messages) > simulate the behavior in another parsing engine based on my lib time4j to get better error messages:
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.
You can circumvent the problem by instructing the builder to always use only four digits of the year:
DateTimeFormatter parser = new DateTimeFormatterBuilder() .appendValue(ChronoField.YEAR,4) .optionalStart() .appendPattern("MM[dd]") .optionalEnd() .parseDefaulting(ChronoField.MONTH_OF_YEAR,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
Note the location of the default element in the builder They are not invoked at the beginning, but are called at last, because unfortunately, in java. The processing of default elements in time is location - sensitive I also added an additional optional part inside the first optional part This solution seems cleaner to me, rather than using a sequence of three optional parts as suggested by Danila zharenkov, because the latter can also parse quite different inputs with more numbers (optional parts may be abused as replacement or patterns, especially in loose parsing)
For location sensitive behavior of default elements, API documentation is referenced here:
By the way: in my lib time4j, I can also use the symbol "|" to define a real or schema, and then create this formatter:
ChronoFormatter<LocalDate> cf = ChronoFormatter .ofPattern( "yyyyMMdd|yyyyMM|yyyy",PatternType.CLDR,PlainDate.axis(TemporalType.LOCAL_DATE) ) .withDefault(PlainDate.MONTH_AS_NUMBER,1) .withDefault(PlainDate.DAY_OF_MONTH,1) .with(Leniency.STRICT);