/* * ----------------------------------------------------------------------- * Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/> * ----------------------------------------------------------------------- * This file (HijriCalendar.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.GeneralTimestamp; import net.time4j.Moment; import net.time4j.PlainTime; import net.time4j.SystemClock; import net.time4j.Weekday; import net.time4j.Weekmodel; import net.time4j.base.MathUtils; import net.time4j.base.TimeSource; import net.time4j.calendar.service.GenericDatePatterns; import net.time4j.calendar.service.StdEnumDateElement; import net.time4j.calendar.service.StdIntegerDateElement; import net.time4j.calendar.service.StdWeekdayElement; import net.time4j.engine.AttributeQuery; import net.time4j.engine.CalendarDays; import net.time4j.engine.CalendarFamily; import net.time4j.engine.CalendarVariant; import net.time4j.engine.ChronoElement; import net.time4j.engine.ChronoEntity; import net.time4j.engine.ChronoException; import net.time4j.engine.ChronoMerger; import net.time4j.engine.ChronoUnit; import net.time4j.engine.DisplayStyle; import net.time4j.engine.ElementRule; import net.time4j.engine.FormattableElement; import net.time4j.engine.StartOfDay; import net.time4j.engine.ValidationElement; import net.time4j.engine.VariantSource; import net.time4j.format.Attributes; import net.time4j.format.CalendarType; import net.time4j.format.Leniency; import net.time4j.format.LocalizedPatternSupport; import net.time4j.tz.TZID; import net.time4j.tz.Timezone; import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.time.chrono.Chronology; import java.time.chrono.HijrahDate; import java.time.temporal.ChronoField; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalQueries; import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * <p>Represents the Hijri calendar used in many islamic countries. </p> * * <p>It is a lunar calendar which exists in several variants and is mainly for religious purposes. * The variant used in Saudi-Arabia is named "islamic-umalqura" and is based on data partially * observed by sighting the new moon, partially by astronomical calculations/predictions. Note that the * religious authorities in most countries often publish dates which deviate from such official calendars * by one or two days. </p> * * <p>The calendar year is divided into 12 islamic months. Every month usually has either 29 or 30 days. The length * of the month in days shall reflect the date when the new moon appears. However, for every variant there * are different data or rules how to determine if a month has 29 or 30 days. The Hijri calendar day starts * in the evening. New variants can be configured by a file named "{variant-name}.data" in the * data-subdirectory of resource class path (where hyphens are replaced by underscores). Format details see * the file "islamic_umalqura.data". </p> * * <p>Following elements which are declared as constants are registered by * this class: </p> * * <ul> * <li>{@link #DAY_OF_WEEK}</li> * <li>{@link #DAY_OF_MONTH}</li> * <li>{@link #DAY_OF_YEAR}</li> * <li>{@link #MONTH_OF_YEAR}</li> * <li>{@link #YEAR_OF_ERA}</li> * <li>{@link #ERA}</li> * </ul> * * <p>Furthermore, all elements defined in {@code EpochDays} and {@link CommonElements} are supported. </p> * * <p>Examples of usage: </p> * * <pre> * // parse a Hijri-string and convert to a gregorian date * ChronoFormatter<HijriCalendar> formatter = * ChronoFormatter.setUp(HijriCalendar.class, Locale.ENGLISH) * .addPattern("EEE, d. MMMM yy", PatternType.NON_ISO_DATE).build() * .withCalendarVariant(HijriCalendar.VARIANT_UMALQURA) * .with(Attributes.PIVOT_YEAR, 1500); // mapped to range 1400-1499 * HijriCalendar hijri = formatter.parse("Thu, 29. Ramadan 36"); * PlainDate date = hijri.transform(PlainDate.class); * System.out.println(date); // 2015-07-16 * * // determine actual Hijri date (two methods) * HijriCalendar today = // conversion valid at noon (not in the evening when next islamic day starts) * SystemClock.inLocalView().today().transform(HijriCalendar.class, HijriCalendar.VARIANT_UMALQURA); * HijriCalendar todayExact = // taking into account the specific start of day for Hijri calendar * SystemClock.inLocalView().now( * HijriCalendar.family(), * HijriCalendar.VARIANT_UMALQURA, * StartOfDay.EVENING // simple approximation => 18:00 * ).toDate(); * </pre> * * <p>Note that the supported range of this class is limited compared with the gregorian counter-example. Users * can apply following code to determine the exact variant-dependent range: </p> * * <pre> * CalendarSystem<HijriCalendar> calsys = * HijriCalendar.family().getCalendarSystem(HijriCalendar.VARIANT_UMALQURA); * long min = calsys.getMinimumSinceUTC(); // -32556 * long max = calsys.getMaximumSinceUTC(); // 38671 * * // same minimum and maximum displayed as Hijri calendar dates * HijriCalendar minHijri = calsys.transform(min); // AH-1300-01-01[islamic-umalqura] * HijriCalendar maxHijri = calsys.transform(max); // AH-1500-12-30[islamic-umalqura] * * // same minimum and maximum displayed as gregorian dates * PlainDate minGregorian = PlainDate.of(min, EpochDays.UTC); // 1882-11-12 * PlainDate maxGregorian = PlainDate.of(max, EpochDays.UTC); // 2077-11-16 * </pre> * * @author Meno Hochschild * @since 3.5/4.3 * @doctags.concurrency {immutable} */ /*[deutsch] * <p>Repräsentiert den Hijri-Kalender, der in vielen islamischen Ländern vorwiegend für * religiöse Zwecke benutzt wird. </p> * * <p>Es handelt sich um einen lunaren Kalender, der in verschiedenen Varianten existiert. Die Variante * in Saudi-Arabien heißt "islamic-umalqura" und basiert teilweise auf Daten gewonnen * durch die Sichtung des Neumonds, teilweise auf astronomischen Berechnungen und Voraussagen. Zu beachten: * Die religiösen Autoritäten in den meisten Ländern folgen nicht streng den offiziellen * Kalendervarianten, sondern veröffentlichen oft ein Datum, das 1 oder 2 Tage abweichen kann. </p> * * <p>Das Kalendarjahr wird in 12 islamische Monate geteilt. Jeder Monat hat gewöhnlich entweder 29 oder * 30 Tage. Die Länge des Monats in Tagen soll den Zeitpunkt reflektieren, wann der Neumond gesichtet wird. * Aber jede Variante kennt verschiedenen Daten oder Regeln, um zu bestimmen, ob ein Monat 29 oder 30 Tage hat. * Der islamische Kalendertag startet am Abend. Neue Varianten können mit Hilfe einer Datei konfiguriert * werden, die den Namen "{variant-name}.data" hat und im data-Unterordner des Ressourcen-Klassenpfads * liegt (wobei Bindestriche durch Unterstriche ersetzt werden). Formatdetails siehe die vorhandene Datei * "islamic_umalqura.data". </p> * * <p>Registriert sind folgende als Konstanten deklarierte Elemente: </p> * * <ul> * <li>{@link #DAY_OF_WEEK}</li> * <li>{@link #DAY_OF_MONTH}</li> * <li>{@link #DAY_OF_YEAR}</li> * <li>{@link #MONTH_OF_YEAR}</li> * <li>{@link #YEAR_OF_ERA}</li> * <li>{@link #ERA}</li> * </ul> * * <p>Au&slig;erdem werden alle Elemente von {@code EpochDays} und {@link CommonElements} unterstützt. </p> * * <p>Anwendungsbeispiele: </p> * * <pre> * // Hijri-Text interpretieren und in ein gregorianisches Datum umwandeln * ChronoFormatter<HijriCalendar> formatter = * ChronoFormatter.setUp(HijriCalendar.class, Locale.ENGLISH) * .addPattern("EEE, d. MMMM yy", PatternType.NON_ISO_DATE).build() * .withCalendarVariant(HijriCalendar.VARIANT_UMALQURA) * .with(Attributes.PIVOT_YEAR, 1500); // abgebildet auf den Bereich 1400-1499 * HijriCalendar hijri = formatter.parse("Thu, 29. Ramadan 36"); * PlainDate date = hijri.transform(PlainDate.class); * System.out.println(date); // 2015-07-16 * * // aktuelles Hijri-Datum bestimmen (zwei Methoden) * HijriCalendar today = // Konversion mittags gültig (nicht abends, wenn der nächste Tag beginnt) * SystemClock.inLocalView().today().transform(HijriCalendar.class, HijriCalendar.VARIANT_UMALQURA); * HijriCalendar todayExact = // berücksichtigt den spezifischen Start des islamischen Tags * SystemClock.inLocalView().now( * HijriCalendar.family(), * HijriCalendar.VARIANT_UMALQURA, * StartOfDay.EVENING // einfache Näherung => 18:00 * ).toDate(); * </pre> * * <p>Hinweis: Der unterstütze variantenabhängige Wertbereich dieser Klasse ist verglichen mit * dem gregorianischen Standardfall begrenzt. Anwender können folgenden Code nutzen, um den genauen * Wertbereich zu bestimmen: </p> * * <pre> * CalendarSystem<HijriCalendar> calsys = * HijriCalendar.family().getCalendarSystem(HijriCalendar.VARIANT_UMALQURA); * long min = calsys.getMinimumSinceUTC(); // -32556 * long max = calsys.getMaximumSinceUTC(); // 38671 * * // gleiches Minimum und Maximum als islamisches Kalendardatum * HijriCalendar minHijri = calsys.transform(min); // AH-1300-01-01[islamic-umalqura] * HijriCalendar maxHijri = calsys.transform(max); // AH-1500-12-30[islamic-umalqura] * * // gleiches Minimum und Maximum als gregorianisches Kalenderdatum * PlainDate minGregorian = PlainDate.of(min, EpochDays.UTC); // 1882-11-12 * PlainDate maxGregorian = PlainDate.of(max, EpochDays.UTC); // 2077-11-16 * </pre> * * @author Meno Hochschild * @since 3.5/4.3 * @doctags.concurrency {immutable} */ @CalendarType("islamic") public final class HijriCalendar extends CalendarVariant<HijriCalendar> implements LocalizedPatternSupport { //~ Statische Felder/Initialisierungen -------------------------------- private static final int YEAR_INDEX = 0; private static final int DAY_OF_MONTH_INDEX = 2; private static final int DAY_OF_YEAR_INDEX = 3; /** * <p>Represents the islamic era. </p> */ /*[deutsch] * <p>Repräsentiert die islamische Ära. </p> */ @FormattableElement(format = "G") public static final ChronoElement<HijriEra> ERA = new StdEnumDateElement<>("ERA", HijriCalendar.class, HijriEra.class, 'G'); /** * <p>Represents the islamic year. </p> */ /*[deutsch] * <p>Repräsentiert das islamische Jahr. </p> */ @FormattableElement(format = "y") public static final StdCalendarElement<Integer, HijriCalendar> YEAR_OF_ERA = new StdIntegerDateElement<>( "YEAR_OF_ERA", HijriCalendar.class, Integer.MIN_VALUE, Integer.MAX_VALUE, 'y', new HijriMonth.Operator(-12), new HijriMonth.Operator(12)); /** * <p>Represents the islamic month. </p> */ /*[deutsch] * <p>Repräsentiert den islamischen Monat. </p> */ @FormattableElement(format = "M", standalone = "L") public static final StdCalendarElement<HijriMonth, HijriCalendar> MONTH_OF_YEAR = new StdEnumDateElement<>( "MONTH_OF_YEAR", HijriCalendar.class, HijriMonth.class, 'M', new HijriMonth.Operator(-1), new HijriMonth.Operator(1)); /** * <p>Represents the islamic day of month. </p> */ /*[deutsch] * <p>Repräsentiert den islamischen Tag des Monats. </p> */ @FormattableElement(format = "d") public static final StdCalendarElement<Integer, HijriCalendar> DAY_OF_MONTH = new StdIntegerDateElement<>("DAY_OF_MONTH", HijriCalendar.class, 1, 30, 'd'); /** * <p>Represents the islamic day of year. </p> */ /*[deutsch] * <p>Repräsentiert den islamischen Tag des Jahres. </p> */ @FormattableElement(format = "D") public static final StdCalendarElement<Integer, HijriCalendar> DAY_OF_YEAR = new StdIntegerDateElement<>("DAY_OF_YEAR", HijriCalendar.class, 1, 355, 'D'); /** * <p>Represents the islamic day of week. </p> * * <p>If the day-of-week is set to a new value then Time4J handles the islamic calendar week * as starting on Sunday. </p> */ /*[deutsch] * <p>Repräsentiert den islamischen Tag der Woche. </p> * * <p>Wenn der Tag der Woche auf einen neuen Wert gesetzt wird, behandelt Time4J die islamische * Kalenderwoche so, daß sie am Sonntag beginnt. </p> */ @FormattableElement(format = "E") public static final StdCalendarElement<Weekday, HijriCalendar> DAY_OF_WEEK = new StdWeekdayElement<>(HijriCalendar.class); /** * The name of Umm-al-qura-variant. * * <p>The supported range of islamic years is 1300-1500. </p> */ /*[deutsch] * Der Name der Umm-al-qura-Variante. * * <p>Der unterstütze Wertebereich der islamischen Jahre ist 1300-1500. </p> */ public static final String VARIANT_UMALQURA = "islamic-umalqura"; /** * The name of the astronomical ICU4J-variant. * * <p>The supported range of islamic years is 1-1600. </p> * * @since 3.6/4.4 */ /*[deutsch] * Der Name der astronomischen ICU4J-Variante. * * <p>Der unterstütze Wertebereich der islamischen Jahre ist 1-1600. </p> * * @since 3.6/4.4 */ public static final String VARIANT_ICU4J = "islamic-icu4j"; /** * The name of the Turkish Diyanet-variant. * * <p>The supported range is 1318-01-01/1444-05-29 (ISO: 1900-05-01/2022-12-23). </p> * * @since 3.9/4.6 */ /*[deutsch] * Der Name der türkischen Diyanet-Variante. * * <p>Der unterstütze Wertebereich ist 1318-01-01/1444-05-29 (ISO: 1900-05-01/2022-12-23). </p> * * @since 3.9/4.6 */ public static final String VARIANT_DIYANET = "islamic-diyanet"; private static final Map<String, EraYearMonthDaySystem<HijriCalendar>> CALSYS; private static final CalendarFamily<HijriCalendar> ENGINE; static { Map<String, EraYearMonthDaySystem<HijriCalendar>> calsys = new VariantMap(); calsys.put(VARIANT_UMALQURA, AstronomicalHijriData.UMALQURA); for (HijriAlgorithm algo : HijriAlgorithm.values()) { calsys.put(algo.getVariant(), algo.getCalendarSystem()); } CALSYS = calsys; CalendarFamily.Builder<HijriCalendar> builder = CalendarFamily.Builder.setUp( HijriCalendar.class, new Merger(), CALSYS) .appendElement( ERA, new EraRule()) .appendElement( YEAR_OF_ERA, new IntegerRule(YEAR_INDEX)) .appendElement( MONTH_OF_YEAR, new MonthRule()) .appendElement( CommonElements.RELATED_GREGORIAN_YEAR, new RelatedGregorianYearRule<>(CALSYS, DAY_OF_YEAR)) .appendElement( DAY_OF_MONTH, new IntegerRule(DAY_OF_MONTH_INDEX)) .appendElement( DAY_OF_YEAR, new IntegerRule(DAY_OF_YEAR_INDEX)) .appendElement( DAY_OF_WEEK, new WeekdayRule()) .appendExtension( new CommonElements.Weekengine( HijriCalendar.class, DAY_OF_MONTH, DAY_OF_YEAR, getDefaultWeekmodel())); ENGINE = builder.build(); } private static final long serialVersionUID = 4666707700222367373L; //~ Instanzvariablen -------------------------------------------------- private transient final int hyear; private transient final int hmonth; private transient final int hdom; private transient final String variant; //~ Konstruktoren ----------------------------------------------------- private HijriCalendar( int hyear, int hmonth, int hdom, String variant ) { super(); this.hyear = hyear; this.hmonth = hmonth; this.hdom = hdom; this.variant = variant; } //~ Methoden ---------------------------------------------------------- /** * <p>Creates a new instance of a Hijri calendar date in given variant. </p> * * @param variant calendar variant * @param hyear islamic year * @param hmonth islamic month * @param hdom islamic day of month * @return new instance of {@code HijriCalendar} * @throws ChronoException if given variant is not supported * @throws IllegalArgumentException in case of any inconsistencies * @since 3.5/4.3 */ /*[deutsch] * <p>Erzeugt ein neues Hijri-Kalenderdatum in der angegebenen Variante. </p> * * @param variant calendar variant * @param hyear islamic year * @param hmonth islamic month * @param hdom islamic day of month * @return new instance of {@code HijriCalendar} * @throws ChronoException if given variant is not supported * @throws IllegalArgumentException in case of any inconsistencies * @since 3.5/4.3 */ public static HijriCalendar of( String variant, int hyear, HijriMonth hmonth, int hdom ) { return HijriCalendar.of(variant, hyear, hmonth.getValue(), hdom); } /** * <p>Creates a new instance of a Hijri calendar date in given variant. </p> * * @param variant calendar variant * @param hyear islamic year * @param hmonth islamic month * @param hdom islamic day of month * @return new instance of {@code HijriCalendar} * @throws ChronoException if given variant is not supported * @throws IllegalArgumentException in case of any inconsistencies * @since 3.5/4.3 */ /*[deutsch] * <p>Erzeugt ein neues Hijri-Kalenderdatum in der angegebenen Variante. </p> * * @param variant calendar variant * @param hyear islamic year * @param hmonth islamic month * @param hdom islamic day of month * @return new instance of {@code HijriCalendar} * @throws ChronoException if given variant is not supported * @throws IllegalArgumentException in case of any inconsistencies * @since 3.5/4.3 */ public static HijriCalendar of( String variant, int hyear, int hmonth, int hdom ) { EraYearMonthDaySystem<HijriCalendar> calsys = getCalendarSystem(variant); if (!calsys.isValid(HijriEra.ANNO_HEGIRAE, hyear, hmonth, hdom)) { throw new IllegalArgumentException( "Invalid hijri date: year=" + hyear + ", month=" + hmonth + ", day=" + hdom); } return new HijriCalendar(hyear, hmonth, hdom, variant); } /** * <p>Creates a new instance of a Hijri calendar date in given variant. </p> * * @param variantSource source of calendar variant * @param hyear islamic year * @param hmonth islamic month * @param hdom islamic day of month * @return new instance of {@code HijriCalendar} * @throws ChronoException if given variant is not supported * @throws IllegalArgumentException in case of any inconsistencies * @since 3.6/4.4 */ /*[deutsch] * <p>Erzeugt ein neues Hijri-Kalenderdatum in der angegebenen Variante. </p> * * @param variantSource source of calendar variant * @param hyear islamic year * @param hmonth islamic month * @param hdom islamic day of month * @return new instance of {@code HijriCalendar} * @throws ChronoException if given variant is not supported * @throws IllegalArgumentException in case of any inconsistencies * @since 3.6/4.4 */ public static HijriCalendar of( VariantSource variantSource, int hyear, HijriMonth hmonth, int hdom ) { return HijriCalendar.of(variantSource.getVariant(), hyear, hmonth.getValue(), hdom); } /** * <p>Creates a new instance of a Hijri calendar date in given variant. </p> * * @param variantSource source of calendar variant * @param hyear islamic year * @param hmonth islamic month * @param hdom islamic day of month * @return new instance of {@code HijriCalendar} * @throws ChronoException if given variant is not supported * @throws IllegalArgumentException in case of any inconsistencies * @since 3.6/4.4 */ /*[deutsch] * <p>Erzeugt ein neues Hijri-Kalenderdatum in der angegebenen Variante. </p> * * @param variantSource source of calendar variant * @param hyear islamic year * @param hmonth islamic month * @param hdom islamic day of month * @return new instance of {@code HijriCalendar} * @throws ChronoException if given variant is not supported * @throws IllegalArgumentException in case of any inconsistencies * @since 3.6/4.4 */ public static HijriCalendar of( VariantSource variantSource, int hyear, int hmonth, int hdom ) { return HijriCalendar.of(variantSource.getVariant(), hyear, hmonth, hdom); } /** * <p>Creates a new instance of a Hijri calendar date in the variant "islamic-umalqura" * used in Saudi-Arabia. </p> * * @param hyear islamic year * @param hmonth islamic month * @param hdom islamic day of month * @return new instance of {@code HijriCalendar} * @throws ChronoException if given variant is not supported * @throws IllegalArgumentException in case of any inconsistencies * @since 3.5/4.3 */ /*[deutsch] * <p>Erzeugt ein neues Hijri-Kalenderdatum in der Variante "islamic-umalqura", die in * Saudi-Arabien benutzt wird. </p> * * @param hyear islamic year * @param hmonth islamic month * @param hdom islamic day of month * @return new instance of {@code HijriCalendar} * @throws IllegalArgumentException in case of any inconsistencies * @since 3.5/4.3 */ public static HijriCalendar ofUmalqura( int hyear, HijriMonth hmonth, int hdom ) { return HijriCalendar.of(VARIANT_UMALQURA, hyear, hmonth.getValue(), hdom); } /** * <p>Creates a new instance of a Hijri calendar date in the variant "islamic-umalqura" * used in Saudi-Arabia. </p> * * @param hyear islamic year * @param hmonth islamic month * @param hdom islamic day of month * @return new instance of {@code HijriCalendar} * @throws ChronoException if given variant is not supported * @throws IllegalArgumentException in case of any inconsistencies * @since 3.5/4.3 */ /*[deutsch] * <p>Erzeugt ein neues Hijri-Kalenderdatum in der Variante "islamic-umalqura", die in * Saudi-Arabien benutzt wird. </p> * * @param hyear islamic year * @param hmonth islamic month * @param hdom islamic day of month * @return new instance of {@code HijriCalendar} * @throws IllegalArgumentException in case of any inconsistencies * @since 3.5/4.3 */ public static HijriCalendar ofUmalqura( int hyear, int hmonth, int hdom ) { return HijriCalendar.of(VARIANT_UMALQURA, hyear, hmonth, hdom); } /** * <p>Obtains the current calendar date in system time. </p> * * <p>Convenient short-cut for: * {@code SystemClock.inLocalView().now(HijriCalendar.family(), variant, startOfDay).toDate())}. </p> * * @param variant calendar variant * @param startOfDay determines the exact time of day when the calendar date will change (usually in the evening) * @return current calendar date in system time zone using the system clock * @see SystemClock#inLocalView() * @see net.time4j.ZonalClock#now(CalendarFamily, String, StartOfDay) * @see StartOfDay#EVENING * @since 3.23/4.19 */ /*[deutsch] * <p>Ermittelt das aktuelle Kalenderdatum in der Systemzeit. </p> * * <p>Bequeme Abkürzung für: * {@code SystemClock.inLocalView().now(HijriCalendar.family(), variant, startOfDay).toDate())}. </p> * * @param variant calendar variant * @param startOfDay determines the exact time of day when the calendar date will change (usually in the evening) * @return current calendar date in system time zone using the system clock * @see SystemClock#inLocalView() * @see net.time4j.ZonalClock#now(CalendarFamily, String, StartOfDay) * @see StartOfDay#EVENING * @since 3.23/4.19 */ public static HijriCalendar nowInSystemTime( String variant, StartOfDay startOfDay ) { return SystemClock.inLocalView().now(HijriCalendar.family(), variant, startOfDay).toDate(); } /** * <p>Obtains the current calendar date in system time. </p> * * <p>Convenient short-cut for: * {@code SystemClock.inLocalView().now(HijriCalendar.family(), variantSource, startOfDay).toDate())}. </p> * * @param variantSource source of calendar variant * @param startOfDay determines the exact time of day when the calendar date will change (usually in the evening) * @return current calendar date in system time zone using the system clock * @see SystemClock#inLocalView() * @see net.time4j.ZonalClock#now(CalendarFamily, VariantSource, StartOfDay) * @see StartOfDay#EVENING * @since 3.23/4.19 */ /*[deutsch] * <p>Ermittelt das aktuelle Kalenderdatum in der Systemzeit. </p> * * <p>Bequeme Abkürzung für: * {@code SystemClock.inLocalView().now(HijriCalendar.family(), variantSource, startOfDay).toDate())}. </p> * * @param variantSource source of calendar variant * @param startOfDay determines the exact time of day when the calendar date will change (usually in the evening) * @return current calendar date in system time zone using the system clock * @see SystemClock#inLocalView() * @see net.time4j.ZonalClock#now(CalendarFamily, VariantSource, StartOfDay) * @see StartOfDay#EVENING * @since 3.23/4.19 */ public static HijriCalendar nowInSystemTime( VariantSource variantSource, StartOfDay startOfDay ) { return SystemClock.inLocalView().now(HijriCalendar.family(), variantSource, startOfDay).toDate(); } /** * <p>Yields the islamic era. </p> * * @return {@link HijriEra#ANNO_HEGIRAE} * @since 3.5/4.3 */ /*[deutsch] * <p>Liefert die islamische Ära. </p> * * @return {@link HijriEra#ANNO_HEGIRAE} * @since 3.5/4.3 */ public HijriEra getEra() { return HijriEra.ANNO_HEGIRAE; } /** * <p>Yields the islamic year. </p> * * @return int * @since 3.5/4.3 */ /*[deutsch] * <p>Liefert das islamische Jahr. </p> * * @return int * @since 3.5/4.3 */ public int getYear() { return this.hyear; } /** * <p>Yields the islamic month. </p> * * @return enum * @since 3.5/4.3 */ /*[deutsch] * <p>Liefert den islamischen Monat. </p> * * @return enum * @since 3.5/4.3 */ public HijriMonth getMonth() { return HijriMonth.valueOf(this.hmonth); } /** * <p>Yields the islamic day of month. </p> * * @return int * @since 3.5/4.3 */ /*[deutsch] * <p>Liefert den islamischen Tag des Monats. </p> * * @return int * @since 3.5/4.3 */ public int getDayOfMonth() { return this.hdom; } /** * <p>Determines the day of week. </p> * * <p>The Hijri calendar also uses a 7-day-week. </p> * * @return Weekday * @since 3.5/4.3 */ /*[deutsch] * <p>Ermittelt den Wochentag. </p> * * <p>Der Hijri-Kalendar verwendet ebenfalls eine 7-Tage-Woche. </p> * * @return Weekday * @since 3.5/4.3 */ public Weekday getDayOfWeek() { long utcDays = getCalendarSystem(variant).transform(this); return Weekday.valueOf(MathUtils.floorModulo(utcDays + 5, 7) + 1); } /** * <p>Yields the islamic day of year. </p> * * @return int * @since 3.5/4.3 */ /*[deutsch] * <p>Liefert den islamischen Tag des Jahres. </p> * * @return int * @since 3.5/4.3 */ public int getDayOfYear() { return this.get(DAY_OF_YEAR).intValue(); } @Override public String getVariant() { return this.variant; } /** * <p>Yields the length of current islamic month in days. </p> * * @return int * @since 3.6/4.4 */ /*[deutsch] * <p>Liefert die Länge des aktuellen islamischen Monats in Tagen. </p> * * @return int * @since 3.6/4.4 */ public int lengthOfMonth() { return this.getCalendarSystem().getLengthOfMonth(HijriEra.ANNO_HEGIRAE, this.hyear, this.hmonth); } /** * <p>Yields the length of current islamic year in days. </p> * * @return int * @since 3.6/4.4 * @throws ChronoException if data are not available for the whole year (edge case) */ /*[deutsch] * <p>Liefert die Länge des aktuellen islamischen Jahres in Tagen. </p> * * @return int * @since 3.6/4.4 * @throws ChronoException if data are not available for the whole year (edge case) */ public int lengthOfYear() { try { return this.getCalendarSystem().getLengthOfYear(HijriEra.ANNO_HEGIRAE, this.hyear); } catch (IllegalArgumentException iae) { throw new ChronoException(iae.getMessage(), iae); } } /** * <p>Convenient short form for {@code with(YEAR_OF_ERA.incremented())}. </p> * * @return copy of this instance at next year * @since 3.5/4.3 */ /*[deutsch] * <p>Kurzform für {@code with(YEAR_OF_ERA.incremented())}. </p> * * @return copy of this instance at next year * @since 3.5/4.3 */ public HijriCalendar nextYear() { return this.with(YEAR_OF_ERA.incremented()); } /** * <p>Convenient short form for {@code with(MONTH_OF_YEAR.incremented())}. </p> * * @return copy of this instance at next month * @since 3.5/4.3 */ /*[deutsch] * <p>Kurzform für {@code with(MONTH_OF_YEAR.incremented())}. </p> * * @return copy of this instance at next month * @since 3.5/4.3 */ public HijriCalendar nextMonth() { return this.with(MONTH_OF_YEAR.incremented()); } /** * <p>Convenient short form for {@code with(DAY_OF_MONTH.incremented())}. </p> * * @return copy of this instance at next day * @since 3.5/4.3 */ /*[deutsch] * <p>Kurzform für {@code with(DAY_OF_MONTH.incremented())}. </p> * * @return copy of this instance at next day * @since 3.5/4.3 */ public HijriCalendar nextDay() { return this.with(DAY_OF_MONTH.incremented()); } /** * <p>Creates a new local timestamp with this date and given wall time. </p> * * <p>If the time {@link PlainTime#midnightAtEndOfDay() T24:00} is used * then the resulting timestamp will automatically be normalized such * that the timestamp will contain the following day instead. </p> * * @param time wall time * @return general timestamp as composition of this date and given time * @since 3.8/4.5 */ /*[deutsch] * <p>Erzeugt einen allgemeinen Zeitstempel mit diesem Datum und der angegebenen Uhrzeit. </p> * * <p>Wenn {@link PlainTime#midnightAtEndOfDay() T24:00} angegeben wird, * dann wird der Zeitstempel automatisch so normalisiert, daß er auf * den nächsten Tag verweist. </p> * * @param time wall time * @return general timestamp as composition of this date and given time * @since 3.8/4.5 */ public GeneralTimestamp<HijriCalendar> at(PlainTime time) { return GeneralTimestamp.of(this, time); } /** * <p>Is equivalent to {@code at(PlainTime.of(hour, minute))}. </p> * * @param hour hour of day in range (0-24) * @param minute minute of hour in range (0-59) * @return general timestamp as composition of this date and given time * @throws IllegalArgumentException if any argument is out of range * @since 3.8/4.5 */ /*[deutsch] * <p>Entspricht {@code at(PlainTime.of(hour, minute))}. </p> * * @param hour hour of day in range (0-24) * @param minute minute of hour in range (0-59) * @return general timestamp as composition of this date and given time * @throws IllegalArgumentException if any argument is out of range * @since 3.8/4.5 */ public GeneralTimestamp<HijriCalendar> atTime( int hour, int minute ) { return this.at(PlainTime.of(hour, minute)); } /** * <p>Adds given calendrical units to this instance. </p> * * @param amount amount to be added (maybe negative) * @param unit calendrical unit * @return result of addition as changed copy, this instance remains unaffected * @throws ArithmeticException in case of numerical overflow * @since 3.14/4.10 */ /*[deutsch] * <p>Addiert die angegebenen Zeiteinheiten zu dieser Instanz. </p> * * @param amount amount to be added (maybe negative) * @param unit calendrical unit * @return result of addition as changed copy, this instance remains unaffected * @throws ArithmeticException in case of numerical overflow * @since 3.14/4.10 */ public HijriCalendar plus( int amount, Unit unit ) { try { return unit.addTo(this, amount); } catch (IllegalArgumentException iae) { ArithmeticException ex = new ArithmeticException(iae.getMessage()); ex.initCause(iae); throw ex; } } /** * <p>Subtracts given calendrical units from this instance. </p> * * @param amount amount to be subtracted (maybe negative) * @param unit calendrical unit * @return result of subtraction as changed copy, this instance remains unaffected * @throws ArithmeticException in case of numerical overflow * @since 3.14/4.10 */ /*[deutsch] * <p>Subtrahiert die angegebenen Zeiteinheiten von dieser Instanz. </p> * * @param amount amount to be subtracted (maybe negative) * @param unit calendrical unit * @return result of subtraction as changed copy, this instance remains unaffected * @throws ArithmeticException in case of numerical overflow * @since 3.14/4.10 */ public HijriCalendar minus( int amount, Unit unit ) { return this.plus(MathUtils.safeNegate(amount), unit); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } else if (obj instanceof HijriCalendar) { HijriCalendar that = (HijriCalendar) obj; return ( (this.hdom == that.hdom) && (this.hmonth == that.hmonth) && (this.hyear == that.hyear) && this.variant.equals(that.variant) ); } else { return false; } } @Override public int hashCode() { return (17 * this.hdom + 31 * this.hmonth + 37 * this.hyear) ^ this.variant.hashCode(); } @Override public String toString() { StringBuilder sb = new StringBuilder(32); sb.append("AH-"); String y = String.valueOf(this.hyear); for (int i = y.length(); i < 4; i++) { sb.append('0'); } sb.append(y); sb.append('-'); if (this.hmonth < 10) { sb.append('0'); } sb.append(this.hmonth); sb.append('-'); if (this.hdom < 10) { sb.append('0'); } sb.append(this.hdom); sb.append('['); sb.append(this.variant); sb.append(']'); return sb.toString(); } /** * <p>Obtains the standard week model of this calendar. </p> * * <p>The Hijri calendar usually starts on Sunday, but with the weekend on Friday and Saturday * (like in Saudi-Arabia). </p> * * @return Weekmodel * @since 3.24/4.20 */ /*[deutsch] * <p>Ermittelt das Standardwochenmodell dieses Kalenders. </p> * * <p>Der islamische Kalender startet per Vorgabe am Sonntag, mit dem Wochenende am Freitag * und Samstag (wie in Saudi-Arabien). </p> * * @return Weekmodel * @since 3.24/4.20 */ public static Weekmodel getDefaultWeekmodel() { return Weekmodel.of(Weekday.SUNDAY, 1, Weekday.FRIDAY, Weekday.SATURDAY); } /** * <p>Returns the associated calendar family. </p> * * @return chronology as calendar family * @since 3.5/4.3 */ /*[deutsch] * <p>Liefert die zugehörige Kalenderfamilie. </p> * * @return chronology as calendar family * @since 3.5/4.3 */ public static CalendarFamily<HijriCalendar> family() { return ENGINE; } /** * <p>Determines the data version of given variant. </p> * * <p>This method serves for data analysis only. </p> * * @param variant name of islamic calendar variant * @return version info (maybe empty if not relevant) * @throws ChronoException if the variant is not recognized * @since 3.9/4.6 */ /*[deutsch] * <p>Bestimmt die Datenversion der angegebenen Kalendervariante. </p> * * <p>Diese Methode dient nur Analysezwecken. </p> * * @param variant name of islamic calendar variant * @return version info (maybe empty if not relevant) * @throws ChronoException if the variant is not recognized * @since 3.9/4.6 */ public static String getVersion(String variant) { Object data = getCalendarSystem(variant); if (data instanceof AstronomicalHijriData) { return AstronomicalHijriData.class.cast(data).getVersion(); } return ""; } @Override protected CalendarFamily<HijriCalendar> getChronology() { return ENGINE; } @Override protected HijriCalendar getContext() { return this; } /** * <p>Returns the variant-dependent calendar system. </p> * * @return associated calendar system */ EraYearMonthDaySystem<HijriCalendar> getCalendarSystem() { return getCalendarSystem(this.variant); } private static EraYearMonthDaySystem<HijriCalendar> getCalendarSystem(String variant) { EraYearMonthDaySystem<HijriCalendar> calsys = CALSYS.get(variant); if (calsys == null) { throw new ChronoException("Unsupported calendar variant: " + variant); } return calsys; } /** * @serialData Uses <a href="../../../serialized-form.html#net.time4j.calendar.SPX"> * a dedicated serialization form</a> as proxy. The first byte contains * the type-ID {@code 1}. Then the UTF-coded variant and its data version * follow. Finally the year is written as int, month and day-of-month as bytes. * The successful serialization requires equal versions per variant on both sides. * * @return replacement object in serialization graph */ private Object writeReplace() { return new SPX(this, SPX.HIJRI); } /** * @serialData Blocks because a serialization proxy is required. * @param in object input stream * @throws InvalidObjectException (always) */ private void readObject(ObjectInputStream in) throws IOException { throw new InvalidObjectException("Serialization proxy required."); } //~ Innere Klassen ---------------------------------------------------- /** * <p>Defines come calendar units for the Hijri calendar. </p> * * @since 3.14/4.10 */ /*[deutsch] * <p>Definiert einige kalendarische Zeiteinheiten für den islamischen Kalender. </p> * * @since 3.14/4.10 */ public static enum Unit implements ChronoUnit { //~ Statische Felder/Initialisierungen ---------------------------- YEARS((354.0 + 11.0 / 30) * 86400.0), MONTHS(((354.0 + 11.0 / 30) / 12) * 86400.0), // ~ 29.53 days WEEKS(7 * 86400.0), DAYS(86400.0); //~ Instanzvariablen ---------------------------------------------- private transient final double length; //~ Konstruktoren ------------------------------------------------- private Unit(double length) { this.length = length; } //~ Methoden ------------------------------------------------------ @Override public double getLength() { return this.length; } @Override public boolean isCalendrical() { return true; } /** * <p>Calculates the difference between given islamic dates in this unit. </p> * * @param start start date (inclusive) * @param end end date (exclusive) * @param variant variant reference to which both dates will be converted first * @return difference counted in this unit * @since 3.14/4.11 */ /*[deutsch] * <p>Berechnet die Differenz zwischen den angegebenen islamischen Datumsparametern in dieser Zeiteinheit. </p> * * @param start start date (inclusive) * @param end end date (exclusive) * @param variant variant reference to which both dates will be converted first * @return difference counted in this unit * @since 3.14/4.11 */ public int between( HijriCalendar start, HijriCalendar end, String variant ) { switch (this) { case YEARS: return MONTHS.between(start, end, variant) / 12; case MONTHS: HijriCalendar s = start.withVariant(variant); HijriCalendar e = end.withVariant(variant); int delta = e.hyear * 12 + (e.hmonth - 1) - s.hyear * 12 - (s.hmonth - 1); if ((delta > 0) && (e.hdom < s.hdom)) { delta--; } else if ((delta < 0) && (e.hdom > s.hdom)) { delta++; } return delta; case WEEKS: return DAYS.between(start, end, variant) / 7; case DAYS: return (int) CalendarDays.between(start, end).getAmount(); default: throw new UnsupportedOperationException(this.name()); } } /** * <p>Equivalent to {@link #between(HijriCalendar, HijriCalendar, String) * between(start, end, variantSource.getVariant())}. </p> * * @param start start date (inclusive) * @param end end date (exclusive) * @param variantSource variant reference to which both dates will be converted first * @return difference counted in this unit * @since 3.14/4.11 */ /*[deutsch] * <p>Äquivalent zu {@link #between(HijriCalendar, HijriCalendar, String) * between(start, end, variantSource.getVariant())}. </p> * * @param start start date (inclusive) * @param end end date (exclusive) * @param variantSource variant reference to which both dates will be converted first * @return difference counted in this unit * @since 3.14/4.11 */ public int between( HijriCalendar start, HijriCalendar end, VariantSource variantSource ) { return this.between(start, end, variantSource.getVariant()); } // called by plus/minus-methods HijriCalendar addTo( HijriCalendar hijri, int amount ) { switch (this) { case YEARS: return hijri.with(HijriCalendar.YEAR_OF_ERA, MathUtils.safeAdd(hijri.getYear(), amount)); case MONTHS: int months = MathUtils.safeAdd(hijri.hyear * 12 + (hijri.hmonth - 1), amount); int y = MathUtils.floorDivide(months, 12); int m = MathUtils.floorModulo(months, 12) + 1; int dmax = hijri.getCalendarSystem().getLengthOfMonth(HijriEra.ANNO_HEGIRAE, y, m); int d = Math.min(hijri.hdom, dmax); return HijriCalendar.of(hijri.getVariant(), y, m, d); case WEEKS: return DAYS.addTo(hijri, MathUtils.safeMultiply(amount, 7)); case DAYS: return hijri.plus(CalendarDays.of(amount)); default: throw new UnsupportedOperationException(this.name()); } } } private static class VariantMap extends ConcurrentHashMap<String, EraYearMonthDaySystem<HijriCalendar>> { //~ Methoden ------------------------------------------------------ @Override public EraYearMonthDaySystem<HijriCalendar> get(Object key) { EraYearMonthDaySystem<HijriCalendar> calsys = super.get(key); if (calsys == null) { String variant = key.toString(); if (key.equals(VARIANT_UMALQURA)) { calsys = AstronomicalHijriData.UMALQURA; } else { try { calsys = new AstronomicalHijriData(variant); } catch (IOException ioe) { return null; } } EraYearMonthDaySystem<HijriCalendar> old = this.putIfAbsent(variant, calsys); if (old != null) { calsys = old; } } return calsys; } } private static class IntegerRule implements ElementRule<HijriCalendar, Integer> { //~ Instanzvariablen ---------------------------------------------- private final int index; //~ Konstruktoren ------------------------------------------------- IntegerRule(int index) { super(); this.index = index; } //~ Methoden ------------------------------------------------------ @Override public Integer getValue(HijriCalendar context) { switch (this.index) { case YEAR_INDEX: return context.hyear; case DAY_OF_MONTH_INDEX: return context.hdom; case DAY_OF_YEAR_INDEX: int doy = 0; EraYearMonthDaySystem<HijriCalendar> calsys = context.getCalendarSystem(); for (int m = 1; m < context.hmonth; m++) { doy += calsys.getLengthOfMonth(HijriEra.ANNO_HEGIRAE, context.hyear, m); } return doy + context.hdom; default: throw new UnsupportedOperationException("Unknown element index: " + this.index); } } @Override public Integer getMinimum(HijriCalendar context) { switch (this.index) { case YEAR_INDEX: EraYearMonthDaySystem<HijriCalendar> calsys = context.getCalendarSystem(); return calsys.transform(calsys.getMinimumSinceUTC()).hyear; case DAY_OF_MONTH_INDEX: case DAY_OF_YEAR_INDEX: return Integer.valueOf(1); default: throw new UnsupportedOperationException("Unknown element index: " + this.index); } } @Override public Integer getMaximum(HijriCalendar context) { EraYearMonthDaySystem<HijriCalendar> calsys = context.getCalendarSystem(); switch (this.index) { case YEAR_INDEX: return calsys.transform(calsys.getMaximumSinceUTC()).hyear; case DAY_OF_MONTH_INDEX: return calsys.getLengthOfMonth(HijriEra.ANNO_HEGIRAE, context.hyear, context.hmonth); case DAY_OF_YEAR_INDEX: return calsys.getLengthOfYear(HijriEra.ANNO_HEGIRAE, context.hyear); default: throw new UnsupportedOperationException("Unknown element index: " + this.index); } } @Override public boolean isValid( HijriCalendar context, Integer value ) { if (value == null) { return false; } Integer min = this.getMinimum(context); Integer max = this.getMaximum(context); return ((min.compareTo(value) <= 0) && (max.compareTo(value) >= 0)); } @Override public HijriCalendar withValue( HijriCalendar context, Integer value, boolean lenient ) { if (!this.isValid(context, value)) { throw new IllegalArgumentException("Out of range: " + value); } switch (this.index) { case YEAR_INDEX: int y = value.intValue(); int dmax = context.getCalendarSystem().getLengthOfMonth(HijriEra.ANNO_HEGIRAE, y, context.hmonth); int d = Math.min(context.hdom, dmax); return HijriCalendar.of(context.getVariant(), y, context.hmonth, d); case DAY_OF_MONTH_INDEX: return new HijriCalendar(context.hyear, context.hmonth, value.intValue(), context.getVariant()); case DAY_OF_YEAR_INDEX: int delta = value.intValue() - this.getValue(context).intValue(); return context.plus(CalendarDays.of(delta)); default: throw new UnsupportedOperationException("Unknown element index: " + this.index); } } @Override public ChronoElement<?> getChildAtFloor(HijriCalendar context) { if (this.index == YEAR_INDEX) { return MONTH_OF_YEAR; } return null; } @Override public ChronoElement<?> getChildAtCeiling(HijriCalendar context) { if (this.index == YEAR_INDEX) { return MONTH_OF_YEAR; } return null; } } private static class MonthRule implements ElementRule<HijriCalendar, HijriMonth> { //~ Methoden ------------------------------------------------------ @Override public HijriMonth getValue(HijriCalendar context) { return context.getMonth(); } @Override public HijriMonth getMinimum(HijriCalendar context) { return HijriMonth.MUHARRAM; } @Override public HijriMonth getMaximum(HijriCalendar context) { return HijriMonth.DHU_AL_HIJJAH; } @Override public boolean isValid( HijriCalendar context, HijriMonth value ) { return (value != null); } @Override public HijriCalendar withValue( HijriCalendar context, HijriMonth value, boolean lenient ) { if (value == null) { throw new IllegalArgumentException("Missing month."); } int m = value.getValue(); int dmax = context.getCalendarSystem().getLengthOfMonth(HijriEra.ANNO_HEGIRAE, context.hyear, m); int d = Math.min(context.hdom, dmax); return new HijriCalendar(context.hyear, m, d, context.getVariant()); } @Override public ChronoElement<?> getChildAtFloor(HijriCalendar context) { return DAY_OF_MONTH; } @Override public ChronoElement<?> getChildAtCeiling(HijriCalendar context) { return DAY_OF_MONTH; } } private static class EraRule implements ElementRule<HijriCalendar, HijriEra> { //~ Methoden ------------------------------------------------------ @Override public HijriEra getValue(HijriCalendar context) { return HijriEra.ANNO_HEGIRAE; } @Override public HijriEra getMinimum(HijriCalendar context) { return HijriEra.ANNO_HEGIRAE; } @Override public HijriEra getMaximum(HijriCalendar context) { return HijriEra.ANNO_HEGIRAE; } @Override public boolean isValid( HijriCalendar context, HijriEra value ) { return (value != null); } @Override public HijriCalendar withValue( HijriCalendar context, HijriEra value, boolean lenient ) { if (value == null) { throw new IllegalArgumentException("Missing era value."); } return context; } @Override public ChronoElement<?> getChildAtFloor(HijriCalendar context) { return YEAR_OF_ERA; } @Override public ChronoElement<?> getChildAtCeiling(HijriCalendar context) { return YEAR_OF_ERA; } } private static class WeekdayRule implements ElementRule<HijriCalendar, Weekday> { //~ Methoden ------------------------------------------------------ @Override public Weekday getValue(HijriCalendar context) { return context.getDayOfWeek(); } @Override public Weekday getMinimum(HijriCalendar context) { return Weekday.SUNDAY; } @Override public Weekday getMaximum(HijriCalendar context) { return Weekday.SATURDAY; } @Override public boolean isValid( HijriCalendar context, Weekday value ) { return (value != null); } @Override public HijriCalendar withValue( HijriCalendar context, Weekday value, boolean lenient ) { if (value == null) { throw new IllegalArgumentException("Missing weekday."); } Weekmodel model = getDefaultWeekmodel(); int oldValue = context.getDayOfWeek().getValue(model); int newValue = value.getValue(model); return context.plus(CalendarDays.of(newValue - oldValue)); } @Override public ChronoElement<?> getChildAtFloor(HijriCalendar context) { return null; } @Override public ChronoElement<?> getChildAtCeiling(HijriCalendar context) { return null; } } private static class Merger implements ChronoMerger<HijriCalendar> { //~ Methoden ------------------------------------------------------ @Override public String getFormatPattern( DisplayStyle style, Locale locale ) { return GenericDatePatterns.get("islamic", style, locale); } @Override public HijriCalendar createFrom( TimeSource<?> clock, AttributeQuery attributes ) { String variant = attributes.get(Attributes.CALENDAR_VARIANT, ""); if (variant.isEmpty()) { return null; } TZID tzid; if (attributes.contains(Attributes.TIMEZONE_ID)) { tzid = attributes.get(Attributes.TIMEZONE_ID); } else if (attributes.get(Attributes.LENIENCY, Leniency.SMART).isLax()) { tzid = Timezone.ofSystem().getID(); } else { return null; } StartOfDay startOfDay = attributes.get(Attributes.START_OF_DAY, this.getDefaultStartOfDay()); return Moment.from(clock.currentTime()).toGeneralTimestamp(ENGINE, variant, tzid, startOfDay).toDate(); } @Override @Deprecated public HijriCalendar createFrom( ChronoEntity<?> entity, AttributeQuery attributes, boolean preparsing ) { boolean lenient = attributes.get(Attributes.LENIENCY, Leniency.SMART).isLax(); return this.createFrom(entity, attributes, lenient, preparsing); } @Override public HijriCalendar createFrom( ChronoEntity<?> entity, AttributeQuery attributes, boolean lenient, boolean preparsing ) { String variant = attributes.get(Attributes.CALENDAR_VARIANT, ""); if (variant.isEmpty()) { entity.with(ValidationElement.ERROR_MESSAGE, "Missing Hijri calendar variant."); return null; } EraYearMonthDaySystem<HijriCalendar> calsys = CALSYS.get(variant); if (calsys == null) { entity.with(ValidationElement.ERROR_MESSAGE, "Unknown Hijri calendar variant: " + variant); return null; } int hyear = entity.getInt(YEAR_OF_ERA); if (hyear == Integer.MIN_VALUE) { entity.with(ValidationElement.ERROR_MESSAGE, "Missing islamic year."); return null; } if (entity.contains(MONTH_OF_YEAR)) { int hmonth = entity.get(MONTH_OF_YEAR).getValue(); int hdom = entity.getInt(DAY_OF_MONTH); if (hdom != Integer.MIN_VALUE) { if (calsys.isValid(HijriEra.ANNO_HEGIRAE, hyear, hmonth, hdom)) { return HijriCalendar.of(variant, hyear, hmonth, hdom); } else { entity.with(ValidationElement.ERROR_MESSAGE, "Invalid Hijri date."); } } } else { int hdoy = entity.getInt(DAY_OF_YEAR); if (hdoy != Integer.MIN_VALUE) { if (hdoy > 0) { int hmonth = 1; int daycount = 0; while (hmonth <= 12) { int len = calsys.getLengthOfMonth(HijriEra.ANNO_HEGIRAE, hyear, hmonth); if (hdoy > daycount + len) { hmonth++; daycount += len; } else { int hdom = hdoy - daycount; return HijriCalendar.of(variant, hyear, hmonth, hdom); } } } entity.with(ValidationElement.ERROR_MESSAGE, "Invalid Hijri date."); } } return null; } @Override public HijriCalendar createFrom( TemporalAccessor threeten, AttributeQuery attributes ) { Chronology c = threeten.query(TemporalQueries.chronology()); if (c != null && c.getId().equals("Hijrah-umalqura")) { HijrahDate hd = HijrahDate.from(threeten); return HijriCalendar.ofUmalqura( hd.get(ChronoField.YEAR_OF_ERA), hd.get(ChronoField.MONTH_OF_YEAR), hd.get(ChronoField.DAY_OF_MONTH)); } return null; } @Override public StartOfDay getDefaultStartOfDay() { return StartOfDay.EVENING; } } }