001/*
002 * Copyright (c) 2007-2013, Stephen Colebourne & Michael Nascimento Santos
003 *
004 * All rights reserved.
005 *
006 * Redistribution and use in source and binary forms, with or without
007 * modification, are permitted provided that the following conditions are met:
008 *
009 *  * Redistributions of source code must retain the above copyright notice,
010 *    this list of conditions and the following disclaimer.
011 *
012 *  * Redistributions in binary form must reproduce the above copyright notice,
013 *    this list of conditions and the following disclaimer in the documentation
014 *    and/or other materials provided with the distribution.
015 *
016 *  * Neither the name of JSR-310 nor the names of its contributors
017 *    may be used to endorse or promote products derived from this software
018 *    without specific prior written permission.
019 *
020 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
021 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
022 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
023 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
024 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
027 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
028 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
029 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
030 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031 */
032package org.threeten.bp.calendar;
033
034import static org.threeten.bp.temporal.ChronoField.YEAR;
035
036import java.io.Serializable;
037import java.util.Arrays;
038import java.util.HashMap;
039import java.util.List;
040import java.util.Locale;
041
042import org.threeten.bp.DateTimeException;
043import org.threeten.bp.LocalDate;
044import org.threeten.bp.temporal.Chrono;
045import org.threeten.bp.temporal.ChronoField;
046import org.threeten.bp.temporal.ChronoLocalDate;
047import org.threeten.bp.temporal.Era;
048import org.threeten.bp.temporal.ISOChrono;
049import org.threeten.bp.temporal.TemporalAccessor;
050import org.threeten.bp.temporal.ValueRange;
051
052/**
053 * The Thai Buddhist calendar system.
054 * <p>
055 * This chronology defines the rules of the Thai Buddhist calendar system.
056 * This calendar system is primarily used in Thailand.
057 * Dates are aligned such that {@code 2484-01-01 (Buddhist)} is {@code 1941-01-01 (ISO)}.
058 * <p>
059 * The fields are defined as follows:
060 * <p><ul>
061 * <li>era - There are two eras, the current 'Buddhist' (ERA_BE) and the previous era (ERA_BEFORE_BE).
062 * <li>year-of-era - The year-of-era for the current era increases uniformly from the epoch at year one.
063 *  For the previous era the year increases from one as time goes backwards.
064 *  The value for the current era is equal to the ISO proleptic-year plus 543.
065 * <li>proleptic-year - The proleptic year is the same as the year-of-era for the
066 *  current era. For the previous era, years have zero, then negative values.
067 *  The value is equal to the ISO proleptic-year plus 543.
068 * <li>month-of-year - The ThaiBuddhist month-of-year exactly matches ISO.
069 * <li>day-of-month - The ThaiBuddhist day-of-month exactly matches ISO.
070 * <li>day-of-year - The ThaiBuddhist day-of-year exactly matches ISO.
071 * <li>leap-year - The ThaiBuddhist leap-year pattern exactly matches ISO, such that the two calendars
072 *  are never out of step.
073 * </ul><p>
074 *
075 * <h3>Specification for implementors</h3>
076 * This class is immutable and thread-safe.
077 */
078public final class ThaiBuddhistChrono extends Chrono<ThaiBuddhistChrono> implements Serializable {
079
080    /**
081     * Singleton instance of the Buddhist chronology.
082     */
083    public static final ThaiBuddhistChrono INSTANCE = new ThaiBuddhistChrono();
084    /**
085     * The singleton instance for the era before the current one - Before Buddhist -
086     * which has the value 0.
087     */
088    public static final Era<ThaiBuddhistChrono> ERA_BEFORE_BE = ThaiBuddhistEra.BEFORE_BE;
089    /**
090     * The singleton instance for the current era - Buddhist - which has the value 1.
091     */
092    public static final Era<ThaiBuddhistChrono> ERA_BE = ThaiBuddhistEra.BE;
093
094    /**
095     * Serialization version.
096     */
097    private static final long serialVersionUID = 2775954514031616474L;
098    /**
099     * Containing the offset to add to the ISO year.
100     */
101    static final int YEARS_DIFFERENCE = 543;
102    /**
103     * Narrow names for eras.
104     */
105    private static final HashMap<String, String[]> ERA_NARROW_NAMES = new HashMap<>();
106    /**
107     * Short names for eras.
108     */
109    private static final HashMap<String, String[]> ERA_SHORT_NAMES = new HashMap<>();
110    /**
111     * Full names for eras.
112     */
113    private static final HashMap<String, String[]> ERA_FULL_NAMES = new HashMap<>();
114    /**
115     * Fallback language for the era names.
116     */
117    private static final String FALLBACK_LANGUAGE = "en";
118    /**
119     * Language that has the era names.
120     */
121    private static final String TARGET_LANGUAGE = "th";
122    /**
123     * Name data.
124     */
125    static {
126        ERA_NARROW_NAMES.put(FALLBACK_LANGUAGE, new String[]{"BB", "BE"});
127        ERA_NARROW_NAMES.put(TARGET_LANGUAGE, new String[]{"BB", "BE"});
128        ERA_SHORT_NAMES.put(FALLBACK_LANGUAGE, new String[]{"B.B.", "B.E."});
129        ERA_SHORT_NAMES.put(TARGET_LANGUAGE,
130                new String[]{"\u0e1e.\u0e28.",
131                "\u0e1b\u0e35\u0e01\u0e48\u0e2d\u0e19\u0e04\u0e23\u0e34\u0e2a\u0e15\u0e4c\u0e01\u0e32\u0e25\u0e17\u0e35\u0e48"});
132        ERA_FULL_NAMES.put(FALLBACK_LANGUAGE, new String[]{"Before Buddhist", "Budhhist Era"});
133        ERA_FULL_NAMES.put(TARGET_LANGUAGE,
134                new String[]{"\u0e1e\u0e38\u0e17\u0e18\u0e28\u0e31\u0e01\u0e23\u0e32\u0e0a",
135                "\u0e1b\u0e35\u0e01\u0e48\u0e2d\u0e19\u0e04\u0e23\u0e34\u0e2a\u0e15\u0e4c\u0e01\u0e32\u0e25\u0e17\u0e35\u0e48"});
136    }
137
138    /**
139     * Restricted constructor.
140     */
141    private ThaiBuddhistChrono() {
142    }
143
144    /**
145     * Resolve singleton.
146     *
147     * @return the singleton instance, not null
148     */
149    private Object readResolve() {
150        return INSTANCE;
151    }
152
153    //-----------------------------------------------------------------------
154    /**
155     * Gets the ID of the chronology - 'ThaiBuddhist'.
156     * <p>
157     * The ID uniquely identifies the {@code Chrono}.
158     * It can be used to lookup the {@code Chrono} using {@link #of(String)}.
159     *
160     * @return the chronology ID - 'ThaiBuddhist'
161     * @see #getCalendarType()
162     */
163    @Override
164    public String getId() {
165        return "ThaiBuddhist";
166    }
167
168    /**
169     * Gets the calendar type of the underlying calendar system - 'buddhist'.
170     * <p>
171     * The calendar type is an identifier defined by the
172     * <em>Unicode Locale Data Markup Language (LDML)</em> specification.
173     * It can be used to lookup the {@code Chrono} using {@link #of(String)}.
174     * It can also be used as part of a locale, accessible via
175     * {@link Locale#getUnicodeLocaleType(String)} with the key 'ca'.
176     *
177     * @return the calendar system type - 'buddhist'
178     * @see #getId()
179     */
180    @Override
181    public String getCalendarType() {
182        return "buddhist";
183    }
184
185    //-----------------------------------------------------------------------
186    @Override
187    public ChronoLocalDate<ThaiBuddhistChrono> date(int prolepticYear, int month, int dayOfMonth) {
188        return new ThaiBuddhistDate(LocalDate.of(prolepticYear - YEARS_DIFFERENCE, month, dayOfMonth));
189    }
190
191    @Override
192    public ChronoLocalDate<ThaiBuddhistChrono> dateYearDay(int prolepticYear, int dayOfYear) {
193        return new ThaiBuddhistDate(LocalDate.ofYearDay(prolepticYear - YEARS_DIFFERENCE, dayOfYear));
194    }
195
196    @Override
197    public ChronoLocalDate<ThaiBuddhistChrono> date(TemporalAccessor temporal) {
198        if (temporal instanceof ThaiBuddhistDate) {
199            return (ThaiBuddhistDate) temporal;
200        }
201        return new ThaiBuddhistDate(LocalDate.from(temporal));
202    }
203
204    //-----------------------------------------------------------------------
205    /**
206     * Checks if the specified year is a leap year.
207     * <p>
208     * Thai Buddhist leap years occur exactly in line with ISO leap years.
209     * This method does not validate the year passed in, and only has a
210     * well-defined result for years in the supported range.
211     *
212     * @param prolepticYear  the proleptic-year to check, not validated for range
213     * @return true if the year is a leap year
214     */
215    @Override
216    public boolean isLeapYear(long prolepticYear) {
217        return ISOChrono.INSTANCE.isLeapYear(prolepticYear - YEARS_DIFFERENCE);
218    }
219
220    @Override
221    public int prolepticYear(Era<ThaiBuddhistChrono> era, int yearOfEra) {
222        if (era instanceof ThaiBuddhistEra == false) {
223            throw new DateTimeException("Era must be BuddhistEra");
224        }
225        return (era == ThaiBuddhistEra.BE ? yearOfEra : 1 - yearOfEra);
226    }
227
228    @Override
229    public Era<ThaiBuddhistChrono> eraOf(int eraValue) {
230        return ThaiBuddhistEra.of(eraValue);
231    }
232
233    @Override
234    public List<Era<ThaiBuddhistChrono>> eras() {
235        return Arrays.<Era<ThaiBuddhistChrono>>asList(ThaiBuddhistEra.values());
236    }
237
238    //-----------------------------------------------------------------------
239    @Override
240    public ValueRange range(ChronoField field) {
241        switch (field) {
242            case YEAR_OF_ERA: {
243                ValueRange range = YEAR.range();
244                return ValueRange.of(1, -(range.getMinimum() + YEARS_DIFFERENCE) + 1, range.getMaximum() + YEARS_DIFFERENCE);
245            }
246            case YEAR: {
247                ValueRange range = YEAR.range();
248                return ValueRange.of(range.getMinimum() + YEARS_DIFFERENCE, range.getMaximum() + YEARS_DIFFERENCE);
249            }
250        }
251        return field.range();
252    }
253
254}