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