src/share/classes/java/time/chrono/Chronology.java

Print this page

        

@@ -59,42 +59,61 @@
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 package java.time.chrono;
 
+import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH;
+import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR;
+import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_MONTH;
+import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR;
+import static java.time.temporal.ChronoField.DAY_OF_MONTH;
+import static java.time.temporal.ChronoField.DAY_OF_WEEK;
+import static java.time.temporal.ChronoField.DAY_OF_YEAR;
+import static java.time.temporal.ChronoField.EPOCH_DAY;
+import static java.time.temporal.ChronoField.ERA;
+import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
+import static java.time.temporal.ChronoField.PROLEPTIC_MONTH;
+import static java.time.temporal.ChronoField.YEAR;
+import static java.time.temporal.ChronoField.YEAR_OF_ERA;
+import static java.time.temporal.TemporalAdjuster.nextOrSame;
+
 import java.io.DataInput;
 import java.io.DataOutput;
 import java.io.IOException;
 import java.io.InvalidObjectException;
 import java.io.ObjectStreamException;
+import java.io.Serializable;
 import java.time.Clock;
 import java.time.DateTimeException;
+import java.time.DayOfWeek;
 import java.time.Instant;
 import java.time.LocalDate;
 import java.time.LocalTime;
 import java.time.ZoneId;
-import java.time.chrono.HijrahChronology;
-import java.time.chrono.JapaneseChronology;
-import java.time.chrono.MinguoChronology;
-import java.time.chrono.ThaiBuddhistChronology;
 import java.time.format.DateTimeFormatterBuilder;
+import java.time.format.ResolverStyle;
 import java.time.format.TextStyle;
 import java.time.temporal.ChronoField;
-import java.time.temporal.Queries;
+import java.time.temporal.ChronoUnit;
 import java.time.temporal.Temporal;
 import java.time.temporal.TemporalAccessor;
 import java.time.temporal.TemporalField;
 import java.time.temporal.TemporalQuery;
+import java.time.temporal.UnsupportedTemporalTypeException;
 import java.time.temporal.ValueRange;
+import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Objects;
 import java.util.ServiceLoader;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
