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