• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.i18n.timezone;
17 
18 import com.android.i18n.timezone.internal.BufferIterator;
19 import com.android.i18n.timezone.internal.ByteBufferIterator;
20 
21 import libcore.util.NonNull;
22 import libcore.util.Nullable;
23 
24 import java.io.IOException;
25 import java.io.ObjectInputStream.GetField;
26 import java.io.ObjectOutputStream.PutField;
27 import java.io.ObjectStreamField;
28 import java.nio.ByteBuffer;
29 import java.util.Arrays;
30 
31 /**
32  * This class holds the data of a time zone backed by the tzfiles. An instance is immutable.
33  *
34  * <p>This reads time zone information from a binary file stored on the platform. The binary file
35  * is essentially a single file containing compacted versions of all the tzfiles produced by the
36  * zone info compiler (zic) tool (see {@code man 5 tzfile} for details of the format and
37  * {@code man 8 zic}) and an index by long name, e.g. Europe/London.
38  *
39  * <p>The compacted form is created by
40  * {@code system/timezone/input_tools/android/zone_compactor/main/java/ZoneCompactor.java} and is
41  * used by both this and Bionic. {@link ZoneInfoDb} is responsible for mapping the binary file, and
42  * reading the index and creating a {@link BufferIterator} that provides access to an entry for a
43  * specific file. This class is responsible for reading the data from that {@link BufferIterator}
44  * and storing it a representation to support the {@link java.util.TimeZone} and
45  * {@link java.util.GregorianCalendar} implementations. See
46  * {@link ZoneInfoData#readTimeZone(String, BufferIterator)}.
47  *
48  * <p>This class does not use all the information from the {@code tzfile}; it uses:
49  * {@code tzh_timecnt} and the associated transition times and type information. For each type
50  * (described by {@code struct ttinfo}) it uses {@code tt_gmtoff} and {@code tt_isdst}.
51  *
52  * @hide
53  */
54 @libcore.api.IntraCoreApi
55 @libcore.api.CorePlatformApi
56 public final class ZoneInfoData {
57     /**
58      * The serialized fields in {@link libcore.util.ZoneInfo} kept for backward app compatibility.
59      *
60      * @hide
61      */
62     @libcore.api.IntraCoreApi
63     public static final @NonNull ObjectStreamField @NonNull [] ZONEINFO_SERIALIZED_FIELDS =
64             new ObjectStreamField[] {
65                     new ObjectStreamField("mRawOffset", int.class),
66                     new ObjectStreamField("mEarliestRawOffset", int.class),
67                     new ObjectStreamField("mTransitions", long[].class),
68                     new ObjectStreamField("mTypes", byte[].class),
69                     new ObjectStreamField("mOffsets", int[].class),
70                     new ObjectStreamField("mIsDsts", byte[].class),
71             };
72 
73     private final String mId;
74 
75     /**
76      * The (best guess) non-DST offset used "today". It is stored in milliseconds.
77      * See also {@link #mOffsets} which holds values relative to this value, albeit in seconds.
78      */
79     private final int mRawOffset;
80 
81     /**
82      * The earliest non-DST offset for the zone. It is stored in milliseconds and is absolute, i.e.
83      * it is not relative to mRawOffset.
84      */
85     private final int mEarliestRawOffset;
86 
87     /**
88      * The times (in Unix epoch time, seconds since 1st Jan 1970 00:00:00 UTC) at which the offsets
89      * changes for any reason, whether that is a change in the offset from UTC or a change in the
90      * DST.
91      *
92      * <p>These times are pre-calculated externally from a set of rules (both historical and
93      * future) and stored in a file from which {@link ZoneInfoData#readTimeZone(String,
94      * BufferIterator)} reads the data. That is quite different to {@link java.util.SimpleTimeZone},
95      * which has essentially human readable rules (e.g. DST starts at 01:00 on the first Sunday in
96      * March and ends at 01:00 on the last Sunday in October) that can be used to determine the
97      * DST transition times across a number of years
98      *
99      * <p>In terms of {@link ZoneInfoData tzfile} structure this array is of length
100      * {@code tzh_timecnt} and contains the times in seconds converted to long to make them safer
101      * to use.
102      *
103      * <p>They are stored in order from earliest (lowest) time to latest (highest). A transition is
104      * identified by its index within this array. A transition {@code T} is active at a specific
105      * time {@code X} if {@code T} is the highest transition whose time is less than or equal to
106      * {@code X}.
107      *
108      * @see #mTypes
109      */
110     final long[] mTransitions;
111 
112     /**
113      * The type of the transition, where type is a pair consisting of the offset and whether the
114      * offset includes DST or not.
115      *
116      * <p>Each transition in {@link #mTransitions} has an associated type in this array at the same
117      * index. The type is an index into the arrays {@link #mOffsets} and {@link #mIsDsts} that each
118      * contain one part of the pair.
119      *
120      * <p>In the {@link ZoneInfoData tzfile} structure the type array only contains unique instances
121      * of the {@code struct ttinfo} to save space and each type may be referenced by multiple
122      * transitions. However, the type pairs stored in this class are not guaranteed unique because
123      * they do not include the {@code tt_abbrind}, which is the abbreviated identifier to use for
124      * the time zone after the transition.
125      *
126      * @see #mTransitions
127      * @see #mOffsets
128      * @see #mIsDsts
129      */
130     final byte[] mTypes;
131 
132     /**
133      * The offset parts of the transition types, in seconds.
134      *
135      * <p>These are actually a delta to the {@link #mRawOffset}. So, if the offset is say +7200
136      * seconds and {@link #mRawOffset} is say +3600 then this will have a value of +3600.
137      *
138      * <p>The offset in milliseconds can be computed using:
139      * {@code mRawOffset + mOffsets[type] * 1000}
140      *
141      * @see #mTypes
142      * @see #mIsDsts
143      */
144     final int[] mOffsets;
145 
146     /**
147      * Specifies whether an associated offset includes DST or not.
148      *
149      * <p>Each entry in here is 1 if the offset at the same index in {@link #mOffsets} includes DST
150      * and 0 otherwise.
151      *
152      * @see #mTypes
153      * @see #mOffsets
154      */
155     final byte[] mIsDsts;
156 
ZoneInfoData(String id, int rawOffset, int earliestRawOffset, long[] transitions, byte[] types, int[] offsets, byte[] isDsts)157     private ZoneInfoData(String id, int rawOffset, int earliestRawOffset,
158             long[] transitions, byte[] types, int[] offsets, byte[] isDsts) {
159         mId = id;
160         mRawOffset = rawOffset;
161         mEarliestRawOffset = earliestRawOffset;
162         mTransitions = transitions;
163         mTypes = types;
164         mOffsets = offsets;
165         mIsDsts = isDsts;
166     }
167 
168     /**
169      * Copy constructor
170      */
ZoneInfoData(ZoneInfoData that)171     private ZoneInfoData(ZoneInfoData that) {
172         this(that, that.mRawOffset);
173     }
174 
175     /**
176      * Copy constructor with a new raw offset.
177      */
ZoneInfoData(ZoneInfoData that, int newRawOffset)178     private ZoneInfoData(ZoneInfoData that, int newRawOffset) {
179         mRawOffset = newRawOffset;
180         mId = that.mId;
181         mEarliestRawOffset = that.mEarliestRawOffset;
182         mTransitions = that.mTransitions == null ? null : that.mTransitions.clone();
183         mTypes = that.mTypes == null ? null : that.mTypes.clone();
184         mOffsets = that.mOffsets == null ? null : that.mOffsets.clone();
185         mIsDsts = that.mIsDsts == null ? null : that.mIsDsts.clone();
186     }
187 
188     // VisibleForTesting
readTimeZone(String id, BufferIterator it)189     public static ZoneInfoData readTimeZone(String id, BufferIterator it)
190             throws IOException {
191 
192         // Skip over the superseded 32-bit header and data.
193         skipOver32BitData(id, it);
194 
195         // Read the v2+ 64-bit header and data.
196         return read64BitData(id, it);
197     }
198 
199     /**
200      * Skip over the 32-bit data with some minimal validation to make sure sure we reading a valid
201      * and supported file.
202      */
skipOver32BitData(String id, BufferIterator it)203     private static void skipOver32BitData(String id, BufferIterator it) throws IOException {
204         // Variable names beginning tzh_ correspond to those in "tzfile.h".
205 
206         // Check tzh_magic.
207         int tzh_magic = it.readInt();
208         if (tzh_magic != 0x545a6966) { // "TZif"
209             throw new IOException("Timezone id=" + id + " has an invalid header=" + tzh_magic);
210         }
211 
212         byte tzh_version = it.readByte();
213         checkTzifVersionAcceptable(id, tzh_version);
214 
215         // Skip the unused bytes.
216         it.skip(15);
217 
218         // Read the header values necessary to read through all the 32-bit data.
219         int tzh_ttisgmtcnt = it.readInt();
220         int tzh_ttisstdcnt = it.readInt();
221         int tzh_leapcnt = it.readInt();
222         int tzh_timecnt = it.readInt();
223         int tzh_typecnt = it.readInt();
224         int tzh_charcnt = it.readInt();
225 
226         // Skip transitions data, 4 bytes for each 32-bit time + 1 byte for isDst.
227         final int transitionInfoSize = 4 + 1;
228         it.skip(tzh_timecnt * transitionInfoSize);
229 
230         // Skip ttinfos.
231         // struct ttinfo {
232         //     int32_t       tt_gmtoff;
233         //     unsigned char tt_isdst;
234         //     unsigned char tt_abbrind;
235         // };
236         final int ttinfoSize = 4 + 1 + 1;
237         it.skip(tzh_typecnt * ttinfoSize);
238 
239         // Skip tzh_charcnt time zone abbreviations.
240         it.skip(tzh_charcnt);
241 
242         // Skip tzh_leapcnt repetitions of a 32-bit time + a 32-bit correction.
243         int leapInfoSize = 4 + 4;
244         it.skip(tzh_leapcnt * leapInfoSize);
245 
246         // Skip ttisstds and ttisgmts information. These can be ignored for our usecases as per
247         // https://mm.icann.org/pipermail/tz/2006-February/013359.html
248         it.skip(tzh_ttisstdcnt + tzh_ttisgmtcnt);
249     }
250 
251     /**
252      * Read the 64-bit header and data for {@code id} from the current position of {@code it} and
253      * return a ZoneInfo.
254      */
read64BitData(String id, BufferIterator it)255     private static ZoneInfoData read64BitData(String id, BufferIterator it)
256             throws IOException {
257         // Variable names beginning tzh_ correspond to those in "tzfile.h".
258 
259         // Check tzh_magic.
260         int tzh_magic = it.readInt();
261         if (tzh_magic != 0x545a6966) { // "TZif"
262             throw new IOException("Timezone id=" + id + " has an invalid header=" + tzh_magic);
263         }
264 
265         byte tzh_version = it.readByte();
266         checkTzifVersionAcceptable(id, tzh_version);
267 
268         // Skip the uninteresting parts of the header.
269         it.skip(27);
270 
271         // Read the sizes of the arrays we're about to read.
272         int tzh_timecnt = it.readInt();
273 
274         // Arbitrary ceiling to prevent allocating memory for corrupt data.
275         final int MAX_TRANSITIONS = 2000;
276         if (tzh_timecnt < 0 || tzh_timecnt > MAX_TRANSITIONS) {
277             throw new IOException(
278                     "Timezone id=" + id + " has an invalid number of transitions="
279                             + tzh_timecnt);
280         }
281 
282         int tzh_typecnt = it.readInt();
283         final int MAX_TYPES = 256;
284         if (tzh_typecnt < 1) {
285             throw new IOException("ZoneInfo requires at least one type "
286                     + "to be provided for each timezone but could not find one for '" + id
287                     + "'");
288         } else if (tzh_typecnt > MAX_TYPES) {
289             throw new IOException(
290                     "Timezone with id " + id + " has too many types=" + tzh_typecnt);
291         }
292 
293         it.skip(4); // Skip tzh_charcnt.
294 
295         long[] transitions64 = new long[tzh_timecnt];
296         it.readLongArray(transitions64, 0, transitions64.length);
297         for (int i = 0; i < tzh_timecnt; ++i) {
298             if (i > 0 && transitions64[i] <= transitions64[i - 1]) {
299                 throw new IOException(
300                         id + " transition at " + i + " is not sorted correctly, is "
301                                 + transitions64[i] + ", previous is " + transitions64[i - 1]);
302             }
303         }
304 
305         byte[] types = new byte[tzh_timecnt];
306         it.readByteArray(types, 0, types.length);
307         for (int i = 0; i < types.length; i++) {
308             int typeIndex = types[i] & 0xff;
309             if (typeIndex >= tzh_typecnt) {
310                 throw new IOException(
311                         id + " type at " + i + " is not < " + tzh_typecnt + ", is "
312                                 + typeIndex);
313             }
314         }
315 
316         int[] gmtOffsets = new int[tzh_typecnt];
317         byte[] isDsts = new byte[tzh_typecnt];
318         for (int i = 0; i < tzh_typecnt; ++i) {
319             gmtOffsets[i] = it.readInt();
320             byte isDst = it.readByte();
321             if (isDst != 0 && isDst != 1) {
322                 throw new IOException(id + " dst at " + i + " is not 0 or 1, is " + isDst);
323             }
324             isDsts[i] = isDst;
325             // We skip the abbreviation index. This would let us provide historically-accurate
326             // time zone abbreviations (such as "AHST", "YST", and "AKST" for standard time in
327             // America/Anchorage in 1982, 1983, and 1984 respectively). ICU only knows the current
328             // names, though, so even if we did use this data to provide the correct abbreviations
329             // for en_US, we wouldn't be able to provide correct abbreviations for other locales,
330             // nor would we be able to provide correct long forms (such as "Yukon Standard Time")
331             // for any locale. (The RI doesn't do any better than us here either.)
332             it.skip(1);
333         }
334         return new ZoneInfoData(id, transitions64, types, gmtOffsets, isDsts);
335     }
336 
checkTzifVersionAcceptable(String id, byte tzh_version)337     private static void checkTzifVersionAcceptable(String id, byte tzh_version) throws IOException {
338         char tzh_version_char = (char) tzh_version;
339         // Version >= 2 is required because the 64-bit time section is required. v3 is the latest
340         // version known at the time of writing and is identical to v2 in the parts used by this
341         // class.
342         if (tzh_version_char != '2' && tzh_version_char != '3') {
343             throw new IOException(
344                     "Timezone id=" + id + " has an invalid format version=\'" + tzh_version_char
345                             + "\' (" + tzh_version + ")");
346         }
347     }
348 
ZoneInfoData(String name, long[] transitions, byte[] types, int[] gmtOffsets, byte[] isDsts)349     private ZoneInfoData(String name, long[] transitions, byte[] types, int[] gmtOffsets,
350             byte[] isDsts) {
351         if (gmtOffsets.length == 0) {
352             throw new IllegalArgumentException("ZoneInfo requires at least one offset "
353                     + "to be provided for each timezone but could not find one for '" + name + "'");
354         }
355         mTransitions = transitions;
356         mTypes = types;
357         mIsDsts = isDsts;
358         mId = name;
359 
360         // Find the latest standard offsets (if any).
361         int lastStdTransitionIndex = -1;
362         for (int i = mTransitions.length - 1; lastStdTransitionIndex == -1 && i >= 0; --i) {
363             int typeIndex = mTypes[i] & 0xff;
364             if (lastStdTransitionIndex == -1 && mIsDsts[typeIndex] == 0) {
365                 lastStdTransitionIndex = i;
366             }
367         }
368 
369         final int rawOffsetInSeconds;
370         // Use the latest non-daylight offset (if any) as the raw offset.
371         if (mTransitions.length == 0) {
372             // This case is no longer expected to occur in the data used on Android after changes
373             // made in zic version 2014c. It is kept as a fallback.
374             // If there are no transitions then use the first GMT offset.
375             rawOffsetInSeconds = gmtOffsets[0];
376         } else {
377             if (lastStdTransitionIndex == -1) {
378                 throw new IllegalStateException( "ZoneInfo requires at least one non-DST "
379                         + "transition to be provided for each timezone that has at least one "
380                         + "transition but could not find one for '" + name + "'");
381             }
382             rawOffsetInSeconds = gmtOffsets[mTypes[lastStdTransitionIndex] & 0xff];
383         }
384 
385         // From the tzfile docs (Jan 2019):
386         // The localtime(3) function uses the first standard-time ttinfo structure
387         // in the file (or simply the first ttinfo structure in the absence of a
388         // standard-time structure) if either tzh_timecnt is zero or the time
389         // argument is less than the first transition time recorded in the file.
390         //
391         // Cache the raw offset associated with the first nonDst type, in case we're asked about
392         // times that predate our transition data. Android falls back to mRawOffset if there are
393         // only DST ttinfo structures (assumed rare).
394         int firstStdTypeIndex = -1;
395         for (int i = 0; i < mIsDsts.length; ++i) {
396             if (mIsDsts[i] == 0) {
397                 firstStdTypeIndex = i;
398                 break;
399             }
400         }
401 
402         int earliestRawOffset = (firstStdTypeIndex != -1)
403                 ? gmtOffsets[firstStdTypeIndex] : rawOffsetInSeconds;
404 
405         // Rather than keep offsets from UTC, we use offsets from local time, so the raw offset
406         // can be changed in the new instance and automatically affects all the offsets.
407         mOffsets = gmtOffsets;
408         for (int i = 0; i < mOffsets.length; i++) {
409             mOffsets[i] -= rawOffsetInSeconds;
410         }
411 
412         // tzdata uses seconds, but Java uses milliseconds.
413         mRawOffset = rawOffsetInSeconds * 1000;
414         mEarliestRawOffset = earliestRawOffset * 1000;
415     }
416 
417     /**
418      * Create an instance from the serialized fields from {@link libcore.util.ZoneInfo}
419      * for backward app compatibility.
420      *
421      * @hide
422      */
423     @libcore.api.IntraCoreApi
createFromSerializationFields(@onNull String id, @NonNull GetField getField)424     public static @NonNull ZoneInfoData createFromSerializationFields(@NonNull String id,
425             @NonNull GetField getField)
426             throws IOException {
427         int rawOffset = getField.get("mRawOffset", 0);
428         int earliestRawOffset = getField.get("mEarliestRawOffset", 0);
429         long[] transitions = (long[]) getField.get("mTransitions", null);
430         byte[] types = (byte[]) getField.get("mTypes", null);
431         int[] offsets = (int[]) getField.get("mOffsets", null);
432         byte[] isDsts = (byte[]) getField.get("mIsDsts", null);
433 
434         return new ZoneInfoData(id, rawOffset, earliestRawOffset, transitions, types, offsets,
435                 isDsts);
436     }
437 
438     /**
439      * Serialize {@link libcore.util.ZoneInfo} into backward app compatible form.
440      *
441      * @hide
442      */
443     @libcore.api.IntraCoreApi
writeToSerializationFields(@onNull PutField putField)444     public void writeToSerializationFields(@NonNull PutField putField) {
445         putField.put("mRawOffset", mRawOffset);
446         putField.put("mEarliestRawOffset", mEarliestRawOffset);
447         putField.put("mTransitions", mTransitions);
448         putField.put("mTypes", mTypes);
449         putField.put("mOffsets", mOffsets);
450         putField.put("mIsDsts", mIsDsts);
451     }
452 
453     /**
454      * Find the transition in the {@code timezone} in effect at {@code seconds}.
455      *
456      * <p>Returns an index in the range -1..timeZone.mTransitions.length - 1. -1 is used to
457      * indicate the time is before the first transition. Other values are an index into
458      * timeZone.mTransitions.
459      */
findTransitionIndex(long seconds)460     public int findTransitionIndex(long seconds) {
461         int transition = Arrays.binarySearch(mTransitions, seconds);
462         if (transition < 0) {
463             transition = ~transition - 1;
464             if (transition < 0) {
465                 return -1;
466             }
467         }
468 
469         return transition;
470     }
471 
472     /**
473      * Finds the index within the {@link #mOffsets}/{@link #mIsDsts} arrays for the specified time
474      * in seconds, since 1st Jan 1970 00:00:00.
475      * @param seconds the time in seconds.
476      * @return -1 if the time is before the first transition, or [0..{@code mOffsets}-1] for the
477      * active offset.
478      */
findOffsetIndexForTimeInSeconds(long seconds)479     int findOffsetIndexForTimeInSeconds(long seconds) {
480         int transition = findTransitionIndex(seconds);
481         if (transition < 0) {
482             return -1;
483         }
484 
485         return mTypes[transition] & 0xff;
486     }
487 
488     /**
489      * Finds the index within the {@link #mOffsets}/{@link #mIsDsts} arrays for the specified time
490      * in milliseconds, since 1st Jan 1970 00:00:00.000.
491      * @param millis the time in milliseconds.
492      * @return -1 if the time is before the first transition, or [0..{@code mOffsets}-1] for the
493      * active offset.
494      */
findOffsetIndexForTimeInMilliseconds(long millis)495     int findOffsetIndexForTimeInMilliseconds(long millis) {
496         // This rounds the time in milliseconds down to the time in seconds.
497         //
498         // It can't just divide a timestamp in millis by 1000 to obtain a transition time in
499         // seconds because / (div) in Java rounds towards zero. Times before 1970 are negative and
500         // if they have a millisecond component then div would result in obtaining a time that is
501         // one second after what we need.
502         //
503         // e.g. dividing -12,001 milliseconds by 1000 would result in -12 seconds. If there was a
504         //      transition at -12 seconds then that would be incorrectly treated as being active
505         //      for a time of -12,001 milliseconds even though that time is before the transition
506         //      should occur.
507 
508         return findOffsetIndexForTimeInSeconds(roundDownMillisToSeconds(millis));
509     }
510 
511     /**
512      * Converts time in milliseconds into a time in seconds, rounding down to the closest time
513      * in seconds before the time in milliseconds.
514      *
515      * <p>It's not sufficient to simply divide by 1000 because that rounds towards 0 and so while
516      * for positive numbers it produces a time in seconds that precedes the time in milliseconds
517      * for negative numbers it can produce a time in seconds that follows the time in milliseconds.
518      *
519      * <p>This basically does the same as {@code (long) Math.floor(millis / 1000.0)} but should be
520      * faster.
521      *
522      * @param millis the time in milliseconds, may be negative.
523      * @return the time in seconds.
524      */
roundDownMillisToSeconds(long millis)525     static long roundDownMillisToSeconds(long millis) {
526         if (millis < 0) {
527             // If the time is less than zero then subtract 999 and then divide by 1000 rounding
528             // towards 0 as usual, e.g.
529             // -12345 -> -13344 / 1000 = -13
530             // -12000 -> -12999 / 1000 = -12
531             // -12001 -> -13000 / 1000 = -13
532             return (millis - 999) / 1000;
533         } else {
534             return millis / 1000;
535         }
536     }
537 
538     /**
539      * Converts time in milliseconds into a time in seconds, rounding up to the closest time
540      * in seconds before the time in milliseconds.
541      *
542      * <p>It's not sufficient to simply divide by 1000 because that rounds towards 0 and so while
543      * for negative numbers it produces a time in seconds that follows the time in milliseconds
544      * for positive numbers it can produce a time in seconds that precedes the time in milliseconds.
545      *
546      * <p>This basically does the same as {@code (long) Math.ceil(millis / 1000.0)} but should be
547      * faster.
548      *
549      * @param millis the time in milliseconds, may be negative.
550      * @return the time in seconds.
551      */
roundUpMillisToSeconds(long millis)552     static long roundUpMillisToSeconds(long millis) {
553         if (millis > 0) {
554             // If the time is greater than zero then add 999 and then divide by 1000 rounding
555             // towards 0 as usual, e.g.
556             // 12345 -> 13344 / 1000 = 13
557             // 12000 -> 12999 / 1000 = 12
558             // 12001 -> 13000 / 1000 = 13
559             return (millis + 999) / 1000;
560         } else {
561             return millis / 1000;
562         }
563     }
564 
565     /**
566      * Get the raw and DST offsets for the specified time in milliseconds since
567      * 1st Jan 1970 00:00:00 UTC.
568      *
569      * <p>The raw offset, i.e. that part of the total offset which is not due to DST, is stored at
570      * index 0 of the {@code offsets} array and the DST offset, i.e. that part of the offset which
571      * is due to DST is stored at index 1.
572      *
573      * @param unixEpochTimeInMillis the Unix epoch time in milliseconds.
574      * @param offsets the array whose length must be greater than or equal to 2.
575      * @return the total offset which is the sum of the raw and DST offsets.
576      *
577      * @hide
578      */
579     @libcore.api.IntraCoreApi
getOffsetsByUtcTime(long unixEpochTimeInMillis, @NonNull int[] offsets)580     public int getOffsetsByUtcTime(long unixEpochTimeInMillis, @NonNull int[] offsets) {
581         int transitionIndex = findTransitionIndex(roundDownMillisToSeconds(unixEpochTimeInMillis));
582         int totalOffset;
583         int rawOffset;
584         int dstOffset;
585         if (transitionIndex == -1) {
586             // See getOffset(long) and inDaylightTime(Date) for an explanation as to why these
587             // values are used for times before the first transition.
588             rawOffset = mEarliestRawOffset;
589             dstOffset = 0;
590             totalOffset = rawOffset;
591         } else {
592             int type = mTypes[transitionIndex] & 0xff;
593 
594             // Get the total offset used for the transition.
595             totalOffset = mRawOffset + mOffsets[type] * 1000;
596             if (mIsDsts[type] == 0) {
597                 // Offset does not include DST so DST is 0 and the raw offset is the total offset.
598                 rawOffset = totalOffset;
599                 dstOffset = 0;
600             } else {
601                 // Offset does include DST, we need to find the preceding transition that did not
602                 // include the DST offset so that we can calculate the DST offset.
603                 rawOffset = -1;
604                 for (transitionIndex -= 1; transitionIndex >= 0; --transitionIndex) {
605                     type = mTypes[transitionIndex] & 0xff;
606                     if (mIsDsts[type] == 0) {
607                         rawOffset = mRawOffset + mOffsets[type] * 1000;
608                         break;
609                     }
610                 }
611                 // If no previous transition was found then use the earliest raw offset.
612                 if (rawOffset == -1) {
613                     rawOffset = mEarliestRawOffset;
614                 }
615 
616                 // The DST offset is the difference between the total and the raw offset.
617                 dstOffset = totalOffset - rawOffset;
618             }
619         }
620 
621         offsets[0] = rawOffset;
622         offsets[1] = dstOffset;
623 
624         return totalOffset;
625     }
626 
627     /**
628      * Returns the offset from UTC in milliseconds at the specified time {@code whenMillis}.
629      *
630      * @param whenMillis the Unix epoch time in milliseconds since 1st Jan 1970, 00:00:00 UTC
631      *
632      * @hide
633      */
634     @libcore.api.IntraCoreApi
getOffset(long whenMillis)635     public int getOffset(long whenMillis) {
636         int offsetIndex = findOffsetIndexForTimeInMilliseconds(whenMillis);
637         if (offsetIndex == -1) {
638             // Assume that all times before our first transition correspond to the
639             // oldest-known non-daylight offset. The obvious alternative would be to
640             // use the current raw offset, but that seems like a greater leap of faith.
641             return mEarliestRawOffset;
642         }
643         return mRawOffset + mOffsets[offsetIndex] * 1000;
644     }
645 
646     /**
647      * Returns whether the given {@code whenMillis} is in daylight saving time in this time zone.
648      *
649      * @param whenMillis the Unix epoch time in milliseconds since 1st Jan 1970, 00:00:00 UTC
650      *
651      * @hide
652      */
653     @libcore.api.IntraCoreApi
isInDaylightTime(long whenMillis)654     public boolean isInDaylightTime(long whenMillis) {
655         int offsetIndex = findOffsetIndexForTimeInMilliseconds(whenMillis);
656         if (offsetIndex == -1) {
657             // Assume that all times before our first transition are non-daylight.
658             // Transition data tends to start with a transition to daylight, so just
659             // copying the first transition would assume the opposite.
660             // http://code.google.com/p/android/issues/detail?id=14395
661             return false;
662         }
663         return mIsDsts[offsetIndex] == 1;
664     }
665 
666     /**
667      * Returns the raw offset in milliseconds. The value is not affected by daylight saving.
668      *
669      * @hide
670      */
671     @libcore.api.IntraCoreApi
getRawOffset()672     public int getRawOffset() {
673         return mRawOffset;
674     }
675 
676     /**
677      * Returns the offset of daylight saving in milliseconds in the latest Daylight Savings Time
678      * after the time {@code whenMillis}. If no known DST occurs after {@code whenMillis}, it
679      * returns {@code null}.
680      *
681      * @param whenMillis the Unix epoch time in milliseconds since 1st Jan 1970, 00:00:00 UTC
682      *
683      * @hide
684      */
685     @libcore.api.IntraCoreApi
getLatestDstSavingsMillis(long whenMillis)686     public @Nullable Integer getLatestDstSavingsMillis(long whenMillis) {
687         // Find the latest daylight and standard offsets (if any).
688         int lastStdTransitionIndex = -1;
689         int lastDstTransitionIndex = -1;
690         for (int i = mTransitions.length - 1;
691                 (lastStdTransitionIndex == -1 || lastDstTransitionIndex == -1) && i >= 0; --i) {
692             int typeIndex = mTypes[i] & 0xff;
693             if (lastStdTransitionIndex == -1 && mIsDsts[typeIndex] == 0) {
694                 lastStdTransitionIndex = i;
695             }
696             if (lastDstTransitionIndex == -1 && mIsDsts[typeIndex] != 0) {
697                 lastDstTransitionIndex = i;
698             }
699         }
700 
701         if (lastDstTransitionIndex != -1) {
702             // Check to see if the last DST transition is in the future or the past. If it is in
703             // the past then we treat it as if it doesn't exist, at least for the purposes of
704             // TimeZone#useDaylightTime() and #getDSTSavings()
705             long lastDSTTransitionTime = mTransitions[lastDstTransitionIndex];
706 
707             // Convert the current time in millis into seconds. Unlike other places that convert
708             // time in milliseconds into seconds in order to compare with transition time this
709             // rounds up rather than down. It does that because this is interested in what
710             // transitions apply in future
711             long currentUnixTimeSeconds = roundUpMillisToSeconds(whenMillis);
712 
713             // Is this zone observing DST currently or in the future?
714             // We don't care if they've historically used it: most places have at least once.
715             // See http://b/36905574.
716             // This test means that for somewhere like Morocco, which tried DST in 2009 but has
717             // no future plans (and thus no future schedule info) will report "true" from
718             // useDaylightTime at the start of 2009 but "false" at the end. This seems appropriate.
719             if (lastDSTTransitionTime < currentUnixTimeSeconds) {
720                 // The last DST transition is before now so treat it as if it doesn't exist.
721                 lastDstTransitionIndex = -1;
722             }
723         }
724 
725         final Integer dstSavings;
726         if (lastDstTransitionIndex == -1) {
727             // There were no DST transitions or at least no future DST transitions so DST is not
728             // used.
729             dstSavings = null;
730         } else {
731             // Use the latest transition's pair of offsets to compute the DST savings.
732             // This isn't generally useful, but it's exposed by TimeZone.getDSTSavings.
733             int lastGmtOffset = mOffsets[mTypes[lastStdTransitionIndex] & 0xff];
734             int lastDstOffset = mOffsets[mTypes[lastDstTransitionIndex] & 0xff];
735             dstSavings = (lastDstOffset - lastGmtOffset) * 1000;
736         }
737         return dstSavings;
738     }
739 
getEarliestRawOffset()740     int getEarliestRawOffset() {
741         return mEarliestRawOffset;
742     }
743 
744     /**
745      * Returns {@code true} if 2 time zones have the same time zone rule.
746      *
747      * @hide
748      */
749     @libcore.api.IntraCoreApi
hasSameRules(@onNull ZoneInfoData other)750     public boolean hasSameRules(@NonNull ZoneInfoData other) {
751         return mRawOffset == other.mRawOffset
752                 // Arrays.equals returns true if both arrays are null
753                 && Arrays.equals(mOffsets, other.mOffsets)
754                 && Arrays.equals(mIsDsts, other.mIsDsts)
755                 && Arrays.equals(mTypes, other.mTypes)
756                 && Arrays.equals(mTransitions, other.mTransitions);
757     }
758 
759     @Override
equals(Object obj)760     public boolean equals(Object obj) {
761         if (!(obj instanceof ZoneInfoData)) {
762             return false;
763         }
764         ZoneInfoData other = (ZoneInfoData) obj;
765         return getID().equals(other.getID()) && hasSameRules(other);
766     }
767 
768     @Override
hashCode()769     public int hashCode() {
770         final int prime = 31;
771         int result = 1;
772         result = prime * result + getID().hashCode();
773         result = prime * result + Arrays.hashCode(mOffsets);
774         result = prime * result + Arrays.hashCode(mIsDsts);
775         result = prime * result + mRawOffset;
776         result = prime * result + Arrays.hashCode(mTransitions);
777         result = prime * result + Arrays.hashCode(mTypes);
778         return result;
779     }
780 
781     /**
782      * Returns a string containing the internal states for debug purpose.
783      */
784     @Override
toString()785     public String toString() {
786         return "[id=\"" + getID() + "\"" +
787             ",mRawOffset=" + mRawOffset +
788             ",mEarliestRawOffset=" + mEarliestRawOffset +
789             ",transitions=" + mTransitions.length +
790             "]";
791     }
792 
793     /**
794      * Returns the time zone id.
795      *
796      * @hide
797      */
798     @libcore.api.CorePlatformApi
799     @libcore.api.IntraCoreApi
getID()800     public @NonNull String getID() {
801         return mId;
802     }
803 
804     /**
805      * Create a deep copy of this object with a new raw offset.
806      *
807      * @hide
808      */
809     @libcore.api.IntraCoreApi
createCopyWithRawOffset(int newRawOffset)810     public @NonNull ZoneInfoData createCopyWithRawOffset(int newRawOffset) {
811         return new ZoneInfoData(this, newRawOffset);
812     }
813 
814     /**
815      * Returns the Unix epoch times (in seconds since 1st Jan 1970 00:00:00 UTC) at which the
816      * offsets changes for any reason, whether that is a change in the offset from UTC or a change
817      * in the DST.
818      *
819      * WARNING: This API is exposed only for app compat usage in @link libcore.util.ZoneInfo}.
820      *
821      * @hide
822      */
823     @libcore.api.IntraCoreApi
getTransitions()824     public @Nullable long[] getTransitions() {
825         return mTransitions == null ? null : mTransitions.clone();
826     }
827 
828     /**
829      * Creates an instance. This method is only for testing purposes.
830      *
831      * @param transitions The times (in seconds) since beginning of the Unix epoch at which
832      *                    the offsets changes
833      * @param types the type of the transition. The offsets and standard/daylight states are
834      *              represented in the corresponding entry in <code>offsets</code> and
835      *              <code>isDsts</code> respectively
836      * @param offsets the total offsets of each type. The max allowed size of this array is 256.
837      * @param isDsts an entry is {@code true} if the type is daylight saving time. The max allowed
838      *               size of this array is 256.
839      * @hide
840      */
841     @libcore.api.IntraCoreApi
createInstance(@onNull String id, @NonNull long[] transitions, @NonNull byte[] types, @NonNull int[] offsets, @NonNull boolean[] isDsts)842     public static @NonNull ZoneInfoData createInstance(@NonNull String id,
843             @NonNull long[] transitions, @NonNull byte[] types, @NonNull int[] offsets,
844             @NonNull boolean[] isDsts) {
845         return new ZoneInfoData(id, transitions, types, offsets, toByteArray(isDsts));
846     }
847 
toByteArray(boolean[] isDsts)848     private static byte[] toByteArray(boolean[] isDsts) {
849         byte[] result = new byte[isDsts.length];
850         for (int i = 0; i < isDsts.length; i++) {
851             result[i] = (byte) (isDsts[i] ? 1 : 0);
852         }
853         return result;
854     }
855 }
856