changeset 16372:bd9dd28d7b4f

8145633: Adjacent value parsing not supported for Localized Patterns Summary: Enhance the localized weekfields to take part in adjacent value parsing Reviewed-by: rriggs, scolebourne
author ntv
date Wed, 21 Dec 2016 18:45:34 +0000
parents 70f9673ebcd6
children 66b07872e356
files src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java test/java/time/tck/java/time/format/TCKLocalizedFieldParser.java
diffstat 2 files changed, 152 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java	Wed Dec 21 07:49:36 2016 -0800
+++ b/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java	Wed Dec 21 18:45:34 2016 +0000
@@ -1774,16 +1774,20 @@
                     if (count > 1) {
                         throw new IllegalArgumentException("Too many pattern letters: " + cur);
                     }
-                    appendInternal(new WeekBasedFieldPrinterParser(cur, count));
+                    appendValue(new WeekBasedFieldPrinterParser(cur, count, count, count));
                 } else if (cur == 'w') {
                     // Fields defined by Locale
                     if (count > 2) {
                         throw new IllegalArgumentException("Too many pattern letters: " + cur);
                     }
-                    appendInternal(new WeekBasedFieldPrinterParser(cur, count));
+                    appendValue(new WeekBasedFieldPrinterParser(cur, count, count, 2));
                 } else if (cur == 'Y') {
                     // Fields defined by Locale
-                    appendInternal(new WeekBasedFieldPrinterParser(cur, count));
+                    if (count == 2) {
+                        appendValue(new WeekBasedFieldPrinterParser(cur, count, count, 2));
+                    } else {
+                        appendValue(new WeekBasedFieldPrinterParser(cur, count, count, 19));
+                    }
                 } else {
                     throw new IllegalArgumentException("Unknown pattern letter: " + cur);
                 }
@@ -1843,7 +1847,10 @@
                 }
                 break;
             case 'c':
