From 5917cdddc03b1495b750dd3eac91da29e4bb5ce0 Mon Sep 17 00:00:00 2001 From: rriggs Date: Sat, 29 Mar 2014 15:01:47 -0400 Subject: [PATCH] 8032491: DateTimeFormatter fixed width adjacent value parsing does not match spec Reviewed-by: lancea, rriggs Contributed-by: Stephen Colebourne --- .../time/format/DateTimeFormatterBuilder.java | 31 +++- .../format/TCKDateTimeFormatterBuilder.java | 150 ++++++++++++++++++ .../java/time/format/TestReducedParser.java | 2 +- 3 files changed, 178 insertions(+), 5 deletions(-) diff --git a/src/share/classes/java/time/format/DateTimeFormatterBuilder.java b/src/share/classes/java/time/format/DateTimeFormatterBuilder.java index 9b9d9637a..f04d59b23 100644 --- a/src/share/classes/java/time/format/DateTimeFormatterBuilder.java +++ b/src/share/classes/java/time/format/DateTimeFormatterBuilder.java @@ -2596,8 +2596,16 @@ public final class DateTimeFormatterBuilder { 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 @@ public final class DateTimeFormatterBuilder { 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 @@ public final class DateTimeFormatterBuilder { 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 --git a/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java b/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java index 59d2ca15b..c5a017c91 100644 --- a/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java +++ b/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java @@ -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.DateTimeFormatterBuilder; 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 @@ public class TCKDateTimeFormatterBuilder { 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 --git a/test/java/time/test/java/time/format/TestReducedParser.java b/test/java/time/test/java/time/format/TestReducedParser.java index e9df91fdc..3bf806563 100644 --- a/test/java/time/test/java/time/format/TestReducedParser.java +++ b/test/java/time/test/java/time/format/TestReducedParser.java @@ -356,7 +356,7 @@ public class TestReducedParser extends AbstractTestPrinterParser { {"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}, -- GitLab