/*
* -----------------------------------------------------------------------
* Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/>
* -----------------------------------------------------------------------
* This file (ChronoHistory.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.history;
import net.time4j.CalendarUnit;
import net.time4j.PlainDate;
import net.time4j.base.GregorianMath;
import net.time4j.engine.AttributeKey;
import net.time4j.engine.ChronoElement;
import net.time4j.engine.EpochDays;
import net.time4j.engine.FormattableElement;
import net.time4j.engine.VariantSource;
import net.time4j.format.Attributes;
import net.time4j.format.TextElement;
import net.time4j.history.internal.HistoricVariant;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
/**
* <p>Represents the chronological history of calendar reforms in a given region. </p>
*
* @author Meno Hochschild
* @since 3.0
* @doctags.concurrency {immutable}
*/
/*[deutsch]
* <p>Repräsentiert die Geschichte der Kalenderreformen in einer gegebenen Region. </p>
*
* @author Meno Hochschild
* @since 3.0
* @doctags.concurrency {immutable}
*/
public final class ChronoHistory
implements VariantSource, Serializable {
//~ Statische Felder/Initialisierungen --------------------------------
/**
* Format attribute controlling the type of historic year used in parsing or formatting.
*
* @since 3.18/4.14
*/
/*[deutsch]
* Formatattribut, das den Typ des beim Formatieren oder Interpretieren zu verwendenden historischen
* Jahres festlegt.
*
* @since 3.18/4.14
*/
public static final AttributeKey<YearDefinition> YEAR_DEFINITION =
Attributes.createKey("YEAR_DEFINITION", YearDefinition.class);
/**
* <p>Describes no real historic event but just the proleptic gregorian calendar which is assumed
* to be in power all times. </p>
*
* <p>This constant rather serves for academic purposes. Users will normally use {@code PlainDate}
* without an era. </p>
*/
/*[deutsch]
* <p>Beschreibt kein wirkliches historisches Ereignis, sondern einfach nur den proleptisch gregorianischen
* Kalender, der als für alle Zeiten gültig angesehen wird. </p>
*
* <p>Diese Konstante dient eher akademischen Übungen. Anwender werden normalerweise direkt die Klasse
* {@code PlainDate} ohne das Ära-Konzept nutzen. </p>
*/
public static final ChronoHistory PROLEPTIC_GREGORIAN;
/**
* <p>Describes no real historic event but just the proleptic julian calendar which is assumed
* to be in power all times. </p>
*
* <p>This constant rather serves for academic purposes because the julian calendar is now nowhere in power
* and has not existed before the calendar reform of Julius Caesar. </p>
*/
/*[deutsch]
* <p>Beschreibt kein wirkliches historisches Ereignis, sondern einfach nur den proleptisch julianischen
* Kalender, der als für alle Zeiten gültig angesehen wird. </p>
*
* <p>Diese Konstante dient eher akademischen Übungen, weil der julianische Kalender aktuell nirgendwo
* in der Welt in Kraft ist und vor der Kalenderreform von Julius Caesar nicht existierte. </p>
*/
public static final ChronoHistory PROLEPTIC_JULIAN;
/**
* <p>Describes no real historic event but just the proleptic byzantine calendar which is assumed
* to be in power all times on or after the creation of the world. </p>
*
* <p>This constant rather serves for academic purposes because the byzantine calendar was in latest use
* in Russia before 1700. </p>
*
* @see HistoricEra#BYZANTINE
* @since 3.14/4.11
*/
/*[deutsch]
* <p>Beschreibt kein wirkliches historisches Ereignis, sondern einfach nur den proleptisch byzantinischen
* Kalender, der als für alle Zeiten ab der Erschaffung der Welt gültig angesehen wird. </p>
*
* <p>Diese Konstante dient eher akademischen Übungen, weil der byzantinische Kalender zuletzt in
* Rußland vor 1700 verwendet wurde. </p>
*
* @see HistoricEra#BYZANTINE
* @since 3.14/4.11
*/
public static final ChronoHistory PROLEPTIC_BYZANTINE;
private static final long EARLIEST_CUTOVER;
private static final ChronoHistory INTRODUCTION_BY_POPE_GREGOR;
private static final ChronoHistory SWEDEN;
private static final Map<String, ChronoHistory> LOOKUP;
static {
PROLEPTIC_GREGORIAN =
new ChronoHistory(
HistoricVariant.PROLEPTIC_GREGORIAN,
Collections.singletonList(
new CutOverEvent(Long.MIN_VALUE, CalendarAlgorithm.GREGORIAN, CalendarAlgorithm.GREGORIAN)));
PROLEPTIC_JULIAN =
new ChronoHistory(
HistoricVariant.PROLEPTIC_JULIAN,
Collections.singletonList(
new CutOverEvent(Long.MIN_VALUE, CalendarAlgorithm.JULIAN, CalendarAlgorithm.JULIAN)));
PROLEPTIC_BYZANTINE =
new ChronoHistory(
HistoricVariant.PROLEPTIC_BYZANTINE,
Collections.singletonList(
new CutOverEvent(Long.MIN_VALUE, CalendarAlgorithm.JULIAN, CalendarAlgorithm.JULIAN)),
null,
new NewYearStrategy(NewYearRule.BEGIN_OF_SEPTEMBER, Integer.MAX_VALUE),
EraPreference.byzantineUntil(PlainDate.axis().getMaximum()));
EARLIEST_CUTOVER = PlainDate.of(1582, 10, 15).get(EpochDays.MODIFIED_JULIAN_DATE);
INTRODUCTION_BY_POPE_GREGOR = ChronoHistory.ofGregorianReform(EARLIEST_CUTOVER);
List<CutOverEvent> events = new ArrayList<>();
events.add(new CutOverEvent(-57959, CalendarAlgorithm.JULIAN, CalendarAlgorithm.SWEDISH)); // 1700-03-01
events.add(new CutOverEvent(-53575, CalendarAlgorithm.SWEDISH, CalendarAlgorithm.JULIAN)); // 1712-03-01
events.add(new CutOverEvent(-38611, CalendarAlgorithm.JULIAN, CalendarAlgorithm.GREGORIAN)); // 1753-03-01
SWEDEN = new ChronoHistory(HistoricVariant.SWEDEN, Collections.unmodifiableList(events));
Map<String, ChronoHistory> tmp = new HashMap<>();
PlainDate ad988 = ChronoHistory.PROLEPTIC_JULIAN.convert(HistoricDate.of(HistoricEra.AD, 988, 3, 1));
PlainDate ad1383 = ChronoHistory.PROLEPTIC_JULIAN.convert(HistoricDate.of(HistoricEra.AD, 1382, 12, 24));
PlainDate ad1422 = ChronoHistory.PROLEPTIC_JULIAN.convert(HistoricDate.of(HistoricEra.AD, 1421, 12, 24));
PlainDate ad1700 = ChronoHistory.PROLEPTIC_JULIAN.convert(HistoricDate.of(HistoricEra.AD, 1699, 12, 31));
tmp.put(
"ES", // source: http://www.newadvent.org/cathen/03738a.htm#beginning
ChronoHistory.ofFirstGregorianReform()
.with(
NewYearRule.BEGIN_OF_JANUARY.until(1383)
.and(NewYearRule.CHRISTMAS_STYLE.until(1556)))
.with(EraPreference.hispanicUntil(ad1383)));
tmp.put(
"PT", // source: http://www.newadvent.org/cathen/03738a.htm#beginning
ChronoHistory.ofFirstGregorianReform()
.with(
NewYearRule.BEGIN_OF_JANUARY.until(1422)
.and(NewYearRule.CHRISTMAS_STYLE.until(1556)))
.with(EraPreference.hispanicUntil(ad1422)));
tmp.put(
"FR",
ChronoHistory.ofGregorianReform(PlainDate.of(1582, 12, 20))
.with(NewYearRule.EASTER_STYLE.until(1567))); // edict of Roussillon in 1564 took effect 3 years later
tmp.put(
"DE", // Holy Roman Empire
ChronoHistory.ofFirstGregorianReform()
.with(NewYearRule.CHRISTMAS_STYLE.until(1544)));
tmp.put(
"DE-BAYERN",
ChronoHistory.ofGregorianReform(PlainDate.of(1583, 10, 16))
.with(NewYearRule.CHRISTMAS_STYLE.until(1544)));
tmp.put(
"DE-PREUSSEN",
ChronoHistory.ofGregorianReform(PlainDate.of(1610, 9, 2))
.with(NewYearRule.CHRISTMAS_STYLE.until(1559)));
tmp.put(
"DE-PROTESTANT",
ChronoHistory.ofGregorianReform(PlainDate.of(1700, 3, 1))
.with(NewYearRule.CHRISTMAS_STYLE.until(1559)));
tmp.put(
"NL",
ChronoHistory.ofGregorianReform(PlainDate.of(1583, 1, 1)));
tmp.put(
"AT",
ChronoHistory.ofGregorianReform(PlainDate.of(1584, 1, 17)));
tmp.put(
"CH",
ChronoHistory.ofGregorianReform(PlainDate.of(1584, 1, 22)));
tmp.put(
"HU",
ChronoHistory.ofGregorianReform(PlainDate.of(1587, 11, 1)));
tmp.put(
"DK",
ChronoHistory.ofGregorianReform(PlainDate.of(1700, 3, 1))
.with(NewYearRule.MARIA_ANUNCIATA.until(1623)));
tmp.put(
"NO",
ChronoHistory.ofGregorianReform(PlainDate.of(1700, 3, 1))
.with(NewYearRule.MARIA_ANUNCIATA.until(1623)));
tmp.put(
"IT",
ChronoHistory.ofFirstGregorianReform()
.with(NewYearRule.CHRISTMAS_STYLE.until(1583)));
tmp.put(
"IT-FLORENCE",
ChronoHistory.ofFirstGregorianReform()
.with(NewYearRule.MARIA_ANUNCIATA.until(1749)));
tmp.put(
"IT-PISA",
ChronoHistory.ofFirstGregorianReform()
.with(NewYearRule.CALCULUS_PISANUS.until(1749)));
tmp.put(
"IT-VENICE",
ChronoHistory.ofFirstGregorianReform()
.with(NewYearRule.BEGIN_OF_MARCH.until(1798)));
tmp.put(
"GB", // source: http://www.newadvent.org/cathen/03738a.htm#beginning
ChronoHistory.ofGregorianReform(PlainDate.of(1752, 9, 14))
.with(
NewYearRule.CHRISTMAS_STYLE.until(1087) // we don't exactly know when it started
.and(NewYearRule.BEGIN_OF_JANUARY.until(1155))
.and(NewYearRule.MARIA_ANUNCIATA.until(1752)))); // http://www.ortelius.de/kalender/jul_en.php
tmp.put(
"GB-SCT", // Scotland (we assume as only difference to England the last new-year-rule)
ChronoHistory.ofGregorianReform(PlainDate.of(1752, 9, 14))
.with(
NewYearRule.CHRISTMAS_STYLE.until(1087)
.and(NewYearRule.BEGIN_OF_JANUARY.until(1155))
.and(NewYearRule.MARIA_ANUNCIATA.until(1600))));
tmp.put(
"RU",
ChronoHistory.ofGregorianReform(PlainDate.of(1918, 2, 14))
.with(
NewYearRule.BEGIN_OF_JANUARY.until(988)
.and(NewYearRule.BEGIN_OF_MARCH.until(1493))
.and(NewYearRule.BEGIN_OF_SEPTEMBER.until(1700)))
.with(EraPreference.byzantineBetween(ad988, ad1700)));
tmp.put("SE", SWEDEN);
LOOKUP = Collections.unmodifiableMap(tmp);
}
// Dient der Serialisierungsunterstützung.
private static final long serialVersionUID = 4100690610730913643L;
//~ Instanzvariablen --------------------------------------------------
private transient final HistoricVariant variant;
private transient final List<CutOverEvent> events;
private transient final AncientJulianLeapYears ajly;
private transient final NewYearStrategy nys;
private transient final EraPreference eraPreference;
private transient final ChronoElement<HistoricDate> dateElement;
private transient final ChronoElement<HistoricEra> eraElement;
private transient final TextElement<Integer> yearOfEraElement;
private transient final ChronoElement<Integer> yearAfterElement;
private transient final ChronoElement<Integer> yearBeforeElement;
private transient final TextElement<Integer> monthElement;
private transient final TextElement<Integer> dayOfMonthElement;
private transient final TextElement<Integer> dayOfYearElement;
private transient final ChronoElement<Integer> centuryElement;
private transient final Set<ChronoElement<?>> elements;
//~ Konstruktoren -----------------------------------------------------
private ChronoHistory(
HistoricVariant variant,
List<CutOverEvent> events
) {
this(variant, events, null, null, EraPreference.DEFAULT);
}
private ChronoHistory(
HistoricVariant variant,
List<CutOverEvent> events,
AncientJulianLeapYears ajly,
NewYearStrategy nys,
EraPreference eraPreference
) {
super();
if (events.isEmpty()) {
throw new IllegalArgumentException(
"At least one cutover event must be present in chronological history.");
} else if (variant == null) {
throw new NullPointerException("Missing historic variant.");
} else if (eraPreference == null) {
throw new NullPointerException("Missing era preference.");
}
this.variant = variant;
this.events = events;
this.ajly = ajly;
this.nys = nys;
this.eraPreference = eraPreference;
this.dateElement = new HistoricDateElement(this);
this.eraElement = new HistoricEraElement(this);
this.yearOfEraElement = new HistoricIntegerElement(
'y',
1,
GregorianMath.MAX_YEAR,
this,
HistoricIntegerElement.YEAR_OF_ERA_INDEX
);
this.yearAfterElement = new HistoricIntegerElement(
'\u0000',
1,
GregorianMath.MAX_YEAR,
this,
HistoricIntegerElement.YEAR_AFTER_INDEX
);
this.yearBeforeElement = new HistoricIntegerElement(
'\u0000',
1,
GregorianMath.MAX_YEAR,
this,
HistoricIntegerElement.YEAR_BEFORE_INDEX
);
this.monthElement = new HistoricIntegerElement(
'M',
1,
12,
this,
HistoricIntegerElement.MONTH_INDEX
);
this.dayOfMonthElement = new HistoricIntegerElement(
'd',
1,
31,
this,
HistoricIntegerElement.DAY_OF_MONTH_INDEX
);
this.dayOfYearElement = new HistoricIntegerElement(
'D',
1,
365,
this,
HistoricIntegerElement.DAY_OF_YEAR_INDEX
);
this.centuryElement = new HistoricIntegerElement(
'\u0000',
1,
((GregorianMath.MAX_YEAR - 1) / 100) + 1,
this,
HistoricIntegerElement.CENTURY_INDEX
);
Set<ChronoElement<?>> set = new HashSet<>();
set.add(this.dateElement);
set.add(this.eraElement);
set.add(this.yearOfEraElement);
set.add(this.yearAfterElement);
set.add(this.yearBeforeElement);
set.add(this.monthElement);
set.add(this.dayOfMonthElement);
set.add(this.dayOfYearElement);
set.add(this.centuryElement);
this.elements = Collections.unmodifiableSet(set);
}
//~ Methoden ----------------------------------------------------------
/**
* <p>Describes the original switch from julian to gregorian calendar introduced
* by pope Gregor on 1582-10-15. </p>
*
* @return chronological history with cutover to gregorian calendar on 1582-10-15
* @see #ofGregorianReform(PlainDate)
* @since 3.0
*/
/*[deutsch]
* <p>Beschreibt die Umstellung vom julianischen zum gregorianischen Kalender wie
* von Papst Gregor zu 1582-10-15 eingeführt. </p>
*
* @return chronological history with cutover to gregorian calendar on 1582-10-15
* @see #ofGregorianReform(PlainDate)
* @since 3.0
*/
public static ChronoHistory ofFirstGregorianReform() {
return INTRODUCTION_BY_POPE_GREGOR;
}
/**
* <p>Describes a single switch from julian to gregorian calendar at given date. </p>
*
* <p>Since version 3.1 there are two special cases to consider. If the given date is the maximum/minimum
* of the date axis then this method will return the proleptic julian/gregorian {@code ChronoHistory}. </p>
*
* @param start calendar date when the gregorian calendar was introduced
* @return new chronological history with only one cutover from julian to gregorian calendar
* @throws IllegalArgumentException if given date is before first introduction of gregorian calendar on 1582-10-15
* @see #ofFirstGregorianReform()
* @since 3.0
*/
/*[deutsch]
* <p>Beschreibt die Umstellung vom julianischen zum gregorianischen Kalender am angegebenen Datum. </p>
*
* <p>Seit Version 3.1 gibt es zwei Sonderfälle. Wenn das angegebene Datum das Maximum/Minimum der
* Datumsachse darstellt, dann wird diese Methode die proleptisch julianische/gregorianische Variante
* von {@code ChronoHistory} zurückgeben. </p>
*
* @param start calendar date when the gregorian calendar was introduced
* @return new chronological history with only one cutover from julian to gregorian calendar
* @throws IllegalArgumentException if given date is before first introduction of gregorian calendar on 1582-10-15
* @see #ofFirstGregorianReform()
* @since 3.0
*/
public static ChronoHistory ofGregorianReform(PlainDate start) {
if (start.equals(PlainDate.axis().getMaximum())) {
return ChronoHistory.PROLEPTIC_JULIAN;
} else if (start.equals(PlainDate.axis().getMinimum())) {
return ChronoHistory.PROLEPTIC_GREGORIAN;
}
long mjd = start.get(EpochDays.MODIFIED_JULIAN_DATE);
check(mjd);
if (mjd == EARLIEST_CUTOVER) {
return INTRODUCTION_BY_POPE_GREGOR;
}
return ChronoHistory.ofGregorianReform(mjd);
}
/**
* <p>Determines the (usually approximate) history of gregorian calendar reforms for given locale. </p>
*
* <p>The actual implementation just falls back to the introduction of gregorian calendar by
* pope Gregor if a locale is not explicitly mentioned in following table. Later releases of Time4J
* might refine the implementation for most European countries. <strong>In any case, this method does not
* reflect the absolute historic truth.</strong> Various regions in many countries used different rules than
* the rest. And sometimes historic sources and literature are simply unreliable so this method is just an
* approach on base of best efforts. In case of doubt, users should prefer to model the concrete history
* themselves as needed. For any cutover date not supported by this method, users can instead call
* {@code ofGregorianReform(PlainDate)}. </p>
*
* <p>This method does not use the language part of given locale but the country part (ISO-3166),
* in case of Scotland also the variant part (SCT), in case of Germany the variant part (PREUSSEN or
* PROTESTANT etc.). In order to create an appropriate locale, use a constructor like
* {@code new Locale("en", "GB", "SCT")}. </p>
*
* <div style="margin-top:5px;">
* <table border="1">
* <caption>Summary</caption>
* <tr>
* <th>Region</th><th>Locale</th><th>Gregorian cutover</th><th>New Year (using estimates)</th><th>Era preference</th>
* </tr>
* <tr>
* <td>Italy</td><td>IT</td><td>1582-10-15</td><td>CHRISTMAS_STYLE.until(1583)</td><td></td>
* </tr>
* <tr>
* <td>Florence</td><td>IT-FLORENCE</td><td>1582-10-15</td><td>MARIA_ANUNCIATA.until(1749)</td><td></td>
* </tr>
* <tr>
* <td>Pisa</td><td>IT-PISA</td><td>1582-10-15</td><td>CALCULUS_PISANUS.until(1749)</td><td></td>
* </tr>
* <tr>
* <td>Republic of Venice</td><td>IT-VENICE</td><td>1582-10-15</td><td>BEGIN_OF_MARCH.until(1798)</td><td></td>
* </tr>
* <tr>
* <td>Spain</td><td>ES</td><td>1582-10-15</td>
* <td>BEGIN_OF_JANUARY.until(1383)<br>.and(CHRISTMAS_STYLE.until(1556))</td>
* <td>HISPANIC (until 1383)</td>
* </tr>
* <tr>
* <td>Portugal</td><td>PT</td><td>1582-10-15</td>
* <td>BEGIN_OF_JANUARY.until(1422)<br>.and(CHRISTMAS_STYLE.until(1556))</td>
* <td>HISPANIC (until 1422)</td>
* </tr>
* <tr>
* <td>Poland</td><td>PL</td><td>1582-10-15</td><td></td><td></td>
* </tr>
* <tr>
* <td>France</td><td>FR</td><td>1582-12-20</td><td>EASTER_STYLE.until(1567)</td><td></td>
* </tr>
* <tr>
* <td>Holland</td><td>NL</td><td>1583-01-01</td><td></td><td></td>
* </tr>
* <tr>
* <td>Holy Roman Empire <sup>1)</sup></td><td>DE</td><td>1582-10-15</td><td>CHRISTMAS_STYLE.until(1544)</td><td></td>
* </tr>
* <tr>
* <td>Bavaria</td><td>DE-BAYERN</td><td>1583-10-16</td><td>CHRISTMAS_STYLE.until(1544)</td><td></td>
* </tr>
* <tr>
* <td>Prussia</td><td>DE-PREUSSEN</td><td>1610-09-02</td><td>CHRISTMAS_STYLE.until(1559)</td><td></td>
* </tr>
* <tr>
* <td>Germany (protestant)</td><td>DE-PROTESTANT</td><td>1700-03-01</td><td>CHRISTMAS_STYLE.until(1559)</td><td></td>
* </tr>
* <tr>
* <td>Austria</td><td>AT</td><td>1584-01-17</td><td></td><td></td>
* </tr>
* <tr>
* <td>Switzerland</td><td>CH</td><td>1584-01-22</td><td></td><td></td>
* </tr>
* <tr>
* <td>Hungaria</td><td>HU</td><td>1587-11-01</td><td></td><td></td>
* </tr>
* <tr>
* <td>Danmark</td><td>DK</td><td>1700-03-01</td><td>MARIA_ANUNCIATA.until(1623)</td><td></td>
* </tr>
* <tr>
* <td>Norway</td><td>NO</td><td>1700-03-01</td><td>MARIA_ANUNCIATA.until(1623)</td><td></td>
* </tr>
* <tr>
* <td>England</td><td>GB</td><td>1752-09-14</td>
* <td>CHRISTMAS_STYLE.until(1087)<br>.and(BEGIN_OF_JANUARY.until(1155))<br>.and(MARIA_ANUNCIATA.until(1752))</td><td></td>
* </tr>
* <tr>
* <td>Scotland</td><td>GB-SCT</td><td>1752-09-14</td>
* <td>CHRISTMAS_STYLE.until(1087)<br>.and(BEGIN_OF_JANUARY.until(1155))<br>.and(MARIA_ANUNCIATA.until(1600))</td><td></td>
* </tr>
* <tr>
* <td>Sweden <sup>2)</sup></td><td>SE</td><td>1753-03-01</td><td></td><td></td>
* </tr>
* <tr>
* <td>Russia <sup>3)</sup></td><td>RU</td><td>1918-02-14</td>
* <td>BEGIN_OF_JANUARY.until(988)<br>.and(BEGIN_OF_MARCH.until(1493))<br>.and(BEGIN_OF_SEPTEMBER.until(1700))</td>
* <td>BYZANTINE (988-1700)</td>
* </tr>
* </table>
* </div>
*
* <p><sup>1)</sup> Fallback for catholic regions in German countries, but most regions started Gregorian calendar later.
* <br><sup>2)</sup> Sweden used a special calendar shifted by one day between 1700 and 1712 (supported by Time4J).
* <br><sup>3)</sup> Special case for Byzantine year 7208: It lasted from AD-1699-09-01 until AD-1699-12-31 (Julian calendar). </p>
*
* @param locale country setting
* @return localized chronological history
* @since 3.0
* @see #ofGregorianReform(PlainDate)
*/
/*[deutsch]
* <p>Ermittelt die (meist genäherte) Geschichte der gregorianischen Kalenderreformen für die
* angegebene Region. </p>
*
* <p>Die aktuelle Implementierung fällt außer für die in der folgenden Tabelle erwähnten
* Regionen auf die erste Einführung des gregorianischen Kalenders durch Papst Gregor zurück.
* Spätere Releases von Time4J können diesen Ansatz für die meisten europäischen
* Länder verfeinern. Für jedes hier nicht unterstützte Umstellungsdatum können
* Anwender stattdessen {@code ofGregorianReform(PlainDate)} nutzen. <strong>Wie auch immer, Anwender
* sollten in Zweifelsfällen der spezifischen Modellierung der Kalendergeschichte gegenüber dieser
* Methode den Vorzug geben.</strong> Verschiedene Regionen in mehreren Ländern haben oft abweichende
* Regeln verwendet. Und manchmal sind historische Quellen einfach unzuverlässig. </p>
*
* <p>Diese Methode nutzt nicht den Sprach-, sondern den Länderteil des Arguments (ISO-3166),
* im Falle Schottlands auch den Variantenteil (SCT), im Falle Deutschlands die Varianten (PREUSSEN
* oder PROTESTANT usw.). Um eine passende {@code Locale} zu erzeugen, ist ein Konstruktor wie
* {@code new Locale("en", "GB", "SCT")} zu verwenden. </p>
*
* <div style="margin-top:5px;">
* <table border="1">
* <caption>Zusammenfassung</caption>
* <tr>
* <th>Region</th><th>Locale</th><th>Gregorianische Umstellung</th><th>Neujahr (Schätzung)</th><th>Bevorzugte Ära</th>
* </tr>
* <tr>
* <td>Italien</td><td>IT</td><td>1582-10-15</td><td>CHRISTMAS_STYLE.until(1583)</td><td></td>
* </tr>
* <tr>
* <td>Florenz</td><td>IT-FLORENCE</td><td>1582-10-15</td><td>MARIA_ANUNCIATA.until(1749)</td><td></td>
* </tr>
* <tr>
* <td>Pisa</td><td>IT-PISA</td><td>1582-10-15</td><td>CALCULUS_PISANUS.until(1749)</td><td></td>
* </tr>
* <tr>
* <td>Republik von Venedig</td><td>IT-VENICE</td><td>1582-10-15</td><td>BEGIN_OF_MARCH.until(1798)</td><td></td>
* </tr>
* <tr>
* <td>Spanien</td><td>ES</td><td>1582-10-15</td>
* <td>BEGIN_OF_JANUARY.until(1383)<br>.and(CHRISTMAS_STYLE.until(1556))</td>
* <td>HISPANIC (until 1383)</td>
* </tr>
* <tr>
* <td>Portugal</td><td>PT</td><td>1582-10-15</td>
* <td>BEGIN_OF_JANUARY.until(1422)<br>.and(CHRISTMAS_STYLE.until(1556))</td>
* <td>HISPANIC (until 1422)</td>
* </tr>
* <tr>
* <td>Polen</td><td>PL</td><td>1582-10-15</td><td></td><td></td>
* </tr>
* <tr>
* <td>Frankreich</td><td>FR</td><td>1582-12-20</td><td>EASTER_STYLE.until(1567)</td><td></td>
* </tr>
* <tr>
* <td>Holland</td><td>NL</td><td>1583-01-01</td><td></td><td></td>
* </tr>
* <tr>
* <td>Heiliges Römisches Reich <sup>1)</sup></td><td>DE</td><td>1582-10-15</td><td>CHRISTMAS_STYLE.until(1544)</td><td></td>
* </tr>
* <tr>
* <td>Bayern</td><td>DE-BAYERN</td><td>1583-10-16</td><td>CHRISTMAS_STYLE.until(1544)</td><td></td>
* </tr>
* <tr>
* <td>Preußen</td><td>DE-PREUSSEN</td><td>1610-09-02</td><td>CHRISTMAS_STYLE.until(1559)</td><td></td>
* </tr>
* <tr>
* <td>Deutschland (protestantisch)</td><td>DE-PROTESTANT</td><td>1700-03-01</td><td>CHRISTMAS_STYLE.until(1559)</td><td></td>
* </tr>
* <tr>
* <td>Österreich</td><td>AT</td><td>1584-01-17</td><td></td><td></td>
* </tr>
* <tr>
* <td>Schweiz</td><td>CH</td><td>1584-01-22</td><td></td><td></td>
* </tr>
* <tr>
* <td>Ungarn</td><td>HU</td><td>1587-11-01</td><td></td><td></td>
* </tr>
* <tr>
* <td>Dänemark</td><td>DK</td><td>1700-03-01</td><td>MARIA_ANUNCIATA.until(1623)</td><td></td>
* </tr>
* <tr>
* <td>Norwegen</td><td>NO</td><td>1700-03-01</td><td>MARIA_ANUNCIATA.until(1623)</td><td></td>
* </tr>
* <tr>
* <td>England</td><td>GB</td><td>1752-09-14</td>
* <td>CHRISTMAS_STYLE.until(1087)<br>.and(BEGIN_OF_JANUARY.until(1155))<br>.and(MARIA_ANUNCIATA.until(1752))</td><td></td>
* </tr>
* <tr>
* <td>Schottland</td><td>GB-SCT</td><td>1752-09-14</td>
* <td>CHRISTMAS_STYLE.until(1087)<br>.and(BEGIN_OF_JANUARY.until(1155))<br>.and(MARIA_ANUNCIATA.until(1600))</td><td></td>
* </tr>
* <tr>
* <td>Schweden <sup>2)</sup></td><td>SE</td><td>1753-03-01</td><td></td><td></td>
* </tr>
* <tr>
* <td>Rußland <sup>3)</sup></td><td>RU</td><td>1918-02-14</td>
* <td>BEGIN_OF_JANUARY.until(988)<br>.and(BEGIN_OF_MARCH.until(1493))<br>.and(BEGIN_OF_SEPTEMBER.until(1700))</td>
* <td>BYZANTINE (988-1700)</td>
* </tr>
* </table>
* </div>
*
* <p><sup>1)</sup> Rückfall für katholische Regionen in deutsch-sprachigen Ländern, aber meistens erfolgte die Umstellung später
* <br><sup>2)</sup> Schweden verwendete einen besonderen Kalender, der zwischen 1700 und 1712 um einen Tag vom julianischen Kalender abwich (von Time4J unterstützt).
* <br><sup>3)</sup> Spezieller Fall für das byzantinische Jahr 7208: Es dauerte von AD-1699-09-01 bis AD-1699-12-31 (julianischer Kalender). </p>
*
* @param locale country setting
* @return localized chronological history
* @since 3.0
* @see #ofGregorianReform(PlainDate)
*/
public static ChronoHistory of(Locale locale) {
String key = locale.getCountry();
ChronoHistory history = null;
if (!locale.getVariant().isEmpty()) {
key = key + "-" + locale.getVariant();
history = LOOKUP.get(key);
}
if (history == null) {
history = LOOKUP.get(key);
}
if (history == null) {
return ChronoHistory.ofFirstGregorianReform();
} else {
return history;
}
}
/**
* <p>The Swedish calendar has three cutover dates due to a failed experiment
* when switching to gregorian calendar in the years 1700-1712 step by step. </p>
*
* @return swedish chronological history
* @since 3.0
*/
/*[deutsch]
* <p>Der schwedische Kalender hat drei Umstellungszeitpunkte, weil ein Experiment in den
* Jahren 1700-1712 zur schrittweisen Einführung des gregorianischen Kalenders mißlang. </p>
*
* @return swedish chronological history
* @since 3.0
*/
public static ChronoHistory ofSweden() {
return SWEDEN;
}
/**
* <p>Is given historic date valid? </p>
*
* <p>If the argument is {@code null} then this method returns {@code false}. </p>
*
* @param date historic calendar date to be checked, maybe {@code null}
* @return {@code false} if given date is invalid else {@code true}
* @since 3.0
*/
/*[deutsch]
* <p>Ist das angegebene historische Datum gültig? </p>
*
* <p>Wenn das Argument {@code null} ist, liefert die Methode {@code false}. </p>
*
* @param date historic calendar date to be checked, maybe {@code null}
* @return {@code false} if given date is invalid else {@code true}
* @since 3.0
*/
public boolean isValid(HistoricDate date) {
if (date == null) {
return false;
}
Calculus algorithm = this.getAlgorithm(date);
return ((algorithm != null) && algorithm.isValid(date));
}
/**
* <p>Converts given historic date to an ISO-8601-date. </p>
*
* @param date historic calendar date
* @return ISO-8601-date (gregorian)
* @throws IllegalArgumentException if given date is invalid or out of supported range
* @see #isValid(HistoricDate)
* @since 3.0
*/
/*[deutsch]
* <p>Konvertiert das angegebene historische Datum zu einem ISO-8601-Datum. </p>
*
* @param date historic calendar date
* @return ISO-8601-date (gregorian)
* @throws IllegalArgumentException if given date is invalid or out of supported range
* @see #isValid(HistoricDate)
* @since 3.0
*/
public PlainDate convert(HistoricDate date) {
Calculus algorithm = this.getAlgorithm(date);
if (algorithm == null) {
throw new IllegalArgumentException("Invalid historic date: " + date);
}
return PlainDate.of(algorithm.toMJD(date), EpochDays.MODIFIED_JULIAN_DATE);
}
/**
* <p>Converts given ISO-8601-date to a historic date. </p>
*
* @param date ISO-8601-date (gregorian)
* @return historic calendar date
* @throws IllegalArgumentException if given date is out of supported range
* @since 3.0
*/
/*[deutsch]
* <p>Konvertiert das angegebene ISO-8601-Datum zu einem historischen Datum. </p>
*
* @param date ISO-8601-date (gregorian)
* @return historic calendar date
* @throws IllegalArgumentException if given date is out of supported range
* @since 3.0
*/
public HistoricDate convert(PlainDate date) {
long mjd = date.get(EpochDays.MODIFIED_JULIAN_DATE);
HistoricDate hd = null;
for (int i = this.events.size() - 1; i >= 0; i--) {
CutOverEvent event = this.events.get(i);
if (mjd >= event.start) {
hd = event.algorithm.fromMJD(mjd);
break;
}
}
if (hd == null) {
hd = this.getJulianAlgorithm().fromMJD(mjd);
}
HistoricEra era = this.eraPreference.getPreferredEra(hd, date);
if (era != hd.getEra()) {
int yoe = era.yearOfEra(hd.getEra(), hd.getYearOfEra());
hd = HistoricDate.of(era, yoe, hd.getMonth(), hd.getDayOfMonth());
}
return hd;
}
/**
* <p>Yields the variant of a historic calendar. </p>
*
* @return text describing the internal state
* @since 3.11/4.8
*/
/*[deutsch]
* <p>Liefert die Variante eines historischen Kalenders. </p>
*
* @return text describing the internal state
* @since 3.11/4.8
*/
@Override
public String getVariant() {
StringBuilder sb = new StringBuilder(64);
sb.append(this.variant.name());
switch (this.variant){
case PROLEPTIC_GREGORIAN:
case PROLEPTIC_JULIAN:
case PROLEPTIC_BYZANTINE:
sb.append(":no-cutover");
break;
case INTRODUCTION_ON_1582_10_15:
case SINGLE_CUTOVER_DATE:
sb.append(":cutover=");
sb.append(this.getGregorianCutOverDate());
// fall-through
default:
sb.append(":ancient-julian-leap-years=");
if (this.ajly != null) {
int[] pattern = this.ajly.getPattern();
sb.append('[');
sb.append(pattern[0]);
for (int i = 1; i < pattern.length; i++) {
sb.append(',');
sb.append(pattern[i]);
}
sb.append(']');
} else {
sb.append("[]");
}
sb.append(":new-year-strategy=");
sb.append(this.getNewYearStrategy());
sb.append(":era-preference=");
sb.append(this.getEraPreference());
}
return sb.toString();
}
/**
* <p>Yields the date of final introduction of gregorian calendar. </p>
*
* @return ISO-8601-date (gregorian)
* @throws UnsupportedOperationException if this history is proleptic
* @see #hasGregorianCutOverDate()
* @since 3.0
*/
/*[deutsch]
* <p>Liefert das Datum der letztlichen Einführung des gregorianischen Kalenders. </p>
*
* @return ISO-8601-date (gregorian)
* @throws UnsupportedOperationException if this history is proleptic
* @see #hasGregorianCutOverDate()
* @since 3.0
*/
public PlainDate getGregorianCutOverDate() {
long start = this.events.get(this.events.size() - 1).start;
if (start == Long.MIN_VALUE) {
throw new UnsupportedOperationException("Proleptic history without any gregorian reform date.");
}
return PlainDate.of(start, EpochDays.MODIFIED_JULIAN_DATE);
}
/**
* <p>Determines if this history has at least one gregorian calendar reform date. </p>
*
* @return boolean
* @since 3.11/4.8
* @see #getGregorianCutOverDate()
*/
/*[deutsch]
* <p>Ermittelt, ob diese {@code ChronoHistory} wenigstens eine gregorianische Kalenderreform kennt. </p>
*
* @return boolean
* @since 3.11/4.8
* @see #getGregorianCutOverDate()
*/
public boolean hasGregorianCutOverDate() {
return (this.events.get(this.events.size() - 1).start > Long.MIN_VALUE);
}
/**
* <p>Determines the date of New Year. </p>
*
* <p>Side note: This method should usually yield a valid historic date unless in case of ill
* configured new-year-strategies which don't play well with configured cutover-dates. </p>
*
* @param era historic era
* @param yearOfEra historic year of era as displayed (deviating from standard calendar year)
* @return historic date of New Year
* @throws IllegalArgumentException if given year is out of range or such that the result becomes invalid
* @see NewYearRule
* @see #getLengthOfYear(HistoricEra, int)
* @see #with(NewYearStrategy)
* @see #isValid(HistoricDate)
* @see HistoricDate#getYearOfEra(NewYearStrategy)
* @since 3.14/4.11
*/
/*[deutsch]
* <p>Determines the date of New Year. </p>
*
* <p>Hinweis: Diese Methode sollte normalerweise ein gültiges historisches Datum liefern,
* es sei denn, die Neujahrsstrategie wurde so konfiguriert, daß sie nicht mit den
* gewählten Kalenderumstellungsdaten harmoniert. </p>
*
* @param era historic era
* @param yearOfEra historic year of era as displayed (deviating from standard calendar year)
* @return historic date of New Year
* @throws IllegalArgumentException if given year is out of range or such that the result becomes invalid
* @see NewYearRule
* @see #getLengthOfYear(HistoricEra, int)
* @see #with(NewYearStrategy)
* @see #isValid(HistoricDate)
* @see HistoricDate#getYearOfEra(NewYearStrategy)
* @since 3.14/4.11
*/
public HistoricDate getBeginOfYear(
HistoricEra era,
int yearOfEra
) {
HistoricDate newYear = this.getNewYearStrategy().newYear(era, yearOfEra);
if (this.isValid(newYear)) {
PlainDate date = this.convert(newYear);
HistoricEra preferredEra = this.eraPreference.getPreferredEra(newYear, date);
if (preferredEra != era) {
int yoe = preferredEra.yearOfEra(newYear.getEra(), newYear.getYearOfEra());
newYear = HistoricDate.of(preferredEra, yoe, newYear.getMonth(), newYear.getDayOfMonth());
}
return newYear;
} else {
throw new IllegalArgumentException("Cannot determine valid New Year: " + era + "-" + yearOfEra);
}
}
/**
* <p>Determines the length of given historic year in days. </p>
*
* <p>The length of year can be affected by cutover gaps, different leap year rules and new year strategies. </p>
*
* @param era historic era
* @param yearOfEra historic year of era as displayed (deviating from standard calendar year)
* @return length of historic year in days or {@code -1} if the length cannot be determined
* @see #getBeginOfYear(HistoricEra, int)
* @see #getGregorianCutOverDate()
* @see #with(NewYearStrategy)
* @see #with(AncientJulianLeapYears)
* @see HistoricDate#getYearOfEra(NewYearStrategy)
* @since 3.0
*/
/*[deutsch]
* <p>Bestimmt die Länge des angegebenen historischen Jahres in Tagen. </p>
*
* <p>Die Länge des Jahres kann durch Kalenderumstellungen, verschiedene Schaltjahresregeln und
* Neujahrsstrategien beeinflusst werden. </p>
*
* @param era historic era
* @param yearOfEra historic year of era as displayed (deviating from standard calendar year)
* @return length of historic year in days or {@code -1} if the length cannot be determined
* @see #getBeginOfYear(HistoricEra, int)
* @see #getGregorianCutOverDate()
* @see #with(NewYearStrategy)
* @see #with(AncientJulianLeapYears)
* @see HistoricDate#getYearOfEra(NewYearStrategy)
* @since 3.0
*/
public int getLengthOfYear(
HistoricEra era,
int yearOfEra
) {
try {
HistoricDate min;
HistoricDate max;
int extra;
if (this.nys == null) {
min = HistoricDate.of(era, yearOfEra, 1, 1);
max = HistoricDate.of(era, yearOfEra, 12, 31);
extra = 1;
} else {
min = this.nys.newYear(era, yearOfEra);
if (era == HistoricEra.BC) {
if (yearOfEra == 1) {
max = this.nys.newYear(HistoricEra.AD, 1);
} else {
max = this.nys.newYear(era, yearOfEra - 1);
}
} else {
max = this.nys.newYear(era, yearOfEra + 1);
if (era == HistoricEra.BYZANTINE) {
HistoricDate hd = this.nys.newYear(HistoricEra.AD, era.annoDomini(yearOfEra));
if (hd.compareTo(min) > 0) {
max = hd;
}
}
}
extra = 0;
}
return (int) (CalendarUnit.DAYS.between(this.convert(min), this.convert(max)) + extra);
} catch (RuntimeException re) {
return -1; // only in very exotic circumstances (for example if given year is out of range)
}
}
/**
* <p>Yields the historic julian leap year pattern if available. </p>
*
* @return sequence of historic julian leap years
* @throws UnsupportedOperationException if this history has not defined any historic julian leap years
* @see #hasAncientJulianLeapYears()
* @see #with(AncientJulianLeapYears)
* @since 3.11/4.8
*/
/*[deutsch]
* <p>Liefert die Sequenz von historischen julianischen Schaltjahren, wenn verfügbar. </p>
*
* @return sequence of historic julian leap years
* @throws UnsupportedOperationException if this history has not defined any historic julian leap years
* @see #hasAncientJulianLeapYears()
* @see #with(AncientJulianLeapYears)
* @since 3.11/4.8
*/
public AncientJulianLeapYears getAncientJulianLeapYears() {
if (this.ajly == null){
throw new UnsupportedOperationException("No historic julian leap years were defined.");
}
return this.ajly;
}
/**
* <p>Determines if this history has defined any historic julian leap year sequence. </p>
*
* @return boolean
* @since 3.11/4.8
* @see #with(AncientJulianLeapYears)
* @see #getAncientJulianLeapYears()
*/
/*[deutsch]
* <p>Ermittelt, ob diese {@code ChronoHistory} eine spezielle Sequenz von historischen
* julianischen Schaltjahren kennt. </p>
*
* @return boolean
* @since 3.11/4.8
* @see #with(AncientJulianLeapYears)
* @see #getAncientJulianLeapYears()
*/
public boolean hasAncientJulianLeapYears() {
return (this.ajly != null);
}
/**
* <p>Creates a copy of this history with given historic julian leap year sequence. </p>
*
* <p>This method has no effect if applied on {@code ChronoHistory.PROLEPTIC_GREGORIAN}
* or {@code ChronoHistory.PROLEPTIC_JULIAN} or {@code ChronoHistory.PROLEPTIC_BYZANTINE}. </p>
*
* @param ancientJulianLeapYears sequence of historic julian leap years
* @return new history which starts at first of January in year BC 45
* @since 3.11/4.8
*/
/*[deutsch]
* <p>Erzeugt eine Kopie dieser Instanz mit den angegebenen historischen julianischen Schaltjahren. </p>
*
* <p>Diese Methode hat keine Auswirkung, wenn angewandt auf {@code ChronoHistory.PROLEPTIC_GREGORIAN}
* oder {@code ChronoHistory.PROLEPTIC_JULIAN} oder {@code ChronoHistory.PROLEPTIC_BYZANTINE}. </p>
*
* @param ancientJulianLeapYears sequence of historic julian leap years
* @return new history which starts at first of January in year BC 45
* @since 3.11/4.8
*/
public ChronoHistory with(AncientJulianLeapYears ancientJulianLeapYears) {
if (ancientJulianLeapYears == null) {
throw new NullPointerException("Missing ancient julian leap years.");
} else if (!this.hasGregorianCutOverDate()) {
return this;
}
return new ChronoHistory(this.variant, this.events, ancientJulianLeapYears, this.nys, this.eraPreference);
}
/**
* <p>Yields the new-year-strategy. </p>
*
* <p>If not specified then this method falls back to first of January. </p>
*
* @return NewYearStrategy
* @see #with(NewYearStrategy)
* @since 3.14/4.11
*/
/*[deutsch]
* <p>Liefert die Neujahrsstrategie. </p>
*
* <p>Wenn nicht explizit festgelegt, wird diese Methode eine Regel basierend
* auf dem ersten Januar liefern. </p>
*
* @return NewYearStrategy
* @see #with(NewYearStrategy)
* @since 3.14/4.11
*/
public NewYearStrategy getNewYearStrategy() {
return ((this.nys == null) ? NewYearStrategy.DEFAULT : this.nys);
}
/**
* <p>Creates a copy of this history with given new-year-strategy. </p>
*
* <p>This method has no effect if applied on {@code ChronoHistory.PROLEPTIC_GREGORIAN}
* or {@code ChronoHistory.PROLEPTIC_JULIAN} or {@code ChronoHistory.PROLEPTIC_BYZANTINE}. </p>
*
* @param newYearStrategy strategy how to determine New Year
* @return new history with changed new-year-strategy
* @see #getBeginOfYear(HistoricEra, int)
* @see #getLengthOfYear(HistoricEra, int)
* @see #getNewYearStrategy()
* @since 3.14/4.11
*/
/*[deutsch]
* <p>Erzeugt eine Kopie dieser Instanz mit der angegebenen Neujahrsstrategie. </p>
*
* <p>Diese Methode hat keine Auswirkung, wenn angewandt auf {@code ChronoHistory.PROLEPTIC_GREGORIAN}
* oder {@code ChronoHistory.PROLEPTIC_JULIAN} oder {@code ChronoHistory.PROLEPTIC_BYZANTINE}. </p>
*
* @param newYearStrategy strategy how to determine New Year
* @return new history with changed new-year-strategy
* @see #getBeginOfYear(HistoricEra, int)
* @see #getLengthOfYear(HistoricEra, int)
* @see #getNewYearStrategy()
* @since 3.14/4.11
*/
public ChronoHistory with(NewYearStrategy newYearStrategy) {
if (newYearStrategy.equals(NewYearStrategy.DEFAULT)) {
if (this.nys == null) {
return this;
} else {
return new ChronoHistory(this.variant, this.events, this.ajly, null, this.eraPreference);
}
} else if (!this.hasGregorianCutOverDate()) {
return this;
}
return new ChronoHistory(this.variant, this.events, this.ajly, newYearStrategy, this.eraPreference);
}
/**
* <p>Creates a copy of this history with given era preference. </p>
*
* <p>This method has no effect if applied on {@code ChronoHistory.PROLEPTIC_GREGORIAN}
* or {@code ChronoHistory.PROLEPTIC_JULIAN} or {@code ChronoHistory.PROLEPTIC_BYZANTINE}. </p>
*
* <p>Background: Some historic calendars used different eras than AD or BC. For example, Russia
* used the Byzantine calendar before Julian year 1700 using the era Anno Mundi. This can be
* expressed by setting a suitable era preference. </p>
*
* @param eraPreference strategy which era should be preferred (most relevant for printing)
* @return new history with changed era preference
* @since 3.14/4.11
*/
/*[deutsch]
* <p>Erzeugt eine Kopie dieser Instanz mit der angegebenen Ärapräferenz. </p>
*
* <p>Diese Methode hat keine Auswirkung, wenn angewandt auf {@code ChronoHistory.PROLEPTIC_GREGORIAN}
* oder {@code ChronoHistory.PROLEPTIC_JULIAN} oder {@code ChronoHistory.PROLEPTIC_BYZANTINE}. </p>
*
* <p>Hintergrund: Einige historische Kalender haben andere Äras als AD oder BC verwendet. Zum
* Beispiel kannte Rußland vor dem julianischen Jahr 1700 den byzantinischen Kalender, der die
* Ära Anno Mundi benötigt. Dies kann mit Hilfe einer geeigneten Ärapräferenz
* ausgedrückt werden. </p>
*
* @param eraPreference strategy which era should be preferred (most relevant for printing)
* @return new history with changed era preference
* @since 3.14/4.11
*/
public ChronoHistory with(EraPreference eraPreference) {
if (eraPreference.equals(this.eraPreference) || !this.hasGregorianCutOverDate()) {
return this;
}
return new ChronoHistory(this.variant, this.events, this.ajly, this.nys, eraPreference);
}
/**
* <p>Defines the element for the historic date. </p>
*
* <p>This element is applicable on all chronological types which have registered the element
* {@link PlainDate#COMPONENT}. </p>
*
* @return date-related element
* @since 3.15/4.12
* @see PlainDate
* @see net.time4j.PlainTimestamp
*/
/*[deutsch]
* <p>Definiert das Element für das historische Datum. </p>
*
* <p>Dieses Element ist auf alle chronologischen Typen anwendbar, die das Element
* {@link PlainDate#COMPONENT} registriert haben. </p>
*
* @return date-related element
* @since 3.15/4.12
* @see PlainDate
* @see net.time4j.PlainTimestamp
*/
public ChronoElement<HistoricDate> date() {
return this.dateElement;
}
/**
* <p>Defines the element for the historic era. </p>
*
* <p>This element is applicable on all chronological types which have registered the element
* {@link PlainDate#COMPONENT}. The era value cannot be changed in any way which makes sense
* so this element is like a display-only element. </p>
*
* @return era-related element
* @since 3.0
* @see PlainDate
* @see net.time4j.PlainTimestamp
*/
/*[deutsch]
* <p>Definiert das Element für die historische Ära. </p>
*
* <p>Dieses Element ist auf alle chronologischen Typen anwendbar, die das Element
* {@link PlainDate#COMPONENT} registriert haben. Der Ära-Wert kann nicht auf eine
* sinnvolle Weise geändert werden, so daß sich dieses Element wie ein reines
* Anzeigeelement verhält. </p>
*
* @return era-related element
* @since 3.0
* @see PlainDate
* @see net.time4j.PlainTimestamp
*/
@FormattableElement(format = "G")
public ChronoElement<HistoricEra> era() {
return this.eraElement;
}
/**
* <p>Defines the element for the year of a given historic era. </p>
*
* <p>This element is applicable on all chronological types which have registered the element
* {@link PlainDate#COMPONENT}. The year starts on first of January. Note: Getting true historic
* years which take care of different new-year-rules is possible via the expression
* {@code plainDate.get(history.date()).getYearOfEra(history.getNewYearStrategy())}. </p>
*
* @return year-of-era-related element
* @since 3.0
* @see PlainDate
* @see net.time4j.PlainTimestamp
* @see #yearOfEra(YearDefinition)
*/
/*[deutsch]
* <p>Definiert das Element für das Jahr einer historischen Ära. </p>
*
* <p>Dieses Element ist auf alle chronologischen Typen anwendbar, die das Element {@link PlainDate#COMPONENT}
* registriert haben. Das Jahr beginnt am ersten Januar. Hinweis: Um wahre historische Jahre zu erhalten, die
* sich um verschiedene Neujahrsregeln kümmern, kann folgender Ausdruck verwendet werden:
* {@code plainDate.get(history.date()).getYearOfEra(history.getNewYearStrategy())}. </p>
*
* @return year-of-era-related element
* @since 3.0
* @see PlainDate
* @see net.time4j.PlainTimestamp
* @see #yearOfEra(YearDefinition)
*/
@FormattableElement(format = "y")
public TextElement<Integer> yearOfEra() {
return this.yearOfEraElement;
}
/**
* <p>Defines the element for the year of a given historic era. </p>
*
* <p>This element is applicable on all chronological types which have registered the element
* {@link PlainDate#COMPONENT}. </p>
*
* <p>Example: Many parts of France used Easter style for New Year so the year 1564 began on 1564-04-01 and
* ended on 1565-04-21 causing an ambivalency for April dates. Setting the date to April 10th in historic
* year 1564 can be expressed in a non-ambivalent way if a suitable year definition is applied (standard
* calendar years 1564 or 1565 possible). </p>
*
* <pre>
* ChronoHistory history = ChronoHistory.of(Locale.FRANCE);
* PlainDate date = history.convert(HistoricDate.of(HistoricEra.AD, 1563, 4, 10));
* assertThat(
* date.with(history.yearOfEra(YearDefinition.AFTER_NEW_YEAR), 1564),
* is(history.convert(HistoricDate.of(HistoricEra.AD, 1564, 4, 10))));
* assertThat(
* date.with(history.yearOfEra(YearDefinition.BEFORE_NEW_YEAR), 1564),
* is(history.convert(HistoricDate.of(HistoricEra.AD, 1565, 4, 10))));
* </pre>
*
* @param yearDefinition determines how to display or interprete a historic year
* @return year-of-era-related element
* @since 3.19/4.15
* @see PlainDate
* @see net.time4j.PlainTimestamp
*/
/*[deutsch]
* <p>Definiert das Element für das Jahr einer historischen Ära. </p>
*
* <p>Dieses Element ist auf alle chronologischen Typen anwendbar, die das Element {@link PlainDate#COMPONENT}
* registriert haben. </p>
*
* <p>Beispiel: Große Teile von Frankreich haben den Osterstil als Neujahrsregel verwendet, so daß
* das Jahr 1564 zum Datum 1564-04-01 begann und zum Datum 1565-04-21 endete. Hier gibt es keine eindeutige
* Zuordnung von Datumsangaben im April. Wenn das Datum etwa auf den 10. April 1564 gesetzt werden soll,
* kann das eindeutig mit Hilfe einer spezifischen Jahresdefinition ausgedrückt werden (Standardkalenderjahre
* 1564 oder 1565 möglich). </p>
*
* <pre>
* ChronoHistory history = ChronoHistory.of(Locale.FRANCE);
* PlainDate date = history.convert(HistoricDate.of(HistoricEra.AD, 1563, 4, 10));
* assertThat(
* date.with(history.yearOfEra(YearDefinition.AFTER_NEW_YEAR), 1564),
* is(history.convert(HistoricDate.of(HistoricEra.AD, 1564, 4, 10))));
* assertThat(
* date.with(history.yearOfEra(YearDefinition.BEFORE_NEW_YEAR), 1564),
* is(history.convert(HistoricDate.of(HistoricEra.AD, 1565, 4, 10))));
* </pre>
*
* @param yearDefinition determines how to display or interprete a historic year
* @return year-of-era-related element
* @since 3.19/4.15
* @see PlainDate
* @see net.time4j.PlainTimestamp
*/
public ChronoElement<Integer> yearOfEra(YearDefinition yearDefinition) {
switch (yearDefinition) {
case DUAL_DATING:
return this.yearOfEraElement;
case AFTER_NEW_YEAR:
return this.yearAfterElement;
case BEFORE_NEW_YEAR:
return this.yearBeforeElement;
default:
throw new UnsupportedOperationException(yearDefinition.name());
}
}
/**
* <p>Defines the element for the century of a year in a given historic era. </p>
*
* <p>This element is applicable on all chronological types which have registered the element
* {@link PlainDate#COMPONENT}. The underlying year starts on first of January. As example,
* the 20th century lasted from year 1901 to year 2000. </p>
*
* @return century-of-era-related element
* @since 3.23/4.19
* @see PlainDate
* @see net.time4j.PlainTimestamp
* @see #yearOfEra()
*/
/*[deutsch]
* <p>Definiert das Element für das Jahrhundert einer historischen Ära. </p>
*
* <p>Dieses Element ist auf alle chronologischen Typen anwendbar, die das Element {@link PlainDate#COMPONENT}
* registriert haben. Das Jahr beginnt am ersten Januar. Zum Beispiel dauerte das 20. Jahrhundert vom Jahr
* 1901 bis zum Jahr 2000. </p>
*
* @return century-of-era-related element
* @since 3.23/4.19
* @see PlainDate
* @see net.time4j.PlainTimestamp
* @see #yearOfEra()
*/
public ChronoElement<Integer> centuryOfEra() {
return this.centuryElement;
}
/**
* <p>Defines the element for the historic month. </p>
*
* <p>This element is applicable on all chronological types which have registered the element
* {@link PlainDate#COMPONENT}. </p>
*
* @return month-related element
* @since 3.0
* @see PlainDate
* @see net.time4j.PlainTimestamp
*/
/*[deutsch]
* <p>Definiert das Element für den historischen Monat. </p>
*
* <p>Dieses Element ist auf alle chronologischen Typen anwendbar, die das Element
* {@link PlainDate#COMPONENT} registriert haben. </p>
*
* @return month-related element
* @since 3.0
* @see PlainDate
* @see net.time4j.PlainTimestamp
*/
@FormattableElement(format = "M")
public TextElement<Integer> month() {
return this.monthElement;
}
/**
* <p>Defines the element for the historic day of month. </p>
*
* <p>This element is applicable on all chronological types which have registered the element
* {@link PlainDate#COMPONENT}. </p>
*
* @return day-of-month-related element
* @since 3.0
* @see PlainDate
* @see net.time4j.PlainTimestamp
*/
/*[deutsch]
* <p>Definiert das Element für den historischen Tag des Monats. </p>
*
* <p>Dieses Element ist auf alle chronologischen Typen anwendbar, die das Element
* {@link PlainDate#COMPONENT} registriert haben. </p>
*
* @return day-of-month-related element
* @since 3.0
* @see PlainDate
* @see net.time4j.PlainTimestamp
*/
@FormattableElement(format = "d")
public TextElement<Integer> dayOfMonth() {
return this.dayOfMonthElement;
}
/**
* <p>Defines the element for the historic day of year. </p>
*
* <p>This element is applicable on all chronological types which have registered the element
* {@link PlainDate#COMPONENT}. Note: An eventually deviating new-year-strategy will be taken
* into account so this element follows the year cycle which is decoupled from the month cycle. </p>
*
* @return day-of-year-related element
* @since 3.16/4.13
* @see PlainDate
* @see net.time4j.PlainTimestamp
*/
/*[deutsch]
* <p>Definiert das Element für den historischen Tag des Jahres. </p>
*
* <p>Dieses Element ist auf alle chronologischen Typen anwendbar, die das Element
* {@link PlainDate#COMPONENT} registriert haben. Hinweis: Eine eventuell abweichende
* Neujahrsregel wird berücksichtigt, so daß dieses Element dem vom Monatszyklus
* entkoppelten Jahreszyklus folgt. </p>
*
* @return day-of-year-related element
* @since 3.16/4.13
* @see PlainDate
* @see net.time4j.PlainTimestamp
*/
@FormattableElement(format = "D")
public TextElement<Integer> dayOfYear() {
return this.dayOfYearElement;
}
/**
* <p>Yields all associated elements. </p>
*
* @return unmodifiable set of historic elements
* @since 3.1
*/
/*[deutsch]
* <p>Liefert alle zugehörigen historischen Elemente. </p>
*
* @return unmodifiable set of historic elements
* @since 3.1
*/
public Set<ChronoElement<?>> getElements() {
return this.elements;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (obj instanceof ChronoHistory) {
ChronoHistory that = (ChronoHistory) obj;
if (this.variant == that.variant) {
if (
isEqual(this.ajly, that.ajly)
&& isEqual(this.nys, that.nys)
&& this.eraPreference.equals(that.eraPreference)
) {
return (
(this.variant != HistoricVariant.SINGLE_CUTOVER_DATE)
|| (this.events.get(0).start == that.events.get(0).start)
);
}
}
}
return false;
}
@Override
public int hashCode() {
if (this.variant == HistoricVariant.SINGLE_CUTOVER_DATE) {
long h = this.events.get(0).start;
return (int) (h ^ (h << 32));
}
return this.variant.hashCode();
}
public String toString() {
return "ChronoHistory[" + this.getVariant() + "]";
}
/**
* <p>Yields the proper calendar algorithm. </p>
*
* @param date historic date
* @return appropriate calendar algorithm or {@code null} if not found (in case of an invalid date)
* @since 3.0
*/
Calculus getAlgorithm(HistoricDate date) {
for (int i = this.events.size() - 1; i >= 0; i--) {
CutOverEvent event = this.events.get(i);
if (date.compareTo(event.dateAtCutOver) >= 0) {
return event.algorithm;
} else if (date.compareTo(event.dateBeforeCutOver) > 0) {
return null; // gap at cutover
}
}
return this.getJulianAlgorithm();
}
/**
* <p>Adjusts given historic date with respect to actual maximum of day-of-month if necessary. </p>
*
* @param date historic date
* @return adjusted date
* @throws IllegalArgumentException if argument is out of range
* @since 3.0
*/
HistoricDate adjustDayOfMonth(HistoricDate date) {
Calculus algorithm = this.getAlgorithm(date);
if (algorithm == null) {
return date; // gap at cutover => let it be unchanged
}
int max = algorithm.getMaximumDayOfMonth(date);
if (max < date.getDayOfMonth()) {
return HistoricDate.of(date.getEra(), date.getYearOfEra(), date.getMonth(), max);
} else {
return date;
}
}
/**
* <p>Returns the list of all associated cutover events. </p>
*
* @return unmodifiable list
* @since 3.0
*/
List<CutOverEvent> getEvents() {
return this.events;
}
/**
* Used in serialization.
*
* @return enum
*/
HistoricVariant getHistoricVariant() {
return this.variant;
}
/**
* Used in serialization.
*
* @return era preference
*/
EraPreference getEraPreference() {
return this.eraPreference;
}
private Calculus getJulianAlgorithm() {
if (this.ajly != null) {
return this.ajly.getCalculus();
}
return CalendarAlgorithm.JULIAN;
}
private static boolean isEqual(
Object a1,
Object a2
) {
return ((a1 == null) ? (a2 == null) : a1.equals(a2));
}
private static void check(long mjd) {
if (mjd < EARLIEST_CUTOVER) {
throw new IllegalArgumentException("Gregorian calendar did not exist before 1582-10-15");
}
}
private static ChronoHistory ofGregorianReform(long mjd) {
return new ChronoHistory(
((mjd == EARLIEST_CUTOVER)
? HistoricVariant.INTRODUCTION_ON_1582_10_15
: HistoricVariant.SINGLE_CUTOVER_DATE),
Collections.singletonList(
new CutOverEvent(mjd, CalendarAlgorithm.JULIAN, CalendarAlgorithm.GREGORIAN)));
}
/**
* @serialData Uses <a href="../../../serialized-form.html#net.time4j.history.SPX">
* a dedicated serialization form</a> as proxy. The format is bit-compressed.
* The first byte contains in the four most significant bits the type-ID {@code 3}.
* The following bits 4-7 contain the variant of history. The variant is usually
* zero, but for PROLEPTIC_GREGORIAN 1, for PROLEPTIC_JULIAN 2, for PROLEPTIC_BYZANTINE 3,
* for SWEDEN 4 and for the first gregorian reform 7. If the variant is zero then the
* cutover date in question will be written as long (modified julian date) into the
* stream. Then the sequence of eventually set ancient julian leap years will be
* written as int-array (length followed by list of extended years). Then the specific
* new-year-strategy will be written as count of list of rule-until-pairs followed by
* the list entries. Finally the era preference will be written such that non-default
* preferences require the byte 127 and else any other number. If non-default then
* the preferred era and the start date and the end date will be written.
*
* @return replacement object in serialization graph
*/
private Object writeReplace() {
return new SPX(this, SPX.VERSION_3);
}
/**
* @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.");
}
}