/* * Copyright (c) 2012, 2015, 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. */ /* * 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.EPOCH_DAY; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.Serializable; import java.security.AccessController; import java.security.PrivilegedActionException; import java.time.Clock; import java.time.DateTimeException; import java.time.Instant; import java.time.LocalDate; import java.time.ZoneId; import java.time.format.ResolverStyle; import java.time.temporal.ChronoField; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalField; import java.time.temporal.ValueRange; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Properties; import sun.util.logging.PlatformLogger; /** * The Hijrah calendar is a lunar calendar supporting Islamic calendars. *

* The HijrahChronology follows the rules of the Hijrah calendar system. The Hijrah * calendar has several variants based on differences in when the new moon is * determined to have occurred and where the observation is made. * In some variants the length of each month is * computed algorithmically from the astronomical data for the moon and earth and * in others the length of the month is determined by an authorized sighting * of the new moon. For the algorithmically based calendars the calendar * can project into the future. * For sighting based calendars only historical data from past * sightings is available. *

* The length of each month is 29 or 30 days. * Ordinary years have 354 days; leap years have 355 days. * *

* CLDR and LDML identify variants: * * * * * * * * * * * * * * * * * *
Chronology IDCalendar TypeLocale extension, see {@link java.util.Locale}Description
Hijrah-umalquraislamic-umalquraca-islamic-umalquraIslamic - Umm Al-Qura calendar of Saudi Arabia
*

Additional variants may be available through {@link Chronology#getAvailableChronologies()}. * *

Example

*

* Selecting the chronology from the locale uses {@link Chronology#ofLocale} * to find the Chronology based on Locale supported BCP 47 extension mechanism * to request a specific calendar ("ca"). For example, *

*
 *      Locale locale = Locale.forLanguageTag("en-US-u-ca-islamic-umalqura");
 *      Chronology chrono = Chronology.ofLocale(locale);
 * 
* * @implSpec * This class is immutable and thread-safe. * * @implNote * Each Hijrah variant is configured individually. Each variant is defined by a * property resource that defines the {@code ID}, the {@code calendar type}, * the start of the calendar, the alignment with the * ISO calendar, and the length of each month for a range of years. * The variants are identified in the {@code calendars.properties} file. * The new properties are prefixed with {@code "calendars.hijrah."}: * * * * * * * * * * * * * * * * * * * * *
Property NameProperty valueDescription
calendars.hijrah.{ID}The property resource defining the {@code {ID}} variantThe property resource is located with the {@code calendars.properties} file
calendars.hijrah.{ID}.typeThe calendar typeLDML defines the calendar type names
*

* The Hijrah property resource is a set of properties that describe the calendar. * The syntax is defined by {@code java.util.Properties#load(Reader)}. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Property Name Property value Description
idChronology Id, for example, "Hijrah-umalqura"The Id of the calendar in common usage
typeCalendar type, for example, "islamic-umalqura"LDML defines the calendar types
versionVersion, for example: "1.8.0_1"The version of the Hijrah variant data
iso-startISO start date, formatted as {@code yyyy-MM-dd}, for example: "1900-04-30"The ISO date of the first day of the minimum Hijrah year.
yyyy - a numeric 4 digit year, for example "1434"The value is a sequence of 12 month lengths, * for example: "29 30 29 30 29 30 30 30 29 30 29 29"The lengths of the 12 months of the year separated by whitespace. * A numeric year property must be present for every year without any gaps. * The month lengths must be between 29-32 inclusive. *
* * @since 1.8 */ public final class HijrahChronology extends AbstractChronology implements Serializable { /** * The Hijrah Calendar id. */ private final transient String typeId; /** * The Hijrah calendarType. */ private final transient String calendarType; /** * Serialization version. */ private static final long serialVersionUID = 3127340209035924785L; /** * Singleton instance of the Islamic Umm Al-Qura calendar of Saudi Arabia. * Other Hijrah chronology variants may be available from * {@link Chronology#getAvailableChronologies}. */ public static final HijrahChronology INSTANCE; /** * Flag to indicate the initialization of configuration data is complete. * @see #checkCalendarInit() */ private transient volatile boolean initComplete; /** * Array of epoch days indexed by Hijrah Epoch month. * Computed by {@link #loadCalendarData}. */ private transient int[] hijrahEpochMonthStartDays; /** * The minimum epoch day of this Hijrah calendar. * Computed by {@link #loadCalendarData}. */ private transient int minEpochDay; /** * The maximum epoch day for which calendar data is available. * Computed by {@link #loadCalendarData}. */ private transient int maxEpochDay; /** * The minimum epoch month. * Computed by {@link #loadCalendarData}. */ private transient int hijrahStartEpochMonth; /** * The minimum length of a month. * Computed by {@link #createEpochMonths}. */ private transient int minMonthLength; /** * The maximum length of a month. * Computed by {@link #createEpochMonths}. */ private transient int maxMonthLength; /** * The minimum length of a year in days. * Computed by {@link #createEpochMonths}. */ private transient int minYearLength; /** * The maximum length of a year in days. * Computed by {@link #createEpochMonths}. */ private transient int maxYearLength; /** * A reference to the properties stored in * ${java.home}/lib/calendars.properties */ private final transient static Properties calendarProperties; /** * Prefix of property names for Hijrah calendar variants. */ private static final String PROP_PREFIX = "calendar.hijrah."; /** * Suffix of property names containing the calendar type of a variant. */ private static final String PROP_TYPE_SUFFIX = ".type"; /** * Static initialization of the predefined calendars found in the * lib/calendars.properties file. */ static { try { calendarProperties = sun.util.calendar.BaseCalendar.getCalendarProperties(); } catch (IOException ioe) { throw new InternalError("Can't initialize lib/calendars.properties", ioe); } try { INSTANCE = new HijrahChronology("Hijrah-umalqura"); // Register it by its aliases 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"); logger.severe("Unable to initialize Hijrah calendar: Hijrah-umalqura", ex); throw new RuntimeException("Unable to initialize Hijrah-umalqura calendar", ex.getCause()); } registerVariants(); } /** * For each Hijrah variant listed, create the HijrahChronology and register it. * Exceptions during initialization are logged but otherwise ignored. */ private static void registerVariants() { for (String name : calendarProperties.stringPropertyNames()) { if (name.startsWith(PROP_PREFIX)) { String id = name.substring(PROP_PREFIX.length()); if (id.indexOf('.') >= 0) { continue; // no name or not a simple name of a calendar } if (id.equals(INSTANCE.getId())) { continue; // do not duplicate the default } try { // Create and register the variant HijrahChronology chrono = new HijrahChronology(id); AbstractChronology.registerChrono(chrono); } catch (DateTimeException ex) { // Log error and continue PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono"); logger.severe("Unable to initialize Hijrah calendar: " + id, ex); } } } } /** * Create a HijrahChronology for the named variant. * The resource and calendar type are retrieved from properties * in the {@code calendars.properties}. * The property names are {@code "calendar.hijrah." + id} * and {@code "calendar.hijrah." + id + ".type"} * @param id the id of the calendar * @throws DateTimeException if the calendar type is missing from the properties file. * @throws IllegalArgumentException if the id is empty */ private HijrahChronology(String id) throws DateTimeException { if (id.isEmpty()) { throw new IllegalArgumentException("calendar id is empty"); } String propName = PROP_PREFIX + id + PROP_TYPE_SUFFIX; String calType = calendarProperties.getProperty(propName); if (calType == null || calType.isEmpty()) { throw new DateTimeException("calendarType is missing or empty for: " + propName); } this.typeId = id; this.calendarType = calType; } /** * Check and ensure that the calendar data has been initialized. * The initialization check is performed at the boundary between * public and package methods. If a public calls another public method * a check is not necessary in the caller. * The constructors of HijrahDate call {@link #getEpochDay} or * {@link #getHijrahDateInfo} so every call from HijrahDate to a * HijrahChronology via package private methods has been checked. * * @throws DateTimeException if the calendar data configuration is * malformed or IOExceptions occur loading the data */ private void checkCalendarInit() { // Keep this short so it can be inlined for performance if (initComplete == false) { loadCalendarData(); initComplete = true; } } //----------------------------------------------------------------------- /** * Gets the ID of the chronology. *

* The ID uniquely identifies the {@code Chronology}. It can be used to * lookup the {@code Chronology} using {@link Chronology#of(String)}. * * @return the chronology ID, non-null * @see #getCalendarType() */ @Override public String getId() { return typeId; } /** * Gets the calendar type of the Islamic calendar. *

* The calendar type is an identifier defined by the * Unicode Locale Data Markup Language (LDML) specification. * It can be used to lookup the {@code Chronology} using {@link Chronology#of(String)}. * * @return the calendar system type; non-null if the calendar has * a standard type, otherwise null * @see #getId() */ @Override public String getCalendarType() { return calendarType; } //----------------------------------------------------------------------- /** * Obtains a local date in Hijrah calendar system from the * era, year-of-era, month-of-year and day-of-month fields. * * @param era the Hijrah era, not null * @param yearOfEra the year-of-era * @param month the month-of-year * @param dayOfMonth the day-of-month * @return the Hijrah local date, not null * @throws DateTimeException if unable to create the date * @throws ClassCastException if the {@code era} is not a {@code HijrahEra} */ @Override public HijrahDate date(Era era, int yearOfEra, int month, int dayOfMonth) { return date(prolepticYear(era, yearOfEra), month, dayOfMonth); } /** * Obtains a local date in Hijrah calendar system from the * proleptic-year, month-of-year and day-of-month fields. * * @param prolepticYear the proleptic-year * @param month the month-of-year * @param dayOfMonth the day-of-month * @return the Hijrah local date, not null * @throws DateTimeException if unable to create the date */ @Override public HijrahDate date(int prolepticYear, int month, int dayOfMonth) { return HijrahDate.of(this, prolepticYear, month, dayOfMonth); } /** * Obtains a local date in Hijrah calendar system from the * era, year-of-era and day-of-year fields. * * @param era the Hijrah era, not null * @param yearOfEra the year-of-era * @param dayOfYear the day-of-year * @return the Hijrah local date, not null * @throws DateTimeException if unable to create the date * @throws ClassCastException if the {@code era} is not a {@code HijrahEra} */ @Override public HijrahDate dateYearDay(Era era, int yearOfEra, int dayOfYear) { return dateYearDay(prolepticYear(era, yearOfEra), dayOfYear); } /** * Obtains a local date in Hijrah calendar system from the * proleptic-year and day-of-year fields. * * @param prolepticYear the proleptic-year * @param dayOfYear the day-of-year * @return the Hijrah local date, not null * @throws DateTimeException if the value of the year is out of range, * or if the day-of-year is invalid for the year */ @Override public HijrahDate dateYearDay(int prolepticYear, int dayOfYear) { HijrahDate date = HijrahDate.of(this, prolepticYear, 1, 1); if (dayOfYear > date.lengthOfYear()) { throw new DateTimeException("Invalid dayOfYear: " + dayOfYear); } return date.plusDays(dayOfYear - 1); } /** * Obtains a local date in the Hijrah calendar system from the epoch-day. * * @param epochDay the epoch day * @return the Hijrah local date, not null * @throws DateTimeException if unable to create the date */ @Override // override with covariant return type public HijrahDate dateEpochDay(long epochDay) { return HijrahDate.ofEpochDay(this, epochDay); } @Override public HijrahDate dateNow() { return dateNow(Clock.systemDefaultZone()); } @Override public HijrahDate dateNow(ZoneId zone) { return dateNow(Clock.system(zone)); } @Override public HijrahDate dateNow(Clock clock) { return date(LocalDate.now(clock)); } @Override public HijrahDate date(TemporalAccessor temporal) { if (temporal instanceof HijrahDate) { return (HijrahDate) temporal; } return HijrahDate.ofEpochDay(this, temporal.getLong(EPOCH_DAY)); } @Override @SuppressWarnings("unchecked") public ChronoLocalDateTime localDateTime(TemporalAccessor temporal) { return (ChronoLocalDateTime) super.localDateTime(temporal); } @Override @SuppressWarnings("unchecked") public ChronoZonedDateTime zonedDateTime(TemporalAccessor temporal) { return (ChronoZonedDateTime) super.zonedDateTime(temporal); } @Override @SuppressWarnings("unchecked") public ChronoZonedDateTime zonedDateTime(Instant instant, ZoneId zone) { return (ChronoZonedDateTime) super.zonedDateTime(instant, zone); } //----------------------------------------------------------------------- @Override public boolean isLeapYear(long prolepticYear) { checkCalendarInit(); if (prolepticYear < getMinimumYear() || prolepticYear > getMaximumYear()) { return false; } int len = getYearLength((int) prolepticYear); return (len > 354); } @Override public int prolepticYear(Era era, int yearOfEra) { if (era instanceof HijrahEra == false) { throw new ClassCastException("Era must be HijrahEra"); } return yearOfEra; } @Override public HijrahEra eraOf(int eraValue) { switch (eraValue) { case 1: return HijrahEra.AH; default: throw new DateTimeException("invalid Hijrah era"); } } @Override public List eras() { return Arrays.asList(HijrahEra.values()); } //----------------------------------------------------------------------- @Override public ValueRange range(ChronoField field) { checkCalendarInit(); if (field instanceof ChronoField) { ChronoField f = field; switch (f) { case DAY_OF_MONTH: return ValueRange.of(1, 1, getMinimumMonthLength(), getMaximumMonthLength()); case DAY_OF_YEAR: return ValueRange.of(1, getMaximumDayOfYear()); case ALIGNED_WEEK_OF_MONTH: return ValueRange.of(1, 5); case YEAR: case YEAR_OF_ERA: return ValueRange.of(getMinimumYear(), getMaximumYear()); case ERA: return ValueRange.of(1, 1); default: return field.range(); } } return field.range(); } //----------------------------------------------------------------------- @Override // override for return type public HijrahDate resolveDate(Map fieldValues, ResolverStyle resolverStyle) { return (HijrahDate) super.resolveDate(fieldValues, resolverStyle); } //----------------------------------------------------------------------- /** * Check the validity of a year. * * @param prolepticYear the year to check */ int checkValidYear(long prolepticYear) { if (prolepticYear < getMinimumYear() || prolepticYear > getMaximumYear()) { throw new DateTimeException("Invalid Hijrah year: " + prolepticYear); } return (int) prolepticYear; } void checkValidDayOfYear(int dayOfYear) { if (dayOfYear < 1 || dayOfYear > getMaximumDayOfYear()) { throw new DateTimeException("Invalid Hijrah day of year: " + dayOfYear); } } void checkValidMonth(int month) { if (month < 1 || month > 12) { throw new DateTimeException("Invalid Hijrah month: " + month); } } //----------------------------------------------------------------------- /** * Returns an array containing the Hijrah year, month and day * computed from the epoch day. * * @param epochDay the EpochDay * @return int[0] = YEAR, int[1] = MONTH, int[2] = DATE */ int[] getHijrahDateInfo(int epochDay) { checkCalendarInit(); // ensure that the chronology is initialized if (epochDay < minEpochDay || epochDay >= maxEpochDay) { throw new DateTimeException("Hijrah date out of range"); } int epochMonth = epochDayToEpochMonth(epochDay); int year = epochMonthToYear(epochMonth); int month = epochMonthToMonth(epochMonth); int day1 = epochMonthToEpochDay(epochMonth); int date = epochDay - day1; // epochDay - dayOfEpoch(year, month); int dateInfo[] = new int[3]; dateInfo[0] = year; dateInfo[1] = month + 1; // change to 1-based. dateInfo[2] = date + 1; // change to 1-based. return dateInfo; } /** * Return the epoch day computed from Hijrah year, month, and day. * * @param prolepticYear the year to represent, 0-origin * @param monthOfYear the month-of-year to represent, 1-origin * @param dayOfMonth the day-of-month to represent, 1-origin * @return the epoch day */ long getEpochDay(int prolepticYear, int monthOfYear, int dayOfMonth) { checkCalendarInit(); // ensure that the chronology is initialized checkValidMonth(monthOfYear); int epochMonth = yearToEpochMonth(prolepticYear) + (monthOfYear - 1); if (epochMonth < 0 || epochMonth >= hijrahEpochMonthStartDays.length) { throw new DateTimeException("Invalid Hijrah date, year: " + prolepticYear + ", month: " + monthOfYear); } if (dayOfMonth < 1 || dayOfMonth > getMonthLength(prolepticYear, monthOfYear)) { throw new DateTimeException("Invalid Hijrah day of month: " + dayOfMonth); } return epochMonthToEpochDay(epochMonth) + (dayOfMonth - 1); } /** * Returns day of year for the year and month. * * @param prolepticYear a proleptic year * @param month a month, 1-origin * @return the day of year, 1-origin */ int getDayOfYear(int prolepticYear, int month) { return yearMonthToDayOfYear(prolepticYear, (month - 1)); } /** * Returns month length for the year and month. * * @param prolepticYear a proleptic year * @param monthOfYear a month, 1-origin. * @return the length of the month */ int getMonthLength(int prolepticYear, int monthOfYear) { int epochMonth = yearToEpochMonth(prolepticYear) + (monthOfYear - 1); if (epochMonth < 0 || epochMonth >= hijrahEpochMonthStartDays.length) { throw new DateTimeException("Invalid Hijrah date, year: " + prolepticYear + ", month: " + monthOfYear); } return epochMonthLength(epochMonth); } /** * Returns year length. * Note: The 12th month must exist in the data. * * @param prolepticYear a proleptic year * @return year length in days */ int getYearLength(int prolepticYear) { return yearMonthToDayOfYear(prolepticYear, 12); } /** * Return the minimum supported Hijrah year. * * @return the minimum */ int getMinimumYear() { return epochMonthToYear(0); } /** * Return the maximum supported Hijrah ear. * * @return the minimum */ int getMaximumYear() { return epochMonthToYear(hijrahEpochMonthStartDays.length - 1) - 1; } /** * Returns maximum day-of-month. * * @return maximum day-of-month */ int getMaximumMonthLength() { return maxMonthLength; } /** * Returns smallest maximum day-of-month. * * @return smallest maximum day-of-month */ int getMinimumMonthLength() { return minMonthLength; } /** * Returns maximum day-of-year. * * @return maximum day-of-year */ int getMaximumDayOfYear() { return maxYearLength; } /** * Returns smallest maximum day-of-year. * * @return smallest maximum day-of-year */ int getSmallestMaximumDayOfYear() { return minYearLength; } /** * Returns the epochMonth found by locating the epochDay in the table. The * epochMonth is the index in the table * * @param epochDay * @return The index of the element of the start of the month containing the * epochDay. */ private int epochDayToEpochMonth(int epochDay) { // binary search int ndx = Arrays.binarySearch(hijrahEpochMonthStartDays, epochDay); if (ndx < 0) { ndx = -ndx - 2; } return ndx; } /** * Returns the year computed from the epochMonth * * @param epochMonth the epochMonth * @return the Hijrah Year */ private int epochMonthToYear(int epochMonth) { return (epochMonth + hijrahStartEpochMonth) / 12; } /** * Returns the epochMonth for the Hijrah Year. * * @param year the HijrahYear * @return the epochMonth for the beginning of the year. */ private int yearToEpochMonth(int year) { return (year * 12) - hijrahStartEpochMonth; } /** * Returns the Hijrah month from the epochMonth. * * @param epochMonth the epochMonth * @return the month of the Hijrah Year */ private int epochMonthToMonth(int epochMonth) { return (epochMonth + hijrahStartEpochMonth) % 12; } /** * Returns the epochDay for the start of the epochMonth. * * @param epochMonth the epochMonth * @return the epochDay for the start of the epochMonth. */ private int epochMonthToEpochDay(int epochMonth) { return hijrahEpochMonthStartDays[epochMonth]; } /** * Returns the day of year for the requested HijrahYear and month. * * @param prolepticYear the Hijrah year * @param month the Hijrah month * @return the day of year for the start of the month of the year */ private int yearMonthToDayOfYear(int prolepticYear, int month) { int epochMonthFirst = yearToEpochMonth(prolepticYear); return epochMonthToEpochDay(epochMonthFirst + month) - epochMonthToEpochDay(epochMonthFirst); } /** * Returns the length of the epochMonth. It is computed from the start of * the following month minus the start of the requested month. * * @param epochMonth the epochMonth; assumed to be within range * @return the length in days of the epochMonth */ private int epochMonthLength(int epochMonth) { // The very last entry in the epochMonth table is not the start of a month return hijrahEpochMonthStartDays[epochMonth + 1] - hijrahEpochMonthStartDays[epochMonth]; } //----------------------------------------------------------------------- private static final String KEY_ID = "id"; private static final String KEY_TYPE = "type"; private static final String KEY_VERSION = "version"; private static final String KEY_ISO_START = "iso-start"; /** * Return the configuration properties from the resource. *

* The default location of the variant configuration resource is: *

     *   "$java.home/lib/" + resource-name
     * 
* * @param resource the name of the calendar property resource * @return a Properties containing the properties read from the resource. * @throws Exception if access to the property resource fails */ private static Properties readConfigProperties(final String resource) throws Exception { // Android-changed: Load system resources. Properties props = new Properties(); try (InputStream is = ClassLoader.getSystemResourceAsStream(resource)) { props.load(is); } return props; } /** * Loads and processes the Hijrah calendar properties file for this calendarType. * The starting Hijrah date and the corresponding ISO date are * extracted and used to calculate the epochDate offset. * The version number is identified and ignored. * Everything else is the data for a year with containing the length of each * of 12 months. * * @throws DateTimeException if initialization of the calendar data from the * resource fails */ private void loadCalendarData() { try { String resourceName = calendarProperties.getProperty(PROP_PREFIX + typeId); Objects.requireNonNull(resourceName, "Resource missing for calendar: " + PROP_PREFIX + typeId); Properties props = readConfigProperties(resourceName); Map years = new HashMap<>(); int minYear = Integer.MAX_VALUE; int maxYear = Integer.MIN_VALUE; String id = null; String type = null; String version = null; int isoStart = 0; for (Map.Entry entry : props.entrySet()) { String key = (String) entry.getKey(); switch (key) { case KEY_ID: id = (String)entry.getValue(); break; case KEY_TYPE: type = (String)entry.getValue(); break; case KEY_VERSION: version = (String)entry.getValue(); break; case KEY_ISO_START: { int[] ymd = parseYMD((String) entry.getValue()); isoStart = (int) LocalDate.of(ymd[0], ymd[1], ymd[2]).toEpochDay(); break; } default: try { // Everything else is either a year or invalid int year = Integer.valueOf(key); int[] months = parseMonths((String) entry.getValue()); years.put(year, months); maxYear = Math.max(maxYear, year); minYear = Math.min(minYear, year); } catch (NumberFormatException nfe) { throw new IllegalArgumentException("bad key: " + key); } } } if (!getId().equals(id)) { throw new IllegalArgumentException("Configuration is for a different calendar: " + id); } if (!getCalendarType().equals(type)) { throw new IllegalArgumentException("Configuration is for a different calendar type: " + type); } if (version == null || version.isEmpty()) { throw new IllegalArgumentException("Configuration does not contain a version"); } if (isoStart == 0) { throw new IllegalArgumentException("Configuration does not contain a ISO start date"); } // Now create and validate the array of epochDays indexed by epochMonth hijrahStartEpochMonth = minYear * 12; minEpochDay = isoStart; hijrahEpochMonthStartDays = createEpochMonths(minEpochDay, minYear, maxYear, years); maxEpochDay = hijrahEpochMonthStartDays[hijrahEpochMonthStartDays.length - 1]; // Compute the min and max year length in days. for (int year = minYear; year < maxYear; year++) { int length = getYearLength(year); minYearLength = Math.min(minYearLength, length); maxYearLength = Math.max(maxYearLength, length); } } catch (Exception ex) { // Log error and throw a DateTimeException PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono"); logger.severe("Unable to initialize Hijrah calendar proxy: " + typeId, ex); throw new DateTimeException("Unable to initialize HijrahCalendar: " + typeId, ex); } } /** * Converts the map of year to month lengths ranging from minYear to maxYear * into a linear contiguous array of epochDays. The index is the hijrahMonth * computed from year and month and offset by minYear. The value of each * entry is the epochDay corresponding to the first day of the month. * * @param minYear The minimum year for which data is provided * @param maxYear The maximum year for which data is provided * @param years a Map of year to the array of 12 month lengths * @return array of epochDays for each month from min to max */ private int[] createEpochMonths(int epochDay, int minYear, int maxYear, Map years) { // Compute the size for the array of dates int numMonths = (maxYear - minYear + 1) * 12 + 1; // Initialize the running epochDay as the corresponding ISO Epoch day int epochMonth = 0; // index into array of epochMonths int[] epochMonths = new int[numMonths]; minMonthLength = Integer.MAX_VALUE; maxMonthLength = Integer.MIN_VALUE; // Only whole years are valid, any zero's in the array are illegal for (int year = minYear; year <= maxYear; year++) { int[] months = years.get(year);// must not be gaps for (int month = 0; month < 12; month++) { int length = months[month]; epochMonths[epochMonth++] = epochDay; if (length < 29 || length > 32) { throw new IllegalArgumentException("Invalid month length in year: " + minYear); } epochDay += length; minMonthLength = Math.min(minMonthLength, length); maxMonthLength = Math.max(maxMonthLength, length); } } // Insert the final epochDay epochMonths[epochMonth++] = epochDay; if (epochMonth != epochMonths.length) { throw new IllegalStateException("Did not fill epochMonths exactly: ndx = " + epochMonth + " should be " + epochMonths.length); } return epochMonths; } /** * Parses the 12 months lengths from a property value for a specific year. * * @param line the value of a year property * @return an array of int[12] containing the 12 month lengths * @throws IllegalArgumentException if the number of months is not 12 * @throws NumberFormatException if the 12 tokens are not numbers */ private int[] parseMonths(String line) { int[] months = new int[12]; String[] numbers = line.split("\\s"); if (numbers.length != 12) { throw new IllegalArgumentException("wrong number of months on line: " + Arrays.toString(numbers) + "; count: " + numbers.length); } for (int i = 0; i < 12; i++) { try { months[i] = Integer.valueOf(numbers[i]); } catch (NumberFormatException nfe) { throw new IllegalArgumentException("bad key: " + numbers[i]); } } return months; } /** * Parse yyyy-MM-dd into a 3 element array [yyyy, mm, dd]. * * @param string the input string * @return the 3 element array with year, month, day */ private int[] parseYMD(String string) { // yyyy-MM-dd string = string.trim(); try { if (string.charAt(4) != '-' || string.charAt(7) != '-') { throw new IllegalArgumentException("date must be yyyy-MM-dd"); } int[] ymd = new int[3]; ymd[0] = Integer.valueOf(string.substring(0, 4)); ymd[1] = Integer.valueOf(string.substring(5, 7)); ymd[2] = Integer.valueOf(string.substring(8, 10)); return ymd; } catch (NumberFormatException ex) { throw new IllegalArgumentException("date must be yyyy-MM-dd", ex); } } //----------------------------------------------------------------------- /** * Writes the Chronology using a * dedicated serialized form. * @serialData *
     *  out.writeByte(1);     // identifies a Chronology
     *  out.writeUTF(getId());
     * 
* * @return the instance of {@code Ser}, not null */ @Override Object writeReplace() { return super.writeReplace(); } /** * Defend against malicious streams. * * @param s the stream to read * @throws InvalidObjectException always */ private void readObject(ObjectInputStream s) throws InvalidObjectException { throw new InvalidObjectException("Deserialization via serialization delegate"); } }