+import sun.util.logging.PlatformLogger;
+
 /**
  * A calendar system, used to organize and identify dates.
  * <p>
  * The main date and time API is built on the ISO calendar system.
  * This class operates behind the scenes to represent the general concept of a calendar system.

@@ -104,11 +123,11 @@
  * linked to the cycles of the Earth around the Sun, and the Moon around the Earth.
  * These shared concepts are defined by {@link ChronoField} and are available
  * for use by any {@code Chronology} implementation:
  * <pre>
  *   LocalDate isoDate = ...
- *   ChronoLocalDate&lt;ThaiBuddhistChronology&gt; thaiDate = ...
+ *   ThaiBuddhistDate thaiDate = ...
  *   int isoYear = isoDate.get(ChronoField.YEAR);
  *   int thaiYear = thaiDate.get(ChronoField.YEAR);
  * </pre>
  * As shown, although the date objects are in different calendar systems, represented by different
  * {@code Chronology} instances, both can be queried using the same constant on {@code ChronoField}.

@@ -152,12 +171,12 @@
  * For lookup by id or calendarType, the system provided calendars are found
  * first followed by application provided calendars.
  * <p>
  * Each chronology must define a chronology ID that is unique within the system.
  * If the chronology represents a calendar system defined by the
- * <em>Unicode Locale Data Markup Language (LDML)</em> specification then that
- * calendar type should also be specified.
+ * CLDR specification then the calendar type is the concatenation of the
+ * CLDR type and, if applicable, the CLDR variant,
  *
  * <h3>Specification for implementors</h3>
  * This class must be implemented with care to ensure other classes operate correctly.
  * All implementations that can be instantiated must be final, immutable and thread-safe.
  * Subclasses should be Serializable wherever possible.

@@ -165,32 +184,78 @@
  * @since 1.8
  */
 public abstract class Chronology implements Comparable<Chronology> {
 
     /**
+     * ChronoLocalDate order constant.
+     */
+    static final Comparator<ChronoLocalDate<?>> DATE_ORDER =
+        (Comparator<ChronoLocalDate<?>> & Serializable) (date1, date2) -> {
+            return Long.compare(date1.toEpochDay(), date2.toEpochDay());
+        };
+    /**
+     * ChronoLocalDateTime order constant.
+     */
+    static final Comparator<ChronoLocalDateTime<?>> DATE_TIME_ORDER =
+        (Comparator<ChronoLocalDateTime<?>> & Serializable) (dateTime1, dateTime2) -> {
+            int cmp = Long.compare(dateTime1.toLocalDate().toEpochDay(), dateTime2.toLocalDate().toEpochDay());
+            if (cmp == 0) {
+                cmp = Long.compare(dateTime1.toLocalTime().toNanoOfDay(), dateTime2.toLocalTime().toNanoOfDay());
+            }
+            return cmp;
+        };
+    /**
+     * ChronoZonedDateTime order constant.
+     */
+    static final Comparator<ChronoZonedDateTime<?>> INSTANT_ORDER =
+            (Comparator<ChronoZonedDateTime<?>> & Serializable) (dateTime1, dateTime2) -> {
+                int cmp = Long.compare(dateTime1.toEpochSecond(), dateTime2.toEpochSecond());
+                if (cmp == 0) {
+                    cmp = Long.compare(dateTime1.toLocalTime().getNano(), dateTime2.toLocalTime().getNano());
+                }
+                return cmp;
+            };
+
+    /**
      * Map of available calendars by ID.
      */
     private static final ConcurrentHashMap<String, Chronology> CHRONOS_BY_ID = new ConcurrentHashMap<>();
     /**
      * Map of available calendars by calendar type.
      */
     private static final ConcurrentHashMap<String, Chronology> CHRONOS_BY_TYPE = new ConcurrentHashMap<>();
 
     /**
+     * Register a Chronology by its ID and type for lookup by {@link #of(java.lang.String)}.
+     * Chronologies must not be registered until they are completely constructed.
+     * Specifically, not in the constructor of Chronology.
+     *
+     * @param chrono the chronology to register; not null
+     * @return the already registered Chronology if any, may be null
+     */
+    static Chronology registerChrono(Chronology chrono) {
+        return registerChrono(chrono, chrono.getId());
+    }
+
+    /**
      * Register a Chronology by ID and type for lookup by {@link #of(java.lang.String)}.
      * Chronos must not be registered until they are completely constructed.
      * Specifically, not in the constructor of Chronology.
+     *
      * @param chrono the chronology to register; not null
+     * @param id the ID to register the chronology; not null
+     * @return the already registered Chronology if any, may be null
      */
-    private static void registerChrono(Chronology chrono) {
-        Chronology prev = CHRONOS_BY_ID.putIfAbsent(chrono.getId(), chrono);
+    static Chronology registerChrono(Chronology chrono, String id) {
+        Chronology prev = CHRONOS_BY_ID.putIfAbsent(id, chrono);
         if (prev == null) {
             String type = chrono.getCalendarType();
             if (type != null) {
                 CHRONOS_BY_TYPE.putIfAbsent(type, chrono);
             }
         }
+        return prev;
     }
 
     /**
      * Initialization of the maps from id and type to Chronology.
      * The ServiceLoader is used to find and register any implementations

@@ -207,22 +272,29 @@
      * @return true if the cache was initialized
      */
     private static boolean initCache() {
         if (CHRONOS_BY_ID.get("ISO") == null) {
             // Initialization is incomplete
-            @SuppressWarnings("rawtypes")
-            ServiceLoader<Chronology> loader =  ServiceLoader.load(Chronology.class, null);
-            for (Chronology chrono : loader) {
-                registerChrono(chrono);
-            }
 
-            // Register these calendars; the ServiceLoader configuration is not used
+            // Register built-in Chronologies
             registerChrono(HijrahChronology.INSTANCE);
             registerChrono(JapaneseChronology.INSTANCE);
             registerChrono(MinguoChronology.INSTANCE);
             registerChrono(ThaiBuddhistChronology.INSTANCE);
 
+            // Register Chronologies from the ServiceLoader
+            @SuppressWarnings("rawtypes")
+            ServiceLoader<Chronology> loader =  ServiceLoader.load(Chronology.class, null);
+            for (Chronology chrono : loader) {
+                String id = chrono.getId();
+                if (id.equals("ISO") || registerChrono(chrono) != null) {
+                    // Log the attempt to replace an existing Chronology
+                    PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono");
+                    logger.warning("Ignoring duplicate Chronology, from ServiceLoader configuration "  + id);
+                }
+            }
+
             // finally, register IsoChronology to mark initialization is complete
             registerChrono(IsoChronology.INSTANCE);
             return true;
         }
         return false;

@@ -234,11 +306,11 @@
      * <p>
      * This obtains a chronology based on the specified temporal.
      * A {@code TemporalAccessor} represents an arbitrary set of date and time information,
      * which this factory converts to an instance of {@code Chronology}.
      * <p>
-     * The conversion will obtain the chronology using {@link Queries#chronology()}.
+     * The conversion will obtain the chronology using {@link TemporalQuery#chronology()}.
      * If the specified temporal object does not have a chronology, {@link IsoChronology} is returned.
      * <p>
      * This method matches the signature of the functional interface {@link TemporalQuery}
      * allowing it to be used in queries via method reference, {@code Chronology::from}.
      *

@@ -246,11 +318,11 @@
      * @return the chronology, not null
      * @throws DateTimeException if unable to convert to an {@code Chronology}
      */
     public static Chronology from(TemporalAccessor temporal) {
         Objects.requireNonNull(temporal, "temporal");
-        Chronology obj = temporal.query(Queries.chronology());
+        Chronology obj = temporal.query(TemporalQuery.chronology());
         return (obj != null ? obj : IsoChronology.INSTANCE);
     }
 
     //-----------------------------------------------------------------------
     /**

@@ -264,17 +336,20 @@
      * for localizing an application. This includes the language and region,
      * such as "en-GB" for English as used in Great Britain.
      * <p>
      * The {@code Locale} class also supports an extension mechanism that
      * can be used to identify a calendar system. The mechanism is a form
-     * of key-value pairs, where the calendar system has the key "ca".
+     * of key-value pairs, where the calendar system has the key "ca"
+     * and an optional variant key "cv".
      * For example, the locale "en-JP-u-ca-japanese" represents the English
      * language as used in Japan with the Japanese calendar system.
      * <p>
      * This method finds the desired calendar system by in a manner equivalent
      * to passing "ca" to {@link Locale#getUnicodeLocaleType(String)}.
      * If the "ca" key is not present, then {@code IsoChronology} is returned.
+     * The variant, if present, is appended to the "ca" value separated by  "-"
+     * and the concatenated value is used to find the calendar system by type.
      * <p>
      * Note that the behavior of this method differs from the older
      * {@link java.util.Calendar#getInstance(Locale)} method.
      * If that method receives a locale of "th_TH" it will return {@code BuddhistCalendar}.
      * By contrast, this method will return {@code IsoChronology}.

@@ -297,29 +372,44 @@
         Objects.requireNonNull(locale, "locale");
         String type = locale.getUnicodeLocaleType("ca");
         if (type == null || "iso".equals(type) || "iso8601".equals(type)) {
             return IsoChronology.INSTANCE;
         }
+        String variant = locale.getUnicodeLocaleType("cv");
+        if (variant != null && !variant.isEmpty()) {
+            type = type + '-' + variant;
+        }
         // Not pre-defined; lookup by the type
         do {
             Chronology chrono = CHRONOS_BY_TYPE.get(type);
             if (chrono != null) {
                 return chrono;
             }
             // If not found, do the initialization (once) and repeat the lookup
         } while (initCache());
+
+        // Look for a Chronology using ServiceLoader of the Thread's ContextClassLoader
+        // Application provided Chronologies must not be cached
+        @SuppressWarnings("rawtypes")
+        ServiceLoader<Chronology> loader = ServiceLoader.load(Chronology.class);
+        for (Chronology chrono : loader) {
+            if (type.equals(chrono.getCalendarType())) {
+                return chrono;
+            }
+        }
         throw new DateTimeException("Unknown calendar system: " + type);
     }
 
     //-----------------------------------------------------------------------
     /**
      * Obtains an instance of {@code Chronology} from a chronology ID or
      * calendar system type.
      * <p>
      * This returns a chronology based on either the ID or the type.
      * The {@link #getId() chronology ID} uniquely identifies the chronology.
-     * The {@link #getCalendarType() calendar system type} is defined by the LDML specification.
+     * The {@link #getCalendarType() calendar system type} is defined by the
+     * CLDR specification.
      * <p>
      * The chronology may be a system chronology or a chronology
      * provided by the application via ServiceLoader configuration.
      * <p>
      * Since some calendars can be customized, the ID or type typically refers

@@ -377,11 +467,11 @@
      *
      * @return the independent, modifiable set of the available chronology IDs, not null
      */
     public static Set<Chronology> getAvailableChronologies() {
         initCache();       // force initialization
-        HashSet<Chronology> chronos = new HashSet(CHRONOS_BY_ID.values());
+        HashSet<Chronology> chronos = new HashSet<>(CHRONOS_BY_ID.values());
 
         /// Add in Chronologies from the ServiceLoader configuration
         @SuppressWarnings("rawtypes")
         ServiceLoader<Chronology> loader = ServiceLoader.load(Chronology.class);
         for (Chronology chrono : loader) {

@@ -404,13 +494,13 @@
      * @param temporal  a date-time to cast, not null
      * @return the date-time checked and cast to {@code ChronoLocalDate}, not null
      * @throws ClassCastException if the date-time cannot be cast to ChronoLocalDate
      *  or the chronology is not equal this Chronology
      */
-    ChronoLocalDate ensureChronoLocalDate(Temporal temporal) {
+    ChronoLocalDate<?> ensureChronoLocalDate(Temporal temporal) {
         @SuppressWarnings("unchecked")
-        ChronoLocalDate other = (ChronoLocalDate) temporal;
+        ChronoLocalDate<?> other = (ChronoLocalDate<?>) temporal;
         if (this.equals(other.getChronology()) == false) {
             throw new ClassCastException("Chronology mismatch, expected: " + getId() + ", actual: " + other.getChronology().getId());
         }
         return other;
     }

@@ -462,19 +552,20 @@
      * @see #getCalendarType()
      */
     public abstract String getId();
 
     /**
-     * Gets the calendar type of the underlying calendar system.
+     * Gets the calendar type of the calendar system.
      * <p>
-     * The calendar type is an identifier defined by the
-     * <em>Unicode Locale Data Markup Language (LDML)</em> specification.
-     * It can be used to lookup the {@code Chronology} using {@link #of(String)}.
-     * It can also be used as part of a locale, accessible via
-     * {@link Locale#getUnicodeLocaleType(String)} with the key 'ca'.
+     * The calendar type is an identifier defined by the CLDR and
+     * <em>Unicode Locale Data Markup Language (LDML)</em> specifications
+     * to uniquely identification a calendar.
+     * The {@code getCalendarType} is the concatenation of the CLDR calendar type
+     * and the variant, if applicable, is appended separated by "-".
+     * The calendar type is used to lookup the {@code Chronology} using {@link #of(String)}.
      *
-     * @return the calendar system type, null if the calendar is not defined by LDML
+     * @return the calendar system type, null if the calendar is not defined
      * @see #getId()
      */
     public abstract String getCalendarType();
 
     //-----------------------------------------------------------------------

@@ -486,12 +577,13 @@
      * @param yearOfEra  the chronology year-of-era
      * @param month  the chronology month-of-year
      * @param dayOfMonth  the chronology day-of-month
      * @return the local date in this chronology, not null
      * @throws DateTimeException if unable to create the date
+     * @throws ClassCastException if the {@code era} is not of the correct type for the chronology
      */
-    public ChronoLocalDate date(Era era, int yearOfEra, int month, int dayOfMonth) {
+    public ChronoLocalDate<?> date(Era era, int yearOfEra, int month, int dayOfMonth) {
         return date(prolepticYear(era, yearOfEra), month, dayOfMonth);
     }
 
     /**
      * Obtains a local date in this chronology from the proleptic-year,

@@ -501,23 +593,24 @@
      * @param month  the chronology month-of-year
      * @param dayOfMonth  the chronology day-of-month
      * @return the local date in this chronology, not null
      * @throws DateTimeException if unable to create the date
      */
-    public abstract ChronoLocalDate date(int prolepticYear, int month, int dayOfMonth);
+    public abstract ChronoLocalDate<?> date(int prolepticYear, int month, int dayOfMonth);
 
     /**
      * Obtains a local date in this chronology from the era, year-of-era and
      * day-of-year fields.
      *
      * @param era  the era of the correct type for the chronology, not null
      * @param yearOfEra  the chronology year-of-era
      * @param dayOfYear  the chronology day-of-year
      * @return the local date in this chronology, not null
      * @throws DateTimeException if unable to create the date
+     * @throws ClassCastException if the {@code era} is not of the correct type for the chronology
      */
-    public ChronoLocalDate dateYearDay(Era era, int yearOfEra, int dayOfYear) {
+    public ChronoLocalDate<?> dateYearDay(Era era, int yearOfEra, int dayOfYear) {
         return dateYearDay(prolepticYear(era, yearOfEra), dayOfYear);
     }
 
     /**
      * Obtains a local date in this chronology from the proleptic-year and

@@ -526,11 +619,23 @@
      * @param prolepticYear  the chronology proleptic-year
      * @param dayOfYear  the chronology day-of-year
      * @return the local date in this chronology, not null
      * @throws DateTimeException if unable to create the date
      */
-    public abstract ChronoLocalDate dateYearDay(int prolepticYear, int dayOfYear);
+    public abstract ChronoLocalDate<?> dateYearDay(int prolepticYear, int dayOfYear);
+
+    /**
+     * Obtains a local date in this chronology from the epoch-day.
+     * <p>
+     * The definition of {@link ChronoField#EPOCH_DAY EPOCH_DAY} is the same
+     * for all calendar systems, thus it can be used for conversion.
+     *
+     * @param epochDay  the epoch day
+     * @return the local date in this chronology, not null
+     * @throws DateTimeException if unable to create the date
+     */
+    public abstract ChronoLocalDate<?> dateEpochDay(long epochDay);
 
     //-----------------------------------------------------------------------
     /**
      * Obtains the current local date in this chronology from the system clock in the default time-zone.
      * <p>

@@ -543,11 +648,11 @@
      * This implementation uses {@link #dateNow(Clock)}.
      *
      * @return the current local date using the system clock and default time-zone, not null
      * @throws DateTimeException if unable to create the date
      */
-    public ChronoLocalDate dateNow() {
+    public ChronoLocalDate<?> dateNow() {
         return dateNow(Clock.systemDefaultZone());
     }
 
     /**
      * Obtains the current local date in this chronology from the system clock in the specified time-zone.

@@ -560,11 +665,11 @@
      *
      * @param zone  the zone ID to use, not null
      * @return the current local date using the system clock, not null
      * @throws DateTimeException if unable to create the date
      */
-    public ChronoLocalDate dateNow(ZoneId zone) {
+    public ChronoLocalDate<?> dateNow(ZoneId zone) {
         return dateNow(Clock.system(zone));
     }
 
     /**
      * Obtains the current local date in this chronology from the specified clock.

@@ -575,20 +680,20 @@
      *
      * @param clock  the clock to use, not null
      * @return the current local date, not null
      * @throws DateTimeException if unable to create the date
      */
-    public ChronoLocalDate dateNow(Clock clock) {
+    public ChronoLocalDate<?> dateNow(Clock clock) {
         Objects.requireNonNull(clock, "clock");
         return date(LocalDate.now(clock));
     }
 
     //-----------------------------------------------------------------------
     /**
      * Obtains a local date in this chronology from another temporal object.
      * <p>
-     * This creates a date in this chronology based on the specified temporal.
+     * This obtains a date in this chronology based on the specified temporal.
      * A {@code TemporalAccessor} represents an arbitrary set of date and time information,
      * which this factory converts to an instance of {@code ChronoLocalDate}.
      * <p>
      * The conversion typically uses the {@link ChronoField#EPOCH_DAY EPOCH_DAY}
      * field, which is standardized across calendar systems.

@@ -597,17 +702,18 @@
      * allowing it to be used as a query via method reference, {@code aChronology::date}.
      *
      * @param temporal  the temporal object to convert, not null
      * @return the local date in this chronology, not null
      * @throws DateTimeException if unable to create the date
+     * @see ChronoLocalDate#from(TemporalAccessor)
      */
-    public abstract ChronoLocalDate date(TemporalAccessor temporal);
+    public abstract ChronoLocalDate<?> date(TemporalAccessor temporal);
 
     /**
      * Obtains a local date-time in this chronology from another temporal object.
      * <p>
-     * This creates a date-time in this chronology based on the specified temporal.
+     * This obtains a date-time in this chronology based on the specified temporal.
      * A {@code TemporalAccessor} represents an arbitrary set of date and time information,
      * which this factory converts to an instance of {@code ChronoLocalDateTime}.
      * <p>
      * The conversion extracts and combines the {@code ChronoLocalDate} and the
      * {@code LocalTime} from the temporal object.

@@ -619,10 +725,11 @@
      * allowing it to be used as a query via method reference, {@code aChronology::localDateTime}.
      *
      * @param temporal  the temporal object to convert, not null
      * @return the local date-time in this chronology, not null
      * @throws DateTimeException if unable to create the date-time
+     * @see ChronoLocalDateTime#from(TemporalAccessor)
      */
     public ChronoLocalDateTime<?> localDateTime(TemporalAccessor temporal) {
         try {
             return date(temporal).atTime(LocalTime.from(temporal));
         } catch (DateTimeException ex) {

@@ -631,11 +738,11 @@
     }
 
     /**
      * Obtains a {@code ChronoZonedDateTime} in this chronology from another temporal object.
      * <p>
-     * This creates a zoned date-time in this chronology based on the specified temporal.
+     * This obtains a zoned date-time in this chronology based on the specified temporal.
      * A {@code TemporalAccessor} represents an arbitrary set of date and time information,
      * which this factory converts to an instance of {@code ChronoZonedDateTime}.
      * <p>
      * The conversion will first obtain a {@code ZoneId} from the temporal object,
      * falling back to a {@code ZoneOffset} if necessary. It will then try to obtain

@@ -650,19 +757,21 @@
      * allowing it to be used as a query via method reference, {@code aChronology::zonedDateTime}.
      *
      * @param temporal  the temporal object to convert, not null
      * @return the zoned date-time in this chronology, not null
      * @throws DateTimeException if unable to create the date-time
+     * @see ChronoZonedDateTime#from(TemporalAccessor)
      */
     public ChronoZonedDateTime<?> zonedDateTime(TemporalAccessor temporal) {
         try {
             ZoneId zone = ZoneId.from(temporal);
             try {
                 Instant instant = Instant.from(temporal);
                 return zonedDateTime(instant, zone);
 
             } catch (DateTimeException ex1) {
+                @SuppressWarnings("rawtypes")
                 ChronoLocalDateTimeImpl cldt = ensureChronoLocalDateTime(localDateTime(temporal));
                 return ChronoZonedDateTimeImpl.ofBest(cldt, zone, null);
             }
         } catch (DateTimeException ex) {
             throw new DateTimeException("Unable to obtain ChronoZonedDateTime from TemporalAccessor: " + temporal.getClass(), ex);

@@ -670,11 +779,11 @@
     }
 
     /**
      * Obtains a {@code ChronoZonedDateTime} in this chronology from an {@code Instant}.
      * <p>
-     * This creates a zoned date-time with the same instant as that specified.
+     * This obtains a zoned date-time with the same instant as that specified.
      *
      * @param instant  the instant to create the date-time from, not null
      * @param zone  the time-zone, not null
      * @return the zoned date-time, not null
      * @throws DateTimeException if the result exceeds the supported range

@@ -701,15 +810,21 @@
 
     /**
      * Calculates the proleptic-year given the era and year-of-era.
      * <p>
      * This combines the era and year-of-era into the single proleptic-year field.
+     * <p>
+     * If the chronology makes active use of eras, such as {@code JapaneseChronology}
+     * then the year-of-era will be validated against the era.
+     * For other chronologies, validation is optional.
      *
      * @param era  the era of the correct type for the chronology, not null
      * @param yearOfEra  the chronology year-of-era
      * @return the proleptic-year
-     * @throws DateTimeException if unable to convert
+     * @throws DateTimeException if unable to convert to a proleptic-year,
+     *  such as if the year is invalid for the era
+     * @throws ClassCastException if the {@code era} is not of the correct type for the chronology
      */
     public abstract int prolepticYear(Era era, int yearOfEra);
 
     /**
      * Creates the chronology era object from the numeric value.

@@ -773,28 +888,179 @@
      * @param style  the style of the text required, not null
      * @param locale  the locale to use, not null
      * @return the text value of the chronology, not null
      */
     public String getDisplayName(TextStyle style, Locale locale) {
-        return new DateTimeFormatterBuilder().appendChronologyText(style).toFormatter(locale).format(new TemporalAccessor() {
+        return new DateTimeFormatterBuilder().appendChronologyText(style).toFormatter(locale).format(toTemporal());
+    }
+
+    /**
+     * Converts this chronology to a {@code TemporalAccessor}.
+     * <p>
+     * A {@code Chronology} can be fully represented as a {@code TemporalAccessor}.
+     * However, the interface is not implemented by this class as most of the
+     * methods on the interface have no meaning to {@code Chronology}.
+     * <p>
+     * The returned temporal has no supported fields, with the query method
+     * supporting the return of the chronology using {@link TemporalQuery#chronology()}.
+     *
+     * @return a temporal equivalent to this chronology, not null
+     */
+    private TemporalAccessor toTemporal() {
+        return new TemporalAccessor() {
             @Override
             public boolean isSupported(TemporalField field) {
                 return false;
             }
             @Override
             public long getLong(TemporalField field) {
-                throw new DateTimeException("Unsupported field: " + field);
+                throw new UnsupportedTemporalTypeException("Unsupported field: " + field);
             }
             @SuppressWarnings("unchecked")
             @Override
             public <R> R query(TemporalQuery<R> query) {
-                if (query == Queries.chronology()) {
+                if (query == TemporalQuery.chronology()) {
                     return (R) Chronology.this;
                 }
                 return TemporalAccessor.super.query(query);
             }
-        });
+        };
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Resolves parsed {@code ChronoField} values into a date during parsing.
+     * <p>
+     * Most {@code TemporalField} implementations are resolved using the
+     * resolve method on the field. By contrast, the {@code ChronoField} class
+     * defines fields that only have meaning relative to the chronology.
+     * As such, {@code ChronoField} date fields are resolved here in the
+     * context of a specific chronology.
+     * <p>
+     * The default implementation is suitable for most calendar systems.
+     * If {@link ChronoField#YEAR_OF_ERA} is found without an {@link ChronoField#ERA}
+     * then the last era in {@link #eras()} is used.
+     * The implementation assumes a 7 day week, that the first day-of-month
+     * has the value 1, and that first day-of-year has the value 1.
+     *
+     * @param fieldValues  the map of fields to values, which can be updated, not null
+     * @param resolverStyle  the requested type of resolve, not null
+     * @return the resolved date, null if insufficient information to create a date
+     * @throws DateTimeException if the date cannot be resolved, typically
+     *  because of a conflict in the input data
+     */
+    public ChronoLocalDate<?> resolveDate(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
+        // check epoch-day before inventing era
+        if (fieldValues.containsKey(EPOCH_DAY)) {
+            return dateEpochDay(fieldValues.remove(EPOCH_DAY));
+        }
+
+        // fix proleptic month before inventing era
+        Long pMonth = fieldValues.remove(PROLEPTIC_MONTH);
+        if (pMonth != null) {
+            // first day-of-month is likely to be safest for setting proleptic-month
+            // cannot add to year zero, as not all chronologies have a year zero
+            ChronoLocalDate<?> chronoDate = dateNow()
+                    .with(DAY_OF_MONTH, 1).with(PROLEPTIC_MONTH, pMonth);
+            addFieldValue(fieldValues, MONTH_OF_YEAR, chronoDate.get(MONTH_OF_YEAR));
+            addFieldValue(fieldValues, YEAR, chronoDate.get(YEAR));
+        }
+
+        // invent era if necessary to resolve year-of-era
+        Long yoeLong = fieldValues.remove(YEAR_OF_ERA);
+        if (yoeLong != null) {
+            Long eraLong = fieldValues.remove(ERA);
+            int yoe = range(YEAR_OF_ERA).checkValidIntValue(yoeLong, YEAR_OF_ERA);
+            if (eraLong != null) {
+                Era eraObj = eraOf(Math.toIntExact(eraLong));
+                addFieldValue(fieldValues, YEAR, prolepticYear(eraObj, yoe));
+            } else if (fieldValues.containsKey(YEAR)) {
+                int year = range(YEAR).checkValidIntValue(fieldValues.get(YEAR), YEAR);
+                ChronoLocalDate<?> chronoDate = dateYearDay(year, 1);
+                addFieldValue(fieldValues, YEAR, prolepticYear(chronoDate.getEra(), yoe));
+            } else {
+                List<Era> eras = eras();
+                if (eras.isEmpty()) {
+                    addFieldValue(fieldValues, YEAR, yoe);
+                } else {
+                    Era eraObj = eras.get(eras.size() - 1);
+                    addFieldValue(fieldValues, YEAR, prolepticYear(eraObj, yoe));
+                }
+            }
+        }
+
+        // build date
+        if (fieldValues.containsKey(YEAR)) {
+            if (fieldValues.containsKey(MONTH_OF_YEAR)) {
+                if (fieldValues.containsKey(DAY_OF_MONTH)) {
+                    int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR);
+                    int moy = range(MONTH_OF_YEAR).checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR), MONTH_OF_YEAR);
+                    int dom = range(DAY_OF_MONTH).checkValidIntValue(fieldValues.remove(DAY_OF_MONTH), DAY_OF_MONTH);
+                    return date(y, moy, dom);
+                }
+                if (fieldValues.containsKey(ALIGNED_WEEK_OF_MONTH)) {
+                    if (fieldValues.containsKey(ALIGNED_DAY_OF_WEEK_IN_MONTH)) {
+                        int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR);
+                        int moy = range(MONTH_OF_YEAR).checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR), MONTH_OF_YEAR);
+                        int aw = range(ALIGNED_WEEK_OF_MONTH).checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_MONTH), ALIGNED_WEEK_OF_MONTH);
+                        int ad = range(ALIGNED_DAY_OF_WEEK_IN_MONTH).checkValidIntValue(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_MONTH), ALIGNED_DAY_OF_WEEK_IN_MONTH);
+                        ChronoLocalDate<?> chronoDate = date(y, moy, 1);
+                        return chronoDate.plus((aw - 1) * 7 + (ad - 1), ChronoUnit.DAYS);
+                    }
+                    if (fieldValues.containsKey(DAY_OF_WEEK)) {
+                        int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR);
+                        int moy = range(MONTH_OF_YEAR).checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR), MONTH_OF_YEAR);
+                        int aw = range(ALIGNED_WEEK_OF_MONTH).checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_MONTH), ALIGNED_WEEK_OF_MONTH);
+                        int dow = range(DAY_OF_WEEK).checkValidIntValue(fieldValues.remove(DAY_OF_WEEK), DAY_OF_WEEK);
+                        ChronoLocalDate<?> chronoDate = date(y, moy, 1);
+                        return chronoDate.plus((aw - 1) * 7, ChronoUnit.DAYS).with(nextOrSame(DayOfWeek.of(dow)));
+                    }
+                }
+            }
+            if (fieldValues.containsKey(DAY_OF_YEAR)) {
+                int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR);
+                int doy = range(DAY_OF_YEAR).checkValidIntValue(fieldValues.remove(DAY_OF_YEAR), DAY_OF_YEAR);
+                return dateYearDay(y, doy);
+            }
+            if (fieldValues.containsKey(ALIGNED_WEEK_OF_YEAR)) {
+                if (fieldValues.containsKey(ALIGNED_DAY_OF_WEEK_IN_YEAR)) {
+                    int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR);
+                    int aw = range(ALIGNED_WEEK_OF_YEAR).checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_YEAR), ALIGNED_WEEK_OF_YEAR);
+                    int ad = range(ALIGNED_DAY_OF_WEEK_IN_YEAR).checkValidIntValue(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_YEAR), ALIGNED_DAY_OF_WEEK_IN_YEAR);
+                    ChronoLocalDate<?> chronoDate = dateYearDay(y, 1);
+                    return chronoDate.plus((aw - 1) * 7 + (ad - 1), ChronoUnit.DAYS);
+                }
+                if (fieldValues.containsKey(DAY_OF_WEEK)) {
+                    int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR);
+                    int aw = range(ALIGNED_WEEK_OF_YEAR).checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_YEAR), ALIGNED_WEEK_OF_YEAR);
+                    int dow = range(DAY_OF_WEEK).checkValidIntValue(fieldValues.remove(DAY_OF_WEEK), DAY_OF_WEEK);
+                    ChronoLocalDate<?> chronoDate = dateYearDay(y, 1);
+                    return chronoDate.plus((aw - 1) * 7, ChronoUnit.DAYS).with(nextOrSame(DayOfWeek.of(dow)));
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Adds a field-value pair to the map, checking for conflicts.
+     * <p>
+     * If the field is not already present, then the field-value pair is added to the map.
+     * If the field is already present and it has the same value as that specified, no action occurs.
+     * If the field is already present and it has a different value to that specified, then
+     * an exception is thrown.
+     *
+     * @param field  the field to add, not null
+     * @param value  the value to add, not null
+     * @throws DateTimeException if the field is already present with a different value
+     */
+    void addFieldValue(Map<TemporalField, Long> fieldValues, ChronoField field, long value) {
+        Long old = fieldValues.get(field);  // check first for better error message
+        if (old != null && old.longValue() != value) {
+            throw new DateTimeException("Conflict found: " + field + " " + old + " differs from " + field + " " + value);
+        }
+        fieldValues.put(field, value);
     }
 
     //-----------------------------------------------------------------------
     /**
      * Compares this chronology to another chronology.

@@ -859,20 +1125,20 @@
         return getId();
     }
 
     //-----------------------------------------------------------------------
     /**
-     * Writes the object using a
-     * <a href="../../../serialized-form.html#java.time.temporal.Ser">dedicated serialized form</a>.
+     * Writes the Chronology using a
+     * <a href="../../../serialized-form.html#java.time.chrono.Ser">dedicated serialized form</a>.
      * <pre>
-     *  out.writeByte(7);  // identifies this as a Chronology
-     * out.writeUTF(chronoId);
+     *  out.writeByte(1);  // identifies this as a Chronology
+     *  out.writeUTF(getId());
      * </pre>
      *
      * @return the instance of {@code Ser}, not null
      */
-    private Object writeReplace() {
+    protected Object writeReplace() {
         return new Ser(Ser.CHRONO_TYPE, this);
     }
 
     /**
      * Defend against malicious streams.