/*
* -----------------------------------------------------------------------
* Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/>
* -----------------------------------------------------------------------
* This file (JulianCalendar.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.Month;
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.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.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.format.TextElement;
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.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
/**
* <p>Represents the proleptic Julian calendar. </p>
*
* <p>It is de facto the ancestor of modern gregorian calendar but does not reflect any historic anomalies
* and applies its leap year rules even backwards into the far past. The main difference to gregorian
* calendar is the leap year rule which considers every year as leap year whose number is divisible by four. </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>
* <li>{@link #DATE}</li>
* </ul>
*
* <p>Furthermore, all elements defined in {@code EpochDays} and {@link CommonElements} are supported. </p>
*
* <p>Example of usage: </p>
*
* <pre>
* ChronoFormatter<JulianCalendar> formatter =
* ChronoFormatter.ofPattern(
* "E, d.MMMM yyyy", PatternType.CLDR, Locale.ENGLISH, JulianCalendar.axis());
* PlainDate today = SystemClock.inLocalView().today();
* JulianCalendar julianDate = today.transform(JulianCalendar.class);
* System.out.println(formatter.format(julianDate));
* </pre>
*
* @author Meno Hochschild
* @since 3.15/4.12
*/
/*[deutsch]
* <p>Repräsentiert den proleptischen julianischen Kalender. </p>
*
* <p>De facto handelt es sich um den Vorgänger des modernen gregorianischen Kalenders.
* Jedoch werden historische Anomalien nicht erfasst. Die Schaltjahresregel, die jedes durch
* vier teilbare Jahr als Schaltjahr ansieht, wird sogar bis in die ferne Vergangenheit angewandt. </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>
* <li>{@link #DATE}</li>
* </ul>
*
* <p>Au&slig;erdem werden alle Elemente von {@code EpochDays} und {@link CommonElements} unterstützt. </p>
*
* <p>Anwendungsbeispiel: </p>
*
* <pre>
* ChronoFormatter<JulianCalendar> formatter =
* ChronoFormatter.ofPattern(
* "E, d.MMMM yyyy", PatternType.CLDR, Locale.ENGLISH, JulianCalendar.axis());
* PlainDate today = SystemClock.inLocalView().today();
* JulianCalendar julianDate = today.transform(JulianCalendar.class);
* System.out.println(formatter.format(julianDate));
* </pre>
*
* @author Meno Hochschild
* @since 3.15/4.12
*/
@CalendarType("julian")
public final class JulianCalendar
extends Calendrical<JulianCalendar.Unit, JulianCalendar>
implements LocalizedPatternSupport {
//~ Statische Felder/Initialisierungen --------------------------------
// maximum of year-of-era
private static final int YMAX = 999999999;
// Tage zwischen [0000-03-01] und [1972-01-01] (julianische Datumsangaben)
private static final int OFFSET = 719470 + 2 * 365;
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 Julian date. </p>
*
* <p>This element is identical to {@code ChronoHistory.PROLEPTIC_JULIAN.date()}. </p>
*/
/*[deutsch]
* <p>Repräsentiert das julianische Datum. </p>
*
* <p>Dieses Element entspricht {@code ChronoHistory.PROLEPTIC_JULIAN.date()}. </p>
*/
public static final ChronoElement<HistoricDate> DATE = ChronoHistory.PROLEPTIC_JULIAN.date();
/**
* <p>Represents the Julian era. </p>
*
* <p>This element is identical to {@code ChronoHistory.PROLEPTIC_JULIAN.era()}. Valid values are
* either {@code HistoricEra.AD} or {@code HistoricEra.BC}. However, the era cannot be changed
* by the expression {@code julianDate.with(ERA, historicEra)}. </p>
*/
/*[deutsch]
* <p>Repräsentiert die julianische Ära. </p>
*
* <p>Dieses Element entspricht {@code ChronoHistory.PROLEPTIC_JULIAN.era()}. Die gültigen Werte
* sind entweder {@code HistoricEra.AD} oder {@code HistoricEra.BC}. Allerdings kann die Ära
* nicht durch den Ausdruck {@code julianDate.with(ERA, historicEra)} geändert werden. </p>
*/
@FormattableElement(format = "G")
public static final ChronoElement<HistoricEra> ERA = ChronoHistory.PROLEPTIC_JULIAN.era();
/**
* <p>Represents the Julian year. </p>
*
* <p>This element is identical to {@code ChronoHistory.PROLEPTIC_JULIAN.yearOfEra()}. </p>
*/
/*[deutsch]
* <p>Repräsentiert das julianische Jahr. </p>
*
* <p>Dieses Element entspricht {@code ChronoHistory.PROLEPTIC_JULIAN.yearOfEra()}. </p>
*/
@FormattableElement(format = "y")
public static final ChronoElement<Integer> YEAR_OF_ERA = ChronoHistory.PROLEPTIC_JULIAN.yearOfEra();
/**
* <p>Represents the Julian month. </p>
*
* <p>This element is identical to {@code ChronoHistory.PROLEPTIC_JULIAN.month()}. </p>
*/
/*[deutsch]
* <p>Repräsentiert den julianischen Monat. </p>
*
* <p>Dieses Element entspricht {@code ChronoHistory.PROLEPTIC_JULIAN.month()}. </p>
*/
@FormattableElement(format = "M", standalone = "L")
public static final TextElement<Integer> MONTH_OF_YEAR = ChronoHistory.PROLEPTIC_JULIAN.month();
/**
* <p>Represents the Julian day of month. </p>
*
* <p>This element is identical to {@code ChronoHistory.PROLEPTIC_JULIAN.dayOfMonth()}. </p>
*/
/*[deutsch]
* <p>Repräsentiert den julianischen Tag des Monats. </p>
*
* <p>Dieses Element entspricht {@code ChronoHistory.PROLEPTIC_JULIAN.dayOfMonth()}. </p>
*/
@FormattableElement(format = "d")
public static final ChronoElement<Integer> DAY_OF_MONTH = ChronoHistory.PROLEPTIC_JULIAN.dayOfMonth();
/**
* <p>Represents the Julian day of year. </p>
*/
/*[deutsch]
* <p>Repräsentiert den julianischen Tag des Jahres. </p>
*/
@FormattableElement(format = "D")
public static final ChronoElement<Integer> DAY_OF_YEAR =
new StdIntegerDateElement<>("DAY_OF_YEAR", JulianCalendar.class, 1, 365, 'D');
/**
* <p>Represents the Julian day of week. </p>
*
* <p>If the day-of-week is set to a new value then Time4J handles the Julian calendar week
* as starting on Sunday which is close to historic use. </p>
*/
/*[deutsch]
* <p>Repräsentiert den julianischen Tag der Woche. </p>
*
* <p>Wenn der Tag der Woche auf einen neuen Wert gesetzt wird, behandelt Time4J die julianische
* Kalenderwoche so, daß sie am Sonntag beginnt, was dem historischen Gebrauch nahe kommt. </p>
*/
@FormattableElement(format = "E")
public static final ChronoElement<Weekday> DAY_OF_WEEK =
new StdWeekdayElement<>(JulianCalendar.class);
private static final EraYearMonthDaySystem<JulianCalendar> CALSYS;
private static final TimeAxis<JulianCalendar.Unit, JulianCalendar> ENGINE;
static {
CALSYS = new Transformer();
TimeAxis.Builder<JulianCalendar.Unit, JulianCalendar> builder =
TimeAxis.Builder.setUp(
JulianCalendar.Unit.class,
JulianCalendar.class,
new Merger(),
CALSYS)
.appendElement(
DATE,
new DateRule())
.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 JulianUnitRule(Unit.YEARS),
Unit.YEARS.getLength(),
Collections.singleton(Unit.MONTHS))
.appendUnit(
Unit.MONTHS,
new JulianUnitRule(Unit.MONTHS),
Unit.MONTHS.getLength(),
Collections.singleton(Unit.YEARS))
.appendUnit(
Unit.WEEKS,
new JulianUnitRule(Unit.WEEKS),
Unit.WEEKS.getLength(),
Collections.singleton(Unit.DAYS))
.appendUnit(
Unit.DAYS,
new JulianUnitRule(Unit.DAYS),
Unit.DAYS.getLength(),
Collections.singleton(Unit.WEEKS))
.appendExtension(
new CommonElements.Weekengine(
JulianCalendar.class,
DAY_OF_MONTH,
DAY_OF_YEAR,
getDefaultWeekmodel()));
ENGINE = builder.build();
}
private static final long serialVersionUID = 3038883058279104976L;
//~ Instanzvariablen --------------------------------------------------
private transient final int prolepticYear;
private transient final int month;
private transient final int dom;
//~ Konstruktoren -----------------------------------------------------
private JulianCalendar(
int prolepticYear,
int month,
int dom
) {
super();
this.prolepticYear = prolepticYear;
this.month = month;
this.dom = dom;
}
//~ Methoden ----------------------------------------------------------
/**
* <p>Creates a new instance of a Julian calendar date. </p>
*
* @param era either {@code HistoricEra.AD} or {@code HistoricEra.BC}
* @param yearOfEra year of era in range 1 until 999,999,999
* @param month month (1-12)
* @param dayOfMonth day of month (1-31)
* @return new instance of {@code JulianCalendar}
* @throws IllegalArgumentException in case of any inconsistencies
* @since 3.15/4.12
*/
/*[deutsch]
* <p>Erzeugt ein neues julianisches Kalenderdatum. </p>
*
* @param era either {@code HistoricEra.AD} or {@code HistoricEra.BC}
* @param yearOfEra year of era in range 1 until 999,999,999
* @param month month (1-12)
* @param dayOfMonth day of month (1-31)
* @return new instance of {@code JulianCalendar}
* @throws IllegalArgumentException in case of any inconsistencies
* @since 3.15/4.12
*/
public static JulianCalendar of(
HistoricEra era,
int yearOfEra,
int month,
int dayOfMonth
) {
if (era == null) {
throw new NullPointerException("Missing Julian era.");
} else if (!CALSYS.isValid(era, yearOfEra, month, dayOfMonth)) {
throw new IllegalArgumentException("Out of bounds: " + toString(era, yearOfEra, month, dayOfMonth));
}
if (era == HistoricEra.AD) {
return new JulianCalendar(yearOfEra, month, dayOfMonth);
} else { // BC
return new JulianCalendar(MathUtils.safeSubtract(1, yearOfEra), month, dayOfMonth);
}
}
/**
* <p>Creates a new instance of a Julian calendar date. </p>
*
* @param era either {@code HistoricEra.AD} or {@code HistoricEra.BC}
* @param yearOfEra year of era in range 1 until 999,999,999
* @param month month (1-12)
* @param dayOfMonth day of month (1-31)
* @return new instance of {@code JulianCalendar}
* @throws IllegalArgumentException in case of any inconsistencies
* @since 3.15/4.12
*/
/*[deutsch]
* <p>Erzeugt ein neues julianisches Kalenderdatum. </p>
*
* @param era either {@code HistoricEra.AD} or {@code HistoricEra.BC}
* @param yearOfEra year of era in range 1 until 999,999,999
* @param month month (1-12)
* @param dayOfMonth day of month (1-31)
* @return new instance of {@code JulianCalendar}
* @throws IllegalArgumentException in case of any inconsistencies
* @since 3.15/4.12
*/
public static JulianCalendar of(
HistoricEra era,
int yearOfEra,
Month month,
int dayOfMonth
) {
return JulianCalendar.of(era, yearOfEra, month.getValue(), dayOfMonth);
}
/**
* <p>Obtains the current calendar date in system time. </p>
*
* <p>Convenient short-cut for: {@code SystemClock.inLocalView().now(JulianCalendar.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(JulianCalendar.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 JulianCalendar nowInSystemTime() {
return SystemClock.inLocalView().now(JulianCalendar.axis());
}
/**
* <p>Yields the Julian era. </p>
*
* @return either {@code HistoricEra.AD} or {@code HistoricEra.BC}
* @since 3.15/4.12
*/
/*[deutsch]
* <p>Liefert die julianische Ära. </p>
*
* @return either {@code HistoricEra.AD} or {@code HistoricEra.BC}
* @since 3.15/4.12
*/
public HistoricEra getEra() {
return ((this.prolepticYear >= 1) ? HistoricEra.AD : HistoricEra.BC);
}
/**
* <p>Yields the Julian year within current era. </p>
*
* @return int ({@code 1 <= year <= 999,999,999})
* @since 3.15/4.12
*/
/*[deutsch]
* <p>Liefert das julianische Jahr innerhalb der aktuellen Ära. </p>
*
* @return int ({@code 1 <= year <= 999,999,999})
* @since 3.15/4.12
*/
public int getYear() {
return ((this.prolepticYear >= 1) ? this.prolepticYear : MathUtils.safeSubtract(1, this.prolepticYear));
}
/**
* <p>Yields the Julian month. </p>
*
* @return enum
* @since 3.15/4.12
*/
/*[deutsch]
* <p>Liefert den julianischen Monat. </p>
*
* @return enum
* @since 3.15/4.12
*/
public Month getMonth() {
return Month.valueOf(this.month);
}
/**
* <p>Yields the Julian day of month. </p>
*
* @return int
* @since 3.15/4.12
*/
/*[deutsch]
* <p>Liefert den julianischen Tag des Monats. </p>
*
* @return int
* @since 3.15/4.12
*/
public int getDayOfMonth() {
return this.dom;
}
/**
* <p>Determines the day of week. </p>
*
* @return Weekday
* @since 3.15/4.12
*/
/*[deutsch]
* <p>Ermittelt den Wochentag. </p>
*
* @return Weekday
* @since 3.15/4.12
*/
public Weekday getDayOfWeek() {
long utcDays = CALSYS.transform(this);
return Weekday.valueOf(MathUtils.floorModulo(utcDays + 5, 7) + 1);
}
/**
* <p>Yields the Julian day of year. </p>
*
* @return int
* @since 3.15/4.12
*/
/*[deutsch]
* <p>Liefert den julianschen Tag des Jahres. </p>
*
* @return int
* @since 3.15/4.12
*/
public int getDayOfYear() {
return this.get(DAY_OF_YEAR).intValue();
}
/**
* <p>Yields the length of current Julian month in days. </p>
*
* @return int
* @since 3.15/4.12
*/
/*[deutsch]
* <p>Liefert die Länge des aktuellen julianischen Monats in Tagen. </p>
*
* @return int
* @since 3.15/4.12
*/
public int lengthOfMonth() {
return lengthOfMonth(this.prolepticYear, this.month);
}
/**
* <p>Yields the length of current Julian year in days. </p>
*
* @return int
* @since 3.15/4.12
*/
/*[deutsch]
* <p>Liefert die Länge des aktuellen julianischen Jahres in Tagen. </p>
*
* @return int
* @since 3.15/4.12
*/
public int lengthOfYear() {
return this.isLeapYear() ? 366 : 365;
}
/**
* <p>Is the year of this date a leap year? </p>
*
* @return boolean
* @since 3.15/4.12
*/
/*[deutsch]
* <p>Liegt dieses Datum in einem Schaltjahr? </p>
*
* @return boolean
* @since 3.15/4.12
*/
public boolean isLeapYear() {
return ((this.prolepticYear % 4) == 0);
}
/**
* <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.15/4.12
*/
/*[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.15/4.12
*/
public GeneralTimestamp<JulianCalendar> 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.15/4.12
*/
/*[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.15/4.12
*/
public GeneralTimestamp<JulianCalendar> 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 JulianCalendar) {
JulianCalendar that = (JulianCalendar) obj;
return (
(this.dom == that.dom)
&& (this.month == that.month)
&& (this.prolepticYear == that.prolepticYear)
);
} else {
return false;
}
}
@Override
public int hashCode() {
return (17 * this.dom + 31 * this.month + 37 * this.prolepticYear);
}
@Override
public String toString() {
return toString(this.getEra(), this.getYear(), this.month, this.dom);
}
/**
* <p>Obtains the standard week model of this calendar. </p>
*
* <p>The Julian calendar usually starts on Sunday. </p>
*
* @return Weekmodel
* @since 3.24/4.20
*/
/*[deutsch]
* <p>Ermittelt das Standardwochenmodell dieses Kalenders. </p>
*
* <p>Der julianische Kalender startet normalerweise am Sonntag. </p>
*
* @return Weekmodel
* @since 3.24/4.20
*/
public static Weekmodel getDefaultWeekmodel() {
return Weekmodel.of(Weekday.SUNDAY, 1);
}
/**
* <p>Returns the associated time axis. </p>
*
* @return chronology
* @since 3.15/4.12
*/
/*[deutsch]
* <p>Liefert die zugehörige Zeitachse. </p>
*
* @return chronology
* @since 3.15/4.12
*/
public static TimeAxis<Unit, JulianCalendar> axis() {
return ENGINE;
}
@Override
protected TimeAxis<Unit, JulianCalendar> getChronology() {
return ENGINE;
}
@Override
protected JulianCalendar getContext() {
return this;
}
// used in serialization
int getProlepticYear() {
return this.prolepticYear;
}
private static String toString(
CalendarEra era,
int yearOfEra,
int month,
int dom
) {
StringBuilder sb = new StringBuilder(32);
sb.append("JULIAN-");
sb.append(era.name());
sb.append('-');
String y = String.valueOf(yearOfEra);
for (int i = y.length(); i < 4; i++) {
sb.append('0');
}
sb.append(y);
sb.append('-');
if (month < 10) {
sb.append('0');
}
sb.append(month);
sb.append('-');
if (dom < 10) {
sb.append('0');
}
sb.append(dom);
return sb.toString();
}
private static int lengthOfMonth(
int pYear,
int monthOfYear
) {
switch (monthOfYear) {
case 4:
case 6:
case 9:
case 11:
return 30;
case 2:
return ((pYear % 4) == 0) ? 29 : 28;
default:
return 31;
}
}
/**
* @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 7}. 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.JULIAN);
}
/**
* @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 Julian calendar. </p>
*
* @since 3.15/4.12
*/
/*[deutsch]
* <p>Definiert einige kalendarische Zeiteinheiten für den julianischen Kalender. </p>
*
* @since 3.15/4.12
*/
public static enum Unit
implements ChronoUnit {
//~ Statische Felder/Initialisierungen ----------------------------
YEARS(365.25 * 86400.0),
MONTHS(365.25 * 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 Julian 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(
JulianCalendar start,
JulianCalendar end
) {
return (int) start.until(end, this); // safe
}
}
private static class Transformer
implements EraYearMonthDaySystem<JulianCalendar> {
//~ Methoden ------------------------------------------------------
@Override
public boolean isValid(
CalendarEra era,
int yearOfEra,
int monthOfYear,
int dayOfMonth
) {
int pYear;
if (era == HistoricEra.AD) {
pYear = yearOfEra;
} else if (era == HistoricEra.BC) {
pYear = MathUtils.safeSubtract(1, yearOfEra);
} else {
return false;
}
if (yearOfEra < 1 || yearOfEra > YMAX || monthOfYear < 1 || monthOfYear > 12 || dayOfMonth < 1) {
return false;
}
return (dayOfMonth <= lengthOfMonth(pYear, monthOfYear));
}
@Override
public int getLengthOfMonth(
CalendarEra era,
int yearOfEra,
int monthOfYear
) {
int pYear;
if (era == HistoricEra.AD) {
pYear = yearOfEra;
} else if (era == HistoricEra.BC) {
pYear = MathUtils.safeSubtract(1, yearOfEra);
} else {
throw new IllegalArgumentException("Invalid era: " + era);
}
if (yearOfEra >= 1 && yearOfEra <= YMAX && monthOfYear >= 1 && monthOfYear <= 12) {
return lengthOfMonth(pYear, monthOfYear);
}
throw new IllegalArgumentException("Out of bounds: year=" + yearOfEra + ", month=" + monthOfYear);
}
@Override
public int getLengthOfYear(
CalendarEra era,
int yearOfEra
) {
int pYear;
if (era == HistoricEra.AD) {
pYear = yearOfEra;
} else if (era == HistoricEra.BC) {
pYear = MathUtils.safeSubtract(1, yearOfEra);
} else {
throw new IllegalArgumentException("Invalid era: " + era);
}
if ((yearOfEra >= 1) && (yearOfEra <= YMAX)) {
return ((pYear % 4) == 0) ? 366 : 365;
}
throw new IllegalArgumentException("Out of bounds: year=" + yearOfEra);
}
@Override
public JulianCalendar transform(long utcDays) {
long y;
int m;
int d;
long days = MathUtils.safeAdd(utcDays, OFFSET);
long q4 = MathUtils.floorDivide(days, 1461);
int r4 = MathUtils.floorModulo(days, 1461);
if (r4 == 1460) {
y = (q4 + 1) * 4;
m = 2;
d = 29;
} else {
int q1 = (r4 / 365);
int r1 = (r4 % 365);
y = q4 * 4 + q1;
m = (((r1 + 31) * 5) / 153) + 2;
d = r1 - (((m + 1) * 153) / 5) + 123;
if (m > 12) {
y++;
m -= 12;
}
}
HistoricEra era = ((y >= 1) ? HistoricEra.AD : HistoricEra.BC);
int yearOfEra = MathUtils.safeCast((y >= 1) ? y : MathUtils.safeSubtract(1, y));
return JulianCalendar.of(era, yearOfEra, m, d);
}
@Override
public long transform(JulianCalendar date) {
long y = date.prolepticYear;
int m = date.month;
if (m < 3) {
y--;
m += 12;
}
long days = (
(y * 365)
+ MathUtils.floorDivide(y, 4)
+ (((m + 1) * 153) / 5) - 123
+ date.dom);
return days - OFFSET;
}
@Override
public long getMinimumSinceUTC() {
JulianCalendar min = new JulianCalendar(1 - YMAX, 1, 1);
return this.transform(min);
}
@Override
public long getMaximumSinceUTC() {
JulianCalendar max = new JulianCalendar(YMAX, 12, 31);
return this.transform(max);
}
@Override
public List<CalendarEra> getEras() {
CalendarEra bc = HistoricEra.BC;
CalendarEra ad = HistoricEra.AD;
return Collections.unmodifiableList(Arrays.asList(bc, ad));
}
}
private static class IntegerRule
implements ElementRule<JulianCalendar, Integer> {
//~ Instanzvariablen ----------------------------------------------
private final int index;
//~ Konstruktoren -------------------------------------------------
IntegerRule(int index) {
super();
this.index = index;
}
//~ Methoden ------------------------------------------------------
@Override
public Integer getValue(JulianCalendar context) {
switch (this.index) {
case YEAR_INDEX:
return context.getYear();
case DAY_OF_MONTH_INDEX:
return context.dom;
case DAY_OF_YEAR_INDEX:
int doy = 0;
for (int m = 1; m < context.month; m++) {
doy += lengthOfMonth(context.prolepticYear, m);
}
return doy + context.dom;
default:
throw new UnsupportedOperationException("Unknown element index: " + this.index);
}
}
@Override
public Integer getMinimum(JulianCalendar 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(JulianCalendar context) {
switch (this.index) {
case YEAR_INDEX:
return YMAX;
case DAY_OF_MONTH_INDEX:
return lengthOfMonth(context.prolepticYear, context.month);
case DAY_OF_YEAR_INDEX:
return (((context.prolepticYear % 4) == 0) ? 366 : 365);
default:
throw new UnsupportedOperationException("Unknown element index: " + this.index);
}
}
@Override
public boolean isValid(
JulianCalendar 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 JulianCalendar withValue(
JulianCalendar context,
Integer value,
boolean lenient
) {
if (value == null) {
throw new IllegalArgumentException("Missing element value.");
}
switch (this.index) {
case YEAR_INDEX:
int y = value.intValue();
int pYear = (context.getEra() == HistoricEra.AD) ? y : MathUtils.safeSubtract(1, y);
int dmax = lengthOfMonth(pYear, context.month);
int d = Math.min(context.dom, dmax);
return JulianCalendar.of(context.getEra(), y, context.month, d);
case DAY_OF_MONTH_INDEX:
return JulianCalendar.of(context.getEra(), context.getYear(), context.month, value.intValue());
case DAY_OF_YEAR_INDEX:
int doy = value.intValue();
if ((doy >= 1) && (doy <= context.lengthOfYear())) {
int delta = value.intValue() - this.getValue(context).intValue();
return context.plus(CalendarDays.of(delta));
} else {
throw new IllegalArgumentException("Invalid day of year: " + value);
}
default:
throw new UnsupportedOperationException("Unknown element index: " + this.index);
}
}
@Override
public ChronoElement<?> getChildAtFloor(JulianCalendar context) {
if (this.index == YEAR_INDEX) {
return MONTH_OF_YEAR;
}
return null;
}
@Override
public ChronoElement<?> getChildAtCeiling(JulianCalendar context) {
if (this.index == YEAR_INDEX) {
return MONTH_OF_YEAR;
}
return null;
}
}
private static class MonthRule
implements ElementRule<JulianCalendar, Integer> {
//~ Methoden ------------------------------------------------------
@Override
public Integer getValue(JulianCalendar context) {
return Integer.valueOf(context.month);
}
@Override
public Integer getMinimum(JulianCalendar context) {
return Integer.valueOf(1);
}
@Override
public Integer getMaximum(JulianCalendar context) {
return Integer.valueOf(12);
}
@Override
public boolean isValid(
JulianCalendar context,
Integer value
) {
if (value == null) {
return false;
}
int m = value.intValue();
return ((m >= 1) && (m <= 12));
}
@Override
public JulianCalendar withValue(
JulianCalendar context,
Integer value,
boolean lenient
) {
if (value == null) {
throw new IllegalArgumentException("Missing month.");
}
int m = value.intValue();
int dmax = lengthOfMonth(context.prolepticYear, m);
int d = Math.min(context.dom, dmax);
return new JulianCalendar(context.prolepticYear, m, d);
}
@Override
public ChronoElement<?> getChildAtFloor(JulianCalendar context) {
return DAY_OF_MONTH;
}
@Override
public ChronoElement<?> getChildAtCeiling(JulianCalendar context) {
return DAY_OF_MONTH;
}
}
private static class EraRule
implements ElementRule<JulianCalendar, HistoricEra> {
//~ Methoden ------------------------------------------------------
@Override
public HistoricEra getValue(JulianCalendar context) {
return context.getEra();
}
@Override
public HistoricEra getMinimum(JulianCalendar context) {
return HistoricEra.BC;
}
@Override
public HistoricEra getMaximum(JulianCalendar context) {
return HistoricEra.AD;
}
@Override
public boolean isValid(
JulianCalendar context,
HistoricEra value
) {
return context.getEra().equals(value);
}
@Override
public JulianCalendar withValue(
JulianCalendar context,
HistoricEra value,
boolean lenient
) {
if (!this.isValid(context, value)) {
throw new IllegalArgumentException("Julian era cannot be changed.");
}
return context;
}
@Override
public ChronoElement<?> getChildAtFloor(JulianCalendar context) {
return YEAR_OF_ERA;
}
@Override
public ChronoElement<?> getChildAtCeiling(JulianCalendar context) {
return YEAR_OF_ERA;
}
}
private static class DateRule
implements ElementRule<JulianCalendar, HistoricDate> {
//~ Methoden ------------------------------------------------------
@Override
public HistoricDate getValue(JulianCalendar context) {
return HistoricDate.of(context.getEra(), context.getYear(), context.month, context.dom);
}
@Override
public HistoricDate getMinimum(JulianCalendar context) {
return HistoricDate.of(HistoricEra.BC, YMAX, 1, 1);
}
@Override
public HistoricDate getMaximum(JulianCalendar context) {
return HistoricDate.of(HistoricEra.AD, YMAX, 12, 31);
}
@Override
public boolean isValid(
JulianCalendar context,
HistoricDate value
) {
if (value == null) {
return false;
}
return CALSYS.isValid(value.getEra(), value.getYearOfEra(), value.getMonth(), value.getDayOfMonth());
}
@Override
public JulianCalendar withValue(
JulianCalendar context,
HistoricDate value,
boolean lenient
) {
if (value == null) {
throw new IllegalArgumentException("Missing historic date value.");
}
return JulianCalendar.of(value.getEra(), value.getYearOfEra(), value.getMonth(), value.getDayOfMonth());
}
@Override
public ChronoElement<?> getChildAtFloor(JulianCalendar context) {
return null;
}
@Override
public ChronoElement<?> getChildAtCeiling(JulianCalendar context) {
return null;
}
}
private static class WeekdayRule
implements ElementRule<JulianCalendar, Weekday> {
//~ Methoden ------------------------------------------------------
@Override
public Weekday getValue(JulianCalendar context) {
return context.getDayOfWeek();
}
@Override
public Weekday getMinimum(JulianCalendar context) {
return Weekday.SUNDAY;
}
@Override
public Weekday getMaximum(JulianCalendar context) {
return Weekday.SATURDAY;
}
@Override
public boolean isValid(
JulianCalendar context,
Weekday value
) {
return (value != null);
}
@Override
public JulianCalendar withValue(
JulianCalendar 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(JulianCalendar context) {
return null;
}
@Override
public ChronoElement<?> getChildAtCeiling(JulianCalendar context) {
return null;
}
}
private static class Merger
implements ChronoMerger<JulianCalendar> {
//~ Methoden ------------------------------------------------------
@Override
public String getFormatPattern(
DisplayStyle style,
Locale locale
) {
return GenericDatePatterns.get("generic", style, locale); // uses era
}
@Override
public JulianCalendar 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 JulianCalendar 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 JulianCalendar createFrom(
ChronoEntity<?> entity,
AttributeQuery attributes,
boolean lenient,
boolean preparsing
) {
if (!entity.contains(ERA)) {
entity.with(ValidationElement.ERROR_MESSAGE, "Missing Julian era.");
return null;
}
HistoricEra era = entity.get(ERA);
int yearOfEra = entity.getInt(YEAR_OF_ERA);
if (yearOfEra == Integer.MIN_VALUE) {
entity.with(ValidationElement.ERROR_MESSAGE, "Missing Julian year.");
return null;
}
int month = entity.getInt(MONTH_OF_YEAR);
if (month != Integer.MIN_VALUE) {
int dom = entity.getInt(DAY_OF_MONTH);
if (dom != Integer.MIN_VALUE) {
if (CALSYS.isValid(era, yearOfEra, month, dom)) {
return JulianCalendar.of(era, yearOfEra, month, dom);
} else {
entity.with(ValidationElement.ERROR_MESSAGE, "Invalid Julian date.");
}
}
}
int doy = entity.getInt(DAY_OF_YEAR);
if (doy != Integer.MIN_VALUE) {
if (doy > 0) {
int m = 1;
int pYear = ((era == HistoricEra.AD) ? yearOfEra : MathUtils.safeSubtract(1, yearOfEra));
int daycount = 0;
while (m <= 12) {
int len = lengthOfMonth(pYear, m);
if (doy > daycount + len) {
m++;
daycount += len;
} else {
return JulianCalendar.of(era, yearOfEra, m, doy - daycount);
}
}
}
entity.with(ValidationElement.ERROR_MESSAGE, "Invalid Julian date.");
}
return null;
}
@Override
public ChronoDisplay preformat(JulianCalendar context, AttributeQuery attributes) {
return context;
}
@Override
public Chronology<?> preparser() {
return null;
}
@Override
public StartOfDay getDefaultStartOfDay() {
return StartOfDay.MIDNIGHT;
}
}
private static class JulianUnitRule
implements UnitRule<JulianCalendar> {
//~ Instanzvariablen ----------------------------------------------
private final Unit unit;
//~ Konstruktoren -------------------------------------------------
JulianUnitRule(Unit unit) {
super();
this.unit = unit;
}
//~ Methoden ------------------------------------------------------
@Override
public JulianCalendar addTo(JulianCalendar 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 pYear = MathUtils.safeCast(MathUtils.floorDivide(ym, 12));
int month = MathUtils.floorModulo(ym, 12) + 1;
int dom =
Math.min(
date.dom,
lengthOfMonth(pYear, month));
HistoricEra era = ((pYear >= 1) ? HistoricEra.AD : HistoricEra.BC);
int yearOfEra = ((pYear >= 1) ? pYear : MathUtils.safeSubtract(1, pYear));
return JulianCalendar.of(era, yearOfEra, 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(JulianCalendar start, JulianCalendar end) {
switch (this.unit) {
case YEARS:
return JulianCalendar.Unit.MONTHS.between(start, end) / 12;
case MONTHS:
long delta = ymValue(end) - ymValue(start);
if ((delta > 0) && (end.dom < start.dom)) {
delta--;
} else if ((delta < 0) && (end.dom > start.dom)) {
delta++;
}
return delta;
case WEEKS:
return JulianCalendar.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(JulianCalendar date) {
return date.prolepticYear * 12 + date.month - 1;
}
}
}