8025828: Late binding of Chronology to appendValueReduced
Summary: Add a listener to the parseContext called when the Chronology changes
Reviewed-by: sherman
--- a/jdk/src/share/classes/java/time/format/DateTimeFormatterBuilder.java Sat Oct 19 00:05:42 2013 +0400
+++ b/jdk/src/share/classes/java/time/format/DateTimeFormatterBuilder.java Fri Oct 18 16:37:58 2013 -0400
@@ -557,12 +557,13 @@
* 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
+ * This behavior 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
+ * determine the effective chronology using the last chronology if
+ * it appears more than once. 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.
@@ -2809,9 +2810,19 @@
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);
+
+ // In case the Chronology is changed later, add a callback when/if it changes
+ final long initialValue = value;
+ context.addChronoChangedListener(
+ (_unused) -> {
+ /* Repeat the set of the field using the current Chronology
+ * The success/error position is ignored because the value is
+ * intentionally being overwritten.
+ */
+ setValue(context, initialValue, errorPos, successPos);
+ });
}
int parseLen = successPos - errorPos;
if (parseLen == minWidth && value >= 0) {
--- a/jdk/src/share/classes/java/time/format/DateTimeParseContext.java Sat Oct 19 00:05:42 2013 +0400
+++ b/jdk/src/share/classes/java/time/format/DateTimeParseContext.java Fri Oct 18 16:37:58 2013 -0400
@@ -61,7 +61,6 @@
*/
package java.time.format;
-import java.time.Duration;
import java.time.ZoneId;
import java.time.chrono.Chronology;
import java.time.chrono.IsoChronology;
@@ -69,6 +68,7 @@
import java.util.ArrayList;
import java.util.Locale;
import java.util.Objects;
+import java.util.function.Consumer;
/**
* Context object used during date and time parsing.
@@ -105,6 +105,10 @@
* The list of parsed data.
*/
private final ArrayList<Parsed> parsed = new ArrayList<>();
+ /**
+ * List of Consumers<Chronology> to be notified if the Chronology changes.
+ */
+ private ArrayList<Consumer<Chronology>> chronoListeners = null;
/**
* Creates a new instance of the context.
@@ -354,12 +358,36 @@
* <p>
* This stores the chronology that has been parsed.
* No validation is performed other than ensuring it is not null.
+ * <p>
+ * The list of listeners is copied and cleared so that each
+ * listener is called only once. A listener can add itself again
+ * if it needs to be notified of future changes.
*
* @param chrono the parsed chronology, not null
*/
void setParsed(Chronology chrono) {
Objects.requireNonNull(chrono, "chrono");
currentParsed().chrono = chrono;
+ if (chronoListeners != null && !chronoListeners.isEmpty()) {
+ Consumer[] tmp = new Consumer[1];
+ Consumer<Chronology>[] listeners = chronoListeners.toArray(tmp);
+ chronoListeners.clear();
+ for (Consumer<Chronology> l : listeners) {
+ l.accept(chrono);
+ }
+ }
+ }
+
+ /**
+ * Adds a Consumer<Chronology> to the list of listeners to be notified
+ * if the Chronology changes.
+ * @param listener a Consumer<Chronology> to be called when Chronology changes
+ */
+ void addChronoChangedListener(Consumer<Chronology> listener) {
+ if (chronoListeners == null) {
+ chronoListeners = new ArrayList<Consumer<Chronology>>();
+ }
+ chronoListeners.add(listener);
}
/**
--- a/jdk/test/java/time/test/java/time/format/TestReducedParser.java Sat Oct 19 00:05:42 2013 +0400
+++ b/jdk/test/java/time/test/java/time/format/TestReducedParser.java Fri Oct 18 16:37:58 2013 -0400
@@ -78,10 +78,12 @@
import java.time.chrono.JapaneseChronology;
import java.time.chrono.MinguoChronology;
import java.time.chrono.ThaiBuddhistChronology;
+import java.time.chrono.ThaiBuddhistDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalField;
+import java.time.temporal.TemporalQueries;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
@@ -443,6 +445,52 @@
}
+ @Test
+ public void test_reducedWithLateChronoChange() {
+ ThaiBuddhistDate date = ThaiBuddhistDate.of(2543, 1, 1);
+ DateTimeFormatter df
+ = new DateTimeFormatterBuilder()
+ .appendValueReduced(YEAR, 2, 2, LocalDate.of(2000, 1, 1))
+ .appendLiteral(" ")
+ .appendChronologyId()
+ .toFormatter();
+ int expected = date.get(YEAR);
+ String input = df.format(date);
+
+ ParsePosition pos = new ParsePosition(0);
+ TemporalAccessor parsed = df.parseUnresolved(input, pos);
+ assertEquals(pos.getIndex(), input.length(), "Input not parsed completely");
+ assertEquals(pos.getErrorIndex(), -1, "Error index should be -1 (no-error)");
+ int actual = parsed.get(YEAR);
+ assertEquals(actual, expected,
+ String.format("Wrong date parsed, chrono: %s, input: %s",
+ parsed.query(TemporalQueries.chronology()), input));
+
+ }
+
+ @Test
+ public void test_reducedWithLateChronoChangeTwice() {
+ DateTimeFormatter df
+ = new DateTimeFormatterBuilder()
+ .appendValueReduced(YEAR, 2, 2, LocalDate.of(2000, 1, 1))
+ .appendLiteral(" ")
+ .appendChronologyId()
+ .appendLiteral(" ")
+ .appendChronologyId()
+ .toFormatter();
+ int expected = 2044;
+ String input = "44 ThaiBuddhist ISO";
+ ParsePosition pos = new ParsePosition(0);
+ TemporalAccessor parsed = df.parseUnresolved(input, pos);
+ assertEquals(pos.getIndex(), input.length(), "Input not parsed completely: " + pos);
+ assertEquals(pos.getErrorIndex(), -1, "Error index should be -1 (no-error)");
+ int actual = parsed.get(YEAR);
+ assertEquals(actual, expected,
+ String.format("Wrong date parsed, chrono: %s, input: %s",
+ parsed.query(TemporalQueries.chronology()), input));
+
+ }
+
//-----------------------------------------------------------------------
// Class to structure the test data
//-----------------------------------------------------------------------