8031085: DateTimeFormatter won't parse dates with custom format "yyyyMMddHHmmssSSS"
authorntv
Mon, 25 Apr 2016 09:37:43 +0000
changeset 37610 12f7514dccc4
parent 37609 332ee9a0ec11
child 37611 46a18ff2e16e
8031085: DateTimeFormatter won't parse dates with custom format "yyyyMMddHHmmssSSS" Summary: Changed the FractionPrinterParser to subclass of NumberPrinterParser to make it participate in adjacent value parsing Reviewed-by: rriggs, scolebourne
jdk/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java
jdk/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java
--- a/jdk/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java	Thu Mar 03 12:47:46 2016 +0100
+++ b/jdk/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java	Mon Apr 25 09:37:43 2016 +0000
@@ -667,8 +667,11 @@
      * No rounding occurs due to the maximum width - digits are simply dropped.
      * <p>
      * When parsing in strict mode, the number of parsed digits must be between
-     * the minimum and maximum width. When parsing in lenient mode, the minimum
-     * width is considered to be zero and the maximum is nine.
+     * the minimum and maximum width. In strict mode, if the minimum and maximum widths
+     * are equal and there is no decimal point then the parser will
+     * participate in adjacent value parsing, see
+     * {@link appendValue(java.time.temporal.TemporalField, int)}. When parsing in lenient mode,
+     * the minimum width is considered to be zero and the maximum is nine.
      * <p>
      * If the value cannot be obtained then an exception will be thrown.
      * If the value is negative an exception will be thrown.
@@ -687,7 +690,12 @@
      */
     public DateTimeFormatterBuilder appendFraction(
             TemporalField field, int minWidth, int maxWidth, boolean decimalPoint) {
-        appendInternal(new FractionPrinterParser(field, minWidth, maxWidth, decimalPoint));
+        if (minWidth == maxWidth && decimalPoint == false) {
+            // adjacent parsing
+            appendValue(new FractionPrinterParser(field, minWidth, maxWidth, decimalPoint));
+        } else {
+            appendInternal(new FractionPrinterParser(field, minWidth, maxWidth, decimalPoint));
+        }
         return this;
     }
 
@@ -2925,11 +2933,8 @@
     /**
      * Prints and parses a numeric date-time field with optional padding.
      */
