/* * ----------------------------------------------------------------------- * Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/> * ----------------------------------------------------------------------- * 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 <http://www.gnu.org/licenses/>. * ----------------------------------------------------------------------- */ package net.time4j.engine; import java.io.Serializable; /** * <p>Represents an immutable calendar variant. </p> * * <p><strong>Display and change chronological element values</strong></p> * * <p>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. </p> * * <p>Element values can only be changed by creating a new immutable copy * of the original instance. This is done via all {@code with()}-methods. </p> * * <p><strong>Calendar system</strong></p> * * <p>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. </p> * * <p><strong>Sorting</strong></p> * * <p>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. </p> * * <p><strong>Implementation notes</strong></p> * * <ul> * <li>All subclasses must be <i>final</i> und <i>immutable</i>. </li> * <li>Documentation of supported and registered elements is required. </li> * <li>The natural order should be consistent with {@code equals()}. </li> * </ul> * * @param <D> generic type of self reference * @author Meno Hochschild * @serial exclude * @since 3.4/4.3 * @see Chronology * @see CalendarFamily */ /*[deutsch] * <p>Repräsentiert eine unveränderlichen Kalendervariante. </p> * * <p><strong>Chronologische Elementwerte anzeigen und ändern</strong></p> * * <p>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. </p> * * <p>Da alle konkreten Implementierungen <i>immutable</i> 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. </p> * * <p><strong>Kalendersystem</strong></p> * * <p>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. </p> * * <p><strong>Sortierung</strong></p> * * <p>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. </p> * * <p><strong>Implementierungshinweise</strong></p> * * <ul> * <li>Alle Subklassen müssen <i>final</i> und <i>immutable</i> sein. </li> * <li>Es muß dokumentiert werden, welche chronologischen Elemente * unterstützt werden bzw. registriert sind. </li> * <li>Die natürliche Ordnung sollte konsistent mit {@code equals()} * sein. </li> * </ul> * * @param <D> generic type of self reference * @author Meno Hochschild * @serial exclude * @since 3.4/4.3 * @see Chronology * @see CalendarFamily */ public abstract class CalendarVariant<D extends CalendarVariant<D>> extends ChronoEntity<D> implements CalendarDate, Comparable<D>,Serializable { //~ Methoden ---------------------------------------------------------- /** * <p>Returns the name of the associated variant of underlying calendar system. </p> * * @return String * @since 3.4/4.3 * @see CalendarFamily#getCalendarSystem(String) */ /*[deutsch] * <p>Liefert den Namen der assoziierten Variante des zugrundeliegenden Kalendersystems. </p> * * @return String * @since 3.4/4.3 * @see CalendarFamily#getCalendarSystem(String) */ public abstract String getVariant(); /** * <p>Creates a copy of this instance with given variant. </p> * * <p>If given variant is equal to the variant of this instance * then the method will just return this instance. </p> * * @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] * <p>Erzeugt eine Kopie dieser Instanz mit der angegebenen Variante. </p> * * <p>Wenn die angegebene Variante der Variante dieser Instanz gleicht, wird die * Methode einfach nur diese Instanz zurückgeben. </p> * * @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); } /** * <p>Equivalent to {@link #withVariant(String) withVariant(variantSource.getVariant())}. </p> * * @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] * <p>Äquivalent zu {@link #withVariant(String) withVariant(variantSource.getVariant())}. </p> * * @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()); } /** * <p>Converts this calendar date to the given target type based on * the count of days relative to UTC epoch [1972-01-01]. </p> * * <p>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. </p> * * @param <T> 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] * <p>Konvertiert dieses Datum zum angegebenen Zieltyp auf Basis der * Anzahl der Tage relativ zur UTC-Epoche [1972-01-01]. </p> * * <p>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. </p> * * @param <T> 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 extends Calendrical<?, T>> T transform(Class<T> target) { String ref = target.getName(); Chronology<T> 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); } /** * <p>Converts this calendar date to the given target type based on * the count of days relative to UTC epoch [1972-01-01]. </p> * * <p>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. </p> * * @param <T> 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] * <p>Konvertiert dieses Datum zum angegebenen Zieltyp auf Basis der * Anzahl der Tage relativ zur UTC-Epoche [1972-01-01]. </p> * * <p>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. </p> * * @param <T> 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 extends CalendarVariant<T>> T transform( Class<T> target, String variant ) { String ref = target.getName(); Chronology<T> 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); } /** * <p>Converts this calendar date to the given target type based on * the count of days relative to UTC epoch [1972-01-01]. </p> * * <p>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. </p> * * @param <T> 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] * <p>Konvertiert dieses Datum zum angegebenen Zieltyp auf Basis der * Anzahl der Tage relativ zur UTC-Epoche [1972-01-01]. </p> * * <p>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. </p> * * @param <T> 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 extends CalendarVariant<T>> T transform( Class<T> target, VariantSource variantSource ) { return this.transform(target, variantSource.getVariant()); } /** * <p>Compares two calendar variants preferably by their temporal positions * on the common date axis and then by their variant names. </p> * * <p>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. </p> * * @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] * <p>Vergleicht zwei Kalendervarianten bevorzugt nach ihrer Position auf der * gemeinsamen Zeitachse und dann lexikalisch nach ihren Variantennamen. </p> * * <p>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. </p> * * @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); } /** * <p>Adds given calendar days to this instance. </p> * * @param days calendar days to be added * @return result of addition * @throws ArithmeticException in case of numerical overflow * @since 3.4/4.3 */ /*[deutsch] * <p>Addiert die angegebenen Kalendertage zu dieser Instanz. </p> * * @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; } } /** * <p>Subtracts given calendar days from this instance. </p> * * @param days calendar days to be subtracted * @return result of subtraction * @throws ArithmeticException in case of numerical overflow * @since 3.4/4.3 */ /*[deutsch] * <p>Subtrahiert die angegebenen Kalendertage von dieser Instanz. </p> * * @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()))); } /** * <p>Compares the whole state of this instance with given object. </p> * * <p>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. </p> * * @see #compareTo(CalendarVariant) */ /*[deutsch] * <p>Vergleicht den gesamten Zustand dieser Instanz mit dem des angegebenen Objekts. </p> * * <p>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. </p> * * @see #compareTo(TimePoint) */ @Override public abstract boolean equals(Object obj); /** * <p>Subclasses must redefine this method corresponding to the * behaviour of {@code equals()}. </p> */ /*[deutsch] * <p>Subklassen müssen diese Methode passend zum Verhalten * von {@code equals()} redefinieren. </p> */ @Override public abstract int hashCode(); /** * <p>Provides a complete textual representation of the state of this calendar variant. </p> */ /*[deutsch] * <p>Liefert eine vollständige Beschreibung des Zustands dieser Kalendervariante. </p> */ @Override public abstract String toString(); @Override public long getDaysSinceEpochUTC() { return this.getCalendarSystem().transform(this.getContext()); } /** * <p>Returns the assigned calendar family which contains all necessary * chronological rules. </p> * * <p>Concrete subclasses must create in a <i>static initializer</i> 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. </p> * * @return chronological system as calendar family (never {@code null}) * @since 3.4/4.3 * @see CalendarFamily.Builder */ /*[deutsch] * <p>Liefert die zugehörige Kalenderfamilie, die alle notwendigen * chronologischen Regeln enthält. </p> * * <p>Konkrete Subklassen müssen in einem <i>static initializer</i> * 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. </p> * * @return chronological system as calendar family (never {@code null}) * @since 3.4/4.3 * @see CalendarFamily.Builder */ @Override protected abstract CalendarFamily<D> getChronology(); @SuppressWarnings("unchecked") @Override <V> ElementRule<D, V> getRule(ChronoElement<V> element) { if (element instanceof EpochDays) { EpochDays ed = EpochDays.class.cast(element); return (ElementRule<D, V>) ed.derive(this.getCalendarSystem()); } else { return super.getRule(element); } } private CalendarSystem<D> getCalendarSystem() { return this.getChronology().getCalendarSystem(this.getVariant()); } private <T> T transform( CalendarSystem<T> 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); } } }