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