Created
September 22, 2013 14:25
-
-
Save jodastephen/6660394 to your computer and use it in GitHub Desktop.
Patch for reduced print/parse
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff -r b7f9218d0f37 src/share/classes/java/time/format/DateTimeFormatterBuilder.java | |
--- a/src/share/classes/java/time/format/DateTimeFormatterBuilder.java Sat Sep 14 22:54:38 2013 +0100 | |
+++ b/src/share/classes/java/time/format/DateTimeFormatterBuilder.java Sun Sep 22 07:24:24 2013 -0700 | |
@@ -78,9 +78,11 @@ | |
import java.text.ParsePosition; | |
import java.time.DateTimeException; | |
import java.time.Instant; | |
+import java.time.LocalDate; | |
import java.time.LocalDateTime; | |
import java.time.ZoneId; | |
import java.time.ZoneOffset; | |
+import java.time.chrono.ChronoLocalDate; | |
import java.time.chrono.Chronology; | |
import java.time.chrono.IsoChronology; | |
import java.time.format.DateTimeTextProvider.LocaleStore; | |
@@ -156,6 +158,10 @@ | |
ZoneId zone = temporal.query(TemporalQuery.zoneId()); | |
return (zone != null && zone instanceof ZoneOffset == false ? zone : null); | |
}; | |
+ /** | |
+ * The base date for reduced value parsing. | |
+ */ | |
+ private static final LocalDate BASE_DATE = LocalDate.of(2000, 1, 1); | |
/** | |
* The currently active builder, used by the outermost builder. | |
@@ -499,51 +505,16 @@ | |
//----------------------------------------------------------------------- | |
/** | |
- * Appends the reduced value of a date-time field with fixed width to the formatter. | |
+ * Appends the reduced value of a date-time field to the formatter. | |
* <p> | |
- * This is typically used for formatting and parsing a two digit year. | |
- * The {@code width} is the printed and parsed width. | |
- * The {@code baseValue} is used during parsing to determine the valid range. | |
- * <p> | |
- * For formatting, the width is used to determine the number of characters to format. | |
- * The rightmost characters are output to match the width, left padding with zero. | |
- * <p> | |
- * For strict parsing, the number of characters allowed by the width are parsed. | |
- * For lenient parsing, the number of characters must be at least 1 and less than 10. | |
- * If the number of digits parsed is equal to {@code width} and the value is positive, | |
- * the value of the field is computed to be the first number greater than | |
- * or equal to the {@code baseValue} with the same least significant characters, | |
- * otherwise the value parsed is the field value. | |
- * This allows a reduced value to be entered for values in range of the baseValue | |
- * and width and absolute values can be entered for values outside the range. | |
- * <p> | |
- * For example, a base value of {@code 1980} and a width of {@code 2} will have | |
- * valid values from {@code 1980} to {@code 2079}. | |
- * During parsing, the text {@code "12"} will result in the value {@code 2012} as that | |
- * is the value within the range where the last two characters are "12". | |
- * Compare with lenient parsing the text {@code "1915"} that will result in the | |
- * value {@code 1915}. | |
- * | |
- * @param field the field to append, not null | |
- * @param width the field width of the printed and parsed field, from 1 to 10 | |
- * @param baseValue the base value of the range of valid values | |
- * @return this, for chaining, not null | |
- * @throws IllegalArgumentException if the width or base value is invalid | |
- * @see #appendValueReduced(java.time.temporal.TemporalField, int, int, int) | |
- */ | |
- public DateTimeFormatterBuilder appendValueReduced(TemporalField field, | |
- int width, int baseValue) { | |
- return appendValueReduced(field, width, width, baseValue); | |
- } | |
- | |
- /** | |
- * Appends the reduced value of a date-time field with a flexible width to the formatter. | |
- * <p> | |
- * This is typically used for formatting and parsing a two digit year | |
- * but allowing for the year value to be up to maxWidth. | |
+ * Since fields such as year vary by chronology, it is recommended to use the | |
+ * {@link #appendValueReduced(TemporalField, int, int, ChronoLocalDate)} date} | |
+ * variant of this method in most cases. This variant is suitable for | |
+ * simple fields or working with only the ISO chronology. | |
* <p> | |
* For formatting, the {@code width} and {@code maxWidth} are used to | |
* determine the number of characters to format. | |
+ * If they are equal then the format is fixed width. | |
* If the value of the field is within the range of the {@code baseValue} using | |
* {@code width} characters then the reduced value is formatted otherwise the value is | |
* truncated to fit {@code maxWidth}. | |
@@ -562,8 +533,7 @@ | |
* valid values from {@code 1980} to {@code 2079}. | |
* During parsing, the text {@code "12"} will result in the value {@code 2012} as that | |
* is the value within the range where the last two characters are "12". | |
- * Compare with parsing the text {@code "1915"} that will result in the | |
- * value {@code 1915}. | |
+ * By contrast, parsing the text {@code "1915"} will result in the value {@code 1915}. | |
* | |
* @param field the field to append, not null | |
* @param width the field width of the printed and parsed field, from 1 to 10 | |
@@ -575,7 +545,67 @@ | |
public DateTimeFormatterBuilder appendValueReduced(TemporalField field, | |
int width, int maxWidth, int baseValue) { | |
Objects.requireNonNull(field, "field"); | |
- ReducedPrinterParser pp = new ReducedPrinterParser(field, width, maxWidth, baseValue); | |
+ ReducedPrinterParser pp = new ReducedPrinterParser(field, width, maxWidth, baseValue, null); | |
+ appendValue(pp); | |
+ return this; | |
+ } | |
+ | |
+ /** | |
+ * Appends the reduced value of a date-time field to the formatter. | |
+ * <p> | |
+ * This is typically used for formatting and parsing a two digit year. | |
+ * <p> | |
+ * The base date is used to calculate the full value during parsing. | |
+ * For example, if the base date is 1950-01-01 then parsed values for | |
+ * a two digit year parse will be in the range 1950-01-01 to 2049-12-31. | |
+ * Only the year would be extracted from the date, thus a base date of | |
+ * 1950-08-25 would also parse to the range 1950-01-01 to 2049-12-31. | |
+ * This behaviour is necessary to support fields such as week-based-year | |
+ * or other calendar systems where the parsed value does not align with | |
+ * standard ISO years. | |
+ * <p> | |
+ * The exact behavior is as follows. Parse the full set of fields and | |
+ * determine the effective chronology. Then convert the base date to the | |
+ * effective chronology. Then extract the specified field from the | |
+ * chronology-specific base date and use it to determine the | |
+ * {@code baseValue} used below. | |
+ * <p> | |
+ * For formatting, the {@code width} and {@code maxWidth} are used to | |
+ * determine the number of characters to format. | |
+ * If they are equal then the format is fixed width. | |
+ * If the value of the field is within the range of the {@code baseValue} using | |
+ * {@code width} characters then the reduced value is formatted otherwise the value is | |
+ * truncated to fit {@code maxWidth}. | |
+ * The rightmost characters are output to match the width, left padding with zero. | |
+ * <p> | |
+ * For strict parsing, the number of characters allowed by {@code width} to {@code maxWidth} are parsed. | |
+ * For lenient parsing, the number of characters must be at least 1 and less than 10. | |
+ * If the number of digits parsed is equal to {@code width} and the value is positive, | |
+ * the value of the field is computed to be the first number greater than | |
+ * or equal to the {@code baseValue} with the same least significant characters, | |
+ * otherwise the value parsed is the field value. | |
+ * This allows a reduced value to be entered for values in range of the baseValue | |
+ * and width and absolute values can be entered for values outside the range. | |
+ * <p> | |
+ * For example, a base value of {@code 1980} and a width of {@code 2} will have | |
+ * valid values from {@code 1980} to {@code 2079}. | |
+ * During parsing, the text {@code "12"} will result in the value {@code 2012} as that | |
+ * is the value within the range where the last two characters are "12". | |
+ * By contrast, parsing the text {@code "1915"} will result in the value {@code 1915}. | |
+ * | |
+ * @param field the field to append, not null | |
+ * @param width the field width of the printed and parsed field, from 1 to 10 | |
+ * @param maxWidth the maximum field width of the printed field, from 1 to 10 | |
+ * @param baseDate the base date used to calculate the base value for the range | |
+ * of valid values in the parsed chronology, not null | |
+ * @return this, for chaining, not null | |
+ * @throws IllegalArgumentException if the width or base value is invalid | |
+ */ | |
+ public DateTimeFormatterBuilder appendValueReduced( | |
+ TemporalField field, int width, int maxWidth, ChronoLocalDate baseDate) { | |
+ Objects.requireNonNull(field, "field"); | |
+ Objects.requireNonNull(baseDate, "baseDate"); | |
+ ReducedPrinterParser pp = new ReducedPrinterParser(field, width, maxWidth, 0, baseDate); | |
appendValue(pp); | |
return this; | |
} | |
@@ -1682,7 +1712,7 @@ | |
case 'u': | |
case 'y': | |
if (count == 2) { | |
- appendValueReduced(field, 2, 2000); | |
+ appendValueReduced(field, 2, 2, BASE_DATE); | |
} else if (count < 4) { | |
appendValue(field, count, 19, SignStyle.NORMAL); | |
} else { | |
@@ -2516,7 +2546,7 @@ | |
if (valueLong == null) { | |
return false; | |
} | |
- long value = getValue(valueLong); | |
+ long value = getValue(context, valueLong); | |
DecimalStyle decimalStyle = context.getDecimalStyle(); | |
String str = (value == Long.MIN_VALUE ? "9223372036854775808" : Long.toString(Math.abs(value))); | |
if (str.length() > maxWidth) { | |
@@ -2560,10 +2590,11 @@ | |
/** | |
* Gets the value to output. | |
* | |
- * @param value the base value of the field, not null | |
+ * @param context the context | |
+ * @param value the value of the field, not null | |
* @return the value | |
*/ | |
- long getValue(long value) { | |
+ long getValue(DateTimePrintContext context, long value) { | |
return value; | |
} | |
@@ -2704,6 +2735,7 @@ | |
*/ | |
static final class ReducedPrinterParser extends NumberPrinterParser { | |
private final int baseValue; | |
+ private final ChronoLocalDate baseDate; | |
/** | |
* Constructor. | |
@@ -2712,10 +2744,11 @@ | |
* @param minWidth the minimum field width, from 1 to 10 | |
* @param maxWidth the maximum field width, from 1 to 10 | |
* @param baseValue the base value | |
+ * @param baseDate the base date | |
*/ | |
ReducedPrinterParser(TemporalField field, int minWidth, int maxWidth, | |
- int baseValue) { | |
- this(field, minWidth, maxWidth, baseValue, 0); | |
+ int baseValue, ChronoLocalDate baseDate) { | |
+ this(field, minWidth, maxWidth, baseValue, baseDate, 0); | |
if (minWidth < 1 || minWidth > 10) { | |
throw new IllegalArgumentException("The minWidth must be from 1 to 10 inclusive but was " + minWidth); | |
} | |
@@ -2726,11 +2759,13 @@ | |
throw new IllegalArgumentException("Maximum width must exceed or equal the minimum width but " + | |
maxWidth + " < " + minWidth); | |
} | |
- if (field.range().isValidValue(baseValue) == false) { | |
- throw new IllegalArgumentException("The base value must be within the range of the field"); | |
- } | |
- if ((((long) baseValue) + EXCEED_POINTS[maxWidth]) > Integer.MAX_VALUE) { | |
- throw new DateTimeException("Unable to add printer-parser as the range exceeds the capacity of an int"); | |
+ if (baseDate == null) { | |
+ if (field.range().isValidValue(baseValue) == false) { | |
+ throw new IllegalArgumentException("The base value must be within the range of the field"); | |
+ } | |
+ if ((((long) baseValue) + EXCEED_POINTS[maxWidth]) > Integer.MAX_VALUE) { | |
+ throw new DateTimeException("Unable to add printer-parser as the range exceeds the capacity of an int"); | |
+ } | |
} | |
} | |
@@ -2742,17 +2777,24 @@ | |
* @param minWidth the minimum field width, from 1 to 10 | |
* @param maxWidth the maximum field width, from 1 to 10 | |
* @param baseValue the base value | |
+ * @param baseDate the base date | |
* @param subsequentWidth the subsequentWidth for this instance | |
*/ | |
private ReducedPrinterParser(TemporalField field, int minWidth, int maxWidth, | |
- int baseValue, int subsequentWidth) { | |
+ int baseValue, ChronoLocalDate baseDate, int subsequentWidth) { | |
super(field, minWidth, maxWidth, SignStyle.NOT_NEGATIVE, subsequentWidth); | |
this.baseValue = baseValue; | |
+ this.baseDate = baseDate; | |
} | |
@Override | |
- long getValue(long value) { | |
+ long getValue(DateTimePrintContext context, long value) { | |
long absValue = Math.abs(value); | |
+ int baseValue = this.baseValue; | |
+ if (baseDate != null) { | |
+ Chronology chrono = Chronology.from(context.getTemporal()); | |
+ baseValue = chrono.date(baseDate).get(field); | |
+ } | |
if (value >= baseValue && value < baseValue + EXCEED_POINTS[minWidth]) { | |
// Use the reduced value if it fits in minWidth | |
return absValue % EXCEED_POINTS[minWidth]; | |
@@ -2763,6 +2805,12 @@ | |
@Override | |
int setValue(DateTimeParseContext context, long value, int errorPos, int successPos) { | |
+ int baseValue = this.baseValue; | |
+ if (baseDate != null) { | |
+ // TODO: effective chrono is inaccurate at this point | |
+ Chronology chrono = context.getEffectiveChronology(); | |
+ baseValue = chrono.date(baseDate).get(field); | |
+ } | |
int parseLen = successPos - errorPos; | |
if (parseLen == minWidth && value >= 0) { | |
long range = EXCEED_POINTS[minWidth]; | |
@@ -2773,7 +2821,7 @@ | |
} else { | |
value = basePart - value; | |
} | |
- if (basePart != 0 && value < baseValue) { | |
+ if (value < baseValue) { | |
value += range; | |
} | |
} | |
@@ -2790,7 +2838,7 @@ | |
if (subsequentWidth == -1) { | |
return this; | |
} | |
- return new ReducedPrinterParser(field, minWidth, maxWidth, baseValue, -1); | |
+ return new ReducedPrinterParser(field, minWidth, maxWidth, baseValue, baseDate, -1); | |
} | |
/** | |
@@ -2801,13 +2849,13 @@ | |
*/ | |
@Override | |
ReducedPrinterParser withSubsequentWidth(int subsequentWidth) { | |
- return new ReducedPrinterParser(field, minWidth, maxWidth, baseValue, | |
+ return new ReducedPrinterParser(field, minWidth, maxWidth, baseValue, baseDate, | |
this.subsequentWidth + subsequentWidth); | |
} | |
@Override | |
public String toString() { | |
- return "ReducedValue(" + field + "," + minWidth + "," + maxWidth + "," + baseValue + ")"; | |
+ return "ReducedValue(" + field + "," + minWidth + "," + maxWidth + "," + (baseDate != null ? baseDate : baseValue) + ")"; | |
} | |
} | |
@@ -4351,7 +4399,7 @@ | |
case 'Y': | |
field = weekDef.weekBasedYear(); | |
if (count == 2) { | |
- return new ReducedPrinterParser(field, 2, 2, 2000, 0); | |
+ return new ReducedPrinterParser(field, 2, 2, 0, BASE_DATE, 0); | |
} else { | |
return new NumberPrinterParser(field, count, 19, | |
(count < 4) ? SignStyle.NORMAL : SignStyle.EXCEEDS_PAD, -1); | |
@@ -4380,7 +4428,7 @@ | |
if (count == 1) { | |
sb.append("WeekBasedYear"); | |
} else if (count == 2) { | |
- sb.append("ReducedValue(WeekBasedYear,2,2000)"); | |
+ sb.append("ReducedValue(WeekBasedYear,2,2,2000-01-01)"); | |
} else { | |
sb.append("WeekBasedYear,").append(count).append(",") | |
.append(19).append(",") | |
diff -r b7f9218d0f37 test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java | |
--- a/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java Sat Sep 14 22:54:38 2013 +0100 | |
+++ b/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java Sun Sep 22 07:24:24 2013 -0700 | |
@@ -190,8 +190,69 @@ | |
//----------------------------------------------------------------------- | |
@Test(expectedExceptions=NullPointerException.class) | |
- public void test_appendValueReduced_null() throws Exception { | |
- builder.appendValueReduced(null, 2, 2000); | |
+ public void test_appendValueReduced_int_nullField() throws Exception { | |
+ builder.appendValueReduced(null, 2, 2, 2000); | |
+ } | |
+ | |
+ @Test(expectedExceptions=IllegalArgumentException.class) | |
+ public void test_appendValueReduced_int_minWidthTooSmall() throws Exception { | |
+ builder.appendValueReduced(YEAR, 0, 2, 2000); | |
+ } | |
+ | |
+ @Test(expectedExceptions=IllegalArgumentException.class) | |
+ public void test_appendValueReduced_int_minWidthTooBig() throws Exception { | |
+ builder.appendValueReduced(YEAR, 11, 2, 2000); | |
+ } | |
+ | |
+ @Test(expectedExceptions=IllegalArgumentException.class) | |
+ public void test_appendValueReduced_int_maxWidthTooSmall() throws Exception { | |
+ builder.appendValueReduced(YEAR, 2, 0, 2000); | |
+ } | |
+ | |
+ @Test(expectedExceptions=IllegalArgumentException.class) | |
+ public void test_appendValueReduced_int_maxWidthTooBig() throws Exception { | |
+ builder.appendValueReduced(YEAR, 2, 11, 2000); | |
+ } | |
+ | |
+ @Test(expectedExceptions=IllegalArgumentException.class) | |
+ public void test_appendValueReduced_int_maxWidthLessThanMin() throws Exception { | |
+ builder.appendValueReduced(YEAR, 2, 1, 2000); | |
+ } | |
+ | |
+ //----------------------------------------------------------------------- | |
+ @Test(expectedExceptions=NullPointerException.class) | |
+ public void test_appendValueReduced_date_nullField() throws Exception { | |
+ builder.appendValueReduced(null, 2, 2, LocalDate.of(2000, 1, 1)); | |
+ } | |
+ | |
+ @Test(expectedExceptions=NullPointerException.class) | |
+ public void test_appendValueReduced_date_nullDate() throws Exception { | |
+ builder.appendValueReduced(YEAR, 2, 2, null); | |
+ } | |
+ | |
+ @Test(expectedExceptions=IllegalArgumentException.class) | |
+ public void test_appendValueReduced_date_minWidthTooSmall() throws Exception { | |
+ builder.appendValueReduced(YEAR, 0, 2, LocalDate.of(2000, 1, 1)); | |
+ } | |
+ | |
+ @Test(expectedExceptions=IllegalArgumentException.class) | |
+ public void test_appendValueReduced_date_minWidthTooBig() throws Exception { | |
+ builder.appendValueReduced(YEAR, 11, 2, LocalDate.of(2000, 1, 1)); | |
+ } | |
+ | |
+ @Test(expectedExceptions=IllegalArgumentException.class) | |
+ public void test_appendValueReduced_date_maxWidthTooSmall() throws Exception { | |
+ builder.appendValueReduced(YEAR, 2, 0, LocalDate.of(2000, 1, 1)); | |
+ } | |
+ | |
+ @Test(expectedExceptions=IllegalArgumentException.class) | |
+ public void test_appendValueReduced_date_maxWidthTooBig() throws Exception { | |
+ builder.appendValueReduced(YEAR, 2, 11, LocalDate.of(2000, 1, 1)); | |
+ } | |
+ | |
+ @Test(expectedExceptions=IllegalArgumentException.class) | |
+ public void test_appendValueReduced_date_maxWidthLessThanMin() throws Exception { | |
+ builder.appendValueReduced(YEAR, 2, 1, LocalDate.of(2000, 1, 1)); | |
} | |
//----------------------------------------------------------------------- | |
diff -r b7f9218d0f37 test/java/time/test/java/time/format/TestDateTimeFormatterBuilder.java | |
--- a/test/java/time/test/java/time/format/TestDateTimeFormatterBuilder.java Sat Sep 14 22:54:38 2013 +0100 | |
+++ b/test/java/time/test/java/time/format/TestDateTimeFormatterBuilder.java Sun Sep 22 07:24:24 2013 -0700 | |
@@ -267,12 +267,12 @@ | |
//----------------------------------------------------------------------- | |
@Test(expectedExceptions=NullPointerException.class) | |
public void test_appendValueReduced_null() throws Exception { | |
- builder.appendValueReduced(null, 2, 2000); | |
+ builder.appendValueReduced(null, 2, 2, 2000); | |
} | |
@Test | |
public void test_appendValueReduced() throws Exception { | |
- builder.appendValueReduced(YEAR, 2, 2000); | |
+ builder.appendValueReduced(YEAR, 2, 2, 2000); | |
DateTimeFormatter f = builder.toFormatter(); | |
assertEquals(f.toString(), "ReducedValue(Year,2,2,2000)"); | |
TemporalAccessor parsed = f.parseUnresolved("12", new ParsePosition(0)); | |
@@ -281,7 +281,7 @@ | |
@Test | |
public void test_appendValueReduced_subsequent_parse() throws Exception { | |
- builder.appendValue(MONTH_OF_YEAR, 1, 2, SignStyle.NORMAL).appendValueReduced(YEAR, 2, 2000); | |
+ builder.appendValue(MONTH_OF_YEAR, 1, 2, SignStyle.NORMAL).appendValueReduced(YEAR, 2, 2, 2000); | |
DateTimeFormatter f = builder.toFormatter(); | |
assertEquals(f.toString(), "Value(MonthOfYear,1,2,NORMAL)ReducedValue(Year,2,2,2000)"); | |
ParsePosition ppos = new ParsePosition(0); | |
@@ -654,19 +654,19 @@ | |
{"GGGGG", "Text(Era,NARROW)"}, | |
{"u", "Value(Year)"}, | |
- {"uu", "ReducedValue(Year,2,2,2000)"}, | |
+ {"uu", "ReducedValue(Year,2,2,2000-01-01)"}, | |
{"uuu", "Value(Year,3,19,NORMAL)"}, | |
{"uuuu", "Value(Year,4,19,EXCEEDS_PAD)"}, | |
{"uuuuu", "Value(Year,5,19,EXCEEDS_PAD)"}, | |
{"y", "Value(YearOfEra)"}, | |
- {"yy", "ReducedValue(YearOfEra,2,2,2000)"}, | |
+ {"yy", "ReducedValue(YearOfEra,2,2,2000-01-01)"}, | |
{"yyy", "Value(YearOfEra,3,19,NORMAL)"}, | |
{"yyyy", "Value(YearOfEra,4,19,EXCEEDS_PAD)"}, | |
{"yyyyy", "Value(YearOfEra,5,19,EXCEEDS_PAD)"}, | |
{"Y", "Localized(WeekBasedYear)"}, | |
- {"YY", "Localized(ReducedValue(WeekBasedYear,2,2000))"}, | |
+ {"YY", "Localized(ReducedValue(WeekBasedYear,2,2,2000-01-01))"}, | |
{"YYY", "Localized(WeekBasedYear,3,19,NORMAL)"}, | |
{"YYYY", "Localized(WeekBasedYear,4,19,EXCEEDS_PAD)"}, | |
{"YYYYY", "Localized(WeekBasedYear,5,19,EXCEEDS_PAD)"}, | |
diff -r b7f9218d0f37 test/java/time/test/java/time/format/TestReducedParser.java | |
--- a/test/java/time/test/java/time/format/TestReducedParser.java Sat Sep 14 22:54:38 2013 +0100 | |
+++ b/test/java/time/test/java/time/format/TestReducedParser.java Sun Sep 22 07:24:24 2013 -0700 | |
@@ -64,11 +64,20 @@ | |
import static java.time.temporal.ChronoField.MONTH_OF_YEAR; | |
import static java.time.temporal.ChronoField.YEAR; | |
import static java.time.temporal.ChronoField.YEAR_OF_ERA; | |
+import static java.time.temporal.ChronoUnit.YEARS; | |
import static org.testng.Assert.assertEquals; | |
import static org.testng.Assert.assertTrue; | |
import static org.testng.Assert.assertNotNull; | |
import java.text.ParsePosition; | |
+import java.time.LocalDate; | |
+import java.time.chrono.Chronology; | |
+import java.time.chrono.ChronoLocalDate; | |
+import java.time.chrono.HijrahChronology; | |
+import java.time.chrono.IsoChronology; | |
+import java.time.chrono.JapaneseChronology; | |
+import java.time.chrono.MinguoChronology; | |
+import java.time.chrono.ThaiBuddhistChronology; | |
import java.time.format.DateTimeFormatter; | |
import java.time.format.DateTimeFormatterBuilder; | |
import java.time.temporal.TemporalAccessor; | |
@@ -86,13 +95,17 @@ | |
private static final boolean LENIENT = false; | |
private DateTimeFormatter getFormatter0(TemporalField field, int width, int baseValue) { | |
- return builder.appendValueReduced(field, width, baseValue).toFormatter(locale).withDecimalStyle(decimalStyle); | |
+ return builder.appendValueReduced(field, width, width, baseValue).toFormatter(locale).withDecimalStyle(decimalStyle); | |
} | |
private DateTimeFormatter getFormatter0(TemporalField field, int minWidth, int maxWidth, int baseValue) { | |
return builder.appendValueReduced(field, minWidth, maxWidth, baseValue).toFormatter(locale).withDecimalStyle(decimalStyle); | |
} | |
+ private DateTimeFormatter getFormatterBaseDate(TemporalField field, int minWidth, int maxWidth, int baseValue) { | |
+ return builder.appendValueReduced(field, minWidth, maxWidth, LocalDate.of(baseValue, 1, 1)).toFormatter(locale).withDecimalStyle(decimalStyle); | |
+ } | |
+ | |
//----------------------------------------------------------------------- | |
@DataProvider(name="error") | |
Object[][] data_error() { | |
@@ -243,6 +256,10 @@ | |
// Negative baseValue | |
{YEAR, 2, 4, -2005, "123", 0, strict(3, 123), lenient(3, 123)}, | |
+ | |
+ // Basics | |
+ {YEAR, 2, 4, 2010, "10", 0, strict(2, 2010), lenient(2, 2010)}, | |
+ {YEAR, 2, 4, 2010, "09", 0, strict(2, 2109), lenient(2, 2109)}, | |
}; | |
} | |
@@ -264,6 +281,21 @@ | |
} | |
} | |
+ @Test(dataProvider="ParseLenientSensitive") | |
+ public void test_parseStrict_baseDate(TemporalField field, int minWidth, int maxWidth, int baseValue, String input, int pos, | |
+ Pair strict, Pair lenient) { | |
+ ParsePosition ppos = new ParsePosition(pos); | |
+ setStrict(true); | |
+ TemporalAccessor parsed = getFormatterBaseDate(field, minWidth, maxWidth, baseValue).parseUnresolved(input, ppos); | |
+ if (ppos.getErrorIndex() != -1) { | |
+ assertEquals(ppos.getErrorIndex(), strict.parseLen, "error case parse position"); | |
+ assertEquals(parsed, strict.parseVal, "unexpected parse result"); | |
+ } else { | |
+ assertEquals(ppos.getIndex(), strict.parseLen, "parse position"); | |
+ assertParsed(parsed, YEAR, strict.parseVal != null ? (long) strict.parseVal : null); | |
+ } | |
+ } | |
+ | |
//----------------------------------------------------------------------- | |
// Parsing tests for lenient mode | |
//----------------------------------------------------------------------- | |
@@ -282,6 +314,21 @@ | |
} | |
} | |
+ @Test(dataProvider="ParseLenientSensitive") | |
+ public void test_parseLenient_baseDate(TemporalField field, int minWidth, int maxWidth, int baseValue, String input, int pos, | |
+ Pair strict, Pair lenient) { | |
+ ParsePosition ppos = new ParsePosition(pos); | |
+ setStrict(false); | |
+ TemporalAccessor parsed = getFormatterBaseDate(field, minWidth, maxWidth, baseValue).parseUnresolved(input, ppos); | |
+ if (ppos.getErrorIndex() != -1) { | |
+ assertEquals(ppos.getErrorIndex(), lenient.parseLen, "error case parse position"); | |
+ assertEquals(parsed, lenient.parseVal, "unexpected parse result"); | |
+ } else { | |
+ assertEquals(ppos.getIndex(), lenient.parseLen, "parse position"); | |
+ assertParsed(parsed, YEAR, lenient.parseVal != null ? (long) lenient.parseVal : null); | |
+ } | |
+ } | |
+ | |
private void assertParsed(TemporalAccessor parsed, TemporalField field, Long value) { | |
if (value == null) { | |
assertEquals(parsed, null, "Parsed Value"); | |
@@ -335,6 +382,68 @@ | |
} | |
//----------------------------------------------------------------------- | |
+ // Cases and values in reduced value parsing mode | |
+ //----------------------------------------------------------------------- | |
+ @DataProvider(name="ReducedWithChrono") | |
+ Object[][] provider_reducedWithChrono() { | |
+ LocalDate baseYear = LocalDate.of(2000, 1, 1); | |
+ return new Object[][] { | |
+ {IsoChronology.INSTANCE.date(baseYear)}, | |
+ {IsoChronology.INSTANCE.date(baseYear).plus(1, YEARS)}, | |
+ {IsoChronology.INSTANCE.date(baseYear).plus(99, YEARS)}, | |
+ {HijrahChronology.INSTANCE.date(baseYear)}, | |
+ {HijrahChronology.INSTANCE.date(baseYear).plus(1, YEARS)}, | |
+ {HijrahChronology.INSTANCE.date(baseYear).plus(99, YEARS)}, | |
+ {JapaneseChronology.INSTANCE.date(baseYear)}, | |
+ {JapaneseChronology.INSTANCE.date(baseYear).plus(1, YEARS)}, | |
+ {JapaneseChronology.INSTANCE.date(baseYear).plus(99, YEARS)}, | |
+ {MinguoChronology.INSTANCE.date(baseYear)}, | |
+ {MinguoChronology.INSTANCE.date(baseYear).plus(1, YEARS)}, | |
+ {MinguoChronology.INSTANCE.date(baseYear).plus(99, YEARS)}, | |
+ {ThaiBuddhistChronology.INSTANCE.date(baseYear)}, | |
+ {ThaiBuddhistChronology.INSTANCE.date(baseYear).plus(1, YEARS)}, | |
+ {ThaiBuddhistChronology.INSTANCE.date(baseYear).plus(99, YEARS)}, | |
+ }; | |
+ } | |
+ | |
+ @Test(dataProvider="ReducedWithChrono") | |
+ public void test_reducedWithChronoYear(ChronoLocalDate date) { | |
+ Chronology chrono = date.getChronology(); | |
+ DateTimeFormatter df | |
+ = new DateTimeFormatterBuilder().appendValueReduced(YEAR, 2, 2, LocalDate.of(2000, 1, 1)) | |
+ .toFormatter() | |
+ .withChronology(chrono); | |
+ int expected = date.get(YEAR); | |
+ String input = df.format(date); | |
+ | |
+ ParsePosition pos = new ParsePosition(0); | |
+ TemporalAccessor parsed = df.parseUnresolved(input, pos); | |
+ int actual = parsed.get(YEAR); | |
+ assertEquals(actual, expected, | |
+ String.format("Wrong date parsed, chrono: %s, input: %s", | |
+ chrono, input)); | |
+ | |
+ } | |
+ @Test(dataProvider="ReducedWithChrono") | |
+ public void test_reducedWithChronoYearOfEra(ChronoLocalDate date) { | |
+ Chronology chrono = date.getChronology(); | |
+ DateTimeFormatter df | |
+ = new DateTimeFormatterBuilder().appendValueReduced(YEAR_OF_ERA, 2, 2, LocalDate.of(2000, 1, 1)) | |
+ .toFormatter() | |
+ .withChronology(chrono); | |
+ int expected = date.get(YEAR_OF_ERA); | |
+ String input = df.format(date); | |
+ | |
+ ParsePosition pos = new ParsePosition(0); | |
+ TemporalAccessor parsed = df.parseUnresolved(input, pos); | |
+ int actual = parsed.get(YEAR_OF_ERA); | |
+ assertEquals(actual, expected, | |
+ String.format("Wrong date parsed, chrono: %s, input: %s", | |
+ chrono, input)); | |
+ | |
+ } | |
+ | |
+ //----------------------------------------------------------------------- | |
// Class to structure the test data | |
//----------------------------------------------------------------------- | |
diff -r b7f9218d0f37 test/java/time/test/java/time/format/TestReducedPrinter.java | |
--- a/test/java/time/test/java/time/format/TestReducedPrinter.java Sat Sep 14 22:54:38 2013 +0100 | |
+++ b/test/java/time/test/java/time/format/TestReducedPrinter.java Sun Sep 22 07:24:24 2013 -0700 | |
@@ -59,19 +59,15 @@ | |
*/ | |
package test.java.time.format; | |
-import java.text.ParsePosition; | |
import static java.time.temporal.ChronoField.YEAR; | |
import static org.testng.Assert.assertEquals; | |
import static org.testng.Assert.fail; | |
import java.time.DateTimeException; | |
import java.time.LocalDate; | |
+import java.time.chrono.MinguoDate; | |
import java.time.format.DateTimeFormatter; | |
import java.time.format.DateTimeFormatterBuilder; | |
-import static java.time.temporal.ChronoField.DAY_OF_MONTH; | |
-import static java.time.temporal.ChronoField.MONTH_OF_YEAR; | |
-import static java.time.temporal.ChronoField.YEAR_OF_ERA; | |
-import java.time.temporal.TemporalAccessor; | |
import java.time.temporal.TemporalField; | |
import org.testng.annotations.DataProvider; | |
@@ -85,13 +81,17 @@ | |
public class TestReducedPrinter extends AbstractTestPrinterParser { | |
private DateTimeFormatter getFormatter0(TemporalField field, int width, int baseValue) { | |
- return builder.appendValueReduced(field, width, baseValue).toFormatter(locale).withDecimalStyle(decimalStyle); | |
+ return builder.appendValueReduced(field, width, width, baseValue).toFormatter(locale).withDecimalStyle(decimalStyle); | |
} | |
private DateTimeFormatter getFormatter0(TemporalField field, int minWidth, int maxWidth, int baseValue) { | |
return builder.appendValueReduced(field, minWidth, maxWidth, baseValue).toFormatter(locale).withDecimalStyle(decimalStyle); | |
} | |
+ private DateTimeFormatter getFormatterBaseDate(TemporalField field, int minWidth, int maxWidth, int baseValue) { | |
+ return builder.appendValueReduced(field, minWidth, maxWidth, LocalDate.of(baseValue, 1, 1)).toFormatter(locale).withDecimalStyle(decimalStyle); | |
+ } | |
+ | |
//----------------------------------------------------------------------- | |
@Test(expectedExceptions=DateTimeException.class) | |
public void test_print_emptyCalendrical() throws Exception { | |
@@ -192,6 +192,58 @@ | |
} | |
} | |
+ @Test(dataProvider="Pivot") | |
+ public void test_pivot_baseDate(int minWidth, int maxWidth, int baseValue, int value, String result) throws Exception { | |
+ try { | |
+ getFormatterBaseDate(YEAR, minWidth, maxWidth, baseValue).formatTo(new MockFieldValue(YEAR, value), buf); | |
+ if (result == null) { | |
+ fail("Expected exception"); | |
+ } | |
+ assertEquals(buf.toString(), result); | |
+ } catch (DateTimeException ex) { | |
+ if (result == null || value < 0) { | |
+ assertEquals(ex.getMessage().contains(YEAR.toString()), true); | |
+ } else { | |
+ throw ex; | |
+ } | |
+ } | |
+ } | |
+ | |
+ //----------------------------------------------------------------------- | |
+ public void test_minguoChrono_fixedWidth() throws Exception { | |
+ // ISO 2021 is Minguo 110 | |
+ DateTimeFormatter f = getFormatterBaseDate(YEAR, 2, 2, 2021); | |
+ MinguoDate date = MinguoDate.of(109, 6, 30); | |
+ assertEquals(f.format(date), "09"); | |
+ date = MinguoDate.of(110, 6, 30); | |
+ assertEquals(f.format(date), "10"); | |
+ date = MinguoDate.of(199, 6, 30); | |
+ assertEquals(f.format(date), "99"); | |
+ date = MinguoDate.of(200, 6, 30); | |
+ assertEquals(f.format(date), "00"); | |
+ date = MinguoDate.of(209, 6, 30); | |
+ assertEquals(f.format(date), "09"); | |
+ date = MinguoDate.of(210, 6, 30); | |
+ assertEquals(f.format(date), "10"); | |
+ } | |
+ | |
+ public void test_minguoChrono_extendedWidth() throws Exception { | |
+ // ISO 2021 is Minguo 110 | |
+ DateTimeFormatter f = getFormatterBaseDate(YEAR, 2, 4, 2021); | |
+ MinguoDate date = MinguoDate.of(109, 6, 30); | |
+ assertEquals(f.format(date), "109"); | |
+ date = MinguoDate.of(110, 6, 30); | |
+ assertEquals(f.format(date), "10"); | |
+ date = MinguoDate.of(199, 6, 30); | |
+ assertEquals(f.format(date), "99"); | |
+ date = MinguoDate.of(200, 6, 30); | |
+ assertEquals(f.format(date), "00"); | |
+ date = MinguoDate.of(209, 6, 30); | |
+ assertEquals(f.format(date), "09"); | |
+ date = MinguoDate.of(210, 6, 30); | |
+ assertEquals(f.format(date), "210"); | |
+ } | |
+ | |
//----------------------------------------------------------------------- | |
public void test_toString() throws Exception { | |
assertEquals(getFormatter0(YEAR, 2, 2, 2005).toString(), "ReducedValue(Year,2,2,2005)"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This looks like a great improvement over the previous incremental attempt.
What is needed for the aforementioned effective chronology redesign?
Should that be done at the same time or do we need to raise a separate issue?