net.time4j.engine.CalendarVariant Maven / Gradle / Ivy
/*
* -----------------------------------------------------------------------
* Copyright © 2013-2016 Meno Hochschild,
* -----------------------------------------------------------------------
* This file (CalendarVariant.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 .
* -----------------------------------------------------------------------
*/
package net.time4j.engine;
import java.io.Serializable;
/**
* Represents an immutable calendar variant.
*
* Display and change chronological element values
*
* The calendar variant consists of chronological elements. This base class
* delegates the element and time arithmetic to the associated calendar family respective to
* the underlying rules of elements and units. However, any concrete subclass
* is required to define the state and reflect it in all {@code get()}-methods
* and also to specify the serialization behaviour.
*
* Element values can only be changed by creating a new immutable copy
* of the original instance. This is done via all {@code with()}-methods.
*
* Calendar system
*
* Every calendar variant is a member of a calendar family. That means referring to
* a calendar system via a variant name. Hence a limited day arithmetic using the
* class {@code CalendarDays} is always possible.
*
* Sorting
*
* The sorting algorithm prefers the temporal order then the lexicographical comparison
* based on variant names. In case of doubt the documentation of the subclass is leading.
* Alternatively, the interface {@code Temporal} can be used to enable a pure temporal order.
*
* Implementation notes
*
*
* - All subclasses must be final und immutable.
* - Documentation of supported and registered elements is required.
* - The natural order should be consistent with {@code equals()}.
*
*
* @param generic type of self reference
* @author Meno Hochschild
* @serial exclude
* @since 3.4/4.3
* @see Chronology
* @see CalendarFamily
*/
/*[deutsch]
* Repräsentiert eine unveränderlichen Kalendervariante.
*
* Chronologische Elementwerte anzeigen und ändern
*
* Der Zeitwert setzt sich aus chronologischen Elementen zusammen. Diese
* abstrakte Basisklasse delegiert die Zeitrechnung immer an die zugehörige
* Kalenderfamilie bzw. genauer an die ihr zugeordneten Regeln der Elemente, muß aber
* selbst den Zustand definieren, in den {@code get()}-Methoden den Zustand reflektieren und
* auch das Serialisierungsverhalten festlegen.
*
* Da alle konkreten Implementierungen immutable sind und sein
* müssen, sind Elementwerte nur dadurch änderbar, daß jeweils
* eine neue Instanz mit geänderten Elementwerten erzeugt wird. Das wird
* unter anderem von allen {@code with()}-Methoden geleistet.
*
* Kalendersystem
*
* Jede Kalendervariante gehört zu einer Kalenderfamilie. Das schließt den
* den Bezug zu einem Kalendersystem mit Hilfe eines Variantennamens ein. Daher ist eine
* begrenzte Zeitarithmetick auf Tageseinheiten basierend immer möglich.
*
* Sortierung
*
* Die Sortierung von Kalendervarianten wird die zeitliche Ordnung bevorzugen
* und dann die lexikalische Ordnung von Variantennamen. Im Zweifelsfall ist die Dokumentation der
* konkreten Subklasse maßgeblich. Alternativ kann auch das Interface {@code Temporal}
* verwendet werden, um eine rein zeitliche Ordnung zu ermöglichen.
*
* Implementierungshinweise
*
*
* - Alle Subklassen müssen final und immutable sein.
* - Es muß dokumentiert werden, welche chronologischen Elemente
* unterstützt werden bzw. registriert sind.
* - Die natürliche Ordnung sollte konsistent mit {@code equals()}
* sein.
*
*
* @param generic type of self reference
* @author Meno Hochschild
* @serial exclude
* @since 3.4/4.3
* @see Chronology
* @see CalendarFamily
*/
public abstract class CalendarVariant>
extends ChronoEntity
implements CalendarDate, Comparable,Serializable {
//~ Methoden ----------------------------------------------------------
/**
* Returns the name of the associated variant of underlying calendar system.
*
* @return String
* @since 3.4/4.3
* @see CalendarFamily#getCalendarSystem(String)
*/
/*[deutsch]
* Liefert den Namen der assoziierten Variante des zugrundeliegenden Kalendersystems.
*
* @return String
* @since 3.4/4.3
* @see CalendarFamily#getCalendarSystem(String)
*/
public abstract String getVariant();
/**
* Creates a copy of this instance with given variant.
*
* If given variant is equal to the variant of this instance
* then the method will just return this instance.
*
* @param variant name of new variant
* @return copy of this instance with equal epoch-day-value but different variant
* @since 3.14/4.11
*/
/*[deutsch]
* Erzeugt eine Kopie dieser Instanz mit der angegebenen Variante.
*
* Wenn die angegebene Variante der Variante dieser Instanz gleicht, wird die
* Methode einfach nur diese Instanz zurückgeben.
*
* @param variant name of new variant
* @return copy of this instance with equal epoch-day-value but different variant
* @since 3.14/4.11
*/
public D withVariant(String variant) {
if (variant.equals(this.getVariant())) { // NPE-check
return this.getContext();
}
return this.transform(this.getChronology().getChronoType(), variant);
}
/**
* Equivalent to {@link #withVariant(String) withVariant(variantSource.getVariant())}.
*
* @param variantSource source of desired calendar variant
* @return copy of this instance with equal epoch-day-value but different variant
* @since 3.14/4.11
*/
/*[deutsch]
* Äquivalent zu {@link #withVariant(String) withVariant(variantSource.getVariant())}.
*
* @param variantSource source of desired calendar variant
* @return copy of this instance with equal epoch-day-value but different variant
* @since 3.14/4.11
*/
public D withVariant(VariantSource variantSource) {
return this.withVariant(variantSource.getVariant());
}
/**
* Converts this calendar date to the given target type based on
* the count of days relative to UTC epoch [1972-01-01].
*
* The conversion occurs on the local timeline at noon. This
* reference time ensures that all date types remain convertible
* even if a calendar system defines dates not starting at midnight.
*
* @param generic target date type
* @param target chronological type this date shall be converted to
* @return converted date of target type T
* @throws IllegalArgumentException if the target class does not
* have any chronology
* @throws ArithmeticException in case of numerical overflow
* @since 3.5/4.3
*/
/*[deutsch]
* Konvertiert dieses Datum zum angegebenen Zieltyp auf Basis der
* Anzahl der Tage relativ zur UTC-Epoche [1972-01-01].
*
* Die Konversion findet auf dem lokalen Zeitstrahl um 12 Uhr mittags
* als angenommener Referenzzeit statt. Diese Referenzzeit stellt sicher,
* daß alle Datumstypen konvertierbar bleiben, auch wenn in einem
* Kalendersystem ein Tag nicht um Mitternacht startet.
*
* @param generic target date type
* @param target chronological type this date shall be converted to
* @return converted date of target type T
* @throws IllegalArgumentException if the target class does not
* have any chronology
* @throws ArithmeticException in case of numerical overflow
* @since 3.5/4.3
*/
public > T transform(Class target) {
String ref = target.getName();
Chronology chronology = Chronology.lookup(target);
if (chronology == null) {
// kommt normal nie vor, weil sich jede Chrono selbst registriert
throw new IllegalArgumentException(
"Cannot find any chronology for given target type: " + ref);
}
return this.transform(chronology.getCalendarSystem(), ref);
}
/**
* Converts this calendar date to the given target type based on
* the count of days relative to UTC epoch [1972-01-01].
*
* The conversion occurs on the local timeline at noon. This
* reference time ensures that all date types remain convertible
* even if a calendar system defines dates not starting at midnight.
*
* @param generic target date type
* @param target chronological type this date shall be converted to
* @param variant desired calendar variant
* @return converted date of target type T
* @throws ChronoException if given variant is not recognized
* @throws IllegalArgumentException if the target class does not have any chronology
* @throws ArithmeticException in case of numerical overflow
* @since 3.5/4.3
*/
/*[deutsch]
* Konvertiert dieses Datum zum angegebenen Zieltyp auf Basis der
* Anzahl der Tage relativ zur UTC-Epoche [1972-01-01].
*
* Die Konversion findet auf dem lokalen Zeitstrahl um 12 Uhr mittags
* als angenommener Referenzzeit statt. Diese Referenzzeit stellt sicher,
* daß alle Datumstypen konvertierbar bleiben, auch wenn in einem
* Kalendersystem ein Tag nicht um Mitternacht startet.
*
* @param generic target date type
* @param target chronological type this date shall be converted to
* @param variant desired calendar variant
* @return converted date of target type T
* @throws ChronoException if given variant is not recognized
* @throws IllegalArgumentException if the target class does not have any chronology
* @throws ArithmeticException in case of numerical overflow
* @since 3.5/4.3
*/
public > T transform(
Class target,
String variant
) {
String ref = target.getName();
Chronology chronology = Chronology.lookup(target);
if (chronology == null) {
// kommt normal nie vor, weil sich jede Chrono selbst registriert
throw new IllegalArgumentException(
"Cannot find any chronology for given target type: " + ref);
}
return this.transform(chronology.getCalendarSystem(variant), ref);
}
/**
* Converts this calendar date to the given target type based on
* the count of days relative to UTC epoch [1972-01-01].
*
* The conversion occurs on the local timeline at noon. This
* reference time ensures that all date types remain convertible
* even if a calendar system defines dates not starting at midnight.
*
* @param generic target date type
* @param target chronological type this date shall be converted to
* @param variantSource source of desired calendar variant
* @return converted date of target type T
* @throws ChronoException if given variant is not recognized
* @throws IllegalArgumentException if the target class does not have any chronology
* @throws ArithmeticException in case of numerical overflow
* @since 3.6/4.4
*/
/*[deutsch]
* Konvertiert dieses Datum zum angegebenen Zieltyp auf Basis der
* Anzahl der Tage relativ zur UTC-Epoche [1972-01-01].
*
* Die Konversion findet auf dem lokalen Zeitstrahl um 12 Uhr mittags
* als angenommener Referenzzeit statt. Diese Referenzzeit stellt sicher,
* daß alle Datumstypen konvertierbar bleiben, auch wenn in einem
* Kalendersystem ein Tag nicht um Mitternacht startet.
*
* @param generic target date type
* @param target chronological type this date shall be converted to
* @param variantSource source of desired calendar variant
* @return converted date of target type T
* @throws ChronoException if given variant is not recognized
* @throws IllegalArgumentException if the target class does not have any chronology
* @throws ArithmeticException in case of numerical overflow
* @since 3.6/4.4
*/
public > T transform(
Class target,
VariantSource variantSource
) {
return this.transform(target, variantSource.getVariant());
}
/**
* Compares two calendar variants preferably by their temporal positions
* on the common date axis and then by their variant names.
*
* Implementation note: In order to make the natural order consistent
* with {@code equals()} the whole state must be taken into account,
* with preference for those attributes which define the temporal
* position on the time axis.
*
* @param calendarVariant the object to be compared.
* @return a negative integer, zero, or a positive integer as this object
* is less than, equal to, or greater than the specified object.
* @see #equals(Object)
*/
/*[deutsch]
* Vergleicht zwei Kalendervarianten bevorzugt nach ihrer Position auf der
* gemeinsamen Zeitachse und dann lexikalisch nach ihren Variantennamen.
*
* Implementierungshinweis: Damit die natürliche Ordnung konsistent
* mit {@code equals()} ist, müssen zum Vergleich alle internen
* Zustandsattribute herangezogen werden, bevorzugt aber die Attribute,
* die die zeitliche Position festlegen.
*
* @param calendarVariant the object to be compared.
* @return a negative integer, zero, or a positive integer as this object
* is less than, equal to, or greater than the specified object.
* @see #equals(Object)
*/
@Override
public int compareTo(D calendarVariant) {
long t1 = this.getDaysSinceEpochUTC();
long t2 = calendarVariant.getDaysSinceEpochUTC();
if (t1 < t2) {
return - 1;
} else if (t1 > t2) {
return 1;
} else {
return this.getVariant().compareTo(calendarVariant.getVariant());
}
}
@Override
public boolean isAfter(CalendarDate other) {
long t1 = this.getDaysSinceEpochUTC();
long t2 = other.getDaysSinceEpochUTC();
return (t1 > t2);
}
@Override
public boolean isBefore(CalendarDate other) {
long t1 = this.getDaysSinceEpochUTC();
long t2 = other.getDaysSinceEpochUTC();
return (t1 < t2);
}
@Override
public boolean isSimultaneous(CalendarDate other) {
long t1 = this.getDaysSinceEpochUTC();
long t2 = other.getDaysSinceEpochUTC();
return (t1 == t2);
}
/**
* Adds given calendar days to this instance.
*
* @param days calendar days to be added
* @return result of addition
* @throws ArithmeticException in case of numerical overflow
* @since 3.4/4.3
*/
/*[deutsch]
* Addiert die angegebenen Kalendertage zu dieser Instanz.
*
* @param days calendar days to be added
* @return result of addition
* @throws ArithmeticException in case of numerical overflow
* @since 3.4/4.3
*/
public D plus(CalendarDays days) {
long result = Math.addExact(this.getDaysSinceEpochUTC(), days.getAmount());
try {
return this.getCalendarSystem().transform(result);
} catch (IllegalArgumentException iae) {
ArithmeticException ex = new ArithmeticException("Out of range: " + result);
ex.initCause(iae);
throw ex;
}
}
/**
* Subtracts given calendar days from this instance.
*
* @param days calendar days to be subtracted
* @return result of subtraction
* @throws ArithmeticException in case of numerical overflow
* @since 3.4/4.3
*/
/*[deutsch]
* Subtrahiert die angegebenen Kalendertage von dieser Instanz.
*
* @param days calendar days to be subtracted
* @return result of subtraction
* @throws ArithmeticException in case of numerical overflow
* @since 3.4/4.3
*/
public D minus(CalendarDays days) {
return this.plus(CalendarDays.of(Math.negateExact(days.getAmount())));
}
/**
* Compares the whole state of this instance with given object.
*
* Implementations will usually define their state based on the temporal position
* and the variant name. Exceptions from this rule should be explicitly documented and reasoned.
*
* @see #compareTo(CalendarVariant)
*/
/*[deutsch]
* Vergleicht den gesamten Zustand dieser Instanz mit dem des angegebenen Objekts.
*
* Implementierungen werden üblicherweise ihren Zustand auf Basis der zeitlichen Position
* und der Variantennamen definieren, da dies am ehesten der Erwartungshaltung der Anwender entspricht.
* Ausnahmen sind explizit zu dokumentieren und zu begründen.
*
* @see #compareTo(TimePoint)
*/
@Override
public abstract boolean equals(Object obj);
/**
* Subclasses must redefine this method corresponding to the
* behaviour of {@code equals()}.
*/
/*[deutsch]
* Subklassen müssen diese Methode passend zum Verhalten
* von {@code equals()} redefinieren.
*/
@Override
public abstract int hashCode();
/**
* Provides a complete textual representation of the state of this calendar variant.
*/
/*[deutsch]
* Liefert eine vollständige Beschreibung des Zustands dieser Kalendervariante.
*/
@Override
public abstract String toString();
@Override
public long getDaysSinceEpochUTC() {
return this.getCalendarSystem().transform(this.getContext());
}
/**
* Returns the assigned calendar family which contains all necessary
* chronological rules.
*
* Concrete subclasses must create in a static initializer a
* calendar family by help of {@code CalendarFamily.Builder}, keep it as static
* constant and make it available here. Using the procedure guarantees
* that a basic set of registered elements and rules will be installed.
*
* @return chronological system as calendar family (never {@code null})
* @since 3.4/4.3
* @see CalendarFamily.Builder
*/
/*[deutsch]
* Liefert die zugehörige Kalenderfamilie, die alle notwendigen
* chronologischen Regeln enthält.
*
* Konkrete Subklassen müssen in einem static initializer
* mit Hilfe von {@code CalendarFamily.Builder} eine Kalenderfamilie bauen, in
* einer eigenen Konstanten halten und hier verfügbar machen.
* Über dieses Verfahren wird zugleich ein Basissatz von Elementen
* und chronologischen Regeln vorinstalliert.
*
* @return chronological system as calendar family (never {@code null})
* @since 3.4/4.3
* @see CalendarFamily.Builder
*/
@Override
protected abstract CalendarFamily getChronology();
@SuppressWarnings("unchecked")
@Override
ElementRule getRule(ChronoElement element) {
if (element instanceof EpochDays) {
EpochDays ed = EpochDays.class.cast(element);
return (ElementRule) ed.derive(this.getCalendarSystem());
} else {
return super.getRule(element);
}
}
private CalendarSystem getCalendarSystem() {
return this.getChronology().getCalendarSystem(this.getVariant());
}
private T transform(
CalendarSystem calsys,
String ref
) {
long utcDays = this.getDaysSinceEpochUTC();
if (
(calsys.getMinimumSinceUTC() > utcDays)
|| (calsys.getMaximumSinceUTC() < utcDays)
) {
throw new ArithmeticException("Cannot transform <" + utcDays + "> to: " + ref);
} else {
return calsys.transform(utcDays);
}
}
}