-    static final class FractionPrinterParser implements DateTimePrinterParser {
-        private final TemporalField field;
-        private final int minWidth;
-        private final int maxWidth;
-        private final boolean decimalPoint;
+    static final class FractionPrinterParser extends NumberPrinterParser {
+       private final boolean decimalPoint;
 
         /**
          * Constructor.
@@ -2940,6 +2945,7 @@
          * @param decimalPoint  whether to output the localized decimal point symbol
          */
         FractionPrinterParser(TemporalField field, int minWidth, int maxWidth, boolean decimalPoint) {
+            this(field, minWidth, maxWidth, decimalPoint, 0);
             Objects.requireNonNull(field, "field");
             if (field.range().isFixed() == false) {
                 throw new IllegalArgumentException("Field must have a fixed set of values: " + field);
@@ -2954,12 +2960,61 @@
                 throw new IllegalArgumentException("Maximum width must exceed or equal the minimum width but " +
                         maxWidth + " < " + minWidth);
             }
-            this.field = field;
-            this.minWidth = minWidth;
-            this.maxWidth = maxWidth;
+        }
+
+        /**
+         * Constructor.
+         *
+         * @param field  the field to output, not null
+         * @param minWidth  the minimum width to output, from 0 to 9
+         * @param maxWidth  the maximum width to output, from 0 to 9
+         * @param decimalPoint  whether to output the localized decimal point symbol
+         * @param subsequentWidth the subsequentWidth for this instance
+         */
+        FractionPrinterParser(TemporalField field, int minWidth, int maxWidth, boolean decimalPoint, int subsequentWidth) {
+            super(field, minWidth, maxWidth, SignStyle.NOT_NEGATIVE, subsequentWidth);
             this.decimalPoint = decimalPoint;
         }
 
+        /**
+         * Returns a new instance with fixed width flag set.
+         *
+         * @return a new updated printer-parser, not null
+         */
+        @Override
+        FractionPrinterParser withFixedWidth() {
+            if (subsequentWidth == -1) {
+                return this;
+            }
+            return new FractionPrinterParser(field, minWidth, maxWidth, decimalPoint, -1);
+        }
+
+        /**
+         * Returns a new instance with an updated subsequent width.
+         *
+         * @param subsequentWidth  the width of subsequent non-negative numbers, 0 or greater
+         * @return a new updated printer-parser, not null
+         */
+        @Override
+        FractionPrinterParser withSubsequentWidth(int subsequentWidth) {
+            return new FractionPrinterParser(field, minWidth, maxWidth, decimalPoint, this.subsequentWidth + subsequentWidth);
+        }
+
+        /**
+         * For FractionPrinterPrinterParser, the width is fixed if context is sttrict,
+         * minWidth equal to maxWidth and decimalpoint is absent.
+         * @param context the context
+         * @return if the field is fixed width
+         * @see DateTimeFormatterBuilder#appendValueFraction(java.time.temporal.TemporalField, int, int, boolean)
+         */
+        @Override
+        boolean isFixedWidth(DateTimeParseContext context) {
+            if (context.isStrict() && minWidth == maxWidth && decimalPoint == false) {
+                return true;
+            }
+            return false;
+        }
+
         @Override
         public boolean format(DateTimePrintContext context, StringBuilder buf) {
             Long value = context.getValue(field);
@@ -2992,8 +3047,8 @@
 
         @Override
         public int parse(DateTimeParseContext context, CharSequence text, int position) {
-            int effectiveMin = (context.isStrict() ? minWidth : 0);
-            int effectiveMax = (context.isStrict() ? maxWidth : 9);
+            int effectiveMin = (context.isStrict() || isFixedWidth(context) ? minWidth : 0);
+            int effectiveMax = (context.isStrict() || isFixedWidth(context) ? maxWidth : 9);
             int length = text.length();
             if (position == length) {
                 // valid if whole field is optional, invalid if minimum width
--- a/jdk/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java	Thu Mar 03 12:47:46 2016 +0100
+++ b/jdk/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java	Mon Apr 25 09:37:43 2016 +0000
@@ -71,6 +71,7 @@
 
 import java.text.ParsePosition;
 import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.time.YearMonth;
 import java.time.ZoneOffset;
 import java.time.format.DateTimeFormatter;
@@ -930,7 +931,7 @@
 
     @Test
     public void test_adjacent_lenient_fractionFollows_0digit() throws Exception {
-        // succeeds because hour/min are fixed width
+        // succeeds because hour, min and fraction of seconds are fixed width
         DateTimeFormatter f = builder.parseLenient().appendValue(HOUR_OF_DAY, 2).appendValue(MINUTE_OF_HOUR, 2).appendFraction(NANO_OF_SECOND, 3, 3, false).toFormatter(Locale.UK);
         ParsePosition pp = new ParsePosition(0);
         TemporalAccessor parsed = f.parseUnresolved("1230", pp);
@@ -940,6 +941,21 @@
         assertEquals(parsed.getLong(MINUTE_OF_HOUR), 30L);
     }
 
+    @DataProvider(name="adjacentFractionParseData")
+    Object[][] data_adjacent_fraction_parse() {
+        return new Object[][] {
+            {"20130812214600025", "yyyyMMddHHmmssSSS", LocalDateTime.of(2013, 8, 12, 21, 46, 00, 25000000)},
+            {"201308122146000256", "yyyyMMddHHmmssSSSS", LocalDateTime.of(2013, 8, 12, 21, 46, 00, 25600000)},
+        };
+    }
+
+    @Test(dataProvider = "adjacentFractionParseData")
+    public void test_adjacent_fraction(String input, String pattern, LocalDateTime expected) {
+        DateTimeFormatter dtf = DateTimeFormatter.ofPattern(pattern);
+        LocalDateTime actual = LocalDateTime.parse(input, dtf);
+        assertEquals(actual, expected);
+    }
+
     @DataProvider(name="lenientOffsetParseData")
     Object[][] data_lenient_offset_parse() {
         return new Object[][] {