/*
* -----------------------------------------------------------------------
* Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/>
* -----------------------------------------------------------------------
* This file (PersianCalendar.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.CalendarUnit;
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.ChronoElement;
import net.time4j.engine.ChronoEntity;
import net.time4j.engine.ChronoMerger;
import net.time4j.engine.ChronoUnit;
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.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 Solar Hijri calendar which is officially used in Iran and Afghanistan. </p>
*
* <p>It is a solar calendar which is in close agreement with the astronomical seasons. The vernal equinox
* serves as the first day of the Persian year (Farvardin 1st). If it is observed before noon at local time
* in Teheran then the associated day is the first day of the year else the next day. More details about the
* length of the vernal-equinox-year see:
* <a href="http://aramis.obspm.fr/~heydari/divers/ir-cal-eng.html">A concise review of the Iranian calendar</a> </p>
*
* <p>The calendar year is divided into 12 Persian months. The first 6 months are 31 days long. The following
* months are 30 days long with the exception of the last month whose length is 29 days in normal years and
* 30 days in leap years. The algorithm is based on the excellent work of Borkowski who describes the details at
* <a href="http://www.astro.uni.torun.pl/~kb/Papers/EMP/PersianC-EMP.htm">The Persian calendar for 3000 years</a>.
* It is in agreement with the well known cycle proposed by Omar Khayam for the years 1178-1633 (ISO: 1799-2254).
* However, dates calculated in far future beyond 2123 can still be wrong due to the uncertainty of astronomical
* term delta-T and should be considered as approximation. </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<PersianCalendar> formatter =
* ChronoFormatter.setUp(PersianCalendar.axis(), new Locale("fa"))
* .addPattern("EEE, d. MMMM yy", PatternType.NON_ISO_DATE).build();
* PlainDate today = SystemClock.inLocalView().today();
* PersianCalendar jalali = today.transform(PersianCalendar.class);
* System.out.println(formatter.format(jalali));
* </pre>
*
* @author Meno Hochschild
* @since 3.9/4.6
* @doctags.concurrency {immutable}
*/
/*[deutsch]
* <p>Repräsentiert den persischen Kalender, der offiziell in Iran und Afghanistan benutzt wird. </p>
*
* <p>Es handelt sich um einen solaren Kalender, der in enger Übereinstimmung mit den astronomischen
* Saisons existiert. Als Startpunkt dient die astronomische Frühlingstagundnachtgleiche. Wird sie vor
* 12 Uhr lokaler Ortszeit in Teheran beobachtet, dann ist der assoziierte Tag der erste Tag des persischen
* Kalenders, sonst der nächste Tag. Mehr Details zur Länge des persischen Jahres:
* <a href="http://aramis.obspm.fr/~heydari/divers/ir-cal-eng.html">A concise review of the Iranian calendar</a> </p>
*
* <p>Das Kalendarjahr wird in 12 persische Monate geteilt. Die ersten 6 Monate sind 31 Tage lang, die folgenden
* nur 30 Tage mit Ausnahme des letzten Monats, dessen Länge 29 oder 30 Tage beträgt je nachdem ob ein
* Schaltjahr vorliegt. Der Algorithmus basiert auf der ausgezeichneten Arbeit von Borkowski, der die Details auf
* <a href="http://www.astro.uni.torun.pl/~kb/Papers/EMP/PersianC-EMP.htm">The Persian calendar for 3000 years</a>.
* beschreibt. Er ist in Übereinstimmung mit dem bekannten Zyklus von Omar Khayam für die Jahre 1178-1633
* (ISO: 1799-2254). Allerdings können Datumswerte nach dem gregorianischen Jahr 2123 falsch sein, weil der
* astronomische Term delta-T sich nicht beliebig genau in die Zukunft prognostizieren lässt. Solche Datumswerte
* weit in der Zukunft sind daher als Näherung anzusehen. </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<PersianCalendar> formatter =
* ChronoFormatter.setUp(PersianCalendar.axis(), new Locale("fa"))
* .addPattern("EEE, d. MMMM yy", PatternType.NON_ISO_DATE).build();
* PlainDate today = SystemClock.inLocalView().today();
* PersianCalendar jalali = today.transform(PersianCalendar.class);
* System.out.println(formatter.format(jalali));
* </pre>
*
* @author Meno Hochschild
* @since 3.9/4.6
* @doctags.concurrency {immutable}
*/
@CalendarType("persian")
public final class PersianCalendar
extends Calendrical<PersianCalendar.Unit, PersianCalendar>
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 Persian era. </p>
*/
/*[deutsch]
* <p>Repräsentiert die persische Ära. </p>
*/
@FormattableElement(format = "G")
public static final ChronoElement<PersianEra> ERA =
new StdEnumDateElement<>("ERA", PersianCalendar.class, PersianEra.class, 'G');
/**
* <p>Represents the Persian year. </p>
*/
/*[deutsch]
* <p>Repräsentiert das persische Jahr. </p>
*/
@FormattableElement(format = "y")
public static final StdCalendarElement<Integer, PersianCalendar> YEAR_OF_ERA =
new StdIntegerDateElement<>(
"YEAR_OF_ERA",
PersianCalendar.class,
1,
3000,
'y',
null,
null);
/**
* <p>Represents the Persian month. </p>
*/
/*[deutsch]
* <p>Repräsentiert den persischen Monat. </p>
*/
@FormattableElement(format = "M", standalone = "L")
public static final StdCalendarElement<PersianMonth, PersianCalendar> MONTH_OF_YEAR =
new StdEnumDateElement<>(
"MONTH_OF_YEAR",
PersianCalendar.class,
PersianMonth.class,
'M');
/**
* <p>Represents the Persian day of month. </p>
*/
/*[deutsch]
* <p>Repräsentiert den persischen Tag des Monats. </p>
*/
@FormattableElement(format = "d")
public static final StdCalendarElement<Integer, PersianCalendar> DAY_OF_MONTH =
new StdIntegerDateElement<>("DAY_OF_MONTH", PersianCalendar.class, 1, 31, 'd');
/**
* <p>Represents the Persian day of year. </p>
*/
/*[deutsch]
* <p>Repräsentiert den persischen Tag des Jahres. </p>
*/
@FormattableElement(format = "D")
public static final StdCalendarElement<Integer, PersianCalendar> DAY_OF_YEAR =
new StdIntegerDateElement<>("DAY_OF_YEAR", PersianCalendar.class, 1, 365, 'D');
/**
* <p>Represents the Persian day of week. </p>
*
* <p>If the day-of-week is set to a new value then Time4J handles the Persian calendar week
* as starting on Saturday. </p>
*/
/*[deutsch]
* <p>Repräsentiert den persischen Tag der Woche. </p>
*
* <p>Wenn der Tag der Woche auf einen neuen Wert gesetzt wird, behandelt Time4J die persische
* Kalenderwoche so, daß sie am Samstag beginnt. </p>
*/
@FormattableElement(format = "E")
public static final StdCalendarElement<Weekday, PersianCalendar> DAY_OF_WEEK =
new StdWeekdayElement<>(PersianCalendar.class);
private static final EraYearMonthDaySystem<PersianCalendar> CALSYS;
private static final TimeAxis<PersianCalendar.Unit, PersianCalendar> ENGINE;
static {
CALSYS = new Transformer();
TimeAxis.Builder<PersianCalendar.Unit, PersianCalendar> builder =
TimeAxis.Builder.setUp(
PersianCalendar.Unit.class,
PersianCalendar.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 PersianUnitRule(Unit.YEARS),
Unit.YEARS.getLength(),
Collections.singleton(Unit.MONTHS))
.appendUnit(
Unit.MONTHS,
new PersianUnitRule(Unit.MONTHS),
Unit.MONTHS.getLength(),
Collections.singleton(Unit.YEARS))
.appendUnit(
Unit.WEEKS,
new PersianUnitRule(Unit.WEEKS),
Unit.WEEKS.getLength(),
Collections.singleton(Unit.DAYS))
.appendUnit(
Unit.DAYS,
new PersianUnitRule(Unit.DAYS),
Unit.DAYS.getLength(),
Collections.singleton(Unit.WEEKS))
.appendExtension(
new CommonElements.Weekengine(
PersianCalendar.class,
DAY_OF_MONTH,
DAY_OF_YEAR,
getDefaultWeekmodel()
)
);
ENGINE = builder.build();
}
private static final long serialVersionUID = -411339992208638290L;
//~ Instanzvariablen --------------------------------------------------
private transient final int pyear;
private transient final int pmonth;
private transient final int pdom;
//~ Konstruktoren -----------------------------------------------------
private PersianCalendar(
int pyear,
int pmonth,
int pdom
) {
super();
this.pyear = pyear;
this.pmonth = pmonth;
this.pdom = pdom;
}
//~ Methoden ----------------------------------------------------------
/**
* <p>Creates a new instance of a Persian calendar date. </p>
*
* @param pyear Persian year in the range 1-3000
* @param pmonth Persian month
* @param pdom Persian day of month
* @return new instance of {@code PersianCalendar}
* @throws IllegalArgumentException in case of any inconsistencies
* @since 3.9/4.6
*/
/*[deutsch]
* <p>Erzeugt ein neues persisches Kalenderdatum. </p>
*
* @param pyear Persian year in the range 1-3000
* @param pmonth Persian month
* @param pdom Persian day of month
* @return new instance of {@code PersianCalendar}
* @throws IllegalArgumentException in case of any inconsistencies
* @since 3.9/4.6
*/
public static PersianCalendar of(
int pyear,
PersianMonth pmonth,
int pdom
) {
return PersianCalendar.of(pyear, pmonth.getValue(), pdom);
}
/**
* <p>Creates a new instance of a Persian calendar date. </p>
*
* @param pyear Persian year in the range 1-3000
* @param pmonth Persian month
* @param pdom Persian day of month
* @return new instance of {@code PersianCalendar}
* @throws IllegalArgumentException in case of any inconsistencies
* @since 3.9/4.6
*/
/*[deutsch]
* <p>Erzeugt ein neues persisches Kalenderdatum. </p>
*
* @param pyear Persian year in the range 1-3000
* @param pmonth Persian month
* @param pdom Persian day of month
* @return new instance of {@code PersianCalendar}
* @throws IllegalArgumentException in case of any inconsistencies
* @since 3.9/4.6
*/
public static PersianCalendar of(
int pyear,
int pmonth,
int pdom
) {
if (!CALSYS.isValid(PersianEra.ANNO_PERSICO, pyear, pmonth, pdom)) {
throw new IllegalArgumentException(
"Invalid Persian date: year=" + pyear + ", month=" + pmonth + ", day=" + pdom);
}
return new PersianCalendar(pyear, pmonth, pdom);
}
/**
* <p>Obtains the current calendar date in system time. </p>
*
* <p>Convenient short-cut for: {@code SystemClock.inLocalView().now(PersianCalendar.axis())}. </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(PersianCalendar.axis())}. </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 PersianCalendar nowInSystemTime() {
return SystemClock.inLocalView().now(PersianCalendar.axis());
}
/**
* <p>Yields the Persian era. </p>
*
* @return {@link PersianEra#ANNO_PERSICO}
* @since 3.9/4.6
*/
/*[deutsch]
* <p>Liefert die persische Ära. </p>
*
* @return {@link PersianEra#ANNO_PERSICO}
* @since 3.9/4.6
*/
public PersianEra getEra() {
return PersianEra.ANNO_PERSICO;
}
/**
* <p>Yields the Persian year. </p>
*
* @return int
* @since 3.9/4.6
*/
/*[deutsch]
* <p>Liefert das persische Jahr. </p>
*
* @return int
* @since 3.9/4.6
*/
public int getYear() {
return this.pyear;
}
/**
* <p>Yields the Persian month. </p>
*
* @return enum
* @since 3.9/4.6
*/
/*[deutsch]
* <p>Liefert den persischen Monat. </p>
*
* @return enum
* @since 3.9/4.6
*/
public PersianMonth getMonth() {
return PersianMonth.valueOf(this.pmonth);
}
/**
* <p>Yields the Persian day of month. </p>
*
* @return int
* @since 3.9/4.6
*/
/*[deutsch]
* <p>Liefert den persischen Tag des Monats. </p>
*
* @return int
* @since 3.9/4.6
*/
public int getDayOfMonth() {
return this.pdom;
}
/**
* <p>Determines the day of week. </p>
*
* <p>The Persian calendar also uses a 7-day-week. </p>
*
* @return Weekday
* @since 3.9/4.6
*/
/*[deutsch]
* <p>Ermittelt den Wochentag. </p>
*
* <p>Der persische Kalendar verwendet ebenfalls eine 7-Tage-Woche. </p>
*
* @return Weekday
* @since 3.9/4.6
*/
public Weekday getDayOfWeek() {
long utcDays = CALSYS.transform(this);
return Weekday.valueOf(MathUtils.floorModulo(utcDays + 5, 7) + 1);
}
/**
* <p>Yields the Persian day of year. </p>
*
* @return int
* @since 3.9/4.6
*/
/*[deutsch]
* <p>Liefert den persischen Tag des Jahres. </p>
*
* @return int
* @since 3.9/4.6
*/
public int getDayOfYear() {
return this.get(DAY_OF_YEAR).intValue();
}
/**
* <p>Yields the length of current Persian month in days. </p>
*
* @return int
* @since 3.9/4.6
*/
/*[deutsch]
* <p>Liefert die Länge des aktuellen persischen Monats in Tagen. </p>
*
* @return int
* @since 3.9/4.6
*/
public int lengthOfMonth() {
return CALSYS.getLengthOfMonth(PersianEra.ANNO_PERSICO, this.pyear, this.pmonth);
}
/**
* <p>Yields the length of current Persian year in days. </p>
*
* @return int
* @since 3.9/4.6
*/
/*[deutsch]
* <p>Liefert die Länge des aktuellen persischen Jahres in Tagen. </p>
*
* @return int
* @since 3.9/4.6
*/
public int lengthOfYear() {
return CALSYS.getLengthOfYear(PersianEra.ANNO_PERSICO, this.pyear);
}
/**
* <p>Is the year of this date a leap year? </p>
*
* @return boolean
* @since 3.9/4.6
*/
/*[deutsch]
* <p>Liegt dieses Datum in einem Schaltjahr? </p>
*
* @return boolean
* @since 3.9/4.6
*/
public boolean isLeapYear() {
return (this.lengthOfYear() > 365);
}
/**
* <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.9/4.6
*/
/*[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.9/4.6
*/
public GeneralTimestamp<PersianCalendar> 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.9/4.6
*/
/*[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.9/4.6
*/
public GeneralTimestamp<PersianCalendar> 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 PersianCalendar) {
PersianCalendar that = (PersianCalendar) obj;
return (
(this.pdom == that.pdom)
&& (this.pmonth == that.pmonth)
&& (this.pyear == that.pyear)
);
} else {
return false;
}
}
@Override
public int hashCode() {
return (17 * this.pdom + 31 * this.pmonth + 37 * this.pyear);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(32);
sb.append("AP-");
String y = String.valueOf(this.pyear);
for (int i = y.length(); i < 4; i++) {
sb.append('0');
}
sb.append(y);
sb.append('-');
if (this.pmonth < 10) {
sb.append('0');
}
sb.append(this.pmonth);
sb.append('-');
if (this.pdom < 10) {
sb.append('0');
}
sb.append(this.pdom);
return sb.toString();
}
/**
* <p>Obtains the standard week model of this calendar. </p>
*
* <p>The persian calendar usually starts on Saturday. </p>
*
* @return Weekmodel
* @since 3.24/4.20
*/
/*[deutsch]
* <p>Ermittelt das Standardwochenmodell dieses Kalenders. </p>
*
* <p>Der persische 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.FRIDAY); // Iran
}
/**
* <p>Returns the associated time axis. </p>
*
* @return chronology
* @since 3.9/4.6
*/
/*[deutsch]
* <p>Liefert die zugehörige Zeitachse. </p>
*
* @return chronology
* @since 3.9/4.6
*/
public static TimeAxis<Unit, PersianCalendar> axis() {
return ENGINE;
}
@Override
protected TimeAxis<Unit, PersianCalendar> getChronology() {
return ENGINE;
}
@Override
protected PersianCalendar 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 2}. 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.PERSIAN);
}
/**
* @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 Persian calendar. </p>
*
* @since 3.9/4.6
*/
/*[deutsch]
* <p>Definiert einige kalendarische Zeiteinheiten für den persischen Kalender. </p>
*
* @since 3.9/4.6
*/
public static enum Unit
implements ChronoUnit {
//~ Statische Felder/Initialisierungen ----------------------------
YEARS(365.2424 * 86400.0), // rounded length of vernal equinox year
MONTHS(365.2424 * 86400.0 / 12),
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 Persian dates in this unit. </p>
*
* @param start start date (inclusive)
* @param end end date (exclusive)
* @return difference counted in this unit
* @since 3.9/4.6
*/
/*[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.9/4.6
*/
public int between(
PersianCalendar start,
PersianCalendar end
) {
return (int) start.until(end, this); // safe
}
}
private static class Transformer
implements EraYearMonthDaySystem<PersianCalendar> {
//~ Methoden ------------------------------------------------------
@Override
public boolean isValid(
CalendarEra era,
int yearOfEra,
int monthOfYear,
int dayOfMonth
) {
return (
(era == PersianEra.ANNO_PERSICO)
&& (yearOfEra >= 1)
&& (yearOfEra <= 3000)
&& (monthOfYear >= 1)
&& (monthOfYear <= 12)
&& (dayOfMonth >= 1)
&& (dayOfMonth <= getLengthOfMonth(era, yearOfEra, monthOfYear))
);
}
@Override
public int getLengthOfMonth(
CalendarEra era,
int yearOfEra,
int monthOfYear
) {
if (era != PersianEra.ANNO_PERSICO) {
throw new IllegalArgumentException("Invalid era: " + era);
}
if (
(era == PersianEra.ANNO_PERSICO)
&& (yearOfEra >= 1)
&& (yearOfEra <= 3000)
&& (monthOfYear >= 1)
&& (monthOfYear <= 12)
) {
if (monthOfYear <= 6) {
return 31;
} else if (monthOfYear <= 11) {
return 30;
} else {
return ((this.getLengthOfYear(era, yearOfEra) == 365) ? 29 : 30);
}
}
throw new IllegalArgumentException("Out of bounds: year=" + yearOfEra + ", month=" + monthOfYear);
}
@Override
public int getLengthOfYear(
CalendarEra era,
int yearOfEra
) {
if (era != PersianEra.ANNO_PERSICO) {
throw new IllegalArgumentException("Invalid era: " + era);
}
if (
(yearOfEra >= 1)
&& (yearOfEra <= 3000)
) {
PersianCalendar nextYear = new PersianCalendar(yearOfEra + 1, 1, 1);
PersianCalendar thisYear = new PersianCalendar(yearOfEra, 1, 1);
return (int) (this.transform(nextYear) - this.transform(thisYear));
}
throw new IllegalArgumentException("Out of bounds: year=" + yearOfEra);
}
@Override
public PersianCalendar transform(long utcDays) {
PlainDate date = PlainDate.of(utcDays, EpochDays.UTC);
int pyear = date.getYear() - 621;
if (date.getMonth() < 3) {
pyear--; // optimization
}
PlainDate equinox = vernalEquinox(pyear);
long delta = CalendarUnit.DAYS.between(equinox, date);
while (delta < 0) {
pyear--;
equinox = vernalEquinox(pyear);
delta = CalendarUnit.DAYS.between(equinox, date);
}
int pmonth = 1;
while (pmonth < 12) {
int len = ((pmonth <= 6) ? 31 : 30);
if (delta < len) {
break;
} else {
delta -= len;
pmonth++;
}
}
int pdom = (int) (delta + 1);
return PersianCalendar.of(pyear, pmonth, pdom);
}
@Override
public long transform(PersianCalendar date) {
long utcDays = vernalEquinox(date.pyear).getDaysSinceEpochUTC();
utcDays += ((date.pmonth - 1) * 31 - ((date.pmonth / 7) * (date.pmonth - 7)) + date.pdom - 1);
return utcDays;
}
@Override
public long getMinimumSinceUTC() {
PersianCalendar min = new PersianCalendar(1, 1, 1);
return this.transform(min);
}
@Override
public long getMaximumSinceUTC() {
PersianCalendar max = new PersianCalendar(3001, 1, 1);
return this.transform(max) - 1;
}
@Override
public List<CalendarEra> getEras() {
CalendarEra era = PersianEra.ANNO_PERSICO;
return Collections.singletonList(era);
}
private static PlainDate vernalEquinox(int pyear) {
int[] breaks =
new int[] {
-61, 9, 38, 199, 426, 686, 756, 818, 1111, 1181,
1210, 1635, 2060, 2097, 2192, 2262, 2324, 2394, 2456, 3178
};
int max = breaks[breaks.length - 1];
if ((pyear < 1) || (pyear >= max)) {
throw new IllegalArgumentException("Persian year out of range 1-" + max + ": " + pyear);
}
int gyear = pyear + 621;
int leapP = -14;
int previousY = breaks[0];
int delta = 0;
for (int i = 1; i < breaks.length; i++) {
int currentY = breaks[i];
delta = currentY - previousY;
if (pyear < currentY) {
break;
}
leapP += ((delta / 33) * 8 + (delta % 33) / 4);
previousY = currentY;
}
int n = pyear - previousY;
leapP += ((n / 33) * 8 + ((n % 33) + 3) / 4);
if (((delta % 33) == 4) && (delta - n == 4)) {
leapP++;
}
int leapG = gyear / 4 - ((gyear / 100 + 1) * 3) / 4 - 150;
int marchDay = 20 + leapP - leapG;
return PlainDate.of(gyear, 3, marchDay);
}
}
private static class IntegerRule
implements ElementRule<PersianCalendar, Integer> {
//~ Instanzvariablen ----------------------------------------------
private final int index;
//~ Konstruktoren -------------------------------------------------
IntegerRule(int index) {
super();
this.index = index;
}
//~ Methoden ------------------------------------------------------
@Override
public Integer getValue(PersianCalendar context) {
switch (this.index) {
case YEAR_INDEX:
return context.pyear;
case DAY_OF_MONTH_INDEX:
return context.pdom;
case DAY_OF_YEAR_INDEX:
int doy = 0;
for (int m = 1; m < context.pmonth; m++) {
doy += CALSYS.getLengthOfMonth(PersianEra.ANNO_PERSICO, context.pyear, m);
}
return doy + context.pdom;
default:
throw new UnsupportedOperationException("Unknown element index: " + this.index);
}
}
@Override
public Integer getMinimum(PersianCalendar 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(PersianCalendar context) {
switch (this.index) {
case YEAR_INDEX:
return 3000;
case DAY_OF_MONTH_INDEX:
return CALSYS.getLengthOfMonth(PersianEra.ANNO_PERSICO, context.pyear, context.pmonth);
case DAY_OF_YEAR_INDEX:
return CALSYS.getLengthOfYear(PersianEra.ANNO_PERSICO, context.pyear);
default:
throw new UnsupportedOperationException("Unknown element index: " + this.index);
}
}
@Override
public boolean isValid(
PersianCalendar 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 PersianCalendar withValue(
PersianCalendar 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(PersianEra.ANNO_PERSICO, y, context.pmonth);
int d = Math.min(context.pdom, dmax);
return PersianCalendar.of(y, context.pmonth, d);
case DAY_OF_MONTH_INDEX:
return new PersianCalendar(context.pyear, context.pmonth, 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(PersianCalendar context) {
if (this.index == YEAR_INDEX) {
return MONTH_OF_YEAR;
}
return null;
}
@Override
public ChronoElement<?> getChildAtCeiling(PersianCalendar context) {
if (this.index == YEAR_INDEX) {
return MONTH_OF_YEAR;
}
return null;
}
}
private static class MonthRule
implements ElementRule<PersianCalendar, PersianMonth> {
//~ Methoden ------------------------------------------------------
@Override
public PersianMonth getValue(PersianCalendar context) {
return context.getMonth();
}
@Override
public PersianMonth getMinimum(PersianCalendar context) {
return PersianMonth.FARVARDIN;
}
@Override
public PersianMonth getMaximum(PersianCalendar context) {
return PersianMonth.ESFAND;
}
@Override
public boolean isValid(
PersianCalendar context,
PersianMonth value
) {
return (value != null);
}
@Override
public PersianCalendar withValue(
PersianCalendar context,
PersianMonth value,
boolean lenient
) {
if (value == null) {
throw new IllegalArgumentException("Missing month.");
}
int m = value.getValue();
int dmax = CALSYS.getLengthOfMonth(PersianEra.ANNO_PERSICO, context.pyear, m);
int d = Math.min(context.pdom, dmax);
return new PersianCalendar(context.pyear, m, d);
}
@Override
public ChronoElement<?> getChildAtFloor(PersianCalendar context) {
return DAY_OF_MONTH;
}
@Override
public ChronoElement<?> getChildAtCeiling(PersianCalendar context) {
return DAY_OF_MONTH;
}
}
private static class EraRule
implements ElementRule<PersianCalendar, PersianEra> {
//~ Methoden ------------------------------------------------------
@Override
public PersianEra getValue(PersianCalendar context) {
return PersianEra.ANNO_PERSICO;
}
@Override
public PersianEra getMinimum(PersianCalendar context) {
return PersianEra.ANNO_PERSICO;
}
@Override
public PersianEra getMaximum(PersianCalendar context) {
return PersianEra.ANNO_PERSICO;
}
@Override
public boolean isValid(
PersianCalendar context,
PersianEra value
) {
return (value != null);
}
@Override
public PersianCalendar withValue(
PersianCalendar context,
PersianEra value,
boolean lenient
) {
if (value == null) {
throw new IllegalArgumentException("Missing era value.");
}
return context;
}
@Override
public ChronoElement<?> getChildAtFloor(PersianCalendar context) {
return YEAR_OF_ERA;
}
@Override
public ChronoElement<?> getChildAtCeiling(PersianCalendar context) {
return YEAR_OF_ERA;
}
}
private static class WeekdayRule
implements ElementRule<PersianCalendar, Weekday> {
//~ Methoden ------------------------------------------------------
@Override
public Weekday getValue(PersianCalendar context) {
return context.getDayOfWeek();
}
@Override
public Weekday getMinimum(PersianCalendar context) {
return Weekday.SATURDAY;
}
@Override
public Weekday getMaximum(PersianCalendar context) {
return Weekday.FRIDAY;
}
@Override
public boolean isValid(
PersianCalendar context,
Weekday value
) {
return (value != null);
}
@Override
public PersianCalendar withValue(
PersianCalendar 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(PersianCalendar context) {
return null;
}
@Override
public ChronoElement<?> getChildAtCeiling(PersianCalendar context) {
return null;
}
}
private static class Merger
implements ChronoMerger<PersianCalendar> {
//~ Methoden ------------------------------------------------------
@Override
public String getFormatPattern(
DisplayStyle style,
Locale locale
) {
return GenericDatePatterns.get("persian", style, locale);
}
@Override
public PersianCalendar 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, StartOfDay.MIDNIGHT);
return Moment.from(clock.currentTime()).toGeneralTimestamp(ENGINE, tzid, startOfDay).toDate();
}
@Override
@Deprecated
public PersianCalendar 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 PersianCalendar createFrom(
ChronoEntity<?> entity,
AttributeQuery attributes,
boolean lenient,
boolean preparsing
) {
int pyear = entity.getInt(YEAR_OF_ERA);
if (pyear == Integer.MIN_VALUE) {
entity.with(ValidationElement.ERROR_MESSAGE, "Missing Persian year.");
return null;
}
if (entity.contains(MONTH_OF_YEAR)) {
int pmonth = entity.get(MONTH_OF_YEAR).getValue();
int pdom = entity.getInt(DAY_OF_MONTH);
if (pdom != Integer.MIN_VALUE) {
if (CALSYS.isValid(PersianEra.ANNO_PERSICO, pyear, pmonth, pdom)) {
return PersianCalendar.of(pyear, pmonth, pdom);
} else {
entity.with(ValidationElement.ERROR_MESSAGE, "Invalid Persian date.");
}
}
} else {
int pdoy = entity.getInt(DAY_OF_YEAR);
if (pdoy != Integer.MIN_VALUE) {
if (pdoy > 0) {
int pmonth = 1;
int daycount = 0;
while (pmonth <= 12) {
int len = CALSYS.getLengthOfMonth(PersianEra.ANNO_PERSICO, pyear, pmonth);
if (pdoy > daycount + len) {
pmonth++;
daycount += len;
} else {
int pdom = pdoy - daycount;
return PersianCalendar.of(pyear, pmonth, pdom);
}
}
}
entity.with(ValidationElement.ERROR_MESSAGE, "Invalid Persian date.");
}
}
return null;
}
}
private static class PersianUnitRule
implements UnitRule<PersianCalendar> {
//~ Instanzvariablen ----------------------------------------------
private final Unit unit;
//~ Konstruktoren -------------------------------------------------
PersianUnitRule(Unit unit) {
super();
this.unit = unit;
}
//~ Methoden ------------------------------------------------------
@Override
public PersianCalendar addTo(PersianCalendar date, long amount) {
switch (this.unit) {
case YEARS:
amount = MathUtils.safeMultiply(amount, 12);
// fall-through
case MONTHS:
long ym = MathUtils.safeAdd(ymValue(date), amount);
int year = MathUtils.safeCast(MathUtils.floorDivide(ym, 12));
int month = MathUtils.floorModulo(ym, 12) + 1;
int dom =
Math.min(
date.pdom,
CALSYS.getLengthOfMonth(PersianEra.ANNO_PERSICO, year, month));
return PersianCalendar.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(PersianCalendar start, PersianCalendar end) {
switch (this.unit) {
case YEARS:
return PersianCalendar.Unit.MONTHS.between(start, end) / 12;
case MONTHS:
long delta = ymValue(end) - ymValue(start);
if ((delta > 0) && (end.pdom < start.pdom)) {
delta--;
} else if ((delta < 0) && (end.pdom > start.pdom)) {
delta++;
}
return delta;
case WEEKS:
return PersianCalendar.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(PersianCalendar date) {
return date.pyear * 12 + date.pmonth - 1;
}
}
}