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