提交 5917cddd 编写于 作者: R rriggs

8032491: DateTimeFormatter fixed width adjacent value parsing does not match spec

Reviewed-by: lancea, rriggs
Contributed-by: NStephen Colebourne <scolebourne@joda.org>
上级 37806183
...@@ -2596,8 +2596,16 @@ public final class DateTimeFormatterBuilder { ...@@ -2596,8 +2596,16 @@ public final class DateTimeFormatterBuilder {
return value; 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 @Override
...@@ -2626,12 +2634,12 @@ public final class DateTimeFormatterBuilder { ...@@ -2626,12 +2634,12 @@ public final class DateTimeFormatterBuilder {
return ~position; return ~position;
} }
} }
int effMinWidth = (context.isStrict() || isFixedWidth() ? minWidth : 1); int effMinWidth = (context.isStrict() || isFixedWidth(context) ? minWidth : 1);
int minEndPos = position + effMinWidth; int minEndPos = position + effMinWidth;
if (minEndPos > length) { if (minEndPos > length) {
return ~position; 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; long total = 0;
BigInteger totalBig = null; BigInteger totalBig = null;
int pos = position; int pos = position;
...@@ -2866,6 +2874,21 @@ public final class DateTimeFormatterBuilder { ...@@ -2866,6 +2874,21 @@ public final class DateTimeFormatterBuilder {
this.subsequentWidth + subsequentWidth); 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 @Override
public String toString() { public String toString() {
return "ReducedValue(" + field + "," + minWidth + "," + maxWidth + "," + (baseDate != null ? baseDate : baseValue) + ")"; return "ReducedValue(" + field + "," + minWidth + "," + maxWidth + "," + (baseDate != null ? baseDate : baseValue) + ")";
......
...@@ -60,11 +60,14 @@ ...@@ -60,11 +60,14 @@
package tck.java.time.format; package tck.java.time.format;
import static java.time.temporal.ChronoField.DAY_OF_MONTH; 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.MINUTE_OF_HOUR;
import static java.time.temporal.ChronoField.MONTH_OF_YEAR; 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 java.time.temporal.ChronoField.YEAR;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import java.text.ParsePosition;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.YearMonth; import java.time.YearMonth;
import java.time.ZoneOffset; import java.time.ZoneOffset;
...@@ -73,6 +76,7 @@ import java.time.format.DateTimeFormatterBuilder; ...@@ -73,6 +76,7 @@ import java.time.format.DateTimeFormatterBuilder;
import java.time.format.SignStyle; import java.time.format.SignStyle;
import java.time.format.TextStyle; import java.time.format.TextStyle;
import java.time.temporal.Temporal; import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
...@@ -728,4 +732,150 @@ public class TCKDateTimeFormatterBuilder { ...@@ -728,4 +732,150 @@ public class TCKDateTimeFormatterBuilder {
return LocalDate.of(y, m, d); 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);
}
} }
...@@ -356,7 +356,7 @@ public class TestReducedParser extends AbstractTestPrinterParser { ...@@ -356,7 +356,7 @@ public class TestReducedParser extends AbstractTestPrinterParser {
{"yyMMdd", "200703", STRICT, 0, 6, 2020, 7, 3}, {"yyMMdd", "200703", STRICT, 0, 6, 2020, 7, 3},
{"ddMMyy", "230714", LENIENT, 0, 6, 2014, 7, 23}, {"ddMMyy", "230714", LENIENT, 0, 6, 2014, 7, 23},
{"ddMMyy", "230714", STRICT, 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}, {"ddMMyy", "25062001", STRICT, 0, 6, 2020, 6, 25},
{"ddMMy", "27052002", LENIENT, 0, 8, 2002, 5, 27}, {"ddMMy", "27052002", LENIENT, 0, 8, 2002, 5, 27},
{"ddMMy", "27052002", STRICT, 0, 8, 2002, 5, 27}, {"ddMMy", "27052002", STRICT, 0, 8, 2002, 5, 27},
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册