8032491: DateTimeFormatter fixed width adjacent value parsing does not match spec
authorrriggs
Sat, 29 Mar 2014 15:01:47 -0400
changeset 23595 08ca64c3f0e4
parent 23594 505c3a4eb0d6
child 23596 ac4e06be5763
8032491: DateTimeFormatter fixed width adjacent value parsing does not match spec Reviewed-by: lancea, rriggs Contributed-by: Stephen Colebourne <scolebourne@joda.org>
jdk/src/share/classes/java/time/format/DateTimeFormatterBuilder.java
jdk/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java
jdk/test/java/time/test/java/time/format/TestReducedParser.java
--- a/jdk/src/share/classes/java/time/format/DateTimeFormatterBuilder.java	Sat Mar 29 12:29:21 2014 +0400
+++ b/jdk/src/share/classes/java/time/format/DateTimeFormatterBuilder.java	Sat Mar 29 15:01:47 2014 -0400
@@ -2596,8 +2596,16 @@
             return value;
         }
 
-        boolean isFixedWidth() {
-            return subsequentWidth == -1;
+        /**
+         * For NumberPrinterParser, the width is fixed depending on the
+         * minWidth, maxWidth, signStyle and whether subsequent fields are fixed.
+         * @param context the context
+         * @return true if the field is fixed width
+         * @see DateTimeFormatterBuilder#appendValue(java.time.temporal.TemporalField, int)
+         */
+        boolean isFixedWidth(DateTimeParseContext context) {
+            return subsequentWidth == -1 ||
+                (subsequentWidth > 0 && minWidth == maxWidth && signStyle == SignStyle.NOT_NEGATIVE);
         }
 
         @Override
@@ -2626,12 +2634,12 @@
                     return ~position;
                 }
             }
-            int effMinWidth = (context.isStrict() || isFixedWidth() ? minWidth : 1);
+            int effMinWidth = (context.isStrict() || isFixedWidth(context) ? minWidth : 1);
             int minEndPos = position + effMinWidth;
             if (minEndPos > length) {
                 return ~position;
             }
-            int effMaxWidth = (context.isStrict() || isFixedWidth() ? maxWidth : 9) + Math.max(subsequentWidth, 0);
+            int effMaxWidth = (context.isStrict() || isFixedWidth(context) ? maxWidth : 9) + Math.max(subsequentWidth, 0);
             long total = 0;
             BigInteger totalBig = null;
             int pos = position;
@@ -2866,6 +2874,21 @@
                     this.subsequentWidth + subsequentWidth);
         }
 
+        /**
+         * For a ReducedPrinterParser, fixed width is false if the mode is strict,
+         * otherwise it is set as for NumberPrinterParser.
+         * @param context the context
+         * @return if the field is fixed width
+         * @see DateTimeFormatterBuilder#appendValueReduced(java.time.temporal.TemporalField, int, int, int)
+         */
+        @Override
+        boolean isFixedWidth(DateTimeParseContext context) {
+           if (context.isStrict() == false) {
+               return false;
+           }
+           return super.isFixedWidth(context);
+        }
+
         @Override
         public String toString() {
             return "ReducedValue(" + field + "," + minWidth + "," + maxWidth + "," + (baseDate != null ? baseDate : baseValue) + ")";
--- a/jdk/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java	Sat Mar 29 12:29:21 2014 +0400
+++ b/jdk/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java	Sat Mar 29 15:01:47 2014 -0400
@@ -60,11 +60,14 @@
 package tck.java.time.format;
 
 import static java.time.temporal.ChronoField.DAY_OF_MONTH;
+import static java.time.temporal.ChronoField.HOUR_OF_DAY;
 import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
 import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
+import static java.time.temporal.ChronoField.NANO_OF_SECOND;
 import static java.time.temporal.ChronoField.YEAR;
 import static org.testng.Assert.assertEquals;
 
+import java.text.ParsePosition;
 import java.time.LocalDate;
 import java.time.YearMonth;
 import java.time.ZoneOffset;
@@ -73,6 +76,7 @@
 import java.time.format.SignStyle;
 import java.time.format.TextStyle;
 import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAccessor;
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
@@ -728,4 +732,150 @@
         return LocalDate.of(y, m, d);
     }
 
