/* * ----------------------------------------------------------------------- * Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/> * ----------------------------------------------------------------------- * This file (CommonElements.java) is part of project Time4J. * * Time4J is free software: You can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 2.1 of the License, or * (at your option) any later version. * * Time4J is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Time4J. If not, see <http://www.gnu.org/licenses/>. * ----------------------------------------------------------------------- */ package net.time4j.calendar; import net.time4j.Weekday; import net.time4j.Weekmodel; import net.time4j.calendar.service.StdEnumDateElement; import net.time4j.calendar.service.StdIntegerDateElement; import net.time4j.engine.AttributeQuery; import net.time4j.engine.BasicElement; import net.time4j.engine.CalendarDate; import net.time4j.engine.ChronoDisplay; import net.time4j.engine.ChronoElement; import net.time4j.engine.ChronoEntity; import net.time4j.engine.ChronoExtension; import net.time4j.engine.ChronoOperator; import net.time4j.engine.Chronology; import net.time4j.engine.ElementRule; import net.time4j.engine.EpochDays; import net.time4j.engine.FormattableElement; import java.io.ObjectStreamException; import java.util.Collections; import java.util.HashSet; import java.util.Locale; import java.util.Set; /** * <p>Defines access to elements which can be used by all calendars defined in this package. </p> * * @author Meno Hochschild * @since 3.20/4.16 */ /*[deutsch] * <p>Definiert einen Zugang zu Elementen, die von allen Kalendern in diesem Paket verwendet werden können. </p> * * @author Meno Hochschild * @since 3.20/4.16 */ public class CommonElements { //~ Statische Felder/Initialisierungen -------------------------------- /** * <p>Represents the related gregorian year which corresponds to the start * of any given non-gregorian calendar year. </p> * * <p>The element is read-only. </p> * * @since 3.20/4.16 */ /*[deutsch] * <p>Repräsentiert das gregorianische Bezugsjahr des Beginns eines gegebenen Kalenderjahres. </p> * * <p>Dieses Element kann nur gelesen werden. </p> * * @since 3.20/4.16 */ @FormattableElement(format = "r") public static final ChronoElement<Integer> RELATED_GREGORIAN_YEAR = RelatedGregorianYearElement.SINGLETON; //~ Konstruktoren ----------------------------------------------------- private CommonElements() { // no instantiation } //~ Methoden ---------------------------------------------------------- /** * <p>Defines an element for the weekday with a localized day number in * the value range {@code 1-7}. </p> * * <p>The given chronology must support a 7-day-week with an element of name "DAY_OF_WEEK" * otherwise an exception will be thrown. </p> * * <p>This element defines localized weekday numbers in numerical formatting * and also a localized sorting order of weekdays, but still manages values * of type {@code Weekday}. However, the value range with its minimum and * maximum is localized, too, i.e. the element defines as minium the value * {@code getFirstDayOfWeek()}. </p> * * @param <T> chronological type * @param chronology the calendrical chronology * @param model the underlying week model * @return day of week with localized order * @throws IllegalArgumentException if the chronology does not support this element * @since 3.24/4.20 */ /*[deutsch] * <p>Liefert ein Element für den Wochentag mit einer lokalisierten * Wochentagsnummer im Wertebereich {@code 1-7} und kann auf alle Chronologien angewandt * werden, die eine 7-Tage-Woche mit einem Element namens "DAY_OF_WEEK" unterstützen. </p> * * <p>Dieses Element definiert lokalisierte Wochentagsnummern in der * numerischen Formatierung und demzufolge auch eine lokalisierte * Wochentagssortierung, verwaltet aber selbst immer noch Enums vom Typ * {@code Weekday} als Werte. Jedoch ist der Wertebereich mitsamt seinem * Minimum und Maximum ebenfalls lokalisiert, d.h., das Element definiert * als Minimum den Wert {@code model.getFirstDayOfWeek()}. </p> * * @param <T> chronological type * @param chronology the calendrical chronology * @param model the underlying week model * @return day of week with localized order * @throws IllegalArgumentException if the chronology does not support this element * @since 3.24/4.20 */ @FormattableElement(format = "e", standalone = "c") public static <T extends ChronoEntity<T> & CalendarDate> StdCalendarElement<Weekday, T> localDayOfWeek( Chronology<T> chronology, Weekmodel model ) { checkSevenDayWeek(chronology); return new DayOfWeekElement<>(chronology.getChronoType(), model); } /** * <p>Creates an integer element for the week of year in given chronology dependent on given week model. </p> * * <p>The given chronology must support a 7-day-week with elements of names "DAY_OF_WEEK" * and "DAY_OF_YEAR", otherwise an exception will be thrown. </p> * * @param <T> chronological type * @param chronology the calendrical chronology * @param model the underlying week model * @return generic calendar element * @throws IllegalArgumentException if the chronology does not support this element * @since 3.24/4.20 */ /*[deutsch] * <p>Erzeugt ein Integer-Element für die Kalenderwoche des Jahres zum angegebenen Kalendersystem * unter Benutzung des angegebenen Wochenmodells. </p> * * <p>Die angegebene Chronologie muß eine 7-Tage-Woche mit Elementen namens "DAY_OF_WEEK" * und "DAY_OF_YEAR" unterstützen, sonst wird eine Ausnahme geworfen. </p> * * @param <T> chronological type * @param chronology the calendrical chronology * @param model the underlying week model * @return generic calendar element * @throws IllegalArgumentException if the chronology does not support this element * @since 3.24/4.20 */ @FormattableElement(format = "w") public static <T extends ChronoEntity<T> & CalendarDate> StdCalendarElement<Integer, T> weekOfYear( Chronology<T> chronology, Weekmodel model ) { ChronoElement<Integer> e = findDayElement(chronology, "DAY_OF_YEAR"); if (e == null) { throw new IllegalArgumentException("Cannot derive a rule for given chronology: " + chronology); } return new CalendarWeekElement<>("WEEK_OF_YEAR", chronology.getChronoType(), 1, 52, 'w', model, e); } /** * <p>Creates an integer element for the week of month in given chronology dependent on given week model. </p> * * <p>The given chronology must support a 7-day-week with elements of names "DAY_OF_WEEK" * and "DAY_OF_MONTH", otherwise an exception will be thrown. </p> * * @param <T> chronological type * @param chronology the calendar chronology * @param model the underlying week model * @return generic calendar element * @throws IllegalArgumentException if the chronology does not support this element * @since 3.24/4.20 */ /*[deutsch] * <p>Erzeugt ein Integer-Element für die Kalenderwoche des Monats zum angegebenen Kalendersystem * unter Benutzung des angegebenen Wochenmodells. </p> * * <p>Die angegebene Chronologie muß eine 7-Tage-Woche mit Elementen namens "DAY_OF_WEEK" * und "DAY_OF_YEAR" unterstützen, sonst wird eine Ausnahme geworfen. </p> * * @param <T> chronological type * @param chronology the calendar chronology * @param model the underlying week model * @return generic calendar element * @throws IllegalArgumentException if the chronology does not support this element * @since 3.24/4.20 */ @FormattableElement(format = "W") public static <T extends ChronoEntity<T> & CalendarDate> StdCalendarElement<Integer, T> weekOfMonth( Chronology<T> chronology, Weekmodel model ) { ChronoElement<Integer> e = findDayElement(chronology, "DAY_OF_MONTH"); if (e == null) { throw new IllegalArgumentException("Cannot derive a rule for given chronology: " + chronology); } return new CalendarWeekElement<>("WEEK_OF_MONTH", chronology.getChronoType(), 1, 5, 'W', model, e); } private static <D extends ChronoEntity<D>> int getMax( ChronoElement<?> element, D context ) { return Integer.class.cast(context.getMaximum(element)).intValue(); } private static Weekday getDayOfWeek(long utcDays) { return Weekday.valueOf((int) (Math.floorMod(utcDays + 5, 7) + 1)); } private static void checkSevenDayWeek(Chronology<?> chronology) { if (CalendarDate.class.isAssignableFrom(chronology.getChronoType())) { for (ChronoElement<?> element : chronology.getRegisteredElements()) { if (element.name().equals("DAY_OF_WEEK")) { Object[] enums = element.getType().getEnumConstants(); if ((enums != null) && (enums.length == 7)) { return; } } } } throw new IllegalArgumentException("No 7-day-week: " + chronology); } @SuppressWarnings("unchecked") private static <D extends ChronoEntity<D>> ChronoElement<Integer> findDayElement( Chronology<D> chronology, String searchName ) { checkSevenDayWeek(chronology); for (ChronoElement<?> e : chronology.getRegisteredElements()) { if (e.name().equals(searchName)) { if (e.getType() == Integer.class) { return (ChronoElement<Integer>) e; } else { break; } } } return null; } //~ Innere Klassen ---------------------------------------------------- /** * <p>Wochenelement-Erweiterung. </p> * * @author Meno Hochschild */ static class Weekengine implements ChronoExtension { //~ Instanzvariablen ---------------------------------------------- private final Class<? extends ChronoEntity> chronoType; private final ChronoElement<Integer> dayOfMonthElement; private final ChronoElement<Integer> dayOfYearElement; private final Weekmodel defaultWeekmodel; //~ Konstruktoren ------------------------------------------------- Weekengine( Class<? extends ChronoEntity> chronoType, ChronoElement<Integer> dayOfMonthElement, ChronoElement<Integer> dayOfYearElement, Weekmodel defaultWeekmodel ) { super(); this.chronoType = chronoType; this.dayOfMonthElement = dayOfMonthElement; this.dayOfYearElement = dayOfYearElement; this.defaultWeekmodel = defaultWeekmodel; } //~ Methoden ------------------------------------------------------ @Override public boolean accept(Class<?> chronoType) { return this.chronoType.equals(chronoType); } @Override public Set<ChronoElement<?>> getElements( Locale locale, AttributeQuery attributes ) { Weekmodel model = (locale.getCountry().isEmpty() ? this.defaultWeekmodel : Weekmodel.of(locale)); Set<ChronoElement<?>> set = new HashSet<>(); set.add( new DayOfWeekElement<>(this.chronoType, model)); set.add(new CalendarWeekElement<>( "WEEK_OF_MONTH", this.chronoType, 1, 5, 'W', model, this.dayOfMonthElement)); set.add(new CalendarWeekElement<>( "WEEK_OF_YEAR", this.chronoType, 1, 52, 'w', model, this.dayOfYearElement)); return Collections.unmodifiableSet(set); } @Override public ChronoEntity<?> resolve( ChronoEntity<?> entity, Locale locale, AttributeQuery attributes ) { return entity; // no-op } } private static class CalendarWeekElement<T extends ChronoEntity<T>> extends StdIntegerDateElement<T> { //~ Statische Felder/Initialisierungen ---------------------------- private static final long serialVersionUID = -7471192143785466686L; //~ Instanzvariablen ---------------------------------------------- /** * @serial the underlying week model */ private final Weekmodel model; /** * @serial reference day element */ private final ChronoElement<Integer> dayElement; //~ Konstruktoren ------------------------------------------------- CalendarWeekElement( String name, Class<T> chrono, int min, int max, char symbol, Weekmodel model, ChronoElement<Integer> dayElement ) { super(name, chrono, min, max, symbol); if (model == null) { throw new NullPointerException("Missing week model."); } this.model = model; this.dayElement = dayElement; } //~ Methoden ------------------------------------------------------ @Override public ChronoOperator<T> decremented() { return new DayOperator<>(-7); } @Override public ChronoOperator<T> incremented() { return new DayOperator<>(7); } @Override public boolean isLenient() { return true; } @Override public boolean doEquals(BasicElement<?> obj) { if (super.doEquals(obj)) { CalendarWeekElement<?> that = CalendarWeekElement.class.cast(obj); return this.model.equals(that.model); } return false; } @Override protected <D extends ChronoEntity<D>> ElementRule<D, Integer> derive(Chronology<D> chronology) { if (this.getChronoType().equals(chronology.getChronoType())) { return new CWRule<>(this); } return null; } @Override protected Object readResolve() throws ObjectStreamException { return this; // no singleton } } private static class CWRule<D extends ChronoEntity<D>> implements ElementRule<D, Integer> { //~ Instanzvariablen ---------------------------------------------- private final CalendarWeekElement<?> owner; //~ Konstruktoren ------------------------------------------------- private CWRule(CalendarWeekElement<?> owner) { super(); this.owner = owner; } //~ Methoden ------------------------------------------------------ @Override public Integer getValue(D context) { return Integer.valueOf(this.getCalendarWeek(context)); } @Override public Integer getMinimum(D context) { return Integer.valueOf(1); } @Override public Integer getMaximum(D context) { return Integer.valueOf(this.getMaxCalendarWeek(context)); } @Override public boolean isValid( D context, Integer value ) { if (value == null) { return false; } int v = value.intValue(); return ((v >= 1) && (v <= this.getMaxCalendarWeek(context))); } @Override public D withValue( D context, Integer value, boolean lenient ) { int v = value.intValue(); if (!lenient && !this.isValid(context, value)) { throw new IllegalArgumentException( "Invalid value: " + v + " (context=" + context + ")"); } return this.setCalendarWeek(context, v); } @Override public ChronoElement<?> getChildAtFloor(D context) { return new DayOfWeekElement<>(context.getClass(), this.owner.model); } @Override public ChronoElement<?> getChildAtCeiling(D context) { return new DayOfWeekElement<>(context.getClass(), this.owner.model); } // letzte Kalenderwoche im Jahr/Monat private int getMaxCalendarWeek(D context) { int scaledDay = context.getInt(this.owner.dayElement); int wCurrent = getFirstCalendarWeekAsDay(context, 0); if (wCurrent <= scaledDay) { int wNext = getFirstCalendarWeekAsDay(context, 1) + getLengthOfYM(context, 0); return (wNext - wCurrent) / 7; } else { int wPrevious = getFirstCalendarWeekAsDay(context, -1); wCurrent = wCurrent + getLengthOfYM(context, -1); return (wCurrent - wPrevious) / 7; } } // Ermittelt den Beginn der ersten Kalenderwoche eines Jahres/Monats // auf einer day-of-year/month-Skala (kann auch <= 0 sein). private int getFirstCalendarWeekAsDay( D context, int shift // -1 = Vorjahr/-monat, 0 = aktuell, +1 = Folgejahr/-monat ) { Weekday wd = this.getWeekdayStart(context, shift); Weekmodel model = this.owner.model; int dow = wd.getValue(model); return ( (dow <= 8 - model.getMinimalDaysInFirstWeek()) ? 2 - dow : 9 - dow ); } // Wochentag des ersten Tags des Jahres/Monats private Weekday getWeekdayStart( D context, int shift // -1 = Vorjahr/-monat, 0 = aktuell, +1 = Folgejahr/-monat ) { int scaledDay = context.getInt(this.owner.dayElement); int lastDay; switch (shift) { case -1: long utcDays = context.get(EpochDays.UTC) - scaledDay; lastDay = context.with(EpochDays.UTC, utcDays).getInt(this.owner.dayElement); return getDayOfWeek(utcDays - lastDay + 1); case 0: return getDayOfWeek(context.get(EpochDays.UTC).longValue() - scaledDay + 1); case 1: lastDay = getMax(this.owner.dayElement, context); return getDayOfWeek(context.get(EpochDays.UTC).longValue() + lastDay + 1 - scaledDay); default: throw new AssertionError("Unexpected: " + shift); } } // Länge eines Jahres/Monats in Tagen private int getLengthOfYM( D context, int shift // -1 = Vorjahr/-monat, 0 = aktuell, +1 = Folgejahr/-monat ) { int scaledDay = context.getInt(this.owner.dayElement); int lastDay; switch (shift) { case -1: return getMax( this.owner.dayElement, context.with(EpochDays.UTC, context.get(EpochDays.UTC).longValue() - scaledDay)); case 0: return getMax(this.owner.dayElement, context); case 1: lastDay = getMax(this.owner.dayElement, context); return getMax( this.owner.dayElement, context.with(EpochDays.UTC, context.get(EpochDays.UTC).longValue() + lastDay + 1 - scaledDay)); default: throw new AssertionError("Unexpected: " + shift); } } private int getCalendarWeek(D context) { int scaledDay = context.getInt(this.owner.dayElement); int wCurrent = getFirstCalendarWeekAsDay(context, 0); if (wCurrent <= scaledDay) { int wNext = getFirstCalendarWeekAsDay(context, 1) + getLengthOfYM(context, 0); if (wNext <= scaledDay) { return 1; } else { return ((scaledDay - wCurrent) / 7) + 1; } } else { int wPrevious = getFirstCalendarWeekAsDay(context, -1); int dayCurrent = scaledDay + getLengthOfYM(context, -1); return ((dayCurrent - wPrevious) / 7) + 1; } } private D setCalendarWeek( D context, int value ) { int old = this.getCalendarWeek(context); if (value == old) { return context; } else { return context.with(EpochDays.UTC, context.get(EpochDays.UTC).longValue() + 7 * (value - old)); } } } private static class DayOfWeekElement<T extends ChronoEntity<T>> extends StdEnumDateElement<Weekday, T> { //~ Statische Felder/Initialisierungen ---------------------------- private static final long serialVersionUID = 5613494586572932860L; //~ Instanzvariablen ---------------------------------------------- /** * @serial the underlying week model */ private final Weekmodel model; //~ Konstruktoren ------------------------------------------------- DayOfWeekElement( Class<T> chronoType, Weekmodel model ) { super("LOCAL_DAY_OF_WEEK", chronoType, Weekday.class, 'e'); this.model = model; } //~ Methoden ------------------------------------------------------ @Override public ChronoOperator<T> decremented() { return new DayOperator<>(-1); } @Override public ChronoOperator<T> incremented() { return new DayOperator<>(1); } @Override public int numerical(Weekday dayOfWeek) { return dayOfWeek.getValue(this.model); } @Override public Weekday getDefaultMinimum() { return this.model.getFirstDayOfWeek(); } @Override public Weekday getDefaultMaximum() { return this.model.getFirstDayOfWeek().roll(6); } @Override public int compare( ChronoDisplay o1, ChronoDisplay o2 ) { int i1 = o1.get(this).getValue(this.model); int i2 = o2.get(this).getValue(this.model); return ((i1 < i2) ? -1 : ((i1 == i2) ? 0 : 1)); } @Override public boolean doEquals(BasicElement<?> obj) { if (super.doEquals(obj)) { DayOfWeekElement<?> that = DayOfWeekElement.class.cast(obj); return this.model.equals(that.model); } return false; } @Override protected <D extends ChronoEntity<D>> ElementRule<D, Weekday> derive(Chronology<D> chronology) { if (this.getChronoType().equals(chronology.getChronoType())) { return new DRule<>(this); } return null; } @Override protected boolean isWeekdayElement() { return true; } @Override protected Object readResolve() throws ObjectStreamException { return this; // no singleton } } private static class DRule<T extends ChronoEntity<T>> implements ElementRule<T, Weekday> { //~ Instanzvariablen ---------------------------------------------- private final DayOfWeekElement<?> element; //~ Konstruktoren ------------------------------------------------- private DRule(DayOfWeekElement<?> element) { super(); this.element = element; } //~ Methoden ------------------------------------------------------ @Override public Weekday getValue(T context) { return getDayOfWeek(context.get(EpochDays.UTC).longValue()); } @Override public Weekday getMinimum(T context) { return this.element.getDefaultMinimum(); } @Override public Weekday getMaximum(T context) { return this.element.getDefaultMaximum(); } @Override public boolean isValid( T context, Weekday value ) { if (value == null) { return false; } try { this.withValue(context, value, false); return true; } catch (ArithmeticException | IllegalArgumentException ex) { return false; } } @Override public T withValue( T context, Weekday value, boolean lenient ) { long utcDays = context.get(EpochDays.UTC).longValue(); Weekday current = getDayOfWeek(utcDays); if (value == current) { return context; } int old = current.getValue(this.element.model); int neu = value.getValue(this.element.model); return context.with(EpochDays.UTC, utcDays + neu - old); } @Override public ChronoElement<?> getChildAtFloor(T context) { return null; } @Override public ChronoElement<?> getChildAtCeiling(T context) { return null; } } private static class DayOperator<T extends ChronoEntity<T>> implements ChronoOperator<T> { //~ Instanzvariablen ---------------------------------------------- private final int amount; //~ Konstruktoren ------------------------------------------------- DayOperator(int amount) { super(); this.amount = amount; } //~ Methoden ------------------------------------------------------ public T apply(T entity) { long e = Math.addExact(entity.get(EpochDays.UTC), this.amount); return entity.with(EpochDays.UTC, e); } } }