Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/java/time/zone/ZoneRules.java
38918 views
/*1* Copyright (c) 2012, 2013, 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.LocalDate;72import java.time.LocalDateTime;73import java.time.ZoneId;74import java.time.ZoneOffset;75import java.time.Year;76import java.util.ArrayList;77import java.util.Arrays;78import java.util.Collections;79import java.util.List;80import java.util.Objects;81import java.util.concurrent.ConcurrentHashMap;82import java.util.concurrent.ConcurrentMap;8384/**85* The rules defining how the zone offset varies for a single time-zone.86* <p>87* The rules model all the historic and future transitions for a time-zone.88* {@link ZoneOffsetTransition} is used for known transitions, typically historic.89* {@link ZoneOffsetTransitionRule} is used for future transitions that are based90* on the result of an algorithm.91* <p>92* The rules are loaded via {@link ZoneRulesProvider} using a {@link ZoneId}.93* The same rules may be shared internally between multiple zone IDs.94* <p>95* Serializing an instance of {@code ZoneRules} will store the entire set of rules.96* It does not store the zone ID as it is not part of the state of this object.97* <p>98* A rule implementation may or may not store full information about historic99* and future transitions, and the information stored is only as accurate as100* that supplied to the implementation by the rules provider.101* Applications should treat the data provided as representing the best information102* available to the implementation of this rule.103*104* @implSpec105* This class is immutable and thread-safe.106*107* @since 1.8108*/109public final class ZoneRules implements Serializable {110111/**112* Serialization version.113*/114private static final long serialVersionUID = 3044319355680032515L;115/**116* The last year to have its transitions cached.117*/118private static final int LAST_CACHED_YEAR = 2100;119120/**121* The transitions between standard offsets (epoch seconds), sorted.122*/123private final long[] standardTransitions;124/**125* The standard offsets.126*/127private final ZoneOffset[] standardOffsets;128/**129* The transitions between instants (epoch seconds), sorted.130*/131private final long[] savingsInstantTransitions;132/**133* The transitions between local date-times, sorted.134* This is a paired array, where the first entry is the start of the transition135* and the second entry is the end of the transition.136*/137private final LocalDateTime[] savingsLocalTransitions;138/**139* The wall offsets.140*/141private final ZoneOffset[] wallOffsets;142/**143* The last rule.144*/145private final ZoneOffsetTransitionRule[] lastRules;146/**147* The map of recent transitions.148*/149private final transient ConcurrentMap<Integer, ZoneOffsetTransition[]> lastRulesCache =150new ConcurrentHashMap<Integer, ZoneOffsetTransition[]>();151/**152* The zero-length long array.153*/154private static final long[] EMPTY_LONG_ARRAY = new long[0];155/**156* The zero-length lastrules array.157*/158private static final ZoneOffsetTransitionRule[] EMPTY_LASTRULES =159new ZoneOffsetTransitionRule[0];160/**161* The zero-length ldt array.162*/163private static final LocalDateTime[] EMPTY_LDT_ARRAY = new LocalDateTime[0];164165/**166* Obtains an instance of a ZoneRules.167*168* @param baseStandardOffset the standard offset to use before legal rules were set, not null169* @param baseWallOffset the wall offset to use before legal rules were set, not null170* @param standardOffsetTransitionList the list of changes to the standard offset, not null171* @param transitionList the list of transitions, not null172* @param lastRules the recurring last rules, size 16 or less, not null173* @return the zone rules, not null174*/175public static ZoneRules of(ZoneOffset baseStandardOffset,176ZoneOffset baseWallOffset,177List<ZoneOffsetTransition> standardOffsetTransitionList,178List<ZoneOffsetTransition> transitionList,179List<ZoneOffsetTransitionRule> lastRules) {180Objects.requireNonNull(baseStandardOffset, "baseStandardOffset");181Objects.requireNonNull(baseWallOffset, "baseWallOffset");182Objects.requireNonNull(standardOffsetTransitionList, "standardOffsetTransitionList");183Objects.requireNonNull(transitionList, "transitionList");184Objects.requireNonNull(lastRules, "lastRules");185return new ZoneRules(baseStandardOffset, baseWallOffset,186standardOffsetTransitionList, transitionList, lastRules);187}188189/**190* Obtains an instance of ZoneRules that has fixed zone rules.191*192* @param offset the offset this fixed zone rules is based on, not null193* @return the zone rules, not null194* @see #isFixedOffset()195*/196public static ZoneRules of(ZoneOffset offset) {197Objects.requireNonNull(offset, "offset");198return new ZoneRules(offset);199}200201/**202* Creates an instance.203*204* @param baseStandardOffset the standard offset to use before legal rules were set, not null205* @param baseWallOffset the wall offset to use before legal rules were set, not null206* @param standardOffsetTransitionList the list of changes to the standard offset, not null207* @param transitionList the list of transitions, not null208* @param lastRules the recurring last rules, size 16 or less, not null209*/210ZoneRules(ZoneOffset baseStandardOffset,211ZoneOffset baseWallOffset,212List<ZoneOffsetTransition> standardOffsetTransitionList,213List<ZoneOffsetTransition> transitionList,214List<ZoneOffsetTransitionRule> lastRules) {215super();216217// convert standard transitions218219this.standardTransitions = new long[standardOffsetTransitionList.size()];220221this.standardOffsets = new ZoneOffset[standardOffsetTransitionList.size() + 1];222this.standardOffsets[0] = baseStandardOffset;223for (int i = 0; i < standardOffsetTransitionList.size(); i++) {224this.standardTransitions[i] = standardOffsetTransitionList.get(i).toEpochSecond();225this.standardOffsets[i + 1] = standardOffsetTransitionList.get(i).getOffsetAfter();226}227228// convert savings transitions to locals229List<LocalDateTime> localTransitionList = new ArrayList<>();230List<ZoneOffset> localTransitionOffsetList = new ArrayList<>();231localTransitionOffsetList.add(baseWallOffset);232for (ZoneOffsetTransition trans : transitionList) {233if (trans.isGap()) {234localTransitionList.add(trans.getDateTimeBefore());235localTransitionList.add(trans.getDateTimeAfter());236} else {237localTransitionList.add(trans.getDateTimeAfter());238localTransitionList.add(trans.getDateTimeBefore());239}240localTransitionOffsetList.add(trans.getOffsetAfter());241}242this.savingsLocalTransitions = localTransitionList.toArray(new LocalDateTime[localTransitionList.size()]);243this.wallOffsets = localTransitionOffsetList.toArray(new ZoneOffset[localTransitionOffsetList.size()]);244245// convert savings transitions to instants246this.savingsInstantTransitions = new long[transitionList.size()];247for (int i = 0; i < transitionList.size(); i++) {248this.savingsInstantTransitions[i] = transitionList.get(i).toEpochSecond();249}250251// last rules252if (lastRules.size() > 16) {253throw new IllegalArgumentException("Too many transition rules");254}255this.lastRules = lastRules.toArray(new ZoneOffsetTransitionRule[lastRules.size()]);256}257258/**259* Constructor.260*261* @param standardTransitions the standard transitions, not null262* @param standardOffsets the standard offsets, not null263* @param savingsInstantTransitions the standard transitions, not null264* @param wallOffsets the wall offsets, not null265* @param lastRules the recurring last rules, size 15 or less, not null266*/267private ZoneRules(long[] standardTransitions,268ZoneOffset[] standardOffsets,269long[] savingsInstantTransitions,270ZoneOffset[] wallOffsets,271ZoneOffsetTransitionRule[] lastRules) {272super();273274this.standardTransitions = standardTransitions;275this.standardOffsets = standardOffsets;276this.savingsInstantTransitions = savingsInstantTransitions;277this.wallOffsets = wallOffsets;278this.lastRules = lastRules;279280if (savingsInstantTransitions.length == 0) {281this.savingsLocalTransitions = EMPTY_LDT_ARRAY;282} else {283// convert savings transitions to locals284List<LocalDateTime> localTransitionList = new ArrayList<>();285for (int i = 0; i < savingsInstantTransitions.length; i++) {286ZoneOffset before = wallOffsets[i];287ZoneOffset after = wallOffsets[i + 1];288ZoneOffsetTransition trans = new ZoneOffsetTransition(savingsInstantTransitions[i], before, after);289if (trans.isGap()) {290localTransitionList.add(trans.getDateTimeBefore());291localTransitionList.add(trans.getDateTimeAfter());292} else {293localTransitionList.add(trans.getDateTimeAfter());294localTransitionList.add(trans.getDateTimeBefore());295}296}297this.savingsLocalTransitions = localTransitionList.toArray(new LocalDateTime[localTransitionList.size()]);298}299}300301/**302* Creates an instance of ZoneRules that has fixed zone rules.303*304* @param offset the offset this fixed zone rules is based on, not null305* @return the zone rules, not null306* @see #isFixedOffset()307*/308private ZoneRules(ZoneOffset offset) {309this.standardOffsets = new ZoneOffset[1];310this.standardOffsets[0] = offset;311this.standardTransitions = EMPTY_LONG_ARRAY;312this.savingsInstantTransitions = EMPTY_LONG_ARRAY;313this.savingsLocalTransitions = EMPTY_LDT_ARRAY;314this.wallOffsets = standardOffsets;315this.lastRules = EMPTY_LASTRULES;316}317318/**319* Defend against malicious streams.320*321* @param s the stream to read322* @throws InvalidObjectException always323*/324private void readObject(ObjectInputStream s) throws InvalidObjectException {325throw new InvalidObjectException("Deserialization via serialization delegate");326}327328/**329* Writes the object using a330* <a href="../../../serialized-form.html#java.time.zone.Ser">dedicated serialized form</a>.331* @serialData332* <pre style="font-size:1.0em">{@code333*334* out.writeByte(1); // identifies a ZoneRules335* out.writeInt(standardTransitions.length);336* for (long trans : standardTransitions) {337* Ser.writeEpochSec(trans, out);338* }339* for (ZoneOffset offset : standardOffsets) {340* Ser.writeOffset(offset, out);341* }342* out.writeInt(savingsInstantTransitions.length);343* for (long trans : savingsInstantTransitions) {344* Ser.writeEpochSec(trans, out);345* }346* for (ZoneOffset offset : wallOffsets) {347* Ser.writeOffset(offset, out);348* }349* out.writeByte(lastRules.length);350* for (ZoneOffsetTransitionRule rule : lastRules) {351* rule.writeExternal(out);352* }353* }354* </pre>355* <p>356* Epoch second values used for offsets are encoded in a variable357* length form to make the common cases put fewer bytes in the stream.358* <pre style="font-size:1.0em">{@code359*360* static void writeEpochSec(long epochSec, DataOutput out) throws IOException {361* if (epochSec >= -4575744000L && epochSec < 10413792000L && epochSec % 900 == 0) { // quarter hours between 1825 and 2300362* int store = (int) ((epochSec + 4575744000L) / 900);363* out.writeByte((store >>> 16) & 255);364* out.writeByte((store >>> 8) & 255);365* out.writeByte(store & 255);366* } else {367* out.writeByte(255);368* out.writeLong(epochSec);369* }370* }371* }372* </pre>373* <p>374* ZoneOffset values are encoded in a variable length form so the375* common cases put fewer bytes in the stream.376* <pre style="font-size:1.0em">{@code377*378* static void writeOffset(ZoneOffset offset, DataOutput out) throws IOException {379* final int offsetSecs = offset.getTotalSeconds();380* int offsetByte = offsetSecs % 900 == 0 ? offsetSecs / 900 : 127; // compress to -72 to +72381* out.writeByte(offsetByte);382* if (offsetByte == 127) {383* out.writeInt(offsetSecs);384* }385* }386*}387* </pre>388* @return the replacing object, not null389*/390private Object writeReplace() {391return new Ser(Ser.ZRULES, this);392}393394/**395* Writes the state to the stream.396*397* @param out the output stream, not null398* @throws IOException if an error occurs399*/400void writeExternal(DataOutput out) throws IOException {401out.writeInt(standardTransitions.length);402for (long trans : standardTransitions) {403Ser.writeEpochSec(trans, out);404}405for (ZoneOffset offset : standardOffsets) {406Ser.writeOffset(offset, out);407}408out.writeInt(savingsInstantTransitions.length);409for (long trans : savingsInstantTransitions) {410Ser.writeEpochSec(trans, out);411}412for (ZoneOffset offset : wallOffsets) {413Ser.writeOffset(offset, out);414}415out.writeByte(lastRules.length);416for (ZoneOffsetTransitionRule rule : lastRules) {417rule.writeExternal(out);418}419}420421/**422* Reads the state from the stream.423*424* @param in the input stream, not null425* @return the created object, not null426* @throws IOException if an error occurs427*/428static ZoneRules readExternal(DataInput in) throws IOException, ClassNotFoundException {429int stdSize = in.readInt();430long[] stdTrans = (stdSize == 0) ? EMPTY_LONG_ARRAY431: new long[stdSize];432for (int i = 0; i < stdSize; i++) {433stdTrans[i] = Ser.readEpochSec(in);434}435ZoneOffset[] stdOffsets = new ZoneOffset[stdSize + 1];436for (int i = 0; i < stdOffsets.length; i++) {437stdOffsets[i] = Ser.readOffset(in);438}439int savSize = in.readInt();440long[] savTrans = (savSize == 0) ? EMPTY_LONG_ARRAY441: new long[savSize];442for (int i = 0; i < savSize; i++) {443savTrans[i] = Ser.readEpochSec(in);444}445ZoneOffset[] savOffsets = new ZoneOffset[savSize + 1];446for (int i = 0; i < savOffsets.length; i++) {447savOffsets[i] = Ser.readOffset(in);448}449int ruleSize = in.readByte();450ZoneOffsetTransitionRule[] rules = (ruleSize == 0) ?451EMPTY_LASTRULES : new ZoneOffsetTransitionRule[ruleSize];452for (int i = 0; i < ruleSize; i++) {453rules[i] = ZoneOffsetTransitionRule.readExternal(in);454}455return new ZoneRules(stdTrans, stdOffsets, savTrans, savOffsets, rules);456}457458/**459* Checks of the zone rules are fixed, such that the offset never varies.460*461* @return true if the time-zone is fixed and the offset never changes462*/463public boolean isFixedOffset() {464return savingsInstantTransitions.length == 0;465}466467/**468* Gets the offset applicable at the specified instant in these rules.469* <p>470* The mapping from an instant to an offset is simple, there is only471* one valid offset for each instant.472* This method returns that offset.473*474* @param instant the instant to find the offset for, not null, but null475* may be ignored if the rules have a single offset for all instants476* @return the offset, not null477*/478public ZoneOffset getOffset(Instant instant) {479if (savingsInstantTransitions.length == 0) {480return standardOffsets[0];481}482long epochSec = instant.getEpochSecond();483// check if using last rules484if (lastRules.length > 0 &&485epochSec > savingsInstantTransitions[savingsInstantTransitions.length - 1]) {486int year = findYear(epochSec, wallOffsets[wallOffsets.length - 1]);487ZoneOffsetTransition[] transArray = findTransitionArray(year);488ZoneOffsetTransition trans = null;489for (int i = 0; i < transArray.length; i++) {490trans = transArray[i];491if (epochSec < trans.toEpochSecond()) {492return trans.getOffsetBefore();493}494}495return trans.getOffsetAfter();496}497498// using historic rules499int index = Arrays.binarySearch(savingsInstantTransitions, epochSec);500if (index < 0) {501// switch negative insert position to start of matched range502index = -index - 2;503}504return wallOffsets[index + 1];505}506507/**508* Gets a suitable offset for the specified local date-time in these rules.509* <p>510* The mapping from a local date-time to an offset is not straightforward.511* There are three cases:512* <ul>513* <li>Normal, with one valid offset. For the vast majority of the year, the normal514* case applies, where there is a single valid offset for the local date-time.</li>515* <li>Gap, with zero valid offsets. This is when clocks jump forward typically516* due to the spring daylight savings change from "winter" to "summer".517* In a gap there are local date-time values with no valid offset.</li>518* <li>Overlap, with two valid offsets. This is when clocks are set back typically519* due to the autumn daylight savings change from "summer" to "winter".520* In an overlap there are local date-time values with two valid offsets.</li>521* </ul>522* Thus, for any given local date-time there can be zero, one or two valid offsets.523* This method returns the single offset in the Normal case, and in the Gap or Overlap524* case it returns the offset before the transition.525* <p>526* Since, in the case of Gap and Overlap, the offset returned is a "best" value, rather527* than the "correct" value, it should be treated with care. Applications that care528* about the correct offset should use a combination of this method,529* {@link #getValidOffsets(LocalDateTime)} and {@link #getTransition(LocalDateTime)}.530*531* @param localDateTime the local date-time to query, not null, but null532* may be ignored if the rules have a single offset for all instants533* @return the best available offset for the local date-time, not null534*/535public ZoneOffset getOffset(LocalDateTime localDateTime) {536Object info = getOffsetInfo(localDateTime);537if (info instanceof ZoneOffsetTransition) {538return ((ZoneOffsetTransition) info).getOffsetBefore();539}540return (ZoneOffset) info;541}542543/**544* Gets the offset applicable at the specified local date-time in these rules.545* <p>546* The mapping from a local date-time to an offset is not straightforward.547* There are three cases:548* <ul>549* <li>Normal, with one valid offset. For the vast majority of the year, the normal550* case applies, where there is a single valid offset for the local date-time.</li>551* <li>Gap, with zero valid offsets. This is when clocks jump forward typically552* due to the spring daylight savings change from "winter" to "summer".553* In a gap there are local date-time values with no valid offset.</li>554* <li>Overlap, with two valid offsets. This is when clocks are set back typically555* due to the autumn daylight savings change from "summer" to "winter".556* In an overlap there are local date-time values with two valid offsets.</li>557* </ul>558* Thus, for any given local date-time there can be zero, one or two valid offsets.559* This method returns that list of valid offsets, which is a list of size 0, 1 or 2.560* In the case where there are two offsets, the earlier offset is returned at index 0561* and the later offset at index 1.562* <p>563* There are various ways to handle the conversion from a {@code LocalDateTime}.564* One technique, using this method, would be:565* <pre>566* List<ZoneOffset> validOffsets = rules.getOffset(localDT);567* if (validOffsets.size() == 1) {568* // Normal case: only one valid offset569* zoneOffset = validOffsets.get(0);570* } else {571* // Gap or Overlap: determine what to do from transition (which will be non-null)572* ZoneOffsetTransition trans = rules.getTransition(localDT);573* }574* </pre>575* <p>576* In theory, it is possible for there to be more than two valid offsets.577* This would happen if clocks to be put back more than once in quick succession.578* This has never happened in the history of time-zones and thus has no special handling.579* However, if it were to happen, then the list would return more than 2 entries.580*581* @param localDateTime the local date-time to query for valid offsets, not null, but null582* may be ignored if the rules have a single offset for all instants583* @return the list of valid offsets, may be immutable, not null584*/585public List<ZoneOffset> getValidOffsets(LocalDateTime localDateTime) {586// should probably be optimized587Object info = getOffsetInfo(localDateTime);588if (info instanceof ZoneOffsetTransition) {589return ((ZoneOffsetTransition) info).getValidOffsets();590}591return Collections.singletonList((ZoneOffset) info);592}593594/**595* Gets the offset transition applicable at the specified local date-time in these rules.596* <p>597* The mapping from a local date-time to an offset is not straightforward.598* There are three cases:599* <ul>600* <li>Normal, with one valid offset. For the vast majority of the year, the normal601* case applies, where there is a single valid offset for the local date-time.</li>602* <li>Gap, with zero valid offsets. This is when clocks jump forward typically603* due to the spring daylight savings change from "winter" to "summer".604* In a gap there are local date-time values with no valid offset.</li>605* <li>Overlap, with two valid offsets. This is when clocks are set back typically606* due to the autumn daylight savings change from "summer" to "winter".607* In an overlap there are local date-time values with two valid offsets.</li>608* </ul>609* A transition is used to model the cases of a Gap or Overlap.610* The Normal case will return null.611* <p>612* There are various ways to handle the conversion from a {@code LocalDateTime}.613* One technique, using this method, would be:614* <pre>615* ZoneOffsetTransition trans = rules.getTransition(localDT);616* if (trans == null) {617* // Gap or Overlap: determine what to do from transition618* } else {619* // Normal case: only one valid offset620* zoneOffset = rule.getOffset(localDT);621* }622* </pre>623*624* @param localDateTime the local date-time to query for offset transition, not null, but null625* may be ignored if the rules have a single offset for all instants626* @return the offset transition, null if the local date-time is not in transition627*/628public ZoneOffsetTransition getTransition(LocalDateTime localDateTime) {629Object info = getOffsetInfo(localDateTime);630return (info instanceof ZoneOffsetTransition ? (ZoneOffsetTransition) info : null);631}632633private Object getOffsetInfo(LocalDateTime dt) {634if (savingsInstantTransitions.length == 0) {635return standardOffsets[0];636}637// check if using last rules638if (lastRules.length > 0 &&639dt.isAfter(savingsLocalTransitions[savingsLocalTransitions.length - 1])) {640ZoneOffsetTransition[] transArray = findTransitionArray(dt.getYear());641Object info = null;642for (ZoneOffsetTransition trans : transArray) {643info = findOffsetInfo(dt, trans);644if (info instanceof ZoneOffsetTransition || info.equals(trans.getOffsetBefore())) {645return info;646}647}648return info;649}650651// using historic rules652int index = Arrays.binarySearch(savingsLocalTransitions, dt);653if (index == -1) {654// before first transition655return wallOffsets[0];656}657if (index < 0) {658// switch negative insert position to start of matched range659index = -index - 2;660} else if (index < savingsLocalTransitions.length - 1 &&661savingsLocalTransitions[index].equals(savingsLocalTransitions[index + 1])) {662// handle overlap immediately following gap663index++;664}665if ((index & 1) == 0) {666// gap or overlap667LocalDateTime dtBefore = savingsLocalTransitions[index];668LocalDateTime dtAfter = savingsLocalTransitions[index + 1];669ZoneOffset offsetBefore = wallOffsets[index / 2];670ZoneOffset offsetAfter = wallOffsets[index / 2 + 1];671if (offsetAfter.getTotalSeconds() > offsetBefore.getTotalSeconds()) {672// gap673return new ZoneOffsetTransition(dtBefore, offsetBefore, offsetAfter);674} else {675// overlap676return new ZoneOffsetTransition(dtAfter, offsetBefore, offsetAfter);677}678} else {679// normal (neither gap or overlap)680return wallOffsets[index / 2 + 1];681}682}683684/**685* Finds the offset info for a local date-time and transition.686*687* @param dt the date-time, not null688* @param trans the transition, not null689* @return the offset info, not null690*/691private Object findOffsetInfo(LocalDateTime dt, ZoneOffsetTransition trans) {692LocalDateTime localTransition = trans.getDateTimeBefore();693if (trans.isGap()) {694if (dt.isBefore(localTransition)) {695return trans.getOffsetBefore();696}697if (dt.isBefore(trans.getDateTimeAfter())) {698return trans;699} else {700return trans.getOffsetAfter();701}702} else {703if (dt.isBefore(localTransition) == false) {704return trans.getOffsetAfter();705}706if (dt.isBefore(trans.getDateTimeAfter())) {707return trans.getOffsetBefore();708} else {709return trans;710}711}712}713714/**715* Finds the appropriate transition array for the given year.716*717* @param year the year, not null718* @return the transition array, not null719*/720private ZoneOffsetTransition[] findTransitionArray(int year) {721Integer yearObj = year; // should use Year class, but this saves a class load722ZoneOffsetTransition[] transArray = lastRulesCache.get(yearObj);723if (transArray != null) {724return transArray;725}726ZoneOffsetTransitionRule[] ruleArray = lastRules;727transArray = new ZoneOffsetTransition[ruleArray.length];728for (int i = 0; i < ruleArray.length; i++) {729transArray[i] = ruleArray[i].createTransition(year);730}731if (year < LAST_CACHED_YEAR) {732lastRulesCache.putIfAbsent(yearObj, transArray);733}734return transArray;735}736737/**738* Gets the standard offset for the specified instant in this zone.739* <p>740* This provides access to historic information on how the standard offset741* has changed over time.742* The standard offset is the offset before any daylight saving time is applied.743* This is typically the offset applicable during winter.744*745* @param instant the instant to find the offset information for, not null, but null746* may be ignored if the rules have a single offset for all instants747* @return the standard offset, not null748*/749public ZoneOffset getStandardOffset(Instant instant) {750if (savingsInstantTransitions.length == 0) {751return standardOffsets[0];752}753long epochSec = instant.getEpochSecond();754int index = Arrays.binarySearch(standardTransitions, epochSec);755if (index < 0) {756// switch negative insert position to start of matched range757index = -index - 2;758}759return standardOffsets[index + 1];760}761762/**763* Gets the amount of daylight savings in use for the specified instant in this zone.764* <p>765* This provides access to historic information on how the amount of daylight766* savings has changed over time.767* This is the difference between the standard offset and the actual offset.768* Typically the amount is zero during winter and one hour during summer.769* Time-zones are second-based, so the nanosecond part of the duration will be zero.770* <p>771* This default implementation calculates the duration from the772* {@link #getOffset(java.time.Instant) actual} and773* {@link #getStandardOffset(java.time.Instant) standard} offsets.774*775* @param instant the instant to find the daylight savings for, not null, but null776* may be ignored if the rules have a single offset for all instants777* @return the difference between the standard and actual offset, not null778*/779public Duration getDaylightSavings(Instant instant) {780if (savingsInstantTransitions.length == 0) {781return Duration.ZERO;782}783ZoneOffset standardOffset = getStandardOffset(instant);784ZoneOffset actualOffset = getOffset(instant);785return Duration.ofSeconds(actualOffset.getTotalSeconds() - standardOffset.getTotalSeconds());786}787788/**789* Checks if the specified instant is in daylight savings.790* <p>791* This checks if the standard offset and the actual offset are the same792* for the specified instant.793* If they are not, it is assumed that daylight savings is in operation.794* <p>795* This default implementation compares the {@link #getOffset(java.time.Instant) actual}796* and {@link #getStandardOffset(java.time.Instant) standard} offsets.797*798* @param instant the instant to find the offset information for, not null, but null799* may be ignored if the rules have a single offset for all instants800* @return the standard offset, not null801*/802public boolean isDaylightSavings(Instant instant) {803return (getStandardOffset(instant).equals(getOffset(instant)) == false);804}805806/**807* Checks if the offset date-time is valid for these rules.808* <p>809* To be valid, the local date-time must not be in a gap and the offset810* must match one of the valid offsets.811* <p>812* This default implementation checks if {@link #getValidOffsets(java.time.LocalDateTime)}813* contains the specified offset.814*815* @param localDateTime the date-time to check, not null, but null816* may be ignored if the rules have a single offset for all instants817* @param offset the offset to check, null returns false818* @return true if the offset date-time is valid for these rules819*/820public boolean isValidOffset(LocalDateTime localDateTime, ZoneOffset offset) {821return getValidOffsets(localDateTime).contains(offset);822}823824/**825* Gets the next transition after the specified instant.826* <p>827* This returns details of the next transition after the specified instant.828* For example, if the instant represents a point where "Summer" daylight savings time829* applies, then the method will return the transition to the next "Winter" time.830*831* @param instant the instant to get the next transition after, not null, but null832* may be ignored if the rules have a single offset for all instants833* @return the next transition after the specified instant, null if this is after the last transition834*/835public ZoneOffsetTransition nextTransition(Instant instant) {836if (savingsInstantTransitions.length == 0) {837return null;838}839long epochSec = instant.getEpochSecond();840// check if using last rules841if (epochSec >= savingsInstantTransitions[savingsInstantTransitions.length - 1]) {842if (lastRules.length == 0) {843return null;844}845// search year the instant is in846int year = findYear(epochSec, wallOffsets[wallOffsets.length - 1]);847ZoneOffsetTransition[] transArray = findTransitionArray(year);848for (ZoneOffsetTransition trans : transArray) {849if (epochSec < trans.toEpochSecond()) {850return trans;851}852}853// use first from following year854if (year < Year.MAX_VALUE) {855transArray = findTransitionArray(year + 1);856return transArray[0];857}858return null;859}860861// using historic rules862int index = Arrays.binarySearch(savingsInstantTransitions, epochSec);863if (index < 0) {864index = -index - 1; // switched value is the next transition865} else {866index += 1; // exact match, so need to add one to get the next867}868return new ZoneOffsetTransition(savingsInstantTransitions[index], wallOffsets[index], wallOffsets[index + 1]);869}870871/**872* Gets the previous transition before the specified instant.873* <p>874* This returns details of the previous transition after the specified instant.875* For example, if the instant represents a point where "summer" daylight saving time876* applies, then the method will return the transition from the previous "winter" time.877*878* @param instant the instant to get the previous transition after, not null, but null879* may be ignored if the rules have a single offset for all instants880* @return the previous transition after the specified instant, null if this is before the first transition881*/882public ZoneOffsetTransition previousTransition(Instant instant) {883if (savingsInstantTransitions.length == 0) {884return null;885}886long epochSec = instant.getEpochSecond();887if (instant.getNano() > 0 && epochSec < Long.MAX_VALUE) {888epochSec += 1; // allow rest of method to only use seconds889}890891// check if using last rules892long lastHistoric = savingsInstantTransitions[savingsInstantTransitions.length - 1];893if (lastRules.length > 0 && epochSec > lastHistoric) {894// search year the instant is in895ZoneOffset lastHistoricOffset = wallOffsets[wallOffsets.length - 1];896int year = findYear(epochSec, lastHistoricOffset);897ZoneOffsetTransition[] transArray = findTransitionArray(year);898for (int i = transArray.length - 1; i >= 0; i--) {899if (epochSec > transArray[i].toEpochSecond()) {900return transArray[i];901}902}903// use last from preceding year904int lastHistoricYear = findYear(lastHistoric, lastHistoricOffset);905if (--year > lastHistoricYear) {906transArray = findTransitionArray(year);907return transArray[transArray.length - 1];908}909// drop through910}911912// using historic rules913int index = Arrays.binarySearch(savingsInstantTransitions, epochSec);914if (index < 0) {915index = -index - 1;916}917if (index <= 0) {918return null;919}920return new ZoneOffsetTransition(savingsInstantTransitions[index - 1], wallOffsets[index - 1], wallOffsets[index]);921}922923private int findYear(long epochSecond, ZoneOffset offset) {924// inline for performance925long localSecond = epochSecond + offset.getTotalSeconds();926long localEpochDay = Math.floorDiv(localSecond, 86400);927return LocalDate.ofEpochDay(localEpochDay).getYear();928}929930/**931* Gets the complete list of fully defined transitions.932* <p>933* The complete set of transitions for this rules instance is defined by this method934* and {@link #getTransitionRules()}. This method returns those transitions that have935* been fully defined. These are typically historical, but may be in the future.936* <p>937* The list will be empty for fixed offset rules and for any time-zone where there has938* only ever been a single offset. The list will also be empty if the transition rules are unknown.939*940* @return an immutable list of fully defined transitions, not null941*/942public List<ZoneOffsetTransition> getTransitions() {943List<ZoneOffsetTransition> list = new ArrayList<>();944for (int i = 0; i < savingsInstantTransitions.length; i++) {945list.add(new ZoneOffsetTransition(savingsInstantTransitions[i], wallOffsets[i], wallOffsets[i + 1]));946}947return Collections.unmodifiableList(list);948}949950/**951* Gets the list of transition rules for years beyond those defined in the transition list.952* <p>953* The complete set of transitions for this rules instance is defined by this method954* and {@link #getTransitions()}. This method returns instances of {@link ZoneOffsetTransitionRule}955* that define an algorithm for when transitions will occur.956* <p>957* For any given {@code ZoneRules}, this list contains the transition rules for years958* beyond those years that have been fully defined. These rules typically refer to future959* daylight saving time rule changes.960* <p>961* If the zone defines daylight savings into the future, then the list will normally962* be of size two and hold information about entering and exiting daylight savings.963* If the zone does not have daylight savings, or information about future changes964* is uncertain, then the list will be empty.965* <p>966* The list will be empty for fixed offset rules and for any time-zone where there is no967* daylight saving time. The list will also be empty if the transition rules are unknown.968*969* @return an immutable list of transition rules, not null970*/971public List<ZoneOffsetTransitionRule> getTransitionRules() {972return Collections.unmodifiableList(Arrays.asList(lastRules));973}974975/**976* Checks if this set of rules equals another.977* <p>978* Two rule sets are equal if they will always result in the same output979* for any given input instant or local date-time.980* Rules from two different groups may return false even if they are in fact the same.981* <p>982* This definition should result in implementations comparing their entire state.983*984* @param otherRules the other rules, null returns false985* @return true if this rules is the same as that specified986*/987@Override988public boolean equals(Object otherRules) {989if (this == otherRules) {990return true;991}992if (otherRules instanceof ZoneRules) {993ZoneRules other = (ZoneRules) otherRules;994return Arrays.equals(standardTransitions, other.standardTransitions) &&995Arrays.equals(standardOffsets, other.standardOffsets) &&996Arrays.equals(savingsInstantTransitions, other.savingsInstantTransitions) &&997Arrays.equals(wallOffsets, other.wallOffsets) &&998Arrays.equals(lastRules, other.lastRules);999}1000return false;1001}10021003/**1004* Returns a suitable hash code given the definition of {@code #equals}.1005*1006* @return the hash code1007*/1008@Override1009public int hashCode() {1010return Arrays.hashCode(standardTransitions) ^1011Arrays.hashCode(standardOffsets) ^1012Arrays.hashCode(savingsInstantTransitions) ^1013Arrays.hashCode(wallOffsets) ^1014Arrays.hashCode(lastRules);1015}10161017/**1018* Returns a string describing this object.1019*1020* @return a string for debugging, not null1021*/1022@Override1023public String toString() {1024return "ZoneRules[currentStandardOffset=" + standardOffsets[standardOffsets.length - 1] + "]";1025}10261027}102810291030