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