-                if (count == 2) {
+                if (count == 1) {
+                    appendValue(new WeekBasedFieldPrinterParser(cur, count, count, count));
+                    break;
+                } else if (count == 2) {
                     throw new IllegalArgumentException("Invalid pattern \"cc\"");
                 }
                 /*fallthrough*/
@@ -1858,8 +1865,8 @@
                 switch (count) {
                     case 1:
                     case 2:
-                        if (cur == 'c' || cur == 'e') {
-                            appendInternal(new WeekBasedFieldPrinterParser(cur, count));
+                        if (cur == 'e') {
+                            appendValue(new WeekBasedFieldPrinterParser(cur, count, count, count));
                         } else if (cur == 'E') {
                             appendText(field, TextStyle.SHORT);
                         } else {
@@ -4770,8 +4777,9 @@
      * the field is to be printed or parsed.
      * The locale is needed to select the proper WeekFields from which
      * the field for day-of-week, week-of-month, or week-of-year is selected.
+     * Hence the inherited field NumberPrinterParser.field is unused.
      */
-    static final class WeekBasedFieldPrinterParser implements DateTimePrinterParser {
+    static final class WeekBasedFieldPrinterParser extends NumberPrinterParser {
         private char chr;
         private int count;
 
@@ -4780,12 +4788,55 @@
          *
          * @param chr the pattern format letter that added this PrinterParser.
          * @param count the repeat count of the format letter
+         * @param minWidth  the minimum field width, from 1 to 19
+         * @param maxWidth  the maximum field width, from minWidth to 19
          */
-        WeekBasedFieldPrinterParser(char chr, int count) {
+        WeekBasedFieldPrinterParser(char chr, int count, int minWidth, int maxWidth) {
+            this(chr, count, minWidth, maxWidth, 0);
+        }
+
+        /**
+         * Constructor.
+         *
+         * @param chr the pattern format letter that added this PrinterParser.
+         * @param count the repeat count of the format letter
+         * @param minWidth  the minimum field width, from 1 to 19
+         * @param maxWidth  the maximum field width, from minWidth to 19
+         * @param subsequentWidth  the width of subsequent non-negative numbers, 0 or greater,
+         * -1 if fixed width due to active adjacent parsing
+         */
+        WeekBasedFieldPrinterParser(char chr, int count, int minWidth, int maxWidth,
+                int subsequentWidth) {
+            super(null, minWidth, maxWidth, SignStyle.NOT_NEGATIVE, subsequentWidth);
             this.chr = chr;
             this.count = count;
         }
 
+        /**
+         * Returns a new instance with fixed width flag set.
+         *
+         * @return a new updated printer-parser, not null
+         */
+        @Override
+        WeekBasedFieldPrinterParser withFixedWidth() {
+            if (subsequentWidth == -1) {
+                return this;
+            }
+            return new WeekBasedFieldPrinterParser(chr, count, minWidth, maxWidth, -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
+        WeekBasedFieldPrinterParser withSubsequentWidth(int subsequentWidth) {
+            return new WeekBasedFieldPrinterParser(chr, count, minWidth, maxWidth,
+                    this.subsequentWidth + subsequentWidth);
+        }
+
         @Override
         public boolean format(DateTimePrintContext context, StringBuilder buf) {
             return printerParser(context.getLocale()).format(context, buf);
@@ -4810,10 +4861,12 @@
                 case 'Y':
                     field = weekDef.weekBasedYear();
                     if (count == 2) {
-                        return new ReducedPrinterParser(field, 2, 2, 0, ReducedPrinterParser.BASE_DATE, 0);
+                        return new ReducedPrinterParser(field, 2, 2, 0, ReducedPrinterParser.BASE_DATE,
+                                this.subsequentWidth);
                     } else {
                         return new NumberPrinterParser(field, count, 19,
-                                (count < 4) ? SignStyle.NORMAL : SignStyle.EXCEEDS_PAD, -1);
+                                (count < 4) ? SignStyle.NORMAL : SignStyle.EXCEEDS_PAD,
+                                this.subsequentWidth);
                     }
                 case 'e':
                 case 'c':
@@ -4828,7 +4881,8 @@
                 default:
                     throw new IllegalStateException("unreachable");
             }
-            return new NumberPrinterParser(field, (count == 2 ? 2 : 1), 2, SignStyle.NOT_NEGATIVE);
+            return new NumberPrinterParser(field, minWidth, maxWidth, SignStyle.NOT_NEGATIVE,
+                    this.subsequentWidth);
         }
 
         @Override
--- a/test/java/time/tck/java/time/format/TCKLocalizedFieldParser.java	Wed Dec 21 07:49:36 2016 -0800
+++ b/test/java/time/tck/java/time/format/TCKLocalizedFieldParser.java	Wed Dec 21 18:45:34 2016 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -66,9 +66,11 @@
 import java.time.LocalDate;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeFormatterBuilder;
+import java.time.format.DateTimeParseException;
 import java.time.temporal.TemporalAccessor;
 import java.time.temporal.TemporalField;
 import java.time.temporal.WeekFields;
+import java.util.Locale;
 
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
@@ -79,12 +81,17 @@
  */
 @Test
 public class TCKLocalizedFieldParser extends AbstractTestPrinterParser {
-
+    public static final WeekFields WEEKDEF = WeekFields.of(Locale.US);
+    public static final TemporalField WEEK_BASED_YEAR = WEEKDEF.weekBasedYear();
+    public static final TemporalField WEEK_OF_WEEK_BASED_YEAR = WEEKDEF.weekOfWeekBasedYear();
+    public static final TemporalField DAY_OF_WEEK = WEEKDEF.dayOfWeek();
     //-----------------------------------------------------------------------
     @DataProvider(name="FieldPatterns")
     Object[][] provider_fieldPatterns() {
         return new Object[][] {
-            {"e",  "6", 0, 1, 6},
+            {"e", "6", 0, 1, 6},
+            {"ee", "06", 0, 2, 6},
+            {"c",  "6", 0, 1 , 6},
             {"W",  "3", 0, 1, 3},
             {"w",  "29", 0, 2, 29},
             {"ww", "29", 0, 2, 29},
@@ -99,6 +106,7 @@
         WeekFields weekDef = WeekFields.of(locale);
         TemporalField field = null;
         switch(pattern.charAt(0)) {
+            case 'c' :
             case 'e' :
                 field = weekDef.dayOfWeek();
                 break;
@@ -176,9 +184,9 @@
             {"Y-w-e",  "2008-01-1", 0, 9, LocalDate.of(2007, 12, 30)},
             {"Y-w-e",  "2008-52-1", 0, 9, LocalDate.of(2008, 12, 21)},
             {"Y-w-e",  "2008-52-7", 0, 9, LocalDate.of(2008, 12, 27)},
-            {"Y-w-e",  "2009-01-01", 0, 10, LocalDate.of(2008, 12, 28)},
-            {"Y-w-e",  "2009-01-04", 0, 10, LocalDate.of(2008, 12, 31)},
-            {"Y-w-e",  "2009-01-05", 0, 10, LocalDate.of(2009, 1, 1)},
+            {"Y-w-e",  "2009-01-1", 0, 9, LocalDate.of(2008, 12, 28)},
+            {"Y-w-e",  "2009-01-4", 0, 9, LocalDate.of(2008, 12, 31)},
+            {"Y-w-e",  "2009-01-5", 0, 9, LocalDate.of(2009, 1, 1)},
        };
     }
 
@@ -202,4 +210,77 @@
         }
     }
 
+    //-----------------------------------------------------------------------
+    @DataProvider(name = "adjacentValuePatterns1")
+    Object[][] provider_adjacentValuePatterns1() {
+        return new Object[][] {
+                {"YYww", WEEK_BASED_YEAR, WEEK_OF_WEEK_BASED_YEAR, "1612", 2016, 12},
+                {"YYYYww", WEEK_BASED_YEAR, WEEK_OF_WEEK_BASED_YEAR, "201612", 2016, 12},
+        };
+    }
+
+    @Test(dataProvider = "adjacentValuePatterns1")
+    public void test_adjacentValuePatterns1(String pattern, TemporalField field1, TemporalField field2,
+            String text, int expected1, int expected2) {
+        DateTimeFormatter df = new DateTimeFormatterBuilder()
+                .appendPattern(pattern).toFormatter(Locale.US);
+        ParsePosition ppos = new ParsePosition(0);
+        TemporalAccessor parsed = df.parseUnresolved(text, ppos);
+        assertEquals(parsed.get(field1), expected1);
+        assertEquals(parsed.get(field2), expected2);
+    }
+
+    @DataProvider(name = "adjacentValuePatterns2")
+    Object[][] provider_adjacentValuePatterns2() {
+        return new Object[][] {
+                {"YYYYwwc", WEEK_BASED_YEAR, WEEK_OF_WEEK_BASED_YEAR, DAY_OF_WEEK,
+                        "2016121", 2016, 12, 1},
+                {"YYYYwwee", WEEK_BASED_YEAR, WEEK_OF_WEEK_BASED_YEAR, DAY_OF_WEEK,
+                        "20161201", 2016, 12, 1},
+                {"YYYYwwe", WEEK_BASED_YEAR, WEEK_OF_WEEK_BASED_YEAR, DAY_OF_WEEK,
+                        "2016121", 2016, 12, 1},
+        };
+    }
+
+    @Test(dataProvider = "adjacentValuePatterns2")
+    public void test_adjacentValuePatterns2(String pattern, TemporalField field1, TemporalField field2,
+            TemporalField field3, String text, int expected1, int expected2, int expected3) {
+        DateTimeFormatter df = new DateTimeFormatterBuilder()
+                .appendPattern(pattern).toFormatter(Locale.US);
+        ParsePosition ppos = new ParsePosition(0);
+        TemporalAccessor parsed = df.parseUnresolved(text, ppos);
+        assertEquals(parsed.get(field1), expected1);
+        assertEquals(parsed.get(field2), expected2);
+        assertEquals(parsed.get(field3), expected3);
+    }
+
+    @Test
+    public void test_adjacentValuePatterns3() {
+        String pattern = "yyyyMMddwwc";
+        String text =  "20120720296";
+        DateTimeFormatter df = new DateTimeFormatterBuilder()
+                .appendPattern(pattern).toFormatter(Locale.US);
+        ParsePosition ppos = new ParsePosition(0);
+        TemporalAccessor parsed = df.parseUnresolved(text, ppos);
+        assertEquals(parsed.get(DAY_OF_WEEK), 6);
+        assertEquals(parsed.get(WEEK_OF_WEEK_BASED_YEAR), 29);
+        LocalDate result = LocalDate.parse(text, df);
+        LocalDate expectedValue = LocalDate.of(2012, 07, 20);
+        assertEquals(result, expectedValue, "LocalDate incorrect for " + pattern);
+    }
+
+    @DataProvider(name = "invalidPatterns")
+    Object[][] provider_invalidPatterns() {
+        return new Object[][] {
+            {"W", "01"},
+            {"c", "01"},
+            {"e", "01"},
+            {"yyyyMMddwwc", "201207202906"}, //  1 extra digit in the input
+        };
+    }
+
+    @Test(dataProvider = "invalidPatterns", expectedExceptions = DateTimeParseException.class)
+    public void test_invalidPatterns(String pattern, String value) {
+        DateTimeFormatter.ofPattern(pattern).parse(value);
+    }
 }