/* * ----------------------------------------------------------------------- * Copyright © 2013-2015 Meno Hochschild, <http://www.menodata.de/> * ----------------------------------------------------------------------- * This file (HijriAlgorithm.java) is part of project Time4J. * * Time4J is free software: You can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 2.1 of the License, or * (at your option) any later version. * * Time4J 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Time4J. If not, see <http://www.gnu.org/licenses/>. * ----------------------------------------------------------------------- */ package net.time4j.calendar; import net.time4j.base.MathUtils; import net.time4j.engine.CalendarEra; import net.time4j.engine.VariantSource; import java.util.Arrays; import java.util.Collections; import java.util.List; /** * <p>Describes islamic calendar variants based on simplistic deterministic algorithms. </p> * * <p>Most algorithms uses a leap year pattern within a 30-year-cycle. All years have 12 months where * the month lengths are alternately 30 or 29 with the exception of last month which has 30 days in * leap years else 29 days. The supported range in islamic years is 1-1600. </p> * * <p>Note that all these algorithms have <strong>approximated</strong> nature only. There are deviations * from sighting-based variants especially in short term. However, main advantage of algorithm-based variants * is the fact that they can be applied into far past or future. Keep in mind that sighting-based calendars * have a much more constrained valid range. For more background see * <a href="http://www.staff.science.uu.nl/~gent0113/islam/islam_tabcal_variants.htm">The Arithmetical * or Tabular Islamic Calendar</a>. </p> * * @author Meno Hochschild * @since 3.6/4.4 */ /*[deutsch] * <p>Beschreibt islamische Kalendervarianten basierend auf vereinfachenden deterministischen Algorithmen. </p> * * <p>Die meisten Algorithmen verwenden ein Schaltjahresmuster innerhalb eines 30-Jahres-Zyklus. Jedes Jahr hat * 12 Monate, die abwechselnd 30 oder 29 Tage lang sind. Als Ausnahme hat der letzte Monat in Schaltjahren * 30 Tage. Der unterstützte Wertbereich ist in islamischen Jahren 1-1600. </p> * * <p>Zu beachten: All diese Algorithmen haben nur <strong>Näherungscharakter</strong>. Abweichungen von * sichtbasierten Varianten sind besonders kurzfristig möglich. Allerdings besteht der Hauptvorteil der * Algorithmen darin, daß sie auf Zeiten weit in der Vergangenheit oder Zukunft angewandt werden * können, während sichtbasierte Varianten stark in ihrem Gültigkeitsbereich eingeschränkt * sind. Mehr Hintergrundinformationen siehe * <a href="http://www.staff.science.uu.nl/~gent0113/islam/islam_tabcal_variants.htm">The Arithmetical * or Tabular Islamic Calendar</a>. </p> * * @author Meno Hochschild * @since 3.6/4.4 */ public enum HijriAlgorithm implements VariantSource { //~ Statische Felder/Initialisierungen -------------------------------- /** * Uses the leap year pattern {2, 5, 7, 10, 13, 15, 18, 21, 24, 26, 29} with civil (Friday) epoch. * * Variant name: "islamic-eastc" */ /*[deutsch] * Verwendet das Schaltjahrmuster {2, 5, 7, 10, 13, 15, 18, 21, 24, 26, 29} mit der Freitagsepoche. * * Variantenname: "islamic-eastc" */ EAST_ISLAMIC_CIVIL("islamic-eastc", new int[] {2, 5, 7, 10, 13, 15, 18, 21, 24, 26, 29}, true), /** * Uses the leap year pattern {2, 5, 7, 10, 13, 15, 18, 21, 24, 26, 29} with astronomical (Thursday) epoch. * * Variant name: "islamic-easta" */ /*[deutsch] * Verwendet das Schaltjahrmuster {2, 5, 7, 10, 13, 15, 18, 21, 24, 26, 29} mit der Donnerstagsepoche. * * Variantenname: "islamic-easta" */ EAST_ISLAMIC_ASTRO("islamic-easta", new int[] {2, 5, 7, 10, 13, 15, 18, 21, 24, 26, 29}, false), /** * Uses the leap year pattern {2, 5, 7, 10, 13, 16, 18, 21, 24, 26, 29} with civil (Friday) epoch. * * Variant name: "islamic-civil" */ /*[deutsch] * Verwendet das Schaltjahrmuster {2, 5, 7, 10, 13, 16, 18, 21, 24, 26, 29} mit der Freitagsepoche. * * Variantenname: "islamic-civil" */ WEST_ISLAMIC_CIVIL("islamic-civil", new int[] {2, 5, 7, 10, 13, 16, 18, 21, 24, 26, 29}, true), /** * Uses the leap year pattern {2, 5, 7, 10, 13, 16, 18, 21, 24, 26, 29} with astronomical (Thursday) epoch. * * Variant name: "islamic-tbla". This variant is equivalent to Microsoft Hijri (Kuwaiti) calendar. */ /*[deutsch] * Verwendet das Schaltjahrmuster {2, 5, 7, 10, 13, 16, 18, 21, 24, 26, 29} mit der Donnerstagsepoche. * * Variantenname: "islamic-tbla" Diese Variante ist äquivalent zum * Hijri-Kalender (Kuwaiti) von Microsoft. */ WEST_ISLAMIC_ASTRO("islamic-tbla", new int[] {2, 5, 7, 10, 13, 16, 18, 21, 24, 26, 29}, false), /** * Uses the leap year pattern {2, 5, 8, 10, 13, 16, 19, 21, 24, 27, 29} with civil (Friday) epoch. * * Variant name: "islamic-fatimidc" */ /*[deutsch] * Verwendet das Schaltjahrmuster {2, 5, 8, 10, 13, 16, 19, 21, 24, 27, 29} mit der Freitagsepoche. * * Variantenname: "islamic-fatimidc" */ FATIMID_CIVIL("islamic-fatimidc", new int[] {2, 5, 8, 10, 13, 16, 19, 21, 24, 27, 29}, true), /** * Uses the leap year pattern {2, 5, 8, 10, 13, 16, 19, 21, 24, 27, 29} with astronomical (Thursday) epoch. * * Variant name: "islamic-fatimida" */ /*[deutsch] * Verwendet das Schaltjahrmuster {2, 5, 8, 10, 13, 16, 19, 21, 24, 27, 29} mit der Donnerstagsepoche. * * Variantenname: "islamic-fatimida" */ FATIMID_ASTRO("islamic-fatimida", new int[] {2, 5, 8, 10, 13, 16, 19, 21, 24, 27, 29}, false), /** * Uses the leap year pattern {2, 5, 8, 11, 13, 16, 19, 21, 24, 27, 30} with civil (Friday) epoch. * * Variant name: "islamic-habashalhasibc" */ /*[deutsch] * Verwendet das Schaltjahrmuster {2, 5, 8, 11, 13, 16, 19, 21, 24, 27, 30} mit der Freitagsepoche. * * Variantenname: "islamic-habashalhasibc" */ HABASH_AL_HASIB_CIVIL("islamic-habashalhasibc", new int[] {2, 5, 8, 11, 13, 16, 19, 21, 24, 27, 30}, true), /** * Uses the leap year pattern {2, 5, 8, 11, 13, 16, 19, 21, 24, 27, 30} with astronomical (Thursday) epoch. * * Variant name: "islamic-habashalhasiba" */ /*[deutsch] * Verwendet das Schaltjahrmuster {2, 5, 8, 11, 13, 16, 19, 21, 24, 27, 30} mit der Donnerstagsepoche. * * Variantenname: "islamic-habashalhasiba" */ HABASH_AL_HASIB_ASTRO("islamic-habashalhasiba", new int[] {2, 5, 8, 11, 13, 16, 19, 21, 24, 27, 30}, false); private static final long LENGTH_OF_30_YEAR_CYCLE; private static final long START_622_07_15; private static final long START_622_07_16; private static final int MAX_YEAR; private static final long ASTRO_1600_12_29; private static final long CIVIL_1600_12_29; static { LENGTH_OF_30_YEAR_CYCLE = 30 * 354 + 11; // HistoricDate date = HistoricDate.of(HistoricEra.AD, 622, 7, 15); // Thursday epoch START_622_07_15 = -492879; // ChronoHistory.PROLEPTIC_JULIAN.convert(date).get(EpochDays.UTC); START_622_07_16 = START_622_07_15 + 1; MAX_YEAR = 1600; ASTRO_1600_12_29 = 74106; CIVIL_1600_12_29 = ASTRO_1600_12_29 + 1; } //~ Instanzvariablen -------------------------------------------------- private final transient Transformer calsys; //~ Konstruktoren ------------------------------------------------- private HijriAlgorithm( String variant, int[] intercalaries, boolean civil ) { this.calsys = new Transformer(variant, intercalaries, civil); } //~ Methoden ------------------------------------------------------ @Override public String getVariant() { return this.calsys.variant; } // yields the calculation engine EraYearMonthDaySystem<HijriCalendar> getCalendarSystem() { return this.calsys; } //~ Innere Klassen ---------------------------------------------------- private static class Transformer implements EraYearMonthDaySystem<HijriCalendar> { //~ Instanzvariablen ---------------------------------------------- private final String variant; private final int[] intercalaries; private final boolean civil; //~ Konstruktoren ------------------------------------------------- Transformer( String variant, int[] intercalaries, boolean civil ) { super(); this.variant = variant; this.intercalaries = intercalaries; this.civil = civil; } //~ Methoden ------------------------------------------------------ @Override public boolean isValid( CalendarEra era, int hyear, int hmonth, int hdom ) { return ( (era == HijriEra.ANNO_HEGIRAE) && (hyear >= 1) && (hyear <= MAX_YEAR) && (hmonth >= 1) && (hmonth <= 12) && (hdom >= 1) && (hdom <= this.getLengthOfMonth(era, hyear, hmonth)) ); } @Override public int getLengthOfMonth( CalendarEra era, int hyear, int hmonth ) { if (era != HijriEra.ANNO_HEGIRAE) { throw new IllegalArgumentException("Wrong era: " + era); } else if (hyear < 1 || hyear > MAX_YEAR || hmonth < 1 || hmonth > 12) { throw new IllegalArgumentException("Out of bounds: " + hyear + "/" + hmonth); } if (hmonth == 12) { int y = ((hyear - 1) % 30) + 1; return ((Arrays.binarySearch(this.intercalaries, y) >= 0) ? 30 : 29); } return ((hmonth % 2 == 1) ? 30 : 29); } @Override public int getLengthOfYear( CalendarEra era, int hyear ) { if (era != HijriEra.ANNO_HEGIRAE) { throw new IllegalArgumentException("Wrong era: " + era); } if ((hyear < 1) || (hyear > MAX_YEAR)) { throw new IllegalArgumentException("Out of bounds: yearOfEra=" + hyear); } int y = ((hyear - 1) % 30) + 1; return ((Arrays.binarySearch(this.intercalaries, y) >= 0) ? 355 : 354); } @Override public HijriCalendar transform(long utcDays) { long start = this.getMinimumSinceUTC(); if ((utcDays < start) || (utcDays > this.getMaximumSinceUTC())) { throw new IllegalArgumentException("Out of supported range: " + utcDays); } long days = Math.subtractExact(utcDays, start); int hyear = 1; int hmonth = 1; int hdom = 1; hyear += MathUtils.safeCast((days / LENGTH_OF_30_YEAR_CYCLE) * 30); int delta = (int) (days % LENGTH_OF_30_YEAR_CYCLE); for (int i = 1; i < 30; i++) { int ylen = 354; if (Arrays.binarySearch(this.intercalaries, i) >= 0) { ylen++; } if (delta > ylen) { delta -= ylen; hyear++; } else { break; } } for (int i = 1; i < 12; i++) { int mlen = 30; if ((i % 2) == 0) { mlen = 29; } if (delta > mlen) { delta -= mlen; hmonth++; } else { break; } } hdom += delta; int test; if (hmonth == 12) { int y = ((hyear - 1) % 30) + 1; test = ((Arrays.binarySearch(this.intercalaries, y) >= 0) ? 30 : 29); } else { test = ((hmonth % 2 == 1) ? 30 : 29); } if (hdom > test) { hdom = 1; hmonth++; if (hmonth > 12) { hmonth = 1; hyear++; } } return HijriCalendar.of(this.variant, hyear, hmonth, hdom); } @Override public long transform(HijriCalendar date) { int hyear = date.getYear(); int hmonth = date.getMonth().getValue(); int hdom = date.getDayOfMonth(); if (hyear < 1 || hyear > MAX_YEAR || hmonth < 1 || hmonth > 12 || hdom < 1 || hdom > 30) { throw new IllegalArgumentException("Out of supported range: " + date); } long days = ((hyear - 1) / 30) * LENGTH_OF_30_YEAR_CYCLE; int y = ((hyear - 1) % 30) + 1; for (int i = 1; i < y; i++) { if (Arrays.binarySearch(this.intercalaries, i) >= 0) { days += 355; } else { days += 354; } } for (int i = 1; i < hmonth; i++) { if ((i % 2) == 0) { days += 29; } else { days += 30; } } if (hdom == 30) { if ( (hmonth == 12 && Arrays.binarySearch(this.intercalaries, y) < 0) || ((hmonth != 12) && (hmonth % 2) == 0) ) { throw new IllegalArgumentException("Invalid day-of-month: " + date); } } days += hdom; return this.getMinimumSinceUTC() + days - 1; } @Override public long getMinimumSinceUTC() { return (this.civil ? START_622_07_16 : START_622_07_15); } @Override public long getMaximumSinceUTC() { return (this.civil ? CIVIL_1600_12_29 : ASTRO_1600_12_29); } @Override public List<CalendarEra> getEras() { CalendarEra era = HijriEra.ANNO_HEGIRAE; return Collections.singletonList(era); } } }