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