1 
2  /*
3  * hunt-time: A time library for D programming language.
4  *
5  * Copyright (C) 2015-2018 HuntLabs
6  *
7  * Website: https://www.huntlabs.net/
8  *
9  * Licensed under the Apache-2.0 License.
10  *
11  */
12 
13 module hunt.time.chrono.HijrahChronology;
14 
15 // import hunt.time.temporal.ChronoField;
16 
17 // // import hunt.io.FilePermission;
18 // import hunt.stream.Common;
19 
20 // //import hunt.io.ObjectInputStream;
21 // import hunt.stream.Common;
22 // // import hunt.security.AccessController;
23 // // import hunt.security.PrivilegedAction;
24 // import hunt.time.Clock;
25 // import hunt.time.Exceptions;
26 // import hunt.time.Instant;
27 // import hunt.time.LocalDate;
28 // import hunt.time.ZoneId;
29 // import hunt.time.format.ResolverStyle;
30 // import hunt.time.temporal.ChronoField;
31 // import hunt.time.temporal.TemporalAccessor;
32 // import hunt.time.temporal.TemporalField;
33 // import hunt.time.temporal.ValueRange;
34 // import hunt.time.util;
35 // //import hunt.concurrent.ConcurrentMap;;
36 
37 // import hunt.collection.List;
38 // import hunt.collection.Map;
39 // // import hunt.util.Properties;
40 
41 // // import sun.util.logging.PlatformLogger;
42 
43 // /**
44 //  * The Hijrah calendar is a lunar calendar supporting Islamic calendars.
45 //  * !(p)
46 //  * The HijrahChronology follows the rules of the Hijrah calendar system. The Hijrah
47 //  * calendar has several variants based on differences _in when the new moon is
48 //  * determined to have occurred and where the observation is made.
49 //  * In some variants the length of each month is
50 //  * computed algorithmically from the astronomical data for the moon and earth and
51 //  * _in others the length of the month is determined by an authorized sighting
52 //  * of the new moon. For the algorithmically based calendars the calendar
53 //  * can project into the future.
54 //  * For sighting based calendars only historical data from past
55 //  * sightings is available.
56 //  * !(p)
57 //  * The length of each month is 29 or 30 days.
58 //  * Ordinary years have 354 days; leap years have 355 days.
59 //  *
60 //  * !(p)
61 //  * CLDR and LDML identify variants:
62 //  * <table class="striped" style="text-align:left">
63 //  * <caption style="display:none">Variants of Hijrah Calendars</caption>
64 //  * !(thead)
65 //  * !(tr)
66 //  * <th scope="col">Chronology ID</th>
67 //  * <th scope="col">Calendar Type</th>
68 //  * <th scope="col">Locale extension, see {@link java.util.Locale}</th>
69 //  * <th scope="col">Description</th>
70 //  * </tr>
71 //  * </thead>
72 //  * !(tbody)
73 //  * !(tr)
74 //  * <th scope="row">Hijrah-umalqura</th>
75 //  * !(td)islamic-umalqura</td>
76 //  * !(td)ca-islamic-umalqura</td>
77 //  * !(td)Islamic - Umm Al-Qura calendar of Saudi Arabia</td>
78 //  * </tr>
79 //  * </tbody>
80 //  * </table>
81 //  * !(p)Additional variants may be available through {@link Chronology#getAvailableChronologies()}.
82 //  *
83 //  * !(p)Example</p>
84 //  * !(p)
85 //  * Selecting the chronology from the locale uses {@link Chronology#ofLocale}
86 //  * to find the Chronology based on Locale supported BCP 47 extension mechanism
87 //  * to request a specific calendar ("ca"). For example,
88 //  * </p>
89 //  * !(pre)
90 //  *      Locale locale = Locale.forLanguageTag("en-US-u-ca-islamic-umalqura");
91 //  *      Chronology chrono = Chronology.ofLocale(locale);
92 //  * </pre>
93 //  *
94 //  * @implSpec
95 //  * This class is immutable and thread-safe.
96 //  *
97 //  * @implNote
98 //  * Each Hijrah variant is configured individually. Each variant is defined by a
99 //  * property resource that defines the {@code ID}, the {@code calendar type},
100 //  * the start of the calendar, the alignment with the
101 //  * ISO calendar, and the length of each month for a range of years.
102 //  * The variants are loaded by HijrahChronology as a resource from
103 //  * hijrah-config-&lt;calendar type&gt;.properties.
104 //  * !(p)
105 //  * The Hijrah property resource is a set of properties that describe the calendar.
106 //  * The syntax is defined by {@code java.util.Properties#load(Reader)}.
107 //  * <table class="striped" style="text-align:left">
108 //  * <caption style="display:none">Configuration of Hijrah Calendar</caption>
109 //  * !(thead)
110 //  * !(tr)
111 //  * <th scope="col">Property Name</th>
112 //  * <th scope="col">Property value</th>
113 //  * <th scope="col">Description</th>
114 //  * </tr>
115 //  * </thead>
116 //  * !(tbody)
117 //  * !(tr)
118 //  * <th scope="row">id</th>
119 //  * !(td)Chronology Id, for example, "Hijrah-umalqura"</td>
120 //  * !(td)The Id of the calendar _in common usage</td>
121 //  * </tr>
122 //  * !(tr)
123 //  * <th scope="row">type</th>
124 //  * !(td)Calendar type, for example, "islamic-umalqura"</td>
125 //  * !(td)LDML defines the calendar types</td>
126 //  * </tr>
127 //  * !(tr)
128 //  * <th scope="row">_version</th>
129 //  * !(td)Version, for example: "1.8.0_1"</td>
130 //  * !(td)The _version of the Hijrah variant data</td>
131 //  * </tr>
132 //  * !(tr)
133 //  * <th scope="row">iso-start</th>
134 //  * !(td)ISO start date, formatted as {@code yyyy-MM-dd}, for example: "1900-04-30"</td>
135 //  * !(td)The ISO date of the first day of the minimum Hijrah year.</td>
136 //  * </tr>
137 //  * !(tr)
138 //  * <th scope="row">yyyy - a numeric 4 digit year, for example "1434"</th>
139 //  * !(td)The value is a sequence of 12 month lengths,
140 //  * for example: "29 30 29 30 29 30 30 30 29 30 29 29"</td>
141 //  * !(td)The lengths of the 12 months of the year separated by whitespace.
142 //  * A numeric year property must be present for every year without any gaps.
143 //  * The month lengths must be between 29-32 inclusive.
144 //  * </td>
145 //  * </tr>
146 //  * </tbody>
147 //  * </table>
148 //  *
149 //  * @since 1.8
150 //  */
151 // public final class HijrahChronology : AbstractChronology , Serializable {
152 
153 //     mixin MakeServiceLoader!AbstractChronology;
154 //     /**
155 //      * The Hijrah Calendar id.
156 //      */
157 //     private final /*transient*/ string typeId;
158 //     /**
159 //      * The Hijrah calendarType.
160 //      */
161 //     private final /*transient*/ string calendarType;
162 //     /**
163 //      * Serialization _version.
164 //      */
165 //     private static final long serialVersionUID = 3127340209035924785L;
166 //     /**
167 //      * Singleton instance of the Islamic Umm Al-Qura calendar of Saudi Arabia.
168 //      * Other Hijrah chronology variants may be available from
169 //      * {@link Chronology#getAvailableChronologies}.
170 //      */
171 //     public static final HijrahChronology INSTANCE;
172 //     /**
173 //      * Flag to indicate the initialization of configuration data is complete.
174 //      * @see #checkCalendarInit()
175 //      */
176 //     private /*transient*/ /* volatile */ bool initComplete;
177 //     /**
178 //      * Array of epoch days indexed by Hijrah Epoch month.
179 //      * Computed by {@link #loadCalendarData}.
180 //      */
181 //     private /*transient*/ int[] hijrahEpochMonthStartDays;
182 //     /**
183 //      * The minimum epoch day of this Hijrah calendar.
184 //      * Computed by {@link #loadCalendarData}.
185 //      */
186 //     private /*transient*/ int minEpochDay;
187 //     /**
188 //      * The maximum epoch day for which calendar data is available.
189 //      * Computed by {@link #loadCalendarData}.
190 //      */
191 //     private /*transient*/ int maxEpochDay;
192 //     /**
193 //      * The minimum epoch month.
194 //      * Computed by {@link #loadCalendarData}.
195 //      */
196 //     private /*transient*/ int hijrahStartEpochMonth;
197 //     /**
198 //      * The minimum length of a month.
199 //      * Computed by {@link #createEpochMonths}.
200 //      */
201 //     private /*transient*/ int minMonthLength;
202 //     /**
203 //      * The maximum length of a month.
204 //      * Computed by {@link #createEpochMonths}.
205 //      */
206 //     private /*transient*/ int maxMonthLength;
207 //     /**
208 //      * The minimum length of a year _in days.
209 //      * Computed by {@link #createEpochMonths}.
210 //      */
211 //     private /*transient*/ int minYearLength;
212 //     /**
213 //      * The maximum length of a year _in days.
214 //      * Computed by {@link #createEpochMonths}.
215 //      */
216 //     private /*transient*/ int maxYearLength;
217 
218 //     /**
219 //      * Prefix of resource names for Hijrah calendar variants.
220 //      */
221 //     private static final string RESOURCE_PREFIX = "hijrah-config-";
222 
223 //     /**
224 //      * Suffix of resource names for Hijrah calendar variants.
225 //      */
226 //     private static final string RESOURCE_SUFFIX = ".properties";
227 
228 //     /**
229 //      * Static initialization of the built-_in calendars.
230 //      * The data is not loaded until it is used.
231 //      */
232 //     static this(){
233 //         INSTANCE = new HijrahChronology("Hijrah-umalqura", "islamic-umalqura");
234 //         // Register it by its aliases
235 //         AbstractChronology.registerChrono(INSTANCE, "Hijrah");
236 //         AbstractChronology.registerChrono(INSTANCE, "islamic");
237 //     }
238 
239 //     /**
240 //      * Create a HijrahChronology for the named variant and type.
241 //      *
242 //      * @param id the id of the calendar
243 //      * @param calType the typeId of the calendar
244 //      * @throws IllegalArgumentException if the id or typeId is empty
245 //      */
246 //     private this(string id, string calType) {
247 //         if (id.isEmpty()) {
248 //             throw new IllegalArgumentException("calendar id is empty");
249 //         }
250 //         if (calType.isEmpty()) {
251 //             throw new IllegalArgumentException("calendar typeId is empty");
252 //         }
253 //         this.typeId = id;
254 //         this.calendarType = calType;
255 //     }
256 
257 //     /**
258 //      * Check and ensure that the calendar data has been initialized.
259 //      * The initialization check is performed at the boundary between
260 //      * public and package methods.  If a public calls another public method
261 //      * a check is not necessary _in the caller.
262 //      * The constructors of HijrahDate call {@link #getEpochDay} or
263 //      * {@link #getHijrahDateInfo} so every call from HijrahDate to a
264 //      * HijrahChronology via package private methods has been checked.
265 //      *
266 //      * @throws DateTimeException if the calendar data configuration is
267 //      *     malformed or IOExceptions occur loading the data
268 //      */
269 //     private void checkCalendarInit() {
270 //         // Keep this short so it can be inlined for performance
271 //         if (initComplete == false) {
272 //             loadCalendarData();
273 //             initComplete = true;
274 //         }
275 //     }
276 
277 //     //-----------------------------------------------------------------------
278 //     /**
279 //      * Gets the ID of the chronology.
280 //      * !(p)
281 //      * The ID uniquely identifies the {@code Chronology}. It can be used to
282 //      * lookup the {@code Chronology} using {@link Chronology#of(string)}.
283 //      *
284 //      * @return the chronology ID, non-null
285 //      * @see #getCalendarType()
286 //      */
287 //     override
288 //     public string getId() {
289 //         return typeId;
290 //     }
291 
292 //     /**
293 //      * Gets the calendar type of the Islamic calendar.
294 //      * !(p)
295 //      * The calendar type is an identifier defined by the
296 //      * !(em)Unicode Locale Data Markup Language (LDML)</em> specification.
297 //      * It can be used to lookup the {@code Chronology} using {@link Chronology#of(string)}.
298 //      *
299 //      * @return the calendar system type; non-null if the calendar has
300 //      *    a standard type, otherwise null
301 //      * @see #getId()
302 //      */
303 //     override
304 //     public string getCalendarType() {
305 //         return calendarType;
306 //     }
307 
308 //     //-----------------------------------------------------------------------
309 //     /**
310 //      * Obtains a local date _in Hijrah calendar system from the
311 //      * era, year-of-era, month-of-year and day-of-month fields.
312 //      *
313 //      * @param era  the Hijrah era, not null
314 //      * @param yearOfEra  the year-of-era
315 //      * @param month  the month-of-year
316 //      * @param dayOfMonth  the day-of-month
317 //      * @return the Hijrah local date, not null
318 //      * @throws DateTimeException if unable to create the date
319 //      * @throws ClassCastException if the {@code era} is not a {@code HijrahEra}
320 //      */
321 //     override
322 //     public HijrahDate date(Era era, int yearOfEra, int month, int dayOfMonth) {
323 //         return date(prolepticYear(era, yearOfEra), month, dayOfMonth);
324 //     }
325 
326 //     /**
327 //      * Obtains a local date _in Hijrah calendar system from the
328 //      * proleptic-year, month-of-year and day-of-month fields.
329 //      *
330 //      * @param prolepticYear  the proleptic-year
331 //      * @param month  the month-of-year
332 //      * @param dayOfMonth  the day-of-month
333 //      * @return the Hijrah local date, not null
334 //      * @throws DateTimeException if unable to create the date
335 //      */
336 //     override
337 //     public HijrahDate date(int prolepticYear, int month, int dayOfMonth) {
338 //         return HijrahDate.of(this, prolepticYear, month, dayOfMonth);
339 //     }
340 
341 //     /**
342 //      * Obtains a local date _in Hijrah calendar system from the
343 //      * era, year-of-era and day-of-year fields.
344 //      *
345 //      * @param era  the Hijrah era, not null
346 //      * @param yearOfEra  the year-of-era
347 //      * @param dayOfYear  the day-of-year
348 //      * @return the Hijrah local date, not null
349 //      * @throws DateTimeException if unable to create the date
350 //      * @throws ClassCastException if the {@code era} is not a {@code HijrahEra}
351 //      */
352 //     override
353 //     public HijrahDate dateYearDay(Era era, int yearOfEra, int dayOfYear) {
354 //         return dateYearDay(prolepticYear(era, yearOfEra), dayOfYear);
355 //     }
356 
357 //     /**
358 //      * Obtains a local date _in Hijrah calendar system from the
359 //      * proleptic-year and day-of-year fields.
360 //      *
361 //      * @param prolepticYear  the proleptic-year
362 //      * @param dayOfYear  the day-of-year
363 //      * @return the Hijrah local date, not null
364 //      * @throws DateTimeException if the value of the year is _out of range,
365 //      *  or if the day-of-year is invalid for the year
366 //      */
367 //     override
368 //     public HijrahDate dateYearDay(int prolepticYear, int dayOfYear) {
369 //         HijrahDate date = HijrahDate.of(this, prolepticYear, 1, 1);
370 //         if (dayOfYear > date.lengthOfYear()) {
371 //             throw new DateTimeException("Invalid dayOfYear: " ~ dayOfYear);
372 //         }
373 //         return date.plusDays(dayOfYear - 1);
374 //     }
375 
376 //     /**
377 //      * Obtains a local date _in the Hijrah calendar system from the epoch-day.
378 //      *
379 //      * @param epochDay  the epoch day
380 //      * @return the Hijrah local date, not null
381 //      * @throws DateTimeException if unable to create the date
382 //      */
383 //     override  // override with covariant return type
384 //     public HijrahDate dateEpochDay(long epochDay) {
385 //         return HijrahDate.ofEpochDay(this, epochDay);
386 //     }
387 
388 //     override
389 //     public HijrahDate dateNow() {
390 //         return dateNow(Clock.systemDefaultZone());
391 //     }
392 
393 //     override
394 //     public HijrahDate dateNow(ZoneId zone) {
395 //         return dateNow(Clock.system(zone));
396 //     }
397 
398 //     override
399 //     public HijrahDate dateNow(Clock clock) {
400 //         return date(LocalDate.now(clock));
401 //     }
402 
403 //     override
404 //     public HijrahDate date(TemporalAccessor temporal) {
405 //         if (cast(HijrahDate)(temporal) !is null) {
406 //             return cast(HijrahDate) temporal;
407 //         }
408 //         return HijrahDate.ofEpochDay(this, temporal.getLong(EPOCH_DAY));
409 //     }
410 
411 //     override
412 //     /*@SuppressWarnings("unchecked")*/
413 //     public ChronoLocalDateTime!(HijrahDate) localDateTime(TemporalAccessor temporal) {
414 //         return cast(ChronoLocalDateTime!(HijrahDate)) super.localDateTime(temporal);
415 //     }
416 
417 //     override
418 //     /*@SuppressWarnings("unchecked")*/
419 //     public ChronoZonedDateTime!(HijrahDate) zonedDateTime(TemporalAccessor temporal) {
420 //         return cast(ChronoZonedDateTime!(HijrahDate)) super.zonedDateTime(temporal);
421 //     }
422 
423 //     override
424 //     /*@SuppressWarnings("unchecked")*/
425 //     public ChronoZonedDateTime!(HijrahDate) zonedDateTime(Instant instant, ZoneId zone) {
426 //         return cast(ChronoZonedDateTime!(HijrahDate)) super.zonedDateTime(instant, zone);
427 //     }
428 
429 //     //-----------------------------------------------------------------------
430 //     override
431 //     public bool isLeapYear(long prolepticYear) {
432 //         checkCalendarInit();
433 //         if (prolepticYear < getMinimumYear() || prolepticYear > getMaximumYear()) {
434 //             return false;
435 //         }
436 //         int len = getYearLength(cast(int) prolepticYear);
437 //         return (len > 354);
438 //     }
439 
440 //     override
441 //     public int prolepticYear(Era era, int yearOfEra) {
442 //         if ((cast(HijrahEra)(era) !is null) == false) {
443 //             throw new ClassCastException("Era must be HijrahEra");
444 //         }
445 //         return yearOfEra;
446 //     }
447 
448 //     /**
449 //      * Creates the HijrahEra object from the numeric value.
450 //      * The Hijrah calendar system has only one era covering the
451 //      * proleptic years greater than zero.
452 //      * This method returns the singleton HijrahEra for the value 1.
453 //      *
454 //      * @param eraValue  the era value
455 //      * @return the calendar system era, not null
456 //      * @throws DateTimeException if unable to create the era
457 //      */
458 //     override
459 //     public HijrahEra eraOf(int eraValue) {
460 //         switch (eraValue) {
461 //             case 1:
462 //                 return HijrahEra.AH;
463 //             default:
464 //                 throw new DateTimeException("invalid Hijrah era");
465 //         }
466 //     }
467 
468 //     override
469 //     public List!(Era) eras() {
470 //         return List.of(HijrahEra.values());
471 //     }
472 
473 //     //-----------------------------------------------------------------------
474 //     override
475 //     public ValueRange range(ChronoField field) {
476 //         checkCalendarInit();
477 //         if (cast(ChronoField)(field) !is null) {
478 //             ChronoField f = field;
479 //             switch (f) {
480 //                 case DAY_OF_MONTH:
481 //                     return ValueRange.of(1, 1, getMinimumMonthLength(), getMaximumMonthLength());
482 //                 case DAY_OF_YEAR:
483 //                     return ValueRange.of(1, getMaximumDayOfYear());
484 //                 case ALIGNED_WEEK_OF_MONTH:
485 //                     return ValueRange.of(1, 5);
486 //                 case YEAR:
487 //                 case YEAR_OF_ERA:
488 //                     return ValueRange.of(getMinimumYear(), getMaximumYear());
489 //                 case ERA:
490 //                     return ValueRange.of(1, 1);
491 //                 default:
492 //                     return field.range();
493 //             }
494 //         }
495 //         return field.range();
496 //     }
497 
498 //     //-----------------------------------------------------------------------
499 //     override  // override for return type
500 //     public HijrahDate resolveDate(Map!(TemporalField, Long) fieldValues, ResolverStyle resolverStyle) {
501 //         return cast(HijrahDate) super.resolveDate(fieldValues, resolverStyle);
502 //     }
503 
504 //     //-----------------------------------------------------------------------
505 //     /**
506 //      * Check the validity of a year.
507 //      *
508 //      * @param prolepticYear the year to check
509 //      */
510 //     int checkValidYear(long prolepticYear) {
511 //         if (prolepticYear < getMinimumYear() || prolepticYear > getMaximumYear()) {
512 //             throw new DateTimeException("Invalid Hijrah year: " ~ prolepticYear);
513 //         }
514 //         return cast(int) prolepticYear;
515 //     }
516 
517 //     void checkValidDayOfYear(int dayOfYear) {
518 //         if (dayOfYear < 1 || dayOfYear > getMaximumDayOfYear()) {
519 //             throw new DateTimeException("Invalid Hijrah day of year: " ~ dayOfYear);
520 //         }
521 //     }
522 
523 //     void checkValidMonth(int month) {
524 //         if (month < 1 || month > 12) {
525 //             throw new DateTimeException("Invalid Hijrah month: " ~ month);
526 //         }
527 //     }
528 
529 //     //-----------------------------------------------------------------------
530 //     /**
531 //      * Returns an array containing the Hijrah year, month and day
532 //      * computed from the epoch day.
533 //      *
534 //      * @param epochDay  the EpochDay
535 //      * @return int[0] = YEAR, int[1] = MONTH, int[2] = DATE
536 //      */
537 //     int[] getHijrahDateInfo(int epochDay) {
538 //         checkCalendarInit();    // ensure that the chronology is initialized
539 //         if (epochDay < minEpochDay || epochDay >= maxEpochDay) {
540 //             throw new DateTimeException("Hijrah date _out of range");
541 //         }
542 
543 //         int epochMonth = epochDayToEpochMonth(epochDay);
544 //         int year = epochMonthToYear(epochMonth);
545 //         int month = epochMonthToMonth(epochMonth);
546 //         int day1 = epochMonthToEpochDay(epochMonth);
547 //         int date = epochDay - day1; // epochDay - dayOfEpoch(year, month);
548 
549 //         int[] dateInfo = new int[3];
550 //         dateInfo[0] = year;
551 //         dateInfo[1] = month + 1; // change to 1-based.
552 //         dateInfo[2] = date + 1; // change to 1-based.
553 //         return dateInfo;
554 //     }
555 
556 //     /**
557 //      * Return the epoch day computed from Hijrah year, month, and day.
558 //      *
559 //      * @param prolepticYear the year to represent, 0-origin
560 //      * @param monthOfYear the month-of-year to represent, 1-origin
561 //      * @param dayOfMonth the day-of-month to represent, 1-origin
562 //      * @return the epoch day
563 //      */
564 //     long getEpochDay(int prolepticYear, int monthOfYear, int dayOfMonth) {
565 //         checkCalendarInit();    // ensure that the chronology is initialized
566 //         checkValidMonth(monthOfYear);
567 //         int epochMonth = yearToEpochMonth(prolepticYear) + (monthOfYear - 1);
568 //         if (epochMonth < 0 || epochMonth >= hijrahEpochMonthStartDays.length) {
569 //             throw new DateTimeException("Invalid Hijrah date, year: " ~
570 //                     prolepticYear +  ", month: " ~ monthOfYear);
571 //         }
572 //         if (dayOfMonth < 1 || dayOfMonth > getMonthLength(prolepticYear, monthOfYear)) {
573 //             throw new DateTimeException("Invalid Hijrah day of month: " ~ dayOfMonth);
574 //         }
575 //         return epochMonthToEpochDay(epochMonth) + (dayOfMonth - 1);
576 //     }
577 
578 //     /**
579 //      * Returns day of year for the year and month.
580 //      *
581 //      * @param prolepticYear a proleptic year
582 //      * @param month a month, 1-origin
583 //      * @return the day of year, 1-origin
584 //      */
585 //     int getDayOfYear(int prolepticYear, int month) {
586 //         return yearMonthToDayOfYear(prolepticYear, (month - 1));
587 //     }
588 
589 //     /**
590 //      * Returns month length for the year and month.
591 //      *
592 //      * @param prolepticYear a proleptic year
593 //      * @param monthOfYear a month, 1-origin.
594 //      * @return the length of the month
595 //      */
596 //     int getMonthLength(int prolepticYear, int monthOfYear) {
597 //         int epochMonth = yearToEpochMonth(prolepticYear) + (monthOfYear - 1);
598 //         if (epochMonth < 0 || epochMonth >= hijrahEpochMonthStartDays.length) {
599 //             throw new DateTimeException("Invalid Hijrah date, year: " ~
600 //                     prolepticYear +  ", month: " ~ monthOfYear);
601 //         }
602 //         return epochMonthLength(epochMonth);
603 //     }
604 
605 //     /**
606 //      * Returns year length.
607 //      * Note: The 12th month must exist _in the data.
608 //      *
609 //      * @param prolepticYear a proleptic year
610 //      * @return year length _in days
611 //      */
612 //     int getYearLength(int prolepticYear) {
613 //         return yearMonthToDayOfYear(prolepticYear, 12);
614 //     }
615 
616 //     /**
617 //      * Return the minimum supported Hijrah year.
618 //      *
619 //      * @return the minimum
620 //      */
621 //     int getMinimumYear() {
622 //         return epochMonthToYear(0);
623 //     }
624 
625 //     /**
626 //      * Return the maximum supported Hijrah year.
627 //      *
628 //      * @return the minimum
629 //      */
630 //     int getMaximumYear() {
631 //         return epochMonthToYear(hijrahEpochMonthStartDays.length - 1) - 1;
632 //     }
633 
634 //     /**
635 //      * Returns maximum day-of-month.
636 //      *
637 //      * @return maximum day-of-month
638 //      */
639 //     int getMaximumMonthLength() {
640 //         return maxMonthLength;
641 //     }
642 
643 //     /**
644 //      * Returns smallest maximum day-of-month.
645 //      *
646 //      * @return smallest maximum day-of-month
647 //      */
648 //     int getMinimumMonthLength() {
649 //         return minMonthLength;
650 //     }
651 
652 //     /**
653 //      * Returns maximum day-of-year.
654 //      *
655 //      * @return maximum day-of-year
656 //      */
657 //     int getMaximumDayOfYear() {
658 //         return maxYearLength;
659 //     }
660 
661 //     /**
662 //      * Returns smallest maximum day-of-year.
663 //      *
664 //      * @return smallest maximum day-of-year
665 //      */
666 //     int getSmallestMaximumDayOfYear() {
667 //         return minYearLength;
668 //     }
669 
670 //     /**
671 //      * Returns the epochMonth found by locating the epochDay _in the table. The
672 //      * epochMonth is the index _in the table
673 //      *
674 //      * @param epochDay
675 //      * @return The index of the element of the start of the month containing the
676 //      * epochDay.
677 //      */
678 //     private int epochDayToEpochMonth(int epochDay) {
679 //         // binary search
680 //         int ndx = Arrays.binarySearch(hijrahEpochMonthStartDays, epochDay);
681 //         if (ndx < 0) {
682 //             ndx = -ndx - 2;
683 //         }
684 //         return ndx;
685 //     }
686 
687 //     /**
688 //      * Returns the year computed from the epochMonth
689 //      *
690 //      * @param epochMonth the epochMonth
691 //      * @return the Hijrah Year
692 //      */
693 //     private int epochMonthToYear(int epochMonth) {
694 //         return (epochMonth + hijrahStartEpochMonth) / 12;
695 //     }
696 
697 //     /**
698 //      * Returns the epochMonth for the Hijrah Year.
699 //      *
700 //      * @param year the HijrahYear
701 //      * @return the epochMonth for the beginning of the year.
702 //      */
703 //     private int yearToEpochMonth(int year) {
704 //         return (year * 12) - hijrahStartEpochMonth;
705 //     }
706 
707 //     /**
708 //      * Returns the Hijrah month from the epochMonth.
709 //      *
710 //      * @param epochMonth the epochMonth
711 //      * @return the month of the Hijrah Year
712 //      */
713 //     private int epochMonthToMonth(int epochMonth) {
714 //         return (epochMonth + hijrahStartEpochMonth) % 12;
715 //     }
716 
717 //     /**
718 //      * Returns the epochDay for the start of the epochMonth.
719 //      *
720 //      * @param epochMonth the epochMonth
721 //      * @return the epochDay for the start of the epochMonth.
722 //      */
723 //     private int epochMonthToEpochDay(int epochMonth) {
724 //         return hijrahEpochMonthStartDays[epochMonth];
725 
726 //     }
727 
728 //     /**
729 //      * Returns the day of year for the requested HijrahYear and month.
730 //      *
731 //      * @param prolepticYear the Hijrah year
732 //      * @param month the Hijrah month
733 //      * @return the day of year for the start of the month of the year
734 //      */
735 //     private int yearMonthToDayOfYear(int prolepticYear, int month) {
736 //         int epochMonthFirst = yearToEpochMonth(prolepticYear);
737 //         return epochMonthToEpochDay(epochMonthFirst + month)
738 //                 - epochMonthToEpochDay(epochMonthFirst);
739 //     }
740 
741 //     /**
742 //      * Returns the length of the epochMonth. It is computed from the start of
743 //      * the following month minus the start of the requested month.
744 //      *
745 //      * @param epochMonth the epochMonth; assumed to be within range
746 //      * @return the length _in days of the epochMonth
747 //      */
748 //     private int epochMonthLength(int epochMonth) {
749 //         // The very last entry _in the epochMonth table is not the start of a month
750 //         return hijrahEpochMonthStartDays[epochMonth + 1]
751 //                 - hijrahEpochMonthStartDays[epochMonth];
752 //     }
753 
754 //     //-----------------------------------------------------------------------
755 //     private static final string KEY_ID = "id";
756 //     private static final string KEY_TYPE = "type";
757 //     private static final string KEY_VERSION = "_version";
758 //     private static final string KEY_ISO_START = "iso-start";
759 
760 //     /**
761 //      * Return the configuration properties from the resource.
762 //      * !(p)
763 //      * The location of the variant configuration resource is:
764 //      * !(pre)
765 //      *   "/java/time/chrono/hijrah-config-" ~ calendarType ~ ".properties"
766 //      * </pre>
767 //      *
768 //      * @param calendarType the calendarType of the calendar variant
769 //      * @return a Properties containing the properties read from the resource.
770 //      * @throws Exception if access to the property resource fails
771 //      */
772 //     private Properties readConfigProperties(final string calendarType) /* throws Exception */ {
773 //         // string resourceName = RESOURCE_PREFIX + calendarType + RESOURCE_SUFFIX;
774 //         // PrivilegedAction!(InputStream) getResourceAction =  () -> HijrahChronology.class.getResourceAsStream(resourceName);
775 //         // FilePermission perm1 = new FilePermission("<!(ALL FILES)>", "read");
776 //         // RuntimePermission perm2 = new RuntimePermission("accessSystemModules");
777 //         // try (InputStream is = AccessController.doPrivileged(getResourceAction, null, perm1, perm2)) {
778 //         //     if (is is null) {
779 //         //         throw new RuntimeException("Hijrah calendar resource not found: /java/time/chrono/" ~ resourceName);
780 //         //     }
781 //         //     Properties props = new Properties();
782 //         //     props.load(is);
783 //         //     return props;
784 //         // }
785 //         implementationMissing(false);
786 //         return null;
787 //     }
788 
789 //     /**
790 //      * Loads and processes the Hijrah calendar properties file for this calendarType.
791 //      * The starting Hijrah date and the corresponding ISO date are
792 //      * extracted and used to calculate the epochDate offset.
793 //      * The _version number is identified and ignored.
794 //      * Everything else is the data for a year with containing the length of each
795 //      * of 12 months.
796 //      *
797 //      * @throws DateTimeException if initialization of the calendar data from the
798 //      *     resource fails
799 //      */
800 //     private void loadCalendarData() {
801 //         try {
802 //             Properties props = readConfigProperties(calendarType);
803 
804 //             Map!(Integer, int[]) years = new HashMap!(Integer, int[])();
805 //             int minYear = Integer.MAX_VALUE;
806 //             int maxYear = Integer.MIN_VALUE;
807 //             string id = null;
808 //             string type = null;
809 //             string _version = null;
810 //             int isoStart = 0;
811 //             foreach(Map.Entry!(Object, Object) entry ; props.entrySet()) {
812 //                 string key = cast(string) entry.getKey();
813 //                 switch (key) {
814 //                     case KEY_ID:
815 //                         id = cast(string)entry.getValue();
816 //                         break;
817 //                     case KEY_TYPE:
818 //                         type = cast(string)entry.getValue();
819 //                         break;
820 //                     case KEY_VERSION:
821 //                         _version = cast(string)entry.getValue();
822 //                         break;
823 //                     case KEY_ISO_START: {
824 //                         int[] ymd = parseYMD(cast(string) entry.getValue());
825 //                         isoStart = cast(int) LocalDate.of(ymd[0], ymd[1], ymd[2]).toEpochDay();
826 //                         break;
827 //                     }
828 //                     default:
829 //                         try {
830 //                             // Everything else is either a year or invalid
831 //                             int year = Integer.parseInt(key);
832 //                             int[] months = parseMonths(cast(string) entry.getValue());
833 //                             years.put(year, months);
834 //                             maxYear = Math.max(maxYear, year);
835 //                             minYear = Math.min(minYear, year);
836 //                         } catch (NumberFormatException nfe) {
837 //                             throw new IllegalArgumentException("bad key: " ~ key);
838 //                         }
839 //                 }
840 //             }
841 
842 //             if (!getId().equals(id)) {
843 //                 throw new IllegalArgumentException("Configuration is for a different calendar: " ~ id);
844 //             }
845 //             if (!getCalendarType().equals(type)) {
846 //                 throw new IllegalArgumentException("Configuration is for a different calendar type: " ~ type);
847 //             }
848 //             if (_version is null || _version.isEmpty()) {
849 //                 throw new IllegalArgumentException("Configuration does not contain a _version");
850 //             }
851 //             if (isoStart == 0) {
852 //                 throw new IllegalArgumentException("Configuration does not contain a ISO start date");
853 //             }
854 
855 //             // Now create and validate the array of epochDays indexed by epochMonth
856 //             hijrahStartEpochMonth = minYear * 12;
857 //             minEpochDay = isoStart;
858 //             hijrahEpochMonthStartDays = createEpochMonths(minEpochDay, minYear, maxYear, years);
859 //             maxEpochDay = hijrahEpochMonthStartDays[hijrahEpochMonthStartDays.length - 1];
860 
861 //             // Compute the min and max year length _in days.
862 //             for (int year = minYear; year < maxYear; year++) {
863 //                 int length = getYearLength(year);
864 //                 minYearLength = Math.min(minYearLength, length);
865 //                 maxYearLength = Math.max(maxYearLength, length);
866 //             }
867 //         } catch (Exception ex) {
868 //             // Log error and throw a DateTimeException
869 //             PlatformLogger logger = PlatformLogger.getLogger("hunt.time.chrono");
870 //             logger.severe("Unable to initialize Hijrah calendar proxy: " ~ typeId, ex);
871 //             throw new DateTimeException("Unable to initialize HijrahCalendar: " ~ typeId, ex);
872 //         }
873 //     }
874 
875 //     /**
876 //      * Converts the map of year to month lengths ranging from minYear to maxYear
877 //      * into a linear contiguous array of epochDays. The index is the hijrahMonth
878 //      * computed from year and month and offset by minYear. The value of each
879 //      * entry is the epochDay corresponding to the first day of the month.
880 //      *
881 //      * @param minYear The minimum year for which data is provided
882 //      * @param maxYear The maximum year for which data is provided
883 //      * @param years a Map of year to the array of 12 month lengths
884 //      * @return array of epochDays for each month from min to max
885 //      */
886 //     private int[] createEpochMonths(int epochDay, int minYear, int maxYear, Map!(Integer, int[]) years) {
887 //         // Compute the size for the array of dates
888 //         int numMonths = (maxYear - minYear + 1) * 12 + 1;
889 
890 //         // Initialize the running epochDay as the corresponding ISO Epoch day
891 //         int epochMonth = 0; // index into array of epochMonths
892 //         int[] epochMonths = new int[numMonths];
893 //         minMonthLength = Integer.MAX_VALUE;
894 //         maxMonthLength = Integer.MIN_VALUE;
895 
896 //         // Only whole years are valid, any zero's _in the array are illegal
897 //         for (int year = minYear; year <= maxYear; year++) {
898 //             int[] months = years.get(year);// must not be gaps
899 //             for (int month = 0; month < 12; month++) {
900 //                 int length = months[month];
901 //                 epochMonths[epochMonth++] = epochDay;
902 
903 //                 if (length < 29 || length > 32) {
904 //                     throw new IllegalArgumentException("Invalid month length _in year: " ~ minYear);
905 //                 }
906 //                 epochDay += length;
907 //                 minMonthLength = Math.min(minMonthLength, length);
908 //                 maxMonthLength = Math.max(maxMonthLength, length);
909 //             }
910 //         }
911 
912 //         // Insert the final epochDay
913 //         epochMonths[epochMonth++] = epochDay;
914 
915 //         if (epochMonth != epochMonths.length) {
916 //             throw new IllegalStateException("Did not fill epochMonths exactly: ndx = " ~ epochMonth
917 //                     ~ " should be " ~ epochMonths.length);
918 //         }
919 
920 //         return epochMonths;
921 //     }
922 
923 //     /**
924 //      * Parses the 12 months lengths from a property value for a specific year.
925 //      *
926 //      * @param line the value of a year property
927 //      * @return an array of int[12] containing the 12 month lengths
928 //      * @throws IllegalArgumentException if the number of months is not 12
929 //      * @throws NumberFormatException if the 12 tokens are not numbers
930 //      */
931 //     private int[] parseMonths(string line) {
932 //         int[] months = new int[12];
933 //         string[] numbers = line.split("\\s");
934 //         if (numbers.length != 12) {
935 //             throw new IllegalArgumentException("wrong number of months on line: " ~ Arrays.toString(numbers) ~ "; count: " ~ numbers.length);
936 //         }
937 //         for (int i = 0; i < 12; i++) {
938 //             try {
939 //                 months[i] = Integer.parseInt(numbers[i]);
940 //             } catch (NumberFormatException nfe) {
941 //                 throw new IllegalArgumentException("bad key: " ~ numbers[i]);
942 //             }
943 //         }
944 //         return months;
945 //     }
946 
947 //     /**
948 //      * Parse yyyy-MM-dd into a 3 element array [yyyy, mm, dd].
949 //      *
950 //      * @param string the input string
951 //      * @return the 3 element array with year, month, day
952 //      */
953 //     private int[] parseYMD(string string) {
954 //         // yyyy-MM-dd
955 //         string = string.trim();
956 //         try {
957 //             if (string[4] != '-' || string[7] != '-') {
958 //                 throw new IllegalArgumentException("date must be yyyy-MM-dd");
959 //             }
960 //             int[] ymd = new int[3];
961 //             ymd[0] = Integer.parseInt(string, 0, 4, 10);
962 //             ymd[1] = Integer.parseInt(string, 5, 7, 10);
963 //             ymd[2] = Integer.parseInt(string, 8, 10, 10);
964 //             return ymd;
965 //         } catch (NumberFormatException ex) {
966 //             throw new IllegalArgumentException("date must be yyyy-MM-dd", ex);
967 //         }
968 //     }
969 
970 //     //-----------------------------------------------------------------------
971 //     /**
972 //      * Writes the Chronology using a
973 //      * <a href="{@docRoot}/serialized-form.html#hunt.time.chrono.Ser">dedicated serialized form</a>.
974 //      * @serialData
975 //      * !(pre)
976 //      *  _out.writeByte(1);     // identifies a Chronology
977 //      *  _out.writeUTF(getId());
978 //      * </pre>
979 //      *
980 //      * @return the instance of {@code Ser}, not null
981 //      */
982 //     override
983 //     Object writeReplace() {
984 //         return super.writeReplace();
985 //     }
986 
987 //     /**
988 //      * Defend against malicious streams.
989 //      *
990 //      * @param s the stream to read
991 //      * @throws InvalidObjectException always
992 //      */
993 //     private void readObject(ObjectInputStream s) /*throws InvalidObjectException*/ {
994 //         throw new InvalidObjectException("Deserialization via serialization delegate");
995 //     }
996 // }