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.temporal;
033
034import java.io.IOException;
035import java.io.InvalidObjectException;
036import java.io.ObjectInput;
037import java.io.ObjectOutput;
038import java.io.ObjectStreamException;
039import java.io.Serializable;
040import java.util.Objects;
041
042import org.threeten.bp.DateTimeException;
043import org.threeten.bp.Period;
044
045/**
046 * A period of time, measured as an amount of a single unit, such as '3 Months'.
047 * <p>
048 * A {@code SimplePeriod} represents an amount of time measured in terms of a
049 * single {@link TemporalUnit unit}. Any unit may be used with this class.
050 * An alternative period implementation is {@link Period}, which
051 * allows a combination of date and time units.
052 * <p>
053 * This class is the return type from {@link TemporalUnit#between}.
054 * It can be used more generally, but is designed to enable the following code:
055 * <pre>
056 *  date = date.minus(MONTHS.between(start, end));
057 * </pre>
058 * The unit determines which calendar systems it can be added to.
059 * <p>
060 * The period is modeled as a directed amount of time, meaning that the period may
061 * be negative. See {@link #abs()} to ensure the period is positive.
062 *
063 * <h3>Specification for implementors</h3>
064 * This class is immutable and thread-safe, providing that the unit is immutable,
065 * which it is required to be.
066 */
067public final class SimplePeriod
068        implements TemporalAdder, TemporalSubtractor, Comparable<SimplePeriod>, Serializable {
069
070    /**
071     * Serialization version.
072     */
073    private static final long serialVersionUID = 3752975649629L;
074
075    /**
076     * The amount of the unit.
077     */
078    private final long amount;
079    /**
080     * The unit.
081     */
082    private final TemporalUnit unit;
083
084    //-----------------------------------------------------------------------
085    /**
086     * Obtains an instance of {@code SimplePeriod} from a period in the specified unit.
087     * <p>
088     * The parameters represent the two parts of a phrase like '6 Days'. For example:
089     * <pre>
090     *  SimplePeriod.of(3, SECONDS);
091     *  SimplePeriod.of(5, YEARS);
092     * </pre>
093     *
094     * @param amount  the amount of the period, measured in terms of the unit, positive or negative
095     * @param unit  the unit that the period is measured in, not null
096     * @return the period, not null
097     */
098    public static SimplePeriod of(long amount, TemporalUnit unit) {
099        Objects.requireNonNull(unit, "unit");
100        return new SimplePeriod(amount, unit);
101    }
102
103    //-----------------------------------------------------------------------
104    /**
105     * Constructor.
106     *
107     * @param amount  the amount of the period, measured in terms of the unit, positive or negative
108     * @param unit  the unit that the period is measured in, not null
109     */
110    SimplePeriod(long amount, TemporalUnit unit) {
111        this.amount = amount;
112        this.unit = unit;
113    }
114
115    //-----------------------------------------------------------------------
116    /**
117     * Gets the amount of this period.
118     * <p>
119     * In the phrase "2 Months", the amount is 2.
120     *
121     * @return the amount of the period, may be negative
122     */
123    public long getAmount() {
124        return amount;
125    }
126
127    /**
128     * Gets the unit of this period.
129     * <p>
130     * In the phrase "2 Months", the unit is "Months".
131     *
132     * @return the unit of the period, not null
133     */
134    public TemporalUnit getUnit() {
135        return unit;
136    }
137
138    //-------------------------------------------------------------------------
139    /**
140     * Adds this period to the specified temporal object.
141     * <p>
142     * This returns a temporal object of the same observable type as the input
143     * with this period added.
144     * <p>
145     * In most cases, it is clearer to reverse the calling pattern by using
146     * {@link Temporal#plus(TemporalAdder)}.
147     * <pre>
148     *   // these two lines are equivalent, but the second approach is recommended
149     *   dateTime = thisPeriod.addTo(dateTime);
150     *   dateTime = dateTime.plus(thisPeriod);
151     * </pre>
152     * <p>
153     * The calculation is equivalent to invoking {@link Temporal#plus(long, TemporalUnit)}.
154     * <p>
155     * This instance is immutable and unaffected by this method call.
156     *
157     * @param temporal  the temporal object to adjust, not null
158     * @return an object of the same type with the adjustment made, not null
159     * @throws DateTimeException if unable to add
160     * @throws ArithmeticException if numeric overflow occurs
161     */
162    @Override
163    public Temporal addTo(Temporal temporal) {
164        return temporal.plus(amount, unit);
165    }
166
167    /**
168     * Subtracts this period to the specified temporal object.
169     * <p>
170     * This returns a temporal object of the same observable type as the input
171     * with this period subtracted.
172     * <p>
173     * In most cases, it is clearer to reverse the calling pattern by using
174     * {@link Temporal#plus(TemporalAdder)}.
175     * <pre>
176     *   // these two lines are equivalent, but the second approach is recommended
177     *   dateTime = thisPeriod.subtractFrom(dateTime);
178     *   dateTime = dateTime.minus(thisPeriod);
179     * </pre>
180     * <p>
181     * The calculation is equivalent to invoking {@link Temporal#minus(long, TemporalUnit)}.
182     * <p>
183     * This instance is immutable and unaffected by this method call.
184     *
185     * @param temporal  the temporal object to adjust, not null
186     * @return an object of the same type with the adjustment made, not null
187     * @throws DateTimeException if unable to subtract
188     * @throws ArithmeticException if numeric overflow occurs
189     */
190    @Override
191    public Temporal subtractFrom(Temporal temporal) {
192        return temporal.minus(amount, unit);
193    }
194
195    //-----------------------------------------------------------------------
196    /**
197     * Returns a copy of this period with a positive amount.
198     * <p>
199     * This returns a period with the absolute value of the amount and the same unit.
200     * If the amount of this period is positive or zero, then this period is returned.
201     * If the amount of this period is negative, then a period with the negated
202     * amount is returned. If the amount equals {@code Long.MIN_VALUE},
203     * an {@code ArithmeticException} is thrown
204     * <p>
205     * This is useful to convert the result of {@link TemporalUnit#between} to
206     * a positive amount when you do not know which date is the earlier and
207     * which is the later.
208     *
209     * @return a period with a positive amount and the same unit, not null
210     * @throws ArithmeticException if the amount is {@code Long.MIN_VALUE}
211     */
212    public SimplePeriod abs() {
213        if (amount == Long.MIN_VALUE) {
214            throw new ArithmeticException("Unable to call abs() on MIN_VALUE");
215        }
216        return (amount >= 0 ? this : new SimplePeriod(-amount, unit));
217    }
218
219    //-----------------------------------------------------------------------
220    /**
221     * Compares this {@code SimplePeriod} to another period.
222     * <p>
223     * The comparison is based on the amount within the unit.
224     * Only two periods with the same unit can be compared.
225     *
226     * @param other  the other period to compare to, not null
227     * @return the comparator value, negative if less, positive if greater
228     * @throws IllegalArgumentException if the units do not match
229     */
230    @Override
231    public int compareTo(SimplePeriod other) {
232        if (unit.equals(other.unit) == false) {
233            throw new IllegalArgumentException("Unable to compare periods with different units");
234        }
235        return Long.compare(amount, other.amount);
236    }
237
238    //-----------------------------------------------------------------------
239    /**
240     * Checks if this period is equal to another period.
241     * <p>
242     * The comparison is based on the amount and unit.
243     *
244     * @param obj  the object to check, null returns false
245     * @return true if this is equal to the other period
246     */
247    @Override
248    public boolean equals(Object obj) {
249        if (this == obj) {
250            return true;
251        }
252        if (obj instanceof SimplePeriod) {
253            SimplePeriod other = (SimplePeriod) obj;
254            return amount == other.amount && unit.equals(other.unit);
255        }
256        return false;
257    }
258
259    /**
260     * A hash code for this period.
261     *
262     * @return a suitable hash code
263     */
264    @Override
265    public int hashCode() {
266        return unit.hashCode() ^ (int) (amount ^ (amount >>> 32));
267    }
268
269    //-----------------------------------------------------------------------
270    /**
271     * Outputs this period as a {@code String}, such as {@code 2 Months}.
272     * <p>
273     * The string consists of the amount, then a space, then the unit name.
274     *
275     * @return a string representation of this period, not null
276     */
277    @Override
278    public String toString() {
279        return amount + " " + unit.getName();
280    }
281
282    //-----------------------------------------------------------------------
283    private Object writeReplace() {
284        return new Ser(Ser.SIMPLE_PERIOD_TYPE, this);
285    }
286
287    /**
288     * Defend against malicious streams.
289     * @return never
290     * @throws InvalidObjectException always
291     */
292    private Object readResolve() throws ObjectStreamException {
293        throw new InvalidObjectException("Deserialization via serialization delegate");
294    }
295
296    void writeExternal(ObjectOutput out) throws IOException {
297        out.writeLong(amount);
298        out.writeObject(unit);
299    }
300
301    static SimplePeriod readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
302        long amount = in.readLong();
303        TemporalUnit unit = (TemporalUnit) in.readObject();
304        return SimplePeriod.of(amount, unit);
305    }
306
307}