• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 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) 2013, 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  */
57 package java.time.chrono;
58 
59 import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
60 import static java.time.temporal.ChronoUnit.DAYS;
61 import static java.time.temporal.ChronoUnit.MONTHS;
62 import static java.time.temporal.ChronoUnit.YEARS;
63 
64 import java.io.DataInput;
65 import java.io.DataOutput;
66 import java.io.IOException;
67 import java.io.InvalidObjectException;
68 import java.io.ObjectInputStream;
69 import java.io.ObjectStreamException;
70 import java.io.Serializable;
71 import java.time.DateTimeException;
72 import java.time.temporal.ChronoUnit;
73 import java.time.temporal.Temporal;
74 import java.time.temporal.TemporalAccessor;
75 import java.time.temporal.TemporalAmount;
76 import java.time.temporal.TemporalQueries;
77 import java.time.temporal.TemporalUnit;
78 import java.time.temporal.UnsupportedTemporalTypeException;
79 import java.time.temporal.ValueRange;
80 import java.util.List;
81 import java.util.Objects;
82 
83 /**
84  * A period expressed in terms of a standard year-month-day calendar system.
85  * <p>
86  * This class is used by applications seeking to handle dates in non-ISO calendar systems.
87  * For example, the Japanese, Minguo, Thai Buddhist and others.
88  *
89  * @implSpec
90  * This class is immutable nad thread-safe.
91  *
92  * @since 1.8
93  */
94 final class ChronoPeriodImpl
95         implements ChronoPeriod, Serializable {
96     // this class is only used by JDK chronology implementations and makes assumptions based on that fact
97 
98     /**
99      * Serialization version.
100      */
101     private static final long serialVersionUID = 57387258289L;
102 
103     /**
104      * The set of supported units.
105      */
106     private static final List<TemporalUnit> SUPPORTED_UNITS = List.of(YEARS, MONTHS, DAYS);
107 
108     /**
109      * The chronology.
110      */
111     private final Chronology chrono;
112     /**
113      * The number of years.
114      */
115     final int years;
116     /**
117      * The number of months.
118      */
119     final int months;
120     /**
121      * The number of days.
122      */
123     final int days;
124 
125     /**
126      * Creates an instance.
127      */
ChronoPeriodImpl(Chronology chrono, int years, int months, int days)128     ChronoPeriodImpl(Chronology chrono, int years, int months, int days) {
129         Objects.requireNonNull(chrono, "chrono");
130         this.chrono = chrono;
131         this.years = years;
132         this.months = months;
133         this.days = days;
134     }
135 
136     //-----------------------------------------------------------------------
137     @Override
get(TemporalUnit unit)138     public long get(TemporalUnit unit) {
139         if (unit == ChronoUnit.YEARS) {
140             return years;
141         } else if (unit == ChronoUnit.MONTHS) {
142             return months;
143         } else if (unit == ChronoUnit.DAYS) {
144             return days;
145         } else {
146             throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
147         }
148     }
149 
150     @Override
getUnits()151     public List<TemporalUnit> getUnits() {
152         return ChronoPeriodImpl.SUPPORTED_UNITS;
153     }
154 
155     @Override
getChronology()156     public Chronology getChronology() {
157         return chrono;
158     }
159 
160     //-----------------------------------------------------------------------
161     @Override
isZero()162     public boolean isZero() {
163         return years == 0 && months == 0 && days == 0;
164     }
165 
166     @Override
isNegative()167     public boolean isNegative() {
168         return years < 0 || months < 0 || days < 0;
169     }
170 
171     //-----------------------------------------------------------------------
172     @Override
plus(TemporalAmount amountToAdd)173     public ChronoPeriod plus(TemporalAmount amountToAdd) {
174         ChronoPeriodImpl amount = validateAmount(amountToAdd);
175         return new ChronoPeriodImpl(
176                 chrono,
177                 Math.addExact(years, amount.years),
178                 Math.addExact(months, amount.months),
179                 Math.addExact(days, amount.days));
180     }
181 
182     @Override
minus(TemporalAmount amountToSubtract)183     public ChronoPeriod minus(TemporalAmount amountToSubtract) {
184         ChronoPeriodImpl amount = validateAmount(amountToSubtract);
185         return new ChronoPeriodImpl(
186                 chrono,
187                 Math.subtractExact(years, amount.years),
188                 Math.subtractExact(months, amount.months),
189                 Math.subtractExact(days, amount.days));
190     }
191 
192     /**
193      * Obtains an instance of {@code ChronoPeriodImpl} from a temporal amount.
194      *
195      * @param amount  the temporal amount to convert, not null
196      * @return the period, not null
197      */
validateAmount(TemporalAmount amount)198     private ChronoPeriodImpl validateAmount(TemporalAmount amount) {
199         Objects.requireNonNull(amount, "amount");
200         if (amount instanceof ChronoPeriodImpl == false) {
201             throw new DateTimeException("Unable to obtain ChronoPeriod from TemporalAmount: " + amount.getClass());
202         }
203         ChronoPeriodImpl period = (ChronoPeriodImpl) amount;
204         if (chrono.equals(period.getChronology()) == false) {
205             throw new ClassCastException("Chronology mismatch, expected: " + chrono.getId() + ", actual: " + period.getChronology().getId());
206         }
207         return period;
208     }
209 
210     //-----------------------------------------------------------------------
211     @Override
multipliedBy(int scalar)212     public ChronoPeriod multipliedBy(int scalar) {
213         if (this.isZero() || scalar == 1) {
214             return this;
215         }
216         return new ChronoPeriodImpl(
217                 chrono,
218                 Math.multiplyExact(years, scalar),
219                 Math.multiplyExact(months, scalar),
220                 Math.multiplyExact(days, scalar));
221     }
222 
223     //-----------------------------------------------------------------------
224     @Override
normalized()225     public ChronoPeriod normalized() {
226         long monthRange = monthRange();
227         if (monthRange > 0) {
228             long totalMonths = years * monthRange + months;
229             long splitYears = totalMonths / monthRange;
230             int splitMonths = (int) (totalMonths % monthRange);  // no overflow
231             if (splitYears == years && splitMonths == months) {
232                 return this;
233             }
234             return new ChronoPeriodImpl(chrono, Math.toIntExact(splitYears), splitMonths, days);
235 
236         }
237         return this;
238     }
239 
240     /**
241      * Calculates the range of months.
242      *
243      * @return the month range, -1 if not fixed range
244      */
monthRange()245     private long monthRange() {
246         ValueRange startRange = chrono.range(MONTH_OF_YEAR);
247         if (startRange.isFixed() && startRange.isIntValue()) {
248             return startRange.getMaximum() - startRange.getMinimum() + 1;
249         }
250         return -1;
251     }
252 
253     //-------------------------------------------------------------------------
254     @Override
addTo(Temporal temporal)255     public Temporal addTo(Temporal temporal) {
256         validateChrono(temporal);
257         if (months == 0) {
258             if (years != 0) {
259                 temporal = temporal.plus(years, YEARS);
260             }
261         } else {
262             long monthRange = monthRange();
263             if (monthRange > 0) {
264                 temporal = temporal.plus(years * monthRange + months, MONTHS);
265             } else {
266                 if (years != 0) {
267                     temporal = temporal.plus(years, YEARS);
268                 }
269                 temporal = temporal.plus(months, MONTHS);
270             }
271         }
272         if (days != 0) {
273             temporal = temporal.plus(days, DAYS);
274         }
275         return temporal;
276     }
277 
278 
279 
280     @Override
subtractFrom(Temporal temporal)281     public Temporal subtractFrom(Temporal temporal) {
282         validateChrono(temporal);
283         if (months == 0) {
284             if (years != 0) {
285                 temporal = temporal.minus(years, YEARS);
286             }
287         } else {
288             long monthRange = monthRange();
289             if (monthRange > 0) {
290                 temporal = temporal.minus(years * monthRange + months, MONTHS);
291             } else {
292                 if (years != 0) {
293                     temporal = temporal.minus(years, YEARS);
294                 }
295                 temporal = temporal.minus(months, MONTHS);
296             }
297         }
298         if (days != 0) {
299             temporal = temporal.minus(days, DAYS);
300         }
301         return temporal;
302     }
303 
304     /**
305      * Validates that the temporal has the correct chronology.
306      */
validateChrono(TemporalAccessor temporal)307     private void validateChrono(TemporalAccessor temporal) {
308         Objects.requireNonNull(temporal, "temporal");
309         Chronology temporalChrono = temporal.query(TemporalQueries.chronology());
310         if (temporalChrono != null && chrono.equals(temporalChrono) == false) {
311             throw new DateTimeException("Chronology mismatch, expected: " + chrono.getId() + ", actual: " + temporalChrono.getId());
312         }
313     }
314 
315     //-----------------------------------------------------------------------
316     @Override
equals(Object obj)317     public boolean equals(Object obj) {
318         if (this == obj) {
319             return true;
320         }
321         if (obj instanceof ChronoPeriodImpl) {
322             ChronoPeriodImpl other = (ChronoPeriodImpl) obj;
323             return years == other.years && months == other.months &&
324                     days == other.days && chrono.equals(other.chrono);
325         }
326         return false;
327     }
328 
329     @Override
hashCode()330     public int hashCode() {
331         return (years + Integer.rotateLeft(months, 8) + Integer.rotateLeft(days, 16)) ^ chrono.hashCode();
332     }
333 
334     //-----------------------------------------------------------------------
335     @Override
toString()336     public String toString() {
337         if (isZero()) {
338             return getChronology().toString() + " P0D";
339         } else {
340             StringBuilder buf = new StringBuilder();
341             buf.append(getChronology().toString()).append(' ').append('P');
342             if (years != 0) {
343                 buf.append(years).append('Y');
344             }
345             if (months != 0) {
346                 buf.append(months).append('M');
347             }
348             if (days != 0) {
349                 buf.append(days).append('D');
350             }
351             return buf.toString();
352         }
353     }
354 
355     //-----------------------------------------------------------------------
356     /**
357      * Writes the Chronology using a
358      * <a href="../../../serialized-form.html#java.time.chrono.Ser">dedicated serialized form</a>.
359      * <pre>
360      *  out.writeByte(12);  // identifies this as a ChronoPeriodImpl
361      *  out.writeUTF(getId());  // the chronology
362      *  out.writeInt(years);
363      *  out.writeInt(months);
364      *  out.writeInt(days);
365      * </pre>
366      *
367      * @return the instance of {@code Ser}, not null
368      */
writeReplace()369     protected Object writeReplace() {
370         return new Ser(Ser.CHRONO_PERIOD_TYPE, this);
371     }
372 
373     /**
374      * Defend against malicious streams.
375      *
376      * @param s the stream to read
377      * @throws InvalidObjectException always
378      */
readObject(ObjectInputStream s)379     private void readObject(ObjectInputStream s) throws ObjectStreamException {
380         throw new InvalidObjectException("Deserialization via serialization delegate");
381     }
382 
writeExternal(DataOutput out)383     void writeExternal(DataOutput out) throws IOException {
384         out.writeUTF(chrono.getId());
385         out.writeInt(years);
386         out.writeInt(months);
387         out.writeInt(days);
388     }
389 
readExternal(DataInput in)390     static ChronoPeriodImpl readExternal(DataInput in) throws IOException {
391         Chronology chrono = Chronology.of(in.readUTF());
392         int years = in.readInt();
393         int months = in.readInt();
394         int days = in.readInt();
395         return new ChronoPeriodImpl(chrono, years, months, days);
396     }
397 
398 }
399