1/*
2 * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26/*
27 * Copyright (c) 2012, Stephen Colebourne & Michael Nascimento Santos
28 *
29 * All rights reserved.
30 *
31 * Redistribution and use in source and binary forms, with or without
32 * modification, are permitted provided that the following conditions are met:
33 *
34 *  * Redistributions of source code must retain the above copyright notice,
35 *    this list of conditions and the following disclaimer.
36 *
37 *  * Redistributions in binary form must reproduce the above copyright notice,
38 *    this list of conditions and the following disclaimer in the documentation
39 *    and/or other materials provided with the distribution.
40 *
41 *  * Neither the name of JSR-310 nor the names of its contributors
42 *    may be used to endorse or promote products derived from this software
43 *    without specific prior written permission.
44 *
45 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
46 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
47 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
48 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
49 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
50 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
51 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
52 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
53 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
54 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
55 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
56 */
57package java.time.chrono;
58
59import static java.time.chrono.ThaiBuddhistChronology.YEARS_DIFFERENCE;
60import static java.time.temporal.ChronoField.DAY_OF_MONTH;
61import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
62import static java.time.temporal.ChronoField.YEAR;
63
64import java.io.DataInput;
65import java.io.DataOutput;
66import java.io.IOException;
67import java.io.InvalidObjectException;
68import java.io.ObjectInputStream;
69import java.io.Serializable;
70import java.time.Clock;
71import java.time.DateTimeException;
72import java.time.LocalDate;
73import java.time.LocalTime;
74import java.time.Period;
75import java.time.ZoneId;
76import java.time.temporal.ChronoField;
77import java.time.temporal.TemporalAccessor;
78import java.time.temporal.TemporalAdjuster;
79import java.time.temporal.TemporalAmount;
80import java.time.temporal.TemporalField;
81import java.time.temporal.TemporalQuery;
82import java.time.temporal.TemporalUnit;
83import java.time.temporal.UnsupportedTemporalTypeException;
84import java.time.temporal.ValueRange;
85import java.util.Objects;
86
87/**
88 * A date in the Thai Buddhist calendar system.
89 * <p>
90 * This date operates using the {@linkplain ThaiBuddhistChronology Thai Buddhist calendar}.
91 * This calendar system is primarily used in Thailand.
92 * Dates are aligned such that {@code 2484-01-01 (Buddhist)} is {@code 1941-01-01 (ISO)}.
93 *
94 * <p>
95 * This is a <a href="{@docRoot}/java/lang/doc-files/ValueBased.html">value-based</a>
96 * class; use of identity-sensitive operations (including reference equality
97 * ({@code ==}), identity hash code, or synchronization) on instances of
98 * {@code ThaiBuddhistDate} may have unpredictable results and should be avoided.
99 * The {@code equals} method should be used for comparisons.
100 *
101 * @implSpec
102 * This class is immutable and thread-safe.
103 *
104 * @since 1.8
105 */
106public final class ThaiBuddhistDate
107        extends ChronoLocalDateImpl<ThaiBuddhistDate>
108        implements ChronoLocalDate, Serializable {
109
110    /**
111     * Serialization version.
112     */
113    private static final long serialVersionUID = -8722293800195731463L;
114
115    /**
116     * The underlying date.
117     */
118    private final transient LocalDate isoDate;
119
120    //-----------------------------------------------------------------------
121    /**
122     * Obtains the current {@code ThaiBuddhistDate} from the system clock in the default time-zone.
123     * <p>
124     * This will query the {@link Clock#systemDefaultZone() system clock} in the default
125     * time-zone to obtain the current date.
126     * <p>
127     * Using this method will prevent the ability to use an alternate clock for testing
128     * because the clock is hard-coded.
129     *
130     * @return the current date using the system clock and default time-zone, not null
131     */
132    public static ThaiBuddhistDate now() {
133        return now(Clock.systemDefaultZone());
134    }
135
136    /**
137     * Obtains the current {@code ThaiBuddhistDate} from the system clock in the specified time-zone.
138     * <p>
139     * This will query the {@link Clock#system(ZoneId) system clock} to obtain the current date.
140     * Specifying the time-zone avoids dependence on the default time-zone.
141     * <p>
142     * Using this method will prevent the ability to use an alternate clock for testing
143     * because the clock is hard-coded.
144     *
145     * @param zone  the zone ID to use, not null
146     * @return the current date using the system clock, not null
147     */
148    public static ThaiBuddhistDate now(ZoneId zone) {
149        return now(Clock.system(zone));
150    }
151
152    /**
153     * Obtains the current {@code ThaiBuddhistDate} from the specified clock.
154     * <p>
155     * This will query the specified clock to obtain the current date - today.
156     * Using this method allows the use of an alternate clock for testing.
157     * The alternate clock may be introduced using {@linkplain Clock dependency injection}.
158     *
159     * @param clock  the clock to use, not null
160     * @return the current date, not null
161     * @throws DateTimeException if the current date cannot be obtained
162     */
163    public static ThaiBuddhistDate now(Clock clock) {
164        return new ThaiBuddhistDate(LocalDate.now(clock));
165    }
166
167    /**
168     * Obtains a {@code ThaiBuddhistDate} representing a date in the Thai Buddhist calendar
169     * system from the proleptic-year, month-of-year and day-of-month fields.
170     * <p>
171     * This returns a {@code ThaiBuddhistDate} with the specified fields.
172     * The day must be valid for the year and month, otherwise an exception will be thrown.
173     *
174     * @param prolepticYear  the Thai Buddhist proleptic-year
175     * @param month  the Thai Buddhist month-of-year, from 1 to 12
176     * @param dayOfMonth  the Thai Buddhist day-of-month, from 1 to 31
177     * @return the date in Thai Buddhist calendar system, not null
178     * @throws DateTimeException if the value of any field is out of range,
179     *  or if the day-of-month is invalid for the month-year
180     */
181    public static ThaiBuddhistDate of(int prolepticYear, int month, int dayOfMonth) {
182        return new ThaiBuddhistDate(LocalDate.of(prolepticYear - YEARS_DIFFERENCE, month, dayOfMonth));
183    }
184
185    /**
186     * Obtains a {@code ThaiBuddhistDate} from a temporal object.
187     * <p>
188     * This obtains a date in the Thai Buddhist calendar system based on the specified temporal.
189     * A {@code TemporalAccessor} represents an arbitrary set of date and time information,
190     * which this factory converts to an instance of {@code ThaiBuddhistDate}.
191     * <p>
192     * The conversion typically uses the {@link ChronoField#EPOCH_DAY EPOCH_DAY}
193     * field, which is standardized across calendar systems.
194     * <p>
195     * This method matches the signature of the functional interface {@link TemporalQuery}
196     * allowing it to be used as a query via method reference, {@code ThaiBuddhistDate::from}.
197     *
198     * @param temporal  the temporal object to convert, not null
199     * @return the date in Thai Buddhist calendar system, not null
200     * @throws DateTimeException if unable to convert to a {@code ThaiBuddhistDate}
201     */
202    public static ThaiBuddhistDate from(TemporalAccessor temporal) {
203        return ThaiBuddhistChronology.INSTANCE.date(temporal);
204    }
205
206    //-----------------------------------------------------------------------
207    /**
208     * Creates an instance from an ISO date.
209     *
210     * @param isoDate  the standard local date, validated not null
211     */
212    ThaiBuddhistDate(LocalDate isoDate) {
213        Objects.requireNonNull(isoDate, "isoDate");
214        this.isoDate = isoDate;
215    }
216
217    //-----------------------------------------------------------------------
218    /**
219     * Gets the chronology of this date, which is the Thai Buddhist calendar system.
220     * <p>
221     * The {@code Chronology} represents the calendar system in use.
222     * The era and other fields in {@link ChronoField} are defined by the chronology.
223     *
224     * @return the Thai Buddhist chronology, not null
225     */
226    @Override
227    public ThaiBuddhistChronology getChronology() {
228        return ThaiBuddhistChronology.INSTANCE;
229    }
230
231    /**
232     * Gets the era applicable at this date.
233     * <p>
234     * The Thai Buddhist calendar system has two eras, 'BE' and 'BEFORE_BE',
235     * defined by {@link ThaiBuddhistEra}.
236     *
237     * @return the era applicable at this date, not null
238     */
239    @Override
240    public ThaiBuddhistEra getEra() {
241        return (getProlepticYear() >= 1 ? ThaiBuddhistEra.BE : ThaiBuddhistEra.BEFORE_BE);
242    }
243
244    /**
245     * Returns the length of the month represented by this date.
246     * <p>
247     * This returns the length of the month in days.
248     * Month lengths match those of the ISO calendar system.
249     *
250     * @return the length of the month in days
251     */
252    @Override
253    public int lengthOfMonth() {
254        return isoDate.lengthOfMonth();
255    }
256
257    //-----------------------------------------------------------------------
258    @Override
259    public ValueRange range(TemporalField field) {
260        if (field instanceof ChronoField) {
261            if (isSupported(field)) {
262                ChronoField f = (ChronoField) field;
263                switch (f) {
264                    case DAY_OF_MONTH:
265                    case DAY_OF_YEAR:
266                    case ALIGNED_WEEK_OF_MONTH:
267                        return isoDate.range(field);
268                    case YEAR_OF_ERA: {
269                        ValueRange range = YEAR.range();
270                        long max = (getProlepticYear() <= 0 ? -(range.getMinimum() + YEARS_DIFFERENCE) + 1 : range.getMaximum() + YEARS_DIFFERENCE);
271                        return ValueRange.of(1, max);
272                    }
273                }
274                return getChronology().range(f);
275            }
276            throw new UnsupportedTemporalTypeException("Unsupported field: " + field);
277        }
278        return field.rangeRefinedBy(this);
279    }
280
281    @Override
282    public long getLong(TemporalField field) {
283        if (field instanceof ChronoField) {
284            switch ((ChronoField) field) {
285                case PROLEPTIC_MONTH:
286                    return getProlepticMonth();
287                case YEAR_OF_ERA: {
288                    int prolepticYear = getProlepticYear();
289                    return (prolepticYear >= 1 ? prolepticYear : 1 - prolepticYear);
290                }
291                case YEAR:
292                    return getProlepticYear();
293                case ERA:
294                    return (getProlepticYear() >= 1 ? 1 : 0);
295            }
296            return isoDate.getLong(field);
297        }
298        return field.getFrom(this);
299    }
300
301    private long getProlepticMonth() {
302        return getProlepticYear() * 12L + isoDate.getMonthValue() - 1;
303    }
304
305    private int getProlepticYear() {
306        return isoDate.getYear() + YEARS_DIFFERENCE;
307    }
308
309    //-----------------------------------------------------------------------
310    @Override
311    public ThaiBuddhistDate with(TemporalField field, long newValue) {
312        if (field instanceof ChronoField) {
313            ChronoField f = (ChronoField) field;
314            if (getLong(f) == newValue) {
315                return this;
316            }
317            switch (f) {
318                case PROLEPTIC_MONTH:
319                    getChronology().range(f).checkValidValue(newValue, f);
320                    return plusMonths(newValue - getProlepticMonth());
321                case YEAR_OF_ERA:
322                case YEAR:
323                case ERA: {
324                    int nvalue = getChronology().range(f).checkValidIntValue(newValue, f);
325                    switch (f) {
326                        case YEAR_OF_ERA:
327                            return with(isoDate.withYear((getProlepticYear() >= 1 ? nvalue : 1 - nvalue)  - YEARS_DIFFERENCE));
328                        case YEAR:
329                            return with(isoDate.withYear(nvalue - YEARS_DIFFERENCE));
330                        case ERA:
331                            return with(isoDate.withYear((1 - getProlepticYear()) - YEARS_DIFFERENCE));
332                    }
333                }
334            }
335            return with(isoDate.with(field, newValue));
336        }
337        return super.with(field, newValue);
338    }
339
340    /**
341     * {@inheritDoc}
342     * @throws DateTimeException {@inheritDoc}
343     * @throws ArithmeticException {@inheritDoc}
344     */
345    @Override
346    public  ThaiBuddhistDate with(TemporalAdjuster adjuster) {
347        return super.with(adjuster);
348    }
349
350    /**
351     * {@inheritDoc}
352     * @throws DateTimeException {@inheritDoc}
353     * @throws ArithmeticException {@inheritDoc}
354     */
355    @Override
356    public ThaiBuddhistDate plus(TemporalAmount amount) {
357        return super.plus(amount);
358    }
359
360    /**
361     * {@inheritDoc}
362     * @throws DateTimeException {@inheritDoc}
363     * @throws ArithmeticException {@inheritDoc}
364     */
365    @Override
366    public ThaiBuddhistDate minus(TemporalAmount amount) {
367        return super.minus(amount);
368    }
369
370    //-----------------------------------------------------------------------
371    @Override
372    ThaiBuddhistDate plusYears(long years) {
373        return with(isoDate.plusYears(years));
374    }
375
376    @Override
377    ThaiBuddhistDate plusMonths(long months) {
378        return with(isoDate.plusMonths(months));
379    }
380
381    @Override
382    ThaiBuddhistDate plusWeeks(long weeksToAdd) {
383        return super.plusWeeks(weeksToAdd);
384    }
385
386    @Override
387    ThaiBuddhistDate plusDays(long days) {
388        return with(isoDate.plusDays(days));
389    }
390
391    @Override
392    public ThaiBuddhistDate plus(long amountToAdd, TemporalUnit unit) {
393        return super.plus(amountToAdd, unit);
394    }
395
396    @Override
397    public ThaiBuddhistDate minus(long amountToAdd, TemporalUnit unit) {
398        return super.minus(amountToAdd, unit);
399    }
400
401    @Override
402    ThaiBuddhistDate minusYears(long yearsToSubtract) {
403        return super.minusYears(yearsToSubtract);
404    }
405
406    @Override
407    ThaiBuddhistDate minusMonths(long monthsToSubtract) {
408        return super.minusMonths(monthsToSubtract);
409    }
410
411    @Override
412    ThaiBuddhistDate minusWeeks(long weeksToSubtract) {
413        return super.minusWeeks(weeksToSubtract);
414    }
415
416    @Override
417    ThaiBuddhistDate minusDays(long daysToSubtract) {
418        return super.minusDays(daysToSubtract);
419    }
420
421    private ThaiBuddhistDate with(LocalDate newDate) {
422        return (newDate.equals(isoDate) ? this : new ThaiBuddhistDate(newDate));
423    }
424
425    @Override        // for javadoc and covariant return type
426    @SuppressWarnings("unchecked")
427    public final ChronoLocalDateTime<ThaiBuddhistDate> atTime(LocalTime localTime) {
428        return (ChronoLocalDateTime<ThaiBuddhistDate>) super.atTime(localTime);
429    }
430
431    @Override
432    public ChronoPeriod until(ChronoLocalDate endDate) {
433        Period period = isoDate.until(endDate);
434        return getChronology().period(period.getYears(), period.getMonths(), period.getDays());
435    }
436
437    @Override  // override for performance
438    public long toEpochDay() {
439        return isoDate.toEpochDay();
440    }
441
442    //-------------------------------------------------------------------------
443    /**
444     * Compares this date to another date, including the chronology.
445     * <p>
446     * Compares this {@code ThaiBuddhistDate} with another ensuring that the date is the same.
447     * <p>
448     * Only objects of type {@code ThaiBuddhistDate} are compared, other types return false.
449     * To compare the dates of two {@code TemporalAccessor} instances, including dates
450     * in two different chronologies, use {@link ChronoField#EPOCH_DAY} as a comparator.
451     *
452     * @param obj  the object to check, null returns false
453     * @return true if this is equal to the other date
454     */
455    @Override  // override for performance
456    public boolean equals(Object obj) {
457        if (this == obj) {
458            return true;
459        }
460        if (obj instanceof ThaiBuddhistDate) {
461            ThaiBuddhistDate otherDate = (ThaiBuddhistDate) obj;
462            return this.isoDate.equals(otherDate.isoDate);
463        }
464        return false;
465    }
466
467    /**
468     * A hash code for this date.
469     *
470     * @return a suitable hash code based only on the Chronology and the date
471     */
472    @Override  // override for performance
473    public int hashCode() {
474        return getChronology().getId().hashCode() ^ isoDate.hashCode();
475    }
476
477    //-----------------------------------------------------------------------
478    /**
479     * Defend against malicious streams.
480     *
481     * @param s the stream to read
482     * @throws InvalidObjectException always
483     */
484    private void readObject(ObjectInputStream s) throws InvalidObjectException {
485        throw new InvalidObjectException("Deserialization via serialization delegate");
486    }
487
488    /**
489     * Writes the object using a
490     * <a href="../../../serialized-form.html#java.time.chrono.Ser">dedicated serialized form</a>.
491     * @serialData
492     * <pre>
493     *  out.writeByte(10);                // identifies a ThaiBuddhistDate
494     *  out.writeInt(get(YEAR));
495     *  out.writeByte(get(MONTH_OF_YEAR));
496     *  out.writeByte(get(DAY_OF_MONTH));
497     * </pre>
498     *
499     * @return the instance of {@code Ser}, not null
500     */
501    private Object writeReplace() {
502        return new Ser(Ser.THAIBUDDHIST_DATE_TYPE, this);
503    }
504
505    void writeExternal(DataOutput out) throws IOException {
506        // ThaiBuddhistChronology is implicit in the THAIBUDDHIST_DATE_TYPE
507        out.writeInt(this.get(YEAR));
508        out.writeByte(this.get(MONTH_OF_YEAR));
509        out.writeByte(this.get(DAY_OF_MONTH));
510    }
511
512    static ThaiBuddhistDate readExternal(DataInput in) throws IOException {
513        int year = in.readInt();
514        int month = in.readByte();
515        int dayOfMonth = in.readByte();
516        return ThaiBuddhistChronology.INSTANCE.date(year, month, dayOfMonth);
517    }
518
519}
520