+    //-----------------------------------------------------------------------
+    @Test
+    public void test_adjacent_strict_firstFixedWidth() throws Exception {
+        // succeeds because both number elements are fixed width
+        DateTimeFormatter f = builder.appendValue(HOUR_OF_DAY, 2).appendValue(MINUTE_OF_HOUR, 2).appendLiteral('9').toFormatter(Locale.UK);
+        ParsePosition pp = new ParsePosition(0);
+        TemporalAccessor parsed = f.parseUnresolved("12309", pp);
+        assertEquals(pp.getErrorIndex(), -1);
+        assertEquals(pp.getIndex(), 5);
+        assertEquals(parsed.getLong(HOUR_OF_DAY), 12L);
+        assertEquals(parsed.getLong(MINUTE_OF_HOUR), 30L);
+    }
+
+    @Test
+    public void test_adjacent_strict_firstVariableWidth_success() throws Exception {
+        // succeeds greedily parsing variable width, then fixed width, to non-numeric Z
+        DateTimeFormatter f = builder.appendValue(HOUR_OF_DAY).appendValue(MINUTE_OF_HOUR, 2).appendLiteral('Z').toFormatter(Locale.UK);
+        ParsePosition pp = new ParsePosition(0);
+        TemporalAccessor parsed = f.parseUnresolved("12309Z", pp);
+        assertEquals(pp.getErrorIndex(), -1);
+        assertEquals(pp.getIndex(), 6);
+        assertEquals(parsed.getLong(HOUR_OF_DAY), 123L);
+        assertEquals(parsed.getLong(MINUTE_OF_HOUR), 9L);
+    }
+
+    @Test
+    public void test_adjacent_strict_firstVariableWidth_fails() throws Exception {
+        // fails because literal is a number and variable width parse greedily absorbs it
+        DateTimeFormatter f = builder.appendValue(HOUR_OF_DAY).appendValue(MINUTE_OF_HOUR, 2).appendLiteral('9').toFormatter(Locale.UK);
+        ParsePosition pp = new ParsePosition(0);
+        TemporalAccessor parsed = f.parseUnresolved("12309", pp);
+        assertEquals(pp.getErrorIndex(), 5);
+        assertEquals(parsed, null);
+    }
+
+    @Test
+    public void test_adjacent_lenient() throws Exception {
+        // succeeds because both number elements are fixed width even in lenient mode
+        DateTimeFormatter f = builder.parseLenient().appendValue(HOUR_OF_DAY, 2).appendValue(MINUTE_OF_HOUR, 2).appendLiteral('9').toFormatter(Locale.UK);
+        ParsePosition pp = new ParsePosition(0);
+        TemporalAccessor parsed = f.parseUnresolved("12309", pp);
+        assertEquals(pp.getErrorIndex(), -1);
+        assertEquals(pp.getIndex(), 5);
+        assertEquals(parsed.getLong(HOUR_OF_DAY), 12L);
+        assertEquals(parsed.getLong(MINUTE_OF_HOUR), 30L);
+    }
+
+    @Test
+    public void test_adjacent_lenient_firstVariableWidth_success() throws Exception {
+        // succeeds greedily parsing variable width, then fixed width, to non-numeric Z
+        DateTimeFormatter f = builder.parseLenient().appendValue(HOUR_OF_DAY).appendValue(MINUTE_OF_HOUR, 2).appendLiteral('Z').toFormatter(Locale.UK);
+        ParsePosition pp = new ParsePosition(0);
+        TemporalAccessor parsed = f.parseUnresolved("12309Z", pp);
+        assertEquals(pp.getErrorIndex(), -1);
+        assertEquals(pp.getIndex(), 6);
+        assertEquals(parsed.getLong(HOUR_OF_DAY), 123L);
+        assertEquals(parsed.getLong(MINUTE_OF_HOUR), 9L);
+    }
+
+    @Test
+    public void test_adjacent_lenient_firstVariableWidth_fails() throws Exception {
+        // fails because literal is a number and variable width parse greedily absorbs it
+        DateTimeFormatter f = builder.parseLenient().appendValue(HOUR_OF_DAY).appendValue(MINUTE_OF_HOUR, 2).appendLiteral('9').toFormatter(Locale.UK);
+        ParsePosition pp = new ParsePosition(0);
+        TemporalAccessor parsed = f.parseUnresolved("12309", pp);
+        assertEquals(pp.getErrorIndex(), 5);
+        assertEquals(parsed, null);
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void test_adjacent_strict_fractionFollows() throws Exception {
+        // succeeds because hour/min are fixed width
+        DateTimeFormatter f = builder.appendValue(HOUR_OF_DAY, 2).appendValue(MINUTE_OF_HOUR, 2).appendFraction(NANO_OF_SECOND, 0, 3, false).toFormatter(Locale.UK);
+        ParsePosition pp = new ParsePosition(0);
+        TemporalAccessor parsed = f.parseUnresolved("1230567", pp);
+        assertEquals(pp.getErrorIndex(), -1);
+        assertEquals(pp.getIndex(), 7);
+        assertEquals(parsed.getLong(HOUR_OF_DAY), 12L);
+        assertEquals(parsed.getLong(MINUTE_OF_HOUR), 30L);
+        assertEquals(parsed.getLong(NANO_OF_SECOND), 567_000_000L);
+    }
+
+    @Test
+    public void test_adjacent_strict_fractionFollows_2digit() throws Exception {
+        // succeeds because hour/min are fixed width
+        DateTimeFormatter f = builder.appendValue(HOUR_OF_DAY, 2).appendValue(MINUTE_OF_HOUR, 2).appendFraction(NANO_OF_SECOND, 0, 3, false).toFormatter(Locale.UK);
+        ParsePosition pp = new ParsePosition(0);
+        TemporalAccessor parsed = f.parseUnresolved("123056", pp);
+        assertEquals(pp.getErrorIndex(), -1);
+        assertEquals(pp.getIndex(), 6);
+        assertEquals(parsed.getLong(HOUR_OF_DAY), 12L);
+        assertEquals(parsed.getLong(MINUTE_OF_HOUR), 30L);
+        assertEquals(parsed.getLong(NANO_OF_SECOND), 560_000_000L);
+    }
+
+    @Test
+    public void test_adjacent_strict_fractionFollows_0digit() throws Exception {
+        // succeeds because hour/min are fixed width
+        DateTimeFormatter f = builder.appendValue(HOUR_OF_DAY, 2).appendValue(MINUTE_OF_HOUR, 2).appendFraction(NANO_OF_SECOND, 0, 3, false).toFormatter(Locale.UK);
+        ParsePosition pp = new ParsePosition(0);
+        TemporalAccessor parsed = f.parseUnresolved("1230", pp);
+        assertEquals(pp.getErrorIndex(), -1);
+        assertEquals(pp.getIndex(), 4);
+        assertEquals(parsed.getLong(HOUR_OF_DAY), 12L);
+        assertEquals(parsed.getLong(MINUTE_OF_HOUR), 30L);
+    }
+
+    @Test
+    public void test_adjacent_lenient_fractionFollows() throws Exception {
+        // succeeds because hour/min 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("1230567", pp);
+        assertEquals(pp.getErrorIndex(), -1);
+        assertEquals(pp.getIndex(), 7);
+        assertEquals(parsed.getLong(HOUR_OF_DAY), 12L);
+        assertEquals(parsed.getLong(MINUTE_OF_HOUR), 30L);
+        assertEquals(parsed.getLong(NANO_OF_SECOND), 567_000_000L);
+    }
+
+    @Test
+    public void test_adjacent_lenient_fractionFollows_2digit() throws Exception {
+        // succeeds because hour/min 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("123056", pp);
+        assertEquals(pp.getErrorIndex(), -1);
+        assertEquals(pp.getIndex(), 6);
+        assertEquals(parsed.getLong(HOUR_OF_DAY), 12L);
+        assertEquals(parsed.getLong(MINUTE_OF_HOUR), 30L);
+        assertEquals(parsed.getLong(NANO_OF_SECOND), 560_000_000L);
+    }
+
+    @Test
+    public void test_adjacent_lenient_fractionFollows_0digit() throws Exception {
+        // succeeds because hour/min 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);
+        assertEquals(pp.getErrorIndex(), -1);
+        assertEquals(pp.getIndex(), 4);
+        assertEquals(parsed.getLong(HOUR_OF_DAY), 12L);
+        assertEquals(parsed.getLong(MINUTE_OF_HOUR), 30L);
+    }
+
 }
--- a/jdk/test/java/time/test/java/time/format/TestReducedParser.java	Sat Mar 29 12:29:21 2014 +0400
+++ b/jdk/test/java/time/test/java/time/format/TestReducedParser.java	Sat Mar 29 15:01:47 2014 -0400
@@ -356,7 +356,7 @@
             {"yyMMdd", "200703",    STRICT, 0, 6, 2020, 7, 3},
             {"ddMMyy", "230714",    LENIENT, 0, 6, 2014, 7, 23},
             {"ddMMyy", "230714",    STRICT, 0, 6, 2014, 7, 23},
-            {"ddMMyy", "25062001",  LENIENT, 0, 8, 2001, 20, 2506},
+            {"ddMMyy", "25062001",  LENIENT, 0, 8, 2001, 6, 25},
             {"ddMMyy", "25062001",  STRICT, 0, 6, 2020, 6, 25},
             {"ddMMy",  "27052002",  LENIENT, 0, 8, 2002, 5, 27},
             {"ddMMy",  "27052002",  STRICT, 0, 8, 2002, 5, 27},