Path: blob/master/src/java.base/share/classes/java/time/zone/ZoneOffsetTransition.java
41159 views
/*1* Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425/*26* This file is available under and governed by the GNU General Public27* License version 2 only, as published by the Free Software Foundation.28* However, the following notice accompanied the original version of this29* file:30*31* Copyright (c) 2009-2012, Stephen Colebourne & Michael Nascimento Santos32*33* All rights reserved.34*35* Redistribution and use in source and binary forms, with or without36* modification, are permitted provided that the following conditions are met:37*38* * Redistributions of source code must retain the above copyright notice,39* this list of conditions and the following disclaimer.40*41* * Redistributions in binary form must reproduce the above copyright notice,42* this list of conditions and the following disclaimer in the documentation43* and/or other materials provided with the distribution.44*45* * Neither the name of JSR-310 nor the names of its contributors46* may be used to endorse or promote products derived from this software47* without specific prior written permission.48*49* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS50* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT51* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR52* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR53* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,54* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,55* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR56* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF57* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING58* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS59* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.60*/61package java.time.zone;6263import java.io.DataInput;64import java.io.DataOutput;65import java.io.IOException;66import java.io.InvalidObjectException;67import java.io.ObjectInputStream;68import java.io.Serializable;69import java.time.Duration;70import java.time.Instant;71import java.time.LocalDateTime;72import java.time.ZoneOffset;73import java.util.Arrays;74import java.util.Collections;75import java.util.List;76import java.util.Objects;7778/**79* A transition between two offsets caused by a discontinuity in the local time-line.80* <p>81* A transition between two offsets is normally the result of a daylight savings cutover.82* The discontinuity is normally a gap in spring and an overlap in autumn.83* {@code ZoneOffsetTransition} models the transition between the two offsets.84* <p>85* Gaps occur where there are local date-times that simply do not exist.86* An example would be when the offset changes from {@code +03:00} to {@code +04:00}.87* This might be described as 'the clocks will move forward one hour tonight at 1am'.88* <p>89* Overlaps occur where there are local date-times that exist twice.90* An example would be when the offset changes from {@code +04:00} to {@code +03:00}.91* This might be described as 'the clocks will move back one hour tonight at 2am'.92*93* @implSpec94* This class is immutable and thread-safe.95*96* @since 1.897*/98public final class ZoneOffsetTransition99implements Comparable<ZoneOffsetTransition>, Serializable {100101/**102* Serialization version.103*/104private static final long serialVersionUID = -6946044323557704546L;105/**106* The transition epoch-second.107*/108private final long epochSecond;109/**110* The local transition date-time at the transition.111*/112private final LocalDateTime transition;113/**114* The offset before transition.115*/116private final ZoneOffset offsetBefore;117/**118* The offset after transition.119*/120private final ZoneOffset offsetAfter;121122//-----------------------------------------------------------------------123/**124* Obtains an instance defining a transition between two offsets.125* <p>126* Applications should normally obtain an instance from {@link ZoneRules}.127* This factory is only intended for use when creating {@link ZoneRules}.128*129* @param transition the transition date-time at the transition, which never130* actually occurs, expressed local to the before offset, not null131* @param offsetBefore the offset before the transition, not null132* @param offsetAfter the offset at and after the transition, not null133* @return the transition, not null134* @throws IllegalArgumentException if {@code offsetBefore} and {@code offsetAfter}135* are equal, or {@code transition.getNano()} returns non-zero value136*/137public static ZoneOffsetTransition of(LocalDateTime transition, ZoneOffset offsetBefore, ZoneOffset offsetAfter) {138Objects.requireNonNull(transition, "transition");139Objects.requireNonNull(offsetBefore, "offsetBefore");140Objects.requireNonNull(offsetAfter, "offsetAfter");141if (offsetBefore.equals(offsetAfter)) {142throw new IllegalArgumentException("Offsets must not be equal");143}144if (transition.getNano() != 0) {145throw new IllegalArgumentException("Nano-of-second must be zero");146}147return new ZoneOffsetTransition(transition, offsetBefore, offsetAfter);148}149150/**151* Creates an instance defining a transition between two offsets.152*153* @param transition the transition date-time with the offset before the transition, not null154* @param offsetBefore the offset before the transition, not null155* @param offsetAfter the offset at and after the transition, not null156*/157ZoneOffsetTransition(LocalDateTime transition, ZoneOffset offsetBefore, ZoneOffset offsetAfter) {158assert transition.getNano() == 0;159this.epochSecond = transition.toEpochSecond(offsetBefore);160this.transition = transition;161this.offsetBefore = offsetBefore;162this.offsetAfter = offsetAfter;163}164165/**166* Creates an instance from epoch-second and offsets.167*168* @param epochSecond the transition epoch-second169* @param offsetBefore the offset before the transition, not null170* @param offsetAfter the offset at and after the transition, not null171*/172ZoneOffsetTransition(long epochSecond, ZoneOffset offsetBefore, ZoneOffset offsetAfter) {173this.epochSecond = epochSecond;174this.transition = LocalDateTime.ofEpochSecond(epochSecond, 0, offsetBefore);175this.offsetBefore = offsetBefore;176this.offsetAfter = offsetAfter;177}178179//-----------------------------------------------------------------------180/**181* Defend against malicious streams.182*183* @param s the stream to read184* @throws InvalidObjectException always185*/186private void readObject(ObjectInputStream s) throws InvalidObjectException {187throw new InvalidObjectException("Deserialization via serialization delegate");188}189190/**191* Writes the object using a192* <a href="{@docRoot}/serialized-form.html#java.time.zone.Ser">dedicated serialized form</a>.193* @serialData194* Refer to the serialized form of195* <a href="{@docRoot}/serialized-form.html#java.time.zone.ZoneRules">ZoneRules.writeReplace</a>196* for the encoding of epoch seconds and offsets.197* <pre style="font-size:1.0em">{@code198*199* out.writeByte(2); // identifies a ZoneOffsetTransition200* out.writeEpochSec(toEpochSecond);201* out.writeOffset(offsetBefore);202* out.writeOffset(offsetAfter);203* }204* </pre>205* @return the replacing object, not null206*/207private Object writeReplace() {208return new Ser(Ser.ZOT, this);209}210211/**212* Writes the state to the stream.213*214* @param out the output stream, not null215* @throws IOException if an error occurs216*/217void writeExternal(DataOutput out) throws IOException {218Ser.writeEpochSec(epochSecond, out);219Ser.writeOffset(offsetBefore, out);220Ser.writeOffset(offsetAfter, out);221}222223/**224* Reads the state from the stream.225*226* @param in the input stream, not null227* @return the created object, not null228* @throws IOException if an error occurs229*/230static ZoneOffsetTransition readExternal(DataInput in) throws IOException {231long epochSecond = Ser.readEpochSec(in);232ZoneOffset before = Ser.readOffset(in);233ZoneOffset after = Ser.readOffset(in);234if (before.equals(after)) {235throw new IllegalArgumentException("Offsets must not be equal");236}237return new ZoneOffsetTransition(epochSecond, before, after);238}239240//-----------------------------------------------------------------------241/**242* Gets the transition instant.243* <p>244* This is the instant of the discontinuity, which is defined as the first245* instant that the 'after' offset applies.246* <p>247* The methods {@link #getInstant()}, {@link #getDateTimeBefore()} and {@link #getDateTimeAfter()}248* all represent the same instant.249*250* @return the transition instant, not null251*/252public Instant getInstant() {253return Instant.ofEpochSecond(epochSecond);254}255256/**257* Gets the transition instant as an epoch second.258*259* @return the transition epoch second260*/261public long toEpochSecond() {262return epochSecond;263}264265//-------------------------------------------------------------------------266/**267* Gets the local transition date-time, as would be expressed with the 'before' offset.268* <p>269* This is the date-time where the discontinuity begins expressed with the 'before' offset.270* At this instant, the 'after' offset is actually used, therefore the combination of this271* date-time and the 'before' offset will never occur.272* <p>273* The combination of the 'before' date-time and offset represents the same instant274* as the 'after' date-time and offset.275*276* @return the transition date-time expressed with the before offset, not null277*/278public LocalDateTime getDateTimeBefore() {279return transition;280}281282/**283* Gets the local transition date-time, as would be expressed with the 'after' offset.284* <p>285* This is the first date-time after the discontinuity, when the new offset applies.286* <p>287* The combination of the 'before' date-time and offset represents the same instant288* as the 'after' date-time and offset.289*290* @return the transition date-time expressed with the after offset, not null291*/292public LocalDateTime getDateTimeAfter() {293return transition.plusSeconds(getDurationSeconds());294}295296/**297* Gets the offset before the transition.298* <p>299* This is the offset in use before the instant of the transition.300*301* @return the offset before the transition, not null302*/303public ZoneOffset getOffsetBefore() {304return offsetBefore;305}306307/**308* Gets the offset after the transition.309* <p>310* This is the offset in use on and after the instant of the transition.311*312* @return the offset after the transition, not null313*/314public ZoneOffset getOffsetAfter() {315return offsetAfter;316}317318/**319* Gets the duration of the transition.320* <p>321* In most cases, the transition duration is one hour, however this is not always the case.322* The duration will be positive for a gap and negative for an overlap.323* Time-zones are second-based, so the nanosecond part of the duration will be zero.324*325* @return the duration of the transition, positive for gaps, negative for overlaps326*/327public Duration getDuration() {328return Duration.ofSeconds(getDurationSeconds());329}330331/**332* Gets the duration of the transition in seconds.333*334* @return the duration in seconds335*/336private int getDurationSeconds() {337return getOffsetAfter().getTotalSeconds() - getOffsetBefore().getTotalSeconds();338}339340/**341* Does this transition represent a gap in the local time-line.342* <p>343* Gaps occur where there are local date-times that simply do not exist.344* An example would be when the offset changes from {@code +01:00} to {@code +02:00}.345* This might be described as 'the clocks will move forward one hour tonight at 1am'.346*347* @return true if this transition is a gap, false if it is an overlap348*/349public boolean isGap() {350return getOffsetAfter().getTotalSeconds() > getOffsetBefore().getTotalSeconds();351}352353/**354* Does this transition represent an overlap in the local time-line.355* <p>356* Overlaps occur where there are local date-times that exist twice.357* An example would be when the offset changes from {@code +02:00} to {@code +01:00}.358* This might be described as 'the clocks will move back one hour tonight at 2am'.359*360* @return true if this transition is an overlap, false if it is a gap361*/362public boolean isOverlap() {363return getOffsetAfter().getTotalSeconds() < getOffsetBefore().getTotalSeconds();364}365366/**367* Checks if the specified offset is valid during this transition.368* <p>369* This checks to see if the given offset will be valid at some point in the transition.370* A gap will always return false.371* An overlap will return true if the offset is either the before or after offset.372*373* @param offset the offset to check, null returns false374* @return true if the offset is valid during the transition375*/376public boolean isValidOffset(ZoneOffset offset) {377return isGap() ? false : (getOffsetBefore().equals(offset) || getOffsetAfter().equals(offset));378}379380/**381* Gets the valid offsets during this transition.382* <p>383* A gap will return an empty list, while an overlap will return both offsets.384*385* @return the list of valid offsets386*/387List<ZoneOffset> getValidOffsets() {388if (isGap()) {389return List.of();390}391return List.of(getOffsetBefore(), getOffsetAfter());392}393394//-----------------------------------------------------------------------395/**396* Compares this transition to another based on the transition instant.397* <p>398* This compares the instants of each transition.399* The offsets are ignored, making this order inconsistent with equals.400*401* @param transition the transition to compare to, not null402* @return the comparator value, negative if less, positive if greater403*/404@Override405public int compareTo(ZoneOffsetTransition transition) {406return Long.compare(epochSecond, transition.epochSecond);407}408409//-----------------------------------------------------------------------410/**411* Checks if this object equals another.412* <p>413* The entire state of the object is compared.414*415* @param other the other object to compare to, null returns false416* @return true if equal417*/418@Override419public boolean equals(Object other) {420if (other == this) {421return true;422}423return (other instanceof ZoneOffsetTransition d)424&& epochSecond == d.epochSecond425&& offsetBefore.equals(d.offsetBefore)426&& offsetAfter.equals(d.offsetAfter);427}428429/**430* Returns a suitable hash code.431*432* @return the hash code433*/434@Override435public int hashCode() {436return transition.hashCode() ^ offsetBefore.hashCode() ^ Integer.rotateLeft(offsetAfter.hashCode(), 16);437}438439//-----------------------------------------------------------------------440/**441* Returns a string describing this object.442*443* @return a string for debugging, not null444*/445@Override446public String toString() {447StringBuilder buf = new StringBuilder();448buf.append("Transition[")449.append(isGap() ? "Gap" : "Overlap")450.append(" at ")451.append(transition)452.append(offsetBefore)453.append(" to ")454.append(offsetAfter)455.append(']');456return buf.toString();457}458459}460461462