/*
* -----------------------------------------------------------------------
* Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/>
* -----------------------------------------------------------------------
* This file (AnnualDate.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.xml;
import net.time4j.Moment;
import net.time4j.Month;
import net.time4j.PlainDate;
import net.time4j.base.GregorianMath;
import net.time4j.base.TimeSource;
import net.time4j.engine.AttributeQuery;
import net.time4j.engine.BridgeChronology;
import net.time4j.engine.ChronoElement;
import net.time4j.engine.ChronoEntity;
import net.time4j.engine.ChronoMerger;
import net.time4j.engine.ChronoOperator;
import net.time4j.engine.Chronology;
import net.time4j.engine.Converter;
import net.time4j.engine.DisplayStyle;
import net.time4j.engine.ElementRule;
import net.time4j.engine.FormattableElement;
import net.time4j.engine.IntElementRule;
import net.time4j.engine.Temporal;
import net.time4j.engine.ThreetenAdapter;
import net.time4j.engine.ValidationElement;
import net.time4j.format.Attributes;
import net.time4j.format.CalendarText;
import net.time4j.format.CalendarType;
import net.time4j.format.Leniency;
import net.time4j.format.LocalizedPatternSupport;
import net.time4j.format.expert.ChronoFormatter;
import net.time4j.format.expert.PatternType;
import net.time4j.tz.Timezone;
import java.io.InvalidObjectException;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.ParseException;
import java.time.MonthDay;
import java.time.chrono.IsoChronology;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalQueries;
import java.util.Locale;
import java.util.Map;
/**
* <p>Represents a combination of month and day-of-month as XML-pendant for {@code xsd:gMonthDay}. </p>
*
* <p>February, the 29th is always valid within the context of this class only. </p>
*
* <p>Following elements which are declared as constants are registered by
* this class: </p>
*
* <ul>
* <li>{@link #DAY_OF_MONTH}</li>
* <li>{@link #MONTH_AS_NUMBER}</li>
* <li>{@link #MONTH_OF_YEAR}</li>
* </ul>
*
* <p>The calendar year is missing. Therefore this class cannot model a complete calendar date like
* {@link PlainDate}. For the same reason, a temporal arithmetic is not defined. The main purpose
* of this class is just modelling partial dates like birthdays etc. Formatting example for localized
* formatting styles: </p>
*
* <pre>
* ChronoFormatter<AnnualDate> usStyle =
* ChronoFormatter.ofStyle(DisplayMode.SHORT, Locale.US, AnnualDate.chronology());
* ChronoFormatter<AnnualDate> germanStyle =
* ChronoFormatter.ofStyle(DisplayMode.SHORT, Locale.GERMANY, AnnualDate.chronology());
* System.out.println("US-format: " + usStyle.format(AnnualDate.of(9, 11))); // US-format: 9/11
* System.out.println("German: " + germanStyle.format(AnnualDate.of(9, 11))); // German: 11.9.
* </pre>
*
* <p>Note: The current annual date can be determined by an expression like:
* {@code AnnualDate current = SystemClock.inLocalView().now(AnnualDate.chronology())}. </p>
*
* @author Meno Hochschild
* @since 3.22/4.18
* @doctags.concurrency {immutable}
*/
/*[deutsch]
* <p>Repräsentiert eine Kombination aus Monat und Tag-des-Monats (Jahrestag) als
* XML-Pendant zu {@code xsd:gMonthDay}. </p>
*
* <p>Der 29. Februar ist nur im Kontext dieser Klasse immer gültig. </p>
*
* <p>Registriert sind folgende als Konstanten deklarierte Elemente: </p>
*
* <ul>
* <li>{@link #DAY_OF_MONTH}</li>
* <li>{@link #MONTH_AS_NUMBER}</li>
* <li>{@link #MONTH_OF_YEAR}</li>
* </ul>
*
* <p>Das Kalenderjahr fehlt. Deshalb kann diese Klasse kein vollständiges Kalenderdatum wie
* {@link PlainDate} modellieren. Aus dem gleichen Grund ist eine Zeitarithmetik nicht definiert.
* Der Hauptzweck dieser Klasse ist daher die Modellierung von kalendarischen Teilangaben wie
* Geburtstagen usw. Formatierungsbeispiel für lokalisierte Formatstile: </p>
*
* <pre>
* ChronoFormatter<AnnualDate> usStyle =
* ChronoFormatter.ofStyle(DisplayMode.SHORT, Locale.US, AnnualDate.chronology());
* ChronoFormatter<AnnualDate> germanStyle =
* ChronoFormatter.ofStyle(DisplayMode.SHORT, Locale.GERMANY, AnnualDate.chronology());
* System.out.println("US-format: " + usStyle.format(AnnualDate.of(9, 11))); // US-format: 9/11
* System.out.println("German: " + germanStyle.format(AnnualDate.of(9, 11))); // German: 11.9.
* </pre>
*
* <p>Hinweis: Der aktuelle Jahrestag kann mit einem Ausdruck wie folgt bestimmt werden:
* {@code AnnualDate current = SystemClock.inLocalView().now(AnnualDate.chronology())}. </p>
*
* @author Meno Hochschild
* @since 3.22/4.18
* @doctags.concurrency {immutable}
*/
@CalendarType("iso8601")
public final class AnnualDate
extends ChronoEntity<AnnualDate>
implements Comparable<AnnualDate>, Temporal<AnnualDate>, ThreetenAdapter, LocalizedPatternSupport, Serializable {
//~ Statische Felder/Initialisierungen --------------------------------
/**
* <p>Element with the calendar month as enum in the value range {@code JANUARY-DECEMBER}). </p>
*/
/*[deutsch]
* <p>Element mit dem Monat als Enum (Wertebereich {@code JANUARY-DECEMBER}). </p>
*/
@FormattableElement(format = "M", standalone = "L")
public static final ChronoElement<Month> MONTH_OF_YEAR = PlainDate.MONTH_OF_YEAR;
/**
* <p>Element with the calendar month in numerical form and the value range
* {@code 1-12}. </p>
* <p/>
* <p>Normally the enum-variant is recommended due to clarity and
* type-safety. The enum-form can also be formatted as text. </p>
*
* @see #MONTH_OF_YEAR
*/
/*[deutsch]
* <p>Element mit dem Monat in Nummernform (Wertebereich {@code 1-12}). </p>
*
* <p>Im allgemeinen empfiehlt sich wegen der Typsicherheit und sprachlichen
* Klarheit die enum-Form, die zudem auch als Text formatierbar ist. </p>
*
* @see #MONTH_OF_YEAR
*/
public static final ChronoElement<Integer> MONTH_AS_NUMBER = PlainDate.MONTH_AS_NUMBER;
/**
* <p>Element with the day of month in the value range {@code 1-28/29/30/31}. </p>
*/
/*[deutsch]
* <p>Element mit dem Tag des Monats (Wertebereich {@code 1-28/29/30/31}). </p>
*/
@FormattableElement(format = "d")
public static final ChronoElement<Integer> DAY_OF_MONTH = PlainDate.DAY_OF_MONTH;
private static final Chronology<AnnualDate> ENGINE =
Chronology.Builder
.setUp(AnnualDate.class, new Merger())
.appendElement(DAY_OF_MONTH, new IntegerElementRule(true))
.appendElement(MONTH_OF_YEAR, new MonthElementRule())
.appendElement(MONTH_AS_NUMBER, new IntegerElementRule(false))
.build();
private static final ChronoFormatter<AnnualDate> PARSER =
ChronoFormatter.setUp(ENGINE, Locale.ROOT).addPattern("--MM-dd", PatternType.CLDR).build();
private static final Chronology<MonthDay> THREETEN;
static {
Converter<MonthDay, AnnualDate> converter =
new Converter<MonthDay, AnnualDate>() {
@Override
public AnnualDate translate(MonthDay source) {
return new AnnualDate(source.getMonthValue(), source.getDayOfMonth());
}
@Override
public MonthDay from(AnnualDate time4j) {
return MonthDay.of(time4j.month, time4j.dayOfMonth);
}
@Override
public Class<MonthDay> getSourceType() {
return MonthDay.class;
}
};
THREETEN = new BridgeChronology<>(converter, ENGINE);
}
private static final long serialVersionUID = 7510648008819092983L;
//~ Instanzvariablen --------------------------------------------------
/**
* @serial the gregorian month in range 1-12
*/
private final int month;
/**
* @serial the day of month in range 1-29/30/31
*/
private final int dayOfMonth;
//~ Konstruktoren -----------------------------------------------------
private AnnualDate(
int month,
int dayOfMonth
) {
super();
this.month = month;
this.dayOfMonth = dayOfMonth;
}
//~ Methoden ----------------------------------------------------------
/**
* <p>Creates a new annual date. </p>
*
* @param month gregorian month as enum
* @param dayOfMonth the day of month in range 1-29/30/31
* @return new annual date
*/
/*[deutsch]
* <p>Erzeugt einen neuen Jahrestag. </p>
*
* @param month gregorian month as enum
* @param dayOfMonth the day of month in range 1-29/30/31
* @return new annual date
*/
public static AnnualDate of(
Month month,
int dayOfMonth
) {
return of(month.getValue(), dayOfMonth);
}
/**
* <p>Creates a new annual date. </p>
*
* @param month gregorian month in range 1-12
* @param dayOfMonth the day of month in range 1-29/30/31
* @return new annual date
*/
/*[deutsch]
* <p>Erzeugt einen neuen Jahrestag. </p>
*
* @param month gregorian month in range 1-12
* @param dayOfMonth the day of month in range 1-29/30/31
* @return new annual date
*/
public static AnnualDate of(
int month,
int dayOfMonth
) {
check(month, dayOfMonth);
return new AnnualDate(month, dayOfMonth);
}
/**
* <p>Converts given JSR-310 type to an annual date. </p>
*
* @param monthDay JSR-310-equivalent
* @return corresponding annual date
* @see #toTemporalAccessor()
*/
/*[deutsch]
* <p>Konvertiert den angegebenen JSR-310-Typ zu einem Jahrestag. </p>
*
* @param monthDay JSR-310-equivalent
* @return corresponding annual date
* @see #toTemporalAccessor()
*/
public static AnnualDate from(MonthDay monthDay){
return new AnnualDate(monthDay.getMonthValue(), monthDay.getDayOfMonth());
}
/**
* <p>Obtains the gregorian month. </p>
*
* @return Month
*/
/*[deutsch]
* <p>Liefert den gregorianischen Monat. </p>
*
* @return Month
*/
public Month getMonth() {
return Month.valueOf(this.month);
}
/**
* <p>Obtains the day of month. </p>
*
* @return int
*/
/*[deutsch]
* <p>Liefert den Tag des Monats. </p>
*
* @return int
*/
public int getDayOfMonth() {
return this.dayOfMonth;
}
@Override
public boolean isAfter(AnnualDate temporal) {
return (this.compareTo(temporal) > 0);
}
@Override
public boolean isBefore(AnnualDate temporal) {
return (this.compareTo(temporal) < 0);
}
@Override
public boolean isSimultaneous(AnnualDate temporal) {
return (this.compareTo(temporal) == 0);
}
@Override
public int compareTo(AnnualDate other) {
if (this.month < other.month) {
return -1;
} else if (this.month > other.month) {
return 1;
} else if (this.dayOfMonth < other.dayOfMonth) {
return -1;
} else if (this.dayOfMonth > other.dayOfMonth) {
return 1;
}
return 0;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (obj instanceof AnnualDate) {
AnnualDate that = (AnnualDate) obj;
return ((this.month == that.month) && (this.dayOfMonth == that.dayOfMonth));
} else {
return false;
}
}
@Override
public int hashCode() {
return (this.month << 16) + this.dayOfMonth;
}
/**
* <p>Yields the full description in the XML-format "--MM-dd". </p>
*
* @return String compatible to lexical space of xsd:gMonthDay
* @see #parseXML(String)
*/
/*[deutsch]
* <p>Liefert die vollständige Beschreibung im XML-Format "--MM-dd". </p>
*
* @return String compatible to lexical space of xsd:gMonthDay
* @see #parseXML(String)
*/
@Override
public String toString() {
return toString(this.month, this.dayOfMonth);
}
/**
* <p>Parses given string to an annual date where the input is in XML-format "--MM-dd". </p>
*
* @param xml string compatible to lexical space of xsd:gMonthDay
* @return AnnualDate
* @throws ParseException if parsing fails
* @see #toString()
*/
/*[deutsch]
* <p>Interpretiert den angegebenen Text als Jahrestag im XML-Format "--MM-dd". </p>
*
* @param xml string compatible to lexical space of xsd:gMonthDay
* @return AnnualDate
* @throws ParseException if parsing fails
* @see #toString()
*/
public static AnnualDate parseXML(String xml) throws ParseException {
return PARSER.parse(xml);
}
/**
* <p>Creates a complete ISO calendar date for given gregorian year. </p>
*
* @param year proleptic iso year [(-999,999,999)-999,999,999]
* @return new or cached calendar date instance
* @throws IllegalArgumentException if the year argument is out of range or if this instance represents
* the 29th of February and is not valid for given year
* @see #isValidDate(int)
*/
/*[deutsch]
* <p>Erzeugt ein vollständiges ISO-Kalenderdatum zum angegebenen gregorianischen Jahr. </p>
*
* @param year proleptic iso year [(-999,999,999)-999,999,999]
* @return new or cached calendar date instance
* @throws IllegalArgumentException if the year argument is out of range or if this instance represents
* the 29th of February and is not valid for given year
* @see #isValidDate(int)
*/
public PlainDate atYear(int year) {
return PlainDate.of(year, this.month, this.dayOfMonth);
}
/**
* <p>Checks if this instance results in a valid ISO calendar date for given gregorian year. </p>
*
* @param year proleptic iso year [(-999,999,999)-999,999,999]
* @return {@code true} if the year argument is out of range or if this instance represents
* the 29th of February and is not valid for given year else {@code false}
* @see #atYear(int)
*/
/*[deutsch]
* <p>Prüft, ob diese Instanz zum angegebenen gregorianischen Jahr ein gültiges
* ISO-Kalenderdatum ergibt. </p>
*
* @param year proleptic iso year [(-999,999,999)-999,999,999]
* @return {@code true} if the year argument is out of range or if this instance represents
* the 29th of February and is not valid for given year else {@code false}
* @see #atYear(int)
*/
public boolean isValidDate(int year) {
return (
(year >= GregorianMath.MIN_YEAR) && (year <= GregorianMath.MAX_YEAR)
&& ((this.month != 2) || (this.dayOfMonth != 29) || GregorianMath.isLeapYear(year))
);
}
@Override
public MonthDay toTemporalAccessor() {
return MonthDay.of(this.month, this.dayOfMonth);
}
/**
* <p>Determines the next possible exact annual date. </p>
*
* <p>If this annual date is a leap day then the next date can be some years later. Example: </p>
*
* <pre>
* System.out.println(PlainDate.of(2015, 2, 28).with(AnnualDate.of(Month.FEBRUARY, 29).asNextEvent()));
* // 2016-02-29
* System.out.println(PlainDate.of(2016, 2, 28).with(AnnualDate.of(Month.FEBRUARY, 29).asNextEvent()));
* // 2016-02-29
* System.out.println(PlainDate.of(2016, 2, 29).with(AnnualDate.of(Month.FEBRUARY, 29).asNextEvent()));
* // 2020-02-29
* </pre>
*
* @return chronological operator
* @see #asNextRoundedEvent()
*/
/*[deutsch]
* <p>Bestimmt den nächstmöglichen exakten Jahrestag. </p>
*
* <p>Wenn dieser Jahrestag ein Schalttag ist, kann das nächste Ereignis Jahre später
* folgen. Beispiel: </p>
*
* <pre>
* System.out.println(PlainDate.of(2015, 2, 28).with(AnnualDate.of(Month.FEBRUARY, 29).asNextEvent()));
* // 2016-02-29
* System.out.println(PlainDate.of(2016, 2, 28).with(AnnualDate.of(Month.FEBRUARY, 29).asNextEvent()));
* // 2016-02-29
* System.out.println(PlainDate.of(2016, 2, 29).with(AnnualDate.of(Month.FEBRUARY, 29).asNextEvent()));
* // 2020-02-29
* </pre>
*
* @return chronological operator
* @see #asNextRoundedEvent()
*/
public ChronoOperator<PlainDate> asNextExactEvent() {
return date -> {
int year = date.getYear();
int month = AnnualDate.this.getMonth().getValue();
int dom = AnnualDate.this.getDayOfMonth();
if ((month < date.getMonth()) || ((month == date.getMonth()) && (dom <= date.getDayOfMonth()))) {
year++;
}
if ((month == 2) && (dom == 29)) {
while (!GregorianMath.isLeapYear(year)) {
year++;
}
}
return PlainDate.of(year, month, dom);
};
}
/**
* <p>Determines the next possible annual date and rounds up to next day if necessary. </p>
*
* <p>If this annual date is a leap day then the next date can be advanced to begin of March. Example: </p>
*
* <pre>
* System.out.println(PlainDate.of(2015, 2, 28).with(AnnualDate.of(Month.FEBRUARY, 29).asNextEvent()));
* // 2015-03-01
* </pre>
*
* @return chronological operator
* @see #asNextExactEvent()
*/
/*[deutsch]
* <p>Bestimmt den nächstmöglichen Jahrestag und rundet notfalls zum nächsten Tag. </p>
*
* <p>Wenn dieser Jahrestag ein Schalttag ist, kann das nächste Ereignis auf den ersten März
* verschoben werden. Beispiel: </p>
*
* <pre>
* System.out.println(PlainDate.of(2015, 2, 28).with(AnnualDate.of(Month.FEBRUARY, 29).asNextEvent()));
* // 2015-03-01
* </pre>
*
* @return chronological operator
* @see #asNextExactEvent()
*/
public ChronoOperator<PlainDate> asNextRoundedEvent() {
return date -> {
int year = date.getYear();
int month = AnnualDate.this.getMonth().getValue();
int dom = AnnualDate.this.getDayOfMonth();
if ((month < date.getMonth()) || ((month == date.getMonth()) && (dom <= date.getDayOfMonth()))) {
year++;
}
if ((month == 2) && (dom == 29) && !GregorianMath.isLeapYear(year)) {
month = 3;
dom = 1;
}
return PlainDate.of(year, month, dom);
};
}
/**
* <p>Yields the associated chronology. </p>
*
* @return the underlying rule engine
*/
/*[deutsch]
* <p>Liefert die assoziierte Chronologie. </p>
*
* @return the underlying rule engine
*/
public static Chronology<AnnualDate> chronology() {
return ENGINE;
}
/**
* <p>Obtains a bridge chronology for the type {@code java.time.MonthDay}. </p>
*
* @return rule engine adapted for the type {@code java.time.MonthDay}
* @see #chronology()
*/
/*[deutsch]
* <p>Liefert eine an den Typ {@code java.time.MonthDay} angepasste Chronologie. </p>
*
* @return rule engine adapted for the type {@code java.time.MonthDay}
* @see #chronology()
*/
public static Chronology<MonthDay> threeten() {
return THREETEN;
}
@Override
protected Chronology<AnnualDate> getChronology() {
return ENGINE;
}
@Override
protected AnnualDate getContext() {
return this;
}
private static void check(
int month,
int dayOfMonth
) {
if ((month < 1) || (month > 12)) {
throw new IllegalArgumentException("Month not in range 1-12: " + month);
} else if ((dayOfMonth < 1) || (dayOfMonth > getMaxDay(month))) {
throw new IllegalArgumentException("Out of bounds: " + toString(month, dayOfMonth));
}
}
private static int getMaxDay(int month) {
switch (month) {
case 2:
return 29;
case 4:
case 6:
case 9:
case 11:
return 30;
default:
return 31;
}
}
private static String toString(
int month,
int dayOfMonth
) {
StringBuilder sb = new StringBuilder();
sb.append("--");
if (month < 10) {
sb.append('0');
}
sb.append(month);
sb.append('-');
if (dayOfMonth < 10) {
sb.append('0');
}
sb.append(dayOfMonth);
return sb.toString();
}
/**
* @return this instance
* @throws InvalidObjectException if the state is inconsistent
* @serialData Checks the consistency
*/
private Object readResolve() throws InvalidObjectException {
try {
check(this.month, this.dayOfMonth);
return this;
} catch (IllegalArgumentException iae) {
throw new InvalidObjectException(iae.getMessage());
}
}
//~ Innere Klassen ----------------------------------------------------
private static class Merger
implements ChronoMerger<AnnualDate> {
//~ Methoden ------------------------------------------------------
@Override
public AnnualDate createFrom(
TimeSource<?> clock,
AttributeQuery attributes
) {
Timezone zone;
if (attributes.contains(Attributes.TIMEZONE_ID)) {
zone = Timezone.of(attributes.get(Attributes.TIMEZONE_ID));
} else if (attributes.get(Attributes.LENIENCY, Leniency.SMART).isLax()) {
zone = Timezone.ofSystem();
} else {
return null;
}
PlainDate date = Moment.from(clock.currentTime()).toZonalTimestamp(zone.getID()).toDate();
return AnnualDate.of(date.getMonth(), date.getDayOfMonth());
}
@Override
public AnnualDate createFrom(
ChronoEntity<?> entity,
AttributeQuery attributes,
boolean preparsing
) {
boolean lenient = attributes.get(Attributes.LENIENCY, Leniency.SMART).isLax();
return this.createFrom(entity, attributes, lenient, preparsing);
}
@Override
public AnnualDate createFrom(
ChronoEntity<?> entity,
AttributeQuery attributes,
boolean lenient,
boolean preparsing
) {
int dom = entity.getInt(DAY_OF_MONTH);
if (dom != Integer.MIN_VALUE) {
int m = entity.getInt(PlainDate.MONTH_AS_NUMBER); // optimization
if ((m == Integer.MIN_VALUE) && entity.contains(MONTH_OF_YEAR)) {
m = entity.get(MONTH_OF_YEAR).getValue();
}
if (m != Integer.MIN_VALUE) {
if ((dom >= 1) && (dom <= getMaxDay(m))) {
if ((m >= 1) && (m <= 12)) {
return new AnnualDate(m, dom);
} else {
entity.with(ValidationElement.ERROR_MESSAGE, "Month out of bounds: " + m);
}
} else {
entity.with(ValidationElement.ERROR_MESSAGE, "Day-of-month out of bounds: " + dom);
}
}
}
return null;
}
@Override
public AnnualDate createFrom(
TemporalAccessor threeten,
AttributeQuery attributes
) {
if (threeten.query(TemporalQueries.chronology()) == IsoChronology.INSTANCE) {
if (threeten.isSupported(ChronoField.MONTH_OF_YEAR)) {
if (threeten.isSupported(ChronoField.DAY_OF_MONTH)) {
int month = threeten.get(ChronoField.MONTH_OF_YEAR);
int dom = threeten.get(ChronoField.DAY_OF_MONTH);
return AnnualDate.of(month, dom);
}
}
}
return null;
}
@Override
public String getFormatPattern(DisplayStyle style, Locale locale) {
Map<String, String> map = CalendarText.getIsoInstance(locale).getTextForms();
String key = null;
switch (style.getStyleValue()) {
case DateFormat.FULL:
key = "F_MMMMd";
break;
case DateFormat.LONG:
key = "F_MMMd";
break;
case DateFormat.MEDIUM:
key = "F_MMd";
break;
case DateFormat.SHORT:
key = "F_Md";
break;
}
String pattern = getFormatPattern(map, key);
return ((pattern == null) ? "MM-dd" : pattern);
}
private static String getFormatPattern(
Map<String, String> map,
String key
) {
if (map.containsKey(key)) {
return map.get(key);
}
switch (key) {
case "F_MMMMd":
return getFormatPattern(map, "F_MMMd");
case "F_MMMd":
return getFormatPattern(map, "F_MMd");
case "F_MMd":
return getFormatPattern(map, "F_Md");
default:
return null;
}
}
}
private static class MonthElementRule
implements ElementRule<AnnualDate, Month> {
//~ Methoden ------------------------------------------------------
@Override
public Month getValue(AnnualDate context) {
return context.getMonth();
}
@Override
public Month getMinimum(AnnualDate context) {
return Month.JANUARY;
}
@Override
public Month getMaximum(AnnualDate context) {
return Month.DECEMBER;
}
@Override
public boolean isValid(
AnnualDate context,
Month value
) {
return (value != null);
}
@Override
public AnnualDate withValue(
AnnualDate context,
Month value,
boolean lenient
) {
if (value == null) {
throw new IllegalArgumentException("Missing new value.");
}
int m = value.getValue();
return new AnnualDate(m, Math.min(context.dayOfMonth, getMaxDay(m)));
}
@Override
public ChronoElement<?> getChildAtFloor(AnnualDate context) {
return DAY_OF_MONTH;
}
@Override
public ChronoElement<?> getChildAtCeiling(AnnualDate context) {
return DAY_OF_MONTH;
}
}
private static class IntegerElementRule
implements IntElementRule<AnnualDate> {
//~ Instanzvariablen ----------------------------------------------
private final boolean daywise;
//~ Konstruktoren -------------------------------------------------
IntegerElementRule(boolean daywise) {
super();
this.daywise = daywise;
}
//~ Methoden ------------------------------------------------------
@Override
public int getInt(AnnualDate context) {
return (this.daywise ? context.dayOfMonth : context.month);
}
@Override
public boolean isValid(
AnnualDate context,
int value
) {
if (value < 1) {
return false;
} else if (this.daywise) {
return (value <= getMaxDay(context.month));
} else {
return (value <= 12);
}
}
@Override
public AnnualDate withValue(
AnnualDate context,
int value,
boolean lenient
) {
if (this.daywise) {
return AnnualDate.of(context.month, value);
} else {
return AnnualDate.of(value, Math.min(context.dayOfMonth, getMaxDay(value)));
}
}
@Override
public Integer getValue(AnnualDate context) {
return Integer.valueOf(this.getInt(context));
}
@Override
public Integer getMinimum(AnnualDate context) {
return Integer.valueOf(1);
}
@Override
public Integer getMaximum(AnnualDate context) {
if (this.daywise) {
return Integer.valueOf(getMaxDay(context.month));
} else {
return Integer.valueOf(12);
}
}
@Override
public boolean isValid(AnnualDate context, Integer value) {
if (value == null) {
return false;
}
return this.isValid(context, value.intValue());
}
@Override
public AnnualDate withValue(
AnnualDate context,
Integer value,
boolean lenient
) {
if (value == null) {
throw new IllegalArgumentException("Missing new value.");
}
return this.withValue(context, value.intValue(), lenient);
}
@Override
public ChronoElement<?> getChildAtFloor(AnnualDate context) {
return (this.daywise ? null : DAY_OF_MONTH);
}
@Override
public ChronoElement<?> getChildAtCeiling(AnnualDate context) {
return (this.daywise ? null : DAY_OF_MONTH);
}
}
}