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