/* * ----------------------------------------------------------------------- * Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/> * ----------------------------------------------------------------------- * This file (CopticCalendar.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.PlainDate; 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.CalendarEra; import net.time4j.engine.Calendrical; import net.time4j.engine.ChronoDisplay; import net.time4j.engine.ChronoElement; import net.time4j.engine.ChronoEntity; import net.time4j.engine.ChronoMerger; import net.time4j.engine.ChronoUnit; import net.time4j.engine.Chronology; import net.time4j.engine.DisplayStyle; import net.time4j.engine.ElementRule; import net.time4j.engine.EpochDays; import net.time4j.engine.FormattableElement; import net.time4j.engine.StartOfDay; import net.time4j.engine.TimeAxis; import net.time4j.engine.UnitRule; import net.time4j.engine.ValidationElement; import net.time4j.format.Attributes; import net.time4j.format.CalendarType; import net.time4j.format.Leniency; import net.time4j.format.LocalizedPatternSupport; import net.time4j.history.ChronoHistory; import net.time4j.history.HistoricDate; import net.time4j.history.HistoricEra; import net.time4j.tz.TZID; import net.time4j.tz.Timezone; import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.util.Collections; import java.util.List; import java.util.Locale; /** * <p>Represents the calendar used by the Coptic church in Egypt. </p> * * <p>It is a solar calendar which defines years consisting of 13 months. The first 12 months are always 30 days long. * The last month has 5 or 6 days depending if a Coptic year is a leap year or not. The leap year rule is the same * as defined in Julian Calendar, namely every fourth year. Years are counted since the era of martyrs where * the Julian year AD 284 is counted as Coptic year 1. See also * <a href="https://en.wikipedia.org/wiki/Coptic_calendar">Wikipedia</a>. According to the book * "Calendrical calculations" of Dershowitz/Reingold, the Coptic day starts at sunset * on the previous day. Time4J will also assume that despite of the fact that the ancient Egypt * calendar (the historic ancestor of the Coptic calendar) started the day at sunrise. We assume * here an adaptation of the Coptic calendar to the habits of Islamic calendar in Egypt. </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>Example of usage: </p> * * <pre> * ChronoFormatter<CopticCalendar> formatter = * ChronoFormatter.setUp(CopticCalendar.axis(), Locale.ENGLISH) * .addPattern("EEE, d. MMMM yyyy", PatternType.NON_ISO_DATE).build(); * PlainDate today = SystemClock.inLocalView().today(); * CopticCalendar copticDate = today.transform(CopticCalendar.class); // conversion at noon * System.out.println(formatter.format(copticDate)); * </pre> * * @author Meno Hochschild * @since 3.11/4.8 * @doctags.concurrency {immutable} */ /*[deutsch] * <p>Repräsentiert den Kalender, der von der koptischen Kirche in Ägypten verwendet wird. </p> * * <p>Es handelt sich um einen Sonnenkalender, dessen Jahre aus 13 Monaten bestehen. Die ersten 12 Monate * sind immer 30 Tage lang, während der letzte Monat 5 oder 6 Tage lang ist, je nachdem ob ein Schaltjahr * vorliegt oder nicht. Die Schaltjahrregel ist die gleiche wie im julianischen Kalender, nämlich * alle 4 Jahre. Jahre werden seit der Ära der Märtyrer gezählt, also seit dem julianischen * Jahr AD 284. Siehe auch <a href="https://en.wikipedia.org/wiki/Coptic_calendar">Wikipedia</a>. Nach dem * Buch "Calendrical calculations" von Dershowitz/Reingold fängt der koptische Tag zum * Sonnenuntergang des Vortags an. Time4J wird das auch annehmen, obwohl der alte ägyptische Kalender, * von dem der koptische Kalender abgeleitet ist, den Tag zum Sonnenaufgang begann. Es wird hier implizit * eine Anpassung des koptischen Kalenders an die Gewohnheiten des islamischen Kalenders in Ägypten * angenommen. </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>Anwendungsbeispiel: </p> * * <pre> * ChronoFormatter<CopticCalendar> formatter = * ChronoFormatter.setUp(CopticCalendar.axis(), Locale.ENGLISH) * .addPattern("EEE, d. MMMM yyyy", PatternType.NON_ISO_DATE).build(); * PlainDate today = SystemClock.inLocalView().today(); * CopticCalendar copticDate = today.transform(CopticCalendar.class); // Konversion zu 12 Uhr mittags * System.out.println(formatter.format(copticDate)); * </pre> * * @author Meno Hochschild * @since 3.11/4.8 * @doctags.concurrency {immutable} */ @CalendarType("coptic") public final class CopticCalendar extends Calendrical<CopticCalendar.Unit, CopticCalendar> implements LocalizedPatternSupport { //~ Statische Felder/Initialisierungen -------------------------------- private static final long DIOCLETIAN; static { PlainDate diocletian = ChronoHistory.PROLEPTIC_JULIAN.convert(HistoricDate.of(HistoricEra.AD, 284, 8, 29)); DIOCLETIAN = diocletian.get(EpochDays.UTC); } 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 Coptic era. </p> */ /*[deutsch] * <p>Repräsentiert die koptische Ära. </p> */ @FormattableElement(format = "G") public static final ChronoElement<CopticEra> ERA = new StdEnumDateElement<>("ERA", CopticCalendar.class, CopticEra.class, 'G'); /** * <p>Represents the Coptic year. </p> */ /*[deutsch] * <p>Repräsentiert das koptische Jahr. </p> */ @FormattableElement(format = "y") public static final StdCalendarElement<Integer, CopticCalendar> YEAR_OF_ERA = new StdIntegerDateElement<>( "YEAR_OF_ERA", CopticCalendar.class, 1, 9999, 'y', null, null); /** * <p>Represents the Coptic month. </p> */ /*[deutsch] * <p>Repräsentiert den koptischen Monat. </p> */ @FormattableElement(format = "M", standalone = "L") public static final StdCalendarElement<CopticMonth, CopticCalendar> MONTH_OF_YEAR = new StdEnumDateElement<>( "MONTH_OF_YEAR", CopticCalendar.class, CopticMonth.class, 'M'); /** * <p>Represents the Coptic day of month. </p> */ /*[deutsch] * <p>Repräsentiert den koptischen Tag des Monats. </p> */ @FormattableElement(format = "d") public static final StdCalendarElement<Integer, CopticCalendar> DAY_OF_MONTH = new StdIntegerDateElement<>("DAY_OF_MONTH", CopticCalendar.class, 1, 30, 'd'); /** * <p>Represents the Coptic day of year. </p> */ /*[deutsch] * <p>Repräsentiert den koptischen Tag des Jahres. </p> */ @FormattableElement(format = "D") public static final StdCalendarElement<Integer, CopticCalendar> DAY_OF_YEAR = new StdIntegerDateElement<>("DAY_OF_YEAR", CopticCalendar.class, 1, 365, 'D'); /** * <p>Represents the Coptic day of week. </p> * * <p>If the day-of-week is set to a new value then Time4J handles the Coptic calendar week * as starting on Saturday. </p> */ /*[deutsch] * <p>Repräsentiert den koptischen Tag der Woche. </p> * * <p>Wenn der Tag der Woche auf einen neuen Wert gesetzt wird, behandelt Time4J die koptische * Kalenderwoche so, daß sie am Samstag beginnt. </p> */ @FormattableElement(format = "E") public static final StdCalendarElement<Weekday, CopticCalendar> DAY_OF_WEEK = new StdWeekdayElement<>(CopticCalendar.class); private static final EraYearMonthDaySystem<CopticCalendar> CALSYS; private static final TimeAxis<CopticCalendar.Unit, CopticCalendar> ENGINE; static { CALSYS = new Transformer(); TimeAxis.Builder<CopticCalendar.Unit, CopticCalendar> builder = TimeAxis.Builder.setUp( CopticCalendar.Unit.class, CopticCalendar.class, new Merger(), CALSYS) .appendElement( ERA, new EraRule()) .appendElement( YEAR_OF_ERA, new IntegerRule(YEAR_INDEX), Unit.YEARS) .appendElement( MONTH_OF_YEAR, new MonthRule(), Unit.MONTHS) .appendElement( DAY_OF_MONTH, new IntegerRule(DAY_OF_MONTH_INDEX), Unit.DAYS) .appendElement( DAY_OF_YEAR, new IntegerRule(DAY_OF_YEAR_INDEX), Unit.DAYS) .appendElement( DAY_OF_WEEK, new WeekdayRule(), Unit.DAYS) .appendElement( CommonElements.RELATED_GREGORIAN_YEAR, new RelatedGregorianYearRule<>(CALSYS, DAY_OF_YEAR)) .appendUnit( Unit.YEARS, new CopticUnitRule(Unit.YEARS), Unit.YEARS.getLength(), Collections.singleton(Unit.MONTHS)) .appendUnit( Unit.MONTHS, new CopticUnitRule(Unit.MONTHS), Unit.MONTHS.getLength(), Collections.singleton(Unit.YEARS)) .appendUnit( Unit.WEEKS, new CopticUnitRule(Unit.WEEKS), Unit.WEEKS.getLength(), Collections.singleton(Unit.DAYS)) .appendUnit( Unit.DAYS, new CopticUnitRule(Unit.DAYS), Unit.DAYS.getLength(), Collections.singleton(Unit.WEEKS)) .appendExtension( new CommonElements.Weekengine( CopticCalendar.class, DAY_OF_MONTH, DAY_OF_YEAR, getDefaultWeekmodel())); ENGINE = builder.build(); } private static final long serialVersionUID = -8248846000788617742L; //~ Instanzvariablen -------------------------------------------------- private transient final int cyear; private transient final int cmonth; private transient final int cdom; //~ Konstruktoren ----------------------------------------------------- private CopticCalendar( int cyear, int cmonth, int cdom ) { super(); this.cyear = cyear; this.cmonth = cmonth; this.cdom = cdom; } //~ Methoden ---------------------------------------------------------- /** * <p>Creates a new instance of a Coptic calendar date. </p> * * @param cyear Coptic year in the range 1-9999 * @param cmonth Coptic month * @param cdom Coptic day of month in range 1-30 * @return new instance of {@code CopticCalendar} * @throws IllegalArgumentException in case of any inconsistencies * @since 3.11/4.8 */ /*[deutsch] * <p>Erzeugt ein neues koptisches Kalenderdatum. </p> * * @param cyear Coptic year in the range 1-9999 * @param cmonth Coptic month * @param cdom Coptic day of month in range 1-30 * @return new instance of {@code CopticCalendar} * @throws IllegalArgumentException in case of any inconsistencies * @since 3.11/4.8 */ public static CopticCalendar of( int cyear, CopticMonth cmonth, int cdom ) { return CopticCalendar.of(cyear, cmonth.getValue(), cdom); } /** * <p>Creates a new instance of a Coptic calendar date. </p> * * @param cyear Coptic year in the range 1-9999 * @param cmonth Coptic monthin range 1-13 * @param cdom Coptic day of month in range 1-30 * @return new instance of {@code CopticCalendar} * @throws IllegalArgumentException in case of any inconsistencies * @since 3.11/4.8 */ /*[deutsch] * <p>Erzeugt ein neues koptisches Kalenderdatum. </p> * * @param cyear Coptic year in the range 1-9999 * @param cmonth Coptic month in range 1-13 * @param cdom Coptic day of month in range 1-30 * @return new instance of {@code CopticCalendar} * @throws IllegalArgumentException in case of any inconsistencies * @since 3.11/4.8 */ public static CopticCalendar of( int cyear, int cmonth, int cdom ) { if (!CALSYS.isValid(CopticEra.ANNO_MARTYRUM, cyear, cmonth, cdom)) { throw new IllegalArgumentException( "Invalid Coptic date: year=" + cyear + ", month=" + cmonth + ", day=" + cdom); } return new CopticCalendar(cyear, cmonth, cdom); } /** * <p>Obtains the current calendar date in system time. </p> * * <p>Convenient short-cut for: {@code SystemClock.inLocalView().now(CopticCalendar.axis())}. * Attention: The Coptic calendar changes the date in the evening at 6 PM. </p> * * @return current calendar date in system time zone using the system clock * @see SystemClock#inLocalView() * @see net.time4j.ZonalClock#now(net.time4j.engine.Chronology) * @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(CopticCalendar.axis())}. * Achtung: Der koptische Kalender wechselt das Datum am Abend um 18 Uhr. </p> * * @return current calendar date in system time zone using the system clock * @see SystemClock#inLocalView() * @see net.time4j.ZonalClock#now(net.time4j.engine.Chronology) * @since 3.23/4.19 */ public static CopticCalendar nowInSystemTime() { return SystemClock.inLocalView().now(CopticCalendar.axis()); } /** * <p>Yields the Coptic era. </p> * * @return {@link CopticEra#ANNO_MARTYRUM} * @since 3.11/4.8 */ /*[deutsch] * <p>Liefert die koptische Ära. </p> * * @return {@link CopticEra#ANNO_MARTYRUM} * @since 3.11/4.8 */ public CopticEra getEra() { return CopticEra.ANNO_MARTYRUM; } /** * <p>Yields the Coptic year. </p> * * @return int * @since 3.11/4.8 */ /*[deutsch] * <p>Liefert das koptische Jahr. </p> * * @return int * @since 3.11/4.8 */ public int getYear() { return this.cyear; } /** * <p>Yields the Coptic month. </p> * * @return enum * @since 3.11/4.8 */ /*[deutsch] * <p>Liefert den koptischen Monat. </p> * * @return enum * @since 3.11/4.8 */ public CopticMonth getMonth() { return CopticMonth.valueOf(this.cmonth); } /** * <p>Yields the Coptic day of month. </p> * * @return int * @since 3.11/4.8 */ /*[deutsch] * <p>Liefert den koptischen Tag des Monats. </p> * * @return int * @since 3.11/4.8 */ public int getDayOfMonth() { return this.cdom; } /** * <p>Determines the day of week. </p> * * <p>The Coptic calendar also uses a 7-day-week. </p> * * @return Weekday * @since 3.11/4.8 */ /*[deutsch] * <p>Ermittelt den Wochentag. </p> * * <p>Der koptische Kalendar verwendet ebenfalls eine 7-Tage-Woche. </p> * * @return Weekday * @since 3.11/4.8 */ public Weekday getDayOfWeek() { long utcDays = CALSYS.transform(this); return Weekday.valueOf(MathUtils.floorModulo(utcDays + 5, 7) + 1); } /** * <p>Yields the Coptic day of year. </p> * * @return int * @since 3.11/4.8 */ /*[deutsch] * <p>Liefert den koptischen Tag des Jahres. </p> * * @return int * @since 3.11/4.8 */ public int getDayOfYear() { return this.get(DAY_OF_YEAR).intValue(); } /** * <p>Yields the length of current Coptic month in days. </p> * * @return int * @since 3.11/4.8 */ /*[deutsch] * <p>Liefert die Länge des aktuellen koptischen Monats in Tagen. </p> * * @return int * @since 3.11/4.8 */ public int lengthOfMonth() { return CALSYS.getLengthOfMonth(CopticEra.ANNO_MARTYRUM, this.cyear, this.cmonth); } /** * <p>Yields the length of current Coptic year in days. </p> * * @return int * @since 3.11/4.8 */ /*[deutsch] * <p>Liefert die Länge des aktuellen koptischen Jahres in Tagen. </p> * * @return int * @since 3.11/4.8 */ public int lengthOfYear() { return this.isLeapYear() ? 366 : 365; } /** * <p>Is the year of this date a leap year? </p> * * @return boolean * @since 3.11/4.8 */ /*[deutsch] * <p>Liegt dieses Datum in einem Schaltjahr? </p> * * @return boolean * @since 3.11/4.8 */ public boolean isLeapYear() { return ((this.cyear % 4) == 3); } /** * <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.11/4.8 */ /*[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.11/4.8 */ public GeneralTimestamp<CopticCalendar> 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.11/4.8 */ /*[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.11/4.8 */ public GeneralTimestamp<CopticCalendar> atTime( int hour, int minute ) { return this.at(PlainTime.of(hour, minute)); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } else if (obj instanceof CopticCalendar) { CopticCalendar that = (CopticCalendar) obj; return ( (this.cdom == that.cdom) && (this.cmonth == that.cmonth) && (this.cyear == that.cyear) ); } else { return false; } } @Override public int hashCode() { return (17 * this.cdom + 31 * this.cmonth + 37 * this.cyear); } @Override public String toString() { StringBuilder sb = new StringBuilder(32); sb.append("A.M.-"); String y = String.valueOf(this.cyear); for (int i = y.length(); i < 4; i++) { sb.append('0'); } sb.append(y); sb.append('-'); if (this.cmonth < 10) { sb.append('0'); } sb.append(this.cmonth); sb.append('-'); if (this.cdom < 10) { sb.append('0'); } sb.append(this.cdom); return sb.toString(); } /** * <p>Obtains the standard week model of this calendar. </p> * * <p>The Coptic calendar usually starts on Saturday. </p> * * @return Weekmodel * @since 3.24/4.20 */ /*[deutsch] * <p>Ermittelt das Standardwochenmodell dieses Kalenders. </p> * * <p>Der koptische Kalender startet normalerweise am Samstag. </p> * * @return Weekmodel * @since 3.24/4.20 */ public static Weekmodel getDefaultWeekmodel() { return Weekmodel.of(Weekday.SATURDAY, 1, Weekday.FRIDAY, Weekday.SATURDAY); // Egypt } /** * <p>Returns the associated time axis. </p> * * @return chronology * @since 3.11/4.8 */ /*[deutsch] * <p>Liefert die zugehörige Zeitachse. </p> * * @return chronology * @since 3.11/4.8 */ public static TimeAxis<Unit, CopticCalendar> axis() { return ENGINE; } @Override protected TimeAxis<Unit, CopticCalendar> getChronology() { return ENGINE; } @Override protected CopticCalendar getContext() { return this; } /** * @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 3}. Then the year is written as int, finally * month and day-of-month as bytes. * * @return replacement object in serialization graph */ private Object writeReplace() { return new SPX(this, SPX.COPTIC); } /** * @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 Coptic calendar. </p> * * @since 3.11/4.8 */ /*[deutsch] * <p>Definiert einige kalendarische Zeiteinheiten für den koptischen Kalender. </p> * * @since 3.11/4.8 */ public static enum Unit implements ChronoUnit { //~ Statische Felder/Initialisierungen ---------------------------- YEARS(365.25 * 86400.0), MONTHS(30 * 86400.0), 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 Coptic dates in this unit. </p> * * @param start start date (inclusive) * @param end end date (exclusive) * @return difference counted in this unit * @since 3.11/4.8 */ /*[deutsch] * <p>Berechnet die Differenz zwischen den angegebenen Datumsparametern in dieser Zeiteinheit. </p> * * @param start start date (inclusive) * @param end end date (exclusive) * @return difference counted in this unit * @since 3.11/4.8 */ public int between( CopticCalendar start, CopticCalendar end ) { return (int) start.until(end, this); // safe } } private static class Transformer implements EraYearMonthDaySystem<CopticCalendar> { //~ Methoden ------------------------------------------------------ @Override public boolean isValid( CalendarEra era, int yearOfEra, int monthOfYear, int dayOfMonth ) { return ( (era == CopticEra.ANNO_MARTYRUM) && (yearOfEra >= 1) && (yearOfEra <= 9999) && (monthOfYear >= 1) && (monthOfYear <= 13) && (dayOfMonth >= 1) && (dayOfMonth <= getLengthOfMonth(era, yearOfEra, monthOfYear)) ); } @Override public int getLengthOfMonth( CalendarEra era, int yearOfEra, int monthOfYear ) { if (era != CopticEra.ANNO_MARTYRUM) { throw new IllegalArgumentException("Invalid era: " + era); } if ( (yearOfEra >= 1) && (yearOfEra <= 9999) && (monthOfYear >= 1) && (monthOfYear <= 13) ) { if (monthOfYear <= 12) { return 30; } else { return ((yearOfEra % 4) == 3) ? 6 : 5; } } throw new IllegalArgumentException("Out of bounds: year=" + yearOfEra + ", month=" + monthOfYear); } @Override public int getLengthOfYear( CalendarEra era, int yearOfEra ) { if (era != CopticEra.ANNO_MARTYRUM) { throw new IllegalArgumentException("Invalid era: " + era); } if ( (yearOfEra >= 1) && (yearOfEra <= 9999) ) { return ((yearOfEra % 4) == 3) ? 366 : 365; } throw new IllegalArgumentException("Out of bounds: year=" + yearOfEra); } @Override public CopticCalendar transform(long utcDays) { int cyear = MathUtils.safeCast( MathUtils.floorDivide( MathUtils.safeAdd( MathUtils.safeMultiply( 4, MathUtils.safeSubtract(utcDays, DIOCLETIAN)), 1463), 1461)); int startOfYear = MathUtils.safeCast(this.transform(new CopticCalendar(cyear, 1, 1))); int cmonth = 1 + MathUtils.safeCast(MathUtils.floorDivide(utcDays - startOfYear, 30)); int startOfMonth = MathUtils.safeCast(this.transform(new CopticCalendar(cyear, cmonth, 1))); int cdom = 1 + MathUtils.safeCast(MathUtils.safeSubtract(utcDays, startOfMonth)); return CopticCalendar.of(cyear, cmonth, cdom); } @Override public long transform(CopticCalendar date) { return ( DIOCLETIAN - 1 + 365 * (date.cyear - 1) + MathUtils.floorDivide(date.cyear, 4) + 30 * (date.cmonth - 1) + date.cdom); } @Override public long getMinimumSinceUTC() { CopticCalendar min = new CopticCalendar(1, 1, 1); return this.transform(min); } @Override public long getMaximumSinceUTC() { CopticCalendar max = new CopticCalendar(9999, 13, 6); return this.transform(max); } @Override public List<CalendarEra> getEras() { CalendarEra era = CopticEra.ANNO_MARTYRUM; return Collections.singletonList(era); } } private static class IntegerRule implements ElementRule<CopticCalendar, Integer> { //~ Instanzvariablen ---------------------------------------------- private final int index; //~ Konstruktoren ------------------------------------------------- IntegerRule(int index) { super(); this.index = index; } //~ Methoden ------------------------------------------------------ @Override public Integer getValue(CopticCalendar context) { switch (this.index) { case YEAR_INDEX: return context.cyear; case DAY_OF_MONTH_INDEX: return context.cdom; case DAY_OF_YEAR_INDEX: int doy = 0; for (int m = 1; m < context.cmonth; m++) { doy += CALSYS.getLengthOfMonth(CopticEra.ANNO_MARTYRUM, context.cyear, m); } return doy + context.cdom; default: throw new UnsupportedOperationException("Unknown element index: " + this.index); } } @Override public Integer getMinimum(CopticCalendar context) { switch (this.index) { case YEAR_INDEX: 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(CopticCalendar context) { switch (this.index) { case YEAR_INDEX: return 9999; case DAY_OF_MONTH_INDEX: return CALSYS.getLengthOfMonth(CopticEra.ANNO_MARTYRUM, context.cyear, context.cmonth); case DAY_OF_YEAR_INDEX: return CALSYS.getLengthOfYear(CopticEra.ANNO_MARTYRUM, context.cyear); default: throw new UnsupportedOperationException("Unknown element index: " + this.index); } } @Override public boolean isValid( CopticCalendar 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 CopticCalendar withValue( CopticCalendar 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 = CALSYS.getLengthOfMonth(CopticEra.ANNO_MARTYRUM, y, context.cmonth); int d = Math.min(context.cdom, dmax); return CopticCalendar.of(y, context.cmonth, d); case DAY_OF_MONTH_INDEX: return new CopticCalendar(context.cyear, context.cmonth, value.intValue()); 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(CopticCalendar context) { if (this.index == YEAR_INDEX) { return MONTH_OF_YEAR; } return null; } @Override public ChronoElement<?> getChildAtCeiling(CopticCalendar context) { if (this.index == YEAR_INDEX) { return MONTH_OF_YEAR; } return null; } } private static class MonthRule implements ElementRule<CopticCalendar, CopticMonth> { //~ Methoden ------------------------------------------------------ @Override public CopticMonth getValue(CopticCalendar context) { return context.getMonth(); } @Override public CopticMonth getMinimum(CopticCalendar context) { return CopticMonth.TOUT; } @Override public CopticMonth getMaximum(CopticCalendar context) { return CopticMonth.NASIE; } @Override public boolean isValid( CopticCalendar context, CopticMonth value ) { return (value != null); } @Override public CopticCalendar withValue( CopticCalendar context, CopticMonth value, boolean lenient ) { if (value == null) { throw new IllegalArgumentException("Missing month."); } int m = value.getValue(); int dmax = CALSYS.getLengthOfMonth(CopticEra.ANNO_MARTYRUM, context.cyear, m); int d = Math.min(context.cdom, dmax); return new CopticCalendar(context.cyear, m, d); } @Override public ChronoElement<?> getChildAtFloor(CopticCalendar context) { return DAY_OF_MONTH; } @Override public ChronoElement<?> getChildAtCeiling(CopticCalendar context) { return DAY_OF_MONTH; } } private static class EraRule implements ElementRule<CopticCalendar, CopticEra> { //~ Methoden ------------------------------------------------------ @Override public CopticEra getValue(CopticCalendar context) { return CopticEra.ANNO_MARTYRUM; } @Override public CopticEra getMinimum(CopticCalendar context) { return CopticEra.ANNO_MARTYRUM; } @Override public CopticEra getMaximum(CopticCalendar context) { return CopticEra.ANNO_MARTYRUM; } @Override public boolean isValid( CopticCalendar context, CopticEra value ) { return (value != null); } @Override public CopticCalendar withValue( CopticCalendar context, CopticEra value, boolean lenient ) { if (value == null) { throw new IllegalArgumentException("Missing era value."); } return context; } @Override public ChronoElement<?> getChildAtFloor(CopticCalendar context) { return YEAR_OF_ERA; } @Override public ChronoElement<?> getChildAtCeiling(CopticCalendar context) { return YEAR_OF_ERA; } } private static class WeekdayRule implements ElementRule<CopticCalendar, Weekday> { //~ Methoden ------------------------------------------------------ @Override public Weekday getValue(CopticCalendar context) { return context.getDayOfWeek(); } @Override public Weekday getMinimum(CopticCalendar context) { return Weekday.SATURDAY; } @Override public Weekday getMaximum(CopticCalendar context) { return Weekday.FRIDAY; } @Override public boolean isValid( CopticCalendar context, Weekday value ) { return (value != null); } @Override public CopticCalendar withValue( CopticCalendar 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(CopticCalendar context) { return null; } @Override public ChronoElement<?> getChildAtCeiling(CopticCalendar context) { return null; } } private static class Merger implements ChronoMerger<CopticCalendar> { //~ Methoden ------------------------------------------------------ @Override public String getFormatPattern( DisplayStyle style, Locale locale ) { return GenericDatePatterns.get("coptic", style, locale); } @Override public CopticCalendar createFrom( TimeSource<?> clock, AttributeQuery attributes ) { 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, tzid, startOfDay).toDate(); } @Override @Deprecated public CopticCalendar 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 CopticCalendar createFrom( ChronoEntity<?> entity, AttributeQuery attributes, boolean lenient, boolean preparsing ) { int cyear = entity.getInt(YEAR_OF_ERA); if (cyear == Integer.MIN_VALUE) { entity.with(ValidationElement.ERROR_MESSAGE, "Missing Coptic year."); return null; } if (entity.contains(MONTH_OF_YEAR)) { int cmonth = entity.get(MONTH_OF_YEAR).getValue(); int cdom = entity.getInt(DAY_OF_MONTH); if (cdom != Integer.MIN_VALUE) { if (CALSYS.isValid(CopticEra.ANNO_MARTYRUM, cyear, cmonth, cdom)) { return CopticCalendar.of(cyear, cmonth, cdom); } else { entity.with(ValidationElement.ERROR_MESSAGE, "Invalid Coptic date."); } } } else { int cdoy = entity.getInt(DAY_OF_YEAR); if (cdoy != Integer.MIN_VALUE) { if (cdoy > 0) { int cmonth = 1; int daycount = 0; while (cmonth <= 13) { int len = CALSYS.getLengthOfMonth(CopticEra.ANNO_MARTYRUM, cyear, cmonth); if (cdoy > daycount + len) { cmonth++; daycount += len; } else { return CopticCalendar.of(cyear, cmonth, cdoy - daycount); } } } entity.with(ValidationElement.ERROR_MESSAGE, "Invalid Coptic date."); } } return null; } @Override public ChronoDisplay preformat(CopticCalendar context, AttributeQuery attributes) { return context; } @Override public Chronology<?> preparser() { return null; } @Override public StartOfDay getDefaultStartOfDay() { return StartOfDay.EVENING; } } private static class CopticUnitRule implements UnitRule<CopticCalendar> { //~ Instanzvariablen ---------------------------------------------- private final Unit unit; //~ Konstruktoren ------------------------------------------------- CopticUnitRule(Unit unit) { super(); this.unit = unit; } //~ Methoden ------------------------------------------------------ @Override public CopticCalendar addTo(CopticCalendar date, long amount) { switch (this.unit) { case YEARS: amount = MathUtils.safeMultiply(amount, 13); // fall-through case MONTHS: long ym = MathUtils.safeAdd(ymValue(date), amount); int year = MathUtils.safeCast(MathUtils.floorDivide(ym, 13)); int month = MathUtils.floorModulo(ym, 13) + 1; int dom = Math.min( date.cdom, CALSYS.getLengthOfMonth(CopticEra.ANNO_MARTYRUM, year, month)); return CopticCalendar.of(year, month, dom); case WEEKS: amount = MathUtils.safeMultiply(amount, 7); // fall-through case DAYS: long utcDays = MathUtils.safeAdd(CALSYS.transform(date), amount); return CALSYS.transform(utcDays); default: throw new UnsupportedOperationException(this.unit.name()); } } @Override public long between(CopticCalendar start, CopticCalendar end) { switch (this.unit) { case YEARS: return CopticCalendar.Unit.MONTHS.between(start, end) / 13; case MONTHS: long delta = ymValue(end) - ymValue(start); if ((delta > 0) && (end.cdom < start.cdom)) { delta--; } else if ((delta < 0) && (end.cdom > start.cdom)) { delta++; } return delta; case WEEKS: return CopticCalendar.Unit.DAYS.between(start, end) / 7; case DAYS: return CALSYS.transform(end) - CALSYS.transform(start); default: throw new UnsupportedOperationException(this.unit.name()); } } private static int ymValue(CopticCalendar date) { return date.cyear * 13 + date.cmonth - 1; } } }