--- /dev/null 2013-01-18 16:17:08.886776012 -0800 +++ new/src/share/classes/java/time/temporal/Chrono.java 2013-01-22 16:58:05.000000000 -0800 @@ -0,0 +1,858 @@ +/* + * 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.temporal; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.time.Clock; +import java.time.DateTimeException; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.calendar.HijrahChrono; +import java.time.calendar.JapaneseChrono; +import java.time.calendar.MinguoChrono; +import java.time.calendar.ThaiBuddhistChrono; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.TextStyle; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A calendar system, used to organize and identify dates. + *

+ * 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. + * For example, the Japanese, Minguo, Thai Buddhist and others. + *

+ * Most other calendar systems also operate on the shared concepts of year, month and day, + * 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 availalbe + * for use by any {@code Chrono} implementation: + *

+ *   LocalDate isoDate = ...
+ *   ChronoLocalDate<ThaiBuddhistChrono> thaiDate = ...
+ *   int isoYear = isoDate.get(ChronoField.YEAR);
+ *   int thaiYear = thaiDate.get(ChronoField.YEAR);
+ * 
+ * As shown, although the date objects are in different calendar systems, represented by different + * {@code Chrono} instances, both can be queried using the same constant on {@code ChronoField}. + * For a full discussion of the implications of this, see {@link ChronoLocalDate}. + * In general, the advice is to use the known ISO-based {@code LocalDate}, rather than + * {@code ChronoLocalDate}. + *

+ * While a {@code Chrono} object typically uses {@code ChronoField} and is based on + * an era, year-of-era, month-of-year, day-of-month model of a date, this is not required. + * A {@code Chrono} instance may represent a totally different kind of calendar system, + * such as the Mayan. + *

+ * In practical terms, the {@code Chrono} instance also acts as a factory. + * The {@link #of(String)} method allows an instance to be looked up by identifier, + * while the {@link #ofLocale(Locale)} method allows lookup by locale. + *

+ * The {@code Chrono} instance provides a set of methods to create {@code ChronoLocalDate} instances. + * The date classes are used to manipulate specific dates. + *

+ * + *

Adding New Calendars

+ * The set of available chronologies can be extended by applications. + * Adding a new calendar system requires the writing of an implementation of + * {@code Chrono}, {@code ChronoLocalDate} and {@code Era}. + * The majority of the logic specific to the calendar system will be in + * {@code ChronoLocalDate}. The {@code Chrono} subclass acts as a factory. + *

+ * To permit the discovery of additional chronologies, the {@link java.util.ServiceLoader ServiceLoader} + * is used. A file must be added to the {@code META-INF/services} directory with the + * name 'java.time.temporal.Chrono' listing the implementation classes. + * See the ServiceLoader for more details on service loading. + * For lookup by id or calendarType, the system provided calendars are found + * first followed by application provided calendars. + *

+ * Each chronology must define a chronology ID that is unique within the system. + * If the chronology represents a calendar system defined by the + * Unicode Locale Data Markup Language (LDML) specification then that + * calendar type should also be specified. + * + *

Specification for implementors

+ * 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. + * + * @param the type of the implementing subclass + * @since 1.8 + */ +public abstract class Chrono> implements Comparable> { + + /** + * Map of available calendars by ID. + */ + private static final ConcurrentHashMap> CHRONOS_BY_ID = new ConcurrentHashMap<>(); + /** + * Map of available calendars by calendar type. + */ + private static final ConcurrentHashMap> CHRONOS_BY_TYPE = new ConcurrentHashMap<>(); + + /** + * Register a Chrono 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 Chrono. + * @param chrono the chronology to register; not null + */ + private static void registerChrono(Chrono chrono) { + Chrono prev = CHRONOS_BY_ID.putIfAbsent(chrono.getId(), chrono); + if (prev == null) { + String type = chrono.getCalendarType(); + if (type != null) { + CHRONOS_BY_TYPE.putIfAbsent(type, chrono); + } + } + } + + /** + * Initialization of the maps from id and type to Chrono. + * The ServiceLoader is used to find and register any implementations + * of {@link javax.time.temporal.Chrono} 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. + *

+ * The initialization is done only once using the registration + * of the ISOChrono as the test and the final step. + * Multiple threads may perform the initialization concurrently. + * Only the first registration of each Chrono 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 + @SuppressWarnings("rawtypes") + ServiceLoader loader = ServiceLoader.load(Chrono.class, null); + for (Chrono chrono : loader) { + registerChrono(chrono); + } + + // Register these calendars; the ServiceLoader configuration is not used + registerChrono(HijrahChrono.INSTANCE); + registerChrono(JapaneseChrono.INSTANCE); + registerChrono(MinguoChrono.INSTANCE); + registerChrono(ThaiBuddhistChrono.INSTANCE); + + // finally, register ISOChrono to mark initialization is complete + registerChrono(ISOChrono.INSTANCE); + return true; + } + return false; + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code Chrono} from a temporal object. + *

+ * A {@code TemporalAccessor} represents some form of date and time information. + * This factory converts the arbitrary temporal object to an instance of {@code Chrono}. + * If the specified temporal object does not have a chronology, {@link ISOChrono} is returned. + *

+ * The conversion will obtain the chronology using {@link Queries#chrono()}. + *

+ * This method matches the signature of the functional interface {@link TemporalQuery} + * allowing it to be used in queries via method reference, {@code Chrono::from}. + * + * @param temporal the temporal to convert, not null + * @return the chronology, not null + * @throws DateTimeException if unable to convert to an {@code Chrono} + */ + public static Chrono from(TemporalAccessor temporal) { + Objects.requireNonNull(temporal, "temporal"); + Chrono obj = temporal.query(Queries.chrono()); + return (obj != null ? obj : ISOChrono.INSTANCE); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code Chrono} from a locale. + *

+ * The locale can be used to identify a calendar. + * This uses {@link Locale#getUnicodeLocaleType(String)} to obtain the "ca" key + * to identify the calendar system. + *

+ * If the locale does not contain calendar system information, the standard + * ISO calendar system is used. + * + * @param locale the locale to use to obtain the calendar system, not null + * @return the calendar system associated with the locale, not null + * @throws DateTimeException if the locale-specified calendar cannot be found + */ + public static Chrono ofLocale(Locale locale) { + Objects.requireNonNull(locale, "locale"); + String type = locale.getUnicodeLocaleType("ca"); + if (type == null) { + return ISOChrono.INSTANCE; + } else if ("iso".equals(type) || "iso8601".equals(type)) { + return ISOChrono.INSTANCE; + } else { + Chrono chrono = CHRONOS_BY_TYPE.get(type); + if (chrono == null) { + throw new DateTimeException("Unknown calendar system: " + type); + } + return chrono; + } + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code Chrono} from a chronology ID or + * calendar system type. + *

+ * 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 chronology may be a system chronology or a chronology + * provided by the application via ServiceLoader configuration. + *

+ * Since some calendars can be customized, the ID or type typically refers + * to the default customization. For example, the Gregorian calendar can have multiple + * cutover dates from the Julian, but the lookup only provides the default cutover date. + * + * @param id the chronology ID or calendar system type, not null + * @return the chronology with the identifier requested, not null + * @throws DateTimeException if the chronology cannot be found + */ + public static Chrono of(String id) { + Objects.requireNonNull(id, "id"); + do { + Chrono chrono = of0(id); + if (chrono != null) { + return chrono; + } + // If not found, do the initialization (once) and repeat the lookup + } while (initCache()); + + // Look for a Chrono using ServiceLoader of the Thread's ContextClassLoader + // Application provided Chronologies must not be cached + @SuppressWarnings("rawtypes") + ServiceLoader loader = ServiceLoader.load(Chrono.class); + for (Chrono chrono : loader) { + if (id.equals(chrono.getId()) || id.equals(chrono.getCalendarType())) { + return chrono; + } + } + throw new DateTimeException("Unknown chronology: " + id); + } + + /** + * Obtains an instance of {@code Chrono} 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 Chrono of0(String id) { + Chrono chrono = CHRONOS_BY_ID.get(id); + if (chrono == null) { + chrono = CHRONOS_BY_TYPE.get(id); + } + return chrono; + } + + /** + * Returns the available chronologies. + *

+ * Each returned {@code Chrono} 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 + */ + public static Set> getAvailableChronologies() { + initCache(); // force initialization + HashSet> chronos = new HashSet<>(CHRONOS_BY_ID.values()); + + /// Add in Chronologies from the ServiceLoader configuration + @SuppressWarnings("rawtypes") + ServiceLoader loader = ServiceLoader.load(Chrono.class); + for (Chrono chrono : loader) { + chronos.add(chrono); + } + return chronos; + } + + //----------------------------------------------------------------------- + /** + * Obtains a local date-time from the a date and time. + *

+ * This combines a {@link ChronoLocalDate}, which provides the {@code Chrono}, + * with a {@link LocalTime} to produce a {@link ChronoLocalDateTime}. + *

+ * This method is intended for chronology implementations. + * It uses a standard implementation that is shared for all chronologies. + * + * @param the chronology of the date + * @param date the date, not null + * @param time the time, not null + * @return the local date-time combining the input date and time, not null + */ + public static > ChronoLocalDateTime dateTime(ChronoLocalDate date, LocalTime time) { + return ChronoLocalDateTimeImpl.of(date, time); + } + + //----------------------------------------------------------------------- + /** + * Creates an instance. + */ + protected Chrono() { + } + + //----------------------------------------------------------------------- + /** + * Casts the {@code Temporal} to {@code ChronoLocalDate} with the same chronology. + * + * @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 Chrono + */ + ChronoLocalDate ensureChronoLocalDate(Temporal temporal) { + @SuppressWarnings("unchecked") + ChronoLocalDate other = (ChronoLocalDate) temporal; + if (this.equals(other.getChrono()) == false) { + throw new ClassCastException("Chrono mismatch, expected: " + getId() + ", actual: " + other.getChrono().getId()); + } + return other; + } + + /** + * Casts the {@code Temporal} to {@code ChronoLocalDateTime} with the same chronology. + * + * @param temporal a date-time to cast, not null + * @return the date-time checked and cast to {@code ChronoLocalDateTime}, not null + * @throws ClassCastException if the date-time cannot be cast to ChronoLocalDateTimeImpl + * or the chronology is not equal this Chrono + */ + ChronoLocalDateTimeImpl ensureChronoLocalDateTime(Temporal temporal) { + @SuppressWarnings("unchecked") + ChronoLocalDateTimeImpl other = (ChronoLocalDateTimeImpl) temporal; + if (this.equals(other.getDate().getChrono()) == false) { + throw new ClassCastException("Chrono mismatch, required: " + getId() + + ", supplied: " + other.getDate().getChrono().getId()); + } + return other; + } + + /** + * Casts the {@code Temporal} to {@code ChronoZonedDateTimeImpl} with the same chronology. + * + * @param temporal a date-time to cast, not null + * @return the date-time checked and cast to {@code ChronoZonedDateTimeImpl}, not null + * @throws ClassCastException if the date-time cannot be cast to ChronoZonedDateTimeImpl + * or the chronology is not equal this Chrono + */ + ChronoZonedDateTimeImpl ensureChronoZonedDateTime(Temporal temporal) { + @SuppressWarnings("unchecked") + ChronoZonedDateTimeImpl other = (ChronoZonedDateTimeImpl) temporal; + if (this.equals(other.getDate().getChrono()) == false) { + throw new ClassCastException("Chrono mismatch, required: " + getId() + + ", supplied: " + other.getDate().getChrono().getId()); + } + return other; + } + + //----------------------------------------------------------------------- + /** + * Gets the ID of the chronology. + *

+ * The ID uniquely identifies the {@code Chrono}. + * It can be used to lookup the {@code Chrono} using {@link #of(String)}. + * + * @return the chronology ID, not null + * @see #getCalendarType() + */ + public abstract String getId(); + + /** + * Gets the calendar type of the underlying calendar system. + *

+ * The calendar type is an identifier defined by the + * Unicode Locale Data Markup Language (LDML) specification. + * It can be used to lookup the {@code Chrono} using {@link #of(String)}. + * It can also be used as part of a locale, accessible via + * {@link Locale#getUnicodeLocaleType(String)} with the key 'ca'. + * + * @return the calendar system type, null if the calendar is not defined by LDML + * @see #getId() + */ + public abstract String getCalendarType(); + + //----------------------------------------------------------------------- + /** + * Obtains a local date in this chronology from the era, year-of-era, + * month-of-year and day-of-month fields. + * + * @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 + * @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 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, + * month-of-year and day-of-month fields. + * + * @param prolepticYear the chronology proleptic-year + * @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); + + /** + * 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 + */ + 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 + * day-of-year fields. + * + * @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); + + /** + * Obtains a local date in this chronology from another temporal object. + *

+ * This creates a date in this chronology based on the specified {@code TemporalAccessor}. + *

+ * The standard mechanism for conversion between date types is the + * {@link ChronoField#EPOCH_DAY local epoch-day} field. + * + * @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 + */ + public abstract ChronoLocalDate date(TemporalAccessor temporal); + + //----------------------------------------------------------------------- + /** + * Obtains the current local date in this chronology from the system clock in the default time-zone. + *

+ * This will query the {@link Clock#systemDefaultZone() system clock} in the default + * time-zone to obtain the current date. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + *

+ * 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() { + return dateNow(Clock.systemDefaultZone()); + } + + /** + * Obtains the current local date in this chronology from the system clock in the specified time-zone. + *

+ * This will query the {@link Clock#system(ZoneId) system clock} to obtain the current date. + * Specifying the time-zone avoids dependence on the default time-zone. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + * + * @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) { + return dateNow(Clock.system(zone)); + } + + /** + * Obtains the current local date in this chronology from the specified clock. + *

+ * This will query the specified clock to obtain the current date - today. + * Using this method allows the use of an alternate clock for testing. + * The alternate clock may be introduced using {@link Clock dependency injection}. + * + * @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) { + Objects.requireNonNull(clock, "clock"); + return date(LocalDate.now(clock)); + } + + //----------------------------------------------------------------------- + /** + * Obtains a local date-time in this chronology from another temporal object. + *

+ * This creates a date-time in this chronology based on the specified {@code TemporalAccessor}. + *

+ * The date of the date-time should be equivalent to that obtained by calling + * {@link #date(TemporalAccessor)}. + * The standard mechanism for conversion between time types is the + * {@link ChronoField#NANO_OF_DAY nano-of-day} field. + * + * @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 + */ + public ChronoLocalDateTime localDateTime(TemporalAccessor temporal) { + try { + return date(temporal).atTime(LocalTime.from(temporal)); + } catch (DateTimeException ex) { + throw new DateTimeException("Unable to obtain ChronoLocalDateTime from TemporalAccessor: " + temporal.getClass(), ex); + } + } + + /** + * Obtains a zoned date-time in this chronology from another temporal object. + *

+ * This creates a date-time in this chronology based on the specified {@code TemporalAccessor}. + *

+ * This should obtain a {@code ZoneId} using {@link ZoneId#from(TemporalAccessor)}. + * The date-time should be obtained by obtaining an {@code Instant}. + * If that fails, the local date-time should be used. + * + * @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 + */ + public ChronoZonedDateTime zonedDateTime(TemporalAccessor temporal) { + try { + ZoneId zone = ZoneId.from(temporal); + try { + Instant instant = Instant.from(temporal); + return zonedDateTime(instant, zone); + + } catch (DateTimeException ex1) { + 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); + } + } + + /** + * Obtains a zoned date-time in this chronology from an {@code Instant}. + *

+ * This creates 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 + */ + public ChronoZonedDateTime zonedDateTime(Instant instant, ZoneId zone) { + return ChronoZonedDateTimeImpl.ofInstant(this, instant, zone); + } + + //----------------------------------------------------------------------- + /** + * Checks if the specified year is a leap year. + *

+ * A leap-year is a year of a longer length than normal. + * The exact meaning is determined by the chronology according to the following constraints. + *

    + *
  • a leap-year must imply a year-length longer than a non leap-year. + *
  • a chronology that does not support the concept of a year must return false. + *

+ * + * @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); + + /** + * Calculates the proleptic-year given the era and year-of-era. + *

+ * This combines the era and year-of-era into the single proleptic-year field. + * + * @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 + */ + public abstract int prolepticYear(Era era, int yearOfEra); + + /** + * Creates the chronology era object from the numeric value. + *

+ * The era is, conceptually, the largest division of the time-line. + * Most calendar systems have a single epoch dividing the time-line into two eras. + * However, some have multiple eras, such as one for the reign of each leader. + * The exact meaning is determined by the chronology according to the following constraints. + *

+ * The era in use at 1970-01-01 must have the value 1. + * Later eras must have sequentially higher values. + * Earlier eras must have sequentially lower values. + * Each chronology must refer to an enum or similar singleton to provide the era values. + *

+ * This method returns the singleton era of the correct type for the specified era value. + * + * @param eraValue the era value + * @return the calendar system era, not null + * @throws DateTimeException if unable to create the era + */ + public abstract Era eraOf(int eraValue); + + /** + * Gets the list of eras for the chronology. + *

+ * Most calendar systems have an era, within which the year has meaning. + * If the calendar system does not support the concept of eras, an empty + * list must be returned. + * + * @return the list of eras for the chronology, may be immutable, not null + */ + public abstract List> eras(); + + //----------------------------------------------------------------------- + /** + * Gets the range of valid values for the specified field. + *

+ * All fields can be expressed as a {@code long} integer. + * This method returns an object that describes the valid range for that value. + *

+ * Note that the result only describes the minimum and maximum valid values + * and it is important not to read too much into them. For example, there + * could be values within the range that are invalid for the field. + *

+ * This method will return a result whether or not the chronology supports the field. + * + * @param field the field to get the range for, not null + * @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); + + //----------------------------------------------------------------------- + /** + * Gets the textual representation of this chronology. + *

+ * This returns the textual name used to identify the chronology. + * The parameters control the style of the returned text and the locale. + * + * @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 getText(TextStyle style, Locale locale) { + return new DateTimeFormatterBuilder().appendChronoText(style).toFormatter(locale).print(new TemporalAccessor() { + @Override + public boolean isSupported(TemporalField field) { + return false; + } + @Override + public long getLong(TemporalField field) { + throw new DateTimeException("Unsupported field: " + field); + } + @SuppressWarnings("unchecked") + @Override + public R query(TemporalQuery query) { + if (query == Queries.chrono()) { + return (R) Chrono.this; + } + return TemporalAccessor.super.query(query); + } + }); + } + + //----------------------------------------------------------------------- + /** + * Compares this chronology to another chronology. + *

+ * 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}. + *

+ * 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(Chrono other) { + return getId().compareTo(other.getId()); + } + + /** + * Checks if this chronology is equal to another chronology. + *

+ * The comparison is based on the entire state of the object. + *

+ * The default implementation checks the type and calls {@link #compareTo(Chrono)}. + * + * @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 Chrono) { + return compareTo((Chrono) obj) == 0; + } + return false; + } + + /** + * A hash code for this chronology. + *

+ * The default implementation is based on the 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 ID. + * + * @return a string representation of this chronology, not null + */ + @Override + public String toString() { + return getId(); + } + + //----------------------------------------------------------------------- + /** + * Writes the object using a + * dedicated serialized form. + *

+     *  out.writeByte(7);  // identifies this as a Chrono
+     * out.writeUTF(chronoId);
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ + private 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 Chrono readExternal(DataInput in) throws IOException { + String id = in.readUTF(); + return Chrono.of(id); + } + +}