diff --git a/src/share/classes/java/time/format/DateTimeFormatterBuilder.java b/src/share/classes/java/time/format/DateTimeFormatterBuilder.java index 29792b8bf796704ddab942a67068b99fe67339d5..9b9d9637a6cd6f1cd5c073fd11c33fb936456bb2 100644 --- a/src/share/classes/java/time/format/DateTimeFormatterBuilder.java +++ b/src/share/classes/java/time/format/DateTimeFormatterBuilder.java @@ -557,12 +557,13 @@ public final class DateTimeFormatterBuilder { * 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. *

* 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 @@ public final class DateTimeFormatterBuilder { 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) { diff --git a/src/share/classes/java/time/format/DateTimeParseContext.java b/src/share/classes/java/time/format/DateTimeParseContext.java index f72ef7b77c9fe75e8d4bd937bb3208ebe4e4dbaa..3157747a6f712967af0342de098ce9be8e40720c 100644 --- a/src/share/classes/java/time/format/DateTimeParseContext.java +++ b/src/share/classes/java/time/format/DateTimeParseContext.java @@ -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.time.temporal.TemporalField; 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 @@ final class DateTimeParseContext { * The list of parsed data. */ private final ArrayList parsed = new ArrayList<>(); + /** + * List of Consumers to be notified if the Chronology changes. + */ + private ArrayList> chronoListeners = null; /** * Creates a new instance of the context. @@ -354,12 +358,36 @@ final class DateTimeParseContext { *

* This stores the chronology that has been parsed. * No validation is performed other than ensuring it is not null. + *

+ * 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[] listeners = chronoListeners.toArray(tmp); + chronoListeners.clear(); + for (Consumer l : listeners) { + l.accept(chrono); + } + } + } + + /** + * Adds a Consumer to the list of listeners to be notified + * if the Chronology changes. + * @param listener a Consumer to be called when Chronology changes + */ + void addChronoChangedListener(Consumer listener) { + if (chronoListeners == null) { + chronoListeners = new ArrayList>(); + } + chronoListeners.add(listener); } /** diff --git a/test/java/time/test/java/time/format/TestReducedParser.java b/test/java/time/test/java/time/format/TestReducedParser.java index ae4ff9814031aff075c89cf6377c407ab4a2e10a..e9df91fdc8e9b391b9a8ef5ea0baaf45ebd067d3 100644 --- a/test/java/time/test/java/time/format/TestReducedParser.java +++ b/test/java/time/test/java/time/format/TestReducedParser.java @@ -78,10 +78,12 @@ import java.time.chrono.IsoChronology; 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 @@ public class TestReducedParser extends AbstractTestPrinterParser { } + @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 //-----------------------------------------------------------------------