1 /*
2  * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 /*
27  * Copyright (c) 2012, Stephen Colebourne & Michael Nascimento Santos
28  *
29  * All rights reserved.
30  *
31  * Redistribution and use in source and binary forms, with or without
32  * modification, are permitted provided that the following conditions are met:
33  *
34  *  * Redistributions of source code must retain the above copyright notice,
35  *    this list of conditions and the following disclaimer.
36  *
37  *  * Redistributions in binary form must reproduce the above copyright notice,
38  *    this list of conditions and the following disclaimer in the documentation
39  *    and/or other materials provided with the distribution.
40  *
41  *  * Neither the name of JSR-310 nor the names of its contributors
42  *    may be used to endorse or promote products derived from this software
43  *    without specific prior written permission.
44  *
45  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
46  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
47  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
48  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
49  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
50  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
51  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
52  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
53  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
54  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
55  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
56  */
57 
58 package java.time.chrono;
59 
60 import static java.time.temporal.ChronoField.EPOCH_DAY;
61 
62 import java.io.File;
63 import java.io.FileInputStream;
64 import java.io.IOException;
65 import java.io.InputStream;
66 import java.io.InvalidObjectException;
67 import java.io.ObjectInputStream;
68 import java.io.Serializable;
69 import java.security.AccessController;
70 import java.security.PrivilegedActionException;
71 import java.time.Clock;
72 import java.time.DateTimeException;
73 import java.time.Instant;
74 import java.time.LocalDate;
75 import java.time.ZoneId;
76 import java.time.format.ResolverStyle;
77 import java.time.temporal.ChronoField;
78 import java.time.temporal.TemporalAccessor;
79 import java.time.temporal.TemporalField;
80 import java.time.temporal.ValueRange;
81 import java.util.Arrays;
82 import java.util.HashMap;
83 import java.util.List;
84 import java.util.Map;
85 import java.util.Objects;
86 import java.util.Properties;
87 
88 import sun.util.logging.PlatformLogger;
89 
90 /**
91  * The Hijrah calendar is a lunar calendar supporting Islamic calendars.
92  * <p>
93  * The HijrahChronology follows the rules of the Hijrah calendar system. The Hijrah
94  * calendar has several variants based on differences in when the new moon is
95  * determined to have occurred and where the observation is made.
96  * In some variants the length of each month is
97  * computed algorithmically from the astronomical data for the moon and earth and
98  * in others the length of the month is determined by an authorized sighting
99  * of the new moon. For the algorithmically based calendars the calendar
100  * can project into the future.
101  * For sighting based calendars only historical data from past
102  * sightings is available.
103  * <p>
104  * The length of each month is 29 or 30 days.
105  * Ordinary years have 354 days; leap years have 355 days.
106  *
107  * <p>
108  * CLDR and LDML identify variants:
109  * <table cellpadding="2" summary="Variants of Hijrah Calendars">
110  * <thead>
111  * <tr class="tableSubHeadingColor">
112  * <th class="colFirst" align="left" >Chronology ID</th>
113  * <th class="colFirst" align="left" >Calendar Type</th>
114  * <th class="colFirst" align="left" >Locale extension, see {@link java.util.Locale}</th>
115  * <th class="colLast" align="left" >Description</th>
116  * </tr>
117  * </thead>
118  * <tbody>
119  * <tr class="altColor">
120  * <td>Hijrah-umalqura</td>
121  * <td>islamic-umalqura</td>
122  * <td>ca-islamic-umalqura</td>
123  * <td>Islamic - Umm Al-Qura calendar of Saudi Arabia</td>
124  * </tr>
125  * </tbody>
126  * </table>
127  * <p>Additional variants may be available through {@link Chronology#getAvailableChronologies()}.
128  *
129  * <p>Example</p>
130  * <p>
131  * Selecting the chronology from the locale uses {@link Chronology#ofLocale}
132  * to find the Chronology based on Locale supported BCP 47 extension mechanism
133  * to request a specific calendar ("ca"). For example,
134  * </p>
135  * <pre>
136  *      Locale locale = Locale.forLanguageTag("en-US-u-ca-islamic-umalqura");
137  *      Chronology chrono = Chronology.ofLocale(locale);
138  * </pre>
139  *
140  * @implSpec
141  * This class is immutable and thread-safe.
142  *
143  * @implNote
144  * Each Hijrah variant is configured individually. Each variant is defined by a
145  * property resource that defines the {@code ID}, the {@code calendar type},
146  * the start of the calendar, the alignment with the
147  * ISO calendar, and the length of each month for a range of years.
148  * The variants are identified in the {@code calendars.properties} file.
149  * The new properties are prefixed with {@code "calendars.hijrah."}:
150  * <table cellpadding="2" border="0" summary="Configuration of Hijrah Calendar Variants">
151  * <thead>
152  * <tr class="tableSubHeadingColor">
153  * <th class="colFirst" align="left">Property Name</th>
154  * <th class="colFirst" align="left">Property value</th>
155  * <th class="colLast" align="left">Description </th>
156  * </tr>
157  * </thead>
158  * <tbody>
159  * <tr class="altColor">
160  * <td>calendars.hijrah.{ID}</td>
161  * <td>The property resource defining the {@code {ID}} variant</td>
162  * <td>The property resource is located with the {@code calendars.properties} file</td>
163  * </tr>
164  * <tr class="rowColor">
165  * <td>calendars.hijrah.{ID}.type</td>
166  * <td>The calendar type</td>
167  * <td>LDML defines the calendar type names</td>
168  * </tr>
169  * </tbody>
170  * </table>
171  * <p>
172  * The Hijrah property resource is a set of properties that describe the calendar.
173  * The syntax is defined by {@code java.util.Properties#load(Reader)}.
174  * <table cellpadding="2" summary="Configuration of Hijrah Calendar">
175  * <thead>
176  * <tr class="tableSubHeadingColor">
177  * <th class="colFirst" align="left" > Property Name</th>
178  * <th class="colFirst" align="left" > Property value</th>
179  * <th class="colLast" align="left" > Description </th>
180  * </tr>
181  * </thead>
182  * <tbody>
183  * <tr class="altColor">
184  * <td>id</td>
185  * <td>Chronology Id, for example, "Hijrah-umalqura"</td>
186  * <td>The Id of the calendar in common usage</td>
187  * </tr>
188  * <tr class="rowColor">
189  * <td>type</td>
190  * <td>Calendar type, for example, "islamic-umalqura"</td>
191  * <td>LDML defines the calendar types</td>
192  * </tr>
193  * <tr class="altColor">
194  * <td>version</td>
195  * <td>Version, for example: "1.8.0_1"</td>
196  * <td>The version of the Hijrah variant data</td>
197  * </tr>
198  * <tr class="rowColor">
199  * <td>iso-start</td>
200  * <td>ISO start date, formatted as {@code yyyy-MM-dd}, for example: "1900-04-30"</td>
201  * <td>The ISO date of the first day of the minimum Hijrah year.</td>
202  * </tr>
203  * <tr class="altColor">
204  * <td>yyyy - a numeric 4 digit year, for example "1434"</td>
205  * <td>The value is a sequence of 12 month lengths,
206  * for example: "29 30 29 30 29 30 30 30 29 30 29 29"</td>
207  * <td>The lengths of the 12 months of the year separated by whitespace.
208  * A numeric year property must be present for every year without any gaps.
209  * The month lengths must be between 29-32 inclusive.
210  * </td>
211  * </tr>
212  * </tbody>
213  * </table>
214  *
215  * @since 1.8
216  */
217 public final class HijrahChronology extends AbstractChronology implements Serializable {
218 
219     /**
220      * The Hijrah Calendar id.
221      */
222     private final transient String typeId;
223     /**
224      * The Hijrah calendarType.
225      */
226     private final transient String calendarType;
227     /**
228      * Serialization version.
229      */
230     private static final long serialVersionUID = 3127340209035924785L;
231     /**
232      * Singleton instance of the Islamic Umm Al-Qura calendar of Saudi Arabia.
233      * Other Hijrah chronology variants may be available from
234      * {@link Chronology#getAvailableChronologies}.
235      */
236     public static final HijrahChronology INSTANCE;
237     /**
238      * Flag to indicate the initialization of configuration data is complete.
239      * @see #checkCalendarInit()
240      */
241     private transient volatile boolean initComplete;
242     /**
243      * Array of epoch days indexed by Hijrah Epoch month.
244      * Computed by {@link #loadCalendarData}.
245      */
246     private transient int[] hijrahEpochMonthStartDays;
247     /**
248      * The minimum epoch day of this Hijrah calendar.
249      * Computed by {@link #loadCalendarData}.
250      */
251     private transient int minEpochDay;
252     /**
253      * The maximum epoch day for which calendar data is available.
254      * Computed by {@link #loadCalendarData}.
255      */
256     private transient int maxEpochDay;
257     /**
258      * The minimum epoch month.
259      * Computed by {@link #loadCalendarData}.
260      */
261     private transient int hijrahStartEpochMonth;
262     /**
263      * The minimum length of a month.
264      * Computed by {@link #createEpochMonths}.
265      */
266     private transient int minMonthLength;
267     /**
268      * The maximum length of a month.
269      * Computed by {@link #createEpochMonths}.
270      */
271     private transient int maxMonthLength;
272     /**
273      * The minimum length of a year in days.
274      * Computed by {@link #createEpochMonths}.
275      */
276     private transient int minYearLength;
277     /**
278      * The maximum length of a year in days.
279      * Computed by {@link #createEpochMonths}.
280      */
281     private transient int maxYearLength;
282     /**
283      * A reference to the properties stored in
284      * ${java.home}/lib/calendars.properties
285      */
286     private final transient static Properties calendarProperties;
287 
288     /**
289      * Prefix of property names for Hijrah calendar variants.
290      */
291     private static final String PROP_PREFIX = "calendar.hijrah.";
292     /**
293      * Suffix of property names containing the calendar type of a variant.
294      */
295     private static final String PROP_TYPE_SUFFIX = ".type";
296 
297     /**
298      * Static initialization of the predefined calendars found in the
299      * lib/calendars.properties file.
300      */
301     static {
302         try {
303             calendarProperties = sun.util.calendar.BaseCalendar.getCalendarProperties();
304         } catch (IOException ioe) {
305             throw new InternalError("Can't initialize lib/calendars.properties", ioe);
306         }
307 
308         try {
309             INSTANCE = new HijrahChronology("Hijrah-umalqura");
310             // Register it by its aliases
AbstractChronology.registerChrono(INSTANCE, "Hijrah")311             AbstractChronology.registerChrono(INSTANCE, "Hijrah");
AbstractChronology.registerChrono(INSTANCE, "islamic")312             AbstractChronology.registerChrono(INSTANCE, "islamic");
313         } catch (DateTimeException ex) {
314             // Absence of Hijrah calendar is fatal to initializing this class.
315             PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono");
316             logger.severe("Unable to initialize Hijrah calendar: Hijrah-umalqura", ex);
317             throw new RuntimeException("Unable to initialize Hijrah-umalqura calendar", ex.getCause());
318         }
registerVariants()319         registerVariants();
320     }
321 
322     /**
323      * For each Hijrah variant listed, create the HijrahChronology and register it.
324      * Exceptions during initialization are logged but otherwise ignored.
325      */
registerVariants()326     private static void registerVariants() {
327         for (String name : calendarProperties.stringPropertyNames()) {
328             if (name.startsWith(PROP_PREFIX)) {
329                 String id = name.substring(PROP_PREFIX.length());
330                 if (id.indexOf('.') >= 0) {
331                     continue;   // no name or not a simple name of a calendar
332                 }
333                 if (id.equals(INSTANCE.getId())) {
334                     continue;           // do not duplicate the default
335                 }
336                 try {
337                     // Create and register the variant
338                     HijrahChronology chrono = new HijrahChronology(id);
339                     AbstractChronology.registerChrono(chrono);
340                 } catch (DateTimeException ex) {
341                     // Log error and continue
342                     PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono");
343                     logger.severe("Unable to initialize Hijrah calendar: " + id, ex);
344                 }
345             }
346         }
347     }
348 
349     /**
350      * Create a HijrahChronology for the named variant.
351      * The resource and calendar type are retrieved from properties
352      * in the {@code calendars.properties}.
353      * The property names are {@code "calendar.hijrah." + id}
354      * and  {@code "calendar.hijrah." + id + ".type"}
355      * @param id the id of the calendar
356      * @throws DateTimeException if the calendar type is missing from the properties file.
357      * @throws IllegalArgumentException if the id is empty
358      */
HijrahChronology(String id)359     private HijrahChronology(String id) throws DateTimeException {
360         if (id.isEmpty()) {
361             throw new IllegalArgumentException("calendar id is empty");
362         }
363         String propName = PROP_PREFIX + id + PROP_TYPE_SUFFIX;
364         String calType = calendarProperties.getProperty(propName);
365         if (calType == null || calType.isEmpty()) {
366             throw new DateTimeException("calendarType is missing or empty for: " + propName);
367         }
368         this.typeId = id;
369         this.calendarType = calType;
370     }
371 
372     /**
373      * Check and ensure that the calendar data has been initialized.
374      * The initialization check is performed at the boundary between
375      * public and package methods.  If a public calls another public method
376      * a check is not necessary in the caller.
377      * The constructors of HijrahDate call {@link #getEpochDay} or
378      * {@link #getHijrahDateInfo} so every call from HijrahDate to a
379      * HijrahChronology via package private methods has been checked.
380      *
381      * @throws DateTimeException if the calendar data configuration is
382      *     malformed or IOExceptions occur loading the data
383      */
checkCalendarInit()384     private void checkCalendarInit() {
385         // Keep this short so it can be inlined for performance
386         if (initComplete == false) {
387             loadCalendarData();
388             initComplete = true;
389         }
390     }
391 
392     //-----------------------------------------------------------------------
393     /**
394      * Gets the ID of the chronology.
395      * <p>
396      * The ID uniquely identifies the {@code Chronology}. It can be used to
397      * lookup the {@code Chronology} using {@link Chronology#of(String)}.
398      *
399      * @return the chronology ID, non-null
400      * @see #getCalendarType()
401      */
402     @Override
getId()403     public String getId() {
404         return typeId;
405     }
406 
407     /**
408      * Gets the calendar type of the Islamic calendar.
409      * <p>
410      * The calendar type is an identifier defined by the
411      * <em>Unicode Locale Data Markup Language (LDML)</em> specification.
412      * It can be used to lookup the {@code Chronology} using {@link Chronology#of(String)}.
413      *
414      * @return the calendar system type; non-null if the calendar has
415      *    a standard type, otherwise null
416      * @see #getId()
417      */
418     @Override
getCalendarType()419     public String getCalendarType() {
420         return calendarType;
421     }
422 
423     //-----------------------------------------------------------------------
424     /**
425      * Obtains a local date in Hijrah calendar system from the
426      * era, year-of-era, month-of-year and day-of-month fields.
427      *
428      * @param era  the Hijrah era, not null
429      * @param yearOfEra  the year-of-era
430      * @param month  the month-of-year
431      * @param dayOfMonth  the day-of-month
432      * @return the Hijrah local date, not null
433      * @throws DateTimeException if unable to create the date
434      * @throws ClassCastException if the {@code era} is not a {@code HijrahEra}
435      */
436     @Override
date(Era era, int yearOfEra, int month, int dayOfMonth)437     public HijrahDate date(Era era, int yearOfEra, int month, int dayOfMonth) {
438         return date(prolepticYear(era, yearOfEra), month, dayOfMonth);
439     }
440 
441     /**
442      * Obtains a local date in Hijrah calendar system from the
443      * proleptic-year, month-of-year and day-of-month fields.
444      *
445      * @param prolepticYear  the proleptic-year
446      * @param month  the month-of-year
447      * @param dayOfMonth  the day-of-month
448      * @return the Hijrah local date, not null
449      * @throws DateTimeException if unable to create the date
450      */
451     @Override
date(int prolepticYear, int month, int dayOfMonth)452     public HijrahDate date(int prolepticYear, int month, int dayOfMonth) {
453         return HijrahDate.of(this, prolepticYear, month, dayOfMonth);
454     }
455 
456     /**
457      * Obtains a local date in Hijrah calendar system from the
458      * era, year-of-era and day-of-year fields.
459      *
460      * @param era  the Hijrah era, not null
461      * @param yearOfEra  the year-of-era
462      * @param dayOfYear  the day-of-year
463      * @return the Hijrah local date, not null
464      * @throws DateTimeException if unable to create the date
465      * @throws ClassCastException if the {@code era} is not a {@code HijrahEra}
466      */
467     @Override
dateYearDay(Era era, int yearOfEra, int dayOfYear)468     public HijrahDate dateYearDay(Era era, int yearOfEra, int dayOfYear) {
469         return dateYearDay(prolepticYear(era, yearOfEra), dayOfYear);
470     }
471 
472     /**
473      * Obtains a local date in Hijrah calendar system from the
474      * proleptic-year and day-of-year fields.
475      *
476      * @param prolepticYear  the proleptic-year
477      * @param dayOfYear  the day-of-year
478      * @return the Hijrah local date, not null
479      * @throws DateTimeException if the value of the year is out of range,
480      *  or if the day-of-year is invalid for the year
481      */
482     @Override
dateYearDay(int prolepticYear, int dayOfYear)483     public HijrahDate dateYearDay(int prolepticYear, int dayOfYear) {
484         HijrahDate date = HijrahDate.of(this, prolepticYear, 1, 1);
485         if (dayOfYear > date.lengthOfYear()) {
486             throw new DateTimeException("Invalid dayOfYear: " + dayOfYear);
487         }
488         return date.plusDays(dayOfYear - 1);
489     }
490 
491     /**
492      * Obtains a local date in the Hijrah calendar system from the epoch-day.
493      *
494      * @param epochDay  the epoch day
495      * @return the Hijrah local date, not null
496      * @throws DateTimeException if unable to create the date
497      */
498     @Override  // override with covariant return type
dateEpochDay(long epochDay)499     public HijrahDate dateEpochDay(long epochDay) {
500         return HijrahDate.ofEpochDay(this, epochDay);
501     }
502 
503     @Override
dateNow()504     public HijrahDate dateNow() {
505         return dateNow(Clock.systemDefaultZone());
506     }
507 
508     @Override
dateNow(ZoneId zone)509     public HijrahDate dateNow(ZoneId zone) {
510         return dateNow(Clock.system(zone));
511     }
512 
513     @Override
dateNow(Clock clock)514     public HijrahDate dateNow(Clock clock) {
515         return date(LocalDate.now(clock));
516     }
517 
518     @Override
date(TemporalAccessor temporal)519     public HijrahDate date(TemporalAccessor temporal) {
520         if (temporal instanceof HijrahDate) {
521             return (HijrahDate) temporal;
522         }
523         return HijrahDate.ofEpochDay(this, temporal.getLong(EPOCH_DAY));
524     }
525 
526     @Override
527     @SuppressWarnings("unchecked")
localDateTime(TemporalAccessor temporal)528     public ChronoLocalDateTime<HijrahDate> localDateTime(TemporalAccessor temporal) {
529         return (ChronoLocalDateTime<HijrahDate>) super.localDateTime(temporal);
530     }
531 
532     @Override
533     @SuppressWarnings("unchecked")
zonedDateTime(TemporalAccessor temporal)534     public ChronoZonedDateTime<HijrahDate> zonedDateTime(TemporalAccessor temporal) {
535         return (ChronoZonedDateTime<HijrahDate>) super.zonedDateTime(temporal);
536     }
537 
538     @Override
539     @SuppressWarnings("unchecked")
zonedDateTime(Instant instant, ZoneId zone)540     public ChronoZonedDateTime<HijrahDate> zonedDateTime(Instant instant, ZoneId zone) {
541         return (ChronoZonedDateTime<HijrahDate>) super.zonedDateTime(instant, zone);
542     }
543 
544     //-----------------------------------------------------------------------
545     @Override
isLeapYear(long prolepticYear)546     public boolean isLeapYear(long prolepticYear) {
547       checkCalendarInit();
548         if (prolepticYear < getMinimumYear() || prolepticYear > getMaximumYear()) {
549             return false;
550         }
551         int len = getYearLength((int) prolepticYear);
552         return (len > 354);
553     }
554 
555     @Override
prolepticYear(Era era, int yearOfEra)556     public int prolepticYear(Era era, int yearOfEra) {
557         if (era instanceof HijrahEra == false) {
558             throw new ClassCastException("Era must be HijrahEra");
559         }
560         return yearOfEra;
561     }
562 
563     @Override
eraOf(int eraValue)564     public HijrahEra eraOf(int eraValue) {
565         switch (eraValue) {
566             case 1:
567                 return HijrahEra.AH;
568             default:
569                 throw new DateTimeException("invalid Hijrah era");
570         }
571     }
572 
573     @Override
eras()574     public List<Era> eras() {
575         return Arrays.<Era>asList(HijrahEra.values());
576     }
577 
578     //-----------------------------------------------------------------------
579     @Override
range(ChronoField field)580     public ValueRange range(ChronoField field) {
581         checkCalendarInit();
582         if (field instanceof ChronoField) {
583             ChronoField f = field;
584             switch (f) {
585                 case DAY_OF_MONTH:
586                     return ValueRange.of(1, 1, getMinimumMonthLength(), getMaximumMonthLength());
587                 case DAY_OF_YEAR:
588                     return ValueRange.of(1, getMaximumDayOfYear());
589                 case ALIGNED_WEEK_OF_MONTH:
590                     return ValueRange.of(1, 5);
591                 case YEAR:
592                 case YEAR_OF_ERA:
593                     return ValueRange.of(getMinimumYear(), getMaximumYear());
594                 case ERA:
595                     return ValueRange.of(1, 1);
596                 default:
597                     return field.range();
598             }
599         }
600         return field.range();
601     }
602 
603     //-----------------------------------------------------------------------
604     @Override  // override for return type
resolveDate(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle)605     public HijrahDate resolveDate(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
606         return (HijrahDate) super.resolveDate(fieldValues, resolverStyle);
607     }
608 
609     //-----------------------------------------------------------------------
610     /**
611      * Check the validity of a year.
612      *
613      * @param prolepticYear the year to check
614      */
checkValidYear(long prolepticYear)615     int checkValidYear(long prolepticYear) {
616         if (prolepticYear < getMinimumYear() || prolepticYear > getMaximumYear()) {
617             throw new DateTimeException("Invalid Hijrah year: " + prolepticYear);
618         }
619         return (int) prolepticYear;
620     }
621 
checkValidDayOfYear(int dayOfYear)622     void checkValidDayOfYear(int dayOfYear) {
623         if (dayOfYear < 1 || dayOfYear > getMaximumDayOfYear()) {
624             throw new DateTimeException("Invalid Hijrah day of year: " + dayOfYear);
625         }
626     }
627 
checkValidMonth(int month)628     void checkValidMonth(int month) {
629         if (month < 1 || month > 12) {
630             throw new DateTimeException("Invalid Hijrah month: " + month);
631         }
632     }
633 
634     //-----------------------------------------------------------------------
635     /**
636      * Returns an array containing the Hijrah year, month and day
637      * computed from the epoch day.
638      *
639      * @param epochDay  the EpochDay
640      * @return int[0] = YEAR, int[1] = MONTH, int[2] = DATE
641      */
getHijrahDateInfo(int epochDay)642     int[] getHijrahDateInfo(int epochDay) {
643         checkCalendarInit();    // ensure that the chronology is initialized
644         if (epochDay < minEpochDay || epochDay >= maxEpochDay) {
645             throw new DateTimeException("Hijrah date out of range");
646         }
647 
648         int epochMonth = epochDayToEpochMonth(epochDay);
649         int year = epochMonthToYear(epochMonth);
650         int month = epochMonthToMonth(epochMonth);
651         int day1 = epochMonthToEpochDay(epochMonth);
652         int date = epochDay - day1; // epochDay - dayOfEpoch(year, month);
653 
654         int dateInfo[] = new int[3];
655         dateInfo[0] = year;
656         dateInfo[1] = month + 1; // change to 1-based.
657         dateInfo[2] = date + 1; // change to 1-based.
658         return dateInfo;
659     }
660 
661     /**
662      * Return the epoch day computed from Hijrah year, month, and day.
663      *
664      * @param prolepticYear the year to represent, 0-origin
665      * @param monthOfYear the month-of-year to represent, 1-origin
666      * @param dayOfMonth the day-of-month to represent, 1-origin
667      * @return the epoch day
668      */
getEpochDay(int prolepticYear, int monthOfYear, int dayOfMonth)669     long getEpochDay(int prolepticYear, int monthOfYear, int dayOfMonth) {
670         checkCalendarInit();    // ensure that the chronology is initialized
671         checkValidMonth(monthOfYear);
672         int epochMonth = yearToEpochMonth(prolepticYear) + (monthOfYear - 1);
673         if (epochMonth < 0 || epochMonth >= hijrahEpochMonthStartDays.length) {
674             throw new DateTimeException("Invalid Hijrah date, year: " +
675                     prolepticYear +  ", month: " + monthOfYear);
676         }
677         if (dayOfMonth < 1 || dayOfMonth > getMonthLength(prolepticYear, monthOfYear)) {
678             throw new DateTimeException("Invalid Hijrah day of month: " + dayOfMonth);
679         }
680         return epochMonthToEpochDay(epochMonth) + (dayOfMonth - 1);
681     }
682 
683     /**
684      * Returns day of year for the year and month.
685      *
686      * @param prolepticYear a proleptic year
687      * @param month a month, 1-origin
688      * @return the day of year, 1-origin
689      */
getDayOfYear(int prolepticYear, int month)690     int getDayOfYear(int prolepticYear, int month) {
691         return yearMonthToDayOfYear(prolepticYear, (month - 1));
692     }
693 
694     /**
695      * Returns month length for the year and month.
696      *
697      * @param prolepticYear a proleptic year
698      * @param monthOfYear a month, 1-origin.
699      * @return the length of the month
700      */
getMonthLength(int prolepticYear, int monthOfYear)701     int getMonthLength(int prolepticYear, int monthOfYear) {
702         int epochMonth = yearToEpochMonth(prolepticYear) + (monthOfYear - 1);
703         if (epochMonth < 0 || epochMonth >= hijrahEpochMonthStartDays.length) {
704             throw new DateTimeException("Invalid Hijrah date, year: " +
705                     prolepticYear +  ", month: " + monthOfYear);
706         }
707         return epochMonthLength(epochMonth);
708     }
709 
710     /**
711      * Returns year length.
712      * Note: The 12th month must exist in the data.
713      *
714      * @param prolepticYear a proleptic year
715      * @return year length in days
716      */
getYearLength(int prolepticYear)717     int getYearLength(int prolepticYear) {
718         return yearMonthToDayOfYear(prolepticYear, 12);
719     }
720 
721     /**
722      * Return the minimum supported Hijrah year.
723      *
724      * @return the minimum
725      */
getMinimumYear()726     int getMinimumYear() {
727         return epochMonthToYear(0);
728     }
729 
730     /**
731      * Return the maximum supported Hijrah ear.
732      *
733      * @return the minimum
734      */
getMaximumYear()735     int getMaximumYear() {
736         return epochMonthToYear(hijrahEpochMonthStartDays.length - 1) - 1;
737     }
738 
739     /**
740      * Returns maximum day-of-month.
741      *
742      * @return maximum day-of-month
743      */
getMaximumMonthLength()744     int getMaximumMonthLength() {
745         return maxMonthLength;
746     }
747 
748     /**
749      * Returns smallest maximum day-of-month.
750      *
751      * @return smallest maximum day-of-month
752      */
getMinimumMonthLength()753     int getMinimumMonthLength() {
754         return minMonthLength;
755     }
756 
757     /**
758      * Returns maximum day-of-year.
759      *
760      * @return maximum day-of-year
761      */
getMaximumDayOfYear()762     int getMaximumDayOfYear() {
763         return maxYearLength;
764     }
765 
766     /**
767      * Returns smallest maximum day-of-year.
768      *
769      * @return smallest maximum day-of-year
770      */
getSmallestMaximumDayOfYear()771     int getSmallestMaximumDayOfYear() {
772         return minYearLength;
773     }
774 
775     /**
776      * Returns the epochMonth found by locating the epochDay in the table. The
777      * epochMonth is the index in the table
778      *
779      * @param epochDay
780      * @return The index of the element of the start of the month containing the
781      * epochDay.
782      */
epochDayToEpochMonth(int epochDay)783     private int epochDayToEpochMonth(int epochDay) {
784         // binary search
785         int ndx = Arrays.binarySearch(hijrahEpochMonthStartDays, epochDay);
786         if (ndx < 0) {
787             ndx = -ndx - 2;
788         }
789         return ndx;
790     }
791 
792     /**
793      * Returns the year computed from the epochMonth
794      *
795      * @param epochMonth the epochMonth
796      * @return the Hijrah Year
797      */
epochMonthToYear(int epochMonth)798     private int epochMonthToYear(int epochMonth) {
799         return (epochMonth + hijrahStartEpochMonth) / 12;
800     }
801 
802     /**
803      * Returns the epochMonth for the Hijrah Year.
804      *
805      * @param year the HijrahYear
806      * @return the epochMonth for the beginning of the year.
807      */
yearToEpochMonth(int year)808     private int yearToEpochMonth(int year) {
809         return (year * 12) - hijrahStartEpochMonth;
810     }
811 
812     /**
813      * Returns the Hijrah month from the epochMonth.
814      *
815      * @param epochMonth the epochMonth
816      * @return the month of the Hijrah Year
817      */
epochMonthToMonth(int epochMonth)818     private int epochMonthToMonth(int epochMonth) {
819         return (epochMonth + hijrahStartEpochMonth) % 12;
820     }
821 
822     /**
823      * Returns the epochDay for the start of the epochMonth.
824      *
825      * @param epochMonth the epochMonth
826      * @return the epochDay for the start of the epochMonth.
827      */
epochMonthToEpochDay(int epochMonth)828     private int epochMonthToEpochDay(int epochMonth) {
829         return hijrahEpochMonthStartDays[epochMonth];
830 
831     }
832 
833     /**
834      * Returns the day of year for the requested HijrahYear and month.
835      *
836      * @param prolepticYear the Hijrah year
837      * @param month the Hijrah month
838      * @return the day of year for the start of the month of the year
839      */
yearMonthToDayOfYear(int prolepticYear, int month)840     private int yearMonthToDayOfYear(int prolepticYear, int month) {
841         int epochMonthFirst = yearToEpochMonth(prolepticYear);
842         return epochMonthToEpochDay(epochMonthFirst + month)
843                 - epochMonthToEpochDay(epochMonthFirst);
844     }
845 
846     /**
847      * Returns the length of the epochMonth. It is computed from the start of
848      * the following month minus the start of the requested month.
849      *
850      * @param epochMonth the epochMonth; assumed to be within range
851      * @return the length in days of the epochMonth
852      */
epochMonthLength(int epochMonth)853     private int epochMonthLength(int epochMonth) {
854         // The very last entry in the epochMonth table is not the start of a month
855         return hijrahEpochMonthStartDays[epochMonth + 1]
856                 - hijrahEpochMonthStartDays[epochMonth];
857     }
858 
859     //-----------------------------------------------------------------------
860     private static final String KEY_ID = "id";
861     private static final String KEY_TYPE = "type";
862     private static final String KEY_VERSION = "version";
863     private static final String KEY_ISO_START = "iso-start";
864 
865     /**
866      * Return the configuration properties from the resource.
867      * <p>
868      * The default location of the variant configuration resource is:
869      * <pre>
870      *   "$java.home/lib/" + resource-name
871      * </pre>
872      *
873      * @param resource the name of the calendar property resource
874      * @return a Properties containing the properties read from the resource.
875      * @throws Exception if access to the property resource fails
876      */
readConfigProperties(final String resource)877     private static Properties readConfigProperties(final String resource) throws Exception {
878         // Android-changed: Load system resources.
879         Properties props = new Properties();
880         try (InputStream is = ClassLoader.getSystemResourceAsStream(resource)) {
881             props.load(is);
882         }
883         return props;
884     }
885 
886     /**
887      * Loads and processes the Hijrah calendar properties file for this calendarType.
888      * The starting Hijrah date and the corresponding ISO date are
889      * extracted and used to calculate the epochDate offset.
890      * The version number is identified and ignored.
891      * Everything else is the data for a year with containing the length of each
892      * of 12 months.
893      *
894      * @throws DateTimeException if initialization of the calendar data from the
895      *     resource fails
896      */
loadCalendarData()897     private void loadCalendarData() {
898         try {
899             String resourceName = calendarProperties.getProperty(PROP_PREFIX + typeId);
900             Objects.requireNonNull(resourceName, "Resource missing for calendar: " + PROP_PREFIX + typeId);
901             Properties props = readConfigProperties(resourceName);
902 
903             Map<Integer, int[]> years = new HashMap<>();
904             int minYear = Integer.MAX_VALUE;
905             int maxYear = Integer.MIN_VALUE;
906             String id = null;
907             String type = null;
908             String version = null;
909             int isoStart = 0;
910             for (Map.Entry<Object, Object> entry : props.entrySet()) {
911                 String key = (String) entry.getKey();
912                 switch (key) {
913                     case KEY_ID:
914                         id = (String)entry.getValue();
915                         break;
916                     case KEY_TYPE:
917                         type = (String)entry.getValue();
918                         break;
919                     case KEY_VERSION:
920                         version = (String)entry.getValue();
921                         break;
922                     case KEY_ISO_START: {
923                         int[] ymd = parseYMD((String) entry.getValue());
924                         isoStart = (int) LocalDate.of(ymd[0], ymd[1], ymd[2]).toEpochDay();
925                         break;
926                     }
927                     default:
928                         try {
929                             // Everything else is either a year or invalid
930                             int year = Integer.valueOf(key);
931                             int[] months = parseMonths((String) entry.getValue());
932                             years.put(year, months);
933                             maxYear = Math.max(maxYear, year);
934                             minYear = Math.min(minYear, year);
935                         } catch (NumberFormatException nfe) {
936                             throw new IllegalArgumentException("bad key: " + key);
937                         }
938                 }
939             }
940 
941             if (!getId().equals(id)) {
942                 throw new IllegalArgumentException("Configuration is for a different calendar: " + id);
943             }
944             if (!getCalendarType().equals(type)) {
945                 throw new IllegalArgumentException("Configuration is for a different calendar type: " + type);
946             }
947             if (version == null || version.isEmpty()) {
948                 throw new IllegalArgumentException("Configuration does not contain a version");
949             }
950             if (isoStart == 0) {
951                 throw new IllegalArgumentException("Configuration does not contain a ISO start date");
952             }
953 
954             // Now create and validate the array of epochDays indexed by epochMonth
955             hijrahStartEpochMonth = minYear * 12;
956             minEpochDay = isoStart;
957             hijrahEpochMonthStartDays = createEpochMonths(minEpochDay, minYear, maxYear, years);
958             maxEpochDay = hijrahEpochMonthStartDays[hijrahEpochMonthStartDays.length - 1];
959 
960             // Compute the min and max year length in days.
961             for (int year = minYear; year < maxYear; year++) {
962                 int length = getYearLength(year);
963                 minYearLength = Math.min(minYearLength, length);
964                 maxYearLength = Math.max(maxYearLength, length);
965             }
966         } catch (Exception ex) {
967             // Log error and throw a DateTimeException
968             PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono");
969             logger.severe("Unable to initialize Hijrah calendar proxy: " + typeId, ex);
970             throw new DateTimeException("Unable to initialize HijrahCalendar: " + typeId, ex);
971         }
972     }
973 
974     /**
975      * Converts the map of year to month lengths ranging from minYear to maxYear
976      * into a linear contiguous array of epochDays. The index is the hijrahMonth
977      * computed from year and month and offset by minYear. The value of each
978      * entry is the epochDay corresponding to the first day of the month.
979      *
980      * @param minYear The minimum year for which data is provided
981      * @param maxYear The maximum year for which data is provided
982      * @param years a Map of year to the array of 12 month lengths
983      * @return array of epochDays for each month from min to max
984      */
createEpochMonths(int epochDay, int minYear, int maxYear, Map<Integer, int[]> years)985     private int[] createEpochMonths(int epochDay, int minYear, int maxYear, Map<Integer, int[]> years) {
986         // Compute the size for the array of dates
987         int numMonths = (maxYear - minYear + 1) * 12 + 1;
988 
989         // Initialize the running epochDay as the corresponding ISO Epoch day
990         int epochMonth = 0; // index into array of epochMonths
991         int[] epochMonths = new int[numMonths];
992         minMonthLength = Integer.MAX_VALUE;
993         maxMonthLength = Integer.MIN_VALUE;
994 
995         // Only whole years are valid, any zero's in the array are illegal
996         for (int year = minYear; year <= maxYear; year++) {
997             int[] months = years.get(year);// must not be gaps
998             for (int month = 0; month < 12; month++) {
999                 int length = months[month];
1000                 epochMonths[epochMonth++] = epochDay;
1001 
1002                 if (length < 29 || length > 32) {
1003                     throw new IllegalArgumentException("Invalid month length in year: " + minYear);
1004                 }
1005                 epochDay += length;
1006                 minMonthLength = Math.min(minMonthLength, length);
1007                 maxMonthLength = Math.max(maxMonthLength, length);
1008             }
1009         }
1010 
1011         // Insert the final epochDay
1012         epochMonths[epochMonth++] = epochDay;
1013 
1014         if (epochMonth != epochMonths.length) {
1015             throw new IllegalStateException("Did not fill epochMonths exactly: ndx = " + epochMonth
1016                     + " should be " + epochMonths.length);
1017         }
1018 
1019         return epochMonths;
1020     }
1021 
1022     /**
1023      * Parses the 12 months lengths from a property value for a specific year.
1024      *
1025      * @param line the value of a year property
1026      * @return an array of int[12] containing the 12 month lengths
1027      * @throws IllegalArgumentException if the number of months is not 12
1028      * @throws NumberFormatException if the 12 tokens are not numbers
1029      */
parseMonths(String line)1030     private int[] parseMonths(String line) {
1031         int[] months = new int[12];
1032         String[] numbers = line.split("\\s");
1033         if (numbers.length != 12) {
1034             throw new IllegalArgumentException("wrong number of months on line: " + Arrays.toString(numbers) + "; count: " + numbers.length);
1035         }
1036         for (int i = 0; i < 12; i++) {
1037             try {
1038                 months[i] = Integer.valueOf(numbers[i]);
1039             } catch (NumberFormatException nfe) {
1040                 throw new IllegalArgumentException("bad key: " + numbers[i]);
1041             }
1042         }
1043         return months;
1044     }
1045 
1046     /**
1047      * Parse yyyy-MM-dd into a 3 element array [yyyy, mm, dd].
1048      *
1049      * @param string the input string
1050      * @return the 3 element array with year, month, day
1051      */
parseYMD(String string)1052     private int[] parseYMD(String string) {
1053         // yyyy-MM-dd
1054         string = string.trim();
1055         try {
1056             if (string.charAt(4) != '-' || string.charAt(7) != '-') {
1057                 throw new IllegalArgumentException("date must be yyyy-MM-dd");
1058             }
1059             int[] ymd = new int[3];
1060             ymd[0] = Integer.valueOf(string.substring(0, 4));
1061             ymd[1] = Integer.valueOf(string.substring(5, 7));
1062             ymd[2] = Integer.valueOf(string.substring(8, 10));
1063             return ymd;
1064         } catch (NumberFormatException ex) {
1065             throw new IllegalArgumentException("date must be yyyy-MM-dd", ex);
1066         }
1067     }
1068 
1069     //-----------------------------------------------------------------------
1070     /**
1071      * Writes the Chronology using a
1072      * <a href="../../../serialized-form.html#java.time.chrono.Ser">dedicated serialized form</a>.
1073      * @serialData
1074      * <pre>
1075      *  out.writeByte(1);     // identifies a Chronology
1076      *  out.writeUTF(getId());
1077      * </pre>
1078      *
1079      * @return the instance of {@code Ser}, not null
1080      */
1081     @Override
writeReplace()1082     Object writeReplace() {
1083         return super.writeReplace();
1084     }
1085 
1086     /**
1087      * Defend against malicious streams.
1088      *
1089      * @param s the stream to read
1090      * @throws InvalidObjectException always
1091      */
readObject(ObjectInputStream s)1092     private void readObject(ObjectInputStream s) throws InvalidObjectException {
1093         throw new InvalidObjectException("Deserialization via serialization delegate");
1094     }
1095 }
1096