Created
July 9, 2013 20:37
-
-
Save jodastephen/5961060 to your computer and use it in GitHub Desktop.
Change Chronology to an interface
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
# HG changeset patch | |
# User scolebourne | |
# Date 1373402104 -3600 | |
# Node ID 31e46fdf3f133e075f7084d522ad47e9e3f0a061 | |
# Parent ae87386e002940843fa82efcfc2bf65995ad5c92 | |
Change Chronology to an interface | |
See #321 | |
diff --git a/src/share/classes/java/time/chrono/AbstractChronology.java b/src/share/classes/java/time/chrono/AbstractChronology.java | |
new file mode 100644 | |
--- /dev/null | |
+++ b/src/share/classes/java/time/chrono/AbstractChronology.java | |
@@ -0,0 +1,783 @@ | |
+/* | |
+ * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. | |
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
+ * | |
+ * This code is free software; you can redistribute it and/or modify it | |
+ * under the terms of the GNU General Public License version 2 only, as | |
+ * published by the Free Software Foundation. Oracle designates this | |
+ * particular file as subject to the "Classpath" exception as provided | |
+ * by Oracle in the LICENSE file that accompanied this code. | |
+ * | |
+ * This code is distributed in the hope that it will be useful, but WITHOUT | |
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
+ * version 2 for more details (a copy is included in the LICENSE file that | |
+ * accompanied this code). | |
+ * | |
+ * You should have received a copy of the GNU General Public License version | |
+ * 2 along with this work; if not, write to the Free Software Foundation, | |
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
+ * | |
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
+ * or visit www.oracle.com if you need additional information or have any | |
+ * questions. | |
+ */ | |
+ | |
+/* | |
+ * This file is available under and governed by the GNU General Public | |
+ * License version 2 only, as published by the Free Software Foundation. | |
+ * However, the following notice accompanied the original version of this | |
+ * file: | |
+ * | |
+ * Copyright (c) 2012, Stephen Colebourne & Michael Nascimento Santos | |
+ * | |
+ * All rights reserved. | |
+ * | |
+ * Redistribution and use in source and binary forms, with or without | |
+ * modification, are permitted provided that the following conditions are met: | |
+ * | |
+ * * Redistributions of source code must retain the above copyright notice, | |
+ * this list of conditions and the following disclaimer. | |
+ * | |
+ * * Redistributions in binary form must reproduce the above copyright notice, | |
+ * this list of conditions and the following disclaimer in the documentation | |
+ * and/or other materials provided with the distribution. | |
+ * | |
+ * * Neither the name of JSR-310 nor the names of its contributors | |
+ * may be used to endorse or promote products derived from this software | |
+ * without specific prior written permission. | |
+ * | |
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | |
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
+ * 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.ChronoUnit.DAYS; | |
+import static java.time.temporal.ChronoUnit.MONTHS; | |
+import static java.time.temporal.ChronoUnit.WEEKS; | |
+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.DateTimeException; | |
+import java.time.DayOfWeek; | |
+import java.time.format.ResolverStyle; | |
+import java.time.temporal.ChronoField; | |
+import java.time.temporal.TemporalAdjuster; | |
+import java.time.temporal.TemporalField; | |
+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; | |
+ | |
+/** | |
+ * An abstract implementation of a calendar system, used to organize and identify dates. | |
+ * <p> | |
+ * The main date and time API is built on the ISO calendar system. | |
+ * The chronology operates behind the scenes to represent the general concept of a calendar system. | |
+ * <p> | |
+ * See {@link Chronology} for more details. | |
+ * | |
+ * @implSpec | |
+ * This class is separated from the {@code Chronology} interface so that the static methods | |
+ * are not inherited. While {@code Chronology} can be implemented directly, it is strongly | |
+ * recommended to extend this abstract class instead. | |
+ * <p> | |
+ * 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. | |
+ * | |
+ * @since 1.8 | |
+ */ | |
+public abstract class AbstractChronology implements 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(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(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 | |
+ */ | |
+ 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 | |
+ * of {@link java.time.chrono.AbstractChronology} found in the bootclass loader. | |
+ * The built-in chronologies are registered explicitly. | |
+ * Calendars configured via the Thread's context classloader are local | |
+ * to that thread and are ignored. | |
+ * <p> | |
+ * The initialization is done only once using the registration | |
+ * of the IsoChronology as the test and the final step. | |
+ * Multiple threads may perform the initialization concurrently. | |
+ * Only the first registration of each Chronology is retained by the | |
+ * ConcurrentHashMap. | |
+ * @return true if the cache was initialized | |
+ */ | |
+ private static boolean initCache() { | |
+ if (CHRONOS_BY_ID.get("ISO") == null) { | |
+ // Initialization is incomplete | |
+ | |
+ // Register built-in Chronologies | |
+ registerChrono(HijrahChronology.INSTANCE); | |
+ registerChrono(JapaneseChronology.INSTANCE); | |
+ registerChrono(MinguoChronology.INSTANCE); | |
+ registerChrono(ThaiBuddhistChronology.INSTANCE); | |
+ | |
+ // Register Chronologies from the ServiceLoader | |
+ @SuppressWarnings("rawtypes") | |
+ ServiceLoader<AbstractChronology> loader = ServiceLoader.load(AbstractChronology.class, null); | |
+ for (AbstractChronology 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; | |
+ } | |
+ | |
+ //----------------------------------------------------------------------- | |
+ /** | |
+ * Obtains an instance of {@code Chronology} from a locale. | |
+ * <p> | |
+ * See {@link Chronology#ofLocale(Locale)}. | |
+ * | |
+ * @param locale the locale to use to obtain the calendar system, not null | |
+ * @return the calendar system associated with the locale, not null | |
+ * @throws java.time.DateTimeException if the locale-specified calendar cannot be found | |
+ */ | |
+ static Chronology ofLocale(Locale locale) { | |
+ Objects.requireNonNull(locale, "locale"); | |
+ String type = locale.getUnicodeLocaleType("ca"); | |
+ if (type == null || "iso".equals(type) || "iso8601".equals(type)) { | |
+ return IsoChronology.INSTANCE; | |
+ } | |
+ // 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> | |
+ * See {@link Chronology#of(String)}. | |
+ * | |
+ * @param id the chronology ID or calendar system type, not null | |
+ * @return the chronology with the identifier requested, not null | |
+ * @throws java.time.DateTimeException if the chronology cannot be found | |
+ */ | |
+ static Chronology of(String id) { | |
+ Objects.requireNonNull(id, "id"); | |
+ do { | |
+ Chronology chrono = of0(id); | |
+ 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 (id.equals(chrono.getId()) || id.equals(chrono.getCalendarType())) { | |
+ return chrono; | |
+ } | |
+ } | |
+ throw new DateTimeException("Unknown chronology: " + id); | |
+ } | |
+ | |
+ /** | |
+ * Obtains an instance of {@code Chronology} from a chronology ID or | |
+ * calendar system type. | |
+ * | |
+ * @param id the chronology ID or calendar system type, not null | |
+ * @return the chronology with the identifier requested, or {@code null} if not found | |
+ */ | |
+ private static Chronology of0(String id) { | |
+ Chronology chrono = CHRONOS_BY_ID.get(id); | |
+ if (chrono == null) { | |
+ chrono = CHRONOS_BY_TYPE.get(id); | |
+ } | |
+ return chrono; | |
+ } | |
+ | |
+ /** | |
+ * Returns the available chronologies. | |
+ * <p> | |
+ * Each returned {@code Chronology} is available for use in the system. | |
+ * The set of chronologies includes the system chronologies and | |
+ * any chronologies provided by the application via ServiceLoader | |
+ * configuration. | |
+ * | |
+ * @return the independent, modifiable set of the available chronology IDs, not null | |
+ */ | |
+ static Set<Chronology> getAvailableChronologies() { | |
+ initCache(); // force initialization | |
+ 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) { | |
+ chronos.add(chrono); | |
+ } | |
+ return chronos; | |
+ } | |
+ | |
+ //----------------------------------------------------------------------- | |
+ /** | |
+ * Creates an instance. | |
+ */ | |
+ protected AbstractChronology() { | |
+ } | |
+ | |
+ //----------------------------------------------------------------------- | |
+ /** | |
+ * 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> | |
+ * {@code ChronoField} instances are resolved by this method, which may | |
+ * be overridden in subclasses. | |
+ * <ul> | |
+ * <li>{@code EPOCH_DAY} - If present, this is converted to a date and | |
+ * all other date fields are then cross-checked against the date. | |
+ * <li>{@code PROLEPTIC_MONTH} - If present, then it is split into the | |
+ * {@code YEAR} and {@code MONTH_OF_YEAR}. If the mode is strict or smart | |
+ * then the field is validated. | |
+ * <li>{@code YEAR_OF_ERA} and {@code ERA} - If both are present, then they | |
+ * are combined to form a {@code YEAR}. In lenient mode, the {@code YEAR_OF_ERA} | |
+ * range is not validated, in smart and strict mode it is. The {@code ERA} is | |
+ * validated for range in all three modes. If only the {@code YEAR_OF_ERA} is | |
+ * present, and the mode is smart or lenient, then the last available era | |
+ * is assumed. In strict mode, no era is assumed and the {@code YEAR_OF_ERA} is | |
+ * left untouched. If only the {@code ERA} is present, then it is left untouched. | |
+ * <li>{@code YEAR}, {@code MONTH_OF_YEAR} and {@code DAY_OF_MONTH} - | |
+ * If all three are present, then they are combined to form a date. | |
+ * In all three modes, the {@code YEAR} is validated. | |
+ * If the mode is smart or strict, then the month and day are validated. | |
+ * If the mode is lenient, then the date is combined in a manner equivalent to | |
+ * creating a date on the first day of the first month in the requested year, | |
+ * then adding the difference in months, then the difference in days. | |
+ * If the mode is smart, and the day-of-month is greater than the maximum for | |
+ * the year-month, then the day-of-month is adjusted to the last day-of-month. | |
+ * If the mode is strict, then the three fields must form a valid date. | |
+ * <li>{@code YEAR} and {@code DAY_OF_YEAR} - | |
+ * If both are present, then they are combined to form a date. | |
+ * In all three modes, the {@code YEAR} is validated. | |
+ * If the mode is lenient, then the date is combined in a manner equivalent to | |
+ * creating a date on the first day of the requested year, then adding | |
+ * the difference in days. | |
+ * If the mode is smart or strict, then the two fields must form a valid date. | |
+ * <li>{@code YEAR}, {@code MONTH_OF_YEAR}, {@code ALIGNED_WEEK_OF_MONTH} and | |
+ * {@code ALIGNED_DAY_OF_WEEK_IN_MONTH} - | |
+ * If all four are present, then they are combined to form a date. | |
+ * In all three modes, the {@code YEAR} is validated. | |
+ * If the mode is lenient, then the date is combined in a manner equivalent to | |
+ * creating a date on the first day of the first month in the requested year, then adding | |
+ * the difference in months, then the difference in weeks, then in days. | |
+ * If the mode is smart or strict, then the all four fields are validated to | |
+ * their outer ranges. The date is then combined in a manner equivalent to | |
+ * creating a date on the first day of the requested year and month, then adding | |
+ * the amount in weeks and days to reach their values. If the mode is strict, | |
+ * the date is additionally validated to check that the day and week adjustment | |
+ * did not change the month. | |
+ * <li>{@code YEAR}, {@code MONTH_OF_YEAR}, {@code ALIGNED_WEEK_OF_MONTH} and | |
+ * {@code DAY_OF_WEEK} - If all four are present, then they are combined to | |
+ * form a date. The approach is the same as described above for | |
+ * years, months and weeks in {@code ALIGNED_DAY_OF_WEEK_IN_MONTH}. | |
+ * The day-of-week is adjusted as the next or same matching day-of-week once | |
+ * the years, months and weeks have been handled. | |
+ * <li>{@code YEAR}, {@code ALIGNED_WEEK_OF_YEAR} and {@code ALIGNED_DAY_OF_WEEK_IN_YEAR} - | |
+ * If all three are present, then they are combined to form a date. | |
+ * In all three modes, the {@code YEAR} is validated. | |
+ * If the mode is lenient, then the date is combined in a manner equivalent to | |
+ * creating a date on the first day of the requested year, then adding | |
+ * the difference in weeks, then in days. | |
+ * If the mode is smart or strict, then the all three fields are validated to | |
+ * their outer ranges. The date is then combined in a manner equivalent to | |
+ * creating a date on the first day of the requested year, then adding | |
+ * the amount in weeks and days to reach their values. If the mode is strict, | |
+ * the date is additionally validated to check that the day and week adjustment | |
+ * did not change the year. | |
+ * <li>{@code YEAR}, {@code ALIGNED_WEEK_OF_YEAR} and {@code DAY_OF_WEEK} - | |
+ * If all three are present, then they are combined to form a date. | |
+ * The approach is the same as described above for years and weeks in | |
+ * {@code ALIGNED_DAY_OF_WEEK_IN_YEAR}. The day-of-week is adjusted as the | |
+ * next or same matching day-of-week once the years and weeks have been handled. | |
+ * </ul> | |
+ * <p> | |
+ * The default implementation is suitable for most calendar systems. | |
+ * If {@link java.time.temporal.ChronoField#YEAR_OF_ERA} is found without an {@link java.time.temporal.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, that first day-of-year has the value 1, and that the | |
+ * first of the month and year always exists. | |
+ * | |
+ * @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 java.time.DateTimeException if the date cannot be resolved, typically | |
+ * because of a conflict in the input data | |
+ */ | |
+ @Override | |
+ 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 | |
+ resolveProlepticMonth(fieldValues, resolverStyle); | |
+ | |
+ // invent era if necessary to resolve year-of-era | |
+ ChronoLocalDate<?> resolved = resolveYearOfEra(fieldValues, resolverStyle); | |
+ if (resolved != null) { | |
+ return resolved; | |
+ } | |
+ | |
+ // build date | |
+ if (fieldValues.containsKey(YEAR)) { | |
+ if (fieldValues.containsKey(MONTH_OF_YEAR)) { | |
+ if (fieldValues.containsKey(DAY_OF_MONTH)) { | |
+ return resolveYMD(fieldValues, resolverStyle); | |
+ } | |
+ if (fieldValues.containsKey(ALIGNED_WEEK_OF_MONTH)) { | |
+ if (fieldValues.containsKey(ALIGNED_DAY_OF_WEEK_IN_MONTH)) { | |
+ return resolveYMAA(fieldValues, resolverStyle); | |
+ } | |
+ if (fieldValues.containsKey(DAY_OF_WEEK)) { | |
+ return resolveYMAD(fieldValues, resolverStyle); | |
+ } | |
+ } | |
+ } | |
+ if (fieldValues.containsKey(DAY_OF_YEAR)) { | |
+ return resolveYD(fieldValues, resolverStyle); | |
+ } | |
+ if (fieldValues.containsKey(ALIGNED_WEEK_OF_YEAR)) { | |
+ if (fieldValues.containsKey(ALIGNED_DAY_OF_WEEK_IN_YEAR)) { | |
+ return resolveYAA(fieldValues, resolverStyle); | |
+ } | |
+ if (fieldValues.containsKey(DAY_OF_WEEK)) { | |
+ return resolveYAD(fieldValues, resolverStyle); | |
+ } | |
+ } | |
+ } | |
+ return null; | |
+ } | |
+ | |
+ void resolveProlepticMonth(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) { | |
+ Long pMonth = fieldValues.remove(PROLEPTIC_MONTH); | |
+ if (pMonth != null) { | |
+ if (resolverStyle != ResolverStyle.LENIENT) { | |
+ PROLEPTIC_MONTH.checkValidValue(pMonth); | |
+ } | |
+ // 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)); | |
+ } | |
+ } | |
+ | |
+ ChronoLocalDate<?> resolveYearOfEra(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) { | |
+ Long yoeLong = fieldValues.remove(YEAR_OF_ERA); | |
+ if (yoeLong != null) { | |
+ Long eraLong = fieldValues.remove(ERA); | |
+ int yoe; | |
+ if (resolverStyle != ResolverStyle.LENIENT) { | |
+ yoe = range(YEAR_OF_ERA).checkValidIntValue(yoeLong, YEAR_OF_ERA); | |
+ } else { | |
+ yoe = Math.toIntExact(yoeLong); | |
+ } | |
+ if (eraLong != null) { | |
+ Era eraObj = eraOf(range(ERA).checkValidIntValue(eraLong, ERA)); | |
+ 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 if (resolverStyle == ResolverStyle.STRICT) { | |
+ // do not invent era if strict | |
+ // reinstate the field removed earlier, no cross-check issues | |
+ fieldValues.put(YEAR_OF_ERA, yoeLong); | |
+ } 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)); | |
+ } | |
+ } | |
+ } | |
+ } else if (fieldValues.containsKey(ERA)) { | |
+ range(ERA).checkValidValue(fieldValues.get(ERA), ERA); // always validated | |
+ } | |
+ return null; | |
+ } | |
+ | |
+ ChronoLocalDate<?> resolveYMD(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) { | |
+ int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR); | |
+ if (resolverStyle == ResolverStyle.LENIENT) { | |
+ long months = Math.subtractExact(fieldValues.remove(MONTH_OF_YEAR), 1); | |
+ long days = Math.subtractExact(fieldValues.remove(DAY_OF_MONTH), 1); | |
+ return date(y, 1, 1).plus(months, MONTHS).plus(days, DAYS); | |
+ } | |
+ int moy = range(MONTH_OF_YEAR).checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR), MONTH_OF_YEAR); | |
+ ValueRange domRange = range(DAY_OF_MONTH); | |
+ int dom = domRange.checkValidIntValue(fieldValues.remove(DAY_OF_MONTH), DAY_OF_MONTH); | |
+ if (resolverStyle == ResolverStyle.SMART) { // previous valid | |
+ try { | |
+ return date(y, moy, dom); | |
+ } catch (DateTimeException ex) { | |
+ return date(y, moy, 1).with(TemporalAdjuster.lastDayOfMonth()); | |
+ } | |
+ } | |
+ return date(y, moy, dom); | |
+ } | |
+ | |
+ ChronoLocalDate<?> resolveYD(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) { | |
+ int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR); | |
+ if (resolverStyle == ResolverStyle.LENIENT) { | |
+ long days = Math.subtractExact(fieldValues.remove(DAY_OF_YEAR), 1); | |
+ return dateYearDay(y, 1).plus(days, DAYS); | |
+ } | |
+ int doy = range(DAY_OF_YEAR).checkValidIntValue(fieldValues.remove(DAY_OF_YEAR), DAY_OF_YEAR); | |
+ return dateYearDay(y, doy); // smart is same as strict | |
+ } | |
+ | |
+ ChronoLocalDate<?> resolveYMAA(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) { | |
+ int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR); | |
+ if (resolverStyle == ResolverStyle.LENIENT) { | |
+ long months = Math.subtractExact(fieldValues.remove(MONTH_OF_YEAR), 1); | |
+ long weeks = Math.subtractExact(fieldValues.remove(ALIGNED_WEEK_OF_MONTH), 1); | |
+ long days = Math.subtractExact(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_MONTH), 1); | |
+ return date(y, 1, 1).plus(months, MONTHS).plus(weeks, WEEKS).plus(days, DAYS); | |
+ } | |
+ 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<?> date = date(y, moy, 1).plus((aw - 1) * 7 + (ad - 1), DAYS); | |
+ if (resolverStyle == ResolverStyle.STRICT && date.get(MONTH_OF_YEAR) != moy) { | |
+ throw new DateTimeException("Strict mode rejected resolved date as it is in a different month"); | |
+ } | |
+ return date; | |
+ } | |
+ | |
+ ChronoLocalDate<?> resolveYMAD(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) { | |
+ int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR); | |
+ if (resolverStyle == ResolverStyle.LENIENT) { | |
+ long months = Math.subtractExact(fieldValues.remove(MONTH_OF_YEAR), 1); | |
+ long weeks = Math.subtractExact(fieldValues.remove(ALIGNED_WEEK_OF_MONTH), 1); | |
+ long dow = Math.subtractExact(fieldValues.remove(DAY_OF_WEEK), 1); | |
+ return resolveAligned(date(y, 1, 1), months, weeks, dow); | |
+ } | |
+ 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<?> date = date(y, moy, 1).plus((aw - 1) * 7, DAYS).with(nextOrSame(DayOfWeek.of(dow))); | |
+ if (resolverStyle == ResolverStyle.STRICT && date.get(MONTH_OF_YEAR) != moy) { | |
+ throw new DateTimeException("Strict mode rejected resolved date as it is in a different month"); | |
+ } | |
+ return date; | |
+ } | |
+ | |
+ ChronoLocalDate<?> resolveYAA(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) { | |
+ int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR); | |
+ if (resolverStyle == ResolverStyle.LENIENT) { | |
+ long weeks = Math.subtractExact(fieldValues.remove(ALIGNED_WEEK_OF_YEAR), 1); | |
+ long days = Math.subtractExact(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_YEAR), 1); | |
+ return dateYearDay(y, 1).plus(weeks, WEEKS).plus(days, DAYS); | |
+ } | |
+ 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<?> date = dateYearDay(y, 1).plus((aw - 1) * 7 + (ad - 1), DAYS); | |
+ if (resolverStyle == ResolverStyle.STRICT && date.get(YEAR) != y) { | |
+ throw new DateTimeException("Strict mode rejected resolved date as it is in a different year"); | |
+ } | |
+ return date; | |
+ } | |
+ | |
+ ChronoLocalDate<?> resolveYAD(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) { | |
+ int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR); | |
+ if (resolverStyle == ResolverStyle.LENIENT) { | |
+ long weeks = Math.subtractExact(fieldValues.remove(ALIGNED_WEEK_OF_YEAR), 1); | |
+ long dow = Math.subtractExact(fieldValues.remove(DAY_OF_WEEK), 1); | |
+ return resolveAligned(dateYearDay(y, 1), 0, weeks, dow); | |
+ } | |
+ 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<?> date = dateYearDay(y, 1).plus((aw - 1) * 7, DAYS).with(nextOrSame(DayOfWeek.of(dow))); | |
+ if (resolverStyle == ResolverStyle.STRICT && date.get(YEAR) != y) { | |
+ throw new DateTimeException("Strict mode rejected resolved date as it is in a different year"); | |
+ } | |
+ return date; | |
+ } | |
+ | |
+ ChronoLocalDate<?> resolveAligned(ChronoLocalDate<?> base, long months, long weeks, long dow) { | |
+ ChronoLocalDate<?> date = base.plus(months, MONTHS).plus(weeks, WEEKS); | |
+ if (dow > 7) { | |
+ date = date.plus((dow - 1) / 7, WEEKS); | |
+ dow = ((dow - 1) % 7) + 1; | |
+ } else if (dow < 1) { | |
+ date = date.plus(Math.subtractExact(dow, 7) / 7, WEEKS); | |
+ dow = ((dow + 6) % 7) + 1; | |
+ } | |
+ return date.with(nextOrSame(DayOfWeek.of((int) dow))); | |
+ } | |
+ | |
+ /** | |
+ * 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 java.time.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. | |
+ * <p> | |
+ * The comparison order first by the chronology ID string, then by any | |
+ * additional information specific to the subclass. | |
+ * It is "consistent with equals", as defined by {@link Comparable}. | |
+ * | |
+ * @implSpec | |
+ * This implementation compares the chronology ID. | |
+ * Subclasses must compare any additional state that they store. | |
+ * | |
+ * @param other the other chronology to compare to, not null | |
+ * @return the comparator value, negative if less, positive if greater | |
+ */ | |
+ @Override | |
+ public int compareTo(Chronology other) { | |
+ return getId().compareTo(other.getId()); | |
+ } | |
+ | |
+ /** | |
+ * Checks if this chronology is equal to another chronology. | |
+ * <p> | |
+ * The comparison is based on the entire state of the object. | |
+ * | |
+ * @implSpec | |
+ * This implementation checks the type and calls | |
+ * {@link #compareTo(java.time.chrono.Chronology)}. | |
+ * | |
+ * @param obj the object to check, null returns false | |
+ * @return true if this is equal to the other chronology | |
+ */ | |
+ @Override | |
+ public boolean equals(Object obj) { | |
+ if (this == obj) { | |
+ return true; | |
+ } | |
+ if (obj instanceof AbstractChronology) { | |
+ return compareTo((AbstractChronology) obj) == 0; | |
+ } | |
+ return false; | |
+ } | |
+ | |
+ /** | |
+ * A hash code for this chronology. | |
+ * <p> | |
+ * The hash code should be based on the entire state of the object. | |
+ * | |
+ * @implSpec | |
+ * This implementation is based on the chronology ID and class. | |
+ * Subclasses should add any additional state that they store. | |
+ * | |
+ * @return a suitable hash code | |
+ */ | |
+ @Override | |
+ public int hashCode() { | |
+ return getClass().hashCode() ^ getId().hashCode(); | |
+ } | |
+ | |
+ //----------------------------------------------------------------------- | |
+ /** | |
+ * Outputs this chronology as a {@code String}, using the chronology ID. | |
+ * | |
+ * @return a string representation of this chronology, not null | |
+ */ | |
+ @Override | |
+ public String toString() { | |
+ return getId(); | |
+ } | |
+ | |
+ //----------------------------------------------------------------------- | |
+ /** | |
+ * Writes the Chronology using a | |
+ * <a href="../../../serialized-form.html#java.time.chrono.Ser">dedicated serialized form</a>. | |
+ * <pre> | |
+ * out.writeByte(1); // identifies this as a Chronology | |
+ * out.writeUTF(getId()); | |
+ * </pre> | |
+ * | |
+ * @return the instance of {@code Ser}, not null | |
+ */ | |
+ protected Object writeReplace() { | |
+ return new Ser(Ser.CHRONO_TYPE, this); | |
+ } | |
+ | |
+ /** | |
+ * Defend against malicious streams. | |
+ * @return never | |
+ * @throws java.io.InvalidObjectException always | |
+ */ | |
+ private Object readResolve() throws ObjectStreamException { | |
+ throw new InvalidObjectException("Deserialization via serialization delegate"); | |
+ } | |
+ | |
+ void writeExternal(DataOutput out) throws IOException { | |
+ out.writeUTF(getId()); | |
+ } | |
+ | |
+ static Chronology readExternal(DataInput in) throws IOException { | |
+ String id = in.readUTF(); | |
+ return Chronology.of(id); | |
+ } | |
+ | |
+} | |
diff --git a/src/share/classes/java/time/chrono/ChronoLocalDate.java b/src/share/classes/java/time/chrono/ChronoLocalDate.java | |
--- a/src/share/classes/java/time/chrono/ChronoLocalDate.java | |
+++ b/src/share/classes/java/time/chrono/ChronoLocalDate.java | |
@@ -264,7 +264,7 @@ | |
* @see #isEqual | |
*/ | |
static Comparator<ChronoLocalDate<?>> timeLineOrder() { | |
- return Chronology.DATE_ORDER; | |
+ return AbstractChronology.DATE_ORDER; | |
} | |
//----------------------------------------------------------------------- | |
diff --git a/src/share/classes/java/time/chrono/ChronoLocalDateTime.java b/src/share/classes/java/time/chrono/ChronoLocalDateTime.java | |
--- a/src/share/classes/java/time/chrono/ChronoLocalDateTime.java | |
+++ b/src/share/classes/java/time/chrono/ChronoLocalDateTime.java | |
@@ -136,7 +136,7 @@ | |
* @see #isEqual | |
*/ | |
static Comparator<ChronoLocalDateTime<?>> timeLineOrder() { | |
- return Chronology.DATE_TIME_ORDER; | |
+ return AbstractChronology.DATE_TIME_ORDER; | |
} | |
//----------------------------------------------------------------------- | |
diff --git a/src/share/classes/java/time/chrono/ChronoZonedDateTime.java b/src/share/classes/java/time/chrono/ChronoZonedDateTime.java | |
--- a/src/share/classes/java/time/chrono/ChronoZonedDateTime.java | |
+++ b/src/share/classes/java/time/chrono/ChronoZonedDateTime.java | |
@@ -137,7 +137,7 @@ | |
* @see #isEqual | |
*/ | |
static Comparator<ChronoZonedDateTime<?>> timeLineOrder() { | |
- return Chronology.INSTANT_ORDER; | |
+ return AbstractChronology.INSTANT_ORDER; | |
} | |
//----------------------------------------------------------------------- | |
diff --git a/src/share/classes/java/time/chrono/Chronology.java b/src/share/classes/java/time/chrono/Chronology.java | |
--- a/src/share/classes/java/time/chrono/Chronology.java | |
+++ b/src/share/classes/java/time/chrono/Chronology.java | |
@@ -61,67 +61,32 @@ | |
*/ | |
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.ChronoUnit.DAYS; | |
-import static java.time.temporal.ChronoUnit.MONTHS; | |
-import static java.time.temporal.ChronoUnit.WEEKS; | |
-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.Month; | |
-import java.time.Year; | |
import java.time.ZoneId; | |
import java.time.format.DateTimeFormatterBuilder; | |
import java.time.format.ResolverStyle; | |
import java.time.format.TextStyle; | |
import java.time.temporal.ChronoField; | |
-import java.time.temporal.Temporal; | |
import java.time.temporal.TemporalAccessor; | |
-import java.time.temporal.TemporalAdjuster; | |
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. | |
+ * The chronology operates behind the scenes to represent the general concept of a calendar system. | |
* For example, the Japanese, Minguo, Thai Buddhist and others. | |
* <p> | |
* Most other calendar systems also operate on the shared concepts of year, month and day, | |
@@ -182,131 +147,15 @@ | |
* CLDR type and, if applicable, the CLDR variant, | |
* | |
* @implSpec | |
- * This class must be implemented with care to ensure other classes operate correctly. | |
+ * This interface 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. | |
* | |
* @since 1.8 | |
*/ | |
-public abstract class Chronology implements Comparable<Chronology> { | |
+public interface Chronology extends 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 | |
- */ | |
- 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 | |
- * of {@link java.time.chrono.Chronology} found in the bootclass loader. | |
- * The built-in chronologies are registered explicitly. | |
- * Calendars configured via the Thread's context classloader are local | |
- * to that thread and are ignored. | |
- * <p> | |
- * The initialization is done only once using the registration | |
- * of the IsoChronology as the test and the final step. | |
- * Multiple threads may perform the initialization concurrently. | |
- * Only the first registration of each Chronology is retained by the | |
- * ConcurrentHashMap. | |
- * @return true if the cache was initialized | |
- */ | |
- private static boolean initCache() { | |
- if (CHRONOS_BY_ID.get("ISO") == null) { | |
- // Initialization is incomplete | |
- | |
- // 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; | |
- } | |
- | |
- //----------------------------------------------------------------------- | |
- /** | |
* Obtains an instance of {@code Chronology} from a temporal object. | |
* <p> | |
* This obtains a chronology based on the specified temporal. | |
@@ -323,7 +172,7 @@ | |
* @return the chronology, not null | |
* @throws DateTimeException if unable to convert to an {@code Chronology} | |
*/ | |
- public static Chronology from(TemporalAccessor temporal) { | |
+ static Chronology from(TemporalAccessor temporal) { | |
Objects.requireNonNull(temporal, "temporal"); | |
Chronology obj = temporal.query(TemporalQuery.chronology()); | |
return (obj != null ? obj : IsoChronology.INSTANCE); | |
@@ -370,31 +219,8 @@ | |
* @return the calendar system associated with the locale, not null | |
* @throws DateTimeException if the locale-specified calendar cannot be found | |
*/ | |
- public static Chronology ofLocale(Locale locale) { | |
- Objects.requireNonNull(locale, "locale"); | |
- String type = locale.getUnicodeLocaleType("ca"); | |
- if (type == null || "iso".equals(type) || "iso8601".equals(type)) { | |
- return IsoChronology.INSTANCE; | |
- } | |
- // 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); | |
+ static Chronology ofLocale(Locale locale) { | |
+ return AbstractChronology.ofLocale(locale); | |
} | |
//----------------------------------------------------------------------- | |
@@ -418,41 +244,8 @@ | |
* @return the chronology with the identifier requested, not null | |
* @throws DateTimeException if the chronology cannot be found | |
*/ | |
- public static Chronology of(String id) { | |
- Objects.requireNonNull(id, "id"); | |
- do { | |
- Chronology chrono = of0(id); | |
- 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 (id.equals(chrono.getId()) || id.equals(chrono.getCalendarType())) { | |
- return chrono; | |
- } | |
- } | |
- throw new DateTimeException("Unknown chronology: " + id); | |
- } | |
- | |
- /** | |
- * Obtains an instance of {@code Chronology} from a chronology ID or | |
- * calendar system type. | |
- * | |
- * @param id the chronology ID or calendar system type, not null | |
- * @return the chronology with the identifier requested, or {@code null} if not found | |
- */ | |
- private static Chronology of0(String id) { | |
- Chronology chrono = CHRONOS_BY_ID.get(id); | |
- if (chrono == null) { | |
- chrono = CHRONOS_BY_TYPE.get(id); | |
- } | |
- return chrono; | |
+ static Chronology of(String id) { | |
+ return AbstractChronology.of(id); | |
} | |
/** | |
@@ -465,24 +258,8 @@ | |
* | |
* @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()); | |
- | |
- /// Add in Chronologies from the ServiceLoader configuration | |
- @SuppressWarnings("rawtypes") | |
- ServiceLoader<Chronology> loader = ServiceLoader.load(Chronology.class); | |
- for (Chronology chrono : loader) { | |
- chronos.add(chrono); | |
- } | |
- return chronos; | |
- } | |
- | |
- //----------------------------------------------------------------------- | |
- /** | |
- * Creates an instance. | |
- */ | |
- protected Chronology() { | |
+ static Set<Chronology> getAvailableChronologies() { | |
+ return AbstractChronology.getAvailableChronologies(); | |
} | |
//----------------------------------------------------------------------- | |
@@ -495,7 +272,7 @@ | |
* @return the chronology ID, not null | |
* @see #getCalendarType() | |
*/ | |
- public abstract String getId(); | |
+ String getId(); | |
/** | |
* Gets the calendar type of the calendar system. | |
@@ -510,13 +287,17 @@ | |
* @return the calendar system type, null if the calendar is not defined by CLDR/LDML | |
* @see #getId() | |
*/ | |
- public abstract String getCalendarType(); | |
+ String getCalendarType(); | |
//----------------------------------------------------------------------- | |
/** | |
* Obtains a local date in this chronology from the era, year-of-era, | |
* month-of-year and day-of-month fields. | |
* | |
+ * @implSpec | |
+ * The default implementation combines the era and year-of-era into a proleptic | |
+ * year before calling {@link #date(int, int, int)}. | |
+ * | |
* @param era the era of the correct type for the chronology, not null | |
* @param yearOfEra the chronology year-of-era | |
* @param month the chronology month-of-year | |
@@ -525,7 +306,7 @@ | |
* @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) { | |
+ default ChronoLocalDate<?> date(Era era, int yearOfEra, int month, int dayOfMonth) { | |
return date(prolepticYear(era, yearOfEra), month, dayOfMonth); | |
} | |
@@ -539,12 +320,16 @@ | |
* @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); | |
+ 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. | |
* | |
+ * @implSpec | |
+ * The default implementation combines the era and year-of-era into a proleptic | |
+ * year before calling {@link #dateYearDay(int, int)}. | |
+ * | |
* @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 | |
@@ -552,7 +337,7 @@ | |
* @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) { | |
+ default ChronoLocalDate<?> dateYearDay(Era era, int yearOfEra, int dayOfYear) { | |
return dateYearDay(prolepticYear(era, yearOfEra), dayOfYear); | |
} | |
@@ -565,7 +350,7 @@ | |
* @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); | |
+ ChronoLocalDate<?> dateYearDay(int prolepticYear, int dayOfYear); | |
/** | |
* Obtains a local date in this chronology from the epoch-day. | |
@@ -577,7 +362,7 @@ | |
* @return the local date in this chronology, not null | |
* @throws DateTimeException if unable to create the date | |
*/ | |
- public abstract ChronoLocalDate<?> dateEpochDay(long epochDay); | |
+ ChronoLocalDate<?> dateEpochDay(long epochDay); | |
//----------------------------------------------------------------------- | |
/** | |
@@ -588,13 +373,14 @@ | |
* <p> | |
* Using this method will prevent the ability to use an alternate clock for testing | |
* because the clock is hard-coded. | |
- * <p> | |
- * This implementation uses {@link #dateNow(Clock)}. | |
+ * | |
+ * @implSpec | |
+ * The default implementation invokes {@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() { | |
+ default ChronoLocalDate<?> dateNow() { | |
return dateNow(Clock.systemDefaultZone()); | |
} | |
@@ -607,11 +393,14 @@ | |
* Using this method will prevent the ability to use an alternate clock for testing | |
* because the clock is hard-coded. | |
* | |
+ * @implSpec | |
+ * The default implementation invokes {@link #dateNow(Clock)}. | |
+ * | |
* @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) { | |
+ default ChronoLocalDate<?> dateNow(ZoneId zone) { | |
return dateNow(Clock.system(zone)); | |
} | |
@@ -622,11 +411,14 @@ | |
* Using this method allows the use of an alternate clock for testing. | |
* The alternate clock may be introduced using {@link Clock dependency injection}. | |
* | |
+ * @implSpec | |
+ * The default implementation invokes {@link #date(TemporalAccessor)} )}. | |
+ * | |
* @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) { | |
+ default ChronoLocalDate<?> dateNow(Clock clock) { | |
Objects.requireNonNull(clock, "clock"); | |
return date(LocalDate.now(clock)); | |
} | |
@@ -650,7 +442,7 @@ | |
* @throws DateTimeException if unable to create the date | |
* @see ChronoLocalDate#from(TemporalAccessor) | |
*/ | |
- public abstract ChronoLocalDate<?> date(TemporalAccessor temporal); | |
+ ChronoLocalDate<?> date(TemporalAccessor temporal); | |
/** | |
* Obtains a local date-time in this chronology from another temporal object. | |
@@ -673,7 +465,7 @@ | |
* @throws DateTimeException if unable to create the date-time | |
* @see ChronoLocalDateTime#from(TemporalAccessor) | |
*/ | |
- public ChronoLocalDateTime<?> localDateTime(TemporalAccessor temporal) { | |
+ default ChronoLocalDateTime<?> localDateTime(TemporalAccessor temporal) { | |
try { | |
return date(temporal).atTime(LocalTime.from(temporal)); | |
} catch (DateTimeException ex) { | |
@@ -705,7 +497,7 @@ | |
* @throws DateTimeException if unable to create the date-time | |
* @see ChronoZonedDateTime#from(TemporalAccessor) | |
*/ | |
- public ChronoZonedDateTime<?> zonedDateTime(TemporalAccessor temporal) { | |
+ default ChronoZonedDateTime<?> zonedDateTime(TemporalAccessor temporal) { | |
try { | |
ZoneId zone = ZoneId.from(temporal); | |
try { | |
@@ -732,7 +524,7 @@ | |
* @return the zoned date-time, not null | |
* @throws DateTimeException if the result exceeds the supported range | |
*/ | |
- public ChronoZonedDateTime<?> zonedDateTime(Instant instant, ZoneId zone) { | |
+ default ChronoZonedDateTime<?> zonedDateTime(Instant instant, ZoneId zone) { | |
return ChronoZonedDateTimeImpl.ofInstant(this, instant, zone); | |
} | |
@@ -750,7 +542,7 @@ | |
* @param prolepticYear the proleptic-year to check, not validated for range | |
* @return true if the year is a leap year | |
*/ | |
- public abstract boolean isLeapYear(long prolepticYear); | |
+ boolean isLeapYear(long prolepticYear); | |
/** | |
* Calculates the proleptic-year given the era and year-of-era. | |
@@ -768,7 +560,7 @@ | |
* 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); | |
+ int prolepticYear(Era era, int yearOfEra); | |
/** | |
* Creates the chronology era object from the numeric value. | |
@@ -789,7 +581,7 @@ | |
* @return the calendar system era, not null | |
* @throws DateTimeException if unable to create the era | |
*/ | |
- public abstract Era eraOf(int eraValue); | |
+ Era eraOf(int eraValue); | |
/** | |
* Gets the list of eras for the chronology. | |
@@ -800,7 +592,7 @@ | |
* | |
* @return the list of eras for the chronology, may be immutable, not null | |
*/ | |
- public abstract List<Era> eras(); | |
+ List<Era> eras(); | |
//----------------------------------------------------------------------- | |
/** | |
@@ -819,7 +611,7 @@ | |
* @return the range of valid values for the field, not null | |
* @throws DateTimeException if the range for the field cannot be obtained | |
*/ | |
- public abstract ValueRange range(ChronoField field); | |
+ ValueRange range(ChronoField field); | |
//----------------------------------------------------------------------- | |
/** | |
@@ -829,28 +621,16 @@ | |
* suitable for presentation to the user. | |
* The parameters control the style of the returned text and the locale. | |
* | |
+ * @implSpec | |
+ * The default implementation behaves as the the formatter was used to | |
+ * format the chronology textual name. | |
+ * | |
* @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(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() { | |
+ default String getDisplayName(TextStyle style, Locale locale) { | |
+ TemporalAccessor temporal = new TemporalAccessor() { | |
@Override | |
public boolean isSupported(TemporalField field) { | |
return false; | |
@@ -868,6 +648,7 @@ | |
return TemporalAccessor.super.query(query); | |
} | |
}; | |
+ return new DateTimeFormatterBuilder().appendChronologyText(style).toFormatter(locale).format(temporal); | |
} | |
//----------------------------------------------------------------------- | |
@@ -880,82 +661,8 @@ | |
* As such, {@code ChronoField} date fields are resolved here in the | |
* context of a specific chronology. | |
* <p> | |
- * {@code ChronoField} instances are resolved by this method, which may | |
- * be overridden in subclasses. | |
- * <ul> | |
- * <li>{@code EPOCH_DAY} - If present, this is converted to a date and | |
- * all other date fields are then cross-checked against the date. | |
- * <li>{@code PROLEPTIC_MONTH} - If present, then it is split into the | |
- * {@code YEAR} and {@code MONTH_OF_YEAR}. If the mode is strict or smart | |
- * then the field is validated. | |
- * <li>{@code YEAR_OF_ERA} and {@code ERA} - If both are present, then they | |
- * are combined to form a {@code YEAR}. In lenient mode, the {@code YEAR_OF_ERA} | |
- * range is not validated, in smart and strict mode it is. The {@code ERA} is | |
- * validated for range in all three modes. If only the {@code YEAR_OF_ERA} is | |
- * present, and the mode is smart or lenient, then the last available era | |
- * is assumed. In strict mode, no era is assumed and the {@code YEAR_OF_ERA} is | |
- * left untouched. If only the {@code ERA} is present, then it is left untouched. | |
- * <li>{@code YEAR}, {@code MONTH_OF_YEAR} and {@code DAY_OF_MONTH} - | |
- * If all three are present, then they are combined to form a date. | |
- * In all three modes, the {@code YEAR} is validated. | |
- * If the mode is smart or strict, then the month and day are validated. | |
- * If the mode is lenient, then the date is combined in a manner equivalent to | |
- * creating a date on the first day of the first month in the requested year, | |
- * then adding the difference in months, then the difference in days. | |
- * If the mode is smart, and the day-of-month is greater than the maximum for | |
- * the year-month, then the day-of-month is adjusted to the last day-of-month. | |
- * If the mode is strict, then the three fields must form a valid date. | |
- * <li>{@code YEAR} and {@code DAY_OF_YEAR} - | |
- * If both are present, then they are combined to form a date. | |
- * In all three modes, the {@code YEAR} is validated. | |
- * If the mode is lenient, then the date is combined in a manner equivalent to | |
- * creating a date on the first day of the requested year, then adding | |
- * the difference in days. | |
- * If the mode is smart or strict, then the two fields must form a valid date. | |
- * <li>{@code YEAR}, {@code MONTH_OF_YEAR}, {@code ALIGNED_WEEK_OF_MONTH} and | |
- * {@code ALIGNED_DAY_OF_WEEK_IN_MONTH} - | |
- * If all four are present, then they are combined to form a date. | |
- * In all three modes, the {@code YEAR} is validated. | |
- * If the mode is lenient, then the date is combined in a manner equivalent to | |
- * creating a date on the first day of the first month in the requested year, then adding | |
- * the difference in months, then the difference in weeks, then in days. | |
- * If the mode is smart or strict, then the all four fields are validated to | |
- * their outer ranges. The date is then combined in a manner equivalent to | |
- * creating a date on the first day of the requested year and month, then adding | |
- * the amount in weeks and days to reach their values. If the mode is strict, | |
- * the date is additionally validated to check that the day and week adjustment | |
- * did not change the month. | |
- * <li>{@code YEAR}, {@code MONTH_OF_YEAR}, {@code ALIGNED_WEEK_OF_MONTH} and | |
- * {@code DAY_OF_WEEK} - If all four are present, then they are combined to | |
- * form a date. The approach is the same as described above for | |
- * years, months and weeks in {@code ALIGNED_DAY_OF_WEEK_IN_MONTH}. | |
- * The day-of-week is adjusted as the next or same matching day-of-week once | |
- * the years, months and weeks have been handled. | |
- * <li>{@code YEAR}, {@code ALIGNED_WEEK_OF_YEAR} and {@code ALIGNED_DAY_OF_WEEK_IN_YEAR} - | |
- * If all three are present, then they are combined to form a date. | |
- * In all three modes, the {@code YEAR} is validated. | |
- * If the mode is lenient, then the date is combined in a manner equivalent to | |
- * creating a date on the first day of the requested year, then adding | |
- * the difference in weeks, then in days. | |
- * If the mode is smart or strict, then the all three fields are validated to | |
- * their outer ranges. The date is then combined in a manner equivalent to | |
- * creating a date on the first day of the requested year, then adding | |
- * the amount in weeks and days to reach their values. If the mode is strict, | |
- * the date is additionally validated to check that the day and week adjustment | |
- * did not change the year. | |
- * <li>{@code YEAR}, {@code ALIGNED_WEEK_OF_YEAR} and {@code DAY_OF_WEEK} - | |
- * If all three are present, then they are combined to form a date. | |
- * The approach is the same as described above for years and weeks in | |
- * {@code ALIGNED_DAY_OF_WEEK_IN_YEAR}. The day-of-week is adjusted as the | |
- * next or same matching day-of-week once the years and weeks have been handled. | |
- * </ul> | |
- * <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, that first day-of-year has the value 1, and that the | |
- * first of the month and year always exists. | |
+ * The default implementation, which explains typical resolve behaviour, | |
+ * is provided in {@link AbstractChronology}. | |
* | |
* @param fieldValues the map of fields to values, which can be updated, not null | |
* @param resolverStyle the requested type of resolve, not null | |
@@ -963,233 +670,7 @@ | |
* @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 | |
- resolveProlepticMonth(fieldValues, resolverStyle); | |
- | |
- // invent era if necessary to resolve year-of-era | |
- ChronoLocalDate<?> resolved = resolveYearOfEra(fieldValues, resolverStyle); | |
- if (resolved != null) { | |
- return resolved; | |
- } | |
- | |
- // build date | |
- if (fieldValues.containsKey(YEAR)) { | |
- if (fieldValues.containsKey(MONTH_OF_YEAR)) { | |
- if (fieldValues.containsKey(DAY_OF_MONTH)) { | |
- return resolveYMD(fieldValues, resolverStyle); | |
- } | |
- if (fieldValues.containsKey(ALIGNED_WEEK_OF_MONTH)) { | |
- if (fieldValues.containsKey(ALIGNED_DAY_OF_WEEK_IN_MONTH)) { | |
- return resolveYMAA(fieldValues, resolverStyle); | |
- } | |
- if (fieldValues.containsKey(DAY_OF_WEEK)) { | |
- return resolveYMAD(fieldValues, resolverStyle); | |
- } | |
- } | |
- } | |
- if (fieldValues.containsKey(DAY_OF_YEAR)) { | |
- return resolveYD(fieldValues, resolverStyle); | |
- } | |
- if (fieldValues.containsKey(ALIGNED_WEEK_OF_YEAR)) { | |
- if (fieldValues.containsKey(ALIGNED_DAY_OF_WEEK_IN_YEAR)) { | |
- return resolveYAA(fieldValues, resolverStyle); | |
- } | |
- if (fieldValues.containsKey(DAY_OF_WEEK)) { | |
- return resolveYAD(fieldValues, resolverStyle); | |
- } | |
- } | |
- } | |
- return null; | |
- } | |
- | |
- void resolveProlepticMonth(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) { | |
- Long pMonth = fieldValues.remove(PROLEPTIC_MONTH); | |
- if (pMonth != null) { | |
- if (resolverStyle != ResolverStyle.LENIENT) { | |
- PROLEPTIC_MONTH.checkValidValue(pMonth); | |
- } | |
- // 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)); | |
- } | |
- } | |
- | |
- ChronoLocalDate<?> resolveYearOfEra(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) { | |
- Long yoeLong = fieldValues.remove(YEAR_OF_ERA); | |
- if (yoeLong != null) { | |
- Long eraLong = fieldValues.remove(ERA); | |
- int yoe; | |
- if (resolverStyle != ResolverStyle.LENIENT) { | |
- yoe = range(YEAR_OF_ERA).checkValidIntValue(yoeLong, YEAR_OF_ERA); | |
- } else { | |
- yoe = Math.toIntExact(yoeLong); | |
- } | |
- if (eraLong != null) { | |
- Era eraObj = eraOf(range(ERA).checkValidIntValue(eraLong, ERA)); | |
- 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 if (resolverStyle == ResolverStyle.STRICT) { | |
- // do not invent era if strict | |
- // reinstate the field removed earlier, no cross-check issues | |
- fieldValues.put(YEAR_OF_ERA, yoeLong); | |
- } 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)); | |
- } | |
- } | |
- } | |
- } else if (fieldValues.containsKey(ERA)) { | |
- range(ERA).checkValidValue(fieldValues.get(ERA), ERA); // always validated | |
- } | |
- return null; | |
- } | |
- | |
- ChronoLocalDate<?> resolveYMD(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) { | |
- int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR); | |
- if (resolverStyle == ResolverStyle.LENIENT) { | |
- long months = Math.subtractExact(fieldValues.remove(MONTH_OF_YEAR), 1); | |
- long days = Math.subtractExact(fieldValues.remove(DAY_OF_MONTH), 1); | |
- return date(y, 1, 1).plus(months, MONTHS).plus(days, DAYS); | |
- } | |
- int moy = range(MONTH_OF_YEAR).checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR), MONTH_OF_YEAR); | |
- ValueRange domRange = range(DAY_OF_MONTH); | |
- int dom = domRange.checkValidIntValue(fieldValues.remove(DAY_OF_MONTH), DAY_OF_MONTH); | |
- if (resolverStyle == ResolverStyle.SMART) { // previous valid | |
- try { | |
- return date(y, moy, dom); | |
- } catch (DateTimeException ex) { | |
- return date(y, moy, 1).with(TemporalAdjuster.lastDayOfMonth()); | |
- } | |
- } | |
- return date(y, moy, dom); | |
- } | |
- | |
- ChronoLocalDate<?> resolveYD(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) { | |
- int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR); | |
- if (resolverStyle == ResolverStyle.LENIENT) { | |
- long days = Math.subtractExact(fieldValues.remove(DAY_OF_YEAR), 1); | |
- return dateYearDay(y, 1).plus(days, DAYS); | |
- } | |
- int doy = range(DAY_OF_YEAR).checkValidIntValue(fieldValues.remove(DAY_OF_YEAR), DAY_OF_YEAR); | |
- return dateYearDay(y, doy); // smart is same as strict | |
- } | |
- | |
- ChronoLocalDate<?> resolveYMAA(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) { | |
- int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR); | |
- if (resolverStyle == ResolverStyle.LENIENT) { | |
- long months = Math.subtractExact(fieldValues.remove(MONTH_OF_YEAR), 1); | |
- long weeks = Math.subtractExact(fieldValues.remove(ALIGNED_WEEK_OF_MONTH), 1); | |
- long days = Math.subtractExact(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_MONTH), 1); | |
- return date(y, 1, 1).plus(months, MONTHS).plus(weeks, WEEKS).plus(days, DAYS); | |
- } | |
- 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<?> date = date(y, moy, 1).plus((aw - 1) * 7 + (ad - 1), DAYS); | |
- if (resolverStyle == ResolverStyle.STRICT && date.get(MONTH_OF_YEAR) != moy) { | |
- throw new DateTimeException("Strict mode rejected resolved date as it is in a different month"); | |
- } | |
- return date; | |
- } | |
- | |
- ChronoLocalDate<?> resolveYMAD(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) { | |
- int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR); | |
- if (resolverStyle == ResolverStyle.LENIENT) { | |
- long months = Math.subtractExact(fieldValues.remove(MONTH_OF_YEAR), 1); | |
- long weeks = Math.subtractExact(fieldValues.remove(ALIGNED_WEEK_OF_MONTH), 1); | |
- long dow = Math.subtractExact(fieldValues.remove(DAY_OF_WEEK), 1); | |
- return resolveAligned(date(y, 1, 1), months, weeks, dow); | |
- } | |
- 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<?> date = date(y, moy, 1).plus((aw - 1) * 7, DAYS).with(nextOrSame(DayOfWeek.of(dow))); | |
- if (resolverStyle == ResolverStyle.STRICT && date.get(MONTH_OF_YEAR) != moy) { | |
- throw new DateTimeException("Strict mode rejected resolved date as it is in a different month"); | |
- } | |
- return date; | |
- } | |
- | |
- ChronoLocalDate<?> resolveYAA(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) { | |
- int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR); | |
- if (resolverStyle == ResolverStyle.LENIENT) { | |
- long weeks = Math.subtractExact(fieldValues.remove(ALIGNED_WEEK_OF_YEAR), 1); | |
- long days = Math.subtractExact(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_YEAR), 1); | |
- return dateYearDay(y, 1).plus(weeks, WEEKS).plus(days, DAYS); | |
- } | |
- 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<?> date = dateYearDay(y, 1).plus((aw - 1) * 7 + (ad - 1), DAYS); | |
- if (resolverStyle == ResolverStyle.STRICT && date.get(YEAR) != y) { | |
- throw new DateTimeException("Strict mode rejected resolved date as it is in a different year"); | |
- } | |
- return date; | |
- } | |
- | |
- ChronoLocalDate<?> resolveYAD(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) { | |
- int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR); | |
- if (resolverStyle == ResolverStyle.LENIENT) { | |
- long weeks = Math.subtractExact(fieldValues.remove(ALIGNED_WEEK_OF_YEAR), 1); | |
- long dow = Math.subtractExact(fieldValues.remove(DAY_OF_WEEK), 1); | |
- return resolveAligned(dateYearDay(y, 1), 0, weeks, dow); | |
- } | |
- 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<?> date = dateYearDay(y, 1).plus((aw - 1) * 7, DAYS).with(nextOrSame(DayOfWeek.of(dow))); | |
- if (resolverStyle == ResolverStyle.STRICT && date.get(YEAR) != y) { | |
- throw new DateTimeException("Strict mode rejected resolved date as it is in a different year"); | |
- } | |
- return date; | |
- } | |
- | |
- ChronoLocalDate<?> resolveAligned(ChronoLocalDate<?> base, long months, long weeks, long dow) { | |
- ChronoLocalDate<?> date = base.plus(months, MONTHS).plus(weeks, WEEKS); | |
- if (dow > 7) { | |
- date = date.plus((dow - 1) / 7, WEEKS); | |
- dow = ((dow - 1) % 7) + 1; | |
- } else if (dow < 1) { | |
- date = date.plus(Math.subtractExact(dow, 7) / 7, WEEKS); | |
- dow = ((dow + 6) % 7) + 1; | |
- } | |
- return date.with(nextOrSame(DayOfWeek.of((int) dow))); | |
- } | |
- | |
- /** | |
- * 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); | |
- } | |
+ ChronoLocalDate<?> resolveDate(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle); | |
//----------------------------------------------------------------------- | |
/** | |
@@ -1198,94 +679,43 @@ | |
* The comparison order first by the chronology ID string, then by any | |
* additional information specific to the subclass. | |
* It is "consistent with equals", as defined by {@link Comparable}. | |
- * <p> | |
- * The default implementation compares the chronology ID. | |
- * Subclasses must compare any additional state that they store. | |
* | |
* @param other the other chronology to compare to, not null | |
* @return the comparator value, negative if less, positive if greater | |
*/ | |
@Override | |
- public int compareTo(Chronology other) { | |
- return getId().compareTo(other.getId()); | |
- } | |
+ int compareTo(Chronology other); | |
/** | |
* Checks if this chronology is equal to another chronology. | |
* <p> | |
* The comparison is based on the entire state of the object. | |
- * <p> | |
- * The default implementation checks the type and calls {@link #compareTo(Chronology)}. | |
* | |
* @param obj the object to check, null returns false | |
* @return true if this is equal to the other chronology | |
*/ | |
@Override | |
- public boolean equals(Object obj) { | |
- if (this == obj) { | |
- return true; | |
- } | |
- if (obj instanceof Chronology) { | |
- return compareTo((Chronology) obj) == 0; | |
- } | |
- return false; | |
- } | |
+ boolean equals(Object obj); | |
/** | |
* A hash code for this chronology. | |
* <p> | |
- * The default implementation is based on the ID and class. | |
- * Subclasses should add any additional state that they store. | |
+ * The hash code should be based on the entire state of the object. | |
* | |
* @return a suitable hash code | |
*/ | |
@Override | |
- public int hashCode() { | |
- return getClass().hashCode() ^ getId().hashCode(); | |
- } | |
+ int hashCode(); | |
//----------------------------------------------------------------------- | |
/** | |
- * Outputs this chronology as a {@code String}, using the ID. | |
+ * Outputs this chronology as a {@code String}. | |
+ * <p> | |
+ * The format should include the entire state of the object. | |
* | |
* @return a string representation of this chronology, not null | |
*/ | |
@Override | |
- public String toString() { | |
- return getId(); | |
- } | |
- | |
- //----------------------------------------------------------------------- | |
- /** | |
- * Writes the Chronology using a | |
- * <a href="../../../serialized-form.html#java.time.chrono.Ser">dedicated serialized form</a>. | |
- * <pre> | |
- * out.writeByte(1); // identifies this as a Chronology | |
- * out.writeUTF(getId()); | |
- * </pre> | |
- * | |
- * @return the instance of {@code Ser}, not null | |
- */ | |
- protected Object writeReplace() { | |
- return new Ser(Ser.CHRONO_TYPE, this); | |
- } | |
- | |
- /** | |
- * Defend against malicious streams. | |
- * @return never | |
- * @throws InvalidObjectException always | |
- */ | |
- private Object readResolve() throws ObjectStreamException { | |
- throw new InvalidObjectException("Deserialization via serialization delegate"); | |
- } | |
- | |
- void writeExternal(DataOutput out) throws IOException { | |
- out.writeUTF(getId()); | |
- } | |
- | |
- static Chronology readExternal(DataInput in) throws IOException { | |
- String id = in.readUTF(); | |
- return Chronology.of(id); | |
- } | |
+ String toString(); | |
} | |
diff --git a/src/share/classes/java/time/chrono/HijrahChronology.java b/src/share/classes/java/time/chrono/HijrahChronology.java | |
--- a/src/share/classes/java/time/chrono/HijrahChronology.java | |
+++ b/src/share/classes/java/time/chrono/HijrahChronology.java | |
@@ -212,7 +212,7 @@ | |
* | |
* @since 1.8 | |
*/ | |
-public final class HijrahChronology extends Chronology implements Serializable { | |
+public final class HijrahChronology extends AbstractChronology implements Serializable { | |
/** | |
* The Hijrah Calendar id. | |
@@ -306,8 +306,8 @@ | |
try { | |
INSTANCE = new HijrahChronology("Hijrah-umalqura"); | |
// Register it by its aliases | |
- Chronology.registerChrono(INSTANCE, "Hijrah"); | |
- Chronology.registerChrono(INSTANCE, "islamic"); | |
+ AbstractChronology.registerChrono(INSTANCE, "Hijrah"); | |
+ AbstractChronology.registerChrono(INSTANCE, "islamic"); | |
} catch (DateTimeException ex) { | |
// Absence of Hijrah calendar is fatal to initializing this class. | |
PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono"); | |
@@ -334,7 +334,7 @@ | |
try { | |
// Create and register the variant | |
HijrahChronology chrono = new HijrahChronology(id); | |
- Chronology.registerChrono(chrono); | |
+ AbstractChronology.registerChrono(chrono); | |
} catch (DateTimeException ex) { | |
// Log error and continue | |
PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono"); | |
diff --git a/src/share/classes/java/time/chrono/IsoChronology.java b/src/share/classes/java/time/chrono/IsoChronology.java | |
--- a/src/share/classes/java/time/chrono/IsoChronology.java | |
+++ b/src/share/classes/java/time/chrono/IsoChronology.java | |
@@ -117,7 +117,7 @@ | |
* | |
* @since 1.8 | |
*/ | |
-public final class IsoChronology extends Chronology implements Serializable { | |
+public final class IsoChronology extends AbstractChronology implements Serializable { | |
/** | |
* Singleton instance of the ISO chronology. | |
diff --git a/src/share/classes/java/time/chrono/JapaneseChronology.java b/src/share/classes/java/time/chrono/JapaneseChronology.java | |
--- a/src/share/classes/java/time/chrono/JapaneseChronology.java | |
+++ b/src/share/classes/java/time/chrono/JapaneseChronology.java | |
@@ -117,7 +117,7 @@ | |
* | |
* @since 1.8 | |
*/ | |
-public final class JapaneseChronology extends Chronology implements Serializable { | |
+public final class JapaneseChronology extends AbstractChronology implements Serializable { | |
static final LocalGregorianCalendar JCAL = | |
(LocalGregorianCalendar) CalendarSystem.forName("japanese"); | |
diff --git a/src/share/classes/java/time/chrono/MinguoChronology.java b/src/share/classes/java/time/chrono/MinguoChronology.java | |
--- a/src/share/classes/java/time/chrono/MinguoChronology.java | |
+++ b/src/share/classes/java/time/chrono/MinguoChronology.java | |
@@ -103,7 +103,7 @@ | |
* | |
* @since 1.8 | |
*/ | |
-public final class MinguoChronology extends Chronology implements Serializable { | |
+public final class MinguoChronology extends AbstractChronology implements Serializable { | |
/** | |
* Singleton instance for the Minguo chronology. | |
diff --git a/src/share/classes/java/time/chrono/Ser.java b/src/share/classes/java/time/chrono/Ser.java | |
--- a/src/share/classes/java/time/chrono/Ser.java | |
+++ b/src/share/classes/java/time/chrono/Ser.java | |
@@ -145,7 +145,7 @@ | |
out.writeByte(type); | |
switch (type) { | |
case CHRONO_TYPE: | |
- ((Chronology) object).writeExternal(out); | |
+ ((AbstractChronology) object).writeExternal(out); | |
break; | |
case CHRONO_LOCAL_DATE_TIME_TYPE: | |
((ChronoLocalDateTimeImpl<?>) object).writeExternal(out); | |
@@ -201,7 +201,7 @@ | |
private static Object readInternal(byte type, ObjectInput in) throws IOException, ClassNotFoundException { | |
switch (type) { | |
- case CHRONO_TYPE: return Chronology.readExternal(in); | |
+ case CHRONO_TYPE: return AbstractChronology.readExternal(in); | |
case CHRONO_LOCAL_DATE_TIME_TYPE: return ChronoLocalDateTimeImpl.readExternal(in); | |
case CHRONO_ZONE_DATE_TIME_TYPE: return ChronoZonedDateTimeImpl.readExternal(in); | |
case JAPANESE_DATE_TYPE: return JapaneseDate.readExternal(in); | |
diff --git a/src/share/classes/java/time/chrono/ThaiBuddhistChronology.java b/src/share/classes/java/time/chrono/ThaiBuddhistChronology.java | |
--- a/src/share/classes/java/time/chrono/ThaiBuddhistChronology.java | |
+++ b/src/share/classes/java/time/chrono/ThaiBuddhistChronology.java | |
@@ -104,7 +104,7 @@ | |
* | |
* @since 1.8 | |
*/ | |
-public final class ThaiBuddhistChronology extends Chronology implements Serializable { | |
+public final class ThaiBuddhistChronology extends AbstractChronology implements Serializable { | |
/** | |
* Singleton instance of the Buddhist chronology. | |
diff --git a/test/java/time/tck/java/time/chrono/CopticChronology.java b/test/java/time/tck/java/time/chrono/CopticChronology.java | |
--- a/test/java/time/tck/java/time/chrono/CopticChronology.java | |
+++ b/test/java/time/tck/java/time/chrono/CopticChronology.java | |
@@ -59,13 +59,11 @@ | |
import static java.time.temporal.ChronoField.EPOCH_DAY; | |
import java.io.Serializable; | |
- | |
+import java.time.chrono.AbstractChronology; | |
+import java.time.chrono.Era; | |
import java.time.temporal.ChronoField; | |
import java.time.temporal.TemporalAccessor; | |
import java.time.temporal.ValueRange; | |
-import java.time.chrono.Chronology; | |
-import java.time.chrono.Era; | |
- | |
import java.util.Arrays; | |
import java.util.List; | |
import java.util.Locale; | |
@@ -95,7 +93,7 @@ | |
* <h4>Implementation notes</h4> | |
* This class is immutable and thread-safe. | |
*/ | |
-public final class CopticChronology extends Chronology implements Serializable { | |
+public final class CopticChronology extends AbstractChronology implements Serializable { | |
/** | |
* Singleton instance of the Coptic chronology. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment