• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
3  *
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  *  * Redistributions of source code must retain the above copyright notice,
10  *    this list of conditions and the following disclaimer.
11  *
12  *  * Redistributions in binary form must reproduce the above copyright notice,
13  *    this list of conditions and the following disclaimer in the documentation
14  *    and/or other materials provided with the distribution.
15  *
16  *  * Neither the name of JSR-310 nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 package org.threeten.bp.chrono;
33 
34 import static org.threeten.bp.temporal.ChronoUnit.SECONDS;
35 
36 import java.io.IOException;
37 import java.io.InvalidObjectException;
38 import java.io.ObjectInput;
39 import java.io.ObjectOutput;
40 import java.io.ObjectStreamException;
41 import java.io.Serializable;
42 import java.util.List;
43 
44 import org.threeten.bp.Instant;
45 import org.threeten.bp.LocalDateTime;
46 import org.threeten.bp.ZoneId;
47 import org.threeten.bp.ZoneOffset;
48 import org.threeten.bp.jdk8.Jdk8Methods;
49 import org.threeten.bp.temporal.ChronoField;
50 import org.threeten.bp.temporal.ChronoUnit;
51 import org.threeten.bp.temporal.Temporal;
52 import org.threeten.bp.temporal.TemporalField;
53 import org.threeten.bp.temporal.TemporalUnit;
54 import org.threeten.bp.zone.ZoneOffsetTransition;
55 import org.threeten.bp.zone.ZoneRules;
56 
57 /**
58  * A date-time with a time-zone in the calendar neutral API.
59  * <p>
60  * {@code ZoneChronoDateTime} is an immutable representation of a date-time with a time-zone.
61  * This class stores all date and time fields, to a precision of nanoseconds,
62  * as well as a time-zone and zone offset.
63  * <p>
64  * The purpose of storing the time-zone is to distinguish the ambiguous case where
65  * the local time-line overlaps, typically as a result of the end of daylight time.
66  * Information about the local-time can be obtained using methods on the time-zone.
67  *
68  * <h3>Specification for implementors</h3>
69  * This class is immutable and thread-safe.
70  *
71  * @param <D> the date type
72  */
73 final class ChronoZonedDateTimeImpl<D extends ChronoLocalDate>
74         extends ChronoZonedDateTime<D>
75         implements Serializable {
76 
77     /**
78      * Serialization version.
79      */
80     private static final long serialVersionUID = -5261813987200935591L;
81 
82     /**
83      * The local date-time.
84      */
85     private final ChronoLocalDateTimeImpl<D> dateTime;
86     /**
87      * The zone offset.
88      */
89     private final ZoneOffset offset;
90     /**
91      * The zone ID.
92      */
93     private final ZoneId zone;
94 
95     //-----------------------------------------------------------------------
96     /**
97      * Obtains an instance from a local date-time using the preferred offset if possible.
98      *
99      * @param localDateTime  the local date-time, not null
100      * @param zone  the zone identifier, not null
101      * @param preferredOffset  the zone offset, null if no preference
102      * @return the zoned date-time, not null
103      */
ofBest( ChronoLocalDateTimeImpl<R> localDateTime, ZoneId zone, ZoneOffset preferredOffset)104     static <R extends ChronoLocalDate> ChronoZonedDateTime<R> ofBest(
105             ChronoLocalDateTimeImpl<R> localDateTime, ZoneId zone, ZoneOffset preferredOffset) {
106         Jdk8Methods.requireNonNull(localDateTime, "localDateTime");
107         Jdk8Methods.requireNonNull(zone, "zone");
108         if (zone instanceof ZoneOffset) {
109             return new ChronoZonedDateTimeImpl<R>(localDateTime, (ZoneOffset) zone, zone);
110         }
111         ZoneRules rules = zone.getRules();
112         LocalDateTime isoLDT = LocalDateTime.from(localDateTime);
113         List<ZoneOffset> validOffsets = rules.getValidOffsets(isoLDT);
114         ZoneOffset offset;
115         if (validOffsets.size() == 1) {
116             offset = validOffsets.get(0);
117         } else if (validOffsets.size() == 0) {
118             ZoneOffsetTransition trans = rules.getTransition(isoLDT);
119             localDateTime = localDateTime.plusSeconds(trans.getDuration().getSeconds());
120             offset = trans.getOffsetAfter();
121         } else {
122             if (preferredOffset != null && validOffsets.contains(preferredOffset)) {
123                 offset = preferredOffset;
124             } else {
125                 offset = validOffsets.get(0);
126             }
127         }
128         Jdk8Methods.requireNonNull(offset, "offset");  // protect against bad ZoneRules
129         return new ChronoZonedDateTimeImpl<R>(localDateTime, offset, zone);
130     }
131 
132     /**
133      * Obtains an instance from an instant using the specified time-zone.
134      *
135      * @param chrono  the chronology, not null
136      * @param instant  the instant, not null
137      * @param zone  the zone identifier, not null
138      * @return the zoned date-time, not null
139      */
ofInstant(Chronology chrono, Instant instant, ZoneId zone)140     static <R extends ChronoLocalDate> ChronoZonedDateTimeImpl<R> ofInstant(Chronology chrono, Instant instant, ZoneId zone) {
141         ZoneRules rules = zone.getRules();
142         ZoneOffset offset = rules.getOffset(instant);
143         Jdk8Methods.requireNonNull(offset, "offset");  // protect against bad ZoneRules
144         LocalDateTime ldt = LocalDateTime.ofEpochSecond(instant.getEpochSecond(), instant.getNano(), offset);
145         @SuppressWarnings("unchecked")
146         ChronoLocalDateTimeImpl<R> cldt = (ChronoLocalDateTimeImpl<R>) chrono.localDateTime(ldt);
147         return new ChronoZonedDateTimeImpl<R>(cldt, offset, zone);
148     }
149 
150     /**
151      * Obtains an instance from an {@code Instant}.
152      *
153      * @param instant  the instant to create the date-time from, not null
154      * @param zone  the time-zone to use, validated not null
155      * @return the zoned date-time, validated not null
156      */
create(Instant instant, ZoneId zone)157     private ChronoZonedDateTimeImpl<D> create(Instant instant, ZoneId zone) {
158         return ofInstant(toLocalDate().getChronology(), instant, zone);
159     }
160 
161     //-----------------------------------------------------------------------
162     /**
163      * Constructor.
164      *
165      * @param dateTime  the date-time, not null
166      * @param offset  the zone offset, not null
167      * @param zone  the zone ID, not null
168      */
ChronoZonedDateTimeImpl(ChronoLocalDateTimeImpl<D> dateTime, ZoneOffset offset, ZoneId zone)169     private ChronoZonedDateTimeImpl(ChronoLocalDateTimeImpl<D> dateTime, ZoneOffset offset, ZoneId zone) {
170         this.dateTime = Jdk8Methods.requireNonNull(dateTime, "dateTime");
171         this.offset = Jdk8Methods.requireNonNull(offset, "offset");
172         this.zone = Jdk8Methods.requireNonNull(zone, "zone");
173     }
174 
175     //-----------------------------------------------------------------------
176     @Override
isSupported(TemporalUnit unit)177     public boolean isSupported(TemporalUnit unit) {
178         if (unit instanceof ChronoUnit) {
179             return unit.isDateBased() || unit.isTimeBased();
180         }
181         return unit != null && unit.isSupportedBy(this);
182     }
183 
getOffset()184     public ZoneOffset getOffset() {
185         return offset;
186     }
187 
188     @Override
withEarlierOffsetAtOverlap()189     public ChronoZonedDateTime<D> withEarlierOffsetAtOverlap() {
190         ZoneOffsetTransition trans = getZone().getRules().getTransition(LocalDateTime.from(this));
191         if (trans != null && trans.isOverlap()) {
192             ZoneOffset earlierOffset = trans.getOffsetBefore();
193             if (earlierOffset.equals(offset) == false) {
194                 return new ChronoZonedDateTimeImpl<D>(dateTime, earlierOffset, zone);
195             }
196         }
197         return this;
198     }
199 
200     @Override
withLaterOffsetAtOverlap()201     public ChronoZonedDateTime<D> withLaterOffsetAtOverlap() {
202         ZoneOffsetTransition trans = getZone().getRules().getTransition(LocalDateTime.from(this));
203         if (trans != null) {
204             ZoneOffset offset = trans.getOffsetAfter();
205             if (offset.equals(getOffset()) == false) {
206                 return new ChronoZonedDateTimeImpl<D>(dateTime, offset, zone);
207             }
208         }
209         return this;
210     }
211 
212     //-----------------------------------------------------------------------
213     @Override
toLocalDateTime()214     public ChronoLocalDateTime<D> toLocalDateTime() {
215         return dateTime;
216     }
217 
getZone()218     public ZoneId getZone() {
219         return zone;
220     }
221 
withZoneSameLocal(ZoneId zone)222     public ChronoZonedDateTime<D> withZoneSameLocal(ZoneId zone) {
223         return ofBest(dateTime, zone, offset);
224     }
225 
226     @Override
withZoneSameInstant(ZoneId zone)227     public ChronoZonedDateTime<D> withZoneSameInstant(ZoneId zone) {
228         Jdk8Methods.requireNonNull(zone, "zone");
229         return this.zone.equals(zone) ? this : create(dateTime.toInstant(offset), zone);
230     }
231 
232     //-----------------------------------------------------------------------
233     @Override
isSupported(TemporalField field)234     public boolean isSupported(TemporalField field) {
235         return field instanceof ChronoField || (field != null && field.isSupportedBy(this));
236     }
237 
238     //-----------------------------------------------------------------------
239     @Override
with(TemporalField field, long newValue)240     public ChronoZonedDateTime<D> with(TemporalField field, long newValue) {
241         if (field instanceof ChronoField) {
242             ChronoField f = (ChronoField) field;
243             switch (f) {
244                 case INSTANT_SECONDS: return plus(newValue - toEpochSecond(), SECONDS);
245                 case OFFSET_SECONDS: {
246                     ZoneOffset offset = ZoneOffset.ofTotalSeconds(f.checkValidIntValue(newValue));
247                     return create(dateTime.toInstant(offset), zone);
248                 }
249             }
250             return ofBest(dateTime.with(field, newValue), zone, offset);
251         }
252         return toLocalDate().getChronology().ensureChronoZonedDateTime(field.adjustInto(this, newValue));
253     }
254 
255     //-----------------------------------------------------------------------
256     @Override
plus(long amountToAdd, TemporalUnit unit)257     public ChronoZonedDateTime<D> plus(long amountToAdd, TemporalUnit unit) {
258         if (unit instanceof ChronoUnit) {
259             return with(dateTime.plus(amountToAdd, unit));
260         }
261         return toLocalDate().getChronology().ensureChronoZonedDateTime(unit.addTo(this, amountToAdd));   /// TODO: Generics replacement Risk!
262     }
263 
264     //-----------------------------------------------------------------------
265     @Override
until(Temporal endExclusive, TemporalUnit unit)266     public long until(Temporal endExclusive, TemporalUnit unit) {
267         @SuppressWarnings("unchecked")
268         ChronoZonedDateTime<D> end = (ChronoZonedDateTime<D>) toLocalDate().getChronology().zonedDateTime(endExclusive);
269         if (unit instanceof ChronoUnit) {
270             end = end.withZoneSameInstant(offset);
271             return dateTime.until(end.toLocalDateTime(), unit);
272         }
273         return unit.between(this, end);
274     }
275 
276     //-----------------------------------------------------------------------
writeReplace()277     private Object writeReplace() {
278         return new Ser(Ser.CHRONO_ZONEDDATETIME_TYPE, this);
279     }
280 
281     /**
282      * Defend against malicious streams.
283      * @return never
284      * @throws InvalidObjectException always
285      */
readResolve()286     private Object readResolve() throws ObjectStreamException {
287         throw new InvalidObjectException("Deserialization via serialization delegate");
288     }
289 
writeExternal(ObjectOutput out)290     void writeExternal(ObjectOutput out) throws IOException {
291         out.writeObject(dateTime);
292         out.writeObject(offset);
293         out.writeObject(zone);
294     }
295 
readExternal(ObjectInput in)296     static ChronoZonedDateTime<?> readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
297         ChronoLocalDateTime<?> dateTime = (ChronoLocalDateTime<?>) in.readObject();
298         ZoneOffset offset = (ZoneOffset) in.readObject();
299         ZoneId zone = (ZoneId) in.readObject();
300         return dateTime.atZone(offset).withZoneSameLocal(zone);
301         // TODO: ZDT uses ofLenient()
302     }
303 
304     //-------------------------------------------------------------------------
305     @Override
equals(Object obj)306     public boolean equals(Object obj) {
307         if (this == obj) {
308             return true;
309         }
310         if (obj instanceof ChronoZonedDateTime) {
311             return compareTo((ChronoZonedDateTime<?>) obj) == 0;
312         }
313         return false;
314     }
315 
316     @Override
hashCode()317     public int hashCode() {
318         return toLocalDateTime().hashCode() ^ getOffset().hashCode() ^ Integer.rotateLeft(getZone().hashCode(), 3);
319     }
320 
321     @Override
toString()322     public String toString() {
323         String str = toLocalDateTime().toString() + getOffset().toString();
324         if (getOffset() != getZone()) {
325             str += '[' + getZone().toString() + ']';
326         }
327         return str;
328     }
329 
330 
331 }
332