javadatetimejava-timedatetimeformatter

Why does parsing with this DateTimeFormatter throw an exception?


Both of these DateTimeFormatters are nearly identical, however only the second one throws an exception. Both of these patterns are valid as far as I can tell.

Why does changing minWidth cause parsing to fail in the second case?

I'm using Java 21.

This works, output is {},ISO resolved to 2024-12-31T23:59:59.123

var result1 = new DateTimeFormatterBuilder()
        .appendPattern("yyyyMMddHHmmss")
        .appendFraction(ChronoField.NANO_OF_SECOND, 3, 3, false) // minWidth is 3
        .toFormatter()
        .parse("20241231235959123");
System.out.println(result1);

This throws an exception:

var result2 = new DateTimeFormatterBuilder()
        .appendPattern("yyyyMMddHHmmss")
        .appendFraction(ChronoField.NANO_OF_SECOND, 1, 3, false) // minWidth is 1
        .toFormatter()
        .parse("20241231235959123");
System.out.println(result2);

The exception:

Exception in thread "main" java.time.format.DateTimeParseException: Text '20241231235959123' could not be parsed at index 0
    at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:2108)
    at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1936)
    at Main.main(Main.java:24)

Solution

  • This is because the pattern yyyy is a variable-width pattern (it can parse between 4 and 19 digits), and the fractional part is also a variable-width pattern. The parser has no way of determining which numbers belong to which part.

    Consider a simpler example:

    var result = new DateTimeFormatterBuilder()
        .appendPattern("yyyy")
        .appendFraction(ChronoField.NANO_OF_SECOND, 1, 3, false)
        .toFormatter()
        .parse("123456");
    

    What is the year in "123456"? Is it 1234, and 56 is fractional part? Or is it 12345, and 6 is the fractional part? This is ambiguous.

    You need to add some other delimiter (e.g. set decimal point to true), or limit the year to a fixed width.

    var result = new DateTimeFormatterBuilder()
        .appendValue(ChronoField.YEAR, 4) // fixed year to 4 digits
        .appendPattern("MMddHHmmss")
        .appendFraction(ChronoField.NANO_OF_SECOND, 1, 3, false) // minWidth is 1
        .toFormatter()
        .parse("20241231235959123");