• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 /*
17  * Elements of the WallTime class are a port of Bionic's localtime.c to Java. That code had the
18  * following header:
19  *
20  * This file is in the public domain, so clarified as of
21  * 1996-06-05 by Arthur David Olson.
22  */
23 package libcore.util;
24 
25 import java.io.IOException;
26 import java.io.ObjectInputStream;
27 import java.util.Arrays;
28 import java.util.Calendar;
29 import java.util.Date;
30 import java.util.GregorianCalendar;
31 import java.util.TimeZone;
32 import libcore.io.BufferIterator;
33 
34 /**
35  * Our concrete TimeZone implementation, backed by zoneinfo data.
36  *
37  * <p>This reads time zone information from a binary file stored on the platform. The binary file
38  * is essentially a single file containing compacted versions of all the tzfile (see
39  * {@code man 5 tzfile} for details of the source) and an index by long name, e.g. Europe/London.
40  *
41  * <p>The compacted form is created by {@code external/icu/tools/ZoneCompactor.java} and is used
42  * by both this and Bionic. {@link ZoneInfoDB} is responsible for mapping the binary file, and
43  * reading the index and creating a {@link BufferIterator} that provides access to an entry for a
44  * specific file. This class is responsible for reading the data from that {@link BufferIterator}
45  * and storing it a representation to support the {@link TimeZone} and {@link GregorianCalendar}
46  * implementations. See {@link ZoneInfo#makeTimeZone(String, BufferIterator)}.
47  *
48  * <p>The main difference between {@code tzfile} and the compacted form is that the
49  * {@code struct ttinfo} only uses a single byte for {@code tt_isdst} and {@code tt_abbrind}.
50  *
51  * <p>This class does not use all the information from the {@code tzfile}; it uses:
52  * {@code tzh_timecnt} and the associated transition times and type information. For each type
53  * (described by {@code struct ttinfo}) it uses {@code tt_gmtoff} and {@code tt_isdst}. Note, that
54  * the definition of {@code struct ttinfo} uses {@code long}, and {@code int} but they do not have
55  * the same meaning as Java. The prose following the definition makes it clear that the {@code long}
56  * is 4 bytes and the {@code int} fields are 1 byte.
57  *
58  * <p>As the data uses 32 bits to store the time in seconds the time range is limited to roughly
59  * 69 years either side of the epoch (1st Jan 1970 00:00:00) that means that it cannot handle any
60  * dates before 1900 and after 2038. There is an extended version of the table that uses 64 bits
61  * to store the data but that information is not used by this.
62  *
63  * @hide - used to implement TimeZone
64  */
65 public final class ZoneInfo extends TimeZone {
66     private static final long MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
67     private static final long MILLISECONDS_PER_400_YEARS =
68             MILLISECONDS_PER_DAY * (400 * 365 + 100 - 3);
69 
70     private static final long UNIX_OFFSET = 62167219200000L;
71 
72     private static final int[] NORMAL = new int[] {
73         0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334,
74     };
75 
76     private static final int[] LEAP = new int[] {
77         0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335,
78     };
79 
80     // Proclaim serialization compatibility with pre-OpenJDK AOSP
81     static final long serialVersionUID = -4598738130123921552L;
82 
83     private int mRawOffset;
84     private final int mEarliestRawOffset;
85 
86     /**
87      * Implements {@link #useDaylightTime()}
88      *
89      * <p>True if the transition active at the time this instance was created, or future
90      * transitions support DST. It is possible that caching this value at construction time and
91      * using it for the lifetime of the instance does not match the contract of the
92      * {@link TimeZone#useDaylightTime()} method but it appears to be what the RI does and that
93      * method is not particularly useful when it comes to historical or future times as it does not
94      * allow the time to be specified.
95      *
96      * <p>When this is false then {@link #mDstSavings} will be 0.
97      *
98      * @see #mDstSavings
99      */
100     private final boolean mUseDst;
101 
102     /**
103      * Implements {@link #getDSTSavings()}
104      *
105      * <p>This should be final but is not because it may need to be fixed up by
106      * {@link #readObject(ObjectInputStream)} to correct an inconsistency in the previous version
107      * of the code whereby this was set to a non-zero value even though DST was not actually used.
108      *
109      * @see #mUseDst
110      */
111     private int mDstSavings;
112 
113     /**
114      * The times (in seconds) at which the offsets changes for any reason, whether that is a change
115      * in the offset from UTC or a change in the DST.
116      *
117      * <p>These times are pre-calculated externally from a set of rules (both historical and
118      * future) and stored in a file from which {@link ZoneInfo#makeTimeZone(String, BufferIterator)}
119      * reads the data. That is quite different to {@link java.util.SimpleTimeZone}, which has
120      * essentially human readable rules (e.g. DST starts at 01:00 on the first Sunday in March and
121      * ends at 01:00 on the last Sunday in October) that can be used to determine the DST transition
122      * times across a number of years
123      *
124      * <p>In terms of {@link ZoneInfo tzfile} structure this array is of length {@code tzh_timecnt}
125      * and contains the times in seconds converted to long to make them safer to use.
126      *
127      * <p>They are stored in order from earliest (lowest) time to latest (highest). A transition is
128      * identified by its index within this array. A transition {@code T} is active at a specific
129      * time {@code X} if {@code T} is the highest transition whose time is less than or equal to
130      * {@code X}.
131      *
132      * @see #mTypes
133      */
134     private final long[] mTransitions;
135 
136     /**
137      * The type of the transition, where type is a pair consisting of the offset and whether the
138      * offset includes DST or not.
139      *
140      * <p>Each transition in {@link #mTransitions} has an associated type in this array at the same
141      * index. The type is an index into the arrays {@link #mOffsets} and {@link #mIsDsts} that each
142      * contain one part of the pair.
143      *
144      * <p>In the {@link ZoneInfo tzfile} structure the type array only contains unique instances of
145      * the {@code struct ttinfo} to save space and each type may be referenced by multiple
146      * transitions. However, the type pairs stored in this class are not guaranteed unique because
147      * they do not include the {@code tt_abbrind}, which is the abbreviated identifier to use for
148      * the time zone after the transition.
149      *
150      * @see #mTransitions
151      * @see #mOffsets
152      * @see #mIsDsts
153      */
154     private final byte[] mTypes;
155 
156     /**
157      * The offset parts of the transition types, in seconds.
158      *
159      * <p>These are actually a delta to the {@link #mRawOffset}. So, if the offset is say +7200
160      * seconds and {@link #mRawOffset} is say +3600 then this will have a value of +3600.
161      *
162      * <p>The offset in milliseconds can be computed using:
163      * {@code mRawOffset + mOffsets[type] * 1000}
164      *
165      * @see #mTypes
166      * @see #mIsDsts
167      */
168     private final int[] mOffsets;
169 
170     /**
171      * Specifies whether an associated offset includes DST or not.
172      *
173      * <p>Each entry in here is 1 if the offset at the same index in {@link #mOffsets} includes DST
174      * and 0 otherwise.
175      *
176      * @see #mTypes
177      * @see #mOffsets
178      */
179     private final byte[] mIsDsts;
180 
makeTimeZone(String id, BufferIterator it)181     public static ZoneInfo makeTimeZone(String id, BufferIterator it) {
182         return makeTimeZone(id, it, System.currentTimeMillis());
183     }
184 
185     /**
186      * Visible for testing.
187      */
makeTimeZone(String id, BufferIterator it, long currentTimeMillis)188     public static ZoneInfo makeTimeZone(String id, BufferIterator it, long currentTimeMillis) {
189         // Variable names beginning tzh_ correspond to those in "tzfile.h".
190 
191         // Check tzh_magic.
192         if (it.readInt() != 0x545a6966) { // "TZif"
193             return null;
194         }
195 
196         // Skip the uninteresting part of the header.
197         it.skip(28);
198 
199         // Read the sizes of the arrays we're about to read.
200         int tzh_timecnt = it.readInt();
201         int tzh_typecnt = it.readInt();
202         if (tzh_typecnt > 256) {
203             throw new IllegalStateException(id + " has more than 256 different types");
204         }
205 
206         it.skip(4); // Skip tzh_charcnt.
207 
208         // Transitions are signed 32 bit integers, but we store them as signed 64 bit
209         // integers since it's easier to compare them against 64 bit inputs (see getOffset
210         // and isDaylightTime) with much less risk of an overflow in our calculations.
211         //
212         // The alternative of checking the input against the first and last transition in
213         // the array is far more awkward and error prone.
214         int[] transitions32 = new int[tzh_timecnt];
215         it.readIntArray(transitions32, 0, transitions32.length);
216 
217         long[] transitions64 = new long[tzh_timecnt];
218         for (int i = 0; i < tzh_timecnt; ++i) {
219             transitions64[i] = transitions32[i];
220         }
221 
222         byte[] type = new byte[tzh_timecnt];
223         it.readByteArray(type, 0, type.length);
224 
225         int[] gmtOffsets = new int[tzh_typecnt];
226         byte[] isDsts = new byte[tzh_typecnt];
227         for (int i = 0; i < tzh_typecnt; ++i) {
228             gmtOffsets[i] = it.readInt();
229             byte b = it.readByte();
230             if (b != 0 && b != 1) {
231                 throw new IllegalStateException(id + " dst at " + i + " is not 0 or 1, is " + b);
232             }
233             isDsts[i] = b;
234             // We skip the abbreviation index. This would let us provide historically-accurate
235             // time zone abbreviations (such as "AHST", "YST", and "AKST" for standard time in
236             // America/Anchorage in 1982, 1983, and 1984 respectively). ICU only knows the current
237             // names, though, so even if we did use this data to provide the correct abbreviations
238             // for en_US, we wouldn't be able to provide correct abbreviations for other locales,
239             // nor would we be able to provide correct long forms (such as "Yukon Standard Time")
240             // for any locale. (The RI doesn't do any better than us here either.)
241             it.skip(1);
242         }
243 
244         return new ZoneInfo(id, transitions64, type, gmtOffsets, isDsts, currentTimeMillis);
245     }
246 
ZoneInfo(String name, long[] transitions, byte[] types, int[] gmtOffsets, byte[] isDsts, long currentTimeMillis)247     private ZoneInfo(String name, long[] transitions, byte[] types, int[] gmtOffsets, byte[] isDsts,
248             long currentTimeMillis) {
249         if (gmtOffsets.length == 0) {
250             throw new IllegalStateException("ZoneInfo requires at least one offset "
251                     + "to be provided for each timezone but could not find one for '" + name + "'");
252         }
253         mTransitions = transitions;
254         mTypes = types;
255         mIsDsts = isDsts;
256         setID(name);
257 
258         // Find the latest daylight and standard offsets (if any).
259         int lastStd = -1;
260         int lastDst = -1;
261         for (int i = mTransitions.length - 1; (lastStd == -1 || lastDst == -1) && i >= 0; --i) {
262             int type = mTypes[i] & 0xff;
263             if (lastStd == -1 && mIsDsts[type] == 0) {
264                 lastStd = i;
265             }
266             if (lastDst == -1 && mIsDsts[type] != 0) {
267                 lastDst = i;
268             }
269         }
270 
271         // Use the latest non-daylight offset (if any) as the raw offset.
272         if (mTransitions.length == 0) {
273             // If there are no transitions then use the first GMT offset.
274             mRawOffset = gmtOffsets[0];
275         } else {
276             if (lastStd == -1) {
277                 throw new IllegalStateException( "ZoneInfo requires at least one non-DST "
278                         + "transition to be provided for each timezone that has at least one "
279                         + "transition but could not find one for '" + name + "'");
280             }
281             mRawOffset = gmtOffsets[mTypes[lastStd] & 0xff];
282         }
283 
284         if (lastDst != -1) {
285             // Check to see if the last DST transition is in the future or the past. If it is in
286             // the past then we treat it as if it doesn't exist, at least for the purposes of
287             // setting mDstSavings and mUseDst.
288             long lastDSTTransitionTime = mTransitions[lastDst];
289 
290             // Convert the current time in millis into seconds. Unlike other places that convert
291             // time in milliseconds into seconds in order to compare with transition time this
292             // rounds up rather than down. It does that because this is interested in what
293             // transitions apply in future
294             long currentUnixTimeSeconds = roundUpMillisToSeconds(currentTimeMillis);
295 
296             // Is this zone observing DST currently or in the future?
297             // We don't care if they've historically used it: most places have at least once.
298             // See http://code.google.com/p/android/issues/detail?id=877.
299             // This test means that for somewhere like Morocco, which tried DST in 2009 but has
300             // no future plans (and thus no future schedule info) will report "true" from
301             // useDaylightTime at the start of 2009 but "false" at the end. This seems appropriate.
302             if (lastDSTTransitionTime < currentUnixTimeSeconds) {
303                 // The last DST transition is before now so treat it as if it doesn't exist.
304                 lastDst = -1;
305             }
306         }
307 
308         if (lastDst == -1) {
309             // There were no DST transitions or at least no future DST transitions so DST is not
310             // used.
311             mDstSavings = 0;
312             mUseDst = false;
313         } else {
314             // Use the latest transition's pair of offsets to compute the DST savings.
315             // This isn't generally useful, but it's exposed by TimeZone.getDSTSavings.
316             int lastGmtOffset = gmtOffsets[mTypes[lastStd] & 0xff];
317             int lastDstOffset = gmtOffsets[mTypes[lastDst] & 0xff];
318             mDstSavings = Math.abs(lastGmtOffset - lastDstOffset) * 1000;
319             mUseDst = true;
320         }
321 
322         // Cache the oldest known raw offset, in case we're asked about times that predate our
323         // transition data.
324         int firstStd = -1;
325         for (int i = 0; i < mTransitions.length; ++i) {
326             if (mIsDsts[mTypes[i] & 0xff] == 0) {
327                 firstStd = i;
328                 break;
329             }
330         }
331         int earliestRawOffset = (firstStd != -1) ? gmtOffsets[mTypes[firstStd] & 0xff] : mRawOffset;
332 
333         // Rather than keep offsets from UTC, we use offsets from local time, so the raw offset
334         // can be changed and automatically affect all the offsets.
335         mOffsets = gmtOffsets;
336         for (int i = 0; i < mOffsets.length; i++) {
337             mOffsets[i] -= mRawOffset;
338         }
339 
340         // tzdata uses seconds, but Java uses milliseconds.
341         mRawOffset *= 1000;
342         mEarliestRawOffset = earliestRawOffset * 1000;
343     }
344 
345     /**
346      * Ensure that when deserializing an instance that {@link #mDstSavings} is always 0 when
347      * {@link #mUseDst} is false.
348      */
readObject(ObjectInputStream in)349     private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
350         in.defaultReadObject();
351         if (!mUseDst && mDstSavings != 0) {
352             mDstSavings = 0;
353         }
354     }
355 
356     @Override
getOffset(int era, int year, int month, int day, int dayOfWeek, int millis)357     public int getOffset(int era, int year, int month, int day, int dayOfWeek, int millis) {
358         // XXX This assumes Gregorian always; Calendar switches from
359         // Julian to Gregorian in 1582.  What calendar system are the
360         // arguments supposed to come from?
361 
362         long calc = (year / 400) * MILLISECONDS_PER_400_YEARS;
363         year %= 400;
364 
365         calc += year * (365 * MILLISECONDS_PER_DAY);
366         calc += ((year + 3) / 4) * MILLISECONDS_PER_DAY;
367 
368         if (year > 0) {
369             calc -= ((year - 1) / 100) * MILLISECONDS_PER_DAY;
370         }
371 
372         boolean isLeap = (year == 0 || (year % 4 == 0 && year % 100 != 0));
373         int[] mlen = isLeap ? LEAP : NORMAL;
374 
375         calc += mlen[month] * MILLISECONDS_PER_DAY;
376         calc += (day - 1) * MILLISECONDS_PER_DAY;
377         calc += millis;
378 
379         calc -= mRawOffset;
380         calc -= UNIX_OFFSET;
381 
382         return getOffset(calc);
383     }
384 
385     /**
386      * Find the transition in the {@code timezone} in effect at {@code seconds}.
387      *
388      * <p>Returns an index in the range -1..timeZone.mTransitions.length - 1. -1 is used to
389      * indicate the time is before the first transition. Other values are an index into
390      * timeZone.mTransitions.
391      */
findTransitionIndex(long seconds)392     public int findTransitionIndex(long seconds) {
393         int transition = Arrays.binarySearch(mTransitions, seconds);
394         if (transition < 0) {
395             transition = ~transition - 1;
396             if (transition < 0) {
397                 return -1;
398             }
399         }
400 
401         return transition;
402     }
403 
404     /**
405      * Finds the index within the {@link #mOffsets}/{@link #mIsDsts} arrays for the specified time
406      * in seconds, since 1st Jan 1970 00:00:00.
407      * @param seconds the time in seconds.
408      * @return -1 if the time is before the first transition, or [0..{@code mOffsets}-1] for the
409      * active offset.
410      */
findOffsetIndexForTimeInSeconds(long seconds)411     int findOffsetIndexForTimeInSeconds(long seconds) {
412         int transition = findTransitionIndex(seconds);
413         if (transition < 0) {
414             return -1;
415         }
416 
417         return mTypes[transition] & 0xff;
418     }
419 
420     /**
421      * Finds the index within the {@link #mOffsets}/{@link #mIsDsts} arrays for the specified time
422      * in milliseconds, since 1st Jan 1970 00:00:00.000.
423      * @param millis the time in milliseconds.
424      * @return -1 if the time is before the first transition, or [0..{@code mOffsets}-1] for the
425      * active offset.
426      */
findOffsetIndexForTimeInMilliseconds(long millis)427     int findOffsetIndexForTimeInMilliseconds(long millis) {
428         // This rounds the time in milliseconds down to the time in seconds.
429         //
430         // It can't just divide a timestamp in millis by 1000 to obtain a transition time in
431         // seconds because / (div) in Java rounds towards zero. Times before 1970 are negative and
432         // if they have a millisecond component then div would result in obtaining a time that is
433         // one second after what we need.
434         //
435         // e.g. dividing -12,001 milliseconds by 1000 would result in -12 seconds. If there was a
436         //      transition at -12 seconds then that would be incorrectly treated as being active
437         //      for a time of -12,001 milliseconds even though that time is before the transition
438         //      should occur.
439 
440         return findOffsetIndexForTimeInSeconds(roundDownMillisToSeconds(millis));
441     }
442 
443     /**
444      * Converts time in milliseconds into a time in seconds, rounding down to the closest time
445      * in seconds before the time in milliseconds.
446      *
447      * <p>It's not sufficient to simply divide by 1000 because that rounds towards 0 and so while
448      * for positive numbers it produces a time in seconds that precedes the time in milliseconds
449      * for negative numbers it can produce a time in seconds that follows the time in milliseconds.
450      *
451      * <p>This basically does the same as {@code (long) Math.floor(millis / 1000.0)} but should be
452      * faster.
453      *
454      * @param millis the time in milliseconds, may be negative.
455      * @return the time in seconds.
456      */
roundDownMillisToSeconds(long millis)457     static long roundDownMillisToSeconds(long millis) {
458         if (millis < 0) {
459             // If the time is less than zero then subtract 999 and then divide by 1000 rounding
460             // towards 0 as usual, e.g.
461             // -12345 -> -13344 / 1000 = -13
462             // -12000 -> -12999 / 1000 = -12
463             // -12001 -> -13000 / 1000 = -13
464             return (millis - 999) / 1000;
465         } else {
466             return millis / 1000;
467         }
468     }
469 
470     /**
471      * Converts time in milliseconds into a time in seconds, rounding up to the closest time
472      * in seconds before the time in milliseconds.
473      *
474      * <p>It's not sufficient to simply divide by 1000 because that rounds towards 0 and so while
475      * for negative numbers it produces a time in seconds that follows the time in milliseconds
476      * for positive numbers it can produce a time in seconds that precedes the time in milliseconds.
477      *
478      * <p>This basically does the same as {@code (long) Math.ceil(millis / 1000.0)} but should be
479      * faster.
480      *
481      * @param millis the time in milliseconds, may be negative.
482      * @return the time in seconds.
483      */
roundUpMillisToSeconds(long millis)484     static long roundUpMillisToSeconds(long millis) {
485         if (millis > 0) {
486             // If the time is greater than zero then add 999 and then divide by 1000 rounding
487             // towards 0 as usual, e.g.
488             // 12345 -> 13344 / 1000 = 13
489             // 12000 -> 12999 / 1000 = 12
490             // 12001 -> 13000 / 1000 = 13
491             return (millis + 999) / 1000;
492         } else {
493             return millis / 1000;
494         }
495     }
496 
497     /**
498      * Get the raw and DST offsets for the specified time in milliseconds since
499      * 1st Jan 1970 00:00:00.000 UTC.
500      *
501      * <p>The raw offset, i.e. that part of the total offset which is not due to DST, is stored at
502      * index 0 of the {@code offsets} array and the DST offset, i.e. that part of the offset which
503      * is due to DST is stored at index 1.
504      *
505      * @param utcTimeInMillis the UTC time in milliseconds.
506      * @param offsets the array whose length must be greater than or equal to 2.
507      * @return the total offset which is the sum of the raw and DST offsets.
508      */
getOffsetsByUtcTime(long utcTimeInMillis, int[] offsets)509     public int getOffsetsByUtcTime(long utcTimeInMillis, int[] offsets) {
510         int transitionIndex = findTransitionIndex(roundDownMillisToSeconds(utcTimeInMillis));
511         int totalOffset;
512         int rawOffset;
513         int dstOffset;
514         if (transitionIndex == -1) {
515             // See getOffset(long) and inDaylightTime(Date) for an explanation as to why these
516             // values are used for times before the first transition.
517             rawOffset = mEarliestRawOffset;
518             dstOffset = 0;
519             totalOffset = rawOffset;
520         } else {
521             int type = mTypes[transitionIndex] & 0xff;
522 
523             // Get the total offset used for the transition.
524             totalOffset = mRawOffset + mOffsets[type] * 1000;
525             if (mIsDsts[type] == 0) {
526                 // Offset does not include DST so DST is 0 and the raw offset is the total offset.
527                 rawOffset = totalOffset;
528                 dstOffset = 0;
529             } else {
530                 // Offset does include DST, we need to find the preceding transition that did not
531                 // include the DST offset so that we can calculate the DST offset.
532                 rawOffset = -1;
533                 for (transitionIndex -= 1; transitionIndex >= 0; --transitionIndex) {
534                     type = mTypes[transitionIndex] & 0xff;
535                     if (mIsDsts[type] == 0) {
536                         rawOffset = mRawOffset + mOffsets[type] * 1000;
537                         break;
538                     }
539                 }
540                 // If no previous transition was found then use the earliest raw offset.
541                 if (rawOffset == -1) {
542                     rawOffset = mEarliestRawOffset;
543                 }
544 
545                 // The DST offset is the difference between the total and the raw offset.
546                 dstOffset = totalOffset - rawOffset;
547             }
548         }
549 
550         offsets[0] = rawOffset;
551         offsets[1] = dstOffset;
552 
553         return totalOffset;
554     }
555 
556     @Override
getOffset(long when)557     public int getOffset(long when) {
558         int offsetIndex = findOffsetIndexForTimeInMilliseconds(when);
559         if (offsetIndex == -1) {
560             // Assume that all times before our first transition correspond to the
561             // oldest-known non-daylight offset. The obvious alternative would be to
562             // use the current raw offset, but that seems like a greater leap of faith.
563             return mEarliestRawOffset;
564         }
565         return mRawOffset + mOffsets[offsetIndex] * 1000;
566     }
567 
inDaylightTime(Date time)568     @Override public boolean inDaylightTime(Date time) {
569         long when = time.getTime();
570         int offsetIndex = findOffsetIndexForTimeInMilliseconds(when);
571         if (offsetIndex == -1) {
572             // Assume that all times before our first transition are non-daylight.
573             // Transition data tends to start with a transition to daylight, so just
574             // copying the first transition would assume the opposite.
575             // http://code.google.com/p/android/issues/detail?id=14395
576             return false;
577         }
578         return mIsDsts[offsetIndex] == 1;
579     }
580 
getRawOffset()581     @Override public int getRawOffset() {
582         return mRawOffset;
583     }
584 
setRawOffset(int off)585     @Override public void setRawOffset(int off) {
586         mRawOffset = off;
587     }
588 
getDSTSavings()589     @Override public int getDSTSavings() {
590         return mDstSavings;
591     }
592 
useDaylightTime()593     @Override public boolean useDaylightTime() {
594         return mUseDst;
595     }
596 
hasSameRules(TimeZone timeZone)597     @Override public boolean hasSameRules(TimeZone timeZone) {
598         if (!(timeZone instanceof ZoneInfo)) {
599             return false;
600         }
601         ZoneInfo other = (ZoneInfo) timeZone;
602         if (mUseDst != other.mUseDst) {
603             return false;
604         }
605         if (!mUseDst) {
606             return mRawOffset == other.mRawOffset;
607         }
608         return mRawOffset == other.mRawOffset
609                 // Arrays.equals returns true if both arrays are null
610                 && Arrays.equals(mOffsets, other.mOffsets)
611                 && Arrays.equals(mIsDsts, other.mIsDsts)
612                 && Arrays.equals(mTypes, other.mTypes)
613                 && Arrays.equals(mTransitions, other.mTransitions);
614     }
615 
equals(Object obj)616     @Override public boolean equals(Object obj) {
617         if (!(obj instanceof ZoneInfo)) {
618             return false;
619         }
620         ZoneInfo other = (ZoneInfo) obj;
621         return getID().equals(other.getID()) && hasSameRules(other);
622     }
623 
624     @Override
hashCode()625     public int hashCode() {
626         final int prime = 31;
627         int result = 1;
628         result = prime * result + getID().hashCode();
629         result = prime * result + Arrays.hashCode(mOffsets);
630         result = prime * result + Arrays.hashCode(mIsDsts);
631         result = prime * result + mRawOffset;
632         result = prime * result + Arrays.hashCode(mTransitions);
633         result = prime * result + Arrays.hashCode(mTypes);
634         result = prime * result + (mUseDst ? 1231 : 1237);
635         return result;
636     }
637 
638     @Override
toString()639     public String toString() {
640         return getClass().getName() + "[id=\"" + getID() + "\"" +
641             ",mRawOffset=" + mRawOffset +
642             ",mEarliestRawOffset=" + mEarliestRawOffset +
643             ",mUseDst=" + mUseDst +
644             ",mDstSavings=" + mDstSavings +
645             ",transitions=" + mTransitions.length +
646             "]";
647     }
648 
649     @Override
clone()650     public Object clone() {
651         // Overridden for documentation. The default clone() behavior is exactly what we want.
652         // Though mutable, the arrays of offset data are treated as immutable. Only ID and
653         // mRawOffset are mutable in this class, and those are an immutable object and a primitive
654         // respectively.
655         return super.clone();
656     }
657 
658     /**
659      * A class that represents a "wall time". This class is modeled on the C tm struct and
660      * is used to support android.text.format.Time behavior. Unlike the tm struct the year is
661      * represented as the full year, not the years since 1900.
662      *
663      * <p>This class contains a rewrite of various native functions that android.text.format.Time
664      * once relied on such as mktime_tz and localtime_tz. This replacement does not support leap
665      * seconds but does try to preserve behavior around ambiguous date/times found in the BSD
666      * version of mktime that was previously used.
667      *
668      * <p>The original native code used a 32-bit value for time_t on 32-bit Android, which
669      * was the only variant of Android available at the time. To preserve old behavior this code
670      * deliberately uses {@code int} rather than {@code long} for most things and performs
671      * calculations in seconds. This creates deliberate truncation issues for date / times before
672      * 1901 and after 2038. This is intentional but might be fixed in future if all the knock-ons
673      * can be resolved: Application code may have come to rely on the range so previously values
674      * like zero for year could indicate an invalid date but if we move to long the year zero would
675      * be valid.
676      *
677      * <p>All offsets are considered to be safe for addition / subtraction / multiplication without
678      * worrying about overflow. All absolute time arithmetic is checked for overflow / underflow.
679      */
680     public static class WallTime {
681 
682         // We use a GregorianCalendar (set to UTC) to handle all the date/time normalization logic
683         // and to convert from a broken-down date/time to a millis value.
684         // Unfortunately, it cannot represent an initial state with a zero day and would
685         // automatically normalize it, so we must copy values into and out of it as needed.
686         private final GregorianCalendar calendar;
687 
688         private int year;
689         private int month;
690         private int monthDay;
691         private int hour;
692         private int minute;
693         private int second;
694         private int weekDay;
695         private int yearDay;
696         private int isDst;
697         private int gmtOffsetSeconds;
698 
WallTime()699         public WallTime() {
700             this.calendar = new GregorianCalendar(0, 0, 0, 0, 0, 0);
701             calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
702         }
703 
704         /**
705          * Sets the wall time to a point in time using the time zone information provided. This
706          * is a replacement for the old native localtime_tz() function.
707          *
708          * <p>When going from an instant to a wall time it is always unambiguous because there
709          * is only one offset rule acting at any given instant. We do not consider leap seconds.
710          */
localtime(int timeSeconds, ZoneInfo zoneInfo)711         public void localtime(int timeSeconds, ZoneInfo zoneInfo) {
712             try {
713                 int offsetSeconds = zoneInfo.mRawOffset / 1000;
714 
715                 // Find out the timezone DST state and adjustment.
716                 byte isDst;
717                 if (zoneInfo.mTransitions.length == 0) {
718                     isDst = 0;
719                 } else {
720                     // offsetIndex can be in the range -1..zoneInfo.mOffsets.length - 1
721                     int offsetIndex = zoneInfo.findOffsetIndexForTimeInSeconds(timeSeconds);
722                     if (offsetIndex == -1) {
723                         // -1 means timeSeconds is "before the first recorded transition". The first
724                         // recorded transition is treated as a transition from non-DST and the raw
725                         // offset.
726                         isDst = 0;
727                     } else {
728                         offsetSeconds += zoneInfo.mOffsets[offsetIndex];
729                         isDst = zoneInfo.mIsDsts[offsetIndex];
730                     }
731                 }
732 
733                 // Perform arithmetic that might underflow before setting fields.
734                 int wallTimeSeconds = checkedAdd(timeSeconds, offsetSeconds);
735 
736                 // Set fields.
737                 calendar.setTimeInMillis(wallTimeSeconds * 1000L);
738                 copyFieldsFromCalendar();
739                 this.isDst = isDst;
740                 this.gmtOffsetSeconds = offsetSeconds;
741             } catch (CheckedArithmeticException e) {
742                 // Just stop, leaving fields untouched.
743             }
744         }
745 
746         /**
747          * Returns the time in seconds since beginning of the Unix epoch for the wall time using the
748          * time zone information provided. This is a replacement for an old native mktime_tz() C
749          * function.
750          *
751          * <p>When going from a wall time to an instant the answer can be ambiguous. A wall
752          * time can map to zero, one or two instants given sane date/time transitions. Sane
753          * in this case means that transitions occur less frequently than the offset
754          * differences between them (which could cause all sorts of craziness like the
755          * skipping out of transitions).
756          *
757          * <p>For example, this is not fully supported:
758          * <ul>
759          *     <li>t1 { time = 1, offset = 0 }
760          *     <li>t2 { time = 2, offset = -1 }
761          *     <li>t3 { time = 3, offset = -2 }
762          * </ul>
763          * A wall time in this case might map to t1, t2 or t3.
764          *
765          * <p>We do not handle leap seconds.
766          * <p>We assume that no timezone offset transition has an absolute offset > 24 hours.
767          * <p>We do not assume that adjacent transitions modify the DST state; adjustments can
768          * occur for other reasons such as when a zone changes its raw offset.
769          */
mktime(ZoneInfo zoneInfo)770         public int mktime(ZoneInfo zoneInfo) {
771             // Normalize isDst to -1, 0 or 1 to simplify isDst equality checks below.
772             this.isDst = this.isDst > 0 ? this.isDst = 1 : this.isDst < 0 ? this.isDst = -1 : 0;
773 
774             copyFieldsToCalendar();
775             final long longWallTimeSeconds = calendar.getTimeInMillis() / 1000;
776             if (Integer.MIN_VALUE > longWallTimeSeconds
777                     || longWallTimeSeconds > Integer.MAX_VALUE) {
778                 // For compatibility with the old native 32-bit implementation we must treat
779                 // this as an error. Note: -1 could be confused with a real time.
780                 return -1;
781             }
782 
783             try {
784                 final int wallTimeSeconds =  (int) longWallTimeSeconds;
785                 final int rawOffsetSeconds = zoneInfo.mRawOffset / 1000;
786                 final int rawTimeSeconds = checkedSubtract(wallTimeSeconds, rawOffsetSeconds);
787 
788                 if (zoneInfo.mTransitions.length == 0) {
789                     // There is no transition information. There is just a raw offset for all time.
790                     if (this.isDst > 0) {
791                         // Caller has asserted DST, but there is no DST information available.
792                         return -1;
793                     }
794                     copyFieldsFromCalendar();
795                     this.isDst = 0;
796                     this.gmtOffsetSeconds = rawOffsetSeconds;
797                     return rawTimeSeconds;
798                 }
799 
800                 // We cannot know for sure what instant the wall time will map to. Unfortunately, in
801                 // order to know for sure we need the timezone information, but to get the timezone
802                 // information we need an instant. To resolve this we use the raw offset to find an
803                 // OffsetInterval; this will get us the OffsetInterval we need or very close.
804 
805                 // The initialTransition can be between -1 and (zoneInfo.mTransitions - 1). -1
806                 // indicates the rawTime is before the first transition and is handled gracefully by
807                 // createOffsetInterval().
808                 final int initialTransitionIndex = zoneInfo.findTransitionIndex(rawTimeSeconds);
809 
810                 if (isDst < 0) {
811                     // This is treated as a special case to get it out of the way:
812                     // When a caller has set isDst == -1 it means we can return the first match for
813                     // the wall time we find. If the caller has specified a wall time that cannot
814                     // exist this always returns -1.
815 
816                     Integer result = doWallTimeSearch(zoneInfo, initialTransitionIndex,
817                             wallTimeSeconds, true /* mustMatchDst */);
818                     return result == null ? -1 : result;
819                 }
820 
821                 // If the wall time asserts a DST (isDst == 0 or 1) the search is performed twice:
822                 // 1) The first attempts to find a DST offset that matches isDst exactly.
823                 // 2) If it fails, isDst is assumed to be incorrect and adjustments are made to see
824                 // if a valid wall time can be created. The result can be somewhat arbitrary.
825 
826                 Integer result = doWallTimeSearch(zoneInfo, initialTransitionIndex, wallTimeSeconds,
827                         true /* mustMatchDst */);
828                 if (result == null) {
829                     result = doWallTimeSearch(zoneInfo, initialTransitionIndex, wallTimeSeconds,
830                             false /* mustMatchDst */);
831                 }
832                 if (result == null) {
833                     result = -1;
834                 }
835                 return result;
836             } catch (CheckedArithmeticException e) {
837                 return -1;
838             }
839         }
840 
841         /**
842          * Attempt to apply DST adjustments to {@code oldWallTimeSeconds} to create a wall time in
843          * {@code targetInterval}.
844          *
845          * <p>This is used when a caller has made an assertion about standard time / DST that cannot
846          * be matched to any offset interval that exists. We must therefore assume that the isDst
847          * assertion is incorrect and the invalid wall time is the result of some modification the
848          * caller made to a valid wall time that pushed them outside of the offset interval they
849          * were in. We must correct for any DST change that should have been applied when they did
850          * so.
851          *
852          * <p>Unfortunately, we have no information about what adjustment they made and so cannot
853          * know which offset interval they were previously in. For example, they may have added a
854          * second or a year to a valid time to arrive at what they have.
855          *
856          * <p>We try all offset types that are not the same as the isDst the caller asserted. For
857          * each possible offset we work out the offset difference between that and
858          * {@code targetInterval}, apply it, and see if we are still in {@code targetInterval}. If
859          * we are, then we have found an adjustment.
860          */
861         private Integer tryOffsetAdjustments(ZoneInfo zoneInfo, int oldWallTimeSeconds,
862                 OffsetInterval targetInterval, int transitionIndex, int isDstToFind)
863                 throws CheckedArithmeticException {
864 
865             int[] offsetsToTry = getOffsetsOfType(zoneInfo, transitionIndex, isDstToFind);
866             for (int j = 0; j < offsetsToTry.length; j++) {
867                 int rawOffsetSeconds = zoneInfo.mRawOffset / 1000;
868                 int jOffsetSeconds = rawOffsetSeconds + offsetsToTry[j];
869                 int targetIntervalOffsetSeconds = targetInterval.getTotalOffsetSeconds();
870                 int adjustmentSeconds = targetIntervalOffsetSeconds - jOffsetSeconds;
871                 int adjustedWallTimeSeconds = checkedAdd(oldWallTimeSeconds, adjustmentSeconds);
872                 if (targetInterval.containsWallTime(adjustedWallTimeSeconds)) {
873                     // Perform any arithmetic that might overflow.
874                     int returnValue = checkedSubtract(adjustedWallTimeSeconds,
875                             targetIntervalOffsetSeconds);
876 
877                     // Modify field state and return the result.
878                     calendar.setTimeInMillis(adjustedWallTimeSeconds * 1000L);
879                     copyFieldsFromCalendar();
880                     this.isDst = targetInterval.getIsDst();
881                     this.gmtOffsetSeconds = targetIntervalOffsetSeconds;
882                     return returnValue;
883                 }
884             }
885             return null;
886         }
887 
888         /**
889          * Return an array of offsets that have the requested {@code isDst} value.
890          * The {@code startIndex} is used as a starting point so transitions nearest
891          * to that index are returned first.
892          */
893         private static int[] getOffsetsOfType(ZoneInfo zoneInfo, int startIndex, int isDst) {
894             // +1 to account for the synthetic transition we invent before the first recorded one.
895             int[] offsets = new int[zoneInfo.mOffsets.length + 1];
896             boolean[] seen = new boolean[zoneInfo.mOffsets.length];
897             int numFound = 0;
898 
899             int delta = 0;
900             boolean clampTop = false;
901             boolean clampBottom = false;
902             do {
903                 // delta = { 1, -1, 2, -2, 3, -3...}
904                 delta *= -1;
905                 if (delta >= 0) {
906                     delta++;
907                 }
908 
909                 int transitionIndex = startIndex + delta;
910                 if (delta < 0 && transitionIndex < -1) {
911                     clampBottom = true;
912                     continue;
913                 } else if (delta > 0 && transitionIndex >=  zoneInfo.mTypes.length) {
914                     clampTop = true;
915                     continue;
916                 }
917 
918                 if (transitionIndex == -1) {
919                     if (isDst == 0) {
920                         // Synthesize a non-DST transition before the first transition we have
921                         // data for.
922                         offsets[numFound++] = 0; // offset of 0 from raw offset
923                     }
924                     continue;
925                 }
926                 int type = zoneInfo.mTypes[transitionIndex] & 0xff;
927                 if (!seen[type]) {
928                     if (zoneInfo.mIsDsts[type] == isDst) {
929                         offsets[numFound++] = zoneInfo.mOffsets[type];
930                     }
931                     seen[type] = true;
932                 }
933             } while (!(clampTop && clampBottom));
934 
935             int[] toReturn = new int[numFound];
936             System.arraycopy(offsets, 0, toReturn, 0, numFound);
937             return toReturn;
938         }
939 
940         /**
941          * Find a time <em>in seconds</em> the same or close to {@code wallTimeSeconds} that
942          * satisfies {@code mustMatchDst}. The search begins around the timezone offset transition
943          * with {@code initialTransitionIndex}.
944          *
945          * <p>If {@code mustMatchDst} is {@code true} the method can only return times that
946          * use timezone offsets that satisfy the {@code this.isDst} requirements.
947          * If {@code this.isDst == -1} it means that any offset can be used.
948          *
949          * <p>If {@code mustMatchDst} is {@code false} any offset that covers the
950          * currently set time is acceptable. That is: if {@code this.isDst} == -1, any offset
951          * transition can be used, if it is 0 or 1 the offset used must match {@code this.isDst}.
952          *
953          * <p>Note: This method both uses and can modify field state. It returns the matching time
954          * in seconds if a match has been found and modifies fields, or it returns {@code null} and
955          * leaves the field state unmodified.
956          */
957         private Integer doWallTimeSearch(ZoneInfo zoneInfo, int initialTransitionIndex,
958                 int wallTimeSeconds, boolean mustMatchDst) throws CheckedArithmeticException {
959 
960             // The loop below starts at the initialTransitionIndex and radiates out from that point
961             // up to 24 hours in either direction by applying transitionIndexDelta to inspect
962             // adjacent transitions (0, -1, +1, -2, +2). 24 hours is used because we assume that no
963             // total offset from UTC is ever > 24 hours. clampTop and clampBottom are used to
964             // indicate whether the search has either searched > 24 hours or exhausted the
965             // transition data in that direction. The search stops when a match is found or if
966             // clampTop and clampBottom are both true.
967             // The match logic employed is determined by the mustMatchDst parameter.
968             final int MAX_SEARCH_SECONDS = 24 * 60 * 60;
969             boolean clampTop = false, clampBottom = false;
970             int loop = 0;
971             do {
972                 // transitionIndexDelta = { 0, -1, 1, -2, 2,..}
973                 int transitionIndexDelta = (loop + 1) / 2;
974                 if (loop % 2 == 1) {
975                     transitionIndexDelta *= -1;
976                 }
977                 loop++;
978 
979                 // Only do any work in this iteration if we need to.
980                 if (transitionIndexDelta > 0 && clampTop
981                         || transitionIndexDelta < 0 && clampBottom) {
982                     continue;
983                 }
984 
985                 // Obtain the OffsetInterval to use.
986                 int currentTransitionIndex = initialTransitionIndex + transitionIndexDelta;
987                 OffsetInterval offsetInterval =
988                         OffsetInterval.create(zoneInfo, currentTransitionIndex);
989                 if (offsetInterval == null) {
990                     // No transition exists with the index we tried: Stop searching in the
991                     // current direction.
992                     clampTop |= (transitionIndexDelta > 0);
993                     clampBottom |= (transitionIndexDelta < 0);
994                     continue;
995                 }
996 
997                 // Match the wallTimeSeconds against the OffsetInterval.
998                 if (mustMatchDst) {
999                     // Work out if the interval contains the wall time the caller specified and
1000                     // matches their isDst value.
1001                     if (offsetInterval.containsWallTime(wallTimeSeconds)) {
1002                         if (this.isDst == -1 || offsetInterval.getIsDst() == this.isDst) {
1003                             // This always returns the first OffsetInterval it finds that matches
1004                             // the wall time and isDst requirements. If this.isDst == -1 this means
1005                             // the result might be a DST or a non-DST answer for wall times that can
1006                             // exist in two OffsetIntervals.
1007                             int totalOffsetSeconds = offsetInterval.getTotalOffsetSeconds();
1008                             int returnValue = checkedSubtract(wallTimeSeconds,
1009                                     totalOffsetSeconds);
1010 
1011                             copyFieldsFromCalendar();
1012                             this.isDst = offsetInterval.getIsDst();
1013                             this.gmtOffsetSeconds = totalOffsetSeconds;
1014                             return returnValue;
1015                         }
1016                     }
1017                 } else {
1018                     // To retain similar behavior to the old native implementation: if the caller is
1019                     // asserting the same isDst value as the OffsetInterval we are looking at we do
1020                     // not try to find an adjustment from another OffsetInterval of the same isDst
1021                     // type. If you remove this you get different results in situations like a
1022                     // DST -> DST transition or STD -> STD transition that results in an interval of
1023                     // "skipped" wall time. For example: if 01:30 (DST) is invalid and between two
1024                     // DST intervals, and the caller has passed isDst == 1, this results in a -1
1025                     // being returned.
1026                     if (isDst != offsetInterval.getIsDst()) {
1027                         final int isDstToFind = isDst;
1028                         Integer returnValue = tryOffsetAdjustments(zoneInfo, wallTimeSeconds,
1029                                 offsetInterval, currentTransitionIndex, isDstToFind);
1030                         if (returnValue != null) {
1031                             return returnValue;
1032                         }
1033                     }
1034                 }
1035 
1036                 // See if we can avoid another loop in the current direction.
1037                 if (transitionIndexDelta > 0) {
1038                     // If we are searching forward and the OffsetInterval we have ends
1039                     // > MAX_SEARCH_SECONDS after the wall time, we don't need to look any further
1040                     // forward.
1041                     boolean endSearch = offsetInterval.getEndWallTimeSeconds() - wallTimeSeconds
1042                             > MAX_SEARCH_SECONDS;
1043                     if (endSearch) {
1044                         clampTop = true;
1045                     }
1046                 } else if (transitionIndexDelta < 0) {
1047                     boolean endSearch = wallTimeSeconds - offsetInterval.getStartWallTimeSeconds()
1048                             >= MAX_SEARCH_SECONDS;
1049                     if (endSearch) {
1050                         // If we are searching backward and the OffsetInterval starts
1051                         // > MAX_SEARCH_SECONDS before the wall time, we don't need to look any
1052                         // further backwards.
1053                         clampBottom = true;
1054                     }
1055                 }
1056             } while (!(clampTop && clampBottom));
1057             return null;
1058         }
1059 
1060         public void setYear(int year) {
1061             this.year = year;
1062         }
1063 
1064         public void setMonth(int month) {
1065             this.month = month;
1066         }
1067 
1068         public void setMonthDay(int monthDay) {
1069             this.monthDay = monthDay;
1070         }
1071 
1072         public void setHour(int hour) {
1073             this.hour = hour;
1074         }
1075 
1076         public void setMinute(int minute) {
1077             this.minute = minute;
1078         }
1079 
1080         public void setSecond(int second) {
1081             this.second = second;
1082         }
1083 
1084         public void setWeekDay(int weekDay) {
1085             this.weekDay = weekDay;
1086         }
1087 
1088         public void setYearDay(int yearDay) {
1089             this.yearDay = yearDay;
1090         }
1091 
1092         public void setIsDst(int isDst) {
1093             this.isDst = isDst;
1094         }
1095 
1096         public void setGmtOffset(int gmtoff) {
1097             this.gmtOffsetSeconds = gmtoff;
1098         }
1099 
1100         public int getYear() {
1101             return year;
1102         }
1103 
1104         public int getMonth() {
1105             return month;
1106         }
1107 
1108         public int getMonthDay() {
1109             return monthDay;
1110         }
1111 
1112         public int getHour() {
1113             return hour;
1114         }
1115 
1116         public int getMinute() {
1117             return minute;
1118         }
1119 
1120         public int getSecond() {
1121             return second;
1122         }
1123 
1124         public int getWeekDay() {
1125             return weekDay;
1126         }
1127 
1128         public int getYearDay() {
1129             return yearDay;
1130         }
1131 
1132         public int getGmtOffset() {
1133             return gmtOffsetSeconds;
1134         }
1135 
1136         public int getIsDst() {
1137             return isDst;
1138         }
1139 
1140         private void copyFieldsToCalendar() {
1141             calendar.set(Calendar.YEAR, year);
1142             calendar.set(Calendar.MONTH, month);
1143             calendar.set(Calendar.DAY_OF_MONTH, monthDay);
1144             calendar.set(Calendar.HOUR_OF_DAY, hour);
1145             calendar.set(Calendar.MINUTE, minute);
1146             calendar.set(Calendar.SECOND, second);
1147             calendar.set(Calendar.MILLISECOND, 0);
1148         }
1149 
1150         private void copyFieldsFromCalendar() {
1151             year = calendar.get(Calendar.YEAR);
1152             month = calendar.get(Calendar.MONTH);
1153             monthDay = calendar.get(Calendar.DAY_OF_MONTH);
1154             hour = calendar.get(Calendar.HOUR_OF_DAY);
1155             minute = calendar.get(Calendar.MINUTE);
1156             second =  calendar.get(Calendar.SECOND);
1157 
1158             // Calendar uses Sunday == 1. Android Time uses Sunday = 0.
1159             weekDay = calendar.get(Calendar.DAY_OF_WEEK) - 1;
1160             // Calendar enumerates from 1, Android Time enumerates from 0.
1161             yearDay = calendar.get(Calendar.DAY_OF_YEAR) - 1;
1162         }
1163     }
1164 
1165     /**
1166      * A wall-time representation of a timezone offset interval.
1167      *
1168      * <p>Wall-time means "as it would appear locally in the timezone in which it applies".
1169      * For example in 2007:
1170      * PST was a -8:00 offset that ran until Mar 11, 2:00 AM.
1171      * PDT was a -7:00 offset and ran from Mar 11, 3:00 AM to Nov 4, 2:00 AM.
1172      * PST was a -8:00 offset and ran from Nov 4, 1:00 AM.
1173      * Crucially this means that there was a "gap" after PST when PDT started, and an overlap when
1174      * PDT ended and PST began.
1175      *
1176      * <p>For convenience all wall-time values are represented as the number of seconds since the
1177      * beginning of the Unix epoch <em>in UTC</em>. To convert from a wall-time to the actual time
1178      * in the offset it is necessary to <em>subtract</em> the {@code totalOffsetSeconds}.
1179      * For example: If the offset in PST is -07:00 hours, then:
1180      * timeInPstSeconds = wallTimeUtcSeconds - offsetSeconds
1181      * i.e. 13:00 UTC - (-07:00) = 20:00 UTC = 13:00 PST
1182      */
1183     static class OffsetInterval {
1184 
1185         private final int startWallTimeSeconds;
1186         private final int endWallTimeSeconds;
1187         private final int isDst;
1188         private final int totalOffsetSeconds;
1189 
1190         /**
1191          * Creates an {@link OffsetInterval}.
1192          *
1193          * <p>If {@code transitionIndex} is -1, the transition is synthesized to be a non-DST offset
1194          * that runs from the beginning of time until the first transition in {@code timeZone} and
1195          * has an offset of {@code timezone.mRawOffset}. If {@code transitionIndex} is the last
1196          * transition that transition is considered to run until the end of representable time.
1197          * Otherwise, the information is extracted from {@code timeZone.mTransitions},
1198          * {@code timeZone.mOffsets} an {@code timeZone.mIsDsts}.
1199          */
1200         public static OffsetInterval create(ZoneInfo timeZone, int transitionIndex)
1201                 throws CheckedArithmeticException {
1202 
1203             if (transitionIndex < -1 || transitionIndex >= timeZone.mTransitions.length) {
1204                 return null;
1205             }
1206 
1207             int rawOffsetSeconds = timeZone.mRawOffset / 1000;
1208             if (transitionIndex == -1) {
1209                 int endWallTimeSeconds = checkedAdd(timeZone.mTransitions[0], rawOffsetSeconds);
1210                 return new OffsetInterval(Integer.MIN_VALUE, endWallTimeSeconds, 0 /* isDst */,
1211                         rawOffsetSeconds);
1212             }
1213 
1214             int type = timeZone.mTypes[transitionIndex] & 0xff;
1215             int totalOffsetSeconds = timeZone.mOffsets[type] + rawOffsetSeconds;
1216             int endWallTimeSeconds;
1217             if (transitionIndex == timeZone.mTransitions.length - 1) {
1218                 // If this is the last transition, make up the end time.
1219                 endWallTimeSeconds = Integer.MAX_VALUE;
1220             } else {
1221                 endWallTimeSeconds = checkedAdd(timeZone.mTransitions[transitionIndex + 1],
1222                         totalOffsetSeconds);
1223             }
1224             int isDst = timeZone.mIsDsts[type];
1225             int startWallTimeSeconds =
1226                     checkedAdd(timeZone.mTransitions[transitionIndex], totalOffsetSeconds);
1227             return new OffsetInterval(
1228                     startWallTimeSeconds, endWallTimeSeconds, isDst, totalOffsetSeconds);
1229         }
1230 
1231         private OffsetInterval(int startWallTimeSeconds, int endWallTimeSeconds, int isDst,
1232                 int totalOffsetSeconds) {
1233             this.startWallTimeSeconds = startWallTimeSeconds;
1234             this.endWallTimeSeconds = endWallTimeSeconds;
1235             this.isDst = isDst;
1236             this.totalOffsetSeconds = totalOffsetSeconds;
1237         }
1238 
1239         public boolean containsWallTime(long wallTimeSeconds) {
1240             return wallTimeSeconds >= startWallTimeSeconds && wallTimeSeconds < endWallTimeSeconds;
1241         }
1242 
1243         public int getIsDst() {
1244             return isDst;
1245         }
1246 
1247         public int getTotalOffsetSeconds() {
1248             return totalOffsetSeconds;
1249         }
1250 
1251         public long getEndWallTimeSeconds() {
1252             return endWallTimeSeconds;
1253         }
1254 
1255         public long getStartWallTimeSeconds() {
1256             return startWallTimeSeconds;
1257         }
1258     }
1259 
1260     /**
1261      * An exception used to indicate an arithmetic overflow or underflow.
1262      */
1263     private static class CheckedArithmeticException extends Exception {
1264     }
1265 
1266     /**
1267      * Calculate (a + b).
1268      *
1269      * @throws CheckedArithmeticException if overflow or underflow occurs
1270      */
1271     private static int checkedAdd(long a, int b) throws CheckedArithmeticException {
1272         // Adapted from Guava IntMath.checkedAdd();
1273         long result = a + b;
1274         if (result != (int) result) {
1275             throw new CheckedArithmeticException();
1276         }
1277         return (int) result;
1278     }
1279 
1280     /**
1281      * Calculate (a - b).
1282      *
1283      * @throws CheckedArithmeticException if overflow or underflow occurs
1284      */
1285     private static int checkedSubtract(int a, int b) throws CheckedArithmeticException {
1286         // Adapted from Guava IntMath.checkedSubtract();
1287         long result = (long) a - b;
1288         if (result != (int) result) {
1289             throw new CheckedArithmeticException();
1290         }
1291         return (int) result;
1292     }
1293 }
1294