ZoneInfo.java revision 3af4662f6f624863e2cf72cfb8ab56b70a1e0355
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 */
23package libcore.util;
24
25import java.io.IOException;
26import java.io.ObjectInputStream;
27import java.util.Arrays;
28import java.util.Calendar;
29import java.util.Date;
30import java.util.GregorianCalendar;
31import java.util.TimeZone;
32import 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 */
65public 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
181    public static ZoneInfo makeTimeZone(String id, BufferIterator it) {
182        return makeTimeZone(id, it, System.currentTimeMillis());
183    }
184
185    /**
186     * Visible for testing.
187     */
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
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     */
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
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     */
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     */
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     */
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     */
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     */
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     */
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
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
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
581    @Override public int getRawOffset() {
582        return mRawOffset;
583    }
584
585    @Override public void setRawOffset(int off) {
586        mRawOffset = off;
587    }
588
589    @Override public int getDSTSavings() {
590        return mDstSavings;
591    }
592
593    @Override public boolean useDaylightTime() {
594        return mUseDst;
595    }
596
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
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
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
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
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
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         */
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         */
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