Home | History | Annotate | Download | only in zone
      1 /*
      2  * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
      3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      4  *
      5  * This code is free software; you can redistribute it and/or modify it
      6  * under the terms of the GNU General Public License version 2 only, as
      7  * published by the Free Software Foundation.  Oracle designates this
      8  * particular file as subject to the "Classpath" exception as provided
      9  * by Oracle in the LICENSE file that accompanied this code.
     10  *
     11  * This code is distributed in the hope that it will be useful, but WITHOUT
     12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     14  * version 2 for more details (a copy is included in the LICENSE file that
     15  * accompanied this code).
     16  *
     17  * You should have received a copy of the GNU General Public License version
     18  * 2 along with this work; if not, write to the Free Software Foundation,
     19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     20  *
     21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     22  * or visit www.oracle.com if you need additional information or have any
     23  * questions.
     24  */
     25 
     26 /*
     27  * This file is available under and governed by the GNU General Public
     28  * License version 2 only, as published by the Free Software Foundation.
     29  * However, the following notice accompanied the original version of this
     30  * file:
     31  *
     32  * Copyright (c) 2009-2012, Stephen Colebourne & Michael Nascimento Santos
     33  *
     34  * All rights reserved.
     35  *
     36  * Redistribution and use in source and binary forms, with or without
     37  * modification, are permitted provided that the following conditions are met:
     38  *
     39  *  * Redistributions of source code must retain the above copyright notice,
     40  *    this list of conditions and the following disclaimer.
     41  *
     42  *  * Redistributions in binary form must reproduce the above copyright notice,
     43  *    this list of conditions and the following disclaimer in the documentation
     44  *    and/or other materials provided with the distribution.
     45  *
     46  *  * Neither the name of JSR-310 nor the names of its contributors
     47  *    may be used to endorse or promote products derived from this software
     48  *    without specific prior written permission.
     49  *
     50  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     51  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     52  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     53  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     54  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     55  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     56  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     57  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     58  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     59  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     60  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     61  */
     62 package java.time.zone;
     63 
     64 import java.io.DataInput;
     65 import java.io.DataOutput;
     66 import java.io.IOException;
     67 import java.io.InvalidObjectException;
     68 import java.io.ObjectInputStream;
     69 import java.io.Serializable;
     70 import java.time.Duration;
     71 import java.time.Instant;
     72 import java.time.LocalDate;
     73 import java.time.LocalDateTime;
     74 import java.time.ZoneId;
     75 import java.time.ZoneOffset;
     76 import java.time.Year;
     77 import java.util.ArrayList;
     78 import java.util.Arrays;
     79 import java.util.Collections;
     80 import java.util.List;
     81 import java.util.Objects;
     82 import java.util.concurrent.ConcurrentHashMap;
     83 import java.util.concurrent.ConcurrentMap;
     84 
     85 // Android-changed: remove mention of ZoneRulesProvider.
     86 /**
     87  * The rules defining how the zone offset varies for a single time-zone.
     88  * <p>
     89  * The rules model all the historic and future transitions for a time-zone.
     90  * {@link ZoneOffsetTransition} is used for known transitions, typically historic.
     91  * {@link ZoneOffsetTransitionRule} is used for future transitions that are based
     92  * on the result of an algorithm.
     93  * <p>
     94  * The same rules may be shared internally between multiple zone IDs.
     95  * <p>
     96  * Serializing an instance of {@code ZoneRules} will store the entire set of rules.
     97  * It does not store the zone ID as it is not part of the state of this object.
     98  * <p>
     99  * A rule implementation may or may not store full information about historic
    100  * and future transitions, and the information stored is only as accurate as
    101  * that supplied to the implementation by the rules provider.
    102  * Applications should treat the data provided as representing the best information
    103  * available to the implementation of this rule.
    104  *
    105  * @implSpec
    106  * This class is immutable and thread-safe.
    107  *
    108  * @since 1.8
    109  */
    110 public final class ZoneRules implements Serializable {
    111 
    112     /**
    113      * Serialization version.
    114      */
    115     private static final long serialVersionUID = 3044319355680032515L;
    116     /**
    117      * The last year to have its transitions cached.
    118      */
    119     private static final int LAST_CACHED_YEAR = 2100;
    120 
    121     /**
    122      * The transitions between standard offsets (epoch seconds), sorted.
    123      */
    124     private final long[] standardTransitions;
    125     /**
    126      * The standard offsets.
    127      */
    128     private final ZoneOffset[] standardOffsets;
    129     /**
    130      * The transitions between instants (epoch seconds), sorted.
    131      */
    132     private final long[] savingsInstantTransitions;
    133     /**
    134      * The transitions between local date-times, sorted.
    135      * This is a paired array, where the first entry is the start of the transition
    136      * and the second entry is the end of the transition.
    137      */
    138     private final LocalDateTime[] savingsLocalTransitions;
    139     /**
    140      * The wall offsets.
    141      */
    142     private final ZoneOffset[] wallOffsets;
    143     /**
    144      * The last rule.
    145      */
    146     private final ZoneOffsetTransitionRule[] lastRules;
    147     /**
    148      * The map of recent transitions.
    149      */
    150     private final transient ConcurrentMap<Integer, ZoneOffsetTransition[]> lastRulesCache =
    151                 new ConcurrentHashMap<Integer, ZoneOffsetTransition[]>();
    152     /**
    153      * The zero-length long array.
    154      */
    155     private static final long[] EMPTY_LONG_ARRAY = new long[0];
    156     /**
    157      * The zero-length lastrules array.
    158      */
    159     private static final ZoneOffsetTransitionRule[] EMPTY_LASTRULES =
    160         new ZoneOffsetTransitionRule[0];
    161     /**
    162      * The zero-length ldt array.
    163      */
    164     private static final LocalDateTime[] EMPTY_LDT_ARRAY = new LocalDateTime[0];
    165 
    166     /**
    167      * Obtains an instance of a ZoneRules.
    168      *
    169      * @param baseStandardOffset  the standard offset to use before legal rules were set, not null
    170      * @param baseWallOffset  the wall offset to use before legal rules were set, not null
    171      * @param standardOffsetTransitionList  the list of changes to the standard offset, not null
    172      * @param transitionList  the list of transitions, not null
    173      * @param lastRules  the recurring last rules, size 16 or less, not null
    174      * @return the zone rules, not null
    175      */
    176     public static ZoneRules of(ZoneOffset baseStandardOffset,
    177                                ZoneOffset baseWallOffset,
    178                                List<ZoneOffsetTransition> standardOffsetTransitionList,
    179                                List<ZoneOffsetTransition> transitionList,
    180                                List<ZoneOffsetTransitionRule> lastRules) {
    181         Objects.requireNonNull(baseStandardOffset, "baseStandardOffset");
    182         Objects.requireNonNull(baseWallOffset, "baseWallOffset");
    183         Objects.requireNonNull(standardOffsetTransitionList, "standardOffsetTransitionList");
    184         Objects.requireNonNull(transitionList, "transitionList");
    185         Objects.requireNonNull(lastRules, "lastRules");
    186         return new ZoneRules(baseStandardOffset, baseWallOffset,
    187                              standardOffsetTransitionList, transitionList, lastRules);
    188     }
    189 
    190     /**
    191      * Obtains an instance of ZoneRules that has fixed zone rules.
    192      *
    193      * @param offset  the offset this fixed zone rules is based on, not null
    194      * @return the zone rules, not null
    195      * @see #isFixedOffset()
    196      */
    197     public static ZoneRules of(ZoneOffset offset) {
    198         Objects.requireNonNull(offset, "offset");
    199         return new ZoneRules(offset);
    200     }
    201 
    202     /**
    203      * Creates an instance.
    204      *
    205      * @param baseStandardOffset  the standard offset to use before legal rules were set, not null
    206      * @param baseWallOffset  the wall offset to use before legal rules were set, not null
    207      * @param standardOffsetTransitionList  the list of changes to the standard offset, not null
    208      * @param transitionList  the list of transitions, not null
    209      * @param lastRules  the recurring last rules, size 16 or less, not null
    210      */
    211     ZoneRules(ZoneOffset baseStandardOffset,
    212               ZoneOffset baseWallOffset,
    213               List<ZoneOffsetTransition> standardOffsetTransitionList,
    214               List<ZoneOffsetTransition> transitionList,
    215               List<ZoneOffsetTransitionRule> lastRules) {
    216         super();
    217 
    218         // convert standard transitions
    219 
    220         this.standardTransitions = new long[standardOffsetTransitionList.size()];
    221 
    222         this.standardOffsets = new ZoneOffset[standardOffsetTransitionList.size() + 1];
    223         this.standardOffsets[0] = baseStandardOffset;
    224         for (int i = 0; i < standardOffsetTransitionList.size(); i++) {
    225             this.standardTransitions[i] = standardOffsetTransitionList.get(i).toEpochSecond();
    226             this.standardOffsets[i + 1] = standardOffsetTransitionList.get(i).getOffsetAfter();
    227         }
    228 
    229         // convert savings transitions to locals
    230         List<LocalDateTime> localTransitionList = new ArrayList<>();
    231         List<ZoneOffset> localTransitionOffsetList = new ArrayList<>();
    232         localTransitionOffsetList.add(baseWallOffset);
    233         for (ZoneOffsetTransition trans : transitionList) {
    234             if (trans.isGap()) {
    235                 localTransitionList.add(trans.getDateTimeBefore());
    236                 localTransitionList.add(trans.getDateTimeAfter());
    237             } else {
    238                 localTransitionList.add(trans.getDateTimeAfter());
    239                 localTransitionList.add(trans.getDateTimeBefore());
    240             }
    241             localTransitionOffsetList.add(trans.getOffsetAfter());
    242         }
    243         this.savingsLocalTransitions = localTransitionList.toArray(new LocalDateTime[localTransitionList.size()]);
    244         this.wallOffsets = localTransitionOffsetList.toArray(new ZoneOffset[localTransitionOffsetList.size()]);
    245 
    246         // convert savings transitions to instants
    247         this.savingsInstantTransitions = new long[transitionList.size()];
    248         for (int i = 0; i < transitionList.size(); i++) {
    249             this.savingsInstantTransitions[i] = transitionList.get(i).toEpochSecond();
    250         }
    251 
    252         // last rules
    253         if (lastRules.size() > 16) {
    254             throw new IllegalArgumentException("Too many transition rules");
    255         }
    256         this.lastRules = lastRules.toArray(new ZoneOffsetTransitionRule[lastRules.size()]);
    257     }
    258 
    259     /**
    260      * Constructor.
    261      *
    262      * @param standardTransitions  the standard transitions, not null
    263      * @param standardOffsets  the standard offsets, not null
    264      * @param savingsInstantTransitions  the standard transitions, not null
    265      * @param wallOffsets  the wall offsets, not null
    266      * @param lastRules  the recurring last rules, size 15 or less, not null
    267      */
    268     private ZoneRules(long[] standardTransitions,
    269                       ZoneOffset[] standardOffsets,
    270                       long[] savingsInstantTransitions,
    271                       ZoneOffset[] wallOffsets,
    272                       ZoneOffsetTransitionRule[] lastRules) {
    273         super();
    274 
    275         this.standardTransitions = standardTransitions;
    276         this.standardOffsets = standardOffsets;
    277         this.savingsInstantTransitions = savingsInstantTransitions;
    278         this.wallOffsets = wallOffsets;
    279         this.lastRules = lastRules;
    280 
    281         if (savingsInstantTransitions.length == 0) {
    282             this.savingsLocalTransitions = EMPTY_LDT_ARRAY;
    283         } else {
    284             // convert savings transitions to locals
    285             List<LocalDateTime> localTransitionList = new ArrayList<>();
    286             for (int i = 0; i < savingsInstantTransitions.length; i++) {
    287                 ZoneOffset before = wallOffsets[i];
    288                 ZoneOffset after = wallOffsets[i + 1];
    289                 ZoneOffsetTransition trans = new ZoneOffsetTransition(savingsInstantTransitions[i], before, after);
    290                 if (trans.isGap()) {
    291                     localTransitionList.add(trans.getDateTimeBefore());
    292                     localTransitionList.add(trans.getDateTimeAfter());
    293                 } else {
    294                     localTransitionList.add(trans.getDateTimeAfter());
    295                     localTransitionList.add(trans.getDateTimeBefore());
    296                }
    297             }
    298             this.savingsLocalTransitions = localTransitionList.toArray(new LocalDateTime[localTransitionList.size()]);
    299         }
    300     }
    301 
    302     /**
    303      * Creates an instance of ZoneRules that has fixed zone rules.
    304      *
    305      * @param offset  the offset this fixed zone rules is based on, not null
    306      * @return the zone rules, not null
    307      * @see #isFixedOffset()
    308      */
    309     private ZoneRules(ZoneOffset offset) {
    310         this.standardOffsets = new ZoneOffset[1];
    311         this.standardOffsets[0] = offset;
    312         this.standardTransitions = EMPTY_LONG_ARRAY;
    313         this.savingsInstantTransitions = EMPTY_LONG_ARRAY;
    314         this.savingsLocalTransitions = EMPTY_LDT_ARRAY;
    315         this.wallOffsets = standardOffsets;
    316         this.lastRules = EMPTY_LASTRULES;
    317     }
    318 
    319     /**
    320      * Defend against malicious streams.
    321      *
    322      * @param s the stream to read
    323      * @throws InvalidObjectException always
    324      */
    325     private void readObject(ObjectInputStream s) throws InvalidObjectException {
    326         throw new InvalidObjectException("Deserialization via serialization delegate");
    327     }
    328 
    329     /**
    330      * Writes the object using a
    331      * <a href="../../../serialized-form.html#java.time.zone.Ser">dedicated serialized form</a>.
    332      * @serialData
    333      * <pre style="font-size:1.0em">{@code
    334      *
    335      *   out.writeByte(1);  // identifies a ZoneRules
    336      *   out.writeInt(standardTransitions.length);
    337      *   for (long trans : standardTransitions) {
    338      *       Ser.writeEpochSec(trans, out);
    339      *   }
    340      *   for (ZoneOffset offset : standardOffsets) {
    341      *       Ser.writeOffset(offset, out);
    342      *   }
    343      *   out.writeInt(savingsInstantTransitions.length);
    344      *   for (long trans : savingsInstantTransitions) {
    345      *       Ser.writeEpochSec(trans, out);
    346      *   }
    347      *   for (ZoneOffset offset : wallOffsets) {
    348      *       Ser.writeOffset(offset, out);
    349      *   }
    350      *   out.writeByte(lastRules.length);
    351      *   for (ZoneOffsetTransitionRule rule : lastRules) {
    352      *       rule.writeExternal(out);
    353      *   }
    354      * }
    355      * </pre>
    356      * <p>
    357      * Epoch second values used for offsets are encoded in a variable
    358      * length form to make the common cases put fewer bytes in the stream.
    359      * <pre style="font-size:1.0em">{@code
    360      *
    361      *  static void writeEpochSec(long epochSec, DataOutput out) throws IOException {
    362      *     if (epochSec >= -4575744000L && epochSec < 10413792000L && epochSec % 900 == 0) {  // quarter hours between 1825 and 2300
    363      *         int store = (int) ((epochSec + 4575744000L) / 900);
    364      *         out.writeByte((store >>> 16) & 255);
    365      *         out.writeByte((store >>> 8) & 255);
    366      *         out.writeByte(store & 255);
    367      *      } else {
    368      *          out.writeByte(255);
    369      *          out.writeLong(epochSec);
    370      *      }
    371      *  }
    372      * }
    373      * </pre>
    374      * <p>
    375      * ZoneOffset values are encoded in a variable length form so the
    376      * common cases put fewer bytes in the stream.
    377      * <pre style="font-size:1.0em">{@code
    378      *
    379      *  static void writeOffset(ZoneOffset offset, DataOutput out) throws IOException {
    380      *     final int offsetSecs = offset.getTotalSeconds();
    381      *     int offsetByte = offsetSecs % 900 == 0 ? offsetSecs / 900 : 127;  // compress to -72 to +72
    382      *     out.writeByte(offsetByte);
    383      *     if (offsetByte == 127) {
    384      *         out.writeInt(offsetSecs);
    385      *     }
    386      * }
    387      *}
    388      * </pre>
    389      * @return the replacing object, not null
    390      */
    391     private Object writeReplace() {
    392         return new Ser(Ser.ZRULES, this);
    393     }
    394 
    395     /**
    396      * Writes the state to the stream.
    397      *
    398      * @param out  the output stream, not null
    399      * @throws IOException if an error occurs
    400      */
    401     void writeExternal(DataOutput out) throws IOException {
    402         out.writeInt(standardTransitions.length);
    403         for (long trans : standardTransitions) {
    404             Ser.writeEpochSec(trans, out);
    405         }
    406         for (ZoneOffset offset : standardOffsets) {
    407             Ser.writeOffset(offset, out);
    408         }
    409         out.writeInt(savingsInstantTransitions.length);
    410         for (long trans : savingsInstantTransitions) {
    411             Ser.writeEpochSec(trans, out);
    412         }
    413         for (ZoneOffset offset : wallOffsets) {
    414             Ser.writeOffset(offset, out);
    415         }
    416         out.writeByte(lastRules.length);
    417         for (ZoneOffsetTransitionRule rule : lastRules) {
    418             rule.writeExternal(out);
    419         }
    420     }
    421 
    422     /**
    423      * Reads the state from the stream.
    424      *
    425      * @param in  the input stream, not null
    426      * @return the created object, not null
    427      * @throws IOException if an error occurs
    428      */
    429     static ZoneRules readExternal(DataInput in) throws IOException, ClassNotFoundException {
    430         int stdSize = in.readInt();
    431         long[] stdTrans = (stdSize == 0) ? EMPTY_LONG_ARRAY
    432                                          : new long[stdSize];
    433         for (int i = 0; i < stdSize; i++) {
    434             stdTrans[i] = Ser.readEpochSec(in);
    435         }
    436         ZoneOffset[] stdOffsets = new ZoneOffset[stdSize + 1];
    437         for (int i = 0; i < stdOffsets.length; i++) {
    438             stdOffsets[i] = Ser.readOffset(in);
    439         }
    440         int savSize = in.readInt();
    441         long[] savTrans = (savSize == 0) ? EMPTY_LONG_ARRAY
    442                                          : new long[savSize];
    443         for (int i = 0; i < savSize; i++) {
    444             savTrans[i] = Ser.readEpochSec(in);
    445         }
    446         ZoneOffset[] savOffsets = new ZoneOffset[savSize + 1];
    447         for (int i = 0; i < savOffsets.length; i++) {
    448             savOffsets[i] = Ser.readOffset(in);
    449         }
    450         int ruleSize = in.readByte();
    451         ZoneOffsetTransitionRule[] rules = (ruleSize == 0) ?
    452             EMPTY_LASTRULES : new ZoneOffsetTransitionRule[ruleSize];
    453         for (int i = 0; i < ruleSize; i++) {
    454             rules[i] = ZoneOffsetTransitionRule.readExternal(in);
    455         }
    456         return new ZoneRules(stdTrans, stdOffsets, savTrans, savOffsets, rules);
    457     }
    458 
    459     /**
    460      * Checks of the zone rules are fixed, such that the offset never varies.
    461      *
    462      * @return true if the time-zone is fixed and the offset never changes
    463      */
    464     public boolean isFixedOffset() {
    465         return savingsInstantTransitions.length == 0;
    466     }
    467 
    468     /**
    469      * Gets the offset applicable at the specified instant in these rules.
    470      * <p>
    471      * The mapping from an instant to an offset is simple, there is only
    472      * one valid offset for each instant.
    473      * This method returns that offset.
    474      *
    475      * @param instant  the instant to find the offset for, not null, but null
    476      *  may be ignored if the rules have a single offset for all instants
    477      * @return the offset, not null
    478      */
    479     public ZoneOffset getOffset(Instant instant) {
    480         if (savingsInstantTransitions.length == 0) {
    481             return standardOffsets[0];
    482         }
    483         long epochSec = instant.getEpochSecond();
    484         // check if using last rules
    485         if (lastRules.length > 0 &&
    486                 epochSec > savingsInstantTransitions[savingsInstantTransitions.length - 1]) {
    487             int year = findYear(epochSec, wallOffsets[wallOffsets.length - 1]);
    488             ZoneOffsetTransition[] transArray = findTransitionArray(year);
    489             ZoneOffsetTransition trans = null;
    490             for (int i = 0; i < transArray.length; i++) {
    491                 trans = transArray[i];
    492                 if (epochSec < trans.toEpochSecond()) {
    493                     return trans.getOffsetBefore();
    494                 }
    495             }
    496             return trans.getOffsetAfter();
    497         }
    498 
    499         // using historic rules
    500         int index  = Arrays.binarySearch(savingsInstantTransitions, epochSec);
    501         if (index < 0) {
    502             // switch negative insert position to start of matched range
    503             index = -index - 2;
    504         }
    505         return wallOffsets[index + 1];
    506     }
    507 
    508     /**
    509      * Gets a suitable offset for the specified local date-time in these rules.
    510      * <p>
    511      * The mapping from a local date-time to an offset is not straightforward.
    512      * There are three cases:
    513      * <ul>
    514      * <li>Normal, with one valid offset. For the vast majority of the year, the normal
    515      *  case applies, where there is a single valid offset for the local date-time.</li>
    516      * <li>Gap, with zero valid offsets. This is when clocks jump forward typically
    517      *  due to the spring daylight savings change from "winter" to "summer".
    518      *  In a gap there are local date-time values with no valid offset.</li>
    519      * <li>Overlap, with two valid offsets. This is when clocks are set back typically
    520      *  due to the autumn daylight savings change from "summer" to "winter".
    521      *  In an overlap there are local date-time values with two valid offsets.</li>
    522      * </ul>
    523      * Thus, for any given local date-time there can be zero, one or two valid offsets.
    524      * This method returns the single offset in the Normal case, and in the Gap or Overlap
    525      * case it returns the offset before the transition.
    526      * <p>
    527      * Since, in the case of Gap and Overlap, the offset returned is a "best" value, rather
    528      * than the "correct" value, it should be treated with care. Applications that care
    529      * about the correct offset should use a combination of this method,
    530      * {@link #getValidOffsets(LocalDateTime)} and {@link #getTransition(LocalDateTime)}.
    531      *
    532      * @param localDateTime  the local date-time to query, not null, but null
    533      *  may be ignored if the rules have a single offset for all instants
    534      * @return the best available offset for the local date-time, not null
    535      */
    536     public ZoneOffset getOffset(LocalDateTime localDateTime) {
    537         Object info = getOffsetInfo(localDateTime);
    538         if (info instanceof ZoneOffsetTransition) {
    539             return ((ZoneOffsetTransition) info).getOffsetBefore();
    540         }
    541         return (ZoneOffset) info;
    542     }
    543 
    544     /**
    545      * Gets the offset applicable at the specified local date-time in these rules.
    546      * <p>
    547      * The mapping from a local date-time to an offset is not straightforward.
    548      * There are three cases:
    549      * <ul>
    550      * <li>Normal, with one valid offset. For the vast majority of the year, the normal
    551      *  case applies, where there is a single valid offset for the local date-time.</li>
    552      * <li>Gap, with zero valid offsets. This is when clocks jump forward typically
    553      *  due to the spring daylight savings change from "winter" to "summer".
    554      *  In a gap there are local date-time values with no valid offset.</li>
    555      * <li>Overlap, with two valid offsets. This is when clocks are set back typically
    556      *  due to the autumn daylight savings change from "summer" to "winter".
    557      *  In an overlap there are local date-time values with two valid offsets.</li>
    558      * </ul>
    559      * Thus, for any given local date-time there can be zero, one or two valid offsets.
    560      * This method returns that list of valid offsets, which is a list of size 0, 1 or 2.
    561      * In the case where there are two offsets, the earlier offset is returned at index 0
    562      * and the later offset at index 1.
    563      * <p>
    564      * There are various ways to handle the conversion from a {@code LocalDateTime}.
    565      * One technique, using this method, would be:
    566      * <pre>
    567      *  List&lt;ZoneOffset&gt; validOffsets = rules.getOffset(localDT);
    568      *  if (validOffsets.size() == 1) {
    569      *    // Normal case: only one valid offset
    570      *    zoneOffset = validOffsets.get(0);
    571      *  } else {
    572      *    // Gap or Overlap: determine what to do from transition (which will be non-null)
    573      *    ZoneOffsetTransition trans = rules.getTransition(localDT);
    574      *  }
    575      * </pre>
    576      * <p>
    577      * In theory, it is possible for there to be more than two valid offsets.
    578      * This would happen if clocks to be put back more than once in quick succession.
    579      * This has never happened in the history of time-zones and thus has no special handling.
    580      * However, if it were to happen, then the list would return more than 2 entries.
    581      *
    582      * @param localDateTime  the local date-time to query for valid offsets, not null, but null
    583      *  may be ignored if the rules have a single offset for all instants
    584      * @return the list of valid offsets, may be immutable, not null
    585      */
    586     public List<ZoneOffset> getValidOffsets(LocalDateTime localDateTime) {
    587         // should probably be optimized
    588         Object info = getOffsetInfo(localDateTime);
    589         if (info instanceof ZoneOffsetTransition) {
    590             return ((ZoneOffsetTransition) info).getValidOffsets();
    591         }
    592         return Collections.singletonList((ZoneOffset) info);
    593     }
    594 
    595     /**
    596      * Gets the offset transition applicable at the specified local date-time in these rules.
    597      * <p>
    598      * The mapping from a local date-time to an offset is not straightforward.
    599      * There are three cases:
    600      * <ul>
    601      * <li>Normal, with one valid offset. For the vast majority of the year, the normal
    602      *  case applies, where there is a single valid offset for the local date-time.</li>
    603      * <li>Gap, with zero valid offsets. This is when clocks jump forward typically
    604      *  due to the spring daylight savings change from "winter" to "summer".
    605      *  In a gap there are local date-time values with no valid offset.</li>
    606      * <li>Overlap, with two valid offsets. This is when clocks are set back typically
    607      *  due to the autumn daylight savings change from "summer" to "winter".
    608      *  In an overlap there are local date-time values with two valid offsets.</li>
    609      * </ul>
    610      * A transition is used to model the cases of a Gap or Overlap.
    611      * The Normal case will return null.
    612      * <p>
    613      * There are various ways to handle the conversion from a {@code LocalDateTime}.
    614      * One technique, using this method, would be:
    615      * <pre>
    616      *  ZoneOffsetTransition trans = rules.getTransition(localDT);
    617      *  if (trans == null) {
    618      *    // Gap or Overlap: determine what to do from transition
    619      *  } else {
    620      *    // Normal case: only one valid offset
    621      *    zoneOffset = rule.getOffset(localDT);
    622      *  }
    623      * </pre>
    624      *
    625      * @param localDateTime  the local date-time to query for offset transition, not null, but null
    626      *  may be ignored if the rules have a single offset for all instants
    627      * @return the offset transition, null if the local date-time is not in transition
    628      */
    629     public ZoneOffsetTransition getTransition(LocalDateTime localDateTime) {
    630         Object info = getOffsetInfo(localDateTime);
    631         return (info instanceof ZoneOffsetTransition ? (ZoneOffsetTransition) info : null);
    632     }
    633 
    634     private Object getOffsetInfo(LocalDateTime dt) {
    635         if (savingsInstantTransitions.length == 0) {
    636             return standardOffsets[0];
    637         }
    638         // check if using last rules
    639         if (lastRules.length > 0 &&
    640                 dt.isAfter(savingsLocalTransitions[savingsLocalTransitions.length - 1])) {
    641             ZoneOffsetTransition[] transArray = findTransitionArray(dt.getYear());
    642             Object info = null;
    643             for (ZoneOffsetTransition trans : transArray) {
    644                 info = findOffsetInfo(dt, trans);
    645                 if (info instanceof ZoneOffsetTransition || info.equals(trans.getOffsetBefore())) {
    646                     return info;
    647                 }
    648             }
    649             return info;
    650         }
    651 
    652         // using historic rules
    653         int index  = Arrays.binarySearch(savingsLocalTransitions, dt);
    654         if (index == -1) {
    655             // before first transition
    656             return wallOffsets[0];
    657         }
    658         if (index < 0) {
    659             // switch negative insert position to start of matched range
    660             index = -index - 2;
    661         } else if (index < savingsLocalTransitions.length - 1 &&
    662                 savingsLocalTransitions[index].equals(savingsLocalTransitions[index + 1])) {
    663             // handle overlap immediately following gap
    664             index++;
    665         }
    666         if ((index & 1) == 0) {
    667             // gap or overlap
    668             LocalDateTime dtBefore = savingsLocalTransitions[index];
    669             LocalDateTime dtAfter = savingsLocalTransitions[index + 1];
    670             ZoneOffset offsetBefore = wallOffsets[index / 2];
    671             ZoneOffset offsetAfter = wallOffsets[index / 2 + 1];
    672             if (offsetAfter.getTotalSeconds() > offsetBefore.getTotalSeconds()) {
    673                 // gap
    674                 return new ZoneOffsetTransition(dtBefore, offsetBefore, offsetAfter);
    675             } else {
    676                 // overlap
    677                 return new ZoneOffsetTransition(dtAfter, offsetBefore, offsetAfter);
    678             }
    679         } else {
    680             // normal (neither gap or overlap)
    681             return wallOffsets[index / 2 + 1];
    682         }
    683     }
    684 
    685     /**
    686      * Finds the offset info for a local date-time and transition.
    687      *
    688      * @param dt  the date-time, not null
    689      * @param trans  the transition, not null
    690      * @return the offset info, not null
    691      */
    692     private Object findOffsetInfo(LocalDateTime dt, ZoneOffsetTransition trans) {
    693         LocalDateTime localTransition = trans.getDateTimeBefore();
    694         if (trans.isGap()) {
    695             if (dt.isBefore(localTransition)) {
    696                 return trans.getOffsetBefore();
    697             }
    698             if (dt.isBefore(trans.getDateTimeAfter())) {
    699                 return trans;
    700             } else {
    701                 return trans.getOffsetAfter();
    702             }
    703         } else {
    704             if (dt.isBefore(localTransition) == false) {
    705                 return trans.getOffsetAfter();
    706             }
    707             if (dt.isBefore(trans.getDateTimeAfter())) {
    708                 return trans.getOffsetBefore();
    709             } else {
    710                 return trans;
    711             }
    712         }
    713     }
    714 
    715     /**
    716      * Finds the appropriate transition array for the given year.
    717      *
    718      * @param year  the year, not null
    719      * @return the transition array, not null
    720      */
    721     private ZoneOffsetTransition[] findTransitionArray(int year) {
    722         Integer yearObj = year;  // should use Year class, but this saves a class load
    723         ZoneOffsetTransition[] transArray = lastRulesCache.get(yearObj);
    724         if (transArray != null) {
    725             return transArray;
    726         }
    727         ZoneOffsetTransitionRule[] ruleArray = lastRules;
    728         transArray  = new ZoneOffsetTransition[ruleArray.length];
    729         for (int i = 0; i < ruleArray.length; i++) {
    730             transArray[i] = ruleArray[i].createTransition(year);
    731         }
    732         if (year < LAST_CACHED_YEAR) {
    733             lastRulesCache.putIfAbsent(yearObj, transArray);
    734         }
    735         return transArray;
    736     }
    737 
    738     /**
    739      * Gets the standard offset for the specified instant in this zone.
    740      * <p>
    741      * This provides access to historic information on how the standard offset
    742      * has changed over time.
    743      * The standard offset is the offset before any daylight saving time is applied.
    744      * This is typically the offset applicable during winter.
    745      *
    746      * @param instant  the instant to find the offset information for, not null, but null
    747      *  may be ignored if the rules have a single offset for all instants
    748      * @return the standard offset, not null
    749      */
    750     public ZoneOffset getStandardOffset(Instant instant) {
    751         if (savingsInstantTransitions.length == 0) {
    752             return standardOffsets[0];
    753         }
    754         long epochSec = instant.getEpochSecond();
    755         int index  = Arrays.binarySearch(standardTransitions, epochSec);
    756         if (index < 0) {
    757             // switch negative insert position to start of matched range
    758             index = -index - 2;
    759         }
    760         return standardOffsets[index + 1];
    761     }
    762 
    763     /**
    764      * Gets the amount of daylight savings in use for the specified instant in this zone.
    765      * <p>
    766      * This provides access to historic information on how the amount of daylight
    767      * savings has changed over time.
    768      * This is the difference between the standard offset and the actual offset.
    769      * Typically the amount is zero during winter and one hour during summer.
    770      * Time-zones are second-based, so the nanosecond part of the duration will be zero.
    771      * <p>
    772      * This default implementation calculates the duration from the
    773      * {@link #getOffset(java.time.Instant) actual} and
    774      * {@link #getStandardOffset(java.time.Instant) standard} offsets.
    775      *
    776      * @param instant  the instant to find the daylight savings for, not null, but null
    777      *  may be ignored if the rules have a single offset for all instants
    778      * @return the difference between the standard and actual offset, not null
    779      */
    780     public Duration getDaylightSavings(Instant instant) {
    781         if (savingsInstantTransitions.length == 0) {
    782             return Duration.ZERO;
    783         }
    784         ZoneOffset standardOffset = getStandardOffset(instant);
    785         ZoneOffset actualOffset = getOffset(instant);
    786         return Duration.ofSeconds(actualOffset.getTotalSeconds() - standardOffset.getTotalSeconds());
    787     }
    788 
    789     /**
    790      * Checks if the specified instant is in daylight savings.
    791      * <p>
    792      * This checks if the standard offset and the actual offset are the same
    793      * for the specified instant.
    794      * If they are not, it is assumed that daylight savings is in operation.
    795      * <p>
    796      * This default implementation compares the {@link #getOffset(java.time.Instant) actual}
    797      * and {@link #getStandardOffset(java.time.Instant) standard} offsets.
    798      *
    799      * @param instant  the instant to find the offset information for, not null, but null
    800      *  may be ignored if the rules have a single offset for all instants
    801      * @return the standard offset, not null
    802      */
    803     public boolean isDaylightSavings(Instant instant) {
    804         return (getStandardOffset(instant).equals(getOffset(instant)) == false);
    805     }
    806 
    807     /**
    808      * Checks if the offset date-time is valid for these rules.
    809      * <p>
    810      * To be valid, the local date-time must not be in a gap and the offset
    811      * must match one of the valid offsets.
    812      * <p>
    813      * This default implementation checks if {@link #getValidOffsets(java.time.LocalDateTime)}
    814      * contains the specified offset.
    815      *
    816      * @param localDateTime  the date-time to check, not null, but null
    817      *  may be ignored if the rules have a single offset for all instants
    818      * @param offset  the offset to check, null returns false
    819      * @return true if the offset date-time is valid for these rules
    820      */
    821     public boolean isValidOffset(LocalDateTime localDateTime, ZoneOffset offset) {
    822         return getValidOffsets(localDateTime).contains(offset);
    823     }
    824 
    825     /**
    826      * Gets the next transition after the specified instant.
    827      * <p>
    828      * This returns details of the next transition after the specified instant.
    829      * For example, if the instant represents a point where "Summer" daylight savings time
    830      * applies, then the method will return the transition to the next "Winter" time.
    831      *
    832      * @param instant  the instant to get the next transition after, not null, but null
    833      *  may be ignored if the rules have a single offset for all instants
    834      * @return the next transition after the specified instant, null if this is after the last transition
    835      */
    836     public ZoneOffsetTransition nextTransition(Instant instant) {
    837         if (savingsInstantTransitions.length == 0) {
    838             return null;
    839         }
    840         long epochSec = instant.getEpochSecond();
    841         // check if using last rules
    842         if (epochSec >= savingsInstantTransitions[savingsInstantTransitions.length - 1]) {
    843             if (lastRules.length == 0) {
    844                 return null;
    845             }
    846             // search year the instant is in
    847             int year = findYear(epochSec, wallOffsets[wallOffsets.length - 1]);
    848             ZoneOffsetTransition[] transArray = findTransitionArray(year);
    849             for (ZoneOffsetTransition trans : transArray) {
    850                 if (epochSec < trans.toEpochSecond()) {
    851                     return trans;
    852                 }
    853             }
    854             // use first from following year
    855             if (year < Year.MAX_VALUE) {
    856                 transArray = findTransitionArray(year + 1);
    857                 return transArray[0];
    858             }
    859             return null;
    860         }
    861 
    862         // using historic rules
    863         int index  = Arrays.binarySearch(savingsInstantTransitions, epochSec);
    864         if (index < 0) {
    865             index = -index - 1;  // switched value is the next transition
    866         } else {
    867             index += 1;  // exact match, so need to add one to get the next
    868         }
    869         return new ZoneOffsetTransition(savingsInstantTransitions[index], wallOffsets[index], wallOffsets[index + 1]);
    870     }
    871 
    872     /**
    873      * Gets the previous transition before the specified instant.
    874      * <p>
    875      * This returns details of the previous transition after the specified instant.
    876      * For example, if the instant represents a point where "summer" daylight saving time
    877      * applies, then the method will return the transition from the previous "winter" time.
    878      *
    879      * @param instant  the instant to get the previous transition after, not null, but null
    880      *  may be ignored if the rules have a single offset for all instants
    881      * @return the previous transition after the specified instant, null if this is before the first transition
    882      */
    883     public ZoneOffsetTransition previousTransition(Instant instant) {
    884         if (savingsInstantTransitions.length == 0) {
    885             return null;
    886         }
    887         long epochSec = instant.getEpochSecond();
    888         if (instant.getNano() > 0 && epochSec < Long.MAX_VALUE) {
    889             epochSec += 1;  // allow rest of method to only use seconds
    890         }
    891 
    892         // check if using last rules
    893         long lastHistoric = savingsInstantTransitions[savingsInstantTransitions.length - 1];
    894         if (lastRules.length > 0 && epochSec > lastHistoric) {
    895             // search year the instant is in
    896             ZoneOffset lastHistoricOffset = wallOffsets[wallOffsets.length - 1];
    897             int year = findYear(epochSec, lastHistoricOffset);
    898             ZoneOffsetTransition[] transArray = findTransitionArray(year);
    899             for (int i = transArray.length - 1; i >= 0; i--) {
    900                 if (epochSec > transArray[i].toEpochSecond()) {
    901                     return transArray[i];
    902                 }
    903             }
    904             // use last from preceding year
    905             int lastHistoricYear = findYear(lastHistoric, lastHistoricOffset);
    906             if (--year > lastHistoricYear) {
    907                 transArray = findTransitionArray(year);
    908                 return transArray[transArray.length - 1];
    909             }
    910             // drop through
    911         }
    912 
    913         // using historic rules
    914         int index  = Arrays.binarySearch(savingsInstantTransitions, epochSec);
    915         if (index < 0) {
    916             index = -index - 1;
    917         }
    918         if (index <= 0) {
    919             return null;
    920         }
    921         return new ZoneOffsetTransition(savingsInstantTransitions[index - 1], wallOffsets[index - 1], wallOffsets[index]);
    922     }
    923 
    924     private int findYear(long epochSecond, ZoneOffset offset) {
    925         // inline for performance
    926         long localSecond = epochSecond + offset.getTotalSeconds();
    927         long localEpochDay = Math.floorDiv(localSecond, 86400);
    928         return LocalDate.ofEpochDay(localEpochDay).getYear();
    929     }
    930 
    931     /**
    932      * Gets the complete list of fully defined transitions.
    933      * <p>
    934      * The complete set of transitions for this rules instance is defined by this method
    935      * and {@link #getTransitionRules()}. This method returns those transitions that have
    936      * been fully defined. These are typically historical, but may be in the future.
    937      * <p>
    938      * The list will be empty for fixed offset rules and for any time-zone where there has
    939      * only ever been a single offset. The list will also be empty if the transition rules are unknown.
    940      *
    941      * @return an immutable list of fully defined transitions, not null
    942      */
    943     public List<ZoneOffsetTransition> getTransitions() {
    944         List<ZoneOffsetTransition> list = new ArrayList<>();
    945         for (int i = 0; i < savingsInstantTransitions.length; i++) {
    946             list.add(new ZoneOffsetTransition(savingsInstantTransitions[i], wallOffsets[i], wallOffsets[i + 1]));
    947         }
    948         return Collections.unmodifiableList(list);
    949     }
    950 
    951     /**
    952      * Gets the list of transition rules for years beyond those defined in the transition list.
    953      * <p>
    954      * The complete set of transitions for this rules instance is defined by this method
    955      * and {@link #getTransitions()}. This method returns instances of {@link ZoneOffsetTransitionRule}
    956      * that define an algorithm for when transitions will occur.
    957      * <p>
    958      * For any given {@code ZoneRules}, this list contains the transition rules for years
    959      * beyond those years that have been fully defined. These rules typically refer to future
    960      * daylight saving time rule changes.
    961      * <p>
    962      * If the zone defines daylight savings into the future, then the list will normally
    963      * be of size two and hold information about entering and exiting daylight savings.
    964      * If the zone does not have daylight savings, or information about future changes
    965      * is uncertain, then the list will be empty.
    966      * <p>
    967      * The list will be empty for fixed offset rules and for any time-zone where there is no
    968      * daylight saving time. The list will also be empty if the transition rules are unknown.
    969      *
    970      * @return an immutable list of transition rules, not null
    971      */
    972     public List<ZoneOffsetTransitionRule> getTransitionRules() {
    973         return Collections.unmodifiableList(Arrays.asList(lastRules));
    974     }
    975 
    976     /**
    977      * Checks if this set of rules equals another.
    978      * <p>
    979      * Two rule sets are equal if they will always result in the same output
    980      * for any given input instant or local date-time.
    981      * Rules from two different groups may return false even if they are in fact the same.
    982      * <p>
    983      * This definition should result in implementations comparing their entire state.
    984      *
    985      * @param otherRules  the other rules, null returns false
    986      * @return true if this rules is the same as that specified
    987      */
    988     @Override
    989     public boolean equals(Object otherRules) {
    990         if (this == otherRules) {
    991            return true;
    992         }
    993         if (otherRules instanceof ZoneRules) {
    994             ZoneRules other = (ZoneRules) otherRules;
    995             return Arrays.equals(standardTransitions, other.standardTransitions) &&
    996                     Arrays.equals(standardOffsets, other.standardOffsets) &&
    997                     Arrays.equals(savingsInstantTransitions, other.savingsInstantTransitions) &&
    998                     Arrays.equals(wallOffsets, other.wallOffsets) &&
    999                     Arrays.equals(lastRules, other.lastRules);
   1000         }
   1001         return false;
   1002     }
   1003 
   1004     /**
   1005      * Returns a suitable hash code given the definition of {@code #equals}.
   1006      *
   1007      * @return the hash code
   1008      */
   1009     @Override
   1010     public int hashCode() {
   1011         return Arrays.hashCode(standardTransitions) ^
   1012                 Arrays.hashCode(standardOffsets) ^
   1013                 Arrays.hashCode(savingsInstantTransitions) ^
   1014                 Arrays.hashCode(wallOffsets) ^
   1015                 Arrays.hashCode(lastRules);
   1016     }
   1017 
   1018     /**
   1019      * Returns a string describing this object.
   1020      *
   1021      * @return a string for debugging, not null
   1022      */
   1023     @Override
   1024     public String toString() {
   1025         return "ZoneRules[currentStandardOffset=" + standardOffsets[standardOffsets.length - 1] + "]";
   1026     }
   1027 
   1028 }
   1029