Path: blob/master/src/java.base/share/classes/java/time/chrono/HijrahChronology.java
41159 views
/*1* Copyright (c) 2012, 2021, 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.FilePermission;62import java.io.IOException;63import java.io.InputStream;64import java.io.InvalidObjectException;65import java.io.ObjectInputStream;66import java.io.Serializable;67import java.io.UncheckedIOException;68import java.nio.file.Files;69import java.nio.file.Path;70import java.nio.file.StandardOpenOption;71import java.security.AccessController;72import java.security.PrivilegedAction;73import java.time.Clock;74import java.time.DateTimeException;75import java.time.Instant;76import java.time.LocalDate;77import java.time.ZoneId;78import java.time.format.ResolverStyle;79import java.time.temporal.ChronoField;80import java.time.temporal.TemporalAccessor;81import java.time.temporal.TemporalField;82import java.time.temporal.ValueRange;83import java.util.Arrays;84import java.util.HashMap;85import java.util.List;86import java.util.Map;87import java.util.Properties;8889import sun.util.logging.PlatformLogger;9091/**92* The Hijrah calendar is a lunar calendar supporting Islamic calendars.93* <p>94* The HijrahChronology follows the rules of the Hijrah calendar system. The Hijrah95* calendar has several variants based on differences in when the new moon is96* determined to have occurred and where the observation is made.97* In some variants the length of each month is98* computed algorithmically from the astronomical data for the moon and earth and99* in others the length of the month is determined by an authorized sighting100* of the new moon. For the algorithmically based calendars the calendar101* can project into the future.102* For sighting based calendars only historical data from past103* sightings is available.104* <p>105* The length of each month is 29 or 30 days.106* Ordinary years have 354 days; leap years have 355 days.107*108* <p>109* CLDR and LDML identify variants:110* <table class="striped" style="text-align:left">111* <caption style="display:none">Variants of Hijrah Calendars</caption>112* <thead>113* <tr>114* <th scope="col">Chronology ID</th>115* <th scope="col">Calendar Type</th>116* <th scope="col">Locale extension, see {@link java.util.Locale}</th>117* <th scope="col">Description</th>118* </tr>119* </thead>120* <tbody>121* <tr>122* <th scope="row">Hijrah-umalqura</th>123* <td>islamic-umalqura</td>124* <td>ca-islamic-umalqura</td>125* <td>Islamic - Umm Al-Qura calendar of Saudi Arabia</td>126* </tr>127* </tbody>128* </table>129* <p>Additional variants may be available through {@link Chronology#getAvailableChronologies()}.130*131* <p>Example</p>132* <p>133* Selecting the chronology from the locale uses {@link Chronology#ofLocale}134* to find the Chronology based on Locale supported BCP 47 extension mechanism135* to request a specific calendar ("ca"). For example,136* </p>137* <pre>138* Locale locale = Locale.forLanguageTag("en-US-u-ca-islamic-umalqura");139* Chronology chrono = Chronology.ofLocale(locale);140* </pre>141*142* @implSpec143* This class is immutable and thread-safe.144*145* @implNote146* Each Hijrah variant is configured individually. Each variant is defined by a147* property resource that defines the {@code ID}, the {@code calendar type},148* the start of the calendar, the alignment with the149* ISO calendar, and the length of each month for a range of years.150* The variants are loaded by HijrahChronology as a resource from151* hijrah-config-<calendar type>.properties.152* <p>153* The Hijrah property resource is a set of properties that describe the calendar.154* The syntax is defined by {@code java.util.Properties#load(Reader)}.155* <table class="striped" style="text-align:left">156* <caption style="display:none">Configuration of Hijrah Calendar</caption>157* <thead>158* <tr>159* <th scope="col">Property Name</th>160* <th scope="col">Property value</th>161* <th scope="col">Description</th>162* </tr>163* </thead>164* <tbody>165* <tr>166* <th scope="row">id</th>167* <td>Chronology Id, for example, "Hijrah-umalqura"</td>168* <td>The Id of the calendar in common usage</td>169* </tr>170* <tr>171* <th scope="row">type</th>172* <td>Calendar type, for example, "islamic-umalqura"</td>173* <td>LDML defines the calendar types</td>174* </tr>175* <tr>176* <th scope="row">version</th>177* <td>Version, for example: "1.8.0_1"</td>178* <td>The version of the Hijrah variant data</td>179* </tr>180* <tr>181* <th scope="row">iso-start</th>182* <td>ISO start date, formatted as {@code yyyy-MM-dd}, for example: "1900-04-30"</td>183* <td>The ISO date of the first day of the minimum Hijrah year.</td>184* </tr>185* <tr>186* <th scope="row">yyyy - a numeric 4 digit year, for example "1434"</th>187* <td>The value is a sequence of 12 month lengths,188* for example: "29 30 29 30 29 30 30 30 29 30 29 29"</td>189* <td>The lengths of the 12 months of the year separated by whitespace.190* A numeric year property must be present for every year without any gaps.191* The month lengths must be between 29-32 inclusive.192* </td>193* </tr>194* </tbody>195* </table>196* <p>197* Additional variants may be added by providing configuration properties files in198* {@code <JAVA_HOME>/conf/chronology} directory. The properties199* files should follow the naming convention of200* {@code hijrah-config-<chronology id>_<calendar type>.properties}.201*202* @since 1.8203*/204@SuppressWarnings("removal")205public final class HijrahChronology extends AbstractChronology implements Serializable {206207/**208* The Hijrah Calendar id.209*/210private final transient String typeId;211/**212* The Hijrah calendarType.213*/214private final transient String calendarType;215/**216* Serialization version.217*/218@java.io.Serial219private static final long serialVersionUID = 3127340209035924785L;220/**221* Singleton instance of the Islamic Umm Al-Qura calendar of Saudi Arabia.222* Other Hijrah chronology variants may be available from223* {@link Chronology#getAvailableChronologies}.224*/225public static final HijrahChronology INSTANCE;226/**227* Flag to indicate the initialization of configuration data is complete.228* @see #checkCalendarInit()229*/230private transient volatile boolean initComplete;231/**232* Array of epoch days indexed by Hijrah Epoch month.233* Computed by {@link #loadCalendarData}.234*/235private transient int[] hijrahEpochMonthStartDays;236/**237* The minimum epoch day of this Hijrah calendar.238* Computed by {@link #loadCalendarData}.239*/240private transient int minEpochDay;241/**242* The maximum epoch day for which calendar data is available.243* Computed by {@link #loadCalendarData}.244*/245private transient int maxEpochDay;246/**247* The minimum epoch month.248* Computed by {@link #loadCalendarData}.249*/250private transient int hijrahStartEpochMonth;251/**252* The minimum length of a month.253* Computed by {@link #createEpochMonths}.254*/255private transient int minMonthLength;256/**257* The maximum length of a month.258* Computed by {@link #createEpochMonths}.259*/260private transient int maxMonthLength;261/**262* The minimum length of a year in days.263* Computed by {@link #createEpochMonths}.264*/265private transient int minYearLength;266/**267* The maximum length of a year in days.268* Computed by {@link #createEpochMonths}.269*/270private transient int maxYearLength;271272/**273* Prefix of resource names for Hijrah calendar variants.274*/275private static final String RESOURCE_PREFIX = "hijrah-config-";276277/**278* Suffix of resource names for Hijrah calendar variants.279*/280private static final String RESOURCE_SUFFIX = ".properties";281282/**283* Static initialization of the built-in calendars.284* The data is not loaded until it is used.285*/286static {287INSTANCE = new HijrahChronology("Hijrah-umalqura", "islamic-umalqura");288// Register it by its aliases289AbstractChronology.registerChrono(INSTANCE, "Hijrah");290AbstractChronology.registerChrono(INSTANCE, "islamic");291292// custom config chronologies293CONF_PATH = Path.of(AccessController.doPrivileged((PrivilegedAction<String>)294() -> System.getProperty("java.home")), "conf", "chronology");295registerCustomChrono();296}297298/**299* Create a HijrahChronology for the named variant and type.300*301* @param id the id of the calendar302* @param calType the typeId of the calendar303* @throws IllegalArgumentException if the id or typeId is empty304*/305private HijrahChronology(String id, String calType) {306if (id.isEmpty()) {307throw new IllegalArgumentException("calendar id is empty");308}309if (calType.isEmpty()) {310throw new IllegalArgumentException("calendar typeId is empty");311}312this.typeId = id;313this.calendarType = calType;314}315316/**317* Check and ensure that the calendar data has been initialized.318* The initialization check is performed at the boundary between319* public and package methods. If a public calls another public method320* a check is not necessary in the caller.321* The constructors of HijrahDate call {@link #getEpochDay} or322* {@link #getHijrahDateInfo} so every call from HijrahDate to a323* HijrahChronology via package private methods has been checked.324*325* @throws DateTimeException if the calendar data configuration is326* malformed or IOExceptions occur loading the data327*/328private void checkCalendarInit() {329// Keep this short so it can be inlined for performance330if (initComplete == false) {331loadCalendarData();332initComplete = true;333}334}335336//-----------------------------------------------------------------------337/**338* Gets the ID of the chronology.339* <p>340* The ID uniquely identifies the {@code Chronology}. It can be used to341* lookup the {@code Chronology} using {@link Chronology#of(String)}.342*343* @return the chronology ID, non-null344* @see #getCalendarType()345*/346@Override347public String getId() {348return typeId;349}350351/**352* Gets the calendar type of the Islamic calendar.353* <p>354* The calendar type is an identifier defined by the355* <em>Unicode Locale Data Markup Language (LDML)</em> specification.356* It can be used to lookup the {@code Chronology} using {@link Chronology#of(String)}.357*358* @return the calendar system type; non-null if the calendar has359* a standard type, otherwise null360* @see #getId()361*/362@Override363public String getCalendarType() {364return calendarType;365}366367//-----------------------------------------------------------------------368/**369* Obtains a local date in Hijrah calendar system from the370* era, year-of-era, month-of-year and day-of-month fields.371*372* @param era the Hijrah era, not null373* @param yearOfEra the year-of-era374* @param month the month-of-year375* @param dayOfMonth the day-of-month376* @return the Hijrah local date, not null377* @throws DateTimeException if unable to create the date378* @throws ClassCastException if the {@code era} is not a {@code HijrahEra}379*/380@Override381public HijrahDate date(Era era, int yearOfEra, int month, int dayOfMonth) {382return date(prolepticYear(era, yearOfEra), month, dayOfMonth);383}384385/**386* Obtains a local date in Hijrah calendar system from the387* proleptic-year, month-of-year and day-of-month fields.388*389* @param prolepticYear the proleptic-year390* @param month the month-of-year391* @param dayOfMonth the day-of-month392* @return the Hijrah local date, not null393* @throws DateTimeException if unable to create the date394*/395@Override396public HijrahDate date(int prolepticYear, int month, int dayOfMonth) {397return HijrahDate.of(this, prolepticYear, month, dayOfMonth);398}399400/**401* Obtains a local date in Hijrah calendar system from the402* era, year-of-era and day-of-year fields.403*404* @param era the Hijrah era, not null405* @param yearOfEra the year-of-era406* @param dayOfYear the day-of-year407* @return the Hijrah local date, not null408* @throws DateTimeException if unable to create the date409* @throws ClassCastException if the {@code era} is not a {@code HijrahEra}410*/411@Override412public HijrahDate dateYearDay(Era era, int yearOfEra, int dayOfYear) {413return dateYearDay(prolepticYear(era, yearOfEra), dayOfYear);414}415416/**417* Obtains a local date in Hijrah calendar system from the418* proleptic-year and day-of-year fields.419*420* @param prolepticYear the proleptic-year421* @param dayOfYear the day-of-year422* @return the Hijrah local date, not null423* @throws DateTimeException if the value of the year is out of range,424* or if the day-of-year is invalid for the year425*/426@Override427public HijrahDate dateYearDay(int prolepticYear, int dayOfYear) {428HijrahDate date = HijrahDate.of(this, prolepticYear, 1, 1);429if (dayOfYear > date.lengthOfYear()) {430throw new DateTimeException("Invalid dayOfYear: " + dayOfYear);431}432return date.plusDays(dayOfYear - 1);433}434435/**436* Obtains a local date in the Hijrah calendar system from the epoch-day.437*438* @param epochDay the epoch day439* @return the Hijrah local date, not null440* @throws DateTimeException if unable to create the date441*/442@Override // override with covariant return type443public HijrahDate dateEpochDay(long epochDay) {444return HijrahDate.ofEpochDay(this, epochDay);445}446447@Override448public HijrahDate dateNow() {449return dateNow(Clock.systemDefaultZone());450}451452@Override453public HijrahDate dateNow(ZoneId zone) {454return dateNow(Clock.system(zone));455}456457@Override458public HijrahDate dateNow(Clock clock) {459return date(LocalDate.now(clock));460}461462@Override463public HijrahDate date(TemporalAccessor temporal) {464if (temporal instanceof HijrahDate) {465return (HijrahDate) temporal;466}467return HijrahDate.ofEpochDay(this, temporal.getLong(EPOCH_DAY));468}469470@Override471@SuppressWarnings("unchecked")472public ChronoLocalDateTime<HijrahDate> localDateTime(TemporalAccessor temporal) {473return (ChronoLocalDateTime<HijrahDate>) super.localDateTime(temporal);474}475476@Override477@SuppressWarnings("unchecked")478public ChronoZonedDateTime<HijrahDate> zonedDateTime(TemporalAccessor temporal) {479return (ChronoZonedDateTime<HijrahDate>) super.zonedDateTime(temporal);480}481482@Override483@SuppressWarnings("unchecked")484public ChronoZonedDateTime<HijrahDate> zonedDateTime(Instant instant, ZoneId zone) {485return (ChronoZonedDateTime<HijrahDate>) super.zonedDateTime(instant, zone);486}487488//-----------------------------------------------------------------------489@Override490public boolean isLeapYear(long prolepticYear) {491checkCalendarInit();492if (prolepticYear < getMinimumYear() || prolepticYear > getMaximumYear()) {493return false;494}495int len = getYearLength((int) prolepticYear);496return (len > 354);497}498499@Override500public int prolepticYear(Era era, int yearOfEra) {501if (!(era instanceof HijrahEra)) {502throw new ClassCastException("Era must be HijrahEra");503}504return yearOfEra;505}506507/**508* Creates the HijrahEra object from the numeric value.509* The Hijrah calendar system has only one era covering the510* proleptic years greater than zero.511* This method returns the singleton HijrahEra for the value 1.512*513* @param eraValue the era value514* @return the calendar system era, not null515* @throws DateTimeException if unable to create the era516*/517@Override518public HijrahEra eraOf(int eraValue) {519switch (eraValue) {520case 1:521return HijrahEra.AH;522default:523throw new DateTimeException("invalid Hijrah era");524}525}526527@Override528public List<Era> eras() {529return List.of(HijrahEra.values());530}531532//-----------------------------------------------------------------------533@Override534public ValueRange range(ChronoField field) {535checkCalendarInit();536if (field instanceof ChronoField) {537ChronoField f = field;538switch (f) {539case DAY_OF_MONTH:540return ValueRange.of(1, 1, getMinimumMonthLength(), getMaximumMonthLength());541case DAY_OF_YEAR:542return ValueRange.of(1, getMaximumDayOfYear());543case ALIGNED_WEEK_OF_MONTH:544return ValueRange.of(1, 5);545case YEAR:546case YEAR_OF_ERA:547return ValueRange.of(getMinimumYear(), getMaximumYear());548case ERA:549return ValueRange.of(1, 1);550default:551return field.range();552}553}554return field.range();555}556557//-----------------------------------------------------------------------558@Override // override for return type559public HijrahDate resolveDate(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {560return (HijrahDate) super.resolveDate(fieldValues, resolverStyle);561}562563//-----------------------------------------------------------------------564/**565* Check the validity of a year.566*567* @param prolepticYear the year to check568*/569int checkValidYear(long prolepticYear) {570if (prolepticYear < getMinimumYear() || prolepticYear > getMaximumYear()) {571throw new DateTimeException("Invalid Hijrah year: " + prolepticYear);572}573return (int) prolepticYear;574}575576void checkValidDayOfYear(int dayOfYear) {577if (dayOfYear < 1 || dayOfYear > getMaximumDayOfYear()) {578throw new DateTimeException("Invalid Hijrah day of year: " + dayOfYear);579}580}581582void checkValidMonth(int month) {583if (month < 1 || month > 12) {584throw new DateTimeException("Invalid Hijrah month: " + month);585}586}587588//-----------------------------------------------------------------------589/**590* Returns an array containing the Hijrah year, month and day591* computed from the epoch day.592*593* @param epochDay the EpochDay594* @return int[0] = YEAR, int[1] = MONTH, int[2] = DATE595*/596int[] getHijrahDateInfo(int epochDay) {597checkCalendarInit(); // ensure that the chronology is initialized598if (epochDay < minEpochDay || epochDay >= maxEpochDay) {599throw new DateTimeException("Hijrah date out of range");600}601602int epochMonth = epochDayToEpochMonth(epochDay);603int year = epochMonthToYear(epochMonth);604int month = epochMonthToMonth(epochMonth);605int day1 = epochMonthToEpochDay(epochMonth);606int date = epochDay - day1; // epochDay - dayOfEpoch(year, month);607608int dateInfo[] = new int[3];609dateInfo[0] = year;610dateInfo[1] = month + 1; // change to 1-based.611dateInfo[2] = date + 1; // change to 1-based.612return dateInfo;613}614615/**616* Return the epoch day computed from Hijrah year, month, and day.617*618* @param prolepticYear the year to represent, 0-origin619* @param monthOfYear the month-of-year to represent, 1-origin620* @param dayOfMonth the day-of-month to represent, 1-origin621* @return the epoch day622*/623long getEpochDay(int prolepticYear, int monthOfYear, int dayOfMonth) {624checkCalendarInit(); // ensure that the chronology is initialized625checkValidMonth(monthOfYear);626int epochMonth = yearToEpochMonth(prolepticYear) + (monthOfYear - 1);627if (epochMonth < 0 || epochMonth >= hijrahEpochMonthStartDays.length) {628throw new DateTimeException("Invalid Hijrah date, year: " +629prolepticYear + ", month: " + monthOfYear);630}631if (dayOfMonth < 1 || dayOfMonth > getMonthLength(prolepticYear, monthOfYear)) {632throw new DateTimeException("Invalid Hijrah day of month: " + dayOfMonth);633}634return epochMonthToEpochDay(epochMonth) + (dayOfMonth - 1);635}636637/**638* Returns day of year for the year and month.639*640* @param prolepticYear a proleptic year641* @param month a month, 1-origin642* @return the day of year, 1-origin643*/644int getDayOfYear(int prolepticYear, int month) {645return yearMonthToDayOfYear(prolepticYear, (month - 1));646}647648/**649* Returns month length for the year and month.650*651* @param prolepticYear a proleptic year652* @param monthOfYear a month, 1-origin.653* @return the length of the month654*/655int getMonthLength(int prolepticYear, int monthOfYear) {656int epochMonth = yearToEpochMonth(prolepticYear) + (monthOfYear - 1);657if (epochMonth < 0 || epochMonth >= hijrahEpochMonthStartDays.length) {658throw new DateTimeException("Invalid Hijrah date, year: " +659prolepticYear + ", month: " + monthOfYear);660}661return epochMonthLength(epochMonth);662}663664/**665* Returns year length.666* Note: The 12th month must exist in the data.667*668* @param prolepticYear a proleptic year669* @return year length in days670*/671int getYearLength(int prolepticYear) {672return yearMonthToDayOfYear(prolepticYear, 12);673}674675/**676* Return the minimum supported Hijrah year.677*678* @return the minimum679*/680int getMinimumYear() {681return epochMonthToYear(0);682}683684/**685* Return the maximum supported Hijrah year.686*687* @return the minimum688*/689int getMaximumYear() {690return epochMonthToYear(hijrahEpochMonthStartDays.length - 1) - 1;691}692693/**694* Returns maximum day-of-month.695*696* @return maximum day-of-month697*/698int getMaximumMonthLength() {699return maxMonthLength;700}701702/**703* Returns smallest maximum day-of-month.704*705* @return smallest maximum day-of-month706*/707int getMinimumMonthLength() {708return minMonthLength;709}710711/**712* Returns maximum day-of-year.713*714* @return maximum day-of-year715*/716int getMaximumDayOfYear() {717return maxYearLength;718}719720/**721* Returns smallest maximum day-of-year.722*723* @return smallest maximum day-of-year724*/725int getSmallestMaximumDayOfYear() {726return minYearLength;727}728729/**730* Returns the epochMonth found by locating the epochDay in the table. The731* epochMonth is the index in the table732*733* @param epochDay734* @return The index of the element of the start of the month containing the735* epochDay.736*/737private int epochDayToEpochMonth(int epochDay) {738// binary search739int ndx = Arrays.binarySearch(hijrahEpochMonthStartDays, epochDay);740if (ndx < 0) {741ndx = -ndx - 2;742}743return ndx;744}745746/**747* Returns the year computed from the epochMonth748*749* @param epochMonth the epochMonth750* @return the Hijrah Year751*/752private int epochMonthToYear(int epochMonth) {753return (epochMonth + hijrahStartEpochMonth) / 12;754}755756/**757* Returns the epochMonth for the Hijrah Year.758*759* @param year the HijrahYear760* @return the epochMonth for the beginning of the year.761*/762private int yearToEpochMonth(int year) {763return (year * 12) - hijrahStartEpochMonth;764}765766/**767* Returns the Hijrah month from the epochMonth.768*769* @param epochMonth the epochMonth770* @return the month of the Hijrah Year771*/772private int epochMonthToMonth(int epochMonth) {773return (epochMonth + hijrahStartEpochMonth) % 12;774}775776/**777* Returns the epochDay for the start of the epochMonth.778*779* @param epochMonth the epochMonth780* @return the epochDay for the start of the epochMonth.781*/782private int epochMonthToEpochDay(int epochMonth) {783return hijrahEpochMonthStartDays[epochMonth];784785}786787/**788* Returns the day of year for the requested HijrahYear and month.789*790* @param prolepticYear the Hijrah year791* @param month the Hijrah month792* @return the day of year for the start of the month of the year793*/794private int yearMonthToDayOfYear(int prolepticYear, int month) {795int epochMonthFirst = yearToEpochMonth(prolepticYear);796return epochMonthToEpochDay(epochMonthFirst + month)797- epochMonthToEpochDay(epochMonthFirst);798}799800/**801* Returns the length of the epochMonth. It is computed from the start of802* the following month minus the start of the requested month.803*804* @param epochMonth the epochMonth; assumed to be within range805* @return the length in days of the epochMonth806*/807private int epochMonthLength(int epochMonth) {808// The very last entry in the epochMonth table is not the start of a month809return hijrahEpochMonthStartDays[epochMonth + 1]810- hijrahEpochMonthStartDays[epochMonth];811}812813//-----------------------------------------------------------------------814private static final String KEY_ID = "id";815private static final String KEY_TYPE = "type";816private static final String KEY_VERSION = "version";817private static final String KEY_ISO_START = "iso-start";818private static final Path CONF_PATH;819820/**821* Return the configuration properties from the resource.822* <p>823* The location of the variant configuration resource is:824* <pre>825* "/java/time/chrono/" (for "islamic-umalqura" type), or826* "<JAVA_HOME>/conf/chronology/" +827* "hijrah-config-" + chronologyId + "_" + calendarType + ".properties"828* </pre>829*830* @param chronologyId the chronology ID of the calendar variant831* @param calendarType the calendarType of the calendar variant832* @return a Properties containing the properties read from the resource.833* @throws Exception if access to the property resource fails834*/835private static Properties readConfigProperties(final String chronologyId, final String calendarType) throws Exception {836String resourceName = RESOURCE_PREFIX + chronologyId + "_" + calendarType + RESOURCE_SUFFIX;837PrivilegedAction<InputStream> getResourceAction = calendarType.equals("islamic-umalqura") ?838() -> HijrahChronology.class.getResourceAsStream(resourceName) :839() -> {840try {841return Files.newInputStream(CONF_PATH.resolve(resourceName),842StandardOpenOption.READ);843} catch (IOException e) {844throw new UncheckedIOException(e);845}846};847FilePermission perm1 = new FilePermission("<<ALL FILES>>", "read");848RuntimePermission perm2 = new RuntimePermission("accessSystemModules");849try (InputStream is = AccessController.doPrivileged(getResourceAction, null, perm1, perm2)) {850if (is == null) {851throw new RuntimeException("Hijrah calendar resource not found: " + resourceName);852}853Properties props = new Properties();854props.load(is);855return props;856}857}858859/**860* Loads and processes the Hijrah calendar properties file for this calendarType.861* The starting Hijrah date and the corresponding ISO date are862* extracted and used to calculate the epochDate offset.863* The version number is identified and ignored.864* Everything else is the data for a year with containing the length of each865* of 12 months.866*867* @throws DateTimeException if initialization of the calendar data from the868* resource fails869*/870private void loadCalendarData() {871try {872Properties props = readConfigProperties(typeId, calendarType);873874Map<Integer, int[]> years = new HashMap<>();875int minYear = Integer.MAX_VALUE;876int maxYear = Integer.MIN_VALUE;877String id = null;878String type = null;879String version = null;880int isoStart = 0;881for (Map.Entry<Object, Object> entry : props.entrySet()) {882String key = (String) entry.getKey();883switch (key) {884case KEY_ID:885id = (String)entry.getValue();886break;887case KEY_TYPE:888type = (String)entry.getValue();889break;890case KEY_VERSION:891version = (String)entry.getValue();892break;893case KEY_ISO_START: {894int[] ymd = parseYMD((String) entry.getValue());895isoStart = (int) LocalDate.of(ymd[0], ymd[1], ymd[2]).toEpochDay();896break;897}898default:899try {900// Everything else is either a year or invalid901int year = Integer.parseInt(key);902int[] months = parseMonths((String) entry.getValue());903years.put(year, months);904maxYear = Math.max(maxYear, year);905minYear = Math.min(minYear, year);906} catch (NumberFormatException nfe) {907throw new IllegalArgumentException("bad key: " + key);908}909}910}911912if (!getId().equals(id)) {913throw new IllegalArgumentException("Configuration is for a different calendar: " + id);914}915if (!getCalendarType().equals(type)) {916throw new IllegalArgumentException("Configuration is for a different calendar type: " + type);917}918if (version == null || version.isEmpty()) {919throw new IllegalArgumentException("Configuration does not contain a version");920}921if (isoStart == 0) {922throw new IllegalArgumentException("Configuration does not contain a ISO start date");923}924925// Now create and validate the array of epochDays indexed by epochMonth926hijrahStartEpochMonth = minYear * 12;927minEpochDay = isoStart;928hijrahEpochMonthStartDays = createEpochMonths(minEpochDay, minYear, maxYear, years);929maxEpochDay = hijrahEpochMonthStartDays[hijrahEpochMonthStartDays.length - 1];930931// Compute the min and max year length in days.932for (int year = minYear; year < maxYear; year++) {933int length = getYearLength(year);934minYearLength = Math.min(minYearLength, length);935maxYearLength = Math.max(maxYearLength, length);936}937} catch (Exception ex) {938// Log error and throw a DateTimeException939PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono");940logger.severe("Unable to initialize Hijrah calendar proxy: " + typeId, ex);941throw new DateTimeException("Unable to initialize HijrahCalendar: " + typeId, ex);942}943}944945/**946* Converts the map of year to month lengths ranging from minYear to maxYear947* into a linear contiguous array of epochDays. The index is the hijrahMonth948* computed from year and month and offset by minYear. The value of each949* entry is the epochDay corresponding to the first day of the month.950*951* @param minYear The minimum year for which data is provided952* @param maxYear The maximum year for which data is provided953* @param years a Map of year to the array of 12 month lengths954* @return array of epochDays for each month from min to max955*/956private int[] createEpochMonths(int epochDay, int minYear, int maxYear, Map<Integer, int[]> years) {957// Compute the size for the array of dates958int numMonths = (maxYear - minYear + 1) * 12 + 1;959960// Initialize the running epochDay as the corresponding ISO Epoch day961int epochMonth = 0; // index into array of epochMonths962int[] epochMonths = new int[numMonths];963minMonthLength = Integer.MAX_VALUE;964maxMonthLength = Integer.MIN_VALUE;965966// Only whole years are valid, any zero's in the array are illegal967for (int year = minYear; year <= maxYear; year++) {968int[] months = years.get(year);// must not be gaps969for (int month = 0; month < 12; month++) {970int length = months[month];971epochMonths[epochMonth++] = epochDay;972973if (length < 29 || length > 32) {974throw new IllegalArgumentException("Invalid month length in year: " + minYear);975}976epochDay += length;977minMonthLength = Math.min(minMonthLength, length);978maxMonthLength = Math.max(maxMonthLength, length);979}980}981982// Insert the final epochDay983epochMonths[epochMonth++] = epochDay;984985if (epochMonth != epochMonths.length) {986throw new IllegalStateException("Did not fill epochMonths exactly: ndx = " + epochMonth987+ " should be " + epochMonths.length);988}989990return epochMonths;991}992993/**994* Parses the 12 months lengths from a property value for a specific year.995*996* @param line the value of a year property997* @return an array of int[12] containing the 12 month lengths998* @throws IllegalArgumentException if the number of months is not 12999* @throws NumberFormatException if the 12 tokens are not numbers1000*/1001private int[] parseMonths(String line) {1002int[] months = new int[12];1003String[] numbers = line.split("\\s");1004if (numbers.length != 12) {1005throw new IllegalArgumentException("wrong number of months on line: " + Arrays.toString(numbers) + "; count: " + numbers.length);1006}1007for (int i = 0; i < 12; i++) {1008try {1009months[i] = Integer.parseInt(numbers[i]);1010} catch (NumberFormatException nfe) {1011throw new IllegalArgumentException("bad key: " + numbers[i]);1012}1013}1014return months;1015}10161017/**1018* Parse yyyy-MM-dd into a 3 element array [yyyy, mm, dd].1019*1020* @param string the input string1021* @return the 3 element array with year, month, day1022*/1023private int[] parseYMD(String string) {1024// yyyy-MM-dd1025string = string.trim();1026try {1027if (string.charAt(4) != '-' || string.charAt(7) != '-') {1028throw new IllegalArgumentException("date must be yyyy-MM-dd");1029}1030int[] ymd = new int[3];1031ymd[0] = Integer.parseInt(string, 0, 4, 10);1032ymd[1] = Integer.parseInt(string, 5, 7, 10);1033ymd[2] = Integer.parseInt(string, 8, 10, 10);1034return ymd;1035} catch (NumberFormatException ex) {1036throw new IllegalArgumentException("date must be yyyy-MM-dd", ex);1037}1038}10391040/**1041* Look for Hijrah chronology variant properties files in1042* <JAVA_HOME>/conf/chronology directory. Then register its chronology, if any.1043*/1044private static void registerCustomChrono() {1045AccessController.doPrivileged(1046(PrivilegedAction<Void>)() -> {1047if (Files.isDirectory(CONF_PATH)) {1048try {1049Files.list(CONF_PATH)1050.map(p -> p.getFileName().toString())1051.filter(fn -> fn.matches("hijrah-config-[^\\.]+\\.properties"))1052.map(fn -> fn.replaceAll("(hijrah-config-|\\.properties)", ""))1053.forEach(idtype -> {1054int delimiterPos = idtype.indexOf('_');1055// '_' should be somewhere in the middle of idtype1056if (delimiterPos > 1 && delimiterPos < idtype.length() - 1) {1057AbstractChronology.registerChrono(1058new HijrahChronology(1059idtype.substring(0, delimiterPos),1060idtype.substring(delimiterPos + 1)));1061} else {1062PlatformLogger.getLogger("java.time.chrono")1063.warning("Hijrah custom config init failed." +1064"'<id>_<type>' name convention not followed: " + idtype);1065}1066});1067} catch (IOException e) {1068PlatformLogger.getLogger("java.time.chrono")1069.warning("Hijrah custom config init failed.", e);1070}1071}1072return null;1073},1074null,1075new FilePermission("<<ALL FILES>>", "read"));1076}10771078//-----------------------------------------------------------------------1079/**1080* Writes the Chronology using a1081* <a href="{@docRoot}/serialized-form.html#java.time.chrono.Ser">dedicated serialized form</a>.1082* @serialData1083* <pre>1084* out.writeByte(1); // identifies a Chronology1085* out.writeUTF(getId());1086* </pre>1087*1088* @return the instance of {@code Ser}, not null1089*/1090@Override1091@java.io.Serial1092Object writeReplace() {1093return super.writeReplace();1094}10951096/**1097* Defend against malicious streams.1098*1099* @param s the stream to read1100* @throws InvalidObjectException always1101*/1102@java.io.Serial1103private void readObject(ObjectInputStream s) throws InvalidObjectException {1104throw new InvalidObjectException("Deserialization via serialization delegate");1105}1106}110711081109