1 /*
   2  * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /*
  27  * Copyright (c) 2012, Stephen Colebourne & Michael Nascimento Santos
  28  *
  29  * All rights reserved.
  30  *
  31  * Redistribution and use in source and binary forms, with or without
  32  * modification, are permitted provided that the following conditions are met:
  33  *
  34  *  * Redistributions of source code must retain the above copyright notice,
  35  *    this list of conditions and the following disclaimer.
  36  *
  37  *  * Redistributions in binary form must reproduce the above copyright notice,
  38  *    this list of conditions and the following disclaimer in the documentation
  39  *    and/or other materials provided with the distribution.
  40  *
  41  *  * Neither the name of JSR-310 nor the names of its contributors
  42  *    may be used to endorse or promote products derived from this software
  43  *    without specific prior written permission.
  44  *
  45  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  46  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  47  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  48  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  49  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  50  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  51  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  52  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  53  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  54  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  55  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  56  */
  57 
  58 package java.time.chrono;
  59 
  60 import static java.time.temporal.ChronoField.EPOCH_DAY;
  61 
  62 import java.io.IOException;
  63 import java.io.Serializable;
  64 import java.text.ParseException;
  65 import java.time.Clock;
  66 import java.time.DateTimeException;
  67 import java.time.Instant;
  68 import java.time.LocalDate;
  69 import java.time.ZoneId;
  70 import java.time.temporal.ChronoField;
  71 import java.time.temporal.TemporalAccessor;
  72 import java.time.temporal.ValueRange;
  73 import java.util.Arrays;
  74 import java.util.HashMap;
  75 import java.util.List;
  76 import java.util.Locale;
  77 
  78 /**
  79  * The Hijrah calendar system.
  80  * <p>
  81  * This chronology defines the rules of the Hijrah calendar system.
  82  * <p>
  83  * The implementation follows the Freeman-Grenville algorithm (*1) and has following features.
  84  * <p><ul>
  85  * <li>A year has 12 months.</li>
  86  * <li>Over a cycle of 30 years there are 11 leap years.</li>
  87  * <li>There are 30 days in month number 1, 3, 5, 7, 9, and 11,
  88  * and 29 days in month number 2, 4, 6, 8, 10, and 12.</li>
  89  * <li>In a leap year month 12 has 30 days.</li>
  90  * <li>In a 30 year cycle, year 2, 5, 7, 10, 13, 16, 18, 21, 24,
  91  * 26, and 29 are leap years.</li>
  92  * <li>Total of 10631 days in a 30 years cycle.</li>
  93  * </ul><p>
  94  * <P>
  95  * The table shows the features described above.
  96  * <blockquote>
  97  * <table border="1">
  98  *   <caption>Hijrah Calendar Months</caption>
  99  *   <tbody>
 100  *     <tr>
 101  *       <th># of month</th>
 102  *       <th>Name of month</th>
 103  *       <th>Number of days</th>
 104  *     </tr>
 105  *     <tr>
 106  *       <td>1</td>
 107  *       <td>Muharram</td>
 108  *       <td>30</td>
 109  *     </tr>
 110  *     <tr>
 111  *       <td>2</td>
 112  *       <td>Safar</td>
 113  *       <td>29</td>
 114  *     </tr>
 115  *     <tr>
 116  *       <td>3</td>
 117  *       <td>Rabi'al-Awwal</td>
 118  *       <td>30</td>
 119  *     </tr>
 120  *     <tr>
 121  *       <td>4</td>
 122  *       <td>Rabi'ath-Thani</td>
 123  *       <td>29</td>
 124  *     </tr>
 125  *     <tr>
 126  *       <td>5</td>
 127  *       <td>Jumada l-Ula</td>
 128  *       <td>30</td>
 129  *     </tr>
 130  *     <tr>
 131  *       <td>6</td>
 132  *       <td>Jumada t-Tania</td>
 133  *       <td>29</td>
 134  *     </tr>
 135  *     <tr>
 136  *       <td>7</td>
 137  *       <td>Rajab</td>
 138  *       <td>30</td>
 139  *     </tr>
 140  *     <tr>
 141  *       <td>8</td>
 142  *       <td>Sha`ban</td>
 143  *       <td>29</td>
 144  *     </tr>
 145  *     <tr>
 146  *       <td>9</td>
 147  *       <td>Ramadan</td>
 148  *       <td>30</td>
 149  *     </tr>
 150  *     <tr>
 151  *       <td>10</td>
 152  *       <td>Shawwal</td>
 153  *       <td>29</td>
 154  *     </tr>
 155  *     <tr>
 156  *       <td>11</td>
 157  *       <td>Dhu 'l-Qa`da</td>
 158  *       <td>30</td>
 159  *     </tr>
 160  *     <tr>
 161  *       <td>12</td>
 162  *       <td>Dhu 'l-Hijja</td>
 163  *       <td>29, but 30 days in years 2, 5, 7, 10,<br>
 164  * 13, 16, 18, 21, 24, 26, and 29</td>
 165  *     </tr>
 166  *   </tbody>
 167  * </table>
 168  * </blockquote>
 169  * <p>
 170  * (*1) The algorithm is taken from the book,
 171  * The Muslim and Christian Calendars by G.S.P. Freeman-Grenville.
 172  * <p>
 173  *
 174  * <h3>Specification for implementors</h3>
 175  * This class is immutable and thread-safe.
 176  *
 177  * @since 1.8
 178  */
 179 public final class HijrahChronology extends Chronology implements Serializable {
 180 
 181     /**
 182      * The Hijrah Calendar id.
 183      */
 184     private final String typeId;
 185 
 186     /**
 187      * The Hijrah calendarType.
 188      */
 189     private final String calendarType;
 190 
 191     /**
 192      * The singleton instance for the era before the current one - Before Hijrah -
 193      * which has the value 0.
 194      */
 195     public static final Era ERA_BEFORE_AH = HijrahEra.BEFORE_AH;
 196     /**
 197      * The singleton instance for the current era - Hijrah - which has the value 1.
 198      */
 199     public static final Era ERA_AH = HijrahEra.AH;
 200     /**
 201      * Serialization version.
 202      */
 203     private static final long serialVersionUID = 3127340209035924785L;
 204     /**
 205      * The minimum valid year-of-era.
 206      */
 207     public static final int MIN_YEAR_OF_ERA = 1;
 208     /**
 209      * The maximum valid year-of-era.
 210      * This is currently set to 9999 but may be changed to increase the valid range
 211      * in a future version of the specification.
 212      */
 213     public static final int MAX_YEAR_OF_ERA = 9999;
 214 
 215     /**
 216      * Number of Gregorian day of July 19, year 622 (Gregorian), which is epoch day
 217      * of Hijrah calendar.
 218      */
 219     private static final int HIJRAH_JAN_1_1_GREGORIAN_DAY = -492148;
 220     /**
 221      * 0-based, for number of day-of-year in the beginning of month in normal
 222      * year.
 223      */
 224     private static final int NUM_DAYS[] =
 225         {0, 30, 59, 89, 118, 148, 177, 207, 236, 266, 295, 325};
 226     /**
 227      * 0-based, for number of day-of-year in the beginning of month in leap year.
 228      */
 229     private static final int LEAP_NUM_DAYS[] =
 230         {0, 30, 59, 89, 118, 148, 177, 207, 236, 266, 295, 325};
 231     /**
 232      * 0-based, for day-of-month in normal year.
 233      */
 234     private static final int MONTH_LENGTH[] =
 235         {30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29};
 236     /**
 237      * 0-based, for day-of-month in leap year.
 238      */
 239     private static final int LEAP_MONTH_LENGTH[] =
 240         {30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 30};
 241 
 242     /**
 243      * <pre>
 244      *                            Greatest       Least
 245      * Field name        Minimum   Minimum     Maximum     Maximum
 246      * ----------        -------   -------     -------     -------
 247      * ERA                     0         0           1           1
 248      * YEAR_OF_ERA             1         1        9999        9999
 249      * MONTH_OF_YEAR           1         1          12          12
 250      * DAY_OF_MONTH            1         1          29          30
 251      * DAY_OF_YEAR             1         1         354         355
 252      * </pre>
 253      *
 254      * Minimum values.
 255      */
 256     private static final int MIN_VALUES[] =
 257         {
 258         0,
 259         MIN_YEAR_OF_ERA,
 260         0,
 261         1,
 262         0,
 263         1,
 264         1
 265         };
 266 
 267     /**
 268      * Least maximum values.
 269      */
 270     private static final int LEAST_MAX_VALUES[] =
 271         {
 272         1,
 273         MAX_YEAR_OF_ERA,
 274         11,
 275         51,
 276         5,
 277         29,
 278         354
 279         };
 280 
 281     /**
 282      * Maximum values.
 283      */
 284     private static final int MAX_VALUES[] =
 285         {
 286         1,
 287         MAX_YEAR_OF_ERA,
 288         11,
 289         52,
 290         6,
 291         30,
 292         355
 293         };
 294 
 295    /**
 296      * Position of day-of-month. This value is used to get the min/max value
 297      * from an array.
 298      */
 299     private static final int POSITION_DAY_OF_MONTH = 5;
 300     /**
 301      * Position of day-of-year. This value is used to get the min/max value from
 302      * an array.
 303      */
 304     private static final int POSITION_DAY_OF_YEAR = 6;
 305     /**
 306      * Zero-based start date of cycle year.
 307      */
 308     private static final int CYCLEYEAR_START_DATE[] =
 309         {
 310         0,
 311         354,
 312         709,
 313         1063,
 314         1417,
 315         1772,
 316         2126,
 317         2481,
 318         2835,
 319         3189,
 320         3544,
 321         3898,
 322         4252,
 323         4607,
 324         4961,
 325         5315,
 326         5670,
 327         6024,
 328         6379,
 329         6733,
 330         7087,
 331         7442,
 332         7796,
 333         8150,
 334         8505,
 335         8859,
 336         9214,
 337         9568,
 338         9922,
 339         10277
 340         };
 341 
 342     /**
 343      * Holding the adjusted month days in year. The key is a year (Integer) and
 344      * the value is the all the month days in year (int[]).
 345      */
 346     private final HashMap<Integer, int[]> ADJUSTED_MONTH_DAYS = new HashMap<>();
 347     /**
 348      * Holding the adjusted month length in year. The key is a year (Integer)
 349      * and the value is the all the month length in year (int[]).
 350      */
 351     private final HashMap<Integer, int[]> ADJUSTED_MONTH_LENGTHS = new HashMap<>();
 352     /**
 353      * Holding the adjusted days in the 30 year cycle. The key is a cycle number
 354      * (Integer) and the value is the all the starting days of the year in the
 355      * cycle (int[]).
 356      */
 357     private final HashMap<Integer, int[]> ADJUSTED_CYCLE_YEARS = new HashMap<>();
 358     /**
 359      * Holding the adjusted cycle in the 1 - 30000 year. The key is the cycle
 360      * number (Integer) and the value is the starting days in the cycle in the
 361      * term.
 362      */
 363     private final long[] ADJUSTED_CYCLES;
 364     /**
 365      * Holding the adjusted min values.
 366      */
 367     private final int[] ADJUSTED_MIN_VALUES;
 368     /**
 369      * Holding the adjusted max least max values.
 370      */
 371     private final int[] ADJUSTED_LEAST_MAX_VALUES;
 372     /**
 373      * Holding adjusted max values.
 374      */
 375     private final int[] ADJUSTED_MAX_VALUES;
 376     /**
 377      * Holding the non-adjusted month days in year for non leap year.
 378      */
 379     private static final int[] DEFAULT_MONTH_DAYS;
 380     /**
 381      * Holding the non-adjusted month days in year for leap year.
 382      */
 383     private static final int[] DEFAULT_LEAP_MONTH_DAYS;
 384     /**
 385      * Holding the non-adjusted month length for non leap year.
 386      */
 387     private static final int[] DEFAULT_MONTH_LENGTHS;
 388     /**
 389      * Holding the non-adjusted month length for leap year.
 390      */
 391     private static final int[] DEFAULT_LEAP_MONTH_LENGTHS;
 392     /**
 393      * Holding the non-adjusted 30 year cycle starting day.
 394      */
 395     private static final int[] DEFAULT_CYCLE_YEARS;
 396     /**
 397      * number of 30-year cycles to hold the deviation data.
 398      */
 399     private static final int MAX_ADJUSTED_CYCLE = 334; // to support year 9999
 400 
 401 
 402     /**
 403      * Narrow names for eras.
 404      */
 405     private static final HashMap<String, String[]> ERA_NARROW_NAMES = new HashMap<>();
 406     /**
 407      * Short names for eras.
 408      */
 409     private static final HashMap<String, String[]> ERA_SHORT_NAMES = new HashMap<>();
 410     /**
 411      * Full names for eras.
 412      */
 413     private static final HashMap<String, String[]> ERA_FULL_NAMES = new HashMap<>();
 414     /**
 415      * Fallback language for the era names.
 416      */
 417     private static final String FALLBACK_LANGUAGE = "en";
 418 
 419     /**
 420      * Singleton instance of the Hijrah chronology.
 421      * Must be initialized after the rest of the static initialization.
 422      */
 423     public static final HijrahChronology INSTANCE;
 424 
 425     /**
 426      * Name data.
 427      */
 428     static {
 429         ERA_NARROW_NAMES.put(FALLBACK_LANGUAGE, new String[]{"BH", "HE"});
 430         ERA_SHORT_NAMES.put(FALLBACK_LANGUAGE, new String[]{"B.H.", "H.E."});
 431         ERA_FULL_NAMES.put(FALLBACK_LANGUAGE, new String[]{"Before Hijrah", "Hijrah Era"});
 432 
 433         DEFAULT_MONTH_DAYS = Arrays.copyOf(NUM_DAYS, NUM_DAYS.length);
 434 
 435         DEFAULT_LEAP_MONTH_DAYS = Arrays.copyOf(LEAP_NUM_DAYS, LEAP_NUM_DAYS.length);
 436 
 437         DEFAULT_MONTH_LENGTHS = Arrays.copyOf(MONTH_LENGTH, MONTH_LENGTH.length);
 438 
 439         DEFAULT_LEAP_MONTH_LENGTHS = Arrays.copyOf(LEAP_MONTH_LENGTH, LEAP_MONTH_LENGTH.length);
 440 
 441         DEFAULT_CYCLE_YEARS = Arrays.copyOf(CYCLEYEAR_START_DATE, CYCLEYEAR_START_DATE.length);
 442 
 443         INSTANCE = new HijrahChronology();
 444 
 445         String extraCalendars = java.security.AccessController.doPrivileged(
 446             new sun.security.action.GetPropertyAction("java.time.chrono.HijrahCalendars"));
 447         if (extraCalendars != null) {
 448             try {
 449                 // Split on whitespace
 450                 String[] splits = extraCalendars.split("\\s");
 451                 for (String cal : splits) {
 452                     if (!cal.isEmpty()) {
 453                         // Split on the delimiter between typeId "-" calendarType
 454                         String[] type = cal.split("-");
 455                         Chronology cal2 = new HijrahChronology(type[0], type.length > 1 ? type[1] : type[0]);
 456                     }
 457                 }
 458             } catch (Exception ex) {
 459                 // Log the error
 460                 // ex.printStackTrace();
 461             }
 462         }
 463     }
 464 
 465     /**
 466      * Restricted constructor.
 467      */
 468     private HijrahChronology() {
 469         this("Hijrah", "islamicc");
 470     }
 471     /**
 472      * Constructor for name and type HijrahChronology.
 473      * @param id the id of the calendar
 474      * @param calendarType the calendar type
 475      */
 476     private HijrahChronology(String id, String calendarType) {
 477         this.typeId = id;
 478         this.calendarType = calendarType;
 479 
 480         ADJUSTED_CYCLES = new long[MAX_ADJUSTED_CYCLE];
 481         for (int i = 0; i < ADJUSTED_CYCLES.length; i++) {
 482             ADJUSTED_CYCLES[i] = (10631L * i);
 483         }
 484         // Initialize min values, least max values and max values.
 485         ADJUSTED_MIN_VALUES = Arrays.copyOf(MIN_VALUES, MIN_VALUES.length);
 486         ADJUSTED_LEAST_MAX_VALUES = Arrays.copyOf(LEAST_MAX_VALUES, LEAST_MAX_VALUES.length);
 487         ADJUSTED_MAX_VALUES = Arrays.copyOf(MAX_VALUES,MAX_VALUES.length);
 488 
 489         try {
 490             // Implicitly reads deviation data for this HijrahChronology.
 491             boolean any = HijrahDeviationReader.readDeviation(typeId, calendarType, this::addDeviationAsHijrah);
 492         } catch (IOException | ParseException e) {
 493             // do nothing. Log deviation config errors.
 494             //e.printStackTrace();
 495         }
 496     }
 497 
 498     /**
 499      * Resolve singleton.
 500      *
 501      * @return the singleton instance, not null
 502      */
 503     private Object readResolve() {
 504         return INSTANCE;
 505     }
 506 
 507     //-----------------------------------------------------------------------
 508     /**
 509      * Gets the ID of the chronology - 'Hijrah'.
 510      * <p>
 511      * The ID uniquely identifies the {@code Chronology}.
 512      * It can be used to lookup the {@code Chronology} using {@link #of(String)}.
 513      *
 514      * @return the chronology ID - 'Hijrah'
 515      * @see #getCalendarType()
 516      */
 517     @Override
 518     public String getId() {
 519         return typeId;
 520     }
 521 
 522     /**
 523      * Gets the calendar type of the underlying calendar system - 'islamicc'.
 524      * <p>
 525      * The calendar type is an identifier defined by the
 526      * <em>Unicode Locale Data Markup Language (LDML)</em> specification.
 527      * It can be used to lookup the {@code Chronology} using {@link #of(String)}.
 528      * It can also be used as part of a locale, accessible via
 529      * {@link Locale#getUnicodeLocaleType(String)} with the key 'ca'.
 530      *
 531      * @return the calendar system type - 'islamicc'
 532      * @see #getId()
 533      */
 534     @Override
 535     public String getCalendarType() {
 536         return calendarType;
 537     }
 538 
 539     //-----------------------------------------------------------------------
 540     @Override
 541     public HijrahDate date(int prolepticYear, int month, int dayOfMonth) {
 542         return HijrahDate.of(this, prolepticYear, month, dayOfMonth);
 543     }
 544 
 545     @Override
 546     public HijrahDate dateYearDay(int prolepticYear, int dayOfYear) {
 547         return HijrahDate.of(this, prolepticYear, 1, 1).plusDays(dayOfYear - 1);  // TODO better
 548     }
 549 
 550     @Override
 551     public HijrahDate date(TemporalAccessor temporal) {
 552         if (temporal instanceof HijrahDate) {
 553             return (HijrahDate) temporal;
 554         }
 555         return HijrahDate.ofEpochDay(this, temporal.getLong(EPOCH_DAY));
 556     }
 557 
 558     @Override
 559     public HijrahDate date(Era era, int yearOfEra, int month, int dayOfMonth) {
 560         return date(prolepticYear(era, yearOfEra), month, dayOfMonth);
 561 
 562     }
 563 
 564     @Override
 565     public HijrahDate dateYearDay(Era era, int yearOfEra, int dayOfYear) {
 566         return dateYearDay(prolepticYear(era, yearOfEra), dayOfYear);
 567     }
 568 
 569     @Override
 570     public HijrahDate dateNow() {
 571         return dateNow(Clock.systemDefaultZone());
 572     }
 573 
 574     @Override
 575     public HijrahDate dateNow(ZoneId zone) {
 576         return dateNow(Clock.system(zone));
 577     }
 578 
 579     @Override
 580     public HijrahDate dateNow(Clock clock) {
 581         return date(LocalDate.now(clock));
 582     }
 583 
 584     @Override
 585     public ChronoLocalDateTime<HijrahDate> localDateTime(TemporalAccessor temporal) {
 586         return (ChronoLocalDateTime<HijrahDate>)super.localDateTime(temporal);
 587     }
 588 
 589     @Override
 590     public ChronoZonedDateTime<HijrahDate> zonedDateTime(TemporalAccessor temporal) {
 591         return (ChronoZonedDateTime<HijrahDate>)super.zonedDateTime(temporal);
 592     }
 593 
 594     @Override
 595     public ChronoZonedDateTime<HijrahDate> zonedDateTime(Instant instant, ZoneId zone) {
 596         return (ChronoZonedDateTime<HijrahDate>)super.zonedDateTime(instant, zone);
 597     }
 598 
 599     //-----------------------------------------------------------------------
 600     @Override
 601     public boolean isLeapYear(long prolepticYear) {
 602         return isLeapYear0(prolepticYear);
 603     }
 604     /**
 605      * Returns if the year is a leap year.
 606      * @param prolepticYear he year to compute from
 607      * @return {@code true} if the year is a leap year, otherwise {@code false}
 608      */
 609     private static boolean isLeapYear0(long prolepticYear) {
 610         return (14 + 11 * (prolepticYear > 0 ? prolepticYear : -prolepticYear)) % 30 < 11;
 611     }
 612 
 613     @Override
 614     public int prolepticYear(Era era, int yearOfEra) {
 615         if (era instanceof HijrahEra == false) {
 616             throw new DateTimeException("Era must be HijrahEra");
 617         }
 618         return (era == HijrahEra.AH ? yearOfEra : 1 - yearOfEra);
 619     }
 620 
 621     @Override
 622     public Era eraOf(int eraValue) {
 623         switch (eraValue) {
 624             case 0:
 625                 return HijrahEra.BEFORE_AH;
 626             case 1:
 627                 return HijrahEra.AH;
 628             default:
 629                 throw new DateTimeException("invalid Hijrah era");
 630         }
 631     }
 632 
 633     @Override
 634     public List<Era> eras() {
 635         return Arrays.<Era>asList(HijrahEra.values());
 636     }
 637 
 638     //-----------------------------------------------------------------------
 639     @Override
 640     public ValueRange range(ChronoField field) {
 641         return field.range();
 642     }
 643 
 644     /**
 645      * Check the validity of a yearOfEra.
 646      * @param yearOfEra the year to check
 647      */
 648     void checkValidYearOfEra(int yearOfEra) {
 649          if (yearOfEra < MIN_YEAR_OF_ERA  ||
 650                  yearOfEra > MAX_YEAR_OF_ERA) {
 651              throw new DateTimeException("Invalid year of Hijrah Era");
 652          }
 653     }
 654 
 655     void checkValidDayOfYear(int dayOfYear) {
 656          if (dayOfYear < 1  ||
 657                  dayOfYear > getMaximumDayOfYear()) {
 658              throw new DateTimeException("Invalid day of year of Hijrah date");
 659          }
 660     }
 661 
 662     void checkValidMonth(int month) {
 663          if (month < 1 || month > 12) {
 664              throw new DateTimeException("Invalid month of Hijrah date");
 665          }
 666     }
 667 
 668     void checkValidDayOfMonth(int dayOfMonth) {
 669          if (dayOfMonth < 1  ||
 670                  dayOfMonth > getMaximumDayOfMonth()) {
 671              throw new DateTimeException("Invalid day of month of Hijrah date, day "
 672                      + dayOfMonth + " greater than " + getMaximumDayOfMonth() + " or less than 1");
 673          }
 674     }
 675 
 676     //-----------------------------------------------------------------------
 677     /**
 678      * Returns the int array containing the following field from the julian day.
 679      *
 680      * int[0] = ERA
 681      * int[1] = YEAR
 682      * int[2] = MONTH
 683      * int[3] = DATE
 684      * int[4] = DAY_OF_YEAR
 685      * int[5] = DAY_OF_WEEK
 686      *
 687      * @param gregorianDays  a julian day.
 688      */
 689     int[] getHijrahDateInfo(long gregorianDays) {
 690         int era, year, month, date, dayOfWeek, dayOfYear;
 691 
 692         int cycleNumber, yearInCycle, dayOfCycle;
 693 
 694         long epochDay = gregorianDays - HIJRAH_JAN_1_1_GREGORIAN_DAY;
 695 
 696         if (epochDay >= 0) {
 697             cycleNumber = getCycleNumber(epochDay); // 0 - 99.
 698             dayOfCycle = getDayOfCycle(epochDay, cycleNumber); // 0 - 10631.
 699             yearInCycle = getYearInCycle(cycleNumber, dayOfCycle); // 0 - 29.
 700             dayOfYear = getDayOfYear(cycleNumber, dayOfCycle, yearInCycle);
 701             // 0 - 354/355
 702             year = cycleNumber * 30 + yearInCycle + 1; // 1-based year.
 703             month = getMonthOfYear(dayOfYear, year); // 0-based month-of-year
 704             date = getDayOfMonth(dayOfYear, month, year); // 0-based date
 705             ++date; // Convert from 0-based to 1-based
 706             era = HijrahEra.AH.getValue();
 707         } else {
 708             cycleNumber = (int) epochDay / 10631; // 0 or negative number.
 709             dayOfCycle = (int) epochDay % 10631; // -10630 - 0.
 710             if (dayOfCycle == 0) {
 711                 dayOfCycle = -10631;
 712                 cycleNumber++;
 713             }
 714             yearInCycle = getYearInCycle(cycleNumber, dayOfCycle); // 0 - 29.
 715             dayOfYear = getDayOfYear(cycleNumber, dayOfCycle, yearInCycle);
 716             year = cycleNumber * 30 - yearInCycle; // negative number.
 717             year = 1 - year;
 718             dayOfYear = (isLeapYear(year) ? (dayOfYear + 355)
 719                     : (dayOfYear + 354));
 720             month = getMonthOfYear(dayOfYear, year);
 721             date = getDayOfMonth(dayOfYear, month, year);
 722             ++date; // Convert from 0-based to 1-based
 723             era = HijrahEra.BEFORE_AH.getValue();
 724         }
 725         // Hijrah day zero is a Friday
 726         dayOfWeek = (int) ((epochDay + 5) % 7);
 727         dayOfWeek += (dayOfWeek <= 0) ? 7 : 0;
 728 
 729         int dateInfo[] = new int[6];
 730         dateInfo[0] = era;
 731         dateInfo[1] = year;
 732         dateInfo[2] = month + 1; // change to 1-based.
 733         dateInfo[3] = date;
 734         dateInfo[4] = dayOfYear + 1; // change to 1-based.
 735         dateInfo[5] = dayOfWeek;
 736         return dateInfo;
 737     }
 738 
 739     /**
 740      * Return Gregorian epoch day from Hijrah year, month, and day.
 741      *
 742      * @param prolepticYear  the year to represent, caller calculated
 743      * @param monthOfYear  the month-of-year to represent, caller calculated
 744      * @param dayOfMonth  the day-of-month to represent, caller calculated
 745      * @return a julian day
 746      */
 747     long getGregorianEpochDay(int prolepticYear, int monthOfYear, int dayOfMonth) {
 748         long day = yearToGregorianEpochDay(prolepticYear);
 749         day += getMonthDays(monthOfYear - 1, prolepticYear);
 750         day += dayOfMonth;
 751         return day;
 752     }
 753 
 754     /**
 755      * Returns the Gregorian epoch day from the proleptic year
 756      * @param prolepticYear the proleptic year
 757      * @return the Epoch day
 758      */
 759     private long yearToGregorianEpochDay(int prolepticYear) {
 760 
 761         int cycleNumber = (prolepticYear - 1) / 30; // 0-based.
 762         int yearInCycle = (prolepticYear - 1) % 30; // 0-based.
 763 
 764         int dayInCycle = getAdjustedCycle(cycleNumber)[Math.abs(yearInCycle)]
 765                 ;
 766 
 767         if (yearInCycle < 0) {
 768             dayInCycle = -dayInCycle;
 769         }
 770 
 771         Long cycleDays;
 772 
 773         try {
 774             cycleDays = ADJUSTED_CYCLES[cycleNumber];
 775         } catch (ArrayIndexOutOfBoundsException e) {
 776             cycleDays = null;
 777         }
 778 
 779         if (cycleDays == null) {
 780             cycleDays = new Long(cycleNumber * 10631);
 781         }
 782 
 783         return (cycleDays.longValue() + dayInCycle + HIJRAH_JAN_1_1_GREGORIAN_DAY - 1);
 784     }
 785 
 786     /**
 787      * Returns the 30 year cycle number from the epoch day.
 788      *
 789      * @param epochDay  an epoch day
 790      * @return a cycle number
 791      */
 792     private int getCycleNumber(long epochDay) {
 793         long[] days = ADJUSTED_CYCLES;
 794         int cycleNumber;
 795         try {
 796             for (int i = 0; i < days.length; i++) {
 797                 if (epochDay < days[i]) {
 798                     return i - 1;
 799                 }
 800             }
 801             cycleNumber = (int) epochDay / 10631;
 802         } catch (ArrayIndexOutOfBoundsException e) {
 803             cycleNumber = (int) epochDay / 10631;
 804         }
 805         return cycleNumber;
 806     }
 807 
 808     /**
 809      * Returns day of cycle from the epoch day and cycle number.
 810      *
 811      * @param epochDay  an epoch day
 812      * @param cycleNumber  a cycle number
 813      * @return a day of cycle
 814      */
 815     private int getDayOfCycle(long epochDay, int cycleNumber) {
 816         Long day;
 817 
 818         try {
 819             day = ADJUSTED_CYCLES[cycleNumber];
 820         } catch (ArrayIndexOutOfBoundsException e) {
 821             day = null;
 822         }
 823         if (day == null) {
 824             day = new Long(cycleNumber * 10631);
 825         }
 826         return (int) (epochDay - day.longValue());
 827     }
 828 
 829     /**
 830      * Returns the year in cycle from the cycle number and day of cycle.
 831      *
 832      * @param cycleNumber  a cycle number
 833      * @param dayOfCycle  day of cycle
 834      * @return a year in cycle
 835      */
 836     private int getYearInCycle(int cycleNumber, long dayOfCycle) {
 837         int[] cycles = getAdjustedCycle(cycleNumber);
 838         if (dayOfCycle == 0) {
 839             return 0;
 840         }
 841 
 842         if (dayOfCycle > 0) {
 843             for (int i = 0; i < cycles.length; i++) {
 844                 if (dayOfCycle < cycles[i]) {
 845                     return i - 1;
 846                 }
 847             }
 848             return 29;
 849         } else {
 850             dayOfCycle = -dayOfCycle;
 851             for (int i = 0; i < cycles.length; i++) {
 852                 if (dayOfCycle <= cycles[i]) {
 853                     return i - 1;
 854                 }
 855             }
 856             return 29;
 857         }
 858     }
 859 
 860     /**
 861      * Returns adjusted 30 year cycle starting day as Integer array from the
 862      * cycle number specified.
 863      *
 864      * @param cycleNumber  a cycle number
 865      * @return an Integer array
 866      */
 867     int[] getAdjustedCycle(int cycleNumber) {
 868         int[] cycles;
 869         try {
 870             cycles = ADJUSTED_CYCLE_YEARS.get(cycleNumber);
 871         } catch (ArrayIndexOutOfBoundsException e) {
 872             cycles = null;
 873         }
 874         if (cycles == null) {
 875             cycles = DEFAULT_CYCLE_YEARS;
 876         }
 877         return cycles;
 878     }
 879 
 880     /**
 881      * Returns adjusted month days as Integer array form the year specified.
 882      *
 883      * @param year  a year
 884      * @return an Integer array
 885      */
 886     int[] getAdjustedMonthDays(int year) {
 887         int[] newMonths;
 888         try {
 889             newMonths = ADJUSTED_MONTH_DAYS.get(year);
 890         } catch (ArrayIndexOutOfBoundsException e) {
 891             newMonths = null;
 892         }
 893         if (newMonths == null) {
 894             if (isLeapYear0(year)) {
 895                 newMonths = DEFAULT_LEAP_MONTH_DAYS;
 896             } else {
 897                 newMonths = DEFAULT_MONTH_DAYS;
 898             }
 899         }
 900         return newMonths;
 901     }
 902 
 903     /**
 904      * Returns adjusted month length as Integer array form the year specified.
 905      *
 906      * @param year  a year
 907      * @return an Integer array
 908      */
 909     int[] getAdjustedMonthLength(int year) {
 910         int[] newMonths;
 911         try {
 912             newMonths = ADJUSTED_MONTH_LENGTHS.get(year);
 913         } catch (ArrayIndexOutOfBoundsException e) {
 914             newMonths = null;
 915         }
 916         if (newMonths == null) {
 917             if (isLeapYear0(year)) {
 918                 newMonths = DEFAULT_LEAP_MONTH_LENGTHS;
 919             } else {
 920                 newMonths = DEFAULT_MONTH_LENGTHS;
 921             }
 922         }
 923         return newMonths;
 924     }
 925 
 926     /**
 927      * Returns day-of-year.
 928      *
 929      * @param cycleNumber  a cycle number
 930      * @param dayOfCycle  day of cycle
 931      * @param yearInCycle  year in cycle
 932      * @return day-of-year
 933      */
 934     private int getDayOfYear(int cycleNumber, int dayOfCycle, int yearInCycle) {
 935         int[] cycles = getAdjustedCycle(cycleNumber);
 936 
 937         if (dayOfCycle > 0) {
 938             return dayOfCycle - cycles[yearInCycle];
 939         } else {
 940             return cycles[yearInCycle] + dayOfCycle;
 941         }
 942     }
 943 
 944     /**
 945      * Returns month-of-year. 0-based.
 946      *
 947      * @param dayOfYear  day-of-year
 948      * @param year  a year
 949      * @return month-of-year
 950      */
 951     private int getMonthOfYear(int dayOfYear, int year) {
 952 
 953         int[] newMonths = getAdjustedMonthDays(year);
 954 
 955         if (dayOfYear >= 0) {
 956             for (int i = 0; i < newMonths.length; i++) {
 957                 if (dayOfYear < newMonths[i]) {
 958                     return i - 1;
 959                 }
 960             }
 961             return 11;
 962         } else {
 963             dayOfYear = (isLeapYear0(year) ? (dayOfYear + 355)
 964                     : (dayOfYear + 354));
 965             for (int i = 0; i < newMonths.length; i++) {
 966                 if (dayOfYear < newMonths[i]) {
 967                     return i - 1;
 968                 }
 969             }
 970             return 11;
 971         }
 972     }
 973 
 974     /**
 975      * Returns day-of-month.
 976      *
 977      * @param dayOfYear  day of  year
 978      * @param month  month
 979      * @param year  year
 980      * @return day-of-month
 981      */
 982     private int getDayOfMonth(int dayOfYear, int month, int year) {
 983 
 984         int[] newMonths = getAdjustedMonthDays(year);
 985 
 986         if (dayOfYear >= 0) {
 987             if (month > 0) {
 988                 return dayOfYear - newMonths[month];
 989             } else {
 990                 return dayOfYear;
 991             }
 992         } else {
 993             dayOfYear = (isLeapYear0(year) ? (dayOfYear + 355)
 994                     : (dayOfYear + 354));
 995             if (month > 0) {
 996                 return dayOfYear - newMonths[month];
 997             } else {
 998                 return dayOfYear;
 999             }
1000         }
1001     }
1002 
1003 
1004     /**
1005      * Returns month days from the beginning of year.
1006      *
1007      * @param month  month (0-based)
1008      * @parma year  year
1009      * @return month days from the beginning of year
1010      */
1011     private int getMonthDays(int month, int year) {
1012         int[] newMonths = getAdjustedMonthDays(year);
1013         return newMonths[month];
1014     }
1015 
1016     /**
1017      * Returns month length.
1018      *
1019      * @param month  month (0-based)
1020      * @param year  year
1021      * @return month length
1022      */
1023     private int getMonthLength(int month, int year) {
1024       int[] newMonths = getAdjustedMonthLength(year);
1025       return newMonths[month];
1026     }
1027 
1028     /**
1029      * Returns year length.
1030      *
1031      * @param year  year
1032      * @return year length
1033      */
1034     int getYearLength(int year) {
1035 
1036         int cycleNumber = (year - 1) / 30;
1037         int[] cycleYears;
1038         try {
1039             cycleYears = ADJUSTED_CYCLE_YEARS.get(cycleNumber);
1040         } catch (ArrayIndexOutOfBoundsException e) {
1041             cycleYears = null;
1042         }
1043         if (cycleYears != null) {
1044             int yearInCycle = (year - 1) % 30;
1045             if (yearInCycle == 29) {
1046                 return (int)(ADJUSTED_CYCLES[cycleNumber + 1]
1047                         - ADJUSTED_CYCLES[cycleNumber]
1048                         - cycleYears[yearInCycle]);
1049             }
1050             return cycleYears[yearInCycle + 1]
1051                     - cycleYears[yearInCycle];
1052         } else {
1053             return isLeapYear0(year) ? 355 : 354;
1054         }
1055     }
1056 
1057 
1058     /**
1059      * Returns maximum day-of-month.
1060      *
1061      * @return maximum day-of-month
1062      */
1063     int getMaximumDayOfMonth() {
1064         return ADJUSTED_MAX_VALUES[POSITION_DAY_OF_MONTH];
1065     }
1066 
1067     /**
1068      * Returns smallest maximum day-of-month.
1069      *
1070      * @return smallest maximum day-of-month
1071      */
1072     int getSmallestMaximumDayOfMonth() {
1073         return ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_MONTH];
1074     }
1075 
1076     /**
1077      * Returns maximum day-of-year.
1078      *
1079      * @return maximum day-of-year
1080      */
1081     int getMaximumDayOfYear() {
1082         return ADJUSTED_MAX_VALUES[POSITION_DAY_OF_YEAR];
1083     }
1084 
1085     /**
1086      * Returns smallest maximum day-of-year.
1087      *
1088      * @return smallest maximum day-of-year
1089      */
1090     int getSmallestMaximumDayOfYear() {
1091         return ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_YEAR];
1092     }
1093 
1094     // ----- Deviation handling -----//
1095 
1096     /**
1097      * Adds deviation definition. The year and month specified should be the
1098      * calculated Hijrah year and month. The month is 1 based. e.g. 9 for
1099      * Ramadan (9th month) Addition of anything minus deviation days is
1100      * calculated negatively in the case the user wants to subtract days from
1101      * the calendar. For example, adding -1 days will subtract one day from the
1102      * current date.
1103      *
1104      * @param startYear  start year, 1 origin
1105      * @param startMonth  start month, 1 origin
1106      * @param endYear  end year, 1 origin
1107      * @param endMonth  end month, 1 origin
1108      * @param offset  offset -2, -1, +1, +2
1109      */
1110     private void addDeviationAsHijrah(Deviation entry) {
1111         int startYear = entry.startYear;
1112         int startMonth = entry.startMonth - 1 ;
1113         int endYear = entry.endYear;
1114         int endMonth = entry.endMonth - 1;
1115         int offset = entry.offset;
1116 
1117         if (startYear < 1) {
1118             throw new IllegalArgumentException("startYear < 1");
1119         }
1120         if (endYear < 1) {
1121             throw new IllegalArgumentException("endYear < 1");
1122         }
1123         if (startMonth < 0 || startMonth > 11) {
1124             throw new IllegalArgumentException(
1125                     "startMonth < 0 || startMonth > 11");
1126         }
1127         if (endMonth < 0 || endMonth > 11) {
1128             throw new IllegalArgumentException("endMonth < 0 || endMonth > 11");
1129         }
1130         if (endYear > 9999) {
1131             throw new IllegalArgumentException("endYear > 9999");
1132         }
1133         if (endYear < startYear) {
1134             throw new IllegalArgumentException("startYear > endYear");
1135         }
1136         if (endYear == startYear && endMonth < startMonth) {
1137             throw new IllegalArgumentException(
1138                     "startYear == endYear && endMonth < startMonth");
1139         }
1140 
1141         // Adjusting start year.
1142         boolean isStartYLeap = isLeapYear0(startYear);
1143 
1144         // Adjusting the number of month.
1145         int[] orgStartMonthNums = ADJUSTED_MONTH_DAYS.get(startYear);
1146         if (orgStartMonthNums == null) {
1147             if (isStartYLeap) {
1148                 orgStartMonthNums = Arrays.copyOf(LEAP_NUM_DAYS, LEAP_NUM_DAYS.length);
1149             } else {
1150                 orgStartMonthNums = Arrays.copyOf(NUM_DAYS, NUM_DAYS.length);
1151             }
1152         }
1153 
1154         int[] newStartMonthNums = new int[orgStartMonthNums.length];
1155 
1156         for (int month = 0; month < 12; month++) {
1157             if (month > startMonth) {
1158                 newStartMonthNums[month] = (orgStartMonthNums[month] - offset);
1159             } else {
1160                 newStartMonthNums[month] = (orgStartMonthNums[month]);
1161             }
1162         }
1163 
1164         ADJUSTED_MONTH_DAYS.put(startYear, newStartMonthNums);
1165 
1166         // Adjusting the days of month.
1167 
1168         int[] orgStartMonthLengths = ADJUSTED_MONTH_LENGTHS.get(startYear);
1169         if (orgStartMonthLengths == null) {
1170             if (isStartYLeap) {
1171                 orgStartMonthLengths = Arrays.copyOf(LEAP_MONTH_LENGTH, LEAP_MONTH_LENGTH.length);
1172             } else {
1173                 orgStartMonthLengths = Arrays.copyOf(MONTH_LENGTH, MONTH_LENGTH.length);
1174             }
1175         }
1176 
1177         int[] newStartMonthLengths = new int[orgStartMonthLengths.length];
1178 
1179         for (int month = 0; month < 12; month++) {
1180             if (month == startMonth) {
1181                 newStartMonthLengths[month] = orgStartMonthLengths[month] - offset;
1182             } else {
1183                 newStartMonthLengths[month] = orgStartMonthLengths[month];
1184             }
1185         }
1186 
1187         ADJUSTED_MONTH_LENGTHS.put(startYear, newStartMonthLengths);
1188 
1189         if (startYear != endYear) {
1190             // System.out.println("over year");
1191             // Adjusting starting 30 year cycle.
1192             int sCycleNumber = (startYear - 1) / 30;
1193             int sYearInCycle = (startYear - 1) % 30; // 0-based.
1194             int[] startCycles = ADJUSTED_CYCLE_YEARS.get(sCycleNumber);
1195             if (startCycles == null) {
1196                 startCycles = Arrays.copyOf(CYCLEYEAR_START_DATE, CYCLEYEAR_START_DATE.length);
1197             }
1198 
1199             for (int j = sYearInCycle + 1; j < CYCLEYEAR_START_DATE.length; j++) {
1200                 startCycles[j] = startCycles[j] - offset;
1201             }
1202 
1203             // System.out.println(sCycleNumber + ":" + sYearInCycle);
1204             ADJUSTED_CYCLE_YEARS.put(sCycleNumber, startCycles);
1205 
1206             int sYearInMaxY = (startYear - 1) / 30;
1207             int sEndInMaxY = (endYear - 1) / 30;
1208 
1209             if (sYearInMaxY != sEndInMaxY) {
1210                 // System.out.println("over 30");
1211                 // Adjusting starting 30 * MAX_ADJUSTED_CYCLE year cycle.
1212                 // System.out.println(sYearInMaxY);
1213 
1214                 for (int j = sYearInMaxY + 1; j < ADJUSTED_CYCLES.length; j++) {
1215                     ADJUSTED_CYCLES[j] = ADJUSTED_CYCLES[j] - offset;
1216                 }
1217 
1218                 // Adjusting ending 30 * MAX_ADJUSTED_CYCLE year cycles.
1219                 for (int j = sEndInMaxY + 1; j < ADJUSTED_CYCLES.length; j++) {
1220                     ADJUSTED_CYCLES[j] = ADJUSTED_CYCLES[j] + offset;
1221                 }
1222             }
1223 
1224             // Adjusting ending 30 year cycle.
1225             int eCycleNumber = (endYear - 1) / 30;
1226             int sEndInCycle = (endYear - 1) % 30; // 0-based.
1227             int[] endCycles = ADJUSTED_CYCLE_YEARS.get(eCycleNumber);
1228             if (endCycles == null) {
1229                 endCycles = Arrays.copyOf(CYCLEYEAR_START_DATE, CYCLEYEAR_START_DATE.length);
1230             }
1231             for (int j = sEndInCycle + 1; j < CYCLEYEAR_START_DATE.length; j++) {
1232                 endCycles[j] = endCycles[j] + offset;
1233             }
1234             ADJUSTED_CYCLE_YEARS.put(eCycleNumber, endCycles);
1235         }
1236 
1237         // Adjusting ending year.
1238         boolean isEndYLeap = isLeapYear0(endYear);
1239 
1240         int[] orgEndMonthDays = ADJUSTED_MONTH_DAYS.get(endYear);
1241 
1242         if (orgEndMonthDays == null) {
1243             if (isEndYLeap) {
1244                 orgEndMonthDays = Arrays.copyOf(LEAP_NUM_DAYS, LEAP_NUM_DAYS.length);
1245             } else {
1246                 orgEndMonthDays = Arrays.copyOf(NUM_DAYS, NUM_DAYS.length);
1247             }
1248         }
1249 
1250         int[] newEndMonthDays = new int[orgEndMonthDays.length];
1251 
1252         for (int month = 0; month < 12; month++) {
1253             if (month > endMonth) {
1254                 newEndMonthDays[month] = orgEndMonthDays[month] + offset;
1255             } else {
1256                 newEndMonthDays[month] = orgEndMonthDays[month];
1257             }
1258         }
1259 
1260         ADJUSTED_MONTH_DAYS.put(endYear, newEndMonthDays);
1261 
1262         // Adjusting the days of month.
1263         int[] orgEndMonthLengths = ADJUSTED_MONTH_LENGTHS.get(endYear);
1264 
1265         if (orgEndMonthLengths == null) {
1266             if (isEndYLeap) {
1267                 orgEndMonthLengths = Arrays.copyOf(LEAP_MONTH_LENGTH, LEAP_MONTH_LENGTH.length);
1268             } else {
1269                 orgEndMonthLengths = Arrays.copyOf(MONTH_LENGTH, MONTH_LENGTH.length);
1270             }
1271         }
1272 
1273         int[] newEndMonthLengths = new int[orgEndMonthLengths.length];
1274 
1275         for (int month = 0; month < 12; month++) {
1276             if (month == endMonth) {
1277                 newEndMonthLengths[month] = orgEndMonthLengths[month] + offset;
1278             } else {
1279                 newEndMonthLengths[month] = orgEndMonthLengths[month];
1280             }
1281         }
1282 
1283         ADJUSTED_MONTH_LENGTHS.put(endYear, newEndMonthLengths);
1284 
1285         int[] startMonthLengths = ADJUSTED_MONTH_LENGTHS.get(startYear);
1286         int[] endMonthLengths = ADJUSTED_MONTH_LENGTHS.get(endYear);
1287         int[] startMonthDays = ADJUSTED_MONTH_DAYS.get(startYear);
1288         int[] endMonthDays = ADJUSTED_MONTH_DAYS.get(endYear);
1289 
1290         int startMonthLength = startMonthLengths[startMonth];
1291         int endMonthLength = endMonthLengths[endMonth];
1292         int startMonthDay = startMonthDays[11] + startMonthLengths[11];
1293         int endMonthDay = endMonthDays[11] + endMonthLengths[11];
1294 
1295         int maxMonthLength = ADJUSTED_MAX_VALUES[POSITION_DAY_OF_MONTH];
1296         int leastMaxMonthLength = ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_MONTH];
1297 
1298         if (maxMonthLength < startMonthLength) {
1299             maxMonthLength = startMonthLength;
1300         }
1301         if (maxMonthLength < endMonthLength) {
1302             maxMonthLength = endMonthLength;
1303         }
1304         ADJUSTED_MAX_VALUES[POSITION_DAY_OF_MONTH] = maxMonthLength;
1305 
1306         if (leastMaxMonthLength > startMonthLength) {
1307             leastMaxMonthLength = startMonthLength;
1308         }
1309         if (leastMaxMonthLength > endMonthLength) {
1310             leastMaxMonthLength = endMonthLength;
1311         }
1312         ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_MONTH] = leastMaxMonthLength;
1313 
1314         int maxMonthDay = ADJUSTED_MAX_VALUES[POSITION_DAY_OF_YEAR];
1315         int leastMaxMonthDay = ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_YEAR];
1316 
1317         if (maxMonthDay < startMonthDay) {
1318             maxMonthDay = startMonthDay;
1319         }
1320         if (maxMonthDay < endMonthDay) {
1321             maxMonthDay = endMonthDay;
1322         }
1323 
1324         ADJUSTED_MAX_VALUES[POSITION_DAY_OF_YEAR] = maxMonthDay;
1325 
1326         if (leastMaxMonthDay > startMonthDay) {
1327             leastMaxMonthDay = startMonthDay;
1328         }
1329         if (leastMaxMonthDay > endMonthDay) {
1330             leastMaxMonthDay = endMonthDay;
1331         }
1332         ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_YEAR] = leastMaxMonthDay;
1333     }
1334 
1335     /**
1336      * Package private Entry for suppling deviations from the Reader.
1337      * Each entry consists of a range using the Hijrah calendar,
1338      * start year, month, end year, end month, and an offset.
1339      * The offset is used to modify the length of the month +2, +1, -1, -2.
1340      */
1341     static final class Deviation {
1342 
1343         Deviation(int startYear, int startMonth, int endYear, int endMonth, int offset) {
1344             this.startYear = startYear;
1345             this.startMonth = startMonth;
1346             this.endYear = endYear;
1347             this.endMonth = endMonth;
1348             this.offset = offset;
1349         }
1350 
1351         final int startYear;
1352         final int startMonth;
1353         final int endYear;
1354         final int endMonth;
1355         final int offset;
1356 
1357         int getStartYear() {
1358             return startYear;
1359         }
1360 
1361         int getStartMonth() {
1362             return startMonth;
1363         }
1364 
1365         int getEndYear() {
1366             return endYear;
1367         }
1368 
1369         int getEndMonth() {
1370             return endMonth;
1371         }
1372 
1373         int getOffset() {
1374             return offset;
1375         }
1376 
1377         @Override
1378         public String toString() {
1379             return String.format("[year: %4d, month: %2d, offset: %+d]", startYear, startMonth, offset);
1380         }
1381     }
1382 
1383 }