/* * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * Copyright (c) 2012, Stephen Colebourne & Michael Nascimento Santos * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * * Neither the name of JSR-310 nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package java.time.calendar; import static java.time.temporal.ChronoField.EPOCH_DAY; import java.io.IOException; import java.io.Serializable; import java.text.ParseException; import java.time.DateTimeException; import java.time.temporal.Chrono; import java.time.temporal.ChronoField; import java.time.temporal.ChronoLocalDate; import java.time.temporal.Era; import java.time.temporal.TemporalAccessor; import java.time.temporal.ValueRange; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Locale; /** * The Hijrah calendar system. *

* This chronology defines the rules of the Hijrah calendar system. *

* The implementation follows the Freeman-Grenville algorithm (*1) and has following features. *

*

* The table shows the features described above. *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Hijrah Calendar Months
# of monthName of monthNumber of days
1Muharram30
2Safar29
3Rabi'al-Awwal30
4Rabi'ath-Thani29
5Jumada l-Ula30
6Jumada t-Tania29
7Rajab30
8Sha`ban29
9Ramadan30
10Shawwal29
11Dhu 'l-Qa`da30
12Dhu 'l-Hijja29, but 30 days in years 2, 5, 7, 10,
* 13, 16, 18, 21, 24, 26, and 29
*
*

* (*1) The algorithm is taken from the book, * The Muslim and Christian Calendars by G.S.P. Freeman-Grenville. *

* *

Specification for implementors

* This class is immutable and thread-safe. * * @since 1.8 */ public final class HijrahChrono extends Chrono implements Serializable { /** * The Hijrah Calendar id. */ private final String typeId; /** * The Hijrah calendarType. */ private final String calendarType; /** * The singleton instance for the era before the current one - Before Hijrah - * which has the value 0. */ public static final Era ERA_BEFORE_AH = HijrahEra.BEFORE_AH; /** * The singleton instance for the current era - Hijrah - which has the value 1. */ public static final Era ERA_AH = HijrahEra.AH; /** * Serialization version. */ private static final long serialVersionUID = 3127340209035924785L; /** * The minimum valid year-of-era. */ public static final int MIN_YEAR_OF_ERA = 1; /** * The maximum valid year-of-era. * This is currently set to 9999 but may be changed to increase the valid range * in a future version of the specification. */ public static final int MAX_YEAR_OF_ERA = 9999; /** * Number of Gregorian day of July 19, year 622 (Gregorian), which is epoch day * of Hijrah calendar. */ private static final int HIJRAH_JAN_1_1_GREGORIAN_DAY = -492148; /** * 0-based, for number of day-of-year in the beginning of month in normal * year. */ private static final int NUM_DAYS[] = {0, 30, 59, 89, 118, 148, 177, 207, 236, 266, 295, 325}; /** * 0-based, for number of day-of-year in the beginning of month in leap year. */ private static final int LEAP_NUM_DAYS[] = {0, 30, 59, 89, 118, 148, 177, 207, 236, 266, 295, 325}; /** * 0-based, for day-of-month in normal year. */ private static final int MONTH_LENGTH[] = {30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29}; /** * 0-based, for day-of-month in leap year. */ private static final int LEAP_MONTH_LENGTH[] = {30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 30}; /** *
     *                            Greatest       Least
     * Field name        Minimum   Minimum     Maximum     Maximum
     * ----------        -------   -------     -------     -------
     * ERA                     0         0           1           1
     * YEAR_OF_ERA             1         1        9999        9999
     * MONTH_OF_YEAR           1         1          12          12
     * DAY_OF_MONTH            1         1          29          30
     * DAY_OF_YEAR             1         1         354         355
     * 
* * Minimum values. */ private static final int MIN_VALUES[] = { 0, MIN_YEAR_OF_ERA, 0, 1, 0, 1, 1 }; /** * Least maximum values. */ private static final int LEAST_MAX_VALUES[] = { 1, MAX_YEAR_OF_ERA, 11, 51, 5, 29, 354 }; /** * Maximum values. */ private static final int MAX_VALUES[] = { 1, MAX_YEAR_OF_ERA, 11, 52, 6, 30, 355 }; /** * Position of day-of-month. This value is used to get the min/max value * from an array. */ private static final int POSITION_DAY_OF_MONTH = 5; /** * Position of day-of-year. This value is used to get the min/max value from * an array. */ private static final int POSITION_DAY_OF_YEAR = 6; /** * Zero-based start date of cycle year. */ private static final int CYCLEYEAR_START_DATE[] = { 0, 354, 709, 1063, 1417, 1772, 2126, 2481, 2835, 3189, 3544, 3898, 4252, 4607, 4961, 5315, 5670, 6024, 6379, 6733, 7087, 7442, 7796, 8150, 8505, 8859, 9214, 9568, 9922, 10277 }; /** * Holding the adjusted month days in year. The key is a year (Integer) and * the value is the all the month days in year (int[]). */ private final HashMap ADJUSTED_MONTH_DAYS = new HashMap<>(); /** * Holding the adjusted month length in year. The key is a year (Integer) * and the value is the all the month length in year (int[]). */ private final HashMap ADJUSTED_MONTH_LENGTHS = new HashMap<>(); /** * Holding the adjusted days in the 30 year cycle. The key is a cycle number * (Integer) and the value is the all the starting days of the year in the * cycle (int[]). */ private final HashMap ADJUSTED_CYCLE_YEARS = new HashMap<>(); /** * Holding the adjusted cycle in the 1 - 30000 year. The key is the cycle * number (Integer) and the value is the starting days in the cycle in the * term. */ private final long[] ADJUSTED_CYCLES; /** * Holding the adjusted min values. */ private final int[] ADJUSTED_MIN_VALUES; /** * Holding the adjusted max least max values. */ private final int[] ADJUSTED_LEAST_MAX_VALUES; /** * Holding adjusted max values. */ private final int[] ADJUSTED_MAX_VALUES; /** * Holding the non-adjusted month days in year for non leap year. */ private static final int[] DEFAULT_MONTH_DAYS; /** * Holding the non-adjusted month days in year for leap year. */ private static final int[] DEFAULT_LEAP_MONTH_DAYS; /** * Holding the non-adjusted month length for non leap year. */ private static final int[] DEFAULT_MONTH_LENGTHS; /** * Holding the non-adjusted month length for leap year. */ private static final int[] DEFAULT_LEAP_MONTH_LENGTHS; /** * Holding the non-adjusted 30 year cycle starting day. */ private static final int[] DEFAULT_CYCLE_YEARS; /** * number of 30-year cycles to hold the deviation data. */ private static final int MAX_ADJUSTED_CYCLE = 334; // to support year 9999 /** * Narrow names for eras. */ private static final HashMap ERA_NARROW_NAMES = new HashMap<>(); /** * Short names for eras. */ private static final HashMap ERA_SHORT_NAMES = new HashMap<>(); /** * Full names for eras. */ private static final HashMap ERA_FULL_NAMES = new HashMap<>(); /** * Fallback language for the era names. */ private static final String FALLBACK_LANGUAGE = "en"; /** * Singleton instance of the Hijrah chronology. * Must be initialized after the rest of the static initialization. */ public static final HijrahChrono INSTANCE; /** * Name data. */ static { ERA_NARROW_NAMES.put(FALLBACK_LANGUAGE, new String[]{"BH", "HE"}); ERA_SHORT_NAMES.put(FALLBACK_LANGUAGE, new String[]{"B.H.", "H.E."}); ERA_FULL_NAMES.put(FALLBACK_LANGUAGE, new String[]{"Before Hijrah", "Hijrah Era"}); DEFAULT_MONTH_DAYS = Arrays.copyOf(NUM_DAYS, NUM_DAYS.length); DEFAULT_LEAP_MONTH_DAYS = Arrays.copyOf(LEAP_NUM_DAYS, LEAP_NUM_DAYS.length); DEFAULT_MONTH_LENGTHS = Arrays.copyOf(MONTH_LENGTH, MONTH_LENGTH.length); DEFAULT_LEAP_MONTH_LENGTHS = Arrays.copyOf(LEAP_MONTH_LENGTH, LEAP_MONTH_LENGTH.length); DEFAULT_CYCLE_YEARS = Arrays.copyOf(CYCLEYEAR_START_DATE, CYCLEYEAR_START_DATE.length); INSTANCE = new HijrahChrono(); String extraCalendars = java.security.AccessController.doPrivileged( new sun.security.action.GetPropertyAction("java.time.calendar.HijrahCalendars")); if (extraCalendars != null) { try { // Split on whitespace String[] splits = extraCalendars.split("\\s"); for (String cal : splits) { if (!cal.isEmpty()) { // Split on the delimiter between typeId "-" calendarType String[] type = cal.split("-"); Chrono cal2 = new HijrahChrono(type[0], type.length > 1 ? type[1] : type[0]); } } } catch (Exception ex) { // Log the error // ex.printStackTrace(); } } } /** * Restricted constructor. */ private HijrahChrono() { this("Hijrah", "islamicc"); } /** * Constructor for name and type HijrahChrono. * @param id the id of the calendar * @param calendarType the calendar type */ private HijrahChrono(String id, String calendarType) { this.typeId = id; this.calendarType = calendarType; ADJUSTED_CYCLES = new long[MAX_ADJUSTED_CYCLE]; for (int i = 0; i < ADJUSTED_CYCLES.length; i++) { ADJUSTED_CYCLES[i] = (10631L * i); } // Initialize min values, least max values and max values. ADJUSTED_MIN_VALUES = Arrays.copyOf(MIN_VALUES, MIN_VALUES.length); ADJUSTED_LEAST_MAX_VALUES = Arrays.copyOf(LEAST_MAX_VALUES, LEAST_MAX_VALUES.length); ADJUSTED_MAX_VALUES = Arrays.copyOf(MAX_VALUES,MAX_VALUES.length); try { // Implicitly reads deviation data for this HijrahChronology. boolean any = HijrahDeviationReader.readDeviation(typeId, calendarType, this::addDeviationAsHijrah); } catch (IOException | ParseException e) { // do nothing. Log deviation config errors. //e.printStackTrace(); } } /** * Resolve singleton. * * @return the singleton instance, not null */ private Object readResolve() { return INSTANCE; } //----------------------------------------------------------------------- /** * Gets the ID of the chronology - 'Hijrah'. *

* The ID uniquely identifies the {@code Chrono}. * It can be used to lookup the {@code Chrono} using {@link #of(String)}. * * @return the chronology ID - 'Hijrah' * @see #getCalendarType() */ @Override public String getId() { return typeId; } /** * Gets the calendar type of the underlying calendar system - 'islamicc'. *

* The calendar type is an identifier defined by the * Unicode Locale Data Markup Language (LDML) specification. * It can be used to lookup the {@code Chrono} using {@link #of(String)}. * It can also be used as part of a locale, accessible via * {@link Locale#getUnicodeLocaleType(String)} with the key 'ca'. * * @return the calendar system type - 'islamicc' * @see #getId() */ @Override public String getCalendarType() { return calendarType; } //----------------------------------------------------------------------- @Override public ChronoLocalDate date(int prolepticYear, int month, int dayOfMonth) { return HijrahDate.of(this, prolepticYear, month, dayOfMonth); } @Override public ChronoLocalDate dateYearDay(int prolepticYear, int dayOfYear) { return HijrahDate.of(this, prolepticYear, 1, 1).plusDays(dayOfYear - 1); // TODO better } @Override public ChronoLocalDate date(TemporalAccessor temporal) { if (temporal instanceof HijrahDate) { return (HijrahDate) temporal; } return HijrahDate.ofEpochDay(this, temporal.getLong(EPOCH_DAY)); } //----------------------------------------------------------------------- @Override public boolean isLeapYear(long prolepticYear) { return isLeapYear0(prolepticYear); } /** * Returns if the year is a leap year. * @param prolepticYear he year to compute from * @return {@code true} if the year is a leap year, otherwise {@code false} */ private static boolean isLeapYear0(long prolepticYear) { return (14 + 11 * (prolepticYear > 0 ? prolepticYear : -prolepticYear)) % 30 < 11; } @Override public int prolepticYear(Era era, int yearOfEra) { if (era instanceof HijrahEra == false) { throw new DateTimeException("Era must be HijrahEra"); } return (era == HijrahEra.AH ? yearOfEra : 1 - yearOfEra); } @Override public Era eraOf(int eraValue) { switch (eraValue) { case 0: return HijrahEra.BEFORE_AH; case 1: return HijrahEra.AH; default: throw new DateTimeException("invalid Hijrah era"); } } @Override public List> eras() { return Arrays.>asList(HijrahEra.values()); } //----------------------------------------------------------------------- @Override public ValueRange range(ChronoField field) { return field.range(); } /** * Check the validity of a yearOfEra. * @param yearOfEra the year to check */ void checkValidYearOfEra(int yearOfEra) { if (yearOfEra < MIN_YEAR_OF_ERA || yearOfEra > MAX_YEAR_OF_ERA) { throw new DateTimeException("Invalid year of Hijrah Era"); } } void checkValidDayOfYear(int dayOfYear) { if (dayOfYear < 1 || dayOfYear > getMaximumDayOfYear()) { throw new DateTimeException("Invalid day of year of Hijrah date"); } } void checkValidMonth(int month) { if (month < 1 || month > 12) { throw new DateTimeException("Invalid month of Hijrah date"); } } void checkValidDayOfMonth(int dayOfMonth) { if (dayOfMonth < 1 || dayOfMonth > getMaximumDayOfMonth()) { throw new DateTimeException("Invalid day of month of Hijrah date, day " + dayOfMonth + " greater than " + getMaximumDayOfMonth() + " or less than 1"); } } //----------------------------------------------------------------------- /** * Returns the int array containing the following field from the julian day. * * int[0] = ERA * int[1] = YEAR * int[2] = MONTH * int[3] = DATE * int[4] = DAY_OF_YEAR * int[5] = DAY_OF_WEEK * * @param gregorianDays a julian day. */ int[] getHijrahDateInfo(long gregorianDays) { int era, year, month, date, dayOfWeek, dayOfYear; int cycleNumber, yearInCycle, dayOfCycle; long epochDay = gregorianDays - HIJRAH_JAN_1_1_GREGORIAN_DAY; if (epochDay >= 0) { cycleNumber = getCycleNumber(epochDay); // 0 - 99. dayOfCycle = getDayOfCycle(epochDay, cycleNumber); // 0 - 10631. yearInCycle = getYearInCycle(cycleNumber, dayOfCycle); // 0 - 29. dayOfYear = getDayOfYear(cycleNumber, dayOfCycle, yearInCycle); // 0 - 354/355 year = cycleNumber * 30 + yearInCycle + 1; // 1-based year. month = getMonthOfYear(dayOfYear, year); // 0-based month-of-year date = getDayOfMonth(dayOfYear, month, year); // 0-based date ++date; // Convert from 0-based to 1-based era = HijrahEra.AH.getValue(); } else { cycleNumber = (int) epochDay / 10631; // 0 or negative number. dayOfCycle = (int) epochDay % 10631; // -10630 - 0. if (dayOfCycle == 0) { dayOfCycle = -10631; cycleNumber++; } yearInCycle = getYearInCycle(cycleNumber, dayOfCycle); // 0 - 29. dayOfYear = getDayOfYear(cycleNumber, dayOfCycle, yearInCycle); year = cycleNumber * 30 - yearInCycle; // negative number. year = 1 - year; dayOfYear = (isLeapYear(year) ? (dayOfYear + 355) : (dayOfYear + 354)); month = getMonthOfYear(dayOfYear, year); date = getDayOfMonth(dayOfYear, month, year); ++date; // Convert from 0-based to 1-based era = HijrahEra.BEFORE_AH.getValue(); } // Hijrah day zero is a Friday dayOfWeek = (int) ((epochDay + 5) % 7); dayOfWeek += (dayOfWeek <= 0) ? 7 : 0; int dateInfo[] = new int[6]; dateInfo[0] = era; dateInfo[1] = year; dateInfo[2] = month + 1; // change to 1-based. dateInfo[3] = date; dateInfo[4] = dayOfYear + 1; // change to 1-based. dateInfo[5] = dayOfWeek; return dateInfo; } /** * Return Gregorian epoch day from Hijrah year, month, and day. * * @param prolepticYear the year to represent, caller calculated * @param monthOfYear the month-of-year to represent, caller calculated * @param dayOfMonth the day-of-month to represent, caller calculated * @return a julian day */ long getGregorianEpochDay(int prolepticYear, int monthOfYear, int dayOfMonth) { long day = yearToGregorianEpochDay(prolepticYear); day += getMonthDays(monthOfYear - 1, prolepticYear); day += dayOfMonth; return day; } /** * Returns the Gregorian epoch day from the proleptic year * @param prolepticYear the proleptic year * @return the Epoch day */ private long yearToGregorianEpochDay(int prolepticYear) { int cycleNumber = (prolepticYear - 1) / 30; // 0-based. int yearInCycle = (prolepticYear - 1) % 30; // 0-based. int dayInCycle = getAdjustedCycle(cycleNumber)[Math.abs(yearInCycle)] ; if (yearInCycle < 0) { dayInCycle = -dayInCycle; } Long cycleDays; try { cycleDays = ADJUSTED_CYCLES[cycleNumber]; } catch (ArrayIndexOutOfBoundsException e) { cycleDays = null; } if (cycleDays == null) { cycleDays = new Long(cycleNumber * 10631); } return (cycleDays.longValue() + dayInCycle + HIJRAH_JAN_1_1_GREGORIAN_DAY - 1); } /** * Returns the 30 year cycle number from the epoch day. * * @param epochDay an epoch day * @return a cycle number */ private int getCycleNumber(long epochDay) { long[] days = ADJUSTED_CYCLES; int cycleNumber; try { for (int i = 0; i < days.length; i++) { if (epochDay < days[i]) { return i - 1; } } cycleNumber = (int) epochDay / 10631; } catch (ArrayIndexOutOfBoundsException e) { cycleNumber = (int) epochDay / 10631; } return cycleNumber; } /** * Returns day of cycle from the epoch day and cycle number. * * @param epochDay an epoch day * @param cycleNumber a cycle number * @return a day of cycle */ private int getDayOfCycle(long epochDay, int cycleNumber) { Long day; try { day = ADJUSTED_CYCLES[cycleNumber]; } catch (ArrayIndexOutOfBoundsException e) { day = null; } if (day == null) { day = new Long(cycleNumber * 10631); } return (int) (epochDay - day.longValue()); } /** * Returns the year in cycle from the cycle number and day of cycle. * * @param cycleNumber a cycle number * @param dayOfCycle day of cycle * @return a year in cycle */ private int getYearInCycle(int cycleNumber, long dayOfCycle) { int[] cycles = getAdjustedCycle(cycleNumber); if (dayOfCycle == 0) { return 0; } if (dayOfCycle > 0) { for (int i = 0; i < cycles.length; i++) { if (dayOfCycle < cycles[i]) { return i - 1; } } return 29; } else { dayOfCycle = -dayOfCycle; for (int i = 0; i < cycles.length; i++) { if (dayOfCycle <= cycles[i]) { return i - 1; } } return 29; } } /** * Returns adjusted 30 year cycle starting day as Integer array from the * cycle number specified. * * @param cycleNumber a cycle number * @return an Integer array */ int[] getAdjustedCycle(int cycleNumber) { int[] cycles; try { cycles = ADJUSTED_CYCLE_YEARS.get(cycleNumber); } catch (ArrayIndexOutOfBoundsException e) { cycles = null; } if (cycles == null) { cycles = DEFAULT_CYCLE_YEARS; } return cycles; } /** * Returns adjusted month days as Integer array form the year specified. * * @param year a year * @return an Integer array */ int[] getAdjustedMonthDays(int year) { int[] newMonths; try { newMonths = ADJUSTED_MONTH_DAYS.get(year); } catch (ArrayIndexOutOfBoundsException e) { newMonths = null; } if (newMonths == null) { if (isLeapYear0(year)) { newMonths = DEFAULT_LEAP_MONTH_DAYS; } else { newMonths = DEFAULT_MONTH_DAYS; } } return newMonths; } /** * Returns adjusted month length as Integer array form the year specified. * * @param year a year * @return an Integer array */ int[] getAdjustedMonthLength(int year) { int[] newMonths; try { newMonths = ADJUSTED_MONTH_LENGTHS.get(year); } catch (ArrayIndexOutOfBoundsException e) { newMonths = null; } if (newMonths == null) { if (isLeapYear0(year)) { newMonths = DEFAULT_LEAP_MONTH_LENGTHS; } else { newMonths = DEFAULT_MONTH_LENGTHS; } } return newMonths; } /** * Returns day-of-year. * * @param cycleNumber a cycle number * @param dayOfCycle day of cycle * @param yearInCycle year in cycle * @return day-of-year */ private int getDayOfYear(int cycleNumber, int dayOfCycle, int yearInCycle) { int[] cycles = getAdjustedCycle(cycleNumber); if (dayOfCycle > 0) { return dayOfCycle - cycles[yearInCycle]; } else { return cycles[yearInCycle] + dayOfCycle; } } /** * Returns month-of-year. 0-based. * * @param dayOfYear day-of-year * @param year a year * @return month-of-year */ private int getMonthOfYear(int dayOfYear, int year) { int[] newMonths = getAdjustedMonthDays(year); if (dayOfYear >= 0) { for (int i = 0; i < newMonths.length; i++) { if (dayOfYear < newMonths[i]) { return i - 1; } } return 11; } else { dayOfYear = (isLeapYear0(year) ? (dayOfYear + 355) : (dayOfYear + 354)); for (int i = 0; i < newMonths.length; i++) { if (dayOfYear < newMonths[i]) { return i - 1; } } return 11; } } /** * Returns day-of-month. * * @param dayOfYear day of year * @param month month * @param year year * @return day-of-month */ private int getDayOfMonth(int dayOfYear, int month, int year) { int[] newMonths = getAdjustedMonthDays(year); if (dayOfYear >= 0) { if (month > 0) { return dayOfYear - newMonths[month]; } else { return dayOfYear; } } else { dayOfYear = (isLeapYear0(year) ? (dayOfYear + 355) : (dayOfYear + 354)); if (month > 0) { return dayOfYear - newMonths[month]; } else { return dayOfYear; } } } /** * Returns month days from the beginning of year. * * @param month month (0-based) * @parma year year * @return month days from the beginning of year */ private int getMonthDays(int month, int year) { int[] newMonths = getAdjustedMonthDays(year); return newMonths[month]; } /** * Returns month length. * * @param month month (0-based) * @param year year * @return month length */ private int getMonthLength(int month, int year) { int[] newMonths = getAdjustedMonthLength(year); return newMonths[month]; } /** * Returns year length. * * @param year year * @return year length */ int getYearLength(int year) { int cycleNumber = (year - 1) / 30; int[] cycleYears; try { cycleYears = ADJUSTED_CYCLE_YEARS.get(cycleNumber); } catch (ArrayIndexOutOfBoundsException e) { cycleYears = null; } if (cycleYears != null) { int yearInCycle = (year - 1) % 30; if (yearInCycle == 29) { return (int)(ADJUSTED_CYCLES[cycleNumber + 1] - ADJUSTED_CYCLES[cycleNumber] - cycleYears[yearInCycle]); } return cycleYears[yearInCycle + 1] - cycleYears[yearInCycle]; } else { return isLeapYear0(year) ? 355 : 354; } } /** * Returns maximum day-of-month. * * @return maximum day-of-month */ int getMaximumDayOfMonth() { return ADJUSTED_MAX_VALUES[POSITION_DAY_OF_MONTH]; } /** * Returns smallest maximum day-of-month. * * @return smallest maximum day-of-month */ int getSmallestMaximumDayOfMonth() { return ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_MONTH]; } /** * Returns maximum day-of-year. * * @return maximum day-of-year */ int getMaximumDayOfYear() { return ADJUSTED_MAX_VALUES[POSITION_DAY_OF_YEAR]; } /** * Returns smallest maximum day-of-year. * * @return smallest maximum day-of-year */ int getSmallestMaximumDayOfYear() { return ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_YEAR]; } // ----- Deviation handling -----// /** * Adds deviation definition. The year and month specified should be the * calculated Hijrah year and month. The month is 1 based. e.g. 9 for * Ramadan (9th month) Addition of anything minus deviation days is * calculated negatively in the case the user wants to subtract days from * the calendar. For example, adding -1 days will subtract one day from the * current date. * * @param startYear start year, 1 origin * @param startMonth start month, 1 origin * @param endYear end year, 1 origin * @param endMonth end month, 1 origin * @param offset offset -2, -1, +1, +2 */ private void addDeviationAsHijrah(Deviation entry) { int startYear = entry.startYear; int startMonth = entry.startMonth - 1 ; int endYear = entry.endYear; int endMonth = entry.endMonth - 1; int offset = entry.offset; if (startYear < 1) { throw new IllegalArgumentException("startYear < 1"); } if (endYear < 1) { throw new IllegalArgumentException("endYear < 1"); } if (startMonth < 0 || startMonth > 11) { throw new IllegalArgumentException( "startMonth < 0 || startMonth > 11"); } if (endMonth < 0 || endMonth > 11) { throw new IllegalArgumentException("endMonth < 0 || endMonth > 11"); } if (endYear > 9999) { throw new IllegalArgumentException("endYear > 9999"); } if (endYear < startYear) { throw new IllegalArgumentException("startYear > endYear"); } if (endYear == startYear && endMonth < startMonth) { throw new IllegalArgumentException( "startYear == endYear && endMonth < startMonth"); } // Adjusting start year. boolean isStartYLeap = isLeapYear0(startYear); // Adjusting the number of month. int[] orgStartMonthNums = ADJUSTED_MONTH_DAYS.get(startYear); if (orgStartMonthNums == null) { if (isStartYLeap) { orgStartMonthNums = Arrays.copyOf(LEAP_NUM_DAYS, LEAP_NUM_DAYS.length); } else { orgStartMonthNums = Arrays.copyOf(NUM_DAYS, NUM_DAYS.length); } } int[] newStartMonthNums = new int[orgStartMonthNums.length]; for (int month = 0; month < 12; month++) { if (month > startMonth) { newStartMonthNums[month] = (orgStartMonthNums[month] - offset); } else { newStartMonthNums[month] = (orgStartMonthNums[month]); } } ADJUSTED_MONTH_DAYS.put(startYear, newStartMonthNums); // Adjusting the days of month. int[] orgStartMonthLengths = ADJUSTED_MONTH_LENGTHS.get(startYear); if (orgStartMonthLengths == null) { if (isStartYLeap) { orgStartMonthLengths = Arrays.copyOf(LEAP_MONTH_LENGTH, LEAP_MONTH_LENGTH.length); } else { orgStartMonthLengths = Arrays.copyOf(MONTH_LENGTH, MONTH_LENGTH.length); } } int[] newStartMonthLengths = new int[orgStartMonthLengths.length]; for (int month = 0; month < 12; month++) { if (month == startMonth) { newStartMonthLengths[month] = orgStartMonthLengths[month] - offset; } else { newStartMonthLengths[month] = orgStartMonthLengths[month]; } } ADJUSTED_MONTH_LENGTHS.put(startYear, newStartMonthLengths); if (startYear != endYear) { // System.out.println("over year"); // Adjusting starting 30 year cycle. int sCycleNumber = (startYear - 1) / 30; int sYearInCycle = (startYear - 1) % 30; // 0-based. int[] startCycles = ADJUSTED_CYCLE_YEARS.get(sCycleNumber); if (startCycles == null) { startCycles = Arrays.copyOf(CYCLEYEAR_START_DATE, CYCLEYEAR_START_DATE.length); } for (int j = sYearInCycle + 1; j < CYCLEYEAR_START_DATE.length; j++) { startCycles[j] = startCycles[j] - offset; } // System.out.println(sCycleNumber + ":" + sYearInCycle); ADJUSTED_CYCLE_YEARS.put(sCycleNumber, startCycles); int sYearInMaxY = (startYear - 1) / 30; int sEndInMaxY = (endYear - 1) / 30; if (sYearInMaxY != sEndInMaxY) { // System.out.println("over 30"); // Adjusting starting 30 * MAX_ADJUSTED_CYCLE year cycle. // System.out.println(sYearInMaxY); for (int j = sYearInMaxY + 1; j < ADJUSTED_CYCLES.length; j++) { ADJUSTED_CYCLES[j] = ADJUSTED_CYCLES[j] - offset; } // Adjusting ending 30 * MAX_ADJUSTED_CYCLE year cycles. for (int j = sEndInMaxY + 1; j < ADJUSTED_CYCLES.length; j++) { ADJUSTED_CYCLES[j] = ADJUSTED_CYCLES[j] + offset; } } // Adjusting ending 30 year cycle. int eCycleNumber = (endYear - 1) / 30; int sEndInCycle = (endYear - 1) % 30; // 0-based. int[] endCycles = ADJUSTED_CYCLE_YEARS.get(eCycleNumber); if (endCycles == null) { endCycles = Arrays.copyOf(CYCLEYEAR_START_DATE, CYCLEYEAR_START_DATE.length); } for (int j = sEndInCycle + 1; j < CYCLEYEAR_START_DATE.length; j++) { endCycles[j] = endCycles[j] + offset; } ADJUSTED_CYCLE_YEARS.put(eCycleNumber, endCycles); } // Adjusting ending year. boolean isEndYLeap = isLeapYear0(endYear); int[] orgEndMonthDays = ADJUSTED_MONTH_DAYS.get(endYear); if (orgEndMonthDays == null) { if (isEndYLeap) { orgEndMonthDays = Arrays.copyOf(LEAP_NUM_DAYS, LEAP_NUM_DAYS.length); } else { orgEndMonthDays = Arrays.copyOf(NUM_DAYS, NUM_DAYS.length); } } int[] newEndMonthDays = new int[orgEndMonthDays.length]; for (int month = 0; month < 12; month++) { if (month > endMonth) { newEndMonthDays[month] = orgEndMonthDays[month] + offset; } else { newEndMonthDays[month] = orgEndMonthDays[month]; } } ADJUSTED_MONTH_DAYS.put(endYear, newEndMonthDays); // Adjusting the days of month. int[] orgEndMonthLengths = ADJUSTED_MONTH_LENGTHS.get(endYear); if (orgEndMonthLengths == null) { if (isEndYLeap) { orgEndMonthLengths = Arrays.copyOf(LEAP_MONTH_LENGTH, LEAP_MONTH_LENGTH.length); } else { orgEndMonthLengths = Arrays.copyOf(MONTH_LENGTH, MONTH_LENGTH.length); } } int[] newEndMonthLengths = new int[orgEndMonthLengths.length]; for (int month = 0; month < 12; month++) { if (month == endMonth) { newEndMonthLengths[month] = orgEndMonthLengths[month] + offset; } else { newEndMonthLengths[month] = orgEndMonthLengths[month]; } } ADJUSTED_MONTH_LENGTHS.put(endYear, newEndMonthLengths); int[] startMonthLengths = ADJUSTED_MONTH_LENGTHS.get(startYear); int[] endMonthLengths = ADJUSTED_MONTH_LENGTHS.get(endYear); int[] startMonthDays = ADJUSTED_MONTH_DAYS.get(startYear); int[] endMonthDays = ADJUSTED_MONTH_DAYS.get(endYear); int startMonthLength = startMonthLengths[startMonth]; int endMonthLength = endMonthLengths[endMonth]; int startMonthDay = startMonthDays[11] + startMonthLengths[11]; int endMonthDay = endMonthDays[11] + endMonthLengths[11]; int maxMonthLength = ADJUSTED_MAX_VALUES[POSITION_DAY_OF_MONTH]; int leastMaxMonthLength = ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_MONTH]; if (maxMonthLength < startMonthLength) { maxMonthLength = startMonthLength; } if (maxMonthLength < endMonthLength) { maxMonthLength = endMonthLength; } ADJUSTED_MAX_VALUES[POSITION_DAY_OF_MONTH] = maxMonthLength; if (leastMaxMonthLength > startMonthLength) { leastMaxMonthLength = startMonthLength; } if (leastMaxMonthLength > endMonthLength) { leastMaxMonthLength = endMonthLength; } ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_MONTH] = leastMaxMonthLength; int maxMonthDay = ADJUSTED_MAX_VALUES[POSITION_DAY_OF_YEAR]; int leastMaxMonthDay = ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_YEAR]; if (maxMonthDay < startMonthDay) { maxMonthDay = startMonthDay; } if (maxMonthDay < endMonthDay) { maxMonthDay = endMonthDay; } ADJUSTED_MAX_VALUES[POSITION_DAY_OF_YEAR] = maxMonthDay; if (leastMaxMonthDay > startMonthDay) { leastMaxMonthDay = startMonthDay; } if (leastMaxMonthDay > endMonthDay) { leastMaxMonthDay = endMonthDay; } ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_YEAR] = leastMaxMonthDay; } /** * Package private Entry for suppling deviations from the Reader. * Each entry consists of a range using the Hijrah calendar, * start year, month, end year, end month, and an offset. * The offset is used to modify the length of the month +2, +1, -1, -2. */ static final class Deviation { Deviation(int startYear, int startMonth, int endYear, int endMonth, int offset) { this.startYear = startYear; this.startMonth = startMonth; this.endYear = endYear; this.endMonth = endMonth; this.offset = offset; } final int startYear; final int startMonth; final int endYear; final int endMonth; final int offset; int getStartYear() { return startYear; } int getStartMonth() { return startMonth; } int getEndYear() { return endYear; } int getEndMonth() { return endMonth; } int getOffset() { return offset; } @Override public String toString() { return String.format("[year: %4d, month: %2d, offset: %+d]", startYear, startMonth, offset); } } }