net.time4j.Weekmodel Maven / Gradle / Ivy
/*
* -----------------------------------------------------------------------
* Copyright © 2013-2016 Meno Hochschild,
* -----------------------------------------------------------------------
* This file (Weekmodel.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;
import net.time4j.base.GregorianDate;
import net.time4j.base.GregorianMath;
import net.time4j.base.MathUtils;
import net.time4j.base.ResourceLoader;
import net.time4j.engine.AttributeQuery;
import net.time4j.engine.BasicElement;
import net.time4j.engine.ChronoCondition;
import net.time4j.engine.ChronoDisplay;
import net.time4j.engine.ChronoElement;
import net.time4j.engine.ChronoEntity;
import net.time4j.engine.Chronology;
import net.time4j.engine.ElementRule;
import net.time4j.engine.FormattableElement;
import net.time4j.format.Attributes;
import net.time4j.format.CalendarText;
import net.time4j.format.NumericalElement;
import net.time4j.format.OutputContext;
import net.time4j.format.TextAccessor;
import net.time4j.format.TextElement;
import net.time4j.format.TextWidth;
import net.time4j.format.WeekdataProvider;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.text.ParsePosition;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import static net.time4j.PlainDate.CALENDAR_DATE;
import static net.time4j.PlainDate.WEEKDAY_IN_MONTH;
/**
* Defines rules for the localized handling of weekdays and calendar weeks
* on the base of a seven-day-week.
*
*
* - 1st rule: Which day of week is the first day of calendar week?
* - 2nd rule: What is the minimum count of days in the first calendar
* week of the year?
*
*
* Furthermore, a {@code Weekmodel} contains some week-related elements
* which can be used in all types containing an ISO-8601-date
* ({@code PlainTimestamp} and {@code PlainDate}).
*
* @author Meno Hochschild
* @see WeekdataProvider
* @doctags.concurrency {immutable}
*/
/*[deutsch]
* Definiert Regeln für den lokalisierten Umgang mit Wochentagen
* und Kalenderwochen auf einer 7-Tage-Wochenbasis.
*
*
* - 1. Regel: Welcher Wochentag ist der erste Tag der Woche?
* - 2. Regel: Was ist die minimale Anzahl der Tage der ersten Woche
* des Kalendarjahres?
*
*
* Außerdem werden einige wochenbezogene Elemente zur Verfügung
* gestellt, die mit allen Klassen umgehen können, die ein ISO-Datum
* enthalten ({@code PlainTimestamp} und {@code PlainDate}).
*
* @author Meno Hochschild
* @see WeekdataProvider
* @doctags.concurrency {immutable}
*/
public final class Weekmodel
implements Serializable {
//~ Statische Felder/Initialisierungen --------------------------------
private static final int CALENDAR_WEEK_OF_YEAR = 0;
private static final int CALENDAR_WEEK_OF_MONTH = 1;
private static final int BOUNDED_WEEK_OF_YEAR = 2;
private static final int BOUNDED_WEEK_OF_MONTH = 3;
private static final Map CACHE = new ConcurrentHashMap<>();
/**
* Standard week rules as defined by ISO-8601.
*
* Monday is considered as first day of calendar week. And the first
* calendar week of year must contain at least four days respective
* contain the first Thursday of year. Saturday and Sunday are considered
* as weekend.
*/
/*[deutsch]
* Standard-Wochenregeln für die ISO-8601-Norm.
*
* Nach der ISO-8601-Norm ist der Montag der erste Tag der Woche, und
* die erste Kalenderwoche des Jahres muß mindestens 4 Tage haben
* bzw. den ersten Donnerstag des Jahres enthalten. Als Wochenende gelten
* die Tage Samstag und Sonntag.
*/
public static final Weekmodel ISO =
new Weekmodel(Weekday.MONDAY, 4, Weekday.SATURDAY, Weekday.SUNDAY);
private static final WeekdataProvider LOCALIZED_WEEKDATA;
static {
WeekdataProvider tmp = null;
for (WeekdataProvider p : ResourceLoader.getInstance().services(WeekdataProvider.class)) {
tmp = p;
break;
}
LOCALIZED_WEEKDATA = tmp;
}
private static final long serialVersionUID = 7794495882610436763L;
//~ Instanzvariablen --------------------------------------------------
// Zustand
private transient final Weekday firstDayOfWeek;
private transient final int minimalDaysInFirstWeek;
private transient final Weekday startOfWeekend;
private transient final Weekday endOfWeekend;
// Elemente kompatibel zu PlainDate
private transient final
AdjustableElement woyElement;
private transient final
AdjustableElement womElement;
private transient final
AdjustableElement boundWoyElement;
private transient final
AdjustableElement boundWomElement;
private transient final
NavigableElement dayOfWeekElement;
private transient final Set> elements;
// Bedingungsausdruck
private transient final ChronoCondition weekendCondition;
//~ Konstruktoren -----------------------------------------------------
private Weekmodel(
Weekday firstDayOfWeek,
int minimalDaysInFirstWeek,
final Weekday startOfWeekend,
final Weekday endOfWeekend
) {
super();
if (firstDayOfWeek == null) {
throw new NullPointerException("Missing first day of week.");
} else if (
(minimalDaysInFirstWeek < 1)
|| (minimalDaysInFirstWeek > 7)
) {
throw new IllegalArgumentException(
"Minimal days in first week out of range: "
+ minimalDaysInFirstWeek);
} else if (startOfWeekend == null) {
throw new NullPointerException("Missing start of weekend.");
} else if (endOfWeekend == null) {
throw new NullPointerException("Missing end of weekend.");
}
this.firstDayOfWeek = firstDayOfWeek;
this.minimalDaysInFirstWeek = minimalDaysInFirstWeek;
this.startOfWeekend = startOfWeekend;
this.endOfWeekend = endOfWeekend;
this.woyElement =
new CalendarWeekElement(
"WEEK_OF_YEAR", CALENDAR_WEEK_OF_YEAR);
this.womElement =
new CalendarWeekElement(
"WEEK_OF_MONTH", CALENDAR_WEEK_OF_MONTH);
this.boundWoyElement =
new CalendarWeekElement(
"BOUNDED_WEEK_OF_YEAR", BOUNDED_WEEK_OF_YEAR);
this.boundWomElement =
new CalendarWeekElement(
"BOUNDED_WEEK_OF_MONTH", BOUNDED_WEEK_OF_MONTH);
this.dayOfWeekElement = new DayOfWeekElement();
this.weekendCondition =
context -> {
int y = context.getYear();
int m = context.getMonth();
int dom = context.getDayOfMonth();
Weekday wd =
Weekday.valueOf(GregorianMath.getDayOfWeek(y, m, dom));
return ((wd == startOfWeekend) || (wd == endOfWeekend));
};
Set> set = new HashSet<>();
set.add(this.woyElement);
set.add(this.womElement);
set.add(this.dayOfWeekElement);
set.add(this.boundWoyElement);
set.add(this.boundWomElement);
this.elements = Collections.unmodifiableSet(set);
}
//~ Methoden ----------------------------------------------------------
/**
* Creates a new week model with the given rules and the
* weekend-definition Saturday/Sunday.
*
* @param firstDayOfWeek localized first day of week
* @param minimalDaysInFirstWeek required minimum count of days for
* the first week of year in range (1-7)
* @return specific week model with weekend on saturday and sunday
* @throws IllegalArgumentException if any argument is out of range
* @see #of(Locale)
*/
/*[deutsch]
* Erzeugt ein neues Wochenmodell mit den angegebenen Einstellungen
* und der Wochenenddefinition Samstag/Sonntag.
*
* @param firstDayOfWeek localized first day of week
* @param minimalDaysInFirstWeek required minimum count of days for
* the first week of year in range (1-7)
* @return specific week model with weekend on saturday and sunday
* @throws IllegalArgumentException if any argument is out of range
* @see #of(Locale)
*/
public static Weekmodel of(
Weekday firstDayOfWeek,
int minimalDaysInFirstWeek
) {
return Weekmodel.of(
firstDayOfWeek,
minimalDaysInFirstWeek,
Weekday.SATURDAY,
Weekday.SUNDAY
);
}
/**
* Creates a new week model with the given rules.
*
* @param firstDayOfWeek localized first day of week
* @param minimalDaysInFirstWeek required minimum count of days for
* the first week of year in range (1-7)
* @param startOfWeekend first day of weekend
* @param endOfWeekend last day of weekend
* @return specific week model
* @throws IllegalArgumentException if any argument is out of range
* @see #of(Locale)
*/
/*[deutsch]
* Erzeugt ein neues Wochenmodell mit den angegebenen Einstellungen.
*
* @param firstDayOfWeek localized first day of week
* @param minimalDaysInFirstWeek required minimum count of days for
* the first week of year in range (1-7)
* @param startOfWeekend first day of weekend
* @param endOfWeekend last day of weekend
* @return specific week model
* @throws IllegalArgumentException if any argument is out of range
* @see #of(Locale)
*/
public static Weekmodel of(
Weekday firstDayOfWeek,
int minimalDaysInFirstWeek,
Weekday startOfWeekend,
Weekday endOfWeekend
) {
if (
(firstDayOfWeek == Weekday.MONDAY)
&& (minimalDaysInFirstWeek == 4)
&& (startOfWeekend == Weekday.SATURDAY)
&& (endOfWeekend == Weekday.SUNDAY)
) {
return Weekmodel.ISO;
}
return new Weekmodel(
firstDayOfWeek,
minimalDaysInFirstWeek,
startOfWeekend,
endOfWeekend
);
}
/**
* Gets a suitable weekmodel for the default locale of system.
*
* Note: In order to get a weekend definition deviating from the
* standard Saturday + Sunday, the i18n-module must be present in
* classpath since v2.2.
*
* @return week model in system locale
* @see Locale#getDefault()
*/
/*[deutsch]
* Ermittelt ein geeignetes Wochenmodell für die aktuelle
* Landeseinstellung des Systems.
*
* Hinweis: Damit eine von Samstag und Sonntag abweichende
* lokalisierte Wochenenddefinition erzeugt werden kann, muß
* seit Version v2.2 das i18n-Modul im Klassenpfad vorhanden sein.
*
* @return week model in system locale
* @see Locale#getDefault()
*/
public static Weekmodel ofSystem() {
return Weekmodel.of(Locale.getDefault());
}
/**
* Gets a suitable weekmodel for the given country.
*
* Note: In order to get a weekend definition deviating from the
* standard Saturday + Sunday, the i18n-module must be present in
* classpath since v2.2. If the country-part of given locale is missing
* then this method will just return {@link #ISO}.
*
* @param locale country setting
* @return localized week model
*/
/*[deutsch]
* Ermittelt ein geeignetes Wochenmodell für das angegebene
* Land.
*
* Hinweis: Damit eine von Samstag und Sonntag abweichende
* lokalisierte Wochenenddefinition erzeugt werden kann, muß
* seit Version v2.2 das i18n-Modul im Klassenpfad vorhanden sein.
* Falls die Landeskomponente des Arguments fehlt, wird diese Methode
* lediglich {@link #ISO} liefern.
*
* @param locale country setting
* @return localized week model
*/
public static Weekmodel of(Locale locale) {
if (locale.getCountry().isEmpty()) {
return Weekmodel.ISO;
}
Weekmodel model = CACHE.get(locale);
if (model != null) {
return model;
}
WeekdataProvider p = LOCALIZED_WEEKDATA;
if (p == null) { // fallback
GregorianCalendar gc = new GregorianCalendar(locale);
int fd = gc.getFirstDayOfWeek();
int firstDayOfWeek = ((fd == 1) ? 7 : (fd - 1));
return Weekmodel.of(
Weekday.valueOf(firstDayOfWeek),
gc.getMinimalDaysInFirstWeek());
}
model =
new Weekmodel(
Weekday.valueOf(p.getFirstDayOfWeek(locale)),
p.getMinimalDaysInFirstWeek(locale),
Weekday.valueOf(p.getStartOfWeekend(locale)),
Weekday.valueOf(p.getEndOfWeekend(locale))
);
if (CACHE.size() > 150) {
CACHE.clear(); // Größenbegrenzung
}
CACHE.put(locale, model);
return model;
}
/**
* Defines the first day of the calendar week in this model.
*
* The first day of week is not required to be identical with the
* first working day. It rather marks the the first column a graphical
* localized calendar. However, in ISO-8601 the first day of week and
* the first working day (equal to first day after weekend) are
* identical.
*
* @return start of week
* @see #getFirstWorkday()
*/
/*[deutsch]
* Definiert den ersten Tag einer Kalenderwoche.
*
* Der erste Tag der Woche muß nicht mit dem ersten Arbeitstag
* einer Woche identisch sein. Vielmehr bezeichnet der erste Tag der
* Woche die erste Spalte in einer graphischen Kalenderdarstellung.
* Im ISO-8601-Standard sind allerdings der erste Tag der Woche und
* der erste Arbeitstag identisch.
*
* @return start of week
* @see #getFirstWorkday()
*/
public Weekday getFirstDayOfWeek() {
return this.firstDayOfWeek;
}
/**
* Defines the minimum count of days the first calendar week of year
* (or month) must contain.
*
* If this method yields {@code 1} then the first calendar week of
* year always contains the first of January. If the return value is
* {@code 7} instead then only the first full seven-day-week is the
* first calendar week of year. In ISO-8601 the value is {@code 4}.
*
* @return required count of days for first week of year in the range (1-7)
*/
/*[deutsch]
* Definiert die minimale Anzahl von Tagen, die die erste Kalenderwoche
* eines Jahres oder Monats enthalten muß.
*
* Bei einem Wert von {@code 1} enthält die erste Kalenderwoche
* des Jahres den 1. Januar, bei einem Wert von {@code 7} ist nur die
* erste volle 7-Tage-Woche die erste Kalenderwoche des Jahres. Im
* ISO-8601-Standard ist der Wert {@code 4}.
*
* @return required count of days for first week of year in the range (1-7)
*/
public int getMinimalDaysInFirstWeek() {
return this.minimalDaysInFirstWeek;
}
/**
* Defines the first day of the weekend.
*
* In ISO-8601 Saturday is considered as start of weekend (note: not
* explicitly mentioned in ISO-paper).
*
* @return start of weekend
*/
/*[deutsch]
* Definiert den ersten Tag des Wochenendes.
*
* Im ISO-8601-Standard ist der Samstag der Beginn des Wochenendes
* (zu beachten: nicht explizit im ISO-Papier erwähnt).
*
* @return start of weekend
*/
public Weekday getStartOfWeekend() {
return this.startOfWeekend;
}
/**
* Defines the last day of weekend.
*
* In ISO-8601 Sunday is considered as end of weekend (note: not
* explicitly mentioned in ISO-paper).
*
* @return end of weekend
*/
/*[deutsch]
* Definiert den letzten Tag des Wochenendes.
*
* Im ISO-8601-Standard ist der Sonntag das Ende des Wochenendes
* (zu beachten: nicht explizit im ISO-Papier erwähnt).
*
* @return end of weekend
*/
public Weekday getEndOfWeekend() {
return this.endOfWeekend;
}
/**
* Gets the first working day as first day after end of weekend.
*
* Note: The last working day of week is not well defined however
* and needs to be defined by the application itself. For example
* Saturday is considered as start of weekend but also handled as legal
* working day in most countries.
*
* @return first day after weekend
*/
/*[deutsch]
* Ermittelt den ersten Arbeitstag als den Tag nach dem Ende des
* Wochenendes.
*
* Hinweis: Der letzte Arbeitstag der Woche als Gegenstück
* zu dieser Methode ist in der Regel nicht eindeutig und daher von
* Anwendungen selbst festzulegen. Zum Beispiel gilt in vielen Ländern
* der Samstag zwar als der Start des Wochenendes, wird aber trotzdem
* gesetzlich als Werktag behandelt.
*
* @return first day after weekend
*/
public Weekday getFirstWorkday() {
return this.getEndOfWeekend().next();
}
/**
* Defines an element for the calendar week of year with a localized
* week number.
*
* In ISO-8601 the value range is given by {@code 1-52/53}. Reference
* year is the week-based year, not the calendar year. Therefore the
* maximum of this element is equivalent to the last calendar week of the
* week-based year. Examples:
*
*
* PlainDate date1 = PlainDate.of(2012, 12, 31); // Monday
* System.out.println(date1.get(Weekmodel.ISO.weekOfYear()));
* // Output: 1 (first calendar week of year 2013)
*
* PlainDate date2 = PlainDate.of(2000, 1, 2); // Sunday
* System.out.println(date2.get(Weekmodel.ISO.weekOfYear()));
* // Output: 52 (last calendar week of year 1999)
*
*
* Note: This element uses the lenient mode if new values are to be set
* ({@code isLenient() == true}).
*
* @return localized week of year
*/
/*[deutsch]
* Liefert ein Element für die Woche des Jahres mit einer
* lokalisierten Wochennummer.
*
* Im ISO-Wochenmodell ist der Wertebereich {@code 1-52/53}. Bezugsjahr
* ist das wochenbasierte Jahr, nicht das Kalenderjahr. Daher ist der
* Maximalwert dieses Elements gleichbedeutend mit der letzten Kalenderwoche
* des wochenbasierten Jahres. Beispiele:
*
*
* PlainDate date1 = PlainDate.of(2012, 12, 31); // Montag
* System.out.println(date1.get(Weekmodel.ISO.weekOfYear()));
* // Ausgabe: 1 (erste Kalenderwoche des Jahres 2013)
*
* PlainDate date2 = PlainDate.of(2000, 1, 2); // Sonntag
* System.out.println(date2.get(Weekmodel.ISO.weekOfYear()));
* // Ausgabe: 52 (letzte Kalenderwoche des Jahres 1999)
*
*
* Achtung: Dieses Element arbeitet beim Setzen von Werten fehlertolerant
* im Nachsichtigkeitsmodus ({@code isLenient() == true}).
*
* @return localized week of year
*/
@FormattableElement(format = "w")
public AdjustableElement weekOfYear() {
return this.woyElement;
}
/**
* Defines an element for the calendar week of month with a localized
* week number.
*
* In ISO-8601 the value range is given by {@code 1-4/5}. The behaviour
* is fully conform to the week of year - like in CLDR standard.
*
* Note: This element uses the lenient mode if new values are to be set
* ({@code isLenient() == true}).
*
* @return localized week of month
* @see #weekOfYear()
*/
/*[deutsch]
* Liefert ein Element für die Woche des Monats mit einer
* lokalisierten Wochennummer.
*
* Im ISO-Wochenmodell ist der Wertebereich {@code 1-4/5}. Das Verhalten
* ist vollkommen analog zur Woche des Jahres - in Übereinstimmung mit
* der CLDR-Norm.
*
* Achtung: Dieses Element arbeitet beim Setzen von Werten fehlertolerant
* im Nachsichtigkeitsmodus ({@code isLenient() == true}).
*
* @return localized week of month
* @see #weekOfYear()
*/
@FormattableElement(format = "W")
public AdjustableElement weekOfMonth() {
return this.womElement;
}
/**
* Defines an element for the weekday with a localized day number in
* the value range {@code 1-7}.
*
* 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()}.
*
* In contrast the element {@link PlainDate#DAY_OF_WEEK} defines a
* strict ISO-8601-conforming order and ISO-weekday-numbers.
*
* @return day of week with localized order
*/
/*[deutsch]
* Liefert ein Element für den Wochentag mit einer lokalisierten
* Wochentagsnummer im Wertebereich {@code 1-7}.
*
* 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 getFirstDayOfWeek()}.
*
* Im Gegensatz hierzu definiert das Element
* {@link PlainDate#DAY_OF_WEEK} eine streng ISO-konforme Sortierung
* nebst rein ISO-konformen Wochentagsnummern in der Formatierung.
*
* @return day of week with localized order
*/
@FormattableElement(format = "e", standalone = "c")
public NavigableElement localDayOfWeek() {
return this.dayOfWeekElement;
}
/**
* Defines an element for the week of year with a localized week number,
* constrained to the current calendar year.
*
* In ISO-8601-calendars the value range is {@code 0/1-52/53}, in other
* weekmodels the maximum value can also be {@code 54}. In contrast to
* {@link #weekOfYear()} this week can be shortened (less than seven days)
* at the start or end of a calendar year. If the week normally belongs
* to the previous year or to the following year then the bounded week
* gets the value {@code 0} resp. for the end of year the incremented
* maximum value. This behaviour is a simplifying deviation from
* CLDR-standard.
*
* Note: This element uses the lenient mode if new values are to be set
* ({@code isLenient() == true}).
*
* @return week of year within the limits of calendar year
*/
/*[deutsch]
* Liefert ein Element für die Woche des Jahres mit einer
* lokalisierten Wochennummer, begrenzt auf das aktuelle Jahr.
*
* In ISO-konformen Kalendersystemem ist der Wertebereich
* {@code 0/1-52/53}, in anderen Wochendefinitionen kann der Maximalwert
* auch {@code 54} sein. Im Unterschied zu {@link #weekOfYear()} kann
* diese Woche am Anfang oder Ende eines Jahres verkürzt sein,
* weil kein Wochenumbruch stattfindet. Falls die Woche am Anfang
* eines Jahres eigentlich in das Vorjahr bzw. die Woche am Ende
* eines Jahres eigentlich in das Folgejahr gehört, bekommt
* die Woche für den Jahresanfang den Wert {@code 0} bzw. für
* das Jahresende den hochgezählten Maximalwert. Dieses Verhalten
* ist eine vereinfachende Abweichung vom CLDR-Standard.
*
* Achtung: Dieses Element arbeitet beim Setzen von Werten fehlertolerant
* im Nachsichtigkeitsmodus ({@code isLenient() == true}).
*
* @return week of year within the limits of calendar year
*/
public AdjustableElement boundedWeekOfYear() {
return this.boundWoyElement;
}
/**
* Defines an element for the week of month with a localized week number,
* constrained to the current calendar month.
*
* In ISO-8601-calendars the value range is {@code 0/1-4/5}, in other
* weekmodels the maximum value can also be {@code 6}. In contrast to
* {@link #weekOfMonth()} this week can be shortened (less than seven days)
* at the start or end of a calendar month. If the week normally belongs
* to the previous month or to the following month then the bounded week
* gets the value {@code 0} resp. for the end of month the incremented
* maximum value. This behaviour is a simplifying deviation from
* CLDR-standard but is the same as defined in the JDK.
*
* Note: This element uses the lenient mode if new values are to be set
* ({@code isLenient() == true}).
*
* @return week of month within the limits of calendar month
* @see #boundedWeekOfYear()
*/
/*[deutsch]
* Liefert ein Element für die Woche des Monats mit einer
* lokalisierten Wochennummer, begrenzt auf den aktuellen Monat.
*
* In ISO-konformen Kalendersystemem ist der Wertebereich
* {@code 0/1-4/5}, in anderen Wochendefinitionen kann der Maximalwert
* auch {@code 6} sein. Im Unterschied zu {@link #weekOfMonth()} kann
* diese Woche am Anfang oder Ende eines Monats verkürzt sein,
* weil kein Wochenumbruch stattfindet. Falls die Woche am Anfang
* eines Monats eigentlich in den Vormonat bzw. die Woche am Ende
* eines Monats eigentlich in den Folgemonat gehört, bekommt
* die Woche für den Monatsanfang den Wert {@code 0} bzw. für
* das Monatsende den hochgezählten Maximalwert. Dieses Verhalten
* entspricht der Woche des Monats in den traditionellen Kalenderklassen
* des JDK, ist aber eine vereinfachende Abweichung vom CLDR-Standard.
*
* Achtung: Dieses Element arbeitet beim Setzen von Werten fehlertolerant
* im Nachsichtigkeitsmodus ({@code isLenient() == true}).
*
* @return week of month within the limits of calendar month
* @see #boundedWeekOfYear()
*/
public AdjustableElement boundedWeekOfMonth() {
return this.boundWomElement;
}
/**
* Defines a chronological condition if a date matches a weekend.
*
* Example:
*
*
* PlainDate date = new PlainDate(2013, 3, 30); // Saturday
* System.out.println(date.matches(Weekmodel.ISO.weekend()));
* // Output: true
*
* Locale yemen = new Locale("ar", "YE");
* System.out.println(date.matches(Weekmodel.of(yemen).weekend()));
* // Output: false (in Yemen the weekend matches Thursday and Friday)
*
*
* @return test for weekend
*/
/*[deutsch]
* Definiert eine Bedingung, ob ein Datum am Wochenende liegt.
*
* Beispiel:
*
*
* PlainDate date = new PlainDate(2013, 3, 30); // Samstag
* System.out.println(date.matches(Weekmodel.ISO.weekend()));
* // Ausgabe: true
*
* Locale yemen = new Locale("ar", "YE");
* System.out.println(date.matches(Weekmodel.of(yemen).weekend()));
* // Ausgabe: false (im Jemen ist das Wochenende Donnerstag und Freitag)
*
*
* @return test for weekend
*/
public ChronoCondition weekend() {
return this.weekendCondition;
}
/**
* Compares on the base of internal week rules.
*/
/*[deutsch]
* Vergleicht auf Basis der internen Wochenregeln.
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (obj instanceof Weekmodel) {
Weekmodel that = (Weekmodel) obj;
return (
(this.firstDayOfWeek == that.firstDayOfWeek)
&& (this.minimalDaysInFirstWeek == that.minimalDaysInFirstWeek)
&& (this.startOfWeekend == that.startOfWeekend)
&& (this.endOfWeekend == that.endOfWeekend)
);
} else {
return false;
}
}
/**
* Defines the hash value.
*/
/*[deutsch]
* Liefert den Hash-Code.
*/
@Override
public int hashCode() {
return (
17 * this.firstDayOfWeek.name().hashCode()
+ 37 * this.minimalDaysInFirstWeek
);
}
/**
* Debugging-support.
*/
/*[deutsch]
* Debugging-Unterstützung.
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder(64);
sb.append(this.getClass().getName());
sb.append("[firstDayOfWeek=");
sb.append(this.firstDayOfWeek);
sb.append(",minimalDaysInFirstWeek=");
sb.append(this.minimalDaysInFirstWeek);
sb.append(",startOfWeekend=");
sb.append(this.startOfWeekend);
sb.append(",endOfWeekend=");
sb.append(this.endOfWeekend);
sb.append(']');
return sb.toString();
}
/**
* Liefert alle definierten chronologischen Elemente.
*
* @return unmodifiable set
*/
Set> getElements() {
return this.elements;
}
/**
* Ermittelt den Wochentag.
*
* @param utcDays count of days relative to [1972-01-01]
* @return day of week as enum
*/
static Weekday getDayOfWeek(long utcDays) {
return Weekday.valueOf(MathUtils.floorModulo(utcDays + 5, 7) + 1);
}
/**
* @serialData Uses
* a dedicated serialization form as proxy. The format
* is bit-compressed. Two data bytes are used, sometimes
* also three. The first byte contains in the four most
* significant bits the type-ID {@code 3}. If the weekend
* is not saturday and sunday then the four least significant
* bits will be set to {@code 1}. The second byte has in the
* four most significant bits the first day of week, in the
* other four bits the minimum days of first calendar week.
* If there is no standard weekend then a third byte follows
* which contains in the four most significant bits the start
* and the four least significant bits the end of weekend.
*
* Schematic algorithm:
*
*
boolean isoWeekend = (
(getStartOfWeekend() == Weekday.SATURDAY)
&& (getEndOfWeekend() == Weekday.SUNDAY)
);
int header = 3;
header <<= 4;
if (!isoWeekend) {
header |= 1;
}
out.writeByte(header);
int state = getFirstDayOfWeek().getValue();
state <<= 4;
state |= getMinimalDaysInFirstWeek();
out.writeByte(state);
if (!isoWeekend) {
state = getStartOfWeekend().getValue();
state <<= 4;
state |= getEndOfWeekend().getValue();
out.writeByte(state);
}
*
* @return replacement object in serialization graph
*/
private Object writeReplace() {
return new SPX(this, SPX.WEEKMODEL_TYPE);
}
/**
* @serialData Blocks because a serialization proxy is required.
* @param in object input stream
* @throws InvalidObjectException (always)
*/
private void readObject(ObjectInputStream in)
throws IOException {
throw new InvalidObjectException("Serialization proxy required.");
}
//~ Innere Klassen ----------------------------------------------------
private class DayOfWeekElement
extends AbstractDateElement
implements NavigableElement,
NumericalElement,
TextElement {
//~ Statische Felder/Initialisierungen ----------------------------
private static final long serialVersionUID = 1945670789283677398L;
//~ Konstruktoren -------------------------------------------------
DayOfWeekElement() {
super("LOCAL_DAY_OF_WEEK");
}
//~ Methoden ------------------------------------------------------
@Override
public Class getType() {
return Weekday.class;
}
@Override
public char getSymbol() {
return 'e';
}
@Override
public int numerical(Weekday dayOfWeek) {
return Integer.valueOf(dayOfWeek.getValue(Weekmodel.this));
}
@Override
public int compare(
ChronoDisplay o1,
ChronoDisplay o2
) {
int i1 = o1.get(this).getValue(Weekmodel.this);
int i2 = o2.get(this).getValue(Weekmodel.this);
return ((i1 < i2) ? -1 : ((i1 == i2) ? 0 : 1));
}
@Override
public Weekday getDefaultMinimum() {
return Weekmodel.this.getFirstDayOfWeek();
}
@Override
public Weekday getDefaultMaximum() {
return Weekmodel.this.getFirstDayOfWeek().roll(6);
}
@Override
public boolean isDateElement() {
return true;
}
@Override
public boolean isTimeElement() {
return false;
}
@Override
public ElementOperator setToNext(Weekday value) {
return new NavigationOperator<>(
this,
ElementOperator.OP_NAV_NEXT,
value
);
}
@Override
public ElementOperator setToPrevious(Weekday value) {
return new NavigationOperator<>(
this,
ElementOperator.OP_NAV_PREVIOUS,
value
);
}
@Override
public ElementOperator setToNextOrSame(Weekday value) {
return new NavigationOperator<>(
this,
ElementOperator.OP_NAV_NEXT_OR_SAME,
value
);
}
@Override
public ElementOperator setToPreviousOrSame(Weekday value) {
return new NavigationOperator<>(
this,
ElementOperator.OP_NAV_PREVIOUS_OR_SAME,
value
);
}
@Override
public void print(
ChronoDisplay context,
Appendable buffer,
AttributeQuery attributes
) throws IOException {
OutputContext oc = attributes.get(Attributes.OUTPUT_CONTEXT, OutputContext.FORMAT);
buffer.append(this.accessor(attributes, oc).print(context.get(this)));
}
@Override
public Weekday parse(
CharSequence text,
ParsePosition status,
AttributeQuery attributes
) {
int index = status.getIndex();
OutputContext oc = attributes.get(Attributes.OUTPUT_CONTEXT, OutputContext.FORMAT);
Weekday result = this.accessor(attributes, oc).parse(text, status, this.getType(), attributes);
if ((result == null) && attributes.get(Attributes.PARSE_MULTIPLE_CONTEXT, Boolean.TRUE)) {
status.setErrorIndex(-1);
status.setIndex(index);
oc = ((oc == OutputContext.FORMAT) ? OutputContext.STANDALONE : OutputContext.FORMAT);
result = this.accessor(attributes, oc).parse(text, status, this.getType(), attributes);
}
return result;
}
@Override
protected boolean doEquals(BasicElement> obj) {
return this.getModel().equals(((DayOfWeekElement) obj).getModel());
}
@Override
protected > ElementRule derive(Chronology chronology) {
if (chronology.isRegistered(CALENDAR_DATE)) {
return new DRule<>(this);
} else {
return null;
}
}
@Override
protected ChronoElement> getParent() {
return PlainDate.DAY_OF_WEEK;
}
private TextAccessor accessor(
AttributeQuery attributes,
OutputContext oc
) {
CalendarText cnames =
CalendarText.getIsoInstance(attributes.get(Attributes.LANGUAGE, Locale.ROOT));
return cnames.getWeekdays(
attributes.get(Attributes.TEXT_WIDTH, TextWidth.WIDE),
oc);
}
private Weekmodel getModel() {
return Weekmodel.this;
}
private Object readResolve() throws ObjectStreamException {
return Weekmodel.this.localDayOfWeek();
}
}
private static class DRule>
implements ElementRule {
//~ Instanzvariablen ----------------------------------------------
final DayOfWeekElement element;
//~ Konstruktoren -------------------------------------------------
private DRule(DayOfWeekElement element) {
super();
this.element = element;
}
//~ Methoden ------------------------------------------------------
@Override
public boolean isValid(
T context,
Weekday value
) {
return (value != null);
}
@Override
public Weekday getMinimum(T context) {
return this.element.getDefaultMinimum();
}
@Override
public Weekday getMaximum(T context) {
return this.element.getDefaultMaximum();
}
@Override
public ChronoElement> getChildAtFloor(T context) {
return this.getChild(context);
}
@Override
public ChronoElement> getChildAtCeiling(T context) {
return this.getChild(context);
}
private ChronoElement> getChild(T context) {
if (context.contains(PlainTime.WALL_TIME)) {
return PlainTime.WALL_TIME;
} else {
return null;
}
}
@Override
public Weekday getValue(T context) {
return getDayOfWeek(
context.get(CALENDAR_DATE).getDaysSinceUTC());
}
@Override
public T withValue(
T context,
Weekday value,
boolean lenient
) {
PlainDate date = context.get(CALENDAR_DATE);
long utcDays = date.getDaysSinceUTC();
Weekday current = getDayOfWeek(utcDays);
if (value == current) {
return context;
}
int old = current.getValue(this.element.getModel());
int neu = value.getValue(this.element.getModel());
date = date.withDaysSinceUTC(utcDays + neu - old);
return context.with(CALENDAR_DATE, date);
}
}
private class CalendarWeekElement
extends AbstractDateElement
implements NumericalElement {
//~ Statische Felder/Initialisierungen ----------------------------
private static final long serialVersionUID = -5936254509996557266L;
//~ Instanzvariablen ----------------------------------------------
/**
* @serial 0 = CALENDAR_WEEK_OF_YEAR, 1 = CALENDAR_WEEK_OF_MONTH,
* 2 = BOUNDED_WEEK_OF_YEAR, 3 = BOUNDED_WEEK_OF_MONTH
*/
private final int category;
//~ Konstruktoren -------------------------------------------------
CalendarWeekElement(
String name,
int category
) {
super(name);
this.category = category;
}
//~ Methoden ------------------------------------------------------
@Override
public char getSymbol() {
switch (this.category) {
case CALENDAR_WEEK_OF_YEAR:
return 'w';
case CALENDAR_WEEK_OF_MONTH:
return 'W';
default:
return super.getSymbol();
}
}
@Override
public Class getType() {
return Integer.class;
}
@Override
public int numerical(Integer value) {
return value.intValue();
}
@Override
public Integer getDefaultMinimum() {
return Integer.valueOf(1);
}
@Override
public Integer getDefaultMaximum() {
return Integer.valueOf(this.isYearRelated() ? 52 : 5);
}
@Override
public boolean isDateElement() {
return true;
}
@Override
public boolean isTimeElement() {
return false;
}
@Override
public boolean isLenient() {
return true;
}
@Override
protected boolean doEquals(BasicElement> obj) {
return this.getModel().equals(((CalendarWeekElement) obj).getModel());
}
@Override
protected ChronoElement> getParent() {
return WEEKDAY_IN_MONTH; // Basiseinheit Wochen!
}
@Override
protected >
ElementRule derive(Chronology chronology) {
if (chronology.isRegistered(CALENDAR_DATE)) {
if (this.isBounded()) {
return new BWRule<>(this);
} else {
return new CWRule<>(this);
}
}
return null;
}
private Object readResolve() throws ObjectStreamException {
Weekmodel model = this.getModel();
switch (this.category) {
case CALENDAR_WEEK_OF_YEAR:
return model.weekOfYear();
case CALENDAR_WEEK_OF_MONTH:
return model.weekOfMonth();
case BOUNDED_WEEK_OF_YEAR:
return model.boundedWeekOfYear();
case BOUNDED_WEEK_OF_MONTH:
return model.boundedWeekOfMonth();
default:
throw new InvalidObjectException(
"Unknown category: " + this.category);
}
}
private Weekmodel getModel() {
return Weekmodel.this;
}
private boolean isYearRelated() {
return ((this.category % 2) == 0);
}
private boolean isBounded() {
return (this.category >= 2);
}
}
private static class CWRule>
implements ElementRule {
//~ Instanzvariablen ----------------------------------------------
private final CalendarWeekElement owner;
//~ Konstruktoren -------------------------------------------------
private CWRule(CalendarWeekElement owner) {
super();
this.owner = owner;
}
//~ Methoden ------------------------------------------------------
@Override
public Integer getMinimum(T context) {
return Integer.valueOf(1);
}
@Override
public Integer getMaximum(T context) {
PlainDate date = context.get(CALENDAR_DATE);
return Integer.valueOf(this.getMaxCalendarWeek(date));
}
@Override
public ChronoElement> getChildAtFloor(T context) {
return this.getChild();
}
@Override
public ChronoElement> getChildAtCeiling(T context) {
return this.getChild();
}
private ChronoElement> getChild() {
return this.owner.getModel().localDayOfWeek();
}
@Override
public Integer getValue(T context) {
PlainDate date = context.get(CALENDAR_DATE);
return Integer.valueOf(this.getCalendarWeek(date));
}
@Override
public boolean isValid(
T context,
Integer value
) {
if (value == null) {
return false;
}
int v = value.intValue();
if (
this.owner.isYearRelated()
&& (v >= 1)
&& (v <= 52)
) {
return true;
}
if (!this.owner.isYearRelated() || (v == 53)) {
PlainDate date = context.get(CALENDAR_DATE);
return ((v >= 1) && (v <= this.getMaxCalendarWeek(date)));
} else {
return false;
}
}
@Override
public T withValue(
T context,
Integer value,
boolean lenient
) {
int v = value.intValue();
PlainDate date = context.get(CALENDAR_DATE);
if (
!lenient
&& !this.isValid(context, value)
) {
throw new IllegalArgumentException(
"Invalid value: " + v + " (context=" + context + ")");
}
return context.with(
CALENDAR_DATE,
this.setCalendarWeek(date, v)
);
}
// letzte Kalenderwoche im Jahr/Monat
private int getMaxCalendarWeek(PlainDate date) {
int scaledDay = (
this.owner.isYearRelated()
? date.getDayOfYear()
: date.getDayOfMonth());
int wCurrent = getFirstCalendarWeekAsDay(date, 0);
if (wCurrent <= scaledDay) {
int wNext =
getFirstCalendarWeekAsDay(date, 1)
+ getLengthOfYM(date, 0);
return (wNext - wCurrent) / 7;
} else {
int wPrevious = getFirstCalendarWeekAsDay(date, -1);
wCurrent = wCurrent + getLengthOfYM(date, -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(
PlainDate date,
int shift // -1 = Vorjahr/-monat, 0 = aktuell, +1 = Folgejahr/-monat
) {
Weekday wd = this.getWeekdayStart(date, shift);
Weekmodel model = this.owner.getModel();
int dow = wd.getValue(model);
return (
(dow <= 8 - model.getMinimalDaysInFirstWeek())
? 2 - dow
: 9 - dow
);
}
// Wochentag des ersten Tags des Jahres/Monats
private Weekday getWeekdayStart(
PlainDate date,
int shift // -1 = Vorjahr/-monat, 0 = aktuell, +1 = Folgejahr/-monat
) {
if (this.owner.isYearRelated()) {
return Weekday.valueOf(
GregorianMath.getDayOfWeek(date.getYear() + shift, 1, 1));
} else {
int year = date.getYear();
int month = date.getMonth() + shift;
if (month == 0) {
month = 12;
year--;
} else if (month == 13) {
month = 1;
year++;
}
return Weekday.valueOf(
GregorianMath.getDayOfWeek(year, month, 1));
}
}
// Länge eines Jahres/Monats in Tagen
private int getLengthOfYM(
PlainDate date,
int shift // -1 = Vorjahr/-monat, 0 = aktuell, +1 = Folgejahr/-monat
) {
if (this.owner.isYearRelated()) {
return (
GregorianMath.isLeapYear(date.getYear() + shift)
? 366
: 365
);
} else {
int year = date.getYear();
int month = date.getMonth() + shift;
if (month == 0) {
month = 12;
year--;
} else if (month == 13) {
month = 1;
year++;
}
return GregorianMath.getLengthOfMonth(year, month);
}
}
private int getCalendarWeek(PlainDate date) {
int scaledDay = (
this.owner.isYearRelated()
? date.getDayOfYear()
: date.getDayOfMonth());
int wCurrent = getFirstCalendarWeekAsDay(date, 0);
if (wCurrent <= scaledDay) {
int result = ((scaledDay - wCurrent) / 7) + 1;
if (
(result >= 53)
|| (!this.owner.isYearRelated() && (result >= 5))
) {
int wNext =
getFirstCalendarWeekAsDay(date, 1)
+ getLengthOfYM(date, 0);
if (wNext <= scaledDay) {
result = 1;
}
}
return result;
} else {
int wPrevious = getFirstCalendarWeekAsDay(date, -1);
int dayCurrent = scaledDay + getLengthOfYM(date, -1);
return ((dayCurrent - wPrevious) / 7) + 1;
}
}
private PlainDate setCalendarWeek(
PlainDate date,
int value
) {
int old = this.getCalendarWeek(date);
if (value == old) {
return date;
} else {
int delta = 7 * (value - old);
return date.withDaysSinceUTC(date.getDaysSinceUTC() + delta);
}
}
}
private static class BWRule>
implements ElementRule {
//~ Instanzvariablen ----------------------------------------------
private final CalendarWeekElement owner;
//~ Konstruktoren -------------------------------------------------
private BWRule(CalendarWeekElement owner) {
super();
this.owner = owner;
}
//~ Methoden ------------------------------------------------------
@Override
public Integer getValue(T context) {
PlainDate date = context.get(CALENDAR_DATE);
return Integer.valueOf(this.getWeek(date));
}
@Override
public Integer getMinimum(T context) {
PlainDate date = context.get(CALENDAR_DATE);
return Integer.valueOf(this.getMinWeek(date));
}
@Override
public Integer getMaximum(T context) {
PlainDate date = context.get(CALENDAR_DATE);
return Integer.valueOf(this.getMaxWeek(date));
}
@Override
public ChronoElement> getChildAtFloor(T context) {
return this.getChild(context, false);
}
@Override
public ChronoElement> getChildAtCeiling(T context) {
return this.getChild(context, true);
}
private ChronoElement> getChild(
T context,
boolean ceiling
) {
PlainDate date = context.get(CALENDAR_DATE);
ChronoElement dow =
this.owner.getModel().localDayOfWeek();
int weeknum = this.getValue(context).intValue();
if (ceiling) {
if (weeknum >= (this.owner.isYearRelated() ? 52 : 4)) {
PlainDate max = date.with(dow, context.getMaximum(dow));
if (this.owner.isYearRelated()) {
if (max.getDayOfYear() < date.getDayOfYear()) {
return PlainDate.DAY_OF_YEAR;
}
} else if (max.getDayOfMonth() < date.getDayOfMonth()) {
return PlainDate.DAY_OF_MONTH;
}
}
} else if (weeknum == 0) {
PlainDate min = date.with(dow, context.getMinimum(dow));
if (this.owner.isYearRelated()) {
if (min.getDayOfYear() > date.getDayOfYear()) {
return PlainDate.DAY_OF_YEAR;
}
} else if (min.getDayOfMonth() > date.getDayOfMonth()) {
return PlainDate.DAY_OF_MONTH;
}
}
return dow;
}
@Override
public boolean isValid(
T context,
Integer value
) {
if (value == null) {
return false;
}
int v = value.intValue();
PlainDate date = context.get(CALENDAR_DATE);
return (
(v >= this.getMinWeek(date))
&& (v <= this.getMaxWeek(date))
);
}
@Override
public T withValue(
T context,
Integer value,
boolean lenient
) {
int v = value.intValue();
PlainDate date = context.get(CALENDAR_DATE);
if (
!lenient
&& !this.isValid(context, value)
) {
throw new IllegalArgumentException(
"Invalid value: " + v + " (context=" + context + ")");
}
return context.with(
CALENDAR_DATE,
this.setWeek(date, v)
);
}
private int getWeek(PlainDate date) {
return this.getWeek(date, 0);
}
private int getMinWeek(PlainDate date) {
return this.getWeek(date, -1);
}
private int getMaxWeek(PlainDate date) {
return this.getWeek(date, 1);
}
private int getWeek(
PlainDate date,
int mode // -1 = Jahres-/Monatsanfang, 0 = aktueller Tag, 1 = Ende
) {
int scaledDay = (
this.owner.isYearRelated()
? date.getDayOfYear()
: date.getDayOfMonth());
Weekday wd = getDayOfWeek(date.getDaysSinceUTC() - scaledDay + 1);
int dow = wd.getValue(this.owner.getModel());
int wstart = (
(dow <= 8 - this.owner.getModel().getMinimalDaysInFirstWeek())
? 2 - dow
: 9 - dow
);
int refday;
switch (mode) {
case -1:
refday = 1;
break;
case 0:
refday = scaledDay;
break;
case 1:
refday = this.getLengthOfYM(date);
break;
default:
throw new AssertionError("Unexpected: " + mode);
}
return MathUtils.floorDivide((refday - wstart), 7) + 1;
}
private PlainDate setWeek(
PlainDate date,
int value
) {
int old = this.getWeek(date);
if (value == old) {
return date;
} else {
int delta = 7 * (value - old);
return date.withDaysSinceUTC(date.getDaysSinceUTC() + delta);
}
}
// Länge eines Jahres/Monats in Tagen
private int getLengthOfYM(PlainDate date) {
if (this.owner.isYearRelated()) {
return (GregorianMath.isLeapYear(date.getYear()) ? 366 : 365);
} else {
return GregorianMath.getLengthOfMonth(
date.getYear(),
date.getMonth()
);
}
}
}
}