/* * ----------------------------------------------------------------------- * Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/> * ----------------------------------------------------------------------- * This file (ThaiSolarCalendar.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.CalendarUnit; import net.time4j.GeneralTimestamp; import net.time4j.Moment; import net.time4j.Month; import net.time4j.PlainDate; import net.time4j.PlainTime; import net.time4j.SystemClock; import net.time4j.Weekday; import net.time4j.Weekmodel; import net.time4j.base.GregorianMath; import net.time4j.base.TimeSource; import net.time4j.calendar.service.GenericDatePatterns; import net.time4j.calendar.service.StdEnumDateElement; import net.time4j.calendar.service.StdIntegerDateElement; import net.time4j.calendar.service.StdWeekdayElement; import net.time4j.engine.AttributeQuery; import net.time4j.engine.CalendarEra; import net.time4j.engine.Calendrical; import net.time4j.engine.ChronoDisplay; import net.time4j.engine.ChronoElement; import net.time4j.engine.ChronoEntity; import net.time4j.engine.ChronoException; import net.time4j.engine.ChronoMerger; import net.time4j.engine.Chronology; import net.time4j.engine.DisplayStyle; import net.time4j.engine.ElementRule; import net.time4j.engine.EpochDays; import net.time4j.engine.FormattableElement; import net.time4j.engine.StartOfDay; import net.time4j.engine.TimeAxis; import net.time4j.engine.UnitRule; import net.time4j.engine.ValidationElement; import net.time4j.format.Attributes; import net.time4j.format.CalendarType; import net.time4j.format.Leniency; import net.time4j.format.LocalizedPatternSupport; import net.time4j.tz.TZID; import net.time4j.tz.Timezone; import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; /** * <p>The Thai solar calendar calendar used in Thailand uses as only difference to western gregorian * calendar a different year numbering with the Buddhist era mainly. </p> * * <p>This class supports the calendar reform of 1940/41 after that the begin of year moved from 1st of April * to 1st of January. See also: <a href="https://en.wikipedia.org/wiki/Thai_solar_calendar">Wikipedia</a>. </p> * * <p>Following elements which are declared as constants are registered by * this class: </p> * * <ul> * <li>{@link #DAY_OF_WEEK}</li> * <li>{@link #DAY_OF_MONTH}</li> * <li>{@link #DAY_OF_YEAR}</li> * <li>{@link #MONTH_OF_YEAR}</li> * <li>{@link #YEAR_OF_ERA}</li> * <li>{@link #ERA}</li> * </ul> * * <p>Furthermore, all elements defined in {@code EpochDays} and {@link CommonElements} are supported. </p> * * <p>The date arithmetic uses the ISO-compatible class {@code CalendarUnit} and always delegate to * the ISO-equivalent {@code PlainDate} due to the fact that this calendar has always been a derivate * of the western gregorian calendar (years are intended as approximate solar years). However, if * applied on dates around the year 1940 where the begin of year was moved from 1st of April to 1st of * January users might observe some changes of buddhist year numbering which appear strange on first * glance. Example: {@code ThaiSolarCalendar.ofBuddhist(2482, FEBRUARY, 1).plus(2, CalendarUnit.YEARS)} * results in {@code ThaiSolarCalendar.ofBuddhist(2485, FEBRUARY, 1)}. The addition of two (solar) years * corresponds to the addition of apparently three buddhist years in this edge case. If translated to * its ISO-equivalent the reason is clear: [1940-02-01] + 2 years = [1942-02-01]. </p> * * @author Meno Hochschild * @since 3.19/4.15 * @doctags.concurrency {immutable} */ /*[deutsch] * Der Thai-Kalender wird in Thailand verwendet und hat als einzige Differenz * zum gregorianischen Kalender eine andere Jahreszählung, indem als Ausgangspunkt gewöhnlich * die buddhistische Ära benutzt wird. </p> * * <p>Diese Klasse unterstützt die Kalenderreform von 1940/41, als der Beginn des Jahres vom ersten * April auf den ersten Januar vorverlegt wurde. Siehe auch: * <a href="https://en.wikipedia.org/wiki/Thai_solar_calendar">Wikipedia</a>. </p> * * <p>Registriert sind folgende als Konstanten deklarierte Elemente: </p> * * <ul> * <li>{@link #DAY_OF_WEEK}</li> * <li>{@link #DAY_OF_MONTH}</li> * <li>{@link #DAY_OF_YEAR}</li> * <li>{@link #MONTH_OF_YEAR}</li> * <li>{@link #YEAR_OF_ERA}</li> * <li>{@link #ERA}</li> * </ul> * * <p>Au&slig;erdem werden alle Elemente von {@code EpochDays} und {@link CommonElements} unterstützt. </p> * * <p>Die Datumsarithmetik benutzt die ISO-kompatible Klasse {@code CalendarUnit} und delegiert immer an * das ISO-Gegenstück {@code PlainDate}, weil dieser Kalender nur eine Abwandlung des westlichen * gregorianischen Kalenders darstellt und Jahre als genäherte Sonnenjahre versteht. Wenn allerdings * die Datumsarithmetik auf Thai-Datumsangaben um das Jahr 1940 herum angewandt wird (als der Beginn des * Jahres vom ersten April auf den ersten Januar vorgezogen wurde), sind auf den ersten Blick unerwartete * Änderungen der buddhistischen Jahreszählung möglich. Beispiel: * {@code ThaiSolarCalendar.ofBuddhist(2482, FEBRUARY, 1).plus(2, CalendarUnit.YEARS)} * ergibt {@code ThaiSolarCalendar.ofBuddhist(2485, FEBRUARY, 1). Die Addition von zwei * (Sonnen-)Jahren entspricht in diesem Einzelfall der Addition von scheinbar drei * buddhistischen Jahren. Wenn die Datumsangaben zu ISO übersetzt werden, ist der * Grund sofort klar: [1940-02-01] + 2 years = [1942-02-01] </p> * * @author Meno Hochschild * @since 3.19/4.15 * @doctags.concurrency {immutable} */ @CalendarType("buddhist") public final class ThaiSolarCalendar extends Calendrical<CalendarUnit, ThaiSolarCalendar> implements LocalizedPatternSupport { //~ Statische Felder/Initialisierungen -------------------------------- private static final PlainDate MIN_ISO = PlainDate.of(-542, 4, 1); /** * <p>Represents the Thai era. </p> */ /*[deutsch] * <p>Repräsentiert die Thai-Ära. </p> */ @FormattableElement(format = "G") public static final ChronoElement<ThaiSolarEra> ERA = new StdEnumDateElement<>("ERA", ThaiSolarCalendar.class, ThaiSolarEra.class, 'G'); /** * <p>Represents the Thai year, usually in buddhist counting. </p> */ /*[deutsch] * <p>Repräsentiert das Thai-Jahr, meistens in buddhistischer Zählweise. </p> */ @FormattableElement(format = "y") public static final StdCalendarElement<Integer, ThaiSolarCalendar> YEAR_OF_ERA = new StdIntegerDateElement<>( "YEAR_OF_ERA", ThaiSolarCalendar.class, 1, GregorianMath.MAX_YEAR + 543, 'y', null, null); /** * <p>Represents the month. </p> */ /*[deutsch] * <p>Repräsentiert den Monat. </p> */ @FormattableElement(format = "M", standalone = "L") public static final StdCalendarElement<Month, ThaiSolarCalendar> MONTH_OF_YEAR = new StdEnumDateElement<>( "MONTH_OF_YEAR", ThaiSolarCalendar.class, Month.class, 'M'); /** * <p>Represents the day of month. </p> */ /*[deutsch] * <p>Repräsentiert den Tag des Monats. </p> */ @FormattableElement(format = "d") public static final StdCalendarElement<Integer, ThaiSolarCalendar> DAY_OF_MONTH = new StdIntegerDateElement<>("DAY_OF_MONTH", ThaiSolarCalendar.class, 1, 31, 'd'); /** * <p>Represents the day of year. </p> */ /*[deutsch] * <p>Repräsentiert den Tag des Jahres. </p> */ @FormattableElement(format = "D") public static final StdCalendarElement<Integer, ThaiSolarCalendar> DAY_OF_YEAR = new StdIntegerDateElement<>("DAY_OF_YEAR", ThaiSolarCalendar.class, 1, 365, 'D'); /** * <p>Represents the day of week. </p> * * <p>If the day-of-week is set to a new value then Time4J handles the calendar week * as starting on Sunday. </p> */ /*[deutsch] * <p>Repräsentiert den Tag der Woche. </p> * * <p>Wenn der Tag der Woche auf einen neuen Wert gesetzt wird, behandelt Time4J die * Kalenderwoche so, daß sie am Sonntag beginnt. </p> */ @FormattableElement(format = "E") public static final StdCalendarElement<Weekday, ThaiSolarCalendar> DAY_OF_WEEK = new StdWeekdayElement<>(ThaiSolarCalendar.class); private static final Map<Object, ChronoElement<?>> CHILDREN; private static final EraYearMonthDaySystem<ThaiSolarCalendar> CALSYS; private static final TimeAxis<CalendarUnit, ThaiSolarCalendar> ENGINE; static { Map<Object, ChronoElement<?>> children = new HashMap<>(); children.put(ERA, YEAR_OF_ERA); children.put(YEAR_OF_ERA, MONTH_OF_YEAR); children.put(MONTH_OF_YEAR, DAY_OF_MONTH); CHILDREN = Collections.unmodifiableMap(children); CALSYS = new Transformer(); TimeAxis.Builder<CalendarUnit, ThaiSolarCalendar> builder = TimeAxis.Builder.setUp( CalendarUnit.class, ThaiSolarCalendar.class, new Merger(), CALSYS) .appendElement( ERA, FieldRule.of(ERA)) .appendElement( YEAR_OF_ERA, FieldRule.of(YEAR_OF_ERA), CalendarUnit.YEARS) .appendElement( MONTH_OF_YEAR, FieldRule.of(MONTH_OF_YEAR), CalendarUnit.MONTHS) .appendElement( CommonElements.RELATED_GREGORIAN_YEAR, new RelatedGregorianYearRule<>(CALSYS, DAY_OF_YEAR)) .appendElement( DAY_OF_MONTH, FieldRule.of(DAY_OF_MONTH), CalendarUnit.DAYS) .appendElement( DAY_OF_YEAR, FieldRule.of(DAY_OF_YEAR), CalendarUnit.DAYS) .appendElement( DAY_OF_WEEK, FieldRule.of(DAY_OF_WEEK), CalendarUnit.DAYS) .appendExtension( new CommonElements.Weekengine( ThaiSolarCalendar.class, DAY_OF_MONTH, DAY_OF_YEAR, getDefaultWeekmodel())); registerUnits(builder); ENGINE = builder.build(); } private static final long serialVersionUID = -6628190121085147706L; //~ Instanzvariablen -------------------------------------------------- private final PlainDate iso; //~ Konstruktoren ----------------------------------------------------- private ThaiSolarCalendar(PlainDate iso) { super(); if (iso.isBefore(MIN_ISO)) { throw new IllegalArgumentException("Before buddhist era: " + iso); } this.iso = iso; } //~ Methoden ---------------------------------------------------------- /** * <p>Creates a new instance of a Thai solar calendar date. </p> * * @param yearOfEra buddhist year of era {@code >= 1} * @param month gregorian month * @param dayOfMonth day of month * @return new instance of {@code ThaiSolarCalendar} * @throws IllegalArgumentException in case of any inconsistencies * @since 3.19/4.15 */ /*[deutsch] * <p>Erzeugt ein neues Thai-Solar-Kalenderdatum. </p> * * @param yearOfEra buddhist year of era {@code >= 1} * @param month gregorian month * @param dayOfMonth day of month * @return new instance of {@code ThaiSolarCalendar} * @throws IllegalArgumentException in case of any inconsistencies * @since 3.19/4.15 */ public static ThaiSolarCalendar ofBuddhist( int yearOfEra, Month month, int dayOfMonth ) { return ThaiSolarCalendar.of(ThaiSolarEra.BUDDHIST, yearOfEra, month.getValue(), dayOfMonth); } /** * <p>Creates a new instance of a Thai solar calendar date. </p> * * @param yearOfEra buddhist year of era {@code >= 1} * @param month gregorian month (1-12) * @param dayOfMonth day of month * @return new instance of {@code ThaiSolarCalendar} * @throws IllegalArgumentException in case of any inconsistencies * @since 3.19/4.15 */ /*[deutsch] * <p>Erzeugt ein neues Thai-Solar-Kalenderdatum. </p> * * @param yearOfEra buddhist year of era {@code >= 1} * @param month gregorian month (1-12) * @param dayOfMonth day of month * @return new instance of {@code ThaiSolarCalendar} * @throws IllegalArgumentException in case of any inconsistencies * @since 3.19/4.15 */ public static ThaiSolarCalendar ofBuddhist( int yearOfEra, int month, int dayOfMonth ) { return ThaiSolarCalendar.of(ThaiSolarEra.BUDDHIST, yearOfEra, month, dayOfMonth); } /** * <p>Creates a new instance of a Thai solar calendar date. </p> * * @param era Thai era * @param yearOfEra Thai year of era {@code >= 1} * @param month gregorian month (1-12) * @param dayOfMonth day of month * @return new instance of {@code ThaiSolarCalendar} * @throws IllegalArgumentException in case of any inconsistencies * @since 3.19/4.15 */ /*[deutsch] * <p>Erzeugt ein neues Thai-Solar-Kalenderdatum. </p> * * @param era Thai era * @param yearOfEra Thai year of era {@code >= 1} * @param month gregorian month (1-12) * @param dayOfMonth day of month * @return new instance of {@code ThaiSolarCalendar} * @throws IllegalArgumentException in case of any inconsistencies * @since 3.19/4.15 */ public static ThaiSolarCalendar of( ThaiSolarEra era, int yearOfEra, int month, int dayOfMonth ) { int prolepticYear = era.toIsoYear(yearOfEra, month); PlainDate iso = PlainDate.of(prolepticYear, month, dayOfMonth); return new ThaiSolarCalendar(iso); } /** * <p>Obtains the current calendar date in system time. </p> * * <p>Convenient short-cut for: {@code SystemClock.inLocalView().now(ThaiSolarCalendar.axis())}. </p> * * @return current calendar date in system time zone using the system clock * @see SystemClock#inLocalView() * @see net.time4j.ZonalClock#now(net.time4j.engine.Chronology) * @since 3.23/4.19 */ /*[deutsch] * <p>Ermittelt das aktuelle Kalenderdatum in der Systemzeit. </p> * * <p>Bequeme Abkürzung für: {@code SystemClock.inLocalView().now(ThaiSolarCalendar.axis())}. </p> * * @return current calendar date in system time zone using the system clock * @see SystemClock#inLocalView() * @see net.time4j.ZonalClock#now(net.time4j.engine.Chronology) * @since 3.23/4.19 */ public static ThaiSolarCalendar nowInSystemTime() { return SystemClock.inLocalView().now(ThaiSolarCalendar.axis()); } /** * <p>Yields the buddhist era. </p> * * @return {@link ThaiSolarEra#BUDDHIST} * @since 3.19/4.15 */ /*[deutsch] * <p>Liefert die buddhistische Ära. </p> * * @return {@link ThaiSolarEra#BUDDHIST} * @since 3.19/4.15 */ public ThaiSolarEra getEra() { return ThaiSolarEra.BUDDHIST; } /** * <p>Yields the buddhist Thai year. </p> * * @return int * @see ThaiSolarEra#BUDDHIST * @since 3.19/4.15 */ /*[deutsch] * <p>Liefert das buddhistische Thai-Jahr. </p> * * @return int * @see ThaiSolarEra#BUDDHIST * @since 3.19/4.15 */ public int getYear() { int isoYear = this.iso.getYear(); if ((isoYear >= 1941) || (this.iso.getMonth() >= 4)) { return isoYear + 543; } else { return isoYear + 542; } } /** * <p>Yields the (gregorian) month. </p> * * @return enum * @since 3.19/4.15 */ /*[deutsch] * <p>Liefert den (gregorianischen) Monat. </p> * * @return enum * @since 3.19/4.15 */ public Month getMonth() { return Month.valueOf(this.iso.getMonth()); } /** * <p>Yields the day of month. </p> * * @return int * @since 3.19/4.15 */ /*[deutsch] * <p>Liefert den Tag des Monats. </p> * * @return int * @since 3.19/4.15 */ public int getDayOfMonth() { return this.iso.getDayOfMonth(); } /** * <p>Determines the day of week. </p> * * @return Weekday * @since 3.19/4.15 */ /*[deutsch] * <p>Ermittelt den Wochentag. </p> * * @return Weekday * @since 3.19/4.15 */ public Weekday getDayOfWeek() { return this.iso.getDayOfWeek(); } /** * <p>Yields the day of year. </p> * * @return int * @since 3.19/4.15 */ /*[deutsch] * <p>Liefert den Tag des Jahres. </p> * * @return int * @since 3.19/4.15 */ public int getDayOfYear() { int doy = this.iso.get(PlainDate.DAY_OF_YEAR); if (this.iso.getYear() < 1941) { if (this.iso.getMonth() >= 4) { doy -= (this.iso.isLeapYear() ? 91 : 90); } else { doy += 275; // 91 + 92 + 92 } } return doy; } /** * <p>Yields the length of current month in days. </p> * * @return int * @since 3.19/4.15 */ /*[deutsch] * <p>Liefert die Länge des aktuellen Monats in Tagen. </p> * * @return int * @since 3.19/4.15 */ public int lengthOfMonth() { return this.iso.lengthOfMonth(); } /** * <p>Yields the length of current year in days. </p> * * @return int * @since 3.19/4.15 */ /*[deutsch] * <p>Liefert die Länge des aktuellen Jahres in Tagen. </p> * * @return int * @since 3.19/4.15 */ public int lengthOfYear() { int isoYear = this.iso.getYear(); if (isoYear >= 1941) { return this.iso.lengthOfYear(); } else if (this.iso.getMonth() >= 4) { if (isoYear == 1940) { return 275; } else { return (GregorianMath.isLeapYear(isoYear + 1) ? 366 : 365); } } else { return (this.iso.isLeapYear() ? 366 : 365); } } /** * <p>Is the year of this date a leap year? </p> * * @return boolean * @since 3.19/4.15 */ /*[deutsch] * <p>Liegt dieses Datum in einem Schaltjahr? </p> * * @return boolean * @since 3.19/4.15 */ public boolean isLeapYear() { return (this.lengthOfYear() == 366); } /** * <p>Creates a new local timestamp with this date and given wall time. </p> * * <p>If the time {@link PlainTime#midnightAtEndOfDay() T24:00} is used * then the resulting timestamp will automatically be normalized such * that the timestamp will contain the following day instead. </p> * * @param time wall time * @return general timestamp as composition of this date and given time * @since 3.19/4.15 */ /*[deutsch] * <p>Erzeugt einen allgemeinen Zeitstempel mit diesem Datum und der angegebenen Uhrzeit. </p> * * <p>Wenn {@link PlainTime#midnightAtEndOfDay() T24:00} angegeben wird, * dann wird der Zeitstempel automatisch so normalisiert, daß er auf * den nächsten Tag verweist. </p> * * @param time wall time * @return general timestamp as composition of this date and given time * @since 3.19/4.15 */ public GeneralTimestamp<ThaiSolarCalendar> at(PlainTime time) { return GeneralTimestamp.of(this, time); } /** * <p>Is equivalent to {@code at(PlainTime.of(hour, minute))}. </p> * * @param hour hour of day in range (0-24) * @param minute minute of hour in range (0-59) * @return general timestamp as composition of this date and given time * @throws IllegalArgumentException if any argument is out of range * @since 3.19/4.15 */ /*[deutsch] * <p>Entspricht {@code at(PlainTime.of(hour, minute))}. </p> * * @param hour hour of day in range (0-24) * @param minute minute of hour in range (0-59) * @return general timestamp as composition of this date and given time * @throws IllegalArgumentException if any argument is out of range * @since 3.19/4.15 */ public GeneralTimestamp<ThaiSolarCalendar> atTime( int hour, int minute ) { return this.at(PlainTime.of(hour, minute)); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } else if (obj instanceof ThaiSolarCalendar) { ThaiSolarCalendar that = (ThaiSolarCalendar) obj; return this.iso.equals(that.iso); } else { return false; } } @Override public int hashCode() { return this.iso.hashCode(); } @Override public String toString() { StringBuilder sb = new StringBuilder(32); sb.append(this.getEra()); sb.append('-'); sb.append(this.getYear()); sb.append('-'); int m = this.getMonth().getValue(); if (m < 10) { sb.append('0'); } sb.append(m); sb.append('-'); int d = this.getDayOfMonth(); if (d < 10) { sb.append('0'); } sb.append(d); return sb.toString(); } /** * <p>Obtains the standard week model of this calendar. </p> * * <p>The thai calendar usually starts on Sunday. </p> * * @return Weekmodel * @since 3.24/4.20 */ /*[deutsch] * <p>Ermittelt das Standardwochenmodell dieses Kalenders. </p> * * <p>Der Thai-Kalender startet normalerweise am Sonntag. </p> * * @return Weekmodel * @since 3.24/4.20 */ public static Weekmodel getDefaultWeekmodel() { return Weekmodel.of(Weekday.SUNDAY, 1); } /** * <p>Returns the associated time axis. </p> * * @return chronology * @since 3.19/4.15 */ /*[deutsch] * <p>Liefert die zugehörige Zeitachse. </p> * * @return chronology * @since 3.19/4.15 */ public static TimeAxis<CalendarUnit, ThaiSolarCalendar> axis() { return ENGINE; } @Override protected TimeAxis<CalendarUnit, ThaiSolarCalendar> getChronology() { return ENGINE; } @Override protected ThaiSolarCalendar getContext() { return this; } // serialization support PlainDate toISO() { return this.iso; } private static void registerUnits(TimeAxis.Builder<CalendarUnit, ThaiSolarCalendar> builder) { Set<CalendarUnit> monthly = EnumSet.range(CalendarUnit.MILLENNIA, CalendarUnit.MONTHS); Set<CalendarUnit> daily = EnumSet.range(CalendarUnit.WEEKS, CalendarUnit.DAYS); for (CalendarUnit unit : CalendarUnit.values()) { builder.appendUnit( unit, new ThaiUnitRule(unit), unit.getLength(), (unit.compareTo(CalendarUnit.WEEKS) < 0) ? monthly : daily ); } } /** * @return replacement object in serialization graph * @serialData Uses <a href="../../../serialized-form.html#net.time4j.calendar.SPX"> * a dedicated serialization form</a> as proxy. The first byte contains * the type-ID {@code 8}. Then the associated gregorian date is written. */ private Object writeReplace() { return new SPX(this, SPX.THAI_SOLAR); } /** * @param in object input stream * @throws InvalidObjectException (always) * @serialData Blocks because a serialization proxy is required. */ private void readObject(ObjectInputStream in) throws IOException { throw new InvalidObjectException("Serialization proxy required."); } //~ Innere Klassen ---------------------------------------------------- private static class Transformer implements EraYearMonthDaySystem<ThaiSolarCalendar> { //~ Methoden ------------------------------------------------------ @Override public boolean isValid( CalendarEra era, int yearOfEra, int monthOfYear, int dayOfMonth ) { try { if ((era instanceof ThaiSolarEra) && (yearOfEra >= 1)) { int prolepticYear = ThaiSolarEra.class.cast(era).toIsoYear(yearOfEra, monthOfYear); return ( (prolepticYear <= GregorianMath.MAX_YEAR) && (monthOfYear >= 1) && (monthOfYear <= 12) && (dayOfMonth >= 1) && (dayOfMonth <= GregorianMath.getLengthOfMonth(prolepticYear, monthOfYear)) ); } } catch (RuntimeException re) { // 1940-anomaly or ArithmeticException => okay, we return false anyway } return false; } @Override public int getLengthOfMonth( CalendarEra era, int yearOfEra, int monthOfYear ) { try { int prolepticYear = ThaiSolarEra.class.cast(era).toIsoYear(yearOfEra, monthOfYear); return GregorianMath.getLengthOfMonth(prolepticYear, monthOfYear); } catch (RuntimeException re) { throw new IllegalArgumentException(re.getMessage(), re); } } @Override public int getLengthOfYear( CalendarEra era, int yearOfEra ) { if (yearOfEra < 1) { throw new IllegalArgumentException("Out of bounds: " + yearOfEra); } else if (era.equals(ThaiSolarEra.BUDDHIST)) { int y = yearOfEra - 543; if (y == 1940) { return 275; } else if (y < 1940) { y++; } return (GregorianMath.isLeapYear(y) ? 366 : 365); } else if (era.equals(ThaiSolarEra.RATTANAKOSIN)) { return (GregorianMath.isLeapYear(yearOfEra + 1782) ? 366 : 365); } else { throw new IllegalArgumentException("Invalid calendar era: " + era); } } @Override public ThaiSolarCalendar transform(long utcDays) { return new ThaiSolarCalendar(PlainDate.of(utcDays, EpochDays.UTC)); } @Override public long transform(ThaiSolarCalendar date) { return date.iso.get(EpochDays.UTC); } @Override public long getMinimumSinceUTC() { return MIN_ISO.getDaysSinceEpochUTC(); } @Override public long getMaximumSinceUTC() { return PlainDate.axis().getCalendarSystem().getMaximumSinceUTC(); } @Override public List<CalendarEra> getEras() { CalendarEra e0 = ThaiSolarEra.RATTANAKOSIN; CalendarEra e1 = ThaiSolarEra.BUDDHIST; return Arrays.asList(e0, e1); } } private static class FieldRule<V extends Comparable<V>> implements ElementRule<ThaiSolarCalendar, V> { //~ Instanzvariablen ---------------------------------------------- private final ChronoElement<V> element; //~ Konstruktoren ------------------------------------------------- private FieldRule(ChronoElement<V> element) { super(); this.element = element; } //~ Methoden ------------------------------------------------------ static <V extends Comparable<V>> FieldRule<V> of(ChronoElement<V> element) { return new FieldRule<>(element); } @Override public V getValue(ThaiSolarCalendar context) { Object result; if (this.element == ERA) { result = context.getEra(); } else if (this.element.equals(YEAR_OF_ERA)) { result = context.getYear(); } else if (this.element.equals(MONTH_OF_YEAR)) { result = context.getMonth(); } else if (this.element.equals(DAY_OF_MONTH)) { result = context.getDayOfMonth(); } else if (this.element.equals(DAY_OF_YEAR)) { result = context.getDayOfYear(); } else if (this.element.equals(DAY_OF_WEEK)) { result = context.getDayOfWeek(); } else { throw new ChronoException("Missing rule for: " + this.element.name()); } return this.element.getType().cast(result); } @Override public V getMinimum(ThaiSolarCalendar context) { Object result; if (this.element == ERA) { result = ThaiSolarEra.BUDDHIST; } else if (Integer.class.isAssignableFrom(this.element.getType())) { result = Integer.valueOf(1); } else if (this.element.equals(MONTH_OF_YEAR)) { result = ((context.iso.getYear() >= 1941) ? Month.JANUARY : Month.APRIL); } else if (this.element.equals(DAY_OF_WEEK)) { result = Weekday.SUNDAY; } else { throw new ChronoException("Missing rule for: " + this.element.name()); } return this.element.getType().cast(result); } @Override public V getMaximum(ThaiSolarCalendar context) { Object result; if (this.element == ERA) { result = ThaiSolarEra.BUDDHIST; } else if (this.element.equals(YEAR_OF_ERA)) { result = Integer.valueOf(GregorianMath.MAX_YEAR + 543); } else if (this.element.equals(MONTH_OF_YEAR)) { result = ((context.getYear() >= 2483) ? Month.DECEMBER : Month.MARCH); } else if (this.element.equals(DAY_OF_MONTH)) { result = Integer.valueOf(context.lengthOfMonth()); } else if (this.element.equals(DAY_OF_YEAR)) { result = Integer.valueOf(context.lengthOfYear()); } else if (this.element.equals(DAY_OF_WEEK)) { result = Weekday.SATURDAY; } else { throw new ChronoException("Missing rule for: " + this.element.name()); } return this.element.getType().cast(result); } @Override public boolean isValid( ThaiSolarCalendar context, V value ) { if (value == null) { return false; } else if (this.element.getType().isEnum()) { if (this.element.equals(MONTH_OF_YEAR) && (context.getYear() == 2483)) { return (Month.class.cast(value).getValue() >= 4); } return true; } V min = this.getMinimum(context); V max = this.getMaximum(context); return ((min.compareTo(value) <= 0) && (value.compareTo(max) <= 0)); } @Override public ThaiSolarCalendar withValue( ThaiSolarCalendar context, V value, boolean lenient ) { if (!this.isValid(context, value)) { throw new IllegalArgumentException("Out of range: " + value); } if (this.element == ERA) { return context; } else if (this.element.equals(YEAR_OF_ERA)) { ThaiSolarCalendar tsc = ThaiSolarCalendar.ofBuddhist(toNumber(value), context.getMonth(), 1); return tsc.with(DAY_OF_MONTH, Math.min(context.getDayOfMonth(), tsc.lengthOfMonth())); } else if (this.element.equals(MONTH_OF_YEAR)) { ThaiSolarCalendar tsc = ThaiSolarCalendar.ofBuddhist(context.getYear(), Month.class.cast(value), 1); return tsc.with(DAY_OF_MONTH, Math.min(context.getDayOfMonth(), tsc.lengthOfMonth())); } else if (this.element.equals(DAY_OF_MONTH)) { PlainDate date = context.iso.with(PlainDate.DAY_OF_MONTH, toNumber(value)); return new ThaiSolarCalendar(date); } else if (this.element.equals(DAY_OF_YEAR)) { int minMonth = ((context.iso.getYear() >= 1941) ? 1 : 4); ThaiSolarCalendar start = ThaiSolarCalendar.ofBuddhist(context.getYear(), minMonth, 1); PlainDate date = start.iso.plus(toNumber(value) - 1, CalendarUnit.DAYS); return new ThaiSolarCalendar(date); } else if (this.element.equals(DAY_OF_WEEK)) { PlainDate date = context.iso.with(getDefaultWeekmodel().localDayOfWeek(), Weekday.class.cast(value)); return new ThaiSolarCalendar(date); } throw new ChronoException("Missing rule for: " + this.element.name()); } // optional @Override public ChronoElement<?> getChildAtFloor(ThaiSolarCalendar context) { return CHILDREN.get(this.element); } // optional @Override public ChronoElement<?> getChildAtCeiling(ThaiSolarCalendar context) { return CHILDREN.get(this.element); } private static int toNumber(Object value) { return Integer.class.cast(value).intValue(); } } private static class ThaiUnitRule implements UnitRule<ThaiSolarCalendar> { //~ Instanzvariablen ---------------------------------------------- private final CalendarUnit unit; //~ Konstruktoren ------------------------------------------------- ThaiUnitRule(CalendarUnit unit) { super(); this.unit = unit; } //~ Methoden ------------------------------------------------------ @Override public ThaiSolarCalendar addTo( ThaiSolarCalendar date, long amount ) { return new ThaiSolarCalendar(date.iso.plus(amount, this.unit)); } @Override public long between( ThaiSolarCalendar start, ThaiSolarCalendar end ) { return this.unit.between(start.iso, end.iso); } } private static class Merger implements ChronoMerger<ThaiSolarCalendar> { //~ Methoden ------------------------------------------------------ @Override public String getFormatPattern( DisplayStyle style, Locale locale ) { return GenericDatePatterns.get("buddhist", style, locale); } @Override public StartOfDay getDefaultStartOfDay() { return StartOfDay.MIDNIGHT; } @Override public ThaiSolarCalendar createFrom( TimeSource<?> clock, AttributeQuery attributes ) { TZID tzid; if (attributes.contains(Attributes.TIMEZONE_ID)) { tzid = attributes.get(Attributes.TIMEZONE_ID); } else if (attributes.get(Attributes.LENIENCY, Leniency.SMART).isLax()) { tzid = Timezone.ofSystem().getID(); } else { return null; } StartOfDay startOfDay = attributes.get(Attributes.START_OF_DAY, this.getDefaultStartOfDay()); return Moment.from(clock.currentTime()).toGeneralTimestamp(ENGINE, tzid, startOfDay).toDate(); } @Override @Deprecated public ThaiSolarCalendar 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 ThaiSolarCalendar createFrom( ChronoEntity<?> entity, AttributeQuery attributes, boolean lenient, boolean preparsing ) { if (entity.contains(PlainDate.COMPONENT)) { return new ThaiSolarCalendar(entity.get(PlainDate.COMPONENT)); } ThaiSolarEra era; if (entity.contains(ERA)) { era = entity.get(ERA); } else if (lenient) { era = ThaiSolarEra.BUDDHIST; } else { entity.with(ValidationElement.ERROR_MESSAGE, "Missing Thai era."); return null; } int yearOfEra = entity.getInt(YEAR_OF_ERA); if (yearOfEra == Integer.MIN_VALUE) { entity.with(ValidationElement.ERROR_MESSAGE, "Missing Thai year."); return null; } if (entity.contains(MONTH_OF_YEAR)) { int month = entity.get(MONTH_OF_YEAR).getValue(); int dom = entity.getInt(DAY_OF_MONTH); if (dom != Integer.MIN_VALUE) { if (CALSYS.isValid(era, yearOfEra, month, dom)) { return ThaiSolarCalendar.of(era, yearOfEra, month, dom); } else { entity.with(ValidationElement.ERROR_MESSAGE, "Invalid Thai calendar date."); } } } else { int doy = entity.getInt(DAY_OF_YEAR); if (doy != Integer.MIN_VALUE) { if (doy > 0) { int offset = ((era == ThaiSolarEra.RATTANAKOSIN) || (yearOfEra < 2484) ? 3 : 0); int year = era.toIsoYear(yearOfEra, 4); int month = 1 + offset; int daycount = 0; while (month <= 12 + offset) { int realYear = year; int realMonth = month; if (realMonth > 12) { if ((era == ThaiSolarEra.BUDDHIST) && (realYear == 1940)) { break; // day-of-year out of bounds } realYear++; realMonth -= 12; } int len = GregorianMath.getLengthOfMonth(realYear, realMonth); if (doy > daycount + len) { month++; daycount += len; } else { int dom = doy - daycount; return ThaiSolarCalendar.of(era, yearOfEra, realMonth, dom); } } } entity.with(ValidationElement.ERROR_MESSAGE, "Invalid Thai calendar date."); } } return null; } @Override public ChronoDisplay preformat( ThaiSolarCalendar context, AttributeQuery attributes ) { return context; } @Override public Chronology<?> preparser() { return null; } } }