Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/java/time/chrono/HijrahChronology.java
38918 views
/*1* Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425/*26* Copyright (c) 2012, Stephen Colebourne & Michael Nascimento Santos27*28* All rights reserved.29*30* Redistribution and use in source and binary forms, with or without31* modification, are permitted provided that the following conditions are met:32*33* * Redistributions of source code must retain the above copyright notice,34* this list of conditions and the following disclaimer.35*36* * Redistributions in binary form must reproduce the above copyright notice,37* this list of conditions and the following disclaimer in the documentation38* and/or other materials provided with the distribution.39*40* * Neither the name of JSR-310 nor the names of its contributors41* may be used to endorse or promote products derived from this software42* without specific prior written permission.43*44* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS45* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT46* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR47* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR48* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,49* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,50* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR51* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF52* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING53* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS54* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.55*/5657package java.time.chrono;5859import static java.time.temporal.ChronoField.EPOCH_DAY;6061import java.io.File;62import java.io.FileInputStream;63import java.io.IOException;64import java.io.InputStream;65import java.io.InvalidObjectException;66import java.io.ObjectInputStream;67import java.io.Serializable;68import java.security.AccessController;69import java.security.PrivilegedActionException;70import java.time.Clock;71import java.time.DateTimeException;72import java.time.Instant;73import java.time.LocalDate;74import java.time.ZoneId;75import java.time.format.ResolverStyle;76import java.time.temporal.ChronoField;77import java.time.temporal.TemporalAccessor;78import java.time.temporal.TemporalField;79import java.time.temporal.ValueRange;80import java.util.Arrays;81import java.util.HashMap;82import java.util.List;83import java.util.Map;84import java.util.Objects;85import java.util.Properties;8687import sun.util.logging.PlatformLogger;8889/**90* The Hijrah calendar is a lunar calendar supporting Islamic calendars.91* <p>92* The HijrahChronology follows the rules of the Hijrah calendar system. The Hijrah93* calendar has several variants based on differences in when the new moon is94* determined to have occurred and where the observation is made.95* In some variants the length of each month is96* computed algorithmically from the astronomical data for the moon and earth and97* in others the length of the month is determined by an authorized sighting98* of the new moon. For the algorithmically based calendars the calendar99* can project into the future.100* For sighting based calendars only historical data from past101* sightings is available.102* <p>103* The length of each month is 29 or 30 days.104* Ordinary years have 354 days; leap years have 355 days.105*106* <p>107* CLDR and LDML identify variants:108* <table cellpadding="2" summary="Variants of Hijrah Calendars">109* <thead>110* <tr class="tableSubHeadingColor">111* <th class="colFirst" align="left" >Chronology ID</th>112* <th class="colFirst" align="left" >Calendar Type</th>113* <th class="colFirst" align="left" >Locale extension, see {@link java.util.Locale}</th>114* <th class="colLast" align="left" >Description</th>115* </tr>116* </thead>117* <tbody>118* <tr class="altColor">119* <td>Hijrah-umalqura</td>120* <td>islamic-umalqura</td>121* <td>ca-islamic-umalqura</td>122* <td>Islamic - Umm Al-Qura calendar of Saudi Arabia</td>123* </tr>124* </tbody>125* </table>126* <p>Additional variants may be available through {@link Chronology#getAvailableChronologies()}.127*128* <p>Example</p>129* <p>130* Selecting the chronology from the locale uses {@link Chronology#ofLocale}131* to find the Chronology based on Locale supported BCP 47 extension mechanism132* to request a specific calendar ("ca"). For example,133* </p>134* <pre>135* Locale locale = Locale.forLanguageTag("en-US-u-ca-islamic-umalqura");136* Chronology chrono = Chronology.ofLocale(locale);137* </pre>138*139* @implSpec140* This class is immutable and thread-safe.141*142* @implNote143* Each Hijrah variant is configured individually. Each variant is defined by a144* property resource that defines the {@code ID}, the {@code calendar type},145* the start of the calendar, the alignment with the146* ISO calendar, and the length of each month for a range of years.147* The variants are identified in the {@code calendars.properties} file.148* The new properties are prefixed with {@code "calendars.hijrah."}:149* <table cellpadding="2" border="0" summary="Configuration of Hijrah Calendar Variants">150* <thead>151* <tr class="tableSubHeadingColor">152* <th class="colFirst" align="left">Property Name</th>153* <th class="colFirst" align="left">Property value</th>154* <th class="colLast" align="left">Description </th>155* </tr>156* </thead>157* <tbody>158* <tr class="altColor">159* <td>calendars.hijrah.{ID}</td>160* <td>The property resource defining the {@code {ID}} variant</td>161* <td>The property resource is located with the {@code calendars.properties} file</td>162* </tr>163* <tr class="rowColor">164* <td>calendars.hijrah.{ID}.type</td>165* <td>The calendar type</td>166* <td>LDML defines the calendar type names</td>167* </tr>168* </tbody>169* </table>170* <p>171* The Hijrah property resource is a set of properties that describe the calendar.172* The syntax is defined by {@code java.util.Properties#load(Reader)}.173* <table cellpadding="2" summary="Configuration of Hijrah Calendar">174* <thead>175* <tr class="tableSubHeadingColor">176* <th class="colFirst" align="left" > Property Name</th>177* <th class="colFirst" align="left" > Property value</th>178* <th class="colLast" align="left" > Description </th>179* </tr>180* </thead>181* <tbody>182* <tr class="altColor">183* <td>id</td>184* <td>Chronology Id, for example, "Hijrah-umalqura"</td>185* <td>The Id of the calendar in common usage</td>186* </tr>187* <tr class="rowColor">188* <td>type</td>189* <td>Calendar type, for example, "islamic-umalqura"</td>190* <td>LDML defines the calendar types</td>191* </tr>192* <tr class="altColor">193* <td>version</td>194* <td>Version, for example: "1.8.0_1"</td>195* <td>The version of the Hijrah variant data</td>196* </tr>197* <tr class="rowColor">198* <td>iso-start</td>199* <td>ISO start date, formatted as {@code yyyy-MM-dd}, for example: "1900-04-30"</td>200* <td>The ISO date of the first day of the minimum Hijrah year.</td>201* </tr>202* <tr class="altColor">203* <td>yyyy - a numeric 4 digit year, for example "1434"</td>204* <td>The value is a sequence of 12 month lengths,205* for example: "29 30 29 30 29 30 30 30 29 30 29 29"</td>206* <td>The lengths of the 12 months of the year separated by whitespace.207* A numeric year property must be present for every year without any gaps.208* The month lengths must be between 29-32 inclusive.209* </td>210* </tr>211* </tbody>212* </table>213*214* @since 1.8215*/216public final class HijrahChronology extends AbstractChronology implements Serializable {217218/**219* The Hijrah Calendar id.220*/221private final transient String typeId;222/**223* The Hijrah calendarType.224*/225private final transient String calendarType;226/**227* Serialization version.228*/229private static final long serialVersionUID = 3127340209035924785L;230/**231* Singleton instance of the Islamic Umm Al-Qura calendar of Saudi Arabia.232* Other Hijrah chronology variants may be available from233* {@link Chronology#getAvailableChronologies}.234*/235public static final HijrahChronology INSTANCE;236/**237* Flag to indicate the initialization of configuration data is complete.238* @see #checkCalendarInit()239*/240private transient volatile boolean initComplete;241/**242* Array of epoch days indexed by Hijrah Epoch month.243* Computed by {@link #loadCalendarData}.244*/245private transient int[] hijrahEpochMonthStartDays;246/**247* The minimum epoch day of this Hijrah calendar.248* Computed by {@link #loadCalendarData}.249*/250private transient int minEpochDay;251/**252* The maximum epoch day for which calendar data is available.253* Computed by {@link #loadCalendarData}.254*/255private transient int maxEpochDay;256/**257* The minimum epoch month.258* Computed by {@link #loadCalendarData}.259*/260private transient int hijrahStartEpochMonth;261/**262* The minimum length of a month.263* Computed by {@link #createEpochMonths}.264*/265private transient int minMonthLength;266/**267* The maximum length of a month.268* Computed by {@link #createEpochMonths}.269*/270private transient int maxMonthLength;271/**272* The minimum length of a year in days.273* Computed by {@link #createEpochMonths}.274*/275private transient int minYearLength;276/**277* The maximum length of a year in days.278* Computed by {@link #createEpochMonths}.279*/280private transient int maxYearLength;281/**282* A reference to the properties stored in283* ${java.home}/lib/calendars.properties284*/285private final transient static Properties calendarProperties;286287/**288* Prefix of property names for Hijrah calendar variants.289*/290private static final String PROP_PREFIX = "calendar.hijrah.";291/**292* Suffix of property names containing the calendar type of a variant.293*/294private static final String PROP_TYPE_SUFFIX = ".type";295296/**297* Static initialization of the predefined calendars found in the298* lib/calendars.properties file.299*/300static {301try {302calendarProperties = sun.util.calendar.BaseCalendar.getCalendarProperties();303} catch (IOException ioe) {304throw new InternalError("Can't initialize lib/calendars.properties", ioe);305}306307try {308INSTANCE = new HijrahChronology("Hijrah-umalqura");309// Register it by its aliases310AbstractChronology.registerChrono(INSTANCE, "Hijrah");311AbstractChronology.registerChrono(INSTANCE, "islamic");312} catch (DateTimeException ex) {313// Absence of Hijrah calendar is fatal to initializing this class.314PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono");315logger.severe("Unable to initialize Hijrah calendar: Hijrah-umalqura", ex);316throw new RuntimeException("Unable to initialize Hijrah-umalqura calendar", ex.getCause());317}318registerVariants();319}320321/**322* For each Hijrah variant listed, create the HijrahChronology and register it.323* Exceptions during initialization are logged but otherwise ignored.324*/325private static void registerVariants() {326for (String name : calendarProperties.stringPropertyNames()) {327if (name.startsWith(PROP_PREFIX)) {328String id = name.substring(PROP_PREFIX.length());329if (id.indexOf('.') >= 0) {330continue; // no name or not a simple name of a calendar331}332if (id.equals(INSTANCE.getId())) {333continue; // do not duplicate the default334}335try {336// Create and register the variant337HijrahChronology chrono = new HijrahChronology(id);338AbstractChronology.registerChrono(chrono);339} catch (DateTimeException ex) {340// Log error and continue341PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono");342logger.severe("Unable to initialize Hijrah calendar: " + id, ex);343}344}345}346}347348/**349* Create a HijrahChronology for the named variant.350* The resource and calendar type are retrieved from properties351* in the {@code calendars.properties}.352* The property names are {@code "calendar.hijrah." + id}353* and {@code "calendar.hijrah." + id + ".type"}354* @param id the id of the calendar355* @throws DateTimeException if the calendar type is missing from the properties file.356* @throws IllegalArgumentException if the id is empty357*/358private HijrahChronology(String id) throws DateTimeException {359if (id.isEmpty()) {360throw new IllegalArgumentException("calendar id is empty");361}362String propName = PROP_PREFIX + id + PROP_TYPE_SUFFIX;363String calType = calendarProperties.getProperty(propName);364if (calType == null || calType.isEmpty()) {365throw new DateTimeException("calendarType is missing or empty for: " + propName);366}367this.typeId = id;368this.calendarType = calType;369}370371/**372* Check and ensure that the calendar data has been initialized.373* The initialization check is performed at the boundary between374* public and package methods. If a public calls another public method375* a check is not necessary in the caller.376* The constructors of HijrahDate call {@link #getEpochDay} or377* {@link #getHijrahDateInfo} so every call from HijrahDate to a378* HijrahChronology via package private methods has been checked.379*380* @throws DateTimeException if the calendar data configuration is381* malformed or IOExceptions occur loading the data382*/383private void checkCalendarInit() {384// Keep this short so it can be inlined for performance385if (initComplete == false) {386loadCalendarData();387initComplete = true;388}389}390391//-----------------------------------------------------------------------392/**393* Gets the ID of the chronology.394* <p>395* The ID uniquely identifies the {@code Chronology}. It can be used to396* lookup the {@code Chronology} using {@link Chronology#of(String)}.397*398* @return the chronology ID, non-null399* @see #getCalendarType()400*/401@Override402public String getId() {403return typeId;404}405406/**407* Gets the calendar type of the Islamic calendar.408* <p>409* The calendar type is an identifier defined by the410* <em>Unicode Locale Data Markup Language (LDML)</em> specification.411* It can be used to lookup the {@code Chronology} using {@link Chronology#of(String)}.412*413* @return the calendar system type; non-null if the calendar has414* a standard type, otherwise null415* @see #getId()416*/417@Override418public String getCalendarType() {419return calendarType;420}421422//-----------------------------------------------------------------------423/**424* Obtains a local date in Hijrah calendar system from the425* era, year-of-era, month-of-year and day-of-month fields.426*427* @param era the Hijrah era, not null428* @param yearOfEra the year-of-era429* @param month the month-of-year430* @param dayOfMonth the day-of-month431* @return the Hijrah local date, not null432* @throws DateTimeException if unable to create the date433* @throws ClassCastException if the {@code era} is not a {@code HijrahEra}434*/435@Override436public HijrahDate date(Era era, int yearOfEra, int month, int dayOfMonth) {437return date(prolepticYear(era, yearOfEra), month, dayOfMonth);438}439440/**441* Obtains a local date in Hijrah calendar system from the442* proleptic-year, month-of-year and day-of-month fields.443*444* @param prolepticYear the proleptic-year445* @param month the month-of-year446* @param dayOfMonth the day-of-month447* @return the Hijrah local date, not null448* @throws DateTimeException if unable to create the date449*/450@Override451public HijrahDate date(int prolepticYear, int month, int dayOfMonth) {452return HijrahDate.of(this, prolepticYear, month, dayOfMonth);453}454455/**456* Obtains a local date in Hijrah calendar system from the457* era, year-of-era and day-of-year fields.458*459* @param era the Hijrah era, not null460* @param yearOfEra the year-of-era461* @param dayOfYear the day-of-year462* @return the Hijrah local date, not null463* @throws DateTimeException if unable to create the date464* @throws ClassCastException if the {@code era} is not a {@code HijrahEra}465*/466@Override467public HijrahDate dateYearDay(Era era, int yearOfEra, int dayOfYear) {468return dateYearDay(prolepticYear(era, yearOfEra), dayOfYear);469}470471/**472* Obtains a local date in Hijrah calendar system from the473* proleptic-year and day-of-year fields.474*475* @param prolepticYear the proleptic-year476* @param dayOfYear the day-of-year477* @return the Hijrah local date, not null478* @throws DateTimeException if the value of the year is out of range,479* or if the day-of-year is invalid for the year480*/481@Override482public HijrahDate dateYearDay(int prolepticYear, int dayOfYear) {483HijrahDate date = HijrahDate.of(this, prolepticYear, 1, 1);484if (dayOfYear > date.lengthOfYear()) {485throw new DateTimeException("Invalid dayOfYear: " + dayOfYear);486}487return date.plusDays(dayOfYear - 1);488}489490/**491* Obtains a local date in the Hijrah calendar system from the epoch-day.492*493* @param epochDay the epoch day494* @return the Hijrah local date, not null495* @throws DateTimeException if unable to create the date496*/497@Override // override with covariant return type498public HijrahDate dateEpochDay(long epochDay) {499return HijrahDate.ofEpochDay(this, epochDay);500}501502@Override503public HijrahDate dateNow() {504return dateNow(Clock.systemDefaultZone());505}506507@Override508public HijrahDate dateNow(ZoneId zone) {509return dateNow(Clock.system(zone));510}511512@Override513public HijrahDate dateNow(Clock clock) {514return date(LocalDate.now(clock));515}516517@Override518public HijrahDate date(TemporalAccessor temporal) {519if (temporal instanceof HijrahDate) {520return (HijrahDate) temporal;521}522return HijrahDate.ofEpochDay(this, temporal.getLong(EPOCH_DAY));523}524525@Override526@SuppressWarnings("unchecked")527public ChronoLocalDateTime<HijrahDate> localDateTime(TemporalAccessor temporal) {528return (ChronoLocalDateTime<HijrahDate>) super.localDateTime(temporal);529}530531@Override532@SuppressWarnings("unchecked")533public ChronoZonedDateTime<HijrahDate> zonedDateTime(TemporalAccessor temporal) {534return (ChronoZonedDateTime<HijrahDate>) super.zonedDateTime(temporal);535}536537@Override538@SuppressWarnings("unchecked")539public ChronoZonedDateTime<HijrahDate> zonedDateTime(Instant instant, ZoneId zone) {540return (ChronoZonedDateTime<HijrahDate>) super.zonedDateTime(instant, zone);541}542543//-----------------------------------------------------------------------544@Override545public boolean isLeapYear(long prolepticYear) {546checkCalendarInit();547if (prolepticYear < getMinimumYear() || prolepticYear > getMaximumYear()) {548return false;549}550int len = getYearLength((int) prolepticYear);551return (len > 354);552}553554@Override555public int prolepticYear(Era era, int yearOfEra) {556if (era instanceof HijrahEra == false) {557throw new ClassCastException("Era must be HijrahEra");558}559return yearOfEra;560}561562@Override563public HijrahEra eraOf(int eraValue) {564switch (eraValue) {565case 1:566return HijrahEra.AH;567default:568throw new DateTimeException("invalid Hijrah era");569}570}571572@Override573public List<Era> eras() {574return Arrays.<Era>asList(HijrahEra.values());575}576577//-----------------------------------------------------------------------578@Override579public ValueRange range(ChronoField field) {580checkCalendarInit();581if (field instanceof ChronoField) {582ChronoField f = field;583switch (f) {584case DAY_OF_MONTH:585return ValueRange.of(1, 1, getMinimumMonthLength(), getMaximumMonthLength());586case DAY_OF_YEAR:587return ValueRange.of(1, getMaximumDayOfYear());588case ALIGNED_WEEK_OF_MONTH:589return ValueRange.of(1, 5);590case YEAR:591case YEAR_OF_ERA:592return ValueRange.of(getMinimumYear(), getMaximumYear());593case ERA:594return ValueRange.of(1, 1);595default:596return field.range();597}598}599return field.range();600}601602//-----------------------------------------------------------------------603@Override // override for return type604public HijrahDate resolveDate(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {605return (HijrahDate) super.resolveDate(fieldValues, resolverStyle);606}607608//-----------------------------------------------------------------------609/**610* Check the validity of a year.611*612* @param prolepticYear the year to check613*/614int checkValidYear(long prolepticYear) {615if (prolepticYear < getMinimumYear() || prolepticYear > getMaximumYear()) {616throw new DateTimeException("Invalid Hijrah year: " + prolepticYear);617}618return (int) prolepticYear;619}620621void checkValidDayOfYear(int dayOfYear) {622if (dayOfYear < 1 || dayOfYear > getMaximumDayOfYear()) {623throw new DateTimeException("Invalid Hijrah day of year: " + dayOfYear);624}625}626627void checkValidMonth(int month) {628if (month < 1 || month > 12) {629throw new DateTimeException("Invalid Hijrah month: " + month);630}631}632633//-----------------------------------------------------------------------634/**635* Returns an array containing the Hijrah year, month and day636* computed from the epoch day.637*638* @param epochDay the EpochDay639* @return int[0] = YEAR, int[1] = MONTH, int[2] = DATE640*/641int[] getHijrahDateInfo(int epochDay) {642checkCalendarInit(); // ensure that the chronology is initialized643if (epochDay < minEpochDay || epochDay >= maxEpochDay) {644throw new DateTimeException("Hijrah date out of range");645}646647int epochMonth = epochDayToEpochMonth(epochDay);648int year = epochMonthToYear(epochMonth);649int month = epochMonthToMonth(epochMonth);650int day1 = epochMonthToEpochDay(epochMonth);651int date = epochDay - day1; // epochDay - dayOfEpoch(year, month);652653int dateInfo[] = new int[3];654dateInfo[0] = year;655dateInfo[1] = month + 1; // change to 1-based.656dateInfo[2] = date + 1; // change to 1-based.657return dateInfo;658}659660/**661* Return the epoch day computed from Hijrah year, month, and day.662*663* @param prolepticYear the year to represent, 0-origin664* @param monthOfYear the month-of-year to represent, 1-origin665* @param dayOfMonth the day-of-month to represent, 1-origin666* @return the epoch day667*/668long getEpochDay(int prolepticYear, int monthOfYear, int dayOfMonth) {669checkCalendarInit(); // ensure that the chronology is initialized670checkValidMonth(monthOfYear);671int epochMonth = yearToEpochMonth(prolepticYear) + (monthOfYear - 1);672if (epochMonth < 0 || epochMonth >= hijrahEpochMonthStartDays.length) {673throw new DateTimeException("Invalid Hijrah date, year: " +674prolepticYear + ", month: " + monthOfYear);675}676if (dayOfMonth < 1 || dayOfMonth > getMonthLength(prolepticYear, monthOfYear)) {677throw new DateTimeException("Invalid Hijrah day of month: " + dayOfMonth);678}679return epochMonthToEpochDay(epochMonth) + (dayOfMonth - 1);680}681682/**683* Returns day of year for the year and month.684*685* @param prolepticYear a proleptic year686* @param month a month, 1-origin687* @return the day of year, 1-origin688*/689int getDayOfYear(int prolepticYear, int month) {690return yearMonthToDayOfYear(prolepticYear, (month - 1));691}692693/**694* Returns month length for the year and month.695*696* @param prolepticYear a proleptic year697* @param monthOfYear a month, 1-origin.698* @return the length of the month699*/700int getMonthLength(int prolepticYear, int monthOfYear) {701int epochMonth = yearToEpochMonth(prolepticYear) + (monthOfYear - 1);702if (epochMonth < 0 || epochMonth >= hijrahEpochMonthStartDays.length) {703throw new DateTimeException("Invalid Hijrah date, year: " +704prolepticYear + ", month: " + monthOfYear);705}706return epochMonthLength(epochMonth);707}708709/**710* Returns year length.711* Note: The 12th month must exist in the data.712*713* @param prolepticYear a proleptic year714* @return year length in days715*/716int getYearLength(int prolepticYear) {717return yearMonthToDayOfYear(prolepticYear, 12);718}719720/**721* Return the minimum supported Hijrah year.722*723* @return the minimum724*/725int getMinimumYear() {726return epochMonthToYear(0);727}728729/**730* Return the maximum supported Hijrah ear.731*732* @return the minimum733*/734int getMaximumYear() {735return epochMonthToYear(hijrahEpochMonthStartDays.length - 1) - 1;736}737738/**739* Returns maximum day-of-month.740*741* @return maximum day-of-month742*/743int getMaximumMonthLength() {744return maxMonthLength;745}746747/**748* Returns smallest maximum day-of-month.749*750* @return smallest maximum day-of-month751*/752int getMinimumMonthLength() {753return minMonthLength;754}755756/**757* Returns maximum day-of-year.758*759* @return maximum day-of-year760*/761int getMaximumDayOfYear() {762return maxYearLength;763}764765/**766* Returns smallest maximum day-of-year.767*768* @return smallest maximum day-of-year769*/770int getSmallestMaximumDayOfYear() {771return minYearLength;772}773774/**775* Returns the epochMonth found by locating the epochDay in the table. The776* epochMonth is the index in the table777*778* @param epochDay779* @return The index of the element of the start of the month containing the780* epochDay.781*/782private int epochDayToEpochMonth(int epochDay) {783// binary search784int ndx = Arrays.binarySearch(hijrahEpochMonthStartDays, epochDay);785if (ndx < 0) {786ndx = -ndx - 2;787}788return ndx;789}790791/**792* Returns the year computed from the epochMonth793*794* @param epochMonth the epochMonth795* @return the Hijrah Year796*/797private int epochMonthToYear(int epochMonth) {798return (epochMonth + hijrahStartEpochMonth) / 12;799}800801/**802* Returns the epochMonth for the Hijrah Year.803*804* @param year the HijrahYear805* @return the epochMonth for the beginning of the year.806*/807private int yearToEpochMonth(int year) {808return (year * 12) - hijrahStartEpochMonth;809}810811/**812* Returns the Hijrah month from the epochMonth.813*814* @param epochMonth the epochMonth815* @return the month of the Hijrah Year816*/817private int epochMonthToMonth(int epochMonth) {818return (epochMonth + hijrahStartEpochMonth) % 12;819}820821/**822* Returns the epochDay for the start of the epochMonth.823*824* @param epochMonth the epochMonth825* @return the epochDay for the start of the epochMonth.826*/827private int epochMonthToEpochDay(int epochMonth) {828return hijrahEpochMonthStartDays[epochMonth];829830}831832/**833* Returns the day of year for the requested HijrahYear and month.834*835* @param prolepticYear the Hijrah year836* @param month the Hijrah month837* @return the day of year for the start of the month of the year838*/839private int yearMonthToDayOfYear(int prolepticYear, int month) {840int epochMonthFirst = yearToEpochMonth(prolepticYear);841return epochMonthToEpochDay(epochMonthFirst + month)842- epochMonthToEpochDay(epochMonthFirst);843}844845/**846* Returns the length of the epochMonth. It is computed from the start of847* the following month minus the start of the requested month.848*849* @param epochMonth the epochMonth; assumed to be within range850* @return the length in days of the epochMonth851*/852private int epochMonthLength(int epochMonth) {853// The very last entry in the epochMonth table is not the start of a month854return hijrahEpochMonthStartDays[epochMonth + 1]855- hijrahEpochMonthStartDays[epochMonth];856}857858//-----------------------------------------------------------------------859private static final String KEY_ID = "id";860private static final String KEY_TYPE = "type";861private static final String KEY_VERSION = "version";862private static final String KEY_ISO_START = "iso-start";863864/**865* Return the configuration properties from the resource.866* <p>867* The default location of the variant configuration resource is:868* <pre>869* "$java.home/lib/" + resource-name870* </pre>871*872* @param resource the name of the calendar property resource873* @return a Properties containing the properties read from the resource.874* @throws Exception if access to the property resource fails875*/876private static Properties readConfigProperties(final String resource) throws Exception {877try {878return AccessController879.doPrivileged((java.security.PrivilegedExceptionAction<Properties>)880() -> {881String libDir = System.getProperty("java.home") + File.separator + "lib";882File file = new File(libDir, resource);883Properties props = new Properties();884try (InputStream is = new FileInputStream(file)) {885props.load(is);886}887return props;888});889} catch (PrivilegedActionException pax) {890throw pax.getException();891}892}893894/**895* Loads and processes the Hijrah calendar properties file for this calendarType.896* The starting Hijrah date and the corresponding ISO date are897* extracted and used to calculate the epochDate offset.898* The version number is identified and ignored.899* Everything else is the data for a year with containing the length of each900* of 12 months.901*902* @throws DateTimeException if initialization of the calendar data from the903* resource fails904*/905private void loadCalendarData() {906try {907String resourceName = calendarProperties.getProperty(PROP_PREFIX + typeId);908Objects.requireNonNull(resourceName, "Resource missing for calendar: " + PROP_PREFIX + typeId);909Properties props = readConfigProperties(resourceName);910911Map<Integer, int[]> years = new HashMap<>();912int minYear = Integer.MAX_VALUE;913int maxYear = Integer.MIN_VALUE;914String id = null;915String type = null;916String version = null;917int isoStart = 0;918for (Map.Entry<Object, Object> entry : props.entrySet()) {919String key = (String) entry.getKey();920switch (key) {921case KEY_ID:922id = (String)entry.getValue();923break;924case KEY_TYPE:925type = (String)entry.getValue();926break;927case KEY_VERSION:928version = (String)entry.getValue();929break;930case KEY_ISO_START: {931int[] ymd = parseYMD((String) entry.getValue());932isoStart = (int) LocalDate.of(ymd[0], ymd[1], ymd[2]).toEpochDay();933break;934}935default:936try {937// Everything else is either a year or invalid938int year = Integer.valueOf(key);939int[] months = parseMonths((String) entry.getValue());940years.put(year, months);941maxYear = Math.max(maxYear, year);942minYear = Math.min(minYear, year);943} catch (NumberFormatException nfe) {944throw new IllegalArgumentException("bad key: " + key);945}946}947}948949if (!getId().equals(id)) {950throw new IllegalArgumentException("Configuration is for a different calendar: " + id);951}952if (!getCalendarType().equals(type)) {953throw new IllegalArgumentException("Configuration is for a different calendar type: " + type);954}955if (version == null || version.isEmpty()) {956throw new IllegalArgumentException("Configuration does not contain a version");957}958if (isoStart == 0) {959throw new IllegalArgumentException("Configuration does not contain a ISO start date");960}961962// Now create and validate the array of epochDays indexed by epochMonth963hijrahStartEpochMonth = minYear * 12;964minEpochDay = isoStart;965hijrahEpochMonthStartDays = createEpochMonths(minEpochDay, minYear, maxYear, years);966maxEpochDay = hijrahEpochMonthStartDays[hijrahEpochMonthStartDays.length - 1];967968// Compute the min and max year length in days.969for (int year = minYear; year < maxYear; year++) {970int length = getYearLength(year);971minYearLength = Math.min(minYearLength, length);972maxYearLength = Math.max(maxYearLength, length);973}974} catch (Exception ex) {975// Log error and throw a DateTimeException976PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono");977logger.severe("Unable to initialize Hijrah calendar proxy: " + typeId, ex);978throw new DateTimeException("Unable to initialize HijrahCalendar: " + typeId, ex);979}980}981982/**983* Converts the map of year to month lengths ranging from minYear to maxYear984* into a linear contiguous array of epochDays. The index is the hijrahMonth985* computed from year and month and offset by minYear. The value of each986* entry is the epochDay corresponding to the first day of the month.987*988* @param minYear The minimum year for which data is provided989* @param maxYear The maximum year for which data is provided990* @param years a Map of year to the array of 12 month lengths991* @return array of epochDays for each month from min to max992*/993private int[] createEpochMonths(int epochDay, int minYear, int maxYear, Map<Integer, int[]> years) {994// Compute the size for the array of dates995int numMonths = (maxYear - minYear + 1) * 12 + 1;996997// Initialize the running epochDay as the corresponding ISO Epoch day998int epochMonth = 0; // index into array of epochMonths999int[] epochMonths = new int[numMonths];1000minMonthLength = Integer.MAX_VALUE;1001maxMonthLength = Integer.MIN_VALUE;10021003// Only whole years are valid, any zero's in the array are illegal1004for (int year = minYear; year <= maxYear; year++) {1005int[] months = years.get(year);// must not be gaps1006for (int month = 0; month < 12; month++) {1007int length = months[month];1008epochMonths[epochMonth++] = epochDay;10091010if (length < 29 || length > 32) {1011throw new IllegalArgumentException("Invalid month length in year: " + minYear);1012}1013epochDay += length;1014minMonthLength = Math.min(minMonthLength, length);1015maxMonthLength = Math.max(maxMonthLength, length);1016}1017}10181019// Insert the final epochDay1020epochMonths[epochMonth++] = epochDay;10211022if (epochMonth != epochMonths.length) {1023throw new IllegalStateException("Did not fill epochMonths exactly: ndx = " + epochMonth1024+ " should be " + epochMonths.length);1025}10261027return epochMonths;1028}10291030/**1031* Parses the 12 months lengths from a property value for a specific year.1032*1033* @param line the value of a year property1034* @return an array of int[12] containing the 12 month lengths1035* @throws IllegalArgumentException if the number of months is not 121036* @throws NumberFormatException if the 12 tokens are not numbers1037*/1038private int[] parseMonths(String line) {1039int[] months = new int[12];1040String[] numbers = line.split("\\s");1041if (numbers.length != 12) {1042throw new IllegalArgumentException("wrong number of months on line: " + Arrays.toString(numbers) + "; count: " + numbers.length);1043}1044for (int i = 0; i < 12; i++) {1045try {1046months[i] = Integer.valueOf(numbers[i]);1047} catch (NumberFormatException nfe) {1048throw new IllegalArgumentException("bad key: " + numbers[i]);1049}1050}1051return months;1052}10531054/**1055* Parse yyyy-MM-dd into a 3 element array [yyyy, mm, dd].1056*1057* @param string the input string1058* @return the 3 element array with year, month, day1059*/1060private int[] parseYMD(String string) {1061// yyyy-MM-dd1062string = string.trim();1063try {1064if (string.charAt(4) != '-' || string.charAt(7) != '-') {1065throw new IllegalArgumentException("date must be yyyy-MM-dd");1066}1067int[] ymd = new int[3];1068ymd[0] = Integer.valueOf(string.substring(0, 4));1069ymd[1] = Integer.valueOf(string.substring(5, 7));1070ymd[2] = Integer.valueOf(string.substring(8, 10));1071return ymd;1072} catch (NumberFormatException ex) {1073throw new IllegalArgumentException("date must be yyyy-MM-dd", ex);1074}1075}10761077//-----------------------------------------------------------------------1078/**1079* Writes the Chronology using a1080* <a href="../../../serialized-form.html#java.time.chrono.Ser">dedicated serialized form</a>.1081* @serialData1082* <pre>1083* out.writeByte(1); // identifies a Chronology1084* out.writeUTF(getId());1085* </pre>1086*1087* @return the instance of {@code Ser}, not null1088*/1089@Override1090Object writeReplace() {1091return super.writeReplace();1092}10931094/**1095* Defend against malicious streams.1096*1097* @param s the stream to read1098* @throws InvalidObjectException always1099*/1100private void readObject(ObjectInputStream s) throws InvalidObjectException {1101throw new InvalidObjectException("Deserialization via serialization delegate");1102}1103}110411051106