1 /*
2  * Copyright (c) 2012, 2020, 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.LocalDateTime;
73 import java.time.ZoneId;
74 import java.time.ZoneOffset;
75 import java.time.Year;
76 import java.util.ArrayList;
77 import java.util.Arrays;
78 import java.util.Collections;
79 import java.util.List;
80 import java.util.Objects;
81 import java.util.concurrent.ConcurrentHashMap;
82 import java.util.concurrent.ConcurrentMap;
83 
84 /**
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 based
90  * 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 historic
99  * and future transitions, and the information stored is only as accurate as
100  * that supplied to the implementation by the rules provider.
101  * Applications should treat the data provided as representing the best information
102  * available to the implementation of this rule.
103  *
104  * @implSpec
105  * This class is immutable and thread-safe.
106  *
107  * @since 1.8
108  */
109 public final class ZoneRules implements Serializable {
110 
111     /**
112      * Serialization version.
113      */
114     private static final long serialVersionUID = 3044319355680032515L;
115     /**
116      * The last year to have its transitions cached.
117      */
118     private static final int LAST_CACHED_YEAR = 2100;
119 
120     /**
121      * The transitions between standard offsets (epoch seconds), sorted.
122      */
123     private final long[] standardTransitions;
124     /**
125      * The standard offsets.
126      */
127     private final ZoneOffset[] standardOffsets;
128     /**
129      * The transitions between instants (epoch seconds), sorted.
130      */
131     private 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 transition
135      * and the second entry is the end of the transition.
136      */
137     private final LocalDateTime[] savingsLocalTransitions;
138     /**
139      * The wall offsets.
140      */
141     private final ZoneOffset[] wallOffsets;
142     /**
143      * The last rule.
144      */
145     private final ZoneOffsetTransitionRule[] lastRules;
146     /**
147      * The map of recent transitions.
148      */
149     private final transient ConcurrentMap<Integer, ZoneOffsetTransition[]> lastRulesCache =
150                 new ConcurrentHashMap<Integer, ZoneOffsetTransition[]>();
151     /**
152      * The zero-length long array.
153      */
154     private static final long[] EMPTY_LONG_ARRAY = new long[0];
155     /**
156      * The zero-length lastrules array.
157      */
158     private static final ZoneOffsetTransitionRule[] EMPTY_LASTRULES =
159         new ZoneOffsetTransitionRule[0];
160     /**
161      * The zero-length ldt array.
162      */
163     private static final LocalDateTime[] EMPTY_LDT_ARRAY = new LocalDateTime[0];
164     /**
165      * The number of days in a 400 year cycle.
166      */
167     private static final int DAYS_PER_CYCLE = 146097;
168     /**
169      * The number of days from year zero to year 1970.
170      * There are five 400 year cycles from year zero to 2000.
171      * There are 7 leap years from 1970 to 2000.
172      */
173     private static final long DAYS_0000_TO_1970 = (DAYS_PER_CYCLE * 5L) - (30L * 365L + 7L);
174 
175     /**
176      * Obtains an instance of a ZoneRules.
177      *
178      * @param baseStandardOffset  the standard offset to use before legal rules were set, not null
179      * @param baseWallOffset  the wall offset to use before legal rules were set, not null
180      * @param standardOffsetTransitionList  the list of changes to the standard offset, not null
181      * @param transitionList  the list of transitions, not null
182      * @param lastRules  the recurring last rules, size 16 or less, not null
183      * @return the zone rules, not null
184      */
of(ZoneOffset baseStandardOffset, ZoneOffset baseWallOffset, List<ZoneOffsetTransition> standardOffsetTransitionList, List<ZoneOffsetTransition> transitionList, List<ZoneOffsetTransitionRule> lastRules)185     public static ZoneRules of(ZoneOffset baseStandardOffset,
186                                ZoneOffset baseWallOffset,
187                                List<ZoneOffsetTransition> standardOffsetTransitionList,
188                                List<ZoneOffsetTransition> transitionList,
189                                List<ZoneOffsetTransitionRule> lastRules) {
190         Objects.requireNonNull(baseStandardOffset, "baseStandardOffset");
191         Objects.requireNonNull(baseWallOffset, "baseWallOffset");
192         Objects.requireNonNull(standardOffsetTransitionList, "standardOffsetTransitionList");
193         Objects.requireNonNull(transitionList, "transitionList");
194         Objects.requireNonNull(lastRules, "lastRules");
195         return new ZoneRules(baseStandardOffset, baseWallOffset,
196                              standardOffsetTransitionList, transitionList, lastRules);
197     }
198 
199     /**
200      * Obtains an instance of ZoneRules that has fixed zone rules.
201      *
202      * @param offset  the offset this fixed zone rules is based on, not null
203      * @return the zone rules, not null
204      * @see #isFixedOffset()
205      */
of(ZoneOffset offset)206     public static ZoneRules of(ZoneOffset offset) {
207         Objects.requireNonNull(offset, "offset");
208         return new ZoneRules(offset);
209     }
210 
211     /**
212      * Creates an instance.
213      *
214      * @param baseStandardOffset  the standard offset to use before legal rules were set, not null
215      * @param baseWallOffset  the wall offset to use before legal rules were set, not null
216      * @param standardOffsetTransitionList  the list of changes to the standard offset, not null
217      * @param transitionList  the list of transitions, not null
218      * @param lastRules  the recurring last rules, size 16 or less, not null
219      */
ZoneRules(ZoneOffset baseStandardOffset, ZoneOffset baseWallOffset, List<ZoneOffsetTransition> standardOffsetTransitionList, List<ZoneOffsetTransition> transitionList, List<ZoneOffsetTransitionRule> lastRules)220     ZoneRules(ZoneOffset baseStandardOffset,
221               ZoneOffset baseWallOffset,
222               List<ZoneOffsetTransition> standardOffsetTransitionList,
223               List<ZoneOffsetTransition> transitionList,
224               List<ZoneOffsetTransitionRule> lastRules) {
225         super();
226 
227         // convert standard transitions
228 
229         this.standardTransitions = new long[standardOffsetTransitionList.size()];
230 
231         this.standardOffsets = new ZoneOffset[standardOffsetTransitionList.size() + 1];
232         this.standardOffsets[0] = baseStandardOffset;
233         for (int i = 0; i < standardOffsetTransitionList.size(); i++) {
234             this.standardTransitions[i] = standardOffsetTransitionList.get(i).toEpochSecond();
235             this.standardOffsets[i + 1] = standardOffsetTransitionList.get(i).getOffsetAfter();
236         }
237 
238         // convert savings transitions to locals
239         List<LocalDateTime> localTransitionList = new ArrayList<>();
240         List<ZoneOffset> localTransitionOffsetList = new ArrayList<>();
241         localTransitionOffsetList.add(baseWallOffset);
242         for (ZoneOffsetTransition trans : transitionList) {
243             if (trans.isGap()) {
244                 localTransitionList.add(trans.getDateTimeBefore());
245                 localTransitionList.add(trans.getDateTimeAfter());
246             } else {
247                 localTransitionList.add(trans.getDateTimeAfter());
248                 localTransitionList.add(trans.getDateTimeBefore());
249             }
250             localTransitionOffsetList.add(trans.getOffsetAfter());
251         }
252         this.savingsLocalTransitions = localTransitionList.toArray(new LocalDateTime[localTransitionList.size()]);
253         this.wallOffsets = localTransitionOffsetList.toArray(new ZoneOffset[localTransitionOffsetList.size()]);
254 
255         // convert savings transitions to instants
256         this.savingsInstantTransitions = new long[transitionList.size()];
257         for (int i = 0; i < transitionList.size(); i++) {
258             this.savingsInstantTransitions[i] = transitionList.get(i).toEpochSecond();
259         }
260 
261         // last rules
262         if (lastRules.size() > 16) {
263             throw new IllegalArgumentException("Too many transition rules");
264         }
265         this.lastRules = lastRules.toArray(new ZoneOffsetTransitionRule[lastRules.size()]);
266     }
267 
268     /**
269      * Constructor.
270      *
271      * @param standardTransitions  the standard transitions, not null
272      * @param standardOffsets  the standard offsets, not null
273      * @param savingsInstantTransitions  the standard transitions, not null
274      * @param wallOffsets  the wall offsets, not null
275      * @param lastRules  the recurring last rules, size 15 or less, not null
276      */
ZoneRules(long[] standardTransitions, ZoneOffset[] standardOffsets, long[] savingsInstantTransitions, ZoneOffset[] wallOffsets, ZoneOffsetTransitionRule[] lastRules)277     private ZoneRules(long[] standardTransitions,
278                       ZoneOffset[] standardOffsets,
279                       long[] savingsInstantTransitions,
280                       ZoneOffset[] wallOffsets,
281                       ZoneOffsetTransitionRule[] lastRules) {
282         super();
283 
284         this.standardTransitions = standardTransitions;
285         this.standardOffsets = standardOffsets;
286         this.savingsInstantTransitions = savingsInstantTransitions;
287         this.wallOffsets = wallOffsets;
288         this.lastRules = lastRules;
289 
290         if (savingsInstantTransitions.length == 0) {
291             this.savingsLocalTransitions = EMPTY_LDT_ARRAY;
292         } else {
293             // convert savings transitions to locals
294             List<LocalDateTime> localTransitionList = new ArrayList<>();
295             for (int i = 0; i < savingsInstantTransitions.length; i++) {
296                 ZoneOffset before = wallOffsets[i];
297                 ZoneOffset after = wallOffsets[i + 1];
298                 ZoneOffsetTransition trans = new ZoneOffsetTransition(savingsInstantTransitions[i], before, after);
299                 if (trans.isGap()) {
300                     localTransitionList.add(trans.getDateTimeBefore());
301                     localTransitionList.add(trans.getDateTimeAfter());
302                 } else {
303                     localTransitionList.add(trans.getDateTimeAfter());
304                     localTransitionList.add(trans.getDateTimeBefore());
305                }
306             }
307             this.savingsLocalTransitions = localTransitionList.toArray(new LocalDateTime[localTransitionList.size()]);
308         }
309     }
310 
311     /**
312      * Creates an instance of ZoneRules that has fixed zone rules.
313      *
314      * @param offset  the offset this fixed zone rules is based on, not null
315      * @see #isFixedOffset()
316      */
ZoneRules(ZoneOffset offset)317     private ZoneRules(ZoneOffset offset) {
318         this.standardOffsets = new ZoneOffset[1];
319         this.standardOffsets[0] = offset;
320         this.standardTransitions = EMPTY_LONG_ARRAY;
321         this.savingsInstantTransitions = EMPTY_LONG_ARRAY;
322         this.savingsLocalTransitions = EMPTY_LDT_ARRAY;
323         this.wallOffsets = standardOffsets;
324         this.lastRules = EMPTY_LASTRULES;
325     }
326 
327     /**
328      * Defend against malicious streams.
329      *
330      * @param s the stream to read
331      * @throws InvalidObjectException always
332      */
readObject(ObjectInputStream s)333     private void readObject(ObjectInputStream s) throws InvalidObjectException {
334         throw new InvalidObjectException("Deserialization via serialization delegate");
335     }
336 
337     /**
338      * Writes the object using a
339      * <a href="{@docRoot}/serialized-form.html#java.time.zone.Ser">dedicated serialized form</a>.
340      * @serialData
341      * <pre style="font-size:1.0em">{@code
342      *
343      *   out.writeByte(1);  // identifies a ZoneRules
344      *   out.writeInt(standardTransitions.length);
345      *   for (long trans : standardTransitions) {
346      *       Ser.writeEpochSec(trans, out);
347      *   }
348      *   for (ZoneOffset offset : standardOffsets) {
349      *       Ser.writeOffset(offset, out);
350      *   }
351      *   out.writeInt(savingsInstantTransitions.length);
352      *   for (long trans : savingsInstantTransitions) {
353      *       Ser.writeEpochSec(trans, out);
354      *   }
355      *   for (ZoneOffset offset : wallOffsets) {
356      *       Ser.writeOffset(offset, out);
357      *   }
358      *   out.writeByte(lastRules.length);
359      *   for (ZoneOffsetTransitionRule rule : lastRules) {
360      *       rule.writeExternal(out);
361      *   }
362      * }
363      * </pre>
364      * <p>
365      * Epoch second values used for offsets are encoded in a variable
366      * length form to make the common cases put fewer bytes in the stream.
367      * <pre style="font-size:1.0em">{@code
368      *
369      *  static void writeEpochSec(long epochSec, DataOutput out) throws IOException {
370      *     if (epochSec >= -4575744000L && epochSec < 10413792000L && epochSec % 900 == 0) {  // quarter hours between 1825 and 2300
371      *         int store = (int) ((epochSec + 4575744000L) / 900);
372      *         out.writeByte((store >>> 16) & 255);
373      *         out.writeByte((store >>> 8) & 255);
374      *         out.writeByte(store & 255);
375      *      } else {
376      *          out.writeByte(255);
377      *          out.writeLong(epochSec);
378      *      }
379      *  }
380      * }
381      * </pre>
382      * <p>
383      * ZoneOffset values are encoded in a variable length form so the
384      * common cases put fewer bytes in the stream.
385      * <pre style="font-size:1.0em">{@code
386      *
387      *  static void writeOffset(ZoneOffset offset, DataOutput out) throws IOException {
388      *     final int offsetSecs = offset.getTotalSeconds();
389      *     int offsetByte = offsetSecs % 900 == 0 ? offsetSecs / 900 : 127;  // compress to -72 to +72
390      *     out.writeByte(offsetByte);
391      *     if (offsetByte == 127) {
392      *         out.writeInt(offsetSecs);
393      *     }
394      * }
395      *}
396      * </pre>
397      * @return the replacing object, not null
398      */
writeReplace()399     private Object writeReplace() {
400         return new Ser(Ser.ZRULES, this);
401     }
402 
403     /**
404      * Writes the state to the stream.
405      *
406      * @param out  the output stream, not null
407      * @throws IOException if an error occurs
408      */
writeExternal(DataOutput out)409     void writeExternal(DataOutput out) throws IOException {
410         out.writeInt(standardTransitions.length);
411         for (long trans : standardTransitions) {
412             Ser.writeEpochSec(trans, out);
413         }
414         for (ZoneOffset offset : standardOffsets) {
415             Ser.writeOffset(offset, out);
416         }
417         out.writeInt(savingsInstantTransitions.length);
418         for (long trans : savingsInstantTransitions) {
419             Ser.writeEpochSec(trans, out);
420         }
421         for (ZoneOffset offset : wallOffsets) {
422             Ser.writeOffset(offset, out);
423         }
424         out.writeByte(lastRules.length);
425         for (ZoneOffsetTransitionRule rule : lastRules) {
426             rule.writeExternal(out);
427         }
428     }
429 
430     /**
431      * Reads the state from the stream. The 1,024 limit to the lengths
432      * of stdTrans and savSize is intended to be the size well enough
433      * to accommodate the max number of transitions in current tzdb data
434      * (203 for Asia/Tehran).
435      *
436      * @param in  the input stream, not null
437      * @return the created object, not null
438      * @throws IOException if an error occurs
439      */
readExternal(DataInput in)440     static ZoneRules readExternal(DataInput in) throws IOException, ClassNotFoundException {
441         int stdSize = in.readInt();
442         if (stdSize > 1024) {
443             throw new InvalidObjectException("Too many transitions");
444         }
445         long[] stdTrans = (stdSize == 0) ? EMPTY_LONG_ARRAY
446                                          : new long[stdSize];
447         for (int i = 0; i < stdSize; i++) {
448             stdTrans[i] = Ser.readEpochSec(in);
449         }
450         ZoneOffset[] stdOffsets = new ZoneOffset[stdSize + 1];
451         for (int i = 0; i < stdOffsets.length; i++) {
452             stdOffsets[i] = Ser.readOffset(in);
453         }
454         int savSize = in.readInt();
455         if (savSize > 1024) {
456             throw new InvalidObjectException("Too many saving offsets");
457         }
458         long[] savTrans = (savSize == 0) ? EMPTY_LONG_ARRAY
459                                          : new long[savSize];
460         for (int i = 0; i < savSize; i++) {
461             savTrans[i] = Ser.readEpochSec(in);
462         }
463         ZoneOffset[] savOffsets = new ZoneOffset[savSize + 1];
464         for (int i = 0; i < savOffsets.length; i++) {
465             savOffsets[i] = Ser.readOffset(in);
466         }
467         int ruleSize = in.readByte();
468         if (ruleSize > 16) {
469             throw new InvalidObjectException("Too many transition rules");
470         }
471         ZoneOffsetTransitionRule[] rules = (ruleSize == 0) ?
472             EMPTY_LASTRULES : new ZoneOffsetTransitionRule[ruleSize];
473         for (int i = 0; i < ruleSize; i++) {
474             rules[i] = ZoneOffsetTransitionRule.readExternal(in);
475         }
476         return new ZoneRules(stdTrans, stdOffsets, savTrans, savOffsets, rules);
477     }
478 
479     /**
480      * Checks of the zone rules are fixed, such that the offset never varies.
481      *
482      * @return true if the time-zone is fixed and the offset never changes
483      */
isFixedOffset()484     public boolean isFixedOffset() {
485         return standardOffsets[0].equals(wallOffsets[0]) &&
486                 standardTransitions.length == 0 &&
487                 savingsInstantTransitions.length == 0 &&
488                 lastRules.length == 0;
489     }
490 
491     /**
492      * Gets the offset applicable at the specified instant in these rules.
493      * <p>
494      * The mapping from an instant to an offset is simple, there is only
495      * one valid offset for each instant.
496      * This method returns that offset.
497      *
498      * @param instant  the instant to find the offset for, not null, but null
499      *  may be ignored if the rules have a single offset for all instants
500      * @return the offset, not null
501      */
getOffset(Instant instant)502     public ZoneOffset getOffset(Instant instant) {
503         if (savingsInstantTransitions.length == 0) {
504             return wallOffsets[0];
505         }
506         long epochSec = instant.getEpochSecond();
507         // check if using last rules
508         if (lastRules.length > 0 &&
509                 epochSec > savingsInstantTransitions[savingsInstantTransitions.length - 1]) {
510             int year = findYear(epochSec, wallOffsets[wallOffsets.length - 1]);
511             ZoneOffsetTransition[] transArray = findTransitionArray(year);
512             ZoneOffsetTransition trans = null;
513             for (int i = 0; i < transArray.length; i++) {
514                 trans = transArray[i];
515                 if (epochSec < trans.toEpochSecond()) {
516                     return trans.getOffsetBefore();
517                 }
518             }
519             return trans.getOffsetAfter();
520         }
521 
522         // using historic rules
523         int index  = Arrays.binarySearch(savingsInstantTransitions, epochSec);
524         if (index < 0) {
525             // switch negative insert position to start of matched range
526             index = -index - 2;
527         }
528         return wallOffsets[index + 1];
529     }
530 
531     /**
532      * Gets a suitable offset for the specified local date-time in these rules.
533      * <p>
534      * The mapping from a local date-time to an offset is not straightforward.
535      * There are three cases:
536      * <ul>
537      * <li>Normal, with one valid offset. For the vast majority of the year, the normal
538      *  case applies, where there is a single valid offset for the local date-time.</li>
539      * <li>Gap, with zero valid offsets. This is when clocks jump forward typically
540      *  due to the spring daylight savings change from "winter" to "summer".
541      *  In a gap there are local date-time values with no valid offset.</li>
542      * <li>Overlap, with two valid offsets. This is when clocks are set back typically
543      *  due to the autumn daylight savings change from "summer" to "winter".
544      *  In an overlap there are local date-time values with two valid offsets.</li>
545      * </ul>
546      * Thus, for any given local date-time there can be zero, one or two valid offsets.
547      * This method returns the single offset in the Normal case, and in the Gap or Overlap
548      * case it returns the offset before the transition.
549      * <p>
550      * Since, in the case of Gap and Overlap, the offset returned is a "best" value, rather
551      * than the "correct" value, it should be treated with care. Applications that care
552      * about the correct offset should use a combination of this method,
553      * {@link #getValidOffsets(LocalDateTime)} and {@link #getTransition(LocalDateTime)}.
554      *
555      * @param localDateTime  the local date-time to query, not null, but null
556      *  may be ignored if the rules have a single offset for all instants
557      * @return the best available offset for the local date-time, not null
558      */
getOffset(LocalDateTime localDateTime)559     public ZoneOffset getOffset(LocalDateTime localDateTime) {
560         Object info = getOffsetInfo(localDateTime);
561         if (info instanceof ZoneOffsetTransition) {
562             return ((ZoneOffsetTransition) info).getOffsetBefore();
563         }
564         return (ZoneOffset) info;
565     }
566 
567     /**
568      * Gets the offset applicable at the specified local date-time in these rules.
569      * <p>
570      * The mapping from a local date-time to an offset is not straightforward.
571      * There are three cases:
572      * <ul>
573      * <li>Normal, with one valid offset. For the vast majority of the year, the normal
574      *  case applies, where there is a single valid offset for the local date-time.</li>
575      * <li>Gap, with zero valid offsets. This is when clocks jump forward typically
576      *  due to the spring daylight savings change from "winter" to "summer".
577      *  In a gap there are local date-time values with no valid offset.</li>
578      * <li>Overlap, with two valid offsets. This is when clocks are set back typically
579      *  due to the autumn daylight savings change from "summer" to "winter".
580      *  In an overlap there are local date-time values with two valid offsets.</li>
581      * </ul>
582      * Thus, for any given local date-time there can be zero, one or two valid offsets.
583      * This method returns that list of valid offsets, which is a list of size 0, 1 or 2.
584      * In the case where there are two offsets, the earlier offset is returned at index 0
585      * and the later offset at index 1.
586      * <p>
587      * There are various ways to handle the conversion from a {@code LocalDateTime}.
588      * One technique, using this method, would be:
589      * <pre>
590      *  List&lt;ZoneOffset&gt; validOffsets = rules.getValidOffsets(localDT);
591      *  if (validOffsets.size() == 1) {
592      *    // Normal case: only one valid offset
593      *    zoneOffset = validOffsets.get(0);
594      *  } else {
595      *    // Gap or Overlap: determine what to do from transition (which will be non-null)
596      *    ZoneOffsetTransition trans = rules.getTransition(localDT);
597      *  }
598      * </pre>
599      * <p>
600      * In theory, it is possible for there to be more than two valid offsets.
601      * This would happen if clocks to be put back more than once in quick succession.
602      * This has never happened in the history of time-zones and thus has no special handling.
603      * However, if it were to happen, then the list would return more than 2 entries.
604      *
605      * @param localDateTime  the local date-time to query for valid offsets, not null, but null
606      *  may be ignored if the rules have a single offset for all instants
607      * @return the list of valid offsets, may be immutable, not null
608      */
getValidOffsets(LocalDateTime localDateTime)609     public List<ZoneOffset> getValidOffsets(LocalDateTime localDateTime) {
610         // should probably be optimized
611         Object info = getOffsetInfo(localDateTime);
612         if (info instanceof ZoneOffsetTransition) {
613             return ((ZoneOffsetTransition) info).getValidOffsets();
614         }
615         return Collections.singletonList((ZoneOffset) info);
616     }
617 
618     /**
619      * Gets the offset transition applicable at the specified local date-time in these rules.
620      * <p>
621      * The mapping from a local date-time to an offset is not straightforward.
622      * There are three cases:
623      * <ul>
624      * <li>Normal, with one valid offset. For the vast majority of the year, the normal
625      *  case applies, where there is a single valid offset for the local date-time.</li>
626      * <li>Gap, with zero valid offsets. This is when clocks jump forward typically
627      *  due to the spring daylight savings change from "winter" to "summer".
628      *  In a gap there are local date-time values with no valid offset.</li>
629      * <li>Overlap, with two valid offsets. This is when clocks are set back typically
630      *  due to the autumn daylight savings change from "summer" to "winter".
631      *  In an overlap there are local date-time values with two valid offsets.</li>
632      * </ul>
633      * A transition is used to model the cases of a Gap or Overlap.
634      * The Normal case will return null.
635      * <p>
636      * There are various ways to handle the conversion from a {@code LocalDateTime}.
637      * One technique, using this method, would be:
638      * <pre>
639      *  ZoneOffsetTransition trans = rules.getTransition(localDT);
640      *  if (trans != null) {
641      *    // Gap or Overlap: determine what to do from transition
642      *  } else {
643      *    // Normal case: only one valid offset
644      *    zoneOffset = rule.getOffset(localDT);
645      *  }
646      * </pre>
647      *
648      * @param localDateTime  the local date-time to query for offset transition, not null, but null
649      *  may be ignored if the rules have a single offset for all instants
650      * @return the offset transition, null if the local date-time is not in transition
651      */
getTransition(LocalDateTime localDateTime)652     public ZoneOffsetTransition getTransition(LocalDateTime localDateTime) {
653         Object info = getOffsetInfo(localDateTime);
654         return (info instanceof ZoneOffsetTransition ? (ZoneOffsetTransition) info : null);
655     }
656 
getOffsetInfo(LocalDateTime dt)657     private Object getOffsetInfo(LocalDateTime dt) {
658         if (savingsLocalTransitions.length == 0) {
659             return wallOffsets[0];
660         }
661         // check if using last rules
662         if (lastRules.length > 0 &&
663                 dt.isAfter(savingsLocalTransitions[savingsLocalTransitions.length - 1])) {
664             ZoneOffsetTransition[] transArray = findTransitionArray(dt.getYear());
665             Object info = null;
666             for (ZoneOffsetTransition trans : transArray) {
667                 info = findOffsetInfo(dt, trans);
668                 if (info instanceof ZoneOffsetTransition || info.equals(trans.getOffsetBefore())) {
669                     return info;
670                 }
671             }
672             return info;
673         }
674 
675         // using historic rules
676         int index  = Arrays.binarySearch(savingsLocalTransitions, dt);
677         if (index == -1) {
678             // before first transition
679             return wallOffsets[0];
680         }
681         if (index < 0) {
682             // switch negative insert position to start of matched range
683             index = -index - 2;
684         } else if (index < savingsLocalTransitions.length - 1 &&
685                 savingsLocalTransitions[index].equals(savingsLocalTransitions[index + 1])) {
686             // handle overlap immediately following gap
687             index++;
688         }
689         if ((index & 1) == 0) {
690             // gap or overlap
691             LocalDateTime dtBefore = savingsLocalTransitions[index];
692             LocalDateTime dtAfter = savingsLocalTransitions[index + 1];
693             ZoneOffset offsetBefore = wallOffsets[index / 2];
694             ZoneOffset offsetAfter = wallOffsets[index / 2 + 1];
695             if (offsetAfter.getTotalSeconds() > offsetBefore.getTotalSeconds()) {
696                 // gap
697                 return new ZoneOffsetTransition(dtBefore, offsetBefore, offsetAfter);
698             } else {
699                 // overlap
700                 return new ZoneOffsetTransition(dtAfter, offsetBefore, offsetAfter);
701             }
702         } else {
703             // normal (neither gap or overlap)
704             return wallOffsets[index / 2 + 1];
705         }
706     }
707 
708     /**
709      * Finds the offset info for a local date-time and transition.
710      *
711      * @param dt  the date-time, not null
712      * @param trans  the transition, not null
713      * @return the offset info, not null
714      */
findOffsetInfo(LocalDateTime dt, ZoneOffsetTransition trans)715     private Object findOffsetInfo(LocalDateTime dt, ZoneOffsetTransition trans) {
716         LocalDateTime localTransition = trans.getDateTimeBefore();
717         if (trans.isGap()) {
718             if (dt.isBefore(localTransition)) {
719                 return trans.getOffsetBefore();
720             }
721             if (dt.isBefore(trans.getDateTimeAfter())) {
722                 return trans;
723             } else {
724                 return trans.getOffsetAfter();
725             }
726         } else {
727             if (dt.isBefore(localTransition) == false) {
728                 return trans.getOffsetAfter();
729             }
730             if (dt.isBefore(trans.getDateTimeAfter())) {
731                 return trans.getOffsetBefore();
732             } else {
733                 return trans;
734             }
735         }
736     }
737 
738     /**
739      * Finds the appropriate transition array for the given year.
740      *
741      * @param year  the year, not null
742      * @return the transition array, not null
743      */
findTransitionArray(int year)744     private ZoneOffsetTransition[] findTransitionArray(int year) {
745         Integer yearObj = year;  // should use Year class, but this saves a class load
746         ZoneOffsetTransition[] transArray = lastRulesCache.get(yearObj);
747         if (transArray != null) {
748             return transArray;
749         }
750         ZoneOffsetTransitionRule[] ruleArray = lastRules;
751         transArray  = new ZoneOffsetTransition[ruleArray.length];
752         for (int i = 0; i < ruleArray.length; i++) {
753             transArray[i] = ruleArray[i].createTransition(year);
754         }
755         if (year < LAST_CACHED_YEAR) {
756             lastRulesCache.putIfAbsent(yearObj, transArray);
757         }
758         return transArray;
759     }
760 
761     /**
762      * Gets the standard offset for the specified instant in this zone.
763      * <p>
764      * This provides access to historic information on how the standard offset
765      * has changed over time.
766      * The standard offset is the offset before any daylight saving time is applied.
767      * This is typically the offset applicable during winter.
768      *
769      * @param instant  the instant to find the offset information for, not null, but null
770      *  may be ignored if the rules have a single offset for all instants
771      * @return the standard offset, not null
772      */
getStandardOffset(Instant instant)773     public ZoneOffset getStandardOffset(Instant instant) {
774         if (standardTransitions.length == 0) {
775             return standardOffsets[0];
776         }
777         long epochSec = instant.getEpochSecond();
778         int index  = Arrays.binarySearch(standardTransitions, epochSec);
779         if (index < 0) {
780             // switch negative insert position to start of matched range
781             index = -index - 2;
782         }
783         return standardOffsets[index + 1];
784     }
785 
786     /**
787      * Gets the amount of daylight savings in use for the specified instant in this zone.
788      * <p>
789      * This provides access to historic information on how the amount of daylight
790      * savings has changed over time.
791      * This is the difference between the standard offset and the actual offset.
792      * Typically the amount is zero during winter and one hour during summer.
793      * Time-zones are second-based, so the nanosecond part of the duration will be zero.
794      * <p>
795      * This default implementation calculates the duration from the
796      * {@link #getOffset(java.time.Instant) actual} and
797      * {@link #getStandardOffset(java.time.Instant) standard} offsets.
798      *
799      * @param instant  the instant to find the daylight savings for, not null, but null
800      *  may be ignored if the rules have a single offset for all instants
801      * @return the difference between the standard and actual offset, not null
802      */
getDaylightSavings(Instant instant)803     public Duration getDaylightSavings(Instant instant) {
804         if (isFixedOffset()) {
805             return Duration.ZERO;
806         }
807         ZoneOffset standardOffset = getStandardOffset(instant);
808         ZoneOffset actualOffset = getOffset(instant);
809         return Duration.ofSeconds(actualOffset.getTotalSeconds() - standardOffset.getTotalSeconds());
810     }
811 
812     /**
813      * Checks if the specified instant is in daylight savings.
814      * <p>
815      * This checks if the standard offset and the actual offset are the same
816      * for the specified instant.
817      * If they are not, it is assumed that daylight savings is in operation.
818      * <p>
819      * This default implementation compares the {@link #getOffset(java.time.Instant) actual}
820      * and {@link #getStandardOffset(java.time.Instant) standard} offsets.
821      *
822      * @param instant  the instant to find the offset information for, not null, but null
823      *  may be ignored if the rules have a single offset for all instants
824      * @return the standard offset, not null
825      */
isDaylightSavings(Instant instant)826     public boolean isDaylightSavings(Instant instant) {
827         return (getStandardOffset(instant).equals(getOffset(instant)) == false);
828     }
829 
830     /**
831      * Checks if the offset date-time is valid for these rules.
832      * <p>
833      * To be valid, the local date-time must not be in a gap and the offset
834      * must match one of the valid offsets.
835      * <p>
836      * This default implementation checks if {@link #getValidOffsets(java.time.LocalDateTime)}
837      * contains the specified offset.
838      *
839      * @param localDateTime  the date-time to check, not null, but null
840      *  may be ignored if the rules have a single offset for all instants
841      * @param offset  the offset to check, null returns false
842      * @return true if the offset date-time is valid for these rules
843      */
isValidOffset(LocalDateTime localDateTime, ZoneOffset offset)844     public boolean isValidOffset(LocalDateTime localDateTime, ZoneOffset offset) {
845         return getValidOffsets(localDateTime).contains(offset);
846     }
847 
848     /**
849      * Gets the next transition after the specified instant.
850      * <p>
851      * This returns details of the next transition after the specified instant.
852      * For example, if the instant represents a point where "Summer" daylight savings time
853      * applies, then the method will return the transition to the next "Winter" time.
854      *
855      * @param instant  the instant to get the next transition after, not null, but null
856      *  may be ignored if the rules have a single offset for all instants
857      * @return the next transition after the specified instant, null if this is after the last transition
858      */
nextTransition(Instant instant)859     public ZoneOffsetTransition nextTransition(Instant instant) {
860         if (savingsInstantTransitions.length == 0) {
861             return null;
862         }
863         long epochSec = instant.getEpochSecond();
864         // check if using last rules
865         if (epochSec >= savingsInstantTransitions[savingsInstantTransitions.length - 1]) {
866             if (lastRules.length == 0) {
867                 return null;
868             }
869             // search year the instant is in
870             int year = findYear(epochSec, wallOffsets[wallOffsets.length - 1]);
871             ZoneOffsetTransition[] transArray = findTransitionArray(year);
872             for (ZoneOffsetTransition trans : transArray) {
873                 if (epochSec < trans.toEpochSecond()) {
874                     return trans;
875                 }
876             }
877             // use first from following year
878             if (year < Year.MAX_VALUE) {
879                 transArray = findTransitionArray(year + 1);
880                 return transArray[0];
881             }
882             return null;
883         }
884 
885         // using historic rules
886         int index  = Arrays.binarySearch(savingsInstantTransitions, epochSec);
887         if (index < 0) {
888             index = -index - 1;  // switched value is the next transition
889         } else {
890             index += 1;  // exact match, so need to add one to get the next
891         }
892         return new ZoneOffsetTransition(savingsInstantTransitions[index], wallOffsets[index], wallOffsets[index + 1]);
893     }
894 
895     /**
896      * Gets the previous transition before the specified instant.
897      * <p>
898      * This returns details of the previous transition before the specified instant.
899      * For example, if the instant represents a point where "summer" daylight saving time
900      * applies, then the method will return the transition from the previous "winter" time.
901      *
902      * @param instant  the instant to get the previous transition after, not null, but null
903      *  may be ignored if the rules have a single offset for all instants
904      * @return the previous transition before the specified instant, null if this is before the first transition
905      */
previousTransition(Instant instant)906     public ZoneOffsetTransition previousTransition(Instant instant) {
907         if (savingsInstantTransitions.length == 0) {
908             return null;
909         }
910         long epochSec = instant.getEpochSecond();
911         if (instant.getNano() > 0 && epochSec < Long.MAX_VALUE) {
912             epochSec += 1;  // allow rest of method to only use seconds
913         }
914 
915         // check if using last rules
916         long lastHistoric = savingsInstantTransitions[savingsInstantTransitions.length - 1];
917         if (lastRules.length > 0 && epochSec > lastHistoric) {
918             // search year the instant is in
919             ZoneOffset lastHistoricOffset = wallOffsets[wallOffsets.length - 1];
920             int year = findYear(epochSec, lastHistoricOffset);
921             ZoneOffsetTransition[] transArray = findTransitionArray(year);
922             for (int i = transArray.length - 1; i >= 0; i--) {
923                 if (epochSec > transArray[i].toEpochSecond()) {
924                     return transArray[i];
925                 }
926             }
927             // use last from preceding year
928             int lastHistoricYear = findYear(lastHistoric, lastHistoricOffset);
929             if (--year > lastHistoricYear) {
930                 transArray = findTransitionArray(year);
931                 return transArray[transArray.length - 1];
932             }
933             // drop through
934         }
935 
936         // using historic rules
937         int index  = Arrays.binarySearch(savingsInstantTransitions, epochSec);
938         if (index < 0) {
939             index = -index - 1;
940         }
941         if (index <= 0) {
942             return null;
943         }
944         return new ZoneOffsetTransition(savingsInstantTransitions[index - 1], wallOffsets[index - 1], wallOffsets[index]);
945     }
946 
findYear(long epochSecond, ZoneOffset offset)947     private int findYear(long epochSecond, ZoneOffset offset) {
948         long localSecond = epochSecond + offset.getTotalSeconds();
949         long zeroDay = Math.floorDiv(localSecond, 86400) + DAYS_0000_TO_1970;
950 
951         // find the march-based year
952         zeroDay -= 60;  // adjust to 0000-03-01 so leap day is at end of four year cycle
953         long adjust = 0;
954         if (zeroDay < 0) {
955             // adjust negative years to positive for calculation
956             long adjustCycles = (zeroDay + 1) / DAYS_PER_CYCLE - 1;
957             adjust = adjustCycles * 400;
958             zeroDay += -adjustCycles * DAYS_PER_CYCLE;
959         }
960         long yearEst = (400 * zeroDay + 591) / DAYS_PER_CYCLE;
961         long doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);
962         if (doyEst < 0) {
963             // fix estimate
964             yearEst--;
965             doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);
966         }
967         yearEst += adjust;  // reset any negative year
968         int marchDoy0 = (int) doyEst;
969 
970         // convert march-based values back to january-based
971         int marchMonth0 = (marchDoy0 * 5 + 2) / 153;
972         yearEst += marchMonth0 / 10;
973 
974         // Cap to the max value
975         return (int)Math.min(yearEst, Year.MAX_VALUE);
976     }
977 
978     /**
979      * Gets the complete list of fully defined transitions.
980      * <p>
981      * The complete set of transitions for this rules instance is defined by this method
982      * and {@link #getTransitionRules()}. This method returns those transitions that have
983      * been fully defined. These are typically historical, but may be in the future.
984      * <p>
985      * The list will be empty for fixed offset rules and for any time-zone where there has
986      * only ever been a single offset. The list will also be empty if the transition rules are unknown.
987      *
988      * @return an immutable list of fully defined transitions, not null
989      */
getTransitions()990     public List<ZoneOffsetTransition> getTransitions() {
991         List<ZoneOffsetTransition> list = new ArrayList<>();
992         for (int i = 0; i < savingsInstantTransitions.length; i++) {
993             list.add(new ZoneOffsetTransition(savingsInstantTransitions[i], wallOffsets[i], wallOffsets[i + 1]));
994         }
995         return Collections.unmodifiableList(list);
996     }
997 
998     /**
999      * Gets the list of transition rules for years beyond those defined in the transition list.
1000      * <p>
1001      * The complete set of transitions for this rules instance is defined by this method
1002      * and {@link #getTransitions()}. This method returns instances of {@link ZoneOffsetTransitionRule}
1003      * that define an algorithm for when transitions will occur.
1004      * <p>
1005      * For any given {@code ZoneRules}, this list contains the transition rules for years
1006      * beyond those years that have been fully defined. These rules typically refer to future
1007      * daylight saving time rule changes.
1008      * <p>
1009      * If the zone defines daylight savings into the future, then the list will normally
1010      * be of size two and hold information about entering and exiting daylight savings.
1011      * If the zone does not have daylight savings, or information about future changes
1012      * is uncertain, then the list will be empty.
1013      * <p>
1014      * The list will be empty for fixed offset rules and for any time-zone where there is no
1015      * daylight saving time. The list will also be empty if the transition rules are unknown.
1016      *
1017      * @return an immutable list of transition rules, not null
1018      */
getTransitionRules()1019     public List<ZoneOffsetTransitionRule> getTransitionRules() {
1020         return List.of(lastRules);
1021     }
1022 
1023     /**
1024      * Checks if this set of rules equals another.
1025      * <p>
1026      * Two rule sets are equal if they will always result in the same output
1027      * for any given input instant or local date-time.
1028      * Rules from two different groups may return false even if they are in fact the same.
1029      * <p>
1030      * This definition should result in implementations comparing their entire state.
1031      *
1032      * @param otherRules  the other rules, null returns false
1033      * @return true if this rules is the same as that specified
1034      */
1035     @Override
equals(Object otherRules)1036     public boolean equals(Object otherRules) {
1037         if (this == otherRules) {
1038            return true;
1039         }
1040         if (otherRules instanceof ZoneRules) {
1041             ZoneRules other = (ZoneRules) otherRules;
1042             return Arrays.equals(standardTransitions, other.standardTransitions) &&
1043                     Arrays.equals(standardOffsets, other.standardOffsets) &&
1044                     Arrays.equals(savingsInstantTransitions, other.savingsInstantTransitions) &&
1045                     Arrays.equals(wallOffsets, other.wallOffsets) &&
1046                     Arrays.equals(lastRules, other.lastRules);
1047         }
1048         return false;
1049     }
1050 
1051     /**
1052      * Returns a suitable hash code given the definition of {@code #equals}.
1053      *
1054      * @return the hash code
1055      */
1056     @Override
hashCode()1057     public int hashCode() {
1058         return Arrays.hashCode(standardTransitions) ^
1059                 Arrays.hashCode(standardOffsets) ^
1060                 Arrays.hashCode(savingsInstantTransitions) ^
1061                 Arrays.hashCode(wallOffsets) ^
1062                 Arrays.hashCode(lastRules);
1063     }
1064 
1065     /**
1066      * Returns a string describing this object.
1067      *
1068      * @return a string for debugging, not null
1069      */
1070     @Override
toString()1071     public String toString() {
1072         return "ZoneRules[currentStandardOffset=" + standardOffsets[standardOffsets.length - 1] + "]";
1073     }
1074 
1075 }
1076