• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
3  *
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  *  * Redistributions of source code must retain the above copyright notice,
10  *    this list of conditions and the following disclaimer.
11  *
12  *  * Redistributions in binary form must reproduce the above copyright notice,
13  *    this list of conditions and the following disclaimer in the documentation
14  *    and/or other materials provided with the distribution.
15  *
16  *  * Neither the name of JSR-310 nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 package org.threeten.bp.chrono;
33 
34 import static org.threeten.bp.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH;
35 import static org.threeten.bp.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR;
36 import static org.threeten.bp.temporal.ChronoField.ALIGNED_WEEK_OF_MONTH;
37 import static org.threeten.bp.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR;
38 import static org.threeten.bp.temporal.ChronoField.DAY_OF_MONTH;
39 import static org.threeten.bp.temporal.ChronoField.MONTH_OF_YEAR;
40 import static org.threeten.bp.temporal.ChronoField.YEAR;
41 
42 import java.io.BufferedReader;
43 import java.io.DataInput;
44 import java.io.DataOutput;
45 import java.io.File;
46 import java.io.FileInputStream;
47 import java.io.IOException;
48 import java.io.InputStream;
49 import java.io.InputStreamReader;
50 import java.io.Serializable;
51 import java.text.ParseException;
52 import java.util.HashMap;
53 import java.util.StringTokenizer;
54 import java.util.zip.ZipEntry;
55 import java.util.zip.ZipFile;
56 
57 import org.threeten.bp.Clock;
58 import org.threeten.bp.DateTimeException;
59 import org.threeten.bp.DayOfWeek;
60 import org.threeten.bp.LocalDate;
61 import org.threeten.bp.LocalTime;
62 import org.threeten.bp.ZoneId;
63 import org.threeten.bp.jdk8.Jdk8Methods;
64 import org.threeten.bp.temporal.ChronoField;
65 import org.threeten.bp.temporal.TemporalAccessor;
66 import org.threeten.bp.temporal.TemporalAdjuster;
67 import org.threeten.bp.temporal.TemporalAmount;
68 import org.threeten.bp.temporal.TemporalField;
69 import org.threeten.bp.temporal.TemporalQuery;
70 import org.threeten.bp.temporal.TemporalUnit;
71 import org.threeten.bp.temporal.UnsupportedTemporalTypeException;
72 import org.threeten.bp.temporal.ValueRange;
73 
74 /**
75  * A date in the Hijrah calendar system.
76  * <p>
77  * This implements {@code ChronoLocalDate} for the {@link HijrahChronology Hijrah calendar}.
78  * <p>
79  * The Hijrah calendar has a different total of days in a year than
80  * Gregorian calendar, and a month is based on the period of a complete
81  * revolution of the moon around the earth (as between successive new moons).
82  * The calendar cycles becomes longer and unstable, and sometimes a manual
83  * adjustment (for entering deviation) is necessary for correctness
84  * because of the complex algorithm.
85  * <p>
86  * HijrahDate supports the manual adjustment feature by providing a configuration
87  * file. The configuration file contains the adjustment (deviation) data with following format.
88  * <pre>
89  *   StartYear/StartMonth(0-based)-EndYear/EndMonth(0-based):Deviation day (1, 2, -1, or -2)
90  *   Line separator or ";" is used for the separator of each deviation data.</pre>
91  *   Here is the example.
92  * <pre>
93  *     1429/0-1429/1:1
94  *     1429/2-1429/7:1;1429/6-1429/11:1
95  *     1429/11-9999/11:1</pre>
96  * The default location of the configuration file is:
97  * <pre>
98  *   $CLASSPATH/org/threeten/bp/chrono</pre>
99  * And the default file name is:
100  * <pre>
101  *   hijrah_deviation.cfg</pre>
102  * The default location and file name can be overriden by setting
103  * following two Java's system property.
104  * <pre>
105  *   Location: org.threeten.bp.i18n.HijrahDate.deviationConfigDir
106  *   File name: org.threeten.bp.i18n.HijrahDate.deviationConfigFile</pre>
107  *
108  * <h3>Specification for implementors</h3>
109  * This class is immutable and thread-safe.
110  */
111 public final class HijrahDate
112         extends ChronoDateImpl<HijrahDate>
113         implements Serializable {
114     // this class is package-scoped so that future conversion to public
115     // would not change serialization
116 
117     /**
118      * Serialization version.
119      */
120     private static final long serialVersionUID = -5207853542612002020L;
121 
122     /**
123      * The minimum valid year-of-era.
124      */
125     public static final int MIN_VALUE_OF_ERA = 1;
126     /**
127      * The maximum valid year-of-era.
128      * This is currently set to 9999 but may be changed to increase the valid range
129      * in a future version of the specification.
130      */
131     public static final int MAX_VALUE_OF_ERA = 9999;
132     /**
133      * 0-based, for number of day-of-year in the beginning of month in normal
134      * year.
135      */
136     private static final int NUM_DAYS[] =
137         {0, 30, 59, 89, 118, 148, 177, 207, 236, 266, 295, 325};
138     /**
139      * 0-based, for number of day-of-year in the beginning of month in leap year.
140      */
141     private static final int LEAP_NUM_DAYS[] =
142         {0, 30, 59, 89, 118, 148, 177, 207, 236, 266, 295, 325};
143     /**
144      * 0-based, for day-of-month in normal year.
145      */
146     private static final int MONTH_LENGTH[] =
147         {30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29};
148     /**
149      * 0-based, for day-of-month in leap year.
150      */
151     private static final int LEAP_MONTH_LENGTH[] =
152         {30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 30};
153 
154     /**
155      * <pre>
156      *                            Greatest       Least
157      * Field name        Minimum   Minimum     Maximum     Maximum
158      * ----------        -------   -------     -------     -------
159      * ERA                     0         0           1           1
160      * YEAR_OF_ERA             1         1        9999        9999
161      * MONTH_OF_YEAR           1         1          12          12
162      * DAY_OF_MONTH            1         1          29          30
163      * DAY_OF_YEAR             1         1         354         355
164      * </pre>
165      *
166      * Minimum values.
167      */
168     private static final int MIN_VALUES[] =
169         {
170         0,
171         MIN_VALUE_OF_ERA,
172         0,
173         1,
174         0,
175         1,
176         1
177         };
178 
179     /**
180      * Least maximum values.
181      */
182     private static final int LEAST_MAX_VALUES[] =
183         {
184         1,
185         MAX_VALUE_OF_ERA,
186         11,
187         51,
188         5,
189         29,
190         354
191         };
192 
193     /**
194      * Maximum values.
195      */
196     private static final int MAX_VALUES[] =
197         {
198         1,
199         MAX_VALUE_OF_ERA,
200         11,
201         52,
202         6,
203         30,
204         355
205         };
206 
207    /**
208      * Position of day-of-month. This value is used to get the min/max value
209      * from an array.
210      */
211     private static final int POSITION_DAY_OF_MONTH = 5;
212     /**
213      * Position of day-of-year. This value is used to get the min/max value from
214      * an array.
215      */
216     private static final int POSITION_DAY_OF_YEAR = 6;
217     /**
218      * Zero-based start date of cycle year.
219      */
220     private static final int CYCLEYEAR_START_DATE[] =
221         {
222         0,
223         354,
224         709,
225         1063,
226         1417,
227         1772,
228         2126,
229         2481,
230         2835,
231         3189,
232         3544,
233         3898,
234         4252,
235         4607,
236         4961,
237         5315,
238         5670,
239         6024,
240         6379,
241         6733,
242         7087,
243         7442,
244         7796,
245         8150,
246         8505,
247         8859,
248         9214,
249         9568,
250         9922,
251         10277
252         };
253 
254     /**
255      * File separator.
256      */
257     private static final char FILE_SEP = File.separatorChar;
258     /**
259      * Path separator.
260      */
261     private static final String PATH_SEP = File.pathSeparator;
262     /**
263      * Default config file name.
264      */
265     private static final String DEFAULT_CONFIG_FILENAME = "hijrah_deviation.cfg";
266     /**
267      * Default path to the config file.
268      */
269     private static final String DEFAULT_CONFIG_PATH = "org" + FILE_SEP + "threeten" + FILE_SEP + "bp" + FILE_SEP + "chrono";
270     /**
271      * Holding the adjusted month days in year. The key is a year (Integer) and
272      * the value is the all the month days in year (Integer[]).
273      */
274     private static final HashMap<Integer, Integer[]> ADJUSTED_MONTH_DAYS = new HashMap<Integer, Integer[]>();
275     /**
276      * Holding the adjusted month length in year. The key is a year (Integer)
277      * and the value is the all the month length in year (Integer[]).
278      */
279     private static final HashMap<Integer, Integer[]> ADJUSTED_MONTH_LENGTHS = new HashMap<Integer, Integer[]>();
280     /**
281      * Holding the adjusted days in the 30 year cycle. The key is a cycle number
282      * (Integer) and the value is the all the starting days of the year in the
283      * cycle (Integer[]).
284      */
285     private static final HashMap<Integer, Integer[]> ADJUSTED_CYCLE_YEARS = new HashMap<Integer, Integer[]>();
286     /**
287      * Holding the adjusted cycle in the 1 - 30000 year. The key is the cycle
288      * number (Integer) and the value is the starting days in the cycle in the
289      * term.
290      */
291     private static final Long[] ADJUSTED_CYCLES;
292     /**
293      * Holding the adjusted min values.
294      */
295     private static final Integer[] ADJUSTED_MIN_VALUES;
296     /**
297      * Holding the adjusted max least max values.
298      */
299     private static final Integer[] ADJUSTED_LEAST_MAX_VALUES;
300     /**
301      * Holding adjusted max values.
302      */
303     private static final Integer[] ADJUSTED_MAX_VALUES;
304     /**
305      * Holding the non-adjusted month days in year for non leap year.
306      */
307     private static final Integer[] DEFAULT_MONTH_DAYS;
308     /**
309      * Holding the non-adjusted month days in year for leap year.
310      */
311     private static final Integer[] DEFAULT_LEAP_MONTH_DAYS;
312     /**
313      * Holding the non-adjusted month length for non leap year.
314      */
315     private static final Integer[] DEFAULT_MONTH_LENGTHS;
316     /**
317      * Holding the non-adjusted month length for leap year.
318      */
319     private static final Integer[] DEFAULT_LEAP_MONTH_LENGTHS;
320     /**
321      * Holding the non-adjusted 30 year cycle starting day.
322      */
323     private static final Integer[] DEFAULT_CYCLE_YEARS;
324     /**
325      * number of 30-year cycles to hold the deviation data.
326      */
327     private static final int MAX_ADJUSTED_CYCLE = 334; // to support year 9999
328 
329     static { // Initialize the static integer array;
330 
331         DEFAULT_MONTH_DAYS = new Integer[NUM_DAYS.length];
332         for (int i = 0; i < NUM_DAYS.length; i++) {
333             DEFAULT_MONTH_DAYS[i] = Integer.valueOf(NUM_DAYS[i]);
334         }
335 
336         DEFAULT_LEAP_MONTH_DAYS = new Integer[LEAP_NUM_DAYS.length];
337         for (int i = 0; i < LEAP_NUM_DAYS.length; i++) {
338             DEFAULT_LEAP_MONTH_DAYS[i] = Integer.valueOf(LEAP_NUM_DAYS[i]);
339         }
340 
341         DEFAULT_MONTH_LENGTHS = new Integer[MONTH_LENGTH.length];
342         for (int i = 0; i < MONTH_LENGTH.length; i++) {
343             DEFAULT_MONTH_LENGTHS[i] = Integer.valueOf(MONTH_LENGTH[i]);
344         }
345 
346         DEFAULT_LEAP_MONTH_LENGTHS = new Integer[LEAP_MONTH_LENGTH.length];
347         for (int i = 0; i < LEAP_MONTH_LENGTH.length; i++) {
348             DEFAULT_LEAP_MONTH_LENGTHS[i] = Integer.valueOf(LEAP_MONTH_LENGTH[i]);
349         }
350 
351         DEFAULT_CYCLE_YEARS = new Integer[CYCLEYEAR_START_DATE.length];
352         for (int i = 0; i < CYCLEYEAR_START_DATE.length; i++) {
353             DEFAULT_CYCLE_YEARS[i] = Integer.valueOf(CYCLEYEAR_START_DATE[i]);
354         }
355 
356         ADJUSTED_CYCLES = new Long[MAX_ADJUSTED_CYCLE];
357         for (int i = 0; i < ADJUSTED_CYCLES.length; i++) {
358             ADJUSTED_CYCLES[i] = Long.valueOf(10631 * i);
359         }
360         // Initialize min values, least max values and max values.
361         ADJUSTED_MIN_VALUES = new Integer[MIN_VALUES.length];
362         for (int i = 0; i < MIN_VALUES.length; i++) {
363             ADJUSTED_MIN_VALUES[i] = Integer.valueOf(MIN_VALUES[i]);
364         }
365         ADJUSTED_LEAST_MAX_VALUES = new Integer[LEAST_MAX_VALUES.length];
366         for (int i = 0; i < LEAST_MAX_VALUES.length; i++) {
367             ADJUSTED_LEAST_MAX_VALUES[i] = Integer.valueOf(LEAST_MAX_VALUES[i]);
368         }
369         ADJUSTED_MAX_VALUES = new Integer[MAX_VALUES.length];
370         for (int i = 0; i < MAX_VALUES.length; i++) {
371             ADJUSTED_MAX_VALUES[i] = Integer.valueOf(MAX_VALUES[i]);
372         }
373         try {
readDeviationConfig()374             readDeviationConfig();
375         } catch (IOException e) {
376             // do nothing. Ignore deviation config.
377             // e.printStackTrace();
378         } catch (ParseException e) {
379             // do nothing. Ignore deviation config.
380             // e.printStackTrace();
381         }
382     }
383     /**
384      * Number of Gregorian day of July 19, year 622 (Gregorian), which is epoch day
385      * of Hijrah calendar.
386      */
387     private static final int HIJRAH_JAN_1_1_GREGORIAN_DAY = -492148;
388 
389     /**
390      * The era.
391      */
392     private final transient HijrahEra era;
393     /**
394      * The year.
395      */
396     private final transient int yearOfEra;
397     /**
398      * The month-of-year.
399      */
400     private final transient int monthOfYear;
401     /**
402      * The day-of-month.
403      */
404     private final transient int dayOfMonth;
405     /**
406      * The day-of-year.
407      */
408     private final transient int dayOfYear;
409     /**
410      * The day-of-week.
411      */
412     private final transient DayOfWeek dayOfWeek;
413     /**
414      * Gregorian days for this object. Holding number of days since 1970/01/01.
415      * The number of days are calculated with pure Gregorian calendar
416      * based.
417      */
418     private final long gregorianEpochDay;
419     /**
420      * True if year is leap year.
421      */
422     private final transient boolean isLeapYear;
423 
424     //-----------------------------------------------------------------------
425     /**
426      * Obtains the current {@code HijrahDate} of the Islamic Umm Al-Qura calendar
427      * in the default time-zone.
428      * <p>
429      * This will query the {@link Clock#systemDefaultZone() system clock} in the default
430      * time-zone to obtain the current date.
431      * <p>
432      * Using this method will prevent the ability to use an alternate clock for testing
433      * because the clock is hard-coded.
434      *
435      * @return the current date using the system clock and default time-zone, not null
436      */
now()437     public static HijrahDate now() {
438         return now(Clock.systemDefaultZone());
439     }
440 
441     /**
442      * Obtains the current {@code HijrahDate} of the Islamic Umm Al-Qura calendar
443      * in the specified time-zone.
444      * <p>
445      * This will query the {@link Clock#system(ZoneId) system clock} to obtain the current date.
446      * Specifying the time-zone avoids dependence on the default time-zone.
447      * <p>
448      * Using this method will prevent the ability to use an alternate clock for testing
449      * because the clock is hard-coded.
450      *
451      * @param zone  the zone ID to use, not null
452      * @return the current date using the system clock, not null
453      */
now(ZoneId zone)454     public static HijrahDate now(ZoneId zone) {
455         return now(Clock.system(zone));
456     }
457 
458     /**
459      * Obtains the current {@code HijrahDate} of the Islamic Umm Al-Qura calendar
460      * from the specified clock.
461      * <p>
462      * This will query the specified clock to obtain the current date - today.
463      * Using this method allows the use of an alternate clock for testing.
464      * The alternate clock may be introduced using {@linkplain Clock dependency injection}.
465      *
466      * @param clock  the clock to use, not null
467      * @return the current date, not null
468      * @throws DateTimeException if the current date cannot be obtained
469      */
now(Clock clock)470     public static HijrahDate now(Clock clock) {
471         return HijrahChronology.INSTANCE.dateNow(clock);
472     }
473 
474     //-------------------------------------------------------------------------
475     /**
476      * Obtains an instance of {@code HijrahDate} from the Hijrah era year,
477      * month-of-year and day-of-month. This uses the Hijrah era.
478      *
479      * @param prolepticYear  the proleptic year to represent in the Hijrah
480      * @param monthOfYear  the month-of-year to represent, from 1 to 12
481      * @param dayOfMonth  the day-of-month to represent, from 1 to 30
482      * @return the Hijrah date, never null
483      * @throws IllegalCalendarFieldValueException if the value of any field is out of range
484      * @throws InvalidCalendarFieldException if the day-of-month is invalid for the month-year
485      */
of(int prolepticYear, int monthOfYear, int dayOfMonth)486     public static HijrahDate of(int prolepticYear, int monthOfYear, int dayOfMonth) {
487         return (prolepticYear >= 1) ?
488             HijrahDate.of(HijrahEra.AH, prolepticYear, monthOfYear, dayOfMonth) :
489             HijrahDate.of(HijrahEra.BEFORE_AH, 1 - prolepticYear, monthOfYear, dayOfMonth);
490     }
491 
492     /**
493      * Obtains an instance of {@code HijrahDate} from the era, year-of-era
494      * month-of-year and day-of-month.
495      *
496      * @param era  the era to represent, not null
497      * @param yearOfEra  the year-of-era to represent, from 1 to 9999
498      * @param monthOfYear  the month-of-year to represent, from 1 to 12
499      * @param dayOfMonth  the day-of-month to represent, from 1 to 31
500      * @return the Hijrah date, never null
501      * @throws IllegalCalendarFieldValueException if the value of any field is out of range
502      * @throws InvalidCalendarFieldException if the day-of-month is invalid for the month-year
503      */
of(HijrahEra era, int yearOfEra, int monthOfYear, int dayOfMonth)504     static HijrahDate of(HijrahEra era, int yearOfEra, int monthOfYear, int dayOfMonth) {
505         Jdk8Methods.requireNonNull(era, "era");
506         checkValidYearOfEra(yearOfEra);
507         checkValidMonth(monthOfYear);
508         checkValidDayOfMonth(dayOfMonth);
509         long gregorianDays = getGregorianEpochDay(era.prolepticYear(yearOfEra), monthOfYear, dayOfMonth);
510         return new HijrahDate(gregorianDays);
511     }
512 
513     /**
514      * Check the validity of a yearOfEra.
515      * @param yearOfEra the year to check
516      */
checkValidYearOfEra(int yearOfEra)517     private static void checkValidYearOfEra(int yearOfEra) {
518          if (yearOfEra < MIN_VALUE_OF_ERA  ||
519                  yearOfEra > MAX_VALUE_OF_ERA) {
520              throw new DateTimeException("Invalid year of Hijrah Era");
521          }
522     }
523 
checkValidDayOfYear(int dayOfYear)524     private static void checkValidDayOfYear(int dayOfYear) {
525          if (dayOfYear < 1  ||
526                  dayOfYear > getMaximumDayOfYear()) {
527              throw new DateTimeException("Invalid day of year of Hijrah date");
528          }
529     }
530 
checkValidMonth(int month)531     private static void checkValidMonth(int month) {
532          if (month < 1 || month > 12) {
533              throw new DateTimeException("Invalid month of Hijrah date");
534          }
535     }
536 
checkValidDayOfMonth(int dayOfMonth)537     private static void checkValidDayOfMonth(int dayOfMonth) {
538          if (dayOfMonth < 1  ||
539                  dayOfMonth > getMaximumDayOfMonth()) {
540              throw new DateTimeException("Invalid day of month of Hijrah date, day "
541                      + dayOfMonth + " greater than " + getMaximumDayOfMonth() + " or less than 1");
542          }
543     }
544 
545     /**
546      * Obtains an instance of {@code HijrahDate} from a date.
547      *
548      * @param date  the date to use, not null
549      * @return the Hijrah date, never null
550      * @throws IllegalCalendarFieldValueException if the year is invalid
551      */
of(LocalDate date)552     static HijrahDate of(LocalDate date) {
553         long gregorianDays = date.toEpochDay();
554         return new HijrahDate(gregorianDays);
555     }
556 
ofEpochDay(long epochDay)557     static HijrahDate ofEpochDay(long epochDay) {
558         return new HijrahDate(epochDay);
559     }
560 
561     /**
562      * Obtains a {@code HijrahDate} of the Islamic Umm Al-Qura calendar from a temporal object.
563      * <p>
564      * This obtains a date in the Hijrah calendar system based on the specified temporal.
565      * A {@code TemporalAccessor} represents an arbitrary set of date and time information,
566      * which this factory converts to an instance of {@code HijrahDate}.
567      * <p>
568      * The conversion typically uses the {@link ChronoField#EPOCH_DAY EPOCH_DAY}
569      * field, which is standardized across calendar systems.
570      * <p>
571      * This method matches the signature of the functional interface {@link TemporalQuery}
572      * allowing it to be used as a query via method reference, {@code HijrahDate::from}.
573      *
574      * @param temporal  the temporal object to convert, not null
575      * @return the date in Hijrah calendar system, not null
576      * @throws DateTimeException if unable to convert to a {@code HijrahDate}
577      */
from(TemporalAccessor temporal)578     public static HijrahDate from(TemporalAccessor temporal) {
579         return HijrahChronology.INSTANCE.date(temporal);
580     }
581 
582     //-------------------------------------------------------------------------
583     /**
584      * Constructs an instance with the specified date.
585      *
586      * @param gregorianDay  the number of days from 0001/01/01 (Gregorian), caller calculated
587      */
HijrahDate(long gregorianDay)588     private HijrahDate(long gregorianDay) {
589         int[] dateInfo = getHijrahDateInfo(gregorianDay);
590 
591         checkValidYearOfEra(dateInfo[1]);
592         checkValidMonth(dateInfo[2]);
593         checkValidDayOfMonth(dateInfo[3]);
594         checkValidDayOfYear(dateInfo[4]);
595 
596         this.era = HijrahEra.of(dateInfo[0]);
597         this.yearOfEra = dateInfo[1];
598         this.monthOfYear = dateInfo[2];
599         this.dayOfMonth = dateInfo[3];
600         this.dayOfYear = dateInfo[4];
601         this.dayOfWeek = DayOfWeek.of(dateInfo[5]);
602         this.gregorianEpochDay = gregorianDay;
603         this.isLeapYear = isLeapYear(this.yearOfEra);
604     }
605 
606     /**
607      * Replaces the date instance from the stream with a valid one.
608      *
609      * @return the resolved date, never null
610      */
readResolve()611     private Object readResolve() {
612         return new HijrahDate(this.gregorianEpochDay);
613     }
614 
615     //-----------------------------------------------------------------------
616     @Override
getChronology()617     public HijrahChronology getChronology() {
618         return HijrahChronology.INSTANCE;
619     }
620 
621     @Override
getEra()622     public HijrahEra getEra() {
623         return this.era;
624     }
625 
626     @Override
range(TemporalField field)627     public ValueRange range(TemporalField field) {
628         if (field instanceof ChronoField) {
629             if (isSupported(field)) {
630                 ChronoField f = (ChronoField) field;
631                 switch (f) {
632                     case DAY_OF_MONTH: return ValueRange.of(1, lengthOfMonth());
633                     case DAY_OF_YEAR: return ValueRange.of(1, lengthOfYear());
634                     case ALIGNED_WEEK_OF_MONTH: return ValueRange.of(1, 5);  // TODO
635                     case YEAR_OF_ERA: return ValueRange.of(1, 1000);  // TODO
636                 }
637                 return getChronology().range(f);
638             }
639             throw new UnsupportedTemporalTypeException("Unsupported field: " + field);
640         }
641         return field.rangeRefinedBy(this);
642     }
643 
644     @Override
getLong(TemporalField field)645     public long getLong(TemporalField field) {
646         if (field instanceof ChronoField) {
647             switch ((ChronoField) field) {
648                 case DAY_OF_WEEK: return dayOfWeek.getValue();
649                 case ALIGNED_DAY_OF_WEEK_IN_MONTH: return ((dayOfMonth - 1) % 7) + 1;
650                 case ALIGNED_DAY_OF_WEEK_IN_YEAR: return ((dayOfYear - 1) % 7) + 1;
651                 case DAY_OF_MONTH: return this.dayOfMonth;
652                 case DAY_OF_YEAR: return this.dayOfYear;
653                 case EPOCH_DAY: return toEpochDay();
654                 case ALIGNED_WEEK_OF_MONTH: return ((dayOfMonth - 1) / 7) + 1;
655                 case ALIGNED_WEEK_OF_YEAR: return ((dayOfYear - 1) / 7) + 1;
656                 case MONTH_OF_YEAR: return monthOfYear;
657                 case YEAR_OF_ERA: return yearOfEra;
658                 case YEAR: return yearOfEra;
659                 case ERA: return era.getValue();
660             }
661             throw new UnsupportedTemporalTypeException("Unsupported field: " + field);
662         }
663         return field.getFrom(this);
664     }
665 
666     //-------------------------------------------------------------------------
667     @Override
with(TemporalAdjuster adjuster)668     public HijrahDate with(TemporalAdjuster adjuster) {
669         return (HijrahDate) super.with(adjuster);
670     }
671 
672     @Override
with(TemporalField field, long newValue)673     public HijrahDate with(TemporalField field, long newValue) {
674         if (field instanceof ChronoField) {
675             ChronoField f = (ChronoField) field;
676             f.checkValidValue(newValue);        // TODO: validate value
677             int nvalue = (int) newValue;
678             switch (f) {
679                 case DAY_OF_WEEK: return plusDays(newValue - dayOfWeek.getValue());
680                 case ALIGNED_DAY_OF_WEEK_IN_MONTH: return plusDays(newValue - getLong(ALIGNED_DAY_OF_WEEK_IN_MONTH));
681                 case ALIGNED_DAY_OF_WEEK_IN_YEAR: return plusDays(newValue - getLong(ALIGNED_DAY_OF_WEEK_IN_YEAR));
682                 case DAY_OF_MONTH: return resolvePreviousValid(yearOfEra, monthOfYear, nvalue);
683                 case DAY_OF_YEAR: return resolvePreviousValid(yearOfEra, ((nvalue - 1) / 30) + 1, ((nvalue - 1) % 30) + 1);
684                 case EPOCH_DAY: return new HijrahDate(nvalue);
685                 case ALIGNED_WEEK_OF_MONTH: return plusDays((newValue - getLong(ALIGNED_WEEK_OF_MONTH)) * 7);
686                 case ALIGNED_WEEK_OF_YEAR: return plusDays((newValue - getLong(ALIGNED_WEEK_OF_YEAR)) * 7);
687                 case MONTH_OF_YEAR: return resolvePreviousValid(yearOfEra, nvalue, dayOfMonth);
688                 case YEAR_OF_ERA: return resolvePreviousValid(yearOfEra >= 1 ? nvalue : 1 - nvalue, monthOfYear, dayOfMonth);
689                 case YEAR: return resolvePreviousValid(nvalue, monthOfYear, dayOfMonth);
690                 case ERA: return resolvePreviousValid(1 - yearOfEra, monthOfYear, dayOfMonth);
691             }
692             throw new UnsupportedTemporalTypeException("Unsupported field: " + field);
693         }
694         return field.adjustInto(this, newValue);
695     }
696 
resolvePreviousValid(int yearOfEra, int month, int day)697     private static HijrahDate resolvePreviousValid(int yearOfEra, int month, int day) {
698         int monthDays = getMonthDays(month - 1, yearOfEra);
699         if (day > monthDays) {
700             day = monthDays;
701         }
702         return HijrahDate.of(yearOfEra, month, day);
703     }
704 
705     @Override
plus(TemporalAmount amount)706     public HijrahDate plus(TemporalAmount amount) {
707         return (HijrahDate) super.plus(amount);
708     }
709 
710     @Override
plus(long amountToAdd, TemporalUnit unit)711     public HijrahDate plus(long amountToAdd, TemporalUnit unit) {
712         return (HijrahDate) super.plus(amountToAdd, unit);
713     }
714 
715     @Override
minus(TemporalAmount amount)716     public HijrahDate minus(TemporalAmount amount) {
717         return (HijrahDate) super.minus(amount);
718     }
719 
720     @Override
minus(long amountToAdd, TemporalUnit unit)721     public HijrahDate minus(long amountToAdd, TemporalUnit unit) {
722         return (HijrahDate) super.minus(amountToAdd, unit);
723     }
724 
725     //-------------------------------------------------------------------------
726     @Override
727     @SuppressWarnings("unchecked")
atTime(LocalTime localTime)728     public final ChronoLocalDateTime<HijrahDate> atTime(LocalTime localTime) {
729         return (ChronoLocalDateTime<HijrahDate>) super.atTime(localTime);
730     }
731 
732     @Override
toEpochDay()733     public long toEpochDay() {
734          return getGregorianEpochDay(yearOfEra, monthOfYear, dayOfMonth);
735     }
736 
737     //-----------------------------------------------------------------------
738     /**
739      * Checks if the year is a leap year, according to the Hijrah calendar system rules.
740      *
741      * @return true if this date is in a leap year
742      */
743     @Override
isLeapYear()744     public boolean isLeapYear() {
745         return this.isLeapYear;
746     }
747 
748     //-----------------------------------------------------------------------
749     @Override
plusYears(long years)750     HijrahDate plusYears(long years) {
751         if (years == 0) {
752             return this;
753         }
754         int newYear = Jdk8Methods.safeAdd(this.yearOfEra, (int)years);
755         return HijrahDate.of(this.era, newYear, this.monthOfYear, this.dayOfMonth);
756     }
757 
758     @Override
plusMonths(long months)759     HijrahDate plusMonths(long months) {
760         if (months == 0) {
761             return this;
762         }
763         int newMonth = this.monthOfYear - 1;
764         newMonth = newMonth + (int)months;
765         int years = newMonth / 12;
766         newMonth = newMonth % 12;
767         while (newMonth < 0) {
768             newMonth += 12;
769             years = Jdk8Methods.safeSubtract(years, 1);
770         }
771         int newYear = Jdk8Methods.safeAdd(this.yearOfEra, years);
772         return HijrahDate.of(this.era, newYear, newMonth + 1, this.dayOfMonth);
773     }
774 
775     @Override
plusDays(long days)776     HijrahDate plusDays(long days) {
777         return new HijrahDate(this.gregorianEpochDay + days);
778     }
779 
780     //-----------------------------------------------------------------------
781     /**
782      * Returns the int array containing the following field from the julian day.
783      *
784      * int[0] = ERA
785      * int[1] = YEAR
786      * int[2] = MONTH
787      * int[3] = DATE
788      * int[4] = DAY_OF_YEAR
789      * int[5] = DAY_OF_WEEK
790      *
791      * @param julianDay  a julian day.
792      */
getHijrahDateInfo(long gregorianDays)793     private static int[] getHijrahDateInfo(long gregorianDays) {
794         int era, year, month, date, dayOfWeek, dayOfYear;
795 
796         int cycleNumber, yearInCycle, dayOfCycle;
797 
798         long epochDay = gregorianDays - HIJRAH_JAN_1_1_GREGORIAN_DAY;
799 
800         if (epochDay >= 0) {
801             cycleNumber = getCycleNumber(epochDay); // 0 - 99.
802             dayOfCycle = getDayOfCycle(epochDay, cycleNumber); // 0 - 10631.
803             yearInCycle = getYearInCycle(cycleNumber, dayOfCycle); // 0 - 29.
804             dayOfYear = getDayOfYear(cycleNumber, dayOfCycle, yearInCycle);
805             // 0 - 354/355
806             year = cycleNumber * 30 + yearInCycle + 1; // 1-based year.
807             month = getMonthOfYear(dayOfYear, year); // 0-based month-of-year
808             date = getDayOfMonth(dayOfYear, month, year); // 0-based date
809             ++date; // Convert from 0-based to 1-based
810             era = HijrahEra.AH.getValue();
811         } else {
812             cycleNumber = (int) epochDay / 10631; // 0 or negative number.
813             dayOfCycle = (int) epochDay % 10631; // -10630 - 0.
814             if (dayOfCycle == 0) {
815                 dayOfCycle = -10631;
816                 cycleNumber++;
817             }
818             yearInCycle = getYearInCycle(cycleNumber, dayOfCycle); // 0 - 29.
819             dayOfYear = getDayOfYear(cycleNumber, dayOfCycle, yearInCycle);
820             year = cycleNumber * 30 - yearInCycle; // negative number.
821             year = 1 - year;
822             dayOfYear = (isLeapYear(year) ? (dayOfYear + 355)
823                     : (dayOfYear + 354));
824             month = getMonthOfYear(dayOfYear, year);
825             date = getDayOfMonth(dayOfYear, month, year);
826             ++date; // Convert from 0-based to 1-based
827             era = HijrahEra.BEFORE_AH.getValue();
828         }
829         // Hijrah day zero is a Friday
830         dayOfWeek = (int) ((epochDay + 5) % 7);
831         dayOfWeek += (dayOfWeek <= 0) ? 7 : 0;
832 
833         int dateInfo[] = new int[6];
834         dateInfo[0] = era;
835         dateInfo[1] = year;
836         dateInfo[2] = month + 1; // change to 1-based.
837         dateInfo[3] = date;
838         dateInfo[4] = dayOfYear + 1; // change to 1-based.
839         dateInfo[5] = dayOfWeek;
840         return dateInfo;
841     }
842 
843     /**
844      * Return Gregorian epoch day from Hijrah year, month, and day.
845      *
846      * @param prolepticYear  the year to represent, caller calculated
847      * @param monthOfYear  the month-of-year to represent, caller calculated
848      * @param dayOfMonth  the day-of-month to represent, caller calculated
849      * @return a julian day
850      */
getGregorianEpochDay(int prolepticYear, int monthOfYear, int dayOfMonth)851     private static long getGregorianEpochDay(int prolepticYear, int monthOfYear, int dayOfMonth) {
852         long day = yearToGregorianEpochDay(prolepticYear);
853         day += getMonthDays(monthOfYear - 1, prolepticYear);
854         day += dayOfMonth;
855         return day;
856     }
857 
858     /**
859      * Returns the Gregorian epoch day from the proleptic year
860      * @param prolepticYear the proleptic year
861      * @return the Epoch day
862      */
yearToGregorianEpochDay(int prolepticYear)863     private static long yearToGregorianEpochDay(int prolepticYear) {
864 
865         int cycleNumber = (prolepticYear - 1) / 30; // 0-based.
866         int yearInCycle = (prolepticYear - 1) % 30; // 0-based.
867 
868         int dayInCycle = getAdjustedCycle(cycleNumber)[Math.abs(yearInCycle)]
869                 .intValue();
870 
871         if (yearInCycle < 0) {
872             dayInCycle = -dayInCycle;
873         }
874 
875         Long cycleDays;
876 
877         try {
878             cycleDays = ADJUSTED_CYCLES[cycleNumber];
879         } catch (ArrayIndexOutOfBoundsException e) {
880             cycleDays = null;
881         }
882 
883         if (cycleDays == null) {
884             cycleDays = Long.valueOf(cycleNumber * 10631);
885         }
886 
887         return (cycleDays.longValue() + dayInCycle + HIJRAH_JAN_1_1_GREGORIAN_DAY - 1);
888     }
889 
890     /**
891      * Returns the 30 year cycle number from the epoch day.
892      *
893      * @param epochDay  an epoch day
894      * @return a cycle number
895      */
getCycleNumber(long epochDay)896     private static int getCycleNumber(long epochDay) {
897         Long[] days = ADJUSTED_CYCLES;
898         int cycleNumber;
899         try {
900             for (int i = 0; i < days.length; i++) {
901                 if (epochDay < days[i].longValue()) {
902                     return i - 1;
903                 }
904             }
905             cycleNumber = (int) epochDay / 10631;
906         } catch (ArrayIndexOutOfBoundsException e) {
907             cycleNumber = (int) epochDay / 10631;
908         }
909         return cycleNumber;
910     }
911 
912     /**
913      * Returns day of cycle from the epoch day and cycle number.
914      *
915      * @param epochDay  an epoch day
916      * @param cycleNumber  a cycle number
917      * @return a day of cycle
918      */
getDayOfCycle(long epochDay, int cycleNumber)919     private static int getDayOfCycle(long epochDay, int cycleNumber) {
920         Long day;
921 
922         try {
923             day = ADJUSTED_CYCLES[cycleNumber];
924         } catch (ArrayIndexOutOfBoundsException e) {
925             day = null;
926         }
927         if (day == null) {
928             day = Long.valueOf(cycleNumber * 10631);
929         }
930         return (int) (epochDay - day.longValue());
931     }
932 
933     /**
934      * Returns the year in cycle from the cycle number and day of cycle.
935      *
936      * @param cycleNumber  a cycle number
937      * @param dayOfCycle  day of cycle
938      * @return a year in cycle
939      */
getYearInCycle(int cycleNumber, long dayOfCycle)940     private static int getYearInCycle(int cycleNumber, long dayOfCycle) {
941         Integer[] cycles = getAdjustedCycle(cycleNumber);
942         if (dayOfCycle == 0) {
943             return 0;
944         }
945 
946         if (dayOfCycle > 0) {
947             for (int i = 0; i < cycles.length; i++) {
948                 if (dayOfCycle < cycles[i].intValue()) {
949                     return i - 1;
950                 }
951             }
952             return 29;
953         } else {
954             dayOfCycle = -dayOfCycle;
955             for (int i = 0; i < cycles.length; i++) {
956                 if (dayOfCycle <= cycles[i].intValue()) {
957                     return i - 1;
958                 }
959             }
960             return 29;
961         }
962     }
963 
964     /**
965      * Returns adjusted 30 year cycle startind day as Integer array from the
966      * cycle number specified.
967      *
968      * @param cycleNumber  a cycle number
969      * @return an Integer array
970      */
getAdjustedCycle(int cycleNumber)971     private static Integer[] getAdjustedCycle(int cycleNumber) {
972         Integer[] cycles;
973         try {
974             cycles = ADJUSTED_CYCLE_YEARS.get(Integer.valueOf(cycleNumber));
975         } catch (ArrayIndexOutOfBoundsException e) {
976             cycles = null;
977         }
978         if (cycles == null) {
979             cycles = DEFAULT_CYCLE_YEARS;
980         }
981         return cycles;
982     }
983 
984     /**
985      * Returns adjusted month days as Integer array form the year specified.
986      *
987      * @param year  a year
988      * @return an Integer array
989      */
getAdjustedMonthDays(int year)990     private static Integer[] getAdjustedMonthDays(int year) {
991         Integer[] newMonths;
992         try {
993             newMonths = ADJUSTED_MONTH_DAYS.get(Integer.valueOf(year));
994         } catch (ArrayIndexOutOfBoundsException e) {
995             newMonths = null;
996         }
997         if (newMonths == null) {
998             if (isLeapYear(year)) {
999                 newMonths = DEFAULT_LEAP_MONTH_DAYS;
1000             } else {
1001                 newMonths = DEFAULT_MONTH_DAYS;
1002             }
1003         }
1004         return newMonths;
1005     }
1006 
1007     /**
1008      * Returns adjusted month length as Integer array form the year specified.
1009      *
1010      * @param year  a year
1011      * @return an Integer array
1012      */
getAdjustedMonthLength(int year)1013     private static Integer[] getAdjustedMonthLength(int year) {
1014         Integer[] newMonths;
1015         try {
1016             newMonths = ADJUSTED_MONTH_LENGTHS.get(Integer.valueOf(year));
1017         } catch (ArrayIndexOutOfBoundsException e) {
1018             newMonths = null;
1019         }
1020         if (newMonths == null) {
1021             if (isLeapYear(year)) {
1022                 newMonths = DEFAULT_LEAP_MONTH_LENGTHS;
1023             } else {
1024                 newMonths = DEFAULT_MONTH_LENGTHS;
1025             }
1026         }
1027         return newMonths;
1028     }
1029 
1030     /**
1031      * Returns day-of-year.
1032      *
1033      * @param cycleNumber  a cycle number
1034      * @param dayOfCycle  day of cycle
1035      * @param yearInCycle  year in cycle
1036      * @return day-of-year
1037      */
getDayOfYear(int cycleNumber, int dayOfCycle, int yearInCycle)1038     private static int getDayOfYear(int cycleNumber, int dayOfCycle, int yearInCycle) {
1039         Integer[] cycles = getAdjustedCycle(cycleNumber);
1040 
1041         if (dayOfCycle > 0) {
1042             return dayOfCycle - cycles[yearInCycle].intValue();
1043         } else {
1044             return cycles[yearInCycle].intValue() + dayOfCycle;
1045         }
1046     }
1047 
1048     /**
1049      * Returns month-of-year. 0-based.
1050      *
1051      * @param dayOfYear  day-of-year
1052      * @param year  a year
1053      * @return month-of-year
1054      */
getMonthOfYear(int dayOfYear, int year)1055     private static int getMonthOfYear(int dayOfYear, int year) {
1056 
1057         Integer[] newMonths = getAdjustedMonthDays(year);
1058 
1059         if (dayOfYear >= 0) {
1060             for (int i = 0; i < newMonths.length; i++) {
1061                 if (dayOfYear < newMonths[i].intValue()) {
1062                     return i - 1;
1063                 }
1064             }
1065             return 11;
1066         } else {
1067             dayOfYear = (isLeapYear(year) ? (dayOfYear + 355)
1068                     : (dayOfYear + 354));
1069             for (int i = 0; i < newMonths.length; i++) {
1070                 if (dayOfYear < newMonths[i].intValue()) {
1071                     return i - 1;
1072                 }
1073             }
1074             return 11;
1075         }
1076     }
1077 
1078     /**
1079      * Returns day-of-month.
1080      *
1081      * @param dayOfYear  day of  year
1082      * @param month  month
1083      * @param year  year
1084      * @return day-of-month
1085      */
getDayOfMonth(int dayOfYear, int month, int year)1086     private static int getDayOfMonth(int dayOfYear, int month, int year) {
1087 
1088         Integer[] newMonths = getAdjustedMonthDays(year);
1089 
1090         if (dayOfYear >= 0) {
1091             if (month > 0) {
1092                 return dayOfYear - newMonths[month].intValue();
1093             } else {
1094                 return dayOfYear;
1095             }
1096         } else {
1097             dayOfYear = (isLeapYear(year) ? (dayOfYear + 355)
1098                     : (dayOfYear + 354));
1099             if (month > 0) {
1100                 return dayOfYear - newMonths[month].intValue();
1101             } else {
1102                 return dayOfYear;
1103             }
1104         }
1105     }
1106 
1107     /**
1108      * Determines if the given year is a leap year.
1109      *
1110      * @param year  year
1111      * @return true if leap year
1112      */
isLeapYear(long year)1113     static boolean isLeapYear(long year) {
1114         return (14 + 11 * (year > 0 ? year : -year)) % 30 < 11;
1115     }
1116 
1117     /**
1118      * Returns month days from the beginning of year.
1119      *
1120      * @param month  month (0-based)
1121      * @parma year  year
1122      * @return month days from the beginning of year
1123      */
getMonthDays(int month, int year)1124     private static int getMonthDays(int month, int year) {
1125         Integer[] newMonths = getAdjustedMonthDays(year);
1126         return newMonths[month].intValue();
1127     }
1128 
1129     /**
1130      * Returns month length.
1131      *
1132      * @param month  month (0-based)
1133      * @param year  year
1134      * @return month length
1135      */
getMonthLength(int month, int year)1136     static int getMonthLength(int month, int year) {
1137       Integer[] newMonths = getAdjustedMonthLength(year);
1138       return newMonths[month].intValue();
1139     }
1140 
1141     @Override
lengthOfMonth()1142     public int lengthOfMonth() {
1143         return getMonthLength(monthOfYear - 1, yearOfEra);
1144     }
1145 
1146     /**
1147      * Returns year length.
1148      *
1149      * @param year  year
1150      * @return year length
1151      */
getYearLength(int year)1152     static int getYearLength(int year) {
1153 
1154         int cycleNumber = (year - 1) / 30;
1155         Integer[] cycleYears;
1156         try {
1157             cycleYears = ADJUSTED_CYCLE_YEARS.get(cycleNumber);
1158         } catch (ArrayIndexOutOfBoundsException e) {
1159             cycleYears = null;
1160         }
1161         if (cycleYears != null) {
1162             int yearInCycle = (year - 1) % 30;
1163             if (yearInCycle == 29) {
1164                 return ADJUSTED_CYCLES[cycleNumber + 1].intValue()
1165                         - ADJUSTED_CYCLES[cycleNumber].intValue()
1166                         - cycleYears[yearInCycle].intValue();
1167             }
1168             return cycleYears[yearInCycle + 1].intValue()
1169                     - cycleYears[yearInCycle].intValue();
1170         } else {
1171             return isLeapYear(year) ? 355 : 354;
1172         }
1173     }
1174 
1175     @Override
lengthOfYear()1176     public int lengthOfYear() {
1177         return getYearLength(yearOfEra);  // TODO: proleptic year
1178     }
1179 
1180     /**
1181      * Returns maximum day-of-month.
1182      *
1183      * @return maximum day-of-month
1184      */
getMaximumDayOfMonth()1185     static int getMaximumDayOfMonth() {
1186         return ADJUSTED_MAX_VALUES[POSITION_DAY_OF_MONTH];
1187     }
1188 
1189     /**
1190      * Returns smallest maximum day-of-month.
1191      *
1192      * @return smallest maximum day-of-month
1193      */
getSmallestMaximumDayOfMonth()1194     static int getSmallestMaximumDayOfMonth() {
1195         return ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_MONTH];
1196     }
1197 
1198     /**
1199      * Returns maximum day-of-year.
1200      *
1201      * @return maximum day-of-year
1202      */
getMaximumDayOfYear()1203     static int getMaximumDayOfYear() {
1204         return ADJUSTED_MAX_VALUES[POSITION_DAY_OF_YEAR];
1205     }
1206 
1207     /**
1208      * Returns smallest maximum day-of-year.
1209      *
1210      * @return smallest maximum day-of-year
1211      */
getSmallestMaximumDayOfYear()1212     static int getSmallestMaximumDayOfYear() {
1213         return ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_YEAR];
1214     }
1215 
1216     // ----- Deviation handling -----//
1217 
1218     /**
1219      * Adds deviation definition. The year and month sepcifed should be the
1220      * caluculated Hijrah year and month. The month is 0 based. e.g. 8 for
1221      * Ramadan (9th month) Addition of anything minus deviation days is
1222      * calculated negatively in the case the user wants to subtract days from
1223      * the calendar. For example, adding -1 days will subtract one day from the
1224      * current date. Please note that this behavior is different from the
1225      * addDeviaiton method.
1226      *
1227      * @param startYear  start year
1228      * @param startMonth  start month
1229      * @param endYear  end year
1230      * @param endMonth  end month
1231      * @param offset  offset
1232      */
addDeviationAsHijrah(int startYear, int startMonth, int endYear, int endMonth, int offset)1233     private static void addDeviationAsHijrah(int startYear,
1234             int startMonth, int endYear, int endMonth, int offset) {
1235 
1236         if (startYear < 1) {
1237             throw new IllegalArgumentException("startYear < 1");
1238         }
1239         if (endYear < 1) {
1240             throw new IllegalArgumentException("endYear < 1");
1241         }
1242         if (startMonth < 0 || startMonth > 11) {
1243             throw new IllegalArgumentException(
1244                     "startMonth < 0 || startMonth > 11");
1245         }
1246         if (endMonth < 0 || endMonth > 11) {
1247             throw new IllegalArgumentException("endMonth < 0 || endMonth > 11");
1248         }
1249         if (endYear > 9999) {
1250             throw new IllegalArgumentException("endYear > 9999");
1251         }
1252         if (endYear < startYear) {
1253             throw new IllegalArgumentException("startYear > endYear");
1254         }
1255         if (endYear == startYear && endMonth < startMonth) {
1256             throw new IllegalArgumentException(
1257                     "startYear == endYear && endMonth < startMonth");
1258         }
1259 
1260         // Adjusting start year.
1261         boolean isStartYLeap = isLeapYear(startYear);
1262 
1263         // Adjusting the number of month.
1264         Integer[] orgStartMonthNums = ADJUSTED_MONTH_DAYS.get(Integer.valueOf(
1265                 startYear));
1266         if (orgStartMonthNums == null) {
1267             if (isStartYLeap) {
1268                 orgStartMonthNums = new Integer[LEAP_NUM_DAYS.length];
1269                 for (int l = 0; l < LEAP_NUM_DAYS.length; l++) {
1270                     orgStartMonthNums[l] = Integer.valueOf(LEAP_NUM_DAYS[l]);
1271                 }
1272             } else {
1273                 orgStartMonthNums = new Integer[NUM_DAYS.length];
1274                 for (int l = 0; l < NUM_DAYS.length; l++) {
1275                     orgStartMonthNums[l] = Integer.valueOf(NUM_DAYS[l]);
1276                 }
1277             }
1278         }
1279 
1280         Integer[] newStartMonthNums = new Integer[orgStartMonthNums.length];
1281 
1282         for (int month = 0; month < 12; month++) {
1283             if (month > startMonth) {
1284                 newStartMonthNums[month] = Integer.valueOf(orgStartMonthNums[month]
1285                         .intValue()
1286                         - offset);
1287             } else {
1288                 newStartMonthNums[month] = Integer.valueOf(orgStartMonthNums[month]
1289                         .intValue());
1290             }
1291         }
1292 
1293         ADJUSTED_MONTH_DAYS.put(Integer.valueOf(startYear), newStartMonthNums);
1294 
1295         // Adjusting the days of month.
1296 
1297         Integer[] orgStartMonthLengths = ADJUSTED_MONTH_LENGTHS.get(Integer.valueOf(
1298                 startYear));
1299         if (orgStartMonthLengths == null) {
1300             if (isStartYLeap) {
1301                 orgStartMonthLengths = new Integer[LEAP_MONTH_LENGTH.length];
1302                 for (int l = 0; l < LEAP_MONTH_LENGTH.length; l++) {
1303                     orgStartMonthLengths[l] = Integer.valueOf(LEAP_MONTH_LENGTH[l]);
1304                 }
1305             } else {
1306                 orgStartMonthLengths = new Integer[MONTH_LENGTH.length];
1307                 for (int l = 0; l < MONTH_LENGTH.length; l++) {
1308                     orgStartMonthLengths[l] = Integer.valueOf(MONTH_LENGTH[l]);
1309                 }
1310             }
1311         }
1312 
1313         Integer[] newStartMonthLengths = new Integer[orgStartMonthLengths.length];
1314 
1315         for (int month = 0; month < 12; month++) {
1316             if (month == startMonth) {
1317                 newStartMonthLengths[month] = Integer.valueOf(
1318                         orgStartMonthLengths[month].intValue() - offset);
1319             } else {
1320                 newStartMonthLengths[month] = Integer.valueOf(
1321                         orgStartMonthLengths[month].intValue());
1322             }
1323         }
1324 
1325         ADJUSTED_MONTH_LENGTHS.put(Integer.valueOf(startYear), newStartMonthLengths);
1326 
1327         if (startYear != endYear) {
1328             // System.out.println("over year");
1329             // Adjusting starting 30 year cycle.
1330             int sCycleNumber = (startYear - 1) / 30;
1331             int sYearInCycle = (startYear - 1) % 30; // 0-based.
1332             Integer[] startCycles = ADJUSTED_CYCLE_YEARS.get(Integer.valueOf(
1333                     sCycleNumber));
1334             if (startCycles == null) {
1335                 startCycles = new Integer[CYCLEYEAR_START_DATE.length];
1336                 for (int j = 0; j < startCycles.length; j++) {
1337                     startCycles[j] = Integer.valueOf(CYCLEYEAR_START_DATE[j]);
1338                 }
1339             }
1340 
1341             for (int j = sYearInCycle + 1; j < CYCLEYEAR_START_DATE.length; j++) {
1342                 startCycles[j] = Integer.valueOf(startCycles[j].intValue() - offset);
1343             }
1344 
1345             // System.out.println(sCycleNumber + ":" + sYearInCycle);
1346             ADJUSTED_CYCLE_YEARS.put(Integer.valueOf(sCycleNumber), startCycles);
1347 
1348             int sYearInMaxY = (startYear - 1) / 30;
1349             int sEndInMaxY = (endYear - 1) / 30;
1350 
1351             if (sYearInMaxY != sEndInMaxY) {
1352                 // System.out.println("over 30");
1353                 // Adjusting starting 30 * MAX_ADJUSTED_CYCLE year cycle.
1354                 // System.out.println(sYearInMaxY);
1355 
1356                 for (int j = sYearInMaxY + 1; j < ADJUSTED_CYCLES.length; j++) {
1357                     ADJUSTED_CYCLES[j] = Long.valueOf(ADJUSTED_CYCLES[j].longValue()
1358                             - offset);
1359                 }
1360 
1361                 // Adjusting ending 30 * MAX_ADJUSTED_CYCLE year cycles.
1362                 for (int j = sEndInMaxY + 1; j < ADJUSTED_CYCLES.length; j++) {
1363                     ADJUSTED_CYCLES[j] = Long.valueOf(ADJUSTED_CYCLES[j].longValue()
1364                             + offset);
1365                 }
1366             }
1367 
1368             // Adjusting ending 30 year cycle.
1369             int eCycleNumber = (endYear - 1) / 30;
1370             int sEndInCycle = (endYear - 1) % 30; // 0-based.
1371             Integer[] endCycles = ADJUSTED_CYCLE_YEARS.get(Integer.valueOf(
1372                     eCycleNumber));
1373             if (endCycles == null) {
1374                 endCycles = new Integer[CYCLEYEAR_START_DATE.length];
1375                 for (int j = 0; j < endCycles.length; j++) {
1376                     endCycles[j] = Integer.valueOf(CYCLEYEAR_START_DATE[j]);
1377                 }
1378             }
1379             for (int j = sEndInCycle + 1; j < CYCLEYEAR_START_DATE.length; j++) {
1380                 endCycles[j] = Integer.valueOf(endCycles[j].intValue() + offset);
1381             }
1382             ADJUSTED_CYCLE_YEARS.put(Integer.valueOf(eCycleNumber), endCycles);
1383         }
1384 
1385         // Adjusting ending year.
1386         boolean isEndYLeap = isLeapYear(endYear);
1387 
1388         Integer[] orgEndMonthDays = ADJUSTED_MONTH_DAYS.get(Integer.valueOf(endYear));
1389 
1390         if (orgEndMonthDays == null) {
1391             if (isEndYLeap) {
1392                 orgEndMonthDays = new Integer[LEAP_NUM_DAYS.length];
1393                 for (int l = 0; l < LEAP_NUM_DAYS.length; l++) {
1394                     orgEndMonthDays[l] = Integer.valueOf(LEAP_NUM_DAYS[l]);
1395                 }
1396             } else {
1397                 orgEndMonthDays = new Integer[NUM_DAYS.length];
1398                 for (int l = 0; l < NUM_DAYS.length; l++) {
1399                     orgEndMonthDays[l] = Integer.valueOf(NUM_DAYS[l]);
1400                 }
1401             }
1402         }
1403 
1404         Integer[] newEndMonthDays = new Integer[orgEndMonthDays.length];
1405 
1406         for (int month = 0; month < 12; month++) {
1407             if (month > endMonth) {
1408                 newEndMonthDays[month] = Integer.valueOf(orgEndMonthDays[month]
1409                         .intValue()
1410                         + offset);
1411             } else {
1412                 newEndMonthDays[month] = Integer.valueOf(orgEndMonthDays[month]
1413                         .intValue());
1414             }
1415         }
1416 
1417         ADJUSTED_MONTH_DAYS.put(Integer.valueOf(endYear), newEndMonthDays);
1418 
1419         // Adjusting the days of month.
1420         Integer[] orgEndMonthLengths = ADJUSTED_MONTH_LENGTHS.get(Integer.valueOf(
1421                 endYear));
1422 
1423         if (orgEndMonthLengths == null) {
1424             if (isEndYLeap) {
1425                 orgEndMonthLengths = new Integer[LEAP_MONTH_LENGTH.length];
1426                 for (int l = 0; l < LEAP_MONTH_LENGTH.length; l++) {
1427                     orgEndMonthLengths[l] = Integer.valueOf(LEAP_MONTH_LENGTH[l]);
1428                 }
1429             } else {
1430                 orgEndMonthLengths = new Integer[MONTH_LENGTH.length];
1431                 for (int l = 0; l < MONTH_LENGTH.length; l++) {
1432                     orgEndMonthLengths[l] = Integer.valueOf(MONTH_LENGTH[l]);
1433                 }
1434             }
1435         }
1436 
1437         Integer[] newEndMonthLengths = new Integer[orgEndMonthLengths.length];
1438 
1439         for (int month = 0; month < 12; month++) {
1440             if (month == endMonth) {
1441                 newEndMonthLengths[month] = Integer.valueOf(
1442                         orgEndMonthLengths[month].intValue() + offset);
1443             } else {
1444                 newEndMonthLengths[month] = Integer.valueOf(
1445                         orgEndMonthLengths[month].intValue());
1446             }
1447         }
1448 
1449         ADJUSTED_MONTH_LENGTHS.put(Integer.valueOf(endYear), newEndMonthLengths);
1450 
1451         Integer[] startMonthLengths = ADJUSTED_MONTH_LENGTHS.get(Integer.valueOf(
1452                 startYear));
1453         Integer[] endMonthLengths = ADJUSTED_MONTH_LENGTHS.get(Integer.valueOf(
1454                 endYear));
1455         Integer[] startMonthDays = ADJUSTED_MONTH_DAYS
1456                 .get(Integer.valueOf(startYear));
1457         Integer[] endMonthDays = ADJUSTED_MONTH_DAYS.get(Integer.valueOf(endYear));
1458 
1459         int startMonthLength = startMonthLengths[startMonth].intValue();
1460         int endMonthLength = endMonthLengths[endMonth].intValue();
1461         int startMonthDay = startMonthDays[11].intValue()
1462                 + startMonthLengths[11].intValue();
1463         int endMonthDay = endMonthDays[11].intValue()
1464                 + endMonthLengths[11].intValue();
1465 
1466         int maxMonthLength = ADJUSTED_MAX_VALUES[POSITION_DAY_OF_MONTH]
1467                 .intValue();
1468         int leastMaxMonthLength = ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_MONTH]
1469                 .intValue();
1470 
1471         if (maxMonthLength < startMonthLength) {
1472             maxMonthLength = startMonthLength;
1473         }
1474         if (maxMonthLength < endMonthLength) {
1475             maxMonthLength = endMonthLength;
1476         }
1477         ADJUSTED_MAX_VALUES[POSITION_DAY_OF_MONTH] = Integer.valueOf(maxMonthLength);
1478 
1479         if (leastMaxMonthLength > startMonthLength) {
1480             leastMaxMonthLength = startMonthLength;
1481         }
1482         if (leastMaxMonthLength > endMonthLength) {
1483             leastMaxMonthLength = endMonthLength;
1484         }
1485         ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_MONTH] = Integer.valueOf(
1486                 leastMaxMonthLength);
1487 
1488         int maxMonthDay = ADJUSTED_MAX_VALUES[POSITION_DAY_OF_YEAR].intValue();
1489         int leastMaxMonthDay = ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_YEAR]
1490                 .intValue();
1491 
1492         if (maxMonthDay < startMonthDay) {
1493             maxMonthDay = startMonthDay;
1494         }
1495         if (maxMonthDay < endMonthDay) {
1496             maxMonthDay = endMonthDay;
1497         }
1498 
1499         ADJUSTED_MAX_VALUES[POSITION_DAY_OF_YEAR] = Integer.valueOf(maxMonthDay);
1500 
1501         if (leastMaxMonthDay > startMonthDay) {
1502             leastMaxMonthDay = startMonthDay;
1503         }
1504         if (leastMaxMonthDay > endMonthDay) {
1505             leastMaxMonthDay = endMonthDay;
1506         }
1507         ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_YEAR] = Integer.valueOf(
1508                 leastMaxMonthDay);
1509     }
1510 
1511     /**
1512      * Read hijrah_deviation.cfg file. The config file contains the deviation data with
1513      * following format.
1514      *
1515      * StartYear/StartMonth(0-based)-EndYear/EndMonth(0-based):Deviation day (1,
1516      * 2, -1, or -2)
1517      *
1518      * Line separator or ";" is used for the separator of each deviation data.
1519      *
1520      * Here is the example.
1521      *
1522      * 1429/0-1429/1:1
1523      * 1429/2-1429/7:1;1429/6-1429/11:1
1524      * 1429/11-9999/11:1
1525      *
1526      * @throws IOException for zip/jar file handling exception.
1527      * @throws ParseException if the format of the configuration file is wrong.
1528      */
readDeviationConfig()1529     private static void readDeviationConfig() throws IOException, ParseException {
1530         InputStream is = getConfigFileInputStream();
1531         if (is != null) {
1532             BufferedReader br = null;
1533             try {
1534                 br = new BufferedReader(new InputStreamReader(is));
1535                 String line = "";
1536                 int num = 0;
1537                 while ((line = br.readLine()) != null) {
1538                     num++;
1539                     line = line.trim();
1540                     parseLine(line, num);
1541                 }
1542             } finally {
1543                 if (br != null) {
1544                     br.close();
1545                 }
1546             }
1547         }
1548     }
1549 
1550     /**
1551      * Parse each deviation element.
1552      *
1553      * @param line  a line to parse
1554      * @param num  line number
1555      * @throws ParseException if line has incorrect format.
1556      */
parseLine(String line, int num)1557     private static void parseLine(String line, int num) throws ParseException {
1558         StringTokenizer st = new StringTokenizer(line, ";");
1559         while (st.hasMoreTokens()) {
1560             String deviationElement = st.nextToken();
1561             int offsetIndex = deviationElement.indexOf(':');
1562             if (offsetIndex != -1) {
1563                 String offsetString = deviationElement.substring(
1564                         offsetIndex + 1, deviationElement.length());
1565                 int offset;
1566                 try {
1567                     offset = Integer.parseInt(offsetString);
1568                 } catch (NumberFormatException ex) {
1569                     throw new ParseException(
1570                             "Offset is not properly set at line " + num + ".",
1571                             num);
1572                 }
1573                 int separatorIndex = deviationElement.indexOf('-');
1574                 if (separatorIndex != -1) {
1575                     String startDateStg = deviationElement.substring(0,
1576                             separatorIndex);
1577                     String endDateStg = deviationElement.substring(
1578                             separatorIndex + 1, offsetIndex);
1579                     int startDateYearSepIndex = startDateStg.indexOf('/');
1580                     int endDateYearSepIndex = endDateStg.indexOf('/');
1581                     int startYear = -1;
1582                     int endYear = -1;
1583                     int startMonth = -1;
1584                     int endMonth = -1;
1585                     if (startDateYearSepIndex != -1) {
1586                         String startYearStg = startDateStg.substring(0,
1587                                 startDateYearSepIndex);
1588                         String startMonthStg = startDateStg.substring(
1589                                 startDateYearSepIndex + 1, startDateStg
1590                                         .length());
1591                         try {
1592                             startYear = Integer.parseInt(startYearStg);
1593                         } catch (NumberFormatException ex) {
1594                             throw new ParseException(
1595                                     "Start year is not properly set at line "
1596                                             + num + ".", num);
1597                         }
1598                         try {
1599                             startMonth = Integer.parseInt(startMonthStg);
1600                         } catch (NumberFormatException ex) {
1601                             throw new ParseException(
1602                                     "Start month is not properly set at line "
1603                                             + num + ".", num);
1604                         }
1605                     } else {
1606                         throw new ParseException(
1607                                 "Start year/month has incorrect format at line "
1608                                         + num + ".", num);
1609                     }
1610                     if (endDateYearSepIndex != -1) {
1611                         String endYearStg = endDateStg.substring(0,
1612                                 endDateYearSepIndex);
1613                         String endMonthStg = endDateStg.substring(
1614                                 endDateYearSepIndex + 1, endDateStg.length());
1615                         try {
1616                             endYear = Integer.parseInt(endYearStg);
1617                         } catch (NumberFormatException ex) {
1618                             throw new ParseException(
1619                                     "End year is not properly set at line "
1620                                             + num + ".", num);
1621                         }
1622                         try {
1623                             endMonth = Integer.parseInt(endMonthStg);
1624                         } catch (NumberFormatException ex) {
1625                             throw new ParseException(
1626                                     "End month is not properly set at line "
1627                                             + num + ".", num);
1628                         }
1629                     } else {
1630                         throw new ParseException(
1631                                 "End year/month has incorrect format at line "
1632                                         + num + ".", num);
1633                     }
1634                     if (startYear != -1 && startMonth != -1 && endYear != -1
1635                             && endMonth != -1) {
1636                         addDeviationAsHijrah(startYear, startMonth, endYear,
1637                                 endMonth, offset);
1638                     } else {
1639                         throw new ParseException("Unknown error at line " + num
1640                                 + ".", num);
1641                     }
1642                 } else {
1643                     throw new ParseException(
1644                             "Start and end year/month has incorrect format at line "
1645                                     + num + ".", num);
1646                 }
1647             } else {
1648                 throw new ParseException("Offset has incorrect format at line "
1649                         + num + ".", num);
1650             }
1651         }
1652     }
1653 
1654     /**
1655      * Return InputStream for deviation configuration file.
1656      * The default location of the deviation file is:
1657      * <pre>
1658      *   $CLASSPATH/org/threeten/bp/chrono
1659      * </pre>
1660      * And the default file name is:
1661      * <pre>
1662      *   hijrah_deviation.cfg
1663      * </pre>
1664      * The default location and file name can be overriden by setting
1665      * following two Java's system property.
1666      * <pre>
1667      *   Location: org.threeten.bp.i18n.HijrahDate.deviationConfigDir
1668      *   File name: org.threeten.bp.i18n.HijrahDate.deviationConfigFile
1669      * </pre>
1670      * Regarding the file format, see readDeviationConfig() method for details.
1671      *
1672      * @return InputStream for file reading exception.
1673      * @throws IOException for zip/jar file handling exception.
1674      */
getConfigFileInputStream()1675     private static InputStream getConfigFileInputStream() throws IOException {
1676 
1677         String fileName = System
1678                 .getProperty("org.threeten.bp.i18n.HijrahDate.deviationConfigFile");
1679 
1680         if (fileName == null) {
1681             fileName = DEFAULT_CONFIG_FILENAME;
1682         }
1683 
1684         String dir = System
1685                 .getProperty("org.threeten.bp.i18n.HijrahDate.deviationConfigDir");
1686 
1687         if (dir != null) {
1688             if (!(dir.length() == 0 && dir.endsWith(System
1689                     .getProperty("file.separator")))) {
1690                 dir = dir + System.getProperty("file.separator");
1691             }
1692             File file = new File(dir + FILE_SEP + fileName);
1693             if (file.exists()) {
1694                 try {
1695                     return new FileInputStream(file);
1696                 } catch (IOException ioe) {
1697                     throw ioe;
1698                 }
1699             } else {
1700                 return null;
1701             }
1702         } else {
1703             String classPath = System.getProperty("java.class.path");
1704             StringTokenizer st = new StringTokenizer(classPath, PATH_SEP);
1705             while (st.hasMoreTokens()) {
1706                 String path = st.nextToken();
1707                 File file = new File(path);
1708                 if (file.exists()) {
1709                     if (file.isDirectory()) {
1710                         File f = new File(
1711                                 path + FILE_SEP + DEFAULT_CONFIG_PATH, fileName);
1712                         if (f.exists()) {
1713                             try {
1714                                 return new FileInputStream(path + FILE_SEP
1715                                         + DEFAULT_CONFIG_PATH + FILE_SEP
1716                                         + fileName);
1717                             } catch (IOException ioe) {
1718                                 throw ioe;
1719                             }
1720                         }
1721                     } else {
1722                         ZipFile zip;
1723                         try {
1724                             zip = new ZipFile(file);
1725                         } catch (IOException ioe) {
1726                             zip = null;
1727                         }
1728 
1729                         if (zip != null) {
1730                             String targetFile = DEFAULT_CONFIG_PATH + FILE_SEP
1731                                     + fileName;
1732                             ZipEntry entry = zip.getEntry(targetFile);
1733 
1734                             if (entry == null) {
1735                                 if (FILE_SEP == '/') {
1736                                     targetFile = targetFile.replace('/', '\\');
1737                                 } else if (FILE_SEP == '\\') {
1738                                     targetFile = targetFile.replace('\\', '/');
1739                                 }
1740                                 entry = zip.getEntry(targetFile);
1741                             }
1742 
1743                             if (entry != null) {
1744                                 try {
1745                                     return zip.getInputStream(entry);
1746                                 } catch (IOException ioe) {
1747                                     throw ioe;
1748                                 }
1749                             }
1750                         }
1751                     }
1752                 }
1753             }
1754             return null;
1755         }
1756     }
1757     //-----------------------------------------------------------------------
writeReplace()1758     private Object writeReplace() {
1759         return new Ser(Ser.HIJRAH_DATE_TYPE, this);
1760     }
1761 
writeExternal(DataOutput out)1762     void writeExternal(DataOutput out) throws IOException {
1763         // HijrahChrono is implicit in the Hijrah_DATE_TYPE
1764         out.writeInt(get(YEAR));
1765         out.writeByte(get(MONTH_OF_YEAR));
1766         out.writeByte(get(DAY_OF_MONTH));
1767     }
1768 
readExternal(DataInput in)1769     static ChronoLocalDate readExternal(DataInput in) throws IOException {
1770         int year = in.readInt();
1771         int month = in.readByte();
1772         int dayOfMonth = in.readByte();
1773         return HijrahChronology.INSTANCE.date(year, month, dayOfMonth);
1774     }
1775 
1776 }
1777