8032491: DateTimeFormatter fixed width adjacent value parsing does not match spec
Reviewed-by: lancea, rriggs
Contributed-by: Stephen Colebourne <scolebourne@joda.org>
--- 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},