FULL PRODUCT VERSION :
java version "1.8.0-ea"
Java(TM) SE Runtime Environment (build 1.8.0-ea-b119)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b61, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Linux 3.8.0-35-generic #50~precise1-Ubuntu SMP Wed Dec 4 17:25:51 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
A DESCRIPTION OF THE PROBLEM :
Demonstration of issue:
DateTimeFormatter.ofPattern("yyyyMMddHHmmss" ).parse("20140115010203" , LocalDateTime::from); // Works as expected.
DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS").parse("20140115010203004", LocalDateTime::from); // Throws DateTimeParseException
The issue appears to be that DateTimeFormatterBuilder does not adjust the subsequentWidth value of the base printer parser on processing the millisecond part of pattern.
Here is a walk through of the issue.
The DateTimeFormatterBuilder works through the elements of the pattern.
Here we see the stack trace when the printer parser for the seconds part of the pattern is appended to the list of printer parsers:
DateTimeFormatterBuilder.appendInternal(DateTimeFormatterBuilder$DateTimePrinterParser) line: 2008
DateTimeFormatterBuilder.appendValue(DateTimeFormatterBuilder$NumberPrinterParser) line: 635
DateTimeFormatterBuilder.appendValue(TemporalField, int) line: 446
DateTimeFormatterBuilder.parseField(char, int, TemporalField) line: 1805
DateTimeFormatterBuilder.parsePattern(String) line: 1604
DateTimeFormatterBuilder.appendPattern(String) line: 1572
DateTimeFormatter.ofPattern(String) line: 537
To get to this point we had to pass through DateTimeFormatterBuilder.appendValue(NumberPrinterParser).
This method increases the subsequentWidth value of the base printer parser (the year printer parser in our case) by the max-width of the seconds printer parser.
So back when we first entered appendValue(NumberPrinterParser) for the year printer parser and set it as the base printer parser it had a subsequentWidth of 0.
But as the method was called for each subsequent printer parser (corresponding to the month, day, hour, minute and second part of the pattern) this was increased until it became 10.
yyyyMMddHHmmssSSS
^^^^^^^^^^ 10 characters.
However for the milliseconds part of the pattern we go through different logic as shown by this stack trace:
DateTimeFormatterBuilder.appendInternal(DateTimeFormatterBuilder$DateTimePrinterParser) line: 2008
DateTimeFormatterBuilder.appendFraction(TemporalField, int, int, boolean) line: 688
DateTimeFormatterBuilder.parseField(char, int, TemporalField) line: 1786
DateTimeFormatterBuilder.parsePattern(String) line: 1604
DateTimeFormatterBuilder.appendPattern(String) line: 1572
DateTimeFormatter.ofPattern(String) line: 537
Here we go throught appendFraction(TemporalField, int, int, boolean) and this does not adjust the subsequentWidth of the base parser.
So the subsequentWidth value of the year parser ends up being 10 whether or not we're working with the pattern above that has a milliseconds portion or the one that does not.
Now we can see how this causes the parse example shown above to fail...
For the case where the pattern has no milliseconds portion the parsing of the year goes perfectly.
In the year printer parser the first scan, to build up the long value representing the year, occurs and then effMaxWidth is recalculated to be 4 here:
DateTimeFormatterBuilder$NumberPrinterParser.parse(DateTimeParseContext, CharSequence, int) line: 2662
DateTimeFormatterBuilder$CompositePrinterParser.parse(DateTimeParseContext, CharSequence, int) line: 2211
DateTimeFormatter.parseUnresolved0(CharSequence, ParsePosition) line: 2005
DateTimeFormatter.parseResolved0(CharSequence, ParsePosition) line: 1938
DateTimeFormatter.parse(CharSequence, TemporalQuery<T>) line: 1850
Then the second scan occurs and the long value 2014 is built up. Then we successfully proceed through the remaining logic to the end of the method and call setValue(DateTimeParseContext, long, int, int) passing in the 2014 value:
DateTimeFormatterBuilder$NumberPrinterParser.setValue(DateTimeParseContext, long, int, int) line: 2715
DateTimeFormatterBuilder$NumberPrinterParser.parse(DateTimeParseContext, CharSequence, int) line: 2702
DateTimeFormatterBuilder$CompositePrinterParser.parse(DateTimeParseContext, CharSequence, int) line: 2211
Things don't go quite so well in the case where the pattern has a milliseconds portion.
The first scan occurs as before but when it comes to recalculating effMaxWidth at the end of this scan parseLen has a value of 17 and subsequentWidth has a value of 10 and so effMaxWidth ends up as 7 rather than 4, i.e. were out by the length of the milliseconds portion of the pattern - that we saw was not taken into account in subsequentWidth.
So here the second scan consumes 3 characters more than it should and builds up a year value of 2014011.
And then at the end of this scan, instead of proceeding to the end of the method, we hit the logic that causes us to return ~position as parseLen is greater than minWidth.
This causes us to bubble back up and first set the error index (as our position is negative):
ParsePosition.setErrorIndex(int) line: 106
DateTimeFormatter.parseUnresolved0(CharSequence, ParsePosition) line: 2007
DateTimeFormatter.parseResolved0(CharSequence, ParsePosition) line: 1938
DateTimeFormatter.parse(CharSequence, TemporalQuery<T>) line: 1850
And then throw a DateTimeParseException exception:
DateTimeParseException.<init>(String, CharSequence, int) line: 100
DateTimeFormatter.parseResolved0(CharSequence, ParsePosition) line: 1948
DateTimeFormatter.parse(CharSequence, TemporalQuery<T>) line: 1850
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
LocalDateTime dateTime = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS").parse("20140115010203004", LocalDateTime::from);
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
A LocalDateTime reflecting the results of parsing "20140115010203004" according to the pattern "yyyyMMddHHmmssSSS" should be assigned to dateTime.
ACTUAL -
A DateTimeParseException is thrown by the parse logic.
ERROR MESSAGES/STACK TRACES THAT OCCUR :
Exception in thread "main" java.time.format.DateTimeParseException: Text '20140115010203004' could not be parsed at index 0
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1948)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1850)
at DateTimeFormatterTest.testMilliPattern(DateTimeFormatterTest.java:15)
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.junit.Test;
public class DateTimeFormatterTest {
@Test
public void testNoMilliPattern() {
DateTimeFormatter.ofPattern("yyyyMMddHHmmss").parse("20140115010203", LocalDateTime::from);
}
@Test
public void testMilliPattern() {
DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS").parse("20140115010203004", LocalDateTime::from);
}
}
---------- END SOURCE ----------
java version "1.8.0-ea"
Java(TM) SE Runtime Environment (build 1.8.0-ea-b119)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b61, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Linux 3.8.0-35-generic #50~precise1-Ubuntu SMP Wed Dec 4 17:25:51 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
A DESCRIPTION OF THE PROBLEM :
Demonstration of issue:
DateTimeFormatter.ofPattern("yyyyMMddHHmmss" ).parse("20140115010203" , LocalDateTime::from); // Works as expected.
DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS").parse("20140115010203004", LocalDateTime::from); // Throws DateTimeParseException
The issue appears to be that DateTimeFormatterBuilder does not adjust the subsequentWidth value of the base printer parser on processing the millisecond part of pattern.
Here is a walk through of the issue.
The DateTimeFormatterBuilder works through the elements of the pattern.
Here we see the stack trace when the printer parser for the seconds part of the pattern is appended to the list of printer parsers:
DateTimeFormatterBuilder.appendInternal(DateTimeFormatterBuilder$DateTimePrinterParser) line: 2008
DateTimeFormatterBuilder.appendValue(DateTimeFormatterBuilder$NumberPrinterParser) line: 635
DateTimeFormatterBuilder.appendValue(TemporalField, int) line: 446
DateTimeFormatterBuilder.parseField(char, int, TemporalField) line: 1805
DateTimeFormatterBuilder.parsePattern(String) line: 1604
DateTimeFormatterBuilder.appendPattern(String) line: 1572
DateTimeFormatter.ofPattern(String) line: 537
To get to this point we had to pass through DateTimeFormatterBuilder.appendValue(NumberPrinterParser).
This method increases the subsequentWidth value of the base printer parser (the year printer parser in our case) by the max-width of the seconds printer parser.
So back when we first entered appendValue(NumberPrinterParser) for the year printer parser and set it as the base printer parser it had a subsequentWidth of 0.
But as the method was called for each subsequent printer parser (corresponding to the month, day, hour, minute and second part of the pattern) this was increased until it became 10.
yyyyMMddHHmmssSSS
^^^^^^^^^^ 10 characters.
However for the milliseconds part of the pattern we go through different logic as shown by this stack trace:
DateTimeFormatterBuilder.appendInternal(DateTimeFormatterBuilder$DateTimePrinterParser) line: 2008
DateTimeFormatterBuilder.appendFraction(TemporalField, int, int, boolean) line: 688
DateTimeFormatterBuilder.parseField(char, int, TemporalField) line: 1786
DateTimeFormatterBuilder.parsePattern(String) line: 1604
DateTimeFormatterBuilder.appendPattern(String) line: 1572
DateTimeFormatter.ofPattern(String) line: 537
Here we go throught appendFraction(TemporalField, int, int, boolean) and this does not adjust the subsequentWidth of the base parser.
So the subsequentWidth value of the year parser ends up being 10 whether or not we're working with the pattern above that has a milliseconds portion or the one that does not.
Now we can see how this causes the parse example shown above to fail...
For the case where the pattern has no milliseconds portion the parsing of the year goes perfectly.
In the year printer parser the first scan, to build up the long value representing the year, occurs and then effMaxWidth is recalculated to be 4 here:
DateTimeFormatterBuilder$NumberPrinterParser.parse(DateTimeParseContext, CharSequence, int) line: 2662
DateTimeFormatterBuilder$CompositePrinterParser.parse(DateTimeParseContext, CharSequence, int) line: 2211
DateTimeFormatter.parseUnresolved0(CharSequence, ParsePosition) line: 2005
DateTimeFormatter.parseResolved0(CharSequence, ParsePosition) line: 1938
DateTimeFormatter.parse(CharSequence, TemporalQuery<T>) line: 1850
Then the second scan occurs and the long value 2014 is built up. Then we successfully proceed through the remaining logic to the end of the method and call setValue(DateTimeParseContext, long, int, int) passing in the 2014 value:
DateTimeFormatterBuilder$NumberPrinterParser.setValue(DateTimeParseContext, long, int, int) line: 2715
DateTimeFormatterBuilder$NumberPrinterParser.parse(DateTimeParseContext, CharSequence, int) line: 2702
DateTimeFormatterBuilder$CompositePrinterParser.parse(DateTimeParseContext, CharSequence, int) line: 2211
Things don't go quite so well in the case where the pattern has a milliseconds portion.
The first scan occurs as before but when it comes to recalculating effMaxWidth at the end of this scan parseLen has a value of 17 and subsequentWidth has a value of 10 and so effMaxWidth ends up as 7 rather than 4, i.e. were out by the length of the milliseconds portion of the pattern - that we saw was not taken into account in subsequentWidth.
So here the second scan consumes 3 characters more than it should and builds up a year value of 2014011.
And then at the end of this scan, instead of proceeding to the end of the method, we hit the logic that causes us to return ~position as parseLen is greater than minWidth.
This causes us to bubble back up and first set the error index (as our position is negative):
ParsePosition.setErrorIndex(int) line: 106
DateTimeFormatter.parseUnresolved0(CharSequence, ParsePosition) line: 2007
DateTimeFormatter.parseResolved0(CharSequence, ParsePosition) line: 1938
DateTimeFormatter.parse(CharSequence, TemporalQuery<T>) line: 1850
And then throw a DateTimeParseException exception:
DateTimeParseException.<init>(String, CharSequence, int) line: 100
DateTimeFormatter.parseResolved0(CharSequence, ParsePosition) line: 1948
DateTimeFormatter.parse(CharSequence, TemporalQuery<T>) line: 1850
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
LocalDateTime dateTime = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS").parse("20140115010203004", LocalDateTime::from);
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
A LocalDateTime reflecting the results of parsing "20140115010203004" according to the pattern "yyyyMMddHHmmssSSS" should be assigned to dateTime.
ACTUAL -
A DateTimeParseException is thrown by the parse logic.
ERROR MESSAGES/STACK TRACES THAT OCCUR :
Exception in thread "main" java.time.format.DateTimeParseException: Text '20140115010203004' could not be parsed at index 0
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1948)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1850)
at DateTimeFormatterTest.testMilliPattern(DateTimeFormatterTest.java:15)
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.junit.Test;
public class DateTimeFormatterTest {
@Test
public void testNoMilliPattern() {
DateTimeFormatter.ofPattern("yyyyMMddHHmmss").parse("20140115010203", LocalDateTime::from);
}
@Test
public void testMilliPattern() {
DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS").parse("20140115010203004", LocalDateTime::from);
}
}
---------- END SOURCE ----------
- duplicates
-
JDK-8031085 DateTimeFormatter won't parse dates with custom format "yyyyMMddHHmmssSSS"
- Resolved