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)
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");
As a supplement to the correct and helpful answer by Sweeper this answer finds the relevant documentation. You asked why your parsing threw an exception, and Sweeper already correctly said that it’s because yyyy denotes a variable-width field. I want to show you the two places in the documentation where this is specified. Since for example MM gives a fixed-width field of exactly two digits, one can easily get surprised when neither yyyy nor uuuu gives a fixed-width 4-digit field.
The documentation of DateTimeFormatterBuilder.appendPattern() first refers to DateTimeFormatter for a user-focused description of the patterns. It in turn says specifically about years:
The count of letters determines the minimum field width below which padding is used. … If the count of letters is less than four … Otherwise, the sign is output if the pad width is exceeded, as per
SignStyle.EXCEEDS_PAD.
So this allows yyyy to print, and as a consequence also parse a year with either 4 digits or more than four digits with a sign.
The documentation of DateTimeFormatterBuilder.appendPattern() goes on to specify that appending a pattern of four or more letters y is equivalent to appendValue(ChronoField.YEAR_OF_ERA, n, 19, SignStyle.EXCEEDS_PAD) where n is the count of letters. We see that yyyy allows a field of width 4 through 19.
Why are years different from months, days, hours, etc.? The above may already give a hint why it would be unreasonable for yyyy to give us a fixed-width field. We already know that months go up to 12, in some calendar systems up to 13 or a little more, but they are not allowed to outgrow 2 digits. Java supports years up to 999 999 999 in some places, occasionally up to 1 000 000 000, so forbidding more than 4 digits would give us trouble in other situations. Therefore the special and different rule for years.
Links
DateTimeFormatterBuilder.appendPattern() documentationDateTimeFormatter patterns documentationuuuu versus yyyy in DateTimeFormatter formatting pattern codes? on Stack OverflowIf you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With