# HG changeset patch # User rriggs # Date 1396119707 14400 # Node ID 08ca64c3f0e4dc077e41b9b60047751fcf5ae3da # Parent 505c3a4eb0d61dae92b92fd0e09f342c8a114991 8032491: DateTimeFormatter fixed width adjacent value parsing does not match spec Reviewed-by: lancea, rriggs Contributed-by: Stephen Colebourne diff -r 505c3a4eb0d6 -r 08ca64c3f0e4 jdk/src/share/classes/java/time/format/DateTimeFormatterBuilder.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) + ")"; diff -r 505c3a4eb0d6 -r 08ca64c3f0e4 jdk/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java --- 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); + } + } diff -r 505c3a4eb0d6 -r 08ca64c3f0e4 jdk/test/java/time/test/java/time/format/TestReducedParser.java --- 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},