• 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.ChronoField.INSTANT_SECONDS;
35 import static org.threeten.bp.temporal.ChronoField.OFFSET_SECONDS;
36 import static org.threeten.bp.temporal.ChronoUnit.NANOS;
37 
38 import java.util.Comparator;
39 
40 import org.threeten.bp.DateTimeException;
41 import org.threeten.bp.Instant;
42 import org.threeten.bp.LocalDate;
43 import org.threeten.bp.LocalTime;
44 import org.threeten.bp.ZoneId;
45 import org.threeten.bp.ZoneOffset;
46 import org.threeten.bp.ZonedDateTime;
47 import org.threeten.bp.format.DateTimeFormatter;
48 import org.threeten.bp.jdk8.DefaultInterfaceTemporal;
49 import org.threeten.bp.jdk8.Jdk8Methods;
50 import org.threeten.bp.temporal.ChronoField;
51 import org.threeten.bp.temporal.Temporal;
52 import org.threeten.bp.temporal.TemporalAccessor;
53 import org.threeten.bp.temporal.TemporalAdjuster;
54 import org.threeten.bp.temporal.TemporalAmount;
55 import org.threeten.bp.temporal.TemporalField;
56 import org.threeten.bp.temporal.TemporalQueries;
57 import org.threeten.bp.temporal.TemporalQuery;
58 import org.threeten.bp.temporal.TemporalUnit;
59 import org.threeten.bp.temporal.UnsupportedTemporalTypeException;
60 import org.threeten.bp.temporal.ValueRange;
61 
62 /**
63  * A date-time with a time-zone in an arbitrary chronology,
64  * intended for advanced globalization use cases.
65  * <p>
66  * <b>Most applications should declare method signatures, fields and variables
67  * as {@link ZonedDateTime}, not this interface.</b>
68  * <p>
69  * A {@code ChronoZonedDateTime} is the abstract representation of an offset date-time
70  * where the {@code Chronology chronology}, or calendar system, is pluggable.
71  * The date-time is defined in terms of fields expressed by {@link TemporalField},
72  * where most common implementations are defined in {@link ChronoField}.
73  * The chronology defines how the calendar system operates and the meaning of
74  * the standard fields.
75  *
76  * <h4>When to use this interface</h4>
77  * The design of the API encourages the use of {@code ZonedDateTime} rather than this
78  * interface, even in the case where the application needs to deal with multiple
79  * calendar systems. The rationale for this is explored in detail in {@link ChronoLocalDate}.
80  * <p>
81  * Ensure that the discussion in {@code ChronoLocalDate} has been read and understood
82  * before using this interface.
83  *
84  * <h3>Specification for implementors</h3>
85  * This interface must be implemented with care to ensure other classes operate correctly.
86  * All implementations that can be instantiated must be final, immutable and thread-safe.
87  * Subclasses should be Serializable wherever possible.
88  * <p>
89  * In JDK 8, this is an interface with default methods.
90  * Since there are no default methods in JDK 7, an abstract class is used.
91  *
92  * @param <D> the date type
93  */
94 public abstract class ChronoZonedDateTime<D extends ChronoLocalDate>
95         extends DefaultInterfaceTemporal
96         implements Temporal, Comparable<ChronoZonedDateTime<?>> {
97 
98     /**
99      * Gets a comparator that compares {@code ChronoZonedDateTime} in
100      * time-line order ignoring the chronology.
101      * <p>
102      * This comparator differs from the comparison in {@link #compareTo} in that it
103      * only compares the underlying instant and not the chronology.
104      * This allows dates in different calendar systems to be compared based
105      * on the position of the date-time on the instant time-line.
106      * The underlying comparison is equivalent to comparing the epoch-second and nano-of-second.
107      *
108      * @return a comparator that compares in time-line order ignoring the chronology
109      * @see #isAfter
110      * @see #isBefore
111      * @see #isEqual
112      */
timeLineOrder()113     public static Comparator<ChronoZonedDateTime<?>> timeLineOrder() {
114         return INSTANT_COMPARATOR;
115     }
116     private static Comparator<ChronoZonedDateTime<?>> INSTANT_COMPARATOR = new Comparator<ChronoZonedDateTime<?>>() {
117         @Override
118         public int compare(ChronoZonedDateTime<?> datetime1, ChronoZonedDateTime<?> datetime2) {
119             int cmp = Jdk8Methods.compareLongs(datetime1.toEpochSecond(), datetime2.toEpochSecond());
120             if (cmp == 0) {
121                 cmp = Jdk8Methods.compareLongs(datetime1.toLocalTime().toNanoOfDay(), datetime2.toLocalTime().toNanoOfDay());
122             }
123             return cmp;
124         }
125     };
126 
127     //-----------------------------------------------------------------------
128     /**
129      * Obtains an instance of {@code ChronoZonedDateTime} from a temporal object.
130      * <p>
131      * This creates a zoned date-time based on the specified temporal.
132      * A {@code TemporalAccessor} represents an arbitrary set of date and time information,
133      * which this factory converts to an instance of {@code ChronoZonedDateTime}.
134      * <p>
135      * The conversion extracts and combines the chronology, date, time and zone
136      * from the temporal object. The behavior is equivalent to using
137      * {@link Chronology#zonedDateTime(TemporalAccessor)} with the extracted chronology.
138      * Implementations are permitted to perform optimizations such as accessing
139      * those fields that are equivalent to the relevant objects.
140      * <p>
141      * This method matches the signature of the functional interface {@link TemporalQuery}
142      * allowing it to be used as a query via method reference, {@code ChronoZonedDateTime::from}.
143      *
144      * @param temporal  the temporal object to convert, not null
145      * @return the date-time, not null
146      * @throws DateTimeException if unable to convert to a {@code ChronoZonedDateTime}
147      * @see Chronology#zonedDateTime(TemporalAccessor)
148      */
from(TemporalAccessor temporal)149     public static ChronoZonedDateTime<?> from(TemporalAccessor temporal) {
150         Jdk8Methods.requireNonNull(temporal, "temporal");
151         if (temporal instanceof ChronoZonedDateTime) {
152             return (ChronoZonedDateTime<?>) temporal;
153         }
154         Chronology chrono = temporal.query(TemporalQueries.chronology());
155         if (chrono == null) {
156             throw new DateTimeException("No Chronology found to create ChronoZonedDateTime: " + temporal.getClass());
157         }
158         return chrono.zonedDateTime(temporal);
159     }
160 
161     //-------------------------------------------------------------------------
162     @Override
range(TemporalField field)163     public ValueRange range(TemporalField field) {
164         if (field instanceof ChronoField) {
165             if (field == INSTANT_SECONDS || field == OFFSET_SECONDS) {
166                 return field.range();
167             }
168             return toLocalDateTime().range(field);
169         }
170         return field.rangeRefinedBy(this);
171     }
172 
173     @Override
get(TemporalField field)174     public int get(TemporalField field) {
175         if (field instanceof ChronoField) {
176             switch ((ChronoField) field) {
177                 case INSTANT_SECONDS: throw new UnsupportedTemporalTypeException("Field too large for an int: " + field);
178                 case OFFSET_SECONDS: return getOffset().getTotalSeconds();
179             }
180             return toLocalDateTime().get(field);
181         }
182         return super.get(field);
183     }
184 
185     @Override
getLong(TemporalField field)186     public long getLong(TemporalField field) {
187         if (field instanceof ChronoField) {
188             switch ((ChronoField) field) {
189                 case INSTANT_SECONDS: return toEpochSecond();
190                 case OFFSET_SECONDS: return getOffset().getTotalSeconds();
191             }
192             return toLocalDateTime().getLong(field);
193         }
194         return field.getFrom(this);
195     }
196 
197     //-------------------------------------------------------------------------
198     /**
199      * Gets the local date part of this date-time.
200      * <p>
201      * This returns a local date with the same year, month and day
202      * as this date-time.
203      *
204      * @return the date part of this date-time, not null
205      */
toLocalDate()206     public D toLocalDate() {
207         return toLocalDateTime().toLocalDate();
208     }
209 
210     /**
211      * Gets the local time part of this date-time.
212      * <p>
213      * This returns a local time with the same hour, minute, second and
214      * nanosecond as this date-time.
215      *
216      * @return the time part of this date-time, not null
217      */
toLocalTime()218     public LocalTime toLocalTime() {
219         return toLocalDateTime().toLocalTime();
220     }
221 
222     /**
223      * Gets the local date-time part of this date-time.
224      * <p>
225      * This returns a local date with the same year, month and day
226      * as this date-time.
227      *
228      * @return the local date-time part of this date-time, not null
229      */
toLocalDateTime()230     public abstract ChronoLocalDateTime<D> toLocalDateTime();
231 
232     /**
233      * Gets the chronology of this date-time.
234      * <p>
235      * The {@code Chronology} represents the calendar system in use.
236      * The era and other fields in {@link ChronoField} are defined by the chronology.
237      *
238      * @return the chronology, not null
239      */
getChronology()240     public Chronology getChronology() {
241         return toLocalDate().getChronology();
242     }
243 
244     /**
245      * Gets the zone offset, such as '+01:00'.
246      * <p>
247      * This is the offset of the local date-time from UTC/Greenwich.
248      *
249      * @return the zone offset, not null
250      */
getOffset()251     public abstract ZoneOffset getOffset();
252 
253     /**
254      * Gets the zone ID, such as 'Europe/Paris'.
255      * <p>
256      * This returns the stored time-zone id used to determine the time-zone rules.
257      *
258      * @return the zone ID, not null
259      */
getZone()260     public abstract ZoneId getZone();
261 
262     //-----------------------------------------------------------------------
263     /**
264      * Returns a copy of this date-time changing the zone offset to the
265      * earlier of the two valid offsets at a local time-line overlap.
266      * <p>
267      * This method only has any effect when the local time-line overlaps, such as
268      * at an autumn daylight savings cutover. In this scenario, there are two
269      * valid offsets for the local date-time. Calling this method will return
270      * a zoned date-time with the earlier of the two selected.
271      * <p>
272      * If this method is called when it is not an overlap, {@code this}
273      * is returned.
274      * <p>
275      * This instance is immutable and unaffected by this method call.
276      *
277      * @return a {@code ZoneChronoDateTime} based on this date-time with the earlier offset, not null
278      * @throws DateTimeException if no rules can be found for the zone
279      * @throws DateTimeException if no rules are valid for this date-time
280      */
withEarlierOffsetAtOverlap()281     public abstract ChronoZonedDateTime<D> withEarlierOffsetAtOverlap();
282 
283     /**
284      * Returns a copy of this date-time changing the zone offset to the
285      * later of the two valid offsets at a local time-line overlap.
286      * <p>
287      * This method only has any effect when the local time-line overlaps, such as
288      * at an autumn daylight savings cutover. In this scenario, there are two
289      * valid offsets for the local date-time. Calling this method will return
290      * a zoned date-time with the later of the two selected.
291      * <p>
292      * If this method is called when it is not an overlap, {@code this}
293      * is returned.
294      * <p>
295      * This instance is immutable and unaffected by this method call.
296      *
297      * @return a {@code ChronoZonedDateTime} based on this date-time with the later offset, not null
298      * @throws DateTimeException if no rules can be found for the zone
299      * @throws DateTimeException if no rules are valid for this date-time
300      */
withLaterOffsetAtOverlap()301     public abstract ChronoZonedDateTime<D> withLaterOffsetAtOverlap();
302 
303     //-----------------------------------------------------------------------
304     /**
305      * Returns a copy of this ZonedDateTime with a different time-zone,
306      * retaining the local date-time if possible.
307      * <p>
308      * This method changes the time-zone and retains the local date-time.
309      * The local date-time is only changed if it is invalid for the new zone.
310      * <p>
311      * To change the zone and adjust the local date-time,
312      * use {@link #withZoneSameInstant(ZoneId)}.
313      * <p>
314      * This instance is immutable and unaffected by this method call.
315      *
316      * @param zoneId  the time-zone to change to, not null
317      * @return a {@code ChronoZonedDateTime} based on this date-time with the requested zone, not null
318      */
withZoneSameLocal(ZoneId zoneId)319     public abstract ChronoZonedDateTime<D> withZoneSameLocal(ZoneId zoneId);
320 
321     /**
322      * Returns a copy of this date-time with a different time-zone,
323      * retaining the instant.
324      * <p>
325      * This method changes the time-zone and retains the instant.
326      * This normally results in a change to the local date-time.
327      * <p>
328      * This method is based on retaining the same instant, thus gaps and overlaps
329      * in the local time-line have no effect on the result.
330      * <p>
331      * To change the offset while keeping the local time,
332      * use {@link #withZoneSameLocal(ZoneId)}.
333      *
334      * @param zoneId  the time-zone to change to, not null
335      * @return a {@code ChronoZonedDateTime} based on this date-time with the requested zone, not null
336      * @throws DateTimeException if the result exceeds the supported date range
337      */
withZoneSameInstant(ZoneId zoneId)338     public abstract ChronoZonedDateTime<D> withZoneSameInstant(ZoneId zoneId);
339 
340     //-------------------------------------------------------------------------
341     // override for covariant return type
342     @Override
with(TemporalAdjuster adjuster)343     public ChronoZonedDateTime<D> with(TemporalAdjuster adjuster) {
344         return toLocalDate().getChronology().ensureChronoZonedDateTime(super.with(adjuster));
345     }
346 
347     @Override
with(TemporalField field, long newValue)348     public abstract ChronoZonedDateTime<D> with(TemporalField field, long newValue);
349 
350     @Override
plus(TemporalAmount amount)351     public ChronoZonedDateTime<D> plus(TemporalAmount amount) {
352         return toLocalDate().getChronology().ensureChronoZonedDateTime(super.plus(amount));
353     }
354 
355     @Override
plus(long amountToAdd, TemporalUnit unit)356     public abstract ChronoZonedDateTime<D> plus(long amountToAdd, TemporalUnit unit);
357 
358     @Override
minus(TemporalAmount amount)359     public ChronoZonedDateTime<D> minus(TemporalAmount amount) {
360         return toLocalDate().getChronology().ensureChronoZonedDateTime(super.minus(amount));
361     }
362 
363     @Override
minus(long amountToSubtract, TemporalUnit unit)364     public ChronoZonedDateTime<D> minus(long amountToSubtract, TemporalUnit unit) {
365         return toLocalDate().getChronology().ensureChronoZonedDateTime(super.minus(amountToSubtract, unit));
366     }
367 
368     //-----------------------------------------------------------------------
369     @SuppressWarnings("unchecked")
370     @Override
query(TemporalQuery<R> query)371     public <R> R query(TemporalQuery<R> query) {
372         if (query == TemporalQueries.zoneId() || query == TemporalQueries.zone()) {
373             return (R) getZone();
374         } else if (query == TemporalQueries.chronology()) {
375             return (R) toLocalDate().getChronology();
376         } else if (query == TemporalQueries.precision()) {
377             return (R) NANOS;
378         } else if (query == TemporalQueries.offset()) {
379             return (R) getOffset();
380         } else if (query == TemporalQueries.localDate()) {
381             return (R) LocalDate.ofEpochDay(toLocalDate().toEpochDay());
382         } else if (query == TemporalQueries.localTime()) {
383             return (R) toLocalTime();
384         }
385         return super.query(query);
386     }
387 
388     /**
389      * Outputs this date-time as a {@code String} using the formatter.
390      *
391      * @param formatter  the formatter to use, not null
392      * @return the formatted date-time string, not null
393      * @throws DateTimeException if an error occurs during printing
394      */
format(DateTimeFormatter formatter)395     public String format(DateTimeFormatter formatter) {
396         Jdk8Methods.requireNonNull(formatter, "formatter");
397         return formatter.format(this);
398     }
399 
400     //-----------------------------------------------------------------------
401     /**
402      * Converts this date-time to an {@code Instant}.
403      * <p>
404      * This returns an {@code Instant} representing the same point on the
405      * time-line as this date-time. The calculation combines the
406      * {@linkplain #toLocalDateTime() local date-time} and
407      * {@linkplain #getOffset() offset}.
408      *
409      * @return an {@code Instant} representing the same instant, not null
410      */
toInstant()411     public Instant toInstant() {
412         return Instant.ofEpochSecond(toEpochSecond(), toLocalTime().getNano());
413     }
414 
415     /**
416      * Converts this date-time to the number of seconds from the epoch
417      * of 1970-01-01T00:00:00Z.
418      * <p>
419      * This uses the {@linkplain #toLocalDateTime() local date-time} and
420      * {@linkplain #getOffset() offset} to calculate the epoch-second value,
421      * which is the number of elapsed seconds from 1970-01-01T00:00:00Z.
422      * Instants on the time-line after the epoch are positive, earlier are negative.
423      *
424      * @return the number of seconds from the epoch of 1970-01-01T00:00:00Z
425      */
toEpochSecond()426     public long toEpochSecond() {
427         long epochDay = toLocalDate().toEpochDay();
428         long secs = epochDay * 86400 + toLocalTime().toSecondOfDay();
429         secs -= getOffset().getTotalSeconds();
430         return secs;
431     }
432 
433     //-----------------------------------------------------------------------
434     /**
435      * Compares this date-time to another date-time, including the chronology.
436      * <p>
437      * The comparison is based first on the instant, then on the local date-time,
438      * then on the zone ID, then on the chronology.
439      * It is "consistent with equals", as defined by {@link Comparable}.
440      * <p>
441      * If all the date-time objects being compared are in the same chronology, then the
442      * additional chronology stage is not required.
443      *
444      * @param other  the other date-time to compare to, not null
445      * @return the comparator value, negative if less, positive if greater
446      */
447     @Override
compareTo(ChronoZonedDateTime<?> other)448     public int compareTo(ChronoZonedDateTime<?> other) {
449         int cmp = Jdk8Methods.compareLongs(toEpochSecond(), other.toEpochSecond());
450         if (cmp == 0) {
451             cmp = toLocalTime().getNano() - other.toLocalTime().getNano();
452             if (cmp == 0) {
453                 cmp = toLocalDateTime().compareTo(other.toLocalDateTime());
454                 if (cmp == 0) {
455                     cmp = getZone().getId().compareTo(other.getZone().getId());
456                     if (cmp == 0) {
457                         cmp = toLocalDate().getChronology().compareTo(other.toLocalDate().getChronology());
458                     }
459                 }
460             }
461         }
462         return cmp;
463     }
464 
465     //-----------------------------------------------------------------------
466     /**
467      * Checks if the instant of this date-time is after that of the specified date-time.
468      * <p>
469      * This method differs from the comparison in {@link #compareTo} in that it
470      * only compares the instant of the date-time. This is equivalent to using
471      * {@code dateTime1.toInstant().isAfter(dateTime2.toInstant());}.
472      *
473      * @param other  the other date-time to compare to, not null
474      * @return true if this is after the specified date-time
475      */
isAfter(ChronoZonedDateTime<?> other)476     public boolean isAfter(ChronoZonedDateTime<?> other) {
477         long thisEpochSec = toEpochSecond();
478         long otherEpochSec = other.toEpochSecond();
479         return thisEpochSec > otherEpochSec ||
480             (thisEpochSec == otherEpochSec && toLocalTime().getNano() > other.toLocalTime().getNano());
481     }
482 
483     /**
484      * Checks if the instant of this date-time is before that of the specified date-time.
485      * <p>
486      * This method differs from the comparison in {@link #compareTo} in that it
487      * only compares the instant of the date-time. This is equivalent to using
488      * {@code dateTime1.toInstant().isBefore(dateTime2.toInstant());}.
489      *
490      * @param other  the other date-time to compare to, not null
491      * @return true if this point is before the specified date-time
492      */
isBefore(ChronoZonedDateTime<?> other)493     public boolean isBefore(ChronoZonedDateTime<?> other) {
494         long thisEpochSec = toEpochSecond();
495         long otherEpochSec = other.toEpochSecond();
496         return thisEpochSec < otherEpochSec ||
497             (thisEpochSec == otherEpochSec && toLocalTime().getNano() < other.toLocalTime().getNano());
498     }
499 
500     /**
501      * Checks if the instant of this date-time is equal to that of the specified date-time.
502      * <p>
503      * This method differs from the comparison in {@link #compareTo} and {@link #equals}
504      * in that it only compares the instant of the date-time. This is equivalent to using
505      * {@code dateTime1.toInstant().equals(dateTime2.toInstant());}.
506      *
507      * @param other  the other date-time to compare to, not null
508      * @return true if the instant equals the instant of the specified date-time
509      */
isEqual(ChronoZonedDateTime<?> other)510     public boolean isEqual(ChronoZonedDateTime<?> other) {
511         return toEpochSecond() == other.toEpochSecond() &&
512                 toLocalTime().getNano() == other.toLocalTime().getNano();
513     }
514 
515     //-----------------------------------------------------------------------
516     /**
517      * Checks if this date-time is equal to another date-time.
518      * <p>
519      * The comparison is based on the offset date-time and the zone.
520      * To compare for the same instant on the time-line, use {@link #compareTo}.
521      * Only objects of type {@code ChronoZoneDateTime} are compared, other types return false.
522      *
523      * @param obj  the object to check, null returns false
524      * @return true if this is equal to the other date-time
525      */
526     @Override
equals(Object obj)527     public boolean equals(Object obj) {
528         if (this == obj) {
529             return true;
530         }
531         if (obj instanceof ChronoZonedDateTime) {
532             return compareTo((ChronoZonedDateTime<?>) obj) == 0;
533         }
534         return false;
535     }
536 
537     /**
538      * A hash code for this date-time.
539      *
540      * @return a suitable hash code
541      */
542     @Override
hashCode()543     public int hashCode() {
544         return toLocalDateTime().hashCode() ^ getOffset().hashCode() ^ Integer.rotateLeft(getZone().hashCode(), 3);
545     }
546 
547     //-----------------------------------------------------------------------
548     /**
549      * Outputs this date-time as a {@code String}.
550      * <p>
551      * The output will include the full zoned date-time and the chronology ID.
552      *
553      * @return a string representation of this date-time, not null
554      */
555     @Override
toString()556     public String toString() {
557         String str = toLocalDateTime().toString() + getOffset().toString();
558         if (getOffset() != getZone()) {
559             str += '[' + getZone().toString() + ']';
560         }
561         return str;
562     }
563 
564 }
565