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