1 /* GENERATED SOURCE. DO NOT MODIFY. */ 2 // 2016 and later: Unicode, Inc. and others. 3 // License & terms of use: http://www.unicode.org/copyright.html#License 4 /* 5 ******************************************************************************* 6 * Copyright (C) 2005-2016, International Business Machines Corporation and 7 * others. All Rights Reserved. 8 ******************************************************************************* 9 */ 10 package android.icu.impl; 11 12 import java.io.IOException; 13 import java.io.ObjectInputStream; 14 import java.util.Arrays; 15 import java.util.Date; 16 import java.util.MissingResourceException; 17 18 import android.icu.util.AnnualTimeZoneRule; 19 import android.icu.util.BasicTimeZone; 20 import android.icu.util.Calendar; 21 import android.icu.util.DateTimeRule; 22 import android.icu.util.GregorianCalendar; 23 import android.icu.util.InitialTimeZoneRule; 24 import android.icu.util.SimpleTimeZone; 25 import android.icu.util.TimeArrayTimeZoneRule; 26 import android.icu.util.TimeZone; 27 import android.icu.util.TimeZoneRule; 28 import android.icu.util.TimeZoneTransition; 29 import android.icu.util.UResourceBundle; 30 31 /** 32 * A time zone based on the Olson tz database. Olson time zones change 33 * behavior over time. The raw offset, rules, presence or absence of 34 * daylight savings time, and even the daylight savings amount can all 35 * vary. 36 * 37 * This class uses a resource bundle named "zoneinfo". Zoneinfo is a 38 * table containing different kinds of resources. In several places, 39 * zones are referred to using integers. A zone's integer is a number 40 * from 0..n-1, where n is the number of zones, with the zones sorted 41 * in lexicographic order. 42 * 43 * 1. Zones. These have keys corresponding to the Olson IDs, e.g., 44 * "Asia/Shanghai". Each resource describes the behavior of the given 45 * zone. Zones come in two different formats. 46 * 47 * a. Zone (table). A zone is a table resource contains several 48 * type of resources below: 49 * 50 * - typeOffsets:intvector (Required) 51 * 52 * Sets of UTC raw/dst offset pairs in seconds. Entries at 53 * 2n represents raw offset and 2n+1 represents dst offset 54 * paired with the raw offset at 2n. The very first pair represents 55 * the initial zone offset (before the first transition) always. 56 * 57 * - trans:intvector (Optional) 58 * 59 * List of transition times represented by 32bit seconds from the 60 * epoch (1970-01-01T00:00Z) in ascending order. 61 * 62 * - transPre32/transPost32:intvector (Optional) 63 * 64 * List of transition times before/after 32bit minimum seconds. 65 * Each time is represented by a pair of 32bit integer. 66 * 67 * - typeMap:bin (Optional) 68 * 69 * Array of bytes representing the mapping between each transition 70 * time (transPre32/trans/transPost32) and its corresponding offset 71 * data (typeOffsets). 72 * 73 * - finalRule:string (Optional) 74 * 75 * If a recurrent transition rule is applicable to a zone forever 76 * after the final transition time, finalRule represents the rule 77 * in Rules data. 78 * 79 * - finalRaw:int (Optional) 80 * 81 * When finalRule is available, finalRaw is required and specifies 82 * the raw (base) offset of the rule. 83 * 84 * - finalYear:int (Optional) 85 * 86 * When finalRule is available, finalYear is required and specifies 87 * the start year of the rule. 88 * 89 * - links:intvector (Optional) 90 * 91 * When this zone data is shared with other zones, links specifies 92 * all zones including the zone itself. Each zone is referenced by 93 * integer index. 94 * 95 * b. Link (int, length 1). A link zone is an int resource. The 96 * integer is the zone number of the target zone. The key of this 97 * resource is an alternate name for the target zone. This data 98 * is corresponding to Link data in the tz database. 99 * 100 * 101 * 2. Rules. These have keys corresponding to the Olson rule IDs, 102 * with an underscore prepended, e.g., "_EU". Each resource describes 103 * the behavior of the given rule using an intvector, containing the 104 * onset list, the cessation list, and the DST savings. The onset and 105 * cessation lists consist of the month, dowim, dow, time, and time 106 * mode. The end result is that the 11 integers describing the rule 107 * can be passed directly into the SimpleTimeZone 13-argument 108 * constructor (the other two arguments will be the raw offset, taken 109 * from the complex zone element 5, and the ID string, which is not 110 * used), with the times and the DST savings multiplied by 1000 to 111 * scale from seconds to milliseconds. 112 * 113 * 3. Regions. An array specifies mapping between zones and regions. 114 * Each item is either a 2-letter ISO country code or "001" 115 * (UN M.49 - World). This data is generated from "zone.tab" 116 * in the tz database. 117 * @hide Only a subset of ICU is exposed in Android 118 */ 119 public class OlsonTimeZone extends BasicTimeZone { 120 121 // Generated by serialver from JDK 1.4.1_01 122 static final long serialVersionUID = -6281977362477515376L; 123 124 /* (non-Javadoc) 125 * @see android.icu.util.TimeZone#getOffset(int, int, int, int, int, int) 126 */ 127 @Override 128 public int getOffset(int era, int year, int month, int day, int dayOfWeek, int milliseconds) { 129 if (month < Calendar.JANUARY || month > Calendar.DECEMBER) { 130 throw new IllegalArgumentException("Month is not in the legal range: " +month); 131 } else { 132 return getOffset(era, year, month, day, dayOfWeek, milliseconds, Grego.monthLength(year, month)); 133 } 134 } 135 136 /** 137 * TimeZone API. 138 */ 139 public int getOffset(int era, int year, int month,int dom, int dow, int millis, int monthLength){ 140 141 if ((era != GregorianCalendar.AD && era != GregorianCalendar.BC) 142 || month < Calendar.JANUARY 143 || month > Calendar.DECEMBER 144 || dom < 1 145 || dom > monthLength 146 || dow < Calendar.SUNDAY 147 || dow > Calendar.SATURDAY 148 || millis < 0 149 || millis >= Grego.MILLIS_PER_DAY 150 || monthLength < 28 151 || monthLength > 31) { 152 throw new IllegalArgumentException(); 153 } 154 155 if (era == GregorianCalendar.BC) { 156 year = -year; 157 } 158 159 if (finalZone != null && year >= finalStartYear) { 160 return finalZone.getOffset(era, year, month, dom, dow, millis); 161 } 162 163 // Compute local epoch millis from input fields 164 long time = Grego.fieldsToDay(year, month, dom) * Grego.MILLIS_PER_DAY + millis; 165 166 int[] offsets = new int[2]; 167 getHistoricalOffset(time, true, LOCAL_DST, LOCAL_STD, offsets); 168 return offsets[0] + offsets[1]; 169 } 170 171 /* (non-Javadoc) 172 * @see android.icu.util.TimeZone#setRawOffset(int) 173 */ 174 @Override 175 public void setRawOffset(int offsetMillis) { 176 if (isFrozen()) { 177 throw new UnsupportedOperationException("Attempt to modify a frozen OlsonTimeZone instance."); 178 } 179 180 if (getRawOffset() == offsetMillis) { 181 return; 182 } 183 long current = System.currentTimeMillis(); 184 185 if (current < finalStartMillis) { 186 SimpleTimeZone stz = new SimpleTimeZone(offsetMillis, getID()); 187 188 boolean bDst = useDaylightTime(); 189 if (bDst) { 190 TimeZoneRule[] currentRules = getSimpleTimeZoneRulesNear(current); 191 if (currentRules.length != 3) { 192 // DST was observed at the beginning of this year, so useDaylightTime 193 // returned true. getSimpleTimeZoneRulesNear requires at least one 194 // future transition for making a pair of rules. This implementation 195 // rolls back the time before the latest offset transition. 196 TimeZoneTransition tzt = getPreviousTransition(current, false); 197 if (tzt != null) { 198 currentRules = getSimpleTimeZoneRulesNear(tzt.getTime() - 1); 199 } 200 } 201 if (currentRules.length == 3 202 && (currentRules[1] instanceof AnnualTimeZoneRule) 203 && (currentRules[2] instanceof AnnualTimeZoneRule)) { 204 // A pair of AnnualTimeZoneRule 205 AnnualTimeZoneRule r1 = (AnnualTimeZoneRule)currentRules[1]; 206 AnnualTimeZoneRule r2 = (AnnualTimeZoneRule)currentRules[2]; 207 DateTimeRule start, end; 208 int offset1 = r1.getRawOffset() + r1.getDSTSavings(); 209 int offset2 = r2.getRawOffset() + r2.getDSTSavings(); 210 int sav; 211 if (offset1 > offset2) { 212 start = r1.getRule(); 213 end = r2.getRule(); 214 sav = offset1 - offset2; 215 } else { 216 start = r2.getRule(); 217 end = r1.getRule(); 218 sav = offset2 - offset1; 219 } 220 // getSimpleTimeZoneRulesNear always return rules using DOW / WALL_TIME 221 stz.setStartRule(start.getRuleMonth(), start.getRuleWeekInMonth(), start.getRuleDayOfWeek(), 222 start.getRuleMillisInDay()); 223 stz.setEndRule(end.getRuleMonth(), end.getRuleWeekInMonth(), end.getRuleDayOfWeek(), 224 end.getRuleMillisInDay()); 225 // set DST saving amount and start year 226 stz.setDSTSavings(sav); 227 } else { 228 // This could only happen if last rule is DST 229 // and the rule used forever. For example, Asia/Dhaka 230 // in tzdata2009i stays in DST forever. 231 232 // Hack - set DST starting at midnight on Jan 1st, 233 // ending 23:59:59.999 on Dec 31st 234 stz.setStartRule(0, 1, 0); 235 stz.setEndRule(11, 31, Grego.MILLIS_PER_DAY - 1); 236 } 237 } 238 239 int[] fields = Grego.timeToFields(current, null); 240 241 finalStartYear = fields[0]; 242 finalStartMillis = Grego.fieldsToDay(fields[0], 0, 1); 243 244 if (bDst) { 245 // we probably do not need to set start year of final rule 246 // to finalzone itself, but we always do this for now. 247 stz.setStartYear(finalStartYear); 248 } 249 250 finalZone = stz; 251 252 } else { 253 finalZone.setRawOffset(offsetMillis); 254 } 255 256 transitionRulesInitialized = false; 257 } 258 259 @Override 260 public Object clone() { 261 if (isFrozen()) { 262 return this; 263 } 264 return cloneAsThawed(); 265 } 266 267 /** 268 * TimeZone API. 269 */ 270 @Override 271 public void getOffset(long date, boolean local, int[] offsets) { 272 if (finalZone != null && date >= finalStartMillis) { 273 finalZone.getOffset(date, local, offsets); 274 } else { 275 getHistoricalOffset(date, local, 276 LOCAL_FORMER, LOCAL_LATTER, offsets); 277 } 278 } 279 280 /** 281 * {@inheritDoc} 282 */ 283 @Override 284 public void getOffsetFromLocal(long date, 285 int nonExistingTimeOpt, int duplicatedTimeOpt, int[] offsets) { 286 if (finalZone != null && date >= finalStartMillis) { 287 finalZone.getOffsetFromLocal(date, nonExistingTimeOpt, duplicatedTimeOpt, offsets); 288 } else { 289 getHistoricalOffset(date, true, nonExistingTimeOpt, duplicatedTimeOpt, offsets); 290 } 291 } 292 293 /* (non-Javadoc) 294 * @see android.icu.util.TimeZone#getRawOffset() 295 */ 296 @Override 297 public int getRawOffset() { 298 int[] ret = new int[2]; 299 getOffset(System.currentTimeMillis(), false, ret); 300 return ret[0]; 301 } 302 303 /* (non-Javadoc) 304 * @see android.icu.util.TimeZone#useDaylightTime() 305 */ 306 @Override 307 public boolean useDaylightTime() { 308 // If DST was observed in 1942 (for example) but has never been 309 // observed from 1943 to the present, most clients will expect 310 // this method to return FALSE. This method determines whether 311 // DST is in use in the current year (at any point in the year) 312 // and returns TRUE if so. 313 long current = System.currentTimeMillis(); 314 315 if (finalZone != null && current >= finalStartMillis) { 316 return (finalZone != null && finalZone.useDaylightTime()); 317 } 318 319 int[] fields = Grego.timeToFields(current, null); 320 321 // Find start of this year, and start of next year 322 long start = Grego.fieldsToDay(fields[0], 0, 1) * SECONDS_PER_DAY; 323 long limit = Grego.fieldsToDay(fields[0] + 1, 0, 1) * SECONDS_PER_DAY; 324 325 // Return TRUE if DST is observed at any time during the current 326 // year. 327 for (int i = 0; i < transitionCount; ++i) { 328 if (transitionTimes64[i] >= limit) { 329 break; 330 } 331 if ((transitionTimes64[i] >= start && dstOffsetAt(i) != 0) 332 || (transitionTimes64[i] > start && i > 0 && dstOffsetAt(i - 1) != 0)) { 333 return true; 334 } 335 } 336 return false; 337 } 338 339 /* (non-Javadoc) 340 * @see android.icu.util.TimeZone#observesDaylightTime() 341 */ 342 @Override 343 public boolean observesDaylightTime() { 344 long current = System.currentTimeMillis(); 345 346 if (finalZone != null) { 347 if (finalZone.useDaylightTime()) { 348 return true; 349 } else if (current >= finalStartMillis) { 350 return false; 351 } 352 } 353 354 // Return TRUE if DST is observed at any future time 355 long currentSec = Grego.floorDivide(current, Grego.MILLIS_PER_SECOND); 356 int trsIdx = transitionCount - 1; 357 if (dstOffsetAt(trsIdx) != 0) { 358 return true; 359 } 360 while (trsIdx >= 0) { 361 if (transitionTimes64[trsIdx] <= currentSec) { 362 break; 363 } 364 if (dstOffsetAt(trsIdx - 1) != 0) { 365 return true; 366 } 367 trsIdx--; 368 } 369 return false; 370 } 371 /** 372 * TimeZone API 373 * Returns the amount of time to be added to local standard time 374 * to get local wall clock time. 375 */ 376 @Override 377 public int getDSTSavings() { 378 if (finalZone != null){ 379 return finalZone.getDSTSavings(); 380 } 381 return super.getDSTSavings(); 382 } 383 384 /* (non-Javadoc) 385 * @see android.icu.util.TimeZone#inDaylightTime(java.util.Date) 386 */ 387 @Override 388 public boolean inDaylightTime(Date date) { 389 int[] temp = new int[2]; 390 getOffset(date.getTime(), false, temp); 391 return temp[1] != 0; 392 } 393 394 /* (non-Javadoc) 395 * @see android.icu.util.TimeZone#hasSameRules(android.icu.util.TimeZone) 396 */ 397 @Override 398 public boolean hasSameRules(TimeZone other) { 399 if (this == other) { 400 return true; 401 } 402 // The super class implementation only check raw offset and 403 // use of daylight saving time. 404 if (!super.hasSameRules(other)) { 405 return false; 406 } 407 408 if (!(other instanceof OlsonTimeZone)) { 409 // We cannot reasonably compare rules in different types 410 return false; 411 } 412 413 // Check final zone 414 OlsonTimeZone o = (OlsonTimeZone)other; 415 if (finalZone == null) { 416 if (o.finalZone != null) { 417 return false; 418 } 419 } else { 420 if (o.finalZone == null 421 || finalStartYear != o.finalStartYear 422 || !(finalZone.hasSameRules(o.finalZone))) { 423 return false; 424 } 425 } 426 // Check transitions 427 // Note: The code below actually fails to compare two equivalent rules in 428 // different representation properly. 429 if (transitionCount != o.transitionCount || 430 !Arrays.equals(transitionTimes64, o.transitionTimes64) || 431 typeCount != o.typeCount || 432 !Arrays.equals(typeMapData, o.typeMapData) || 433 !Arrays.equals(typeOffsets, o.typeOffsets)){ 434 return false; 435 } 436 return true; 437 } 438 439 /** 440 * Returns the canonical ID of this system time zone 441 */ 442 public String getCanonicalID() { 443 if (canonicalID == null) { 444 synchronized(this) { 445 if (canonicalID == null) { 446 canonicalID = getCanonicalID(getID()); 447 448 assert(canonicalID != null); 449 if (canonicalID == null) { 450 // This should never happen... 451 canonicalID = getID(); 452 } 453 } 454 } 455 } 456 return canonicalID; 457 } 458 459 /** 460 * Construct a GMT+0 zone with no transitions. This is done when a 461 * constructor fails so the resultant object is well-behaved. 462 */ 463 private void constructEmpty(){ 464 transitionCount = 0; 465 transitionTimes64 = null; 466 typeMapData = null; 467 468 typeCount = 1; 469 typeOffsets = new int[]{0,0}; 470 finalZone = null; 471 finalStartYear = Integer.MAX_VALUE; 472 finalStartMillis = Double.MAX_VALUE; 473 474 transitionRulesInitialized = false; 475 } 476 477 /** 478 * Construct from a resource bundle 479 * @param top the top-level zoneinfo resource bundle. This is used 480 * to lookup the rule that `res' may refer to, if there is one. 481 * @param res the resource bundle of the zone to be constructed 482 * @param id time zone ID 483 */ 484 public OlsonTimeZone(UResourceBundle top, UResourceBundle res, String id){ 485 super(id); 486 construct(top, res); 487 } 488 489 private void construct(UResourceBundle top, UResourceBundle res){ 490 491 if ((top == null || res == null)) { 492 throw new IllegalArgumentException(); 493 } 494 if(DEBUG) System.out.println("OlsonTimeZone(" + res.getKey() +")"); 495 496 UResourceBundle r; 497 int[] transPre32, trans32, transPost32; 498 transPre32 = trans32 = transPost32 = null; 499 500 transitionCount = 0; 501 502 // Pre-32bit second transitions 503 try { 504 r = res.get("transPre32"); 505 transPre32 = r.getIntVector(); 506 if (transPre32.length % 2 != 0) { 507 // elements in the pre-32bit must be an even number 508 throw new IllegalArgumentException("Invalid Format"); 509 } 510 transitionCount += transPre32.length / 2; 511 } catch (MissingResourceException e) { 512 // Pre-32bit transition data is optional 513 } 514 515 // 32bit second transitions 516 try { 517 r = res.get("trans"); 518 trans32 = r.getIntVector(); 519 transitionCount += trans32.length; 520 } catch (MissingResourceException e) { 521 // 32bit transition data is optional 522 } 523 524 // Post-32bit second transitions 525 try { 526 r = res.get("transPost32"); 527 transPost32 = r.getIntVector(); 528 if (transPost32.length % 2 != 0) { 529 // elements in the post-32bit must be an even number 530 throw new IllegalArgumentException("Invalid Format"); 531 } 532 transitionCount += transPost32.length / 2; 533 } catch (MissingResourceException e) { 534 // Post-32bit transition data is optional 535 } 536 537 if (transitionCount > 0) { 538 transitionTimes64 = new long[transitionCount]; 539 int idx = 0; 540 if (transPre32 != null) { 541 for (int i = 0; i < transPre32.length / 2; i++, idx++) { 542 transitionTimes64[idx] = 543 ((transPre32[i * 2]) & 0x00000000FFFFFFFFL) << 32 544 | ((transPre32[i * 2 + 1]) & 0x00000000FFFFFFFFL); 545 } 546 } 547 if (trans32 != null) { 548 for (int i = 0; i < trans32.length; i++, idx++) { 549 transitionTimes64[idx] = trans32[i]; 550 } 551 } 552 if (transPost32 != null) { 553 for (int i = 0; i < transPost32.length / 2; i++, idx++) { 554 transitionTimes64[idx] = 555 ((transPost32[i * 2]) & 0x00000000FFFFFFFFL) << 32 556 | ((transPost32[i * 2 + 1]) & 0x00000000FFFFFFFFL); 557 } 558 } 559 } else { 560 transitionTimes64 = null; 561 } 562 563 // Type offsets list must be of even size, with size >= 2 564 r = res.get("typeOffsets"); 565 typeOffsets = r.getIntVector(); 566 if ((typeOffsets.length < 2 || typeOffsets.length > 0x7FFE || typeOffsets.length % 2 != 0)) { 567 throw new IllegalArgumentException("Invalid Format"); 568 } 569 typeCount = typeOffsets.length / 2; 570 571 // Type map data must be of the same size as the transition count 572 if (transitionCount > 0) { 573 r = res.get("typeMap"); 574 typeMapData = r.getBinary(null); 575 if (typeMapData == null || typeMapData.length != transitionCount) { 576 throw new IllegalArgumentException("Invalid Format"); 577 } 578 } else { 579 typeMapData = null; 580 } 581 582 // Process final rule and data, if any 583 finalZone = null; 584 finalStartYear = Integer.MAX_VALUE; 585 finalStartMillis = Double.MAX_VALUE; 586 587 String ruleID = null; 588 try { 589 ruleID = res.getString("finalRule"); 590 591 r = res.get("finalRaw"); 592 int ruleRaw = r.getInt() * Grego.MILLIS_PER_SECOND; 593 r = loadRule(top, ruleID); 594 int[] ruleData = r.getIntVector(); 595 596 if (ruleData == null || ruleData.length != 11) { 597 throw new IllegalArgumentException("Invalid Format"); 598 } 599 finalZone = new SimpleTimeZone(ruleRaw, "", 600 ruleData[0], ruleData[1], ruleData[2], 601 ruleData[3] * Grego.MILLIS_PER_SECOND, 602 ruleData[4], 603 ruleData[5], ruleData[6], ruleData[7], 604 ruleData[8] * Grego.MILLIS_PER_SECOND, 605 ruleData[9], 606 ruleData[10] * Grego.MILLIS_PER_SECOND); 607 608 r = res.get("finalYear"); 609 finalStartYear = r.getInt(); 610 611 // Note: Setting finalStartYear to the finalZone is problematic. When a date is around 612 // year boundary, SimpleTimeZone may return false result when DST is observed at the 613 // beginning of year. We could apply safe margin (day or two), but when one of recurrent 614 // rules falls around year boundary, it could return false result. Without setting the 615 // start year, finalZone works fine around the year boundary of the start year. 616 617 // finalZone.setStartYear(finalStartYear); 618 619 // Compute the millis for Jan 1, 0:00 GMT of the finalYear 620 621 // Note: finalStartMillis is used for detecting either if 622 // historic transition data or finalZone to be used. In an 623 // extreme edge case - for example, two transitions fall into 624 // small windows of time around the year boundary, this may 625 // result incorrect offset computation. But I think it will 626 // never happen practically. Yoshito - Feb 20, 2010 627 finalStartMillis = Grego.fieldsToDay(finalStartYear, 0, 1) * Grego.MILLIS_PER_DAY; 628 } catch (MissingResourceException e) { 629 if (ruleID != null) { 630 // ruleID is found, but missing other data required for 631 // creating finalZone 632 throw new IllegalArgumentException("Invalid Format"); 633 } 634 } 635 } 636 637 // This constructor is used for testing purpose only 638 public OlsonTimeZone(String id){ 639 super(id); 640 UResourceBundle top = UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, 641 ZONEINFORES, ICUResourceBundle.ICU_DATA_CLASS_LOADER); 642 UResourceBundle res = ZoneMeta.openOlsonResource(top, id); 643 construct(top, res); 644 if (finalZone != null){ 645 finalZone.setID(id); 646 } 647 } 648 649 /* (non-Javadoc) 650 * @see android.icu.util.TimeZone#setID(java.lang.String) 651 */ 652 @Override 653 public void setID(String id){ 654 if (isFrozen()) { 655 throw new UnsupportedOperationException("Attempt to modify a frozen OlsonTimeZone instance."); 656 } 657 658 // Before updating the ID, preserve the original ID's canonical ID. 659 if (canonicalID == null) { 660 canonicalID = getCanonicalID(getID()); 661 assert(canonicalID != null); 662 if (canonicalID == null) { 663 // This should never happen... 664 canonicalID = getID(); 665 } 666 } 667 668 if (finalZone != null){ 669 finalZone.setID(id); 670 } 671 super.setID(id); 672 transitionRulesInitialized = false; 673 } 674 675 // Maximum absolute offset in seconds = 1 day. 676 // getHistoricalOffset uses this constant as safety margin of 677 // quick zone transition checking. 678 private static final int MAX_OFFSET_SECONDS = 86400; // 60 * 60 * 24; 679 680 private void getHistoricalOffset(long date, boolean local, 681 int NonExistingTimeOpt, int DuplicatedTimeOpt, int[] offsets) { 682 if (transitionCount != 0) { 683 long sec = Grego.floorDivide(date, Grego.MILLIS_PER_SECOND); 684 if (!local && sec < transitionTimes64[0]) { 685 // Before the first transition time 686 offsets[0] = initialRawOffset() * Grego.MILLIS_PER_SECOND; 687 offsets[1] = initialDstOffset() * Grego.MILLIS_PER_SECOND; 688 } else { 689 // Linear search from the end is the fastest approach, since 690 // most lookups will happen at/near the end. 691 int transIdx; 692 for (transIdx = transitionCount - 1; transIdx >= 0; transIdx--) { 693 long transition = transitionTimes64[transIdx]; 694 if (local && (sec >= (transition - MAX_OFFSET_SECONDS))) { 695 int offsetBefore = zoneOffsetAt(transIdx - 1); 696 boolean dstBefore = dstOffsetAt(transIdx - 1) != 0; 697 698 int offsetAfter = zoneOffsetAt(transIdx); 699 boolean dstAfter = dstOffsetAt(transIdx) != 0; 700 701 boolean dstToStd = dstBefore && !dstAfter; 702 boolean stdToDst = !dstBefore && dstAfter; 703 704 if (offsetAfter - offsetBefore >= 0) { 705 // Positive transition, which makes a non-existing local time range 706 if (((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_STD && dstToStd) 707 || ((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_DST && stdToDst)) { 708 transition += offsetBefore; 709 } else if (((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_STD && stdToDst) 710 || ((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_DST && dstToStd)) { 711 transition += offsetAfter; 712 } else if ((NonExistingTimeOpt & FORMER_LATTER_MASK) == LOCAL_LATTER) { 713 transition += offsetBefore; 714 } else { 715 // Interprets the time with rule before the transition, 716 // default for non-existing time range 717 transition += offsetAfter; 718 } 719 } else { 720 // Negative transition, which makes a duplicated local time range 721 if (((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_STD && dstToStd) 722 || ((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_DST && stdToDst)) { 723 transition += offsetAfter; 724 } else if (((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_STD && stdToDst) 725 || ((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_DST && dstToStd)) { 726 transition += offsetBefore; 727 } else if ((DuplicatedTimeOpt & FORMER_LATTER_MASK) == LOCAL_FORMER) { 728 transition += offsetBefore; 729 } else { 730 // Interprets the time with rule after the transition, 731 // default for duplicated local time range 732 transition += offsetAfter; 733 } 734 } 735 } 736 if (sec >= transition) { 737 break; 738 } 739 } 740 // transIdx could be -1 when local=true 741 offsets[0] = rawOffsetAt(transIdx) * Grego.MILLIS_PER_SECOND; 742 offsets[1] = dstOffsetAt(transIdx) * Grego.MILLIS_PER_SECOND; 743 } 744 } else { 745 // No transitions, single pair of offsets only 746 offsets[0] = initialRawOffset() * Grego.MILLIS_PER_SECOND; 747 offsets[1] = initialDstOffset() * Grego.MILLIS_PER_SECOND; 748 } 749 } 750 751 private int getInt(byte val){ 752 return val & 0xFF; 753 } 754 755 /* 756 * Following 3 methods return an offset at the given transition time index. 757 * When the index is negative, return the initial offset. 758 */ 759 private int zoneOffsetAt(int transIdx) { 760 int typeIdx = transIdx >= 0 ? getInt(typeMapData[transIdx]) * 2 : 0; 761 return typeOffsets[typeIdx] + typeOffsets[typeIdx + 1]; 762 } 763 764 private int rawOffsetAt(int transIdx) { 765 int typeIdx = transIdx >= 0 ? getInt(typeMapData[transIdx]) * 2 : 0; 766 return typeOffsets[typeIdx]; 767 } 768 769 private int dstOffsetAt(int transIdx) { 770 int typeIdx = transIdx >= 0 ? getInt(typeMapData[transIdx]) * 2 : 0; 771 return typeOffsets[typeIdx + 1]; 772 } 773 774 private int initialRawOffset() { 775 return typeOffsets[0]; 776 } 777 778 private int initialDstOffset() { 779 return typeOffsets[1]; 780 } 781 782 // temp 783 @Override 784 public String toString() { 785 StringBuilder buf = new StringBuilder(); 786 buf.append(super.toString()); 787 buf.append('['); 788 buf.append("transitionCount=" + transitionCount); 789 buf.append(",typeCount=" + typeCount); 790 buf.append(",transitionTimes="); 791 if (transitionTimes64 != null) { 792 buf.append('['); 793 for (int i = 0; i < transitionTimes64.length; ++i) { 794 if (i > 0) { 795 buf.append(','); 796 } 797 buf.append(Long.toString(transitionTimes64[i])); 798 } 799 buf.append(']'); 800 } else { 801 buf.append("null"); 802 } 803 buf.append(",typeOffsets="); 804 if (typeOffsets != null) { 805 buf.append('['); 806 for (int i = 0; i < typeOffsets.length; ++i) { 807 if (i > 0) { 808 buf.append(','); 809 } 810 buf.append(Integer.toString(typeOffsets[i])); 811 } 812 buf.append(']'); 813 } else { 814 buf.append("null"); 815 } 816 buf.append(",typeMapData="); 817 if (typeMapData != null) { 818 buf.append('['); 819 for (int i = 0; i < typeMapData.length; ++i) { 820 if (i > 0) { 821 buf.append(','); 822 } 823 buf.append(Byte.toString(typeMapData[i])); 824 } 825 } else { 826 buf.append("null"); 827 } 828 buf.append(",finalStartYear=" + finalStartYear); 829 buf.append(",finalStartMillis=" + finalStartMillis); 830 buf.append(",finalZone=" + finalZone); 831 buf.append(']'); 832 833 return buf.toString(); 834 } 835 836 /** 837 * Number of transitions, 0..~370 838 */ 839 private int transitionCount; 840 841 /** 842 * Number of types, 1..255 843 */ 844 private int typeCount; 845 846 /** 847 * Time of each transition in seconds from 1970 epoch. 848 */ 849 private long[] transitionTimes64; 850 851 /** 852 * Offset from GMT in seconds for each type. 853 * Length is equal to typeCount 854 */ 855 private int[] typeOffsets; 856 857 /** 858 * Type description data, consisting of transitionCount uint8_t 859 * type indices (from 0..typeCount-1). 860 * Length is equal to transitionCount 861 */ 862 private byte[] typeMapData; 863 864 /** 865 * For year >= finalStartYear, the finalZone will be used. 866 */ 867 private int finalStartYear = Integer.MAX_VALUE; 868 869 /** 870 * For date >= finalStartMillis, the finalZone will be used. 871 */ 872 private double finalStartMillis = Double.MAX_VALUE; 873 874 /** 875 * A SimpleTimeZone that governs the behavior for years >= finalYear. 876 * If and only if finalYear == INT32_MAX then finalZone == 0. 877 */ 878 private SimpleTimeZone finalZone = null; // owned, may be NULL 879 880 /** 881 * The canonical ID of this zone. Initialized when {@link #getCanonicalID()} 882 * is invoked first time, or {@link #setID(String)} is called. 883 */ 884 private volatile String canonicalID = null; 885 886 private static final String ZONEINFORES = "zoneinfo64"; 887 888 private static final boolean DEBUG = ICUDebug.enabled("olson"); 889 private static final int SECONDS_PER_DAY = 24*60*60; 890 891 private static UResourceBundle loadRule(UResourceBundle top, String ruleid) { 892 UResourceBundle r = top.get("Rules"); 893 r = r.get(ruleid); 894 return r; 895 } 896 897 @Override 898 public boolean equals(Object obj){ 899 if (!super.equals(obj)) return false; // super does class check 900 901 OlsonTimeZone z = (OlsonTimeZone) obj; 902 903 return (Utility.arrayEquals(typeMapData, z.typeMapData) || 904 // If the pointers are not equal, the zones may still 905 // be equal if their rules and transitions are equal 906 (finalStartYear == z.finalStartYear && 907 // Don't compare finalMillis; if finalYear is ==, so is finalMillis 908 ((finalZone == null && z.finalZone == null) || 909 (finalZone != null && z.finalZone != null && 910 finalZone.equals(z.finalZone)) && 911 transitionCount == z.transitionCount && 912 typeCount == z.typeCount && 913 Utility.arrayEquals(transitionTimes64, z.transitionTimes64) && 914 Utility.arrayEquals(typeOffsets, z.typeOffsets) && 915 Utility.arrayEquals(typeMapData, z.typeMapData) 916 ))); 917 918 } 919 920 @Override 921 public int hashCode(){ 922 int ret = (int) (finalStartYear ^ (finalStartYear>>>4) + 923 transitionCount ^ (transitionCount>>>6) + 924 typeCount ^ (typeCount>>>8) + 925 Double.doubleToLongBits(finalStartMillis)+ 926 (finalZone == null ? 0 : finalZone.hashCode()) + 927 super.hashCode()); 928 if (transitionTimes64 != null) { 929 for(int i=0; i<transitionTimes64.length; i++){ 930 ret+=transitionTimes64[i]^(transitionTimes64[i]>>>8); 931 } 932 } 933 for(int i=0; i<typeOffsets.length; i++){ 934 ret+=typeOffsets[i]^(typeOffsets[i]>>>8); 935 } 936 if (typeMapData != null) { 937 for(int i=0; i<typeMapData.length; i++){ 938 ret+=typeMapData[i] & 0xFF; 939 } 940 } 941 return ret; 942 } 943 944 // 945 // BasicTimeZone methods 946 // 947 948 /* (non-Javadoc) 949 * @see android.icu.util.BasicTimeZone#getNextTransition(long, boolean) 950 */ 951 @Override 952 public TimeZoneTransition getNextTransition(long base, boolean inclusive) { 953 initTransitionRules(); 954 955 if (finalZone != null) { 956 if (inclusive && base == firstFinalTZTransition.getTime()) { 957 return firstFinalTZTransition; 958 } else if (base >= firstFinalTZTransition.getTime()) { 959 if (finalZone.useDaylightTime()) { 960 //return finalZone.getNextTransition(base, inclusive); 961 return finalZoneWithStartYear.getNextTransition(base, inclusive); 962 } else { 963 // No more transitions 964 return null; 965 } 966 } 967 } 968 if (historicRules != null) { 969 // Find a historical transition 970 int ttidx = transitionCount - 1; 971 for (; ttidx >= firstTZTransitionIdx; ttidx--) { 972 long t = transitionTimes64[ttidx] * Grego.MILLIS_PER_SECOND; 973 if (base > t || (!inclusive && base == t)) { 974 break; 975 } 976 } 977 if (ttidx == transitionCount - 1) { 978 return firstFinalTZTransition; 979 } else if (ttidx < firstTZTransitionIdx) { 980 return firstTZTransition; 981 } else { 982 // Create a TimeZoneTransition 983 TimeZoneRule to = historicRules[getInt(typeMapData[ttidx + 1])]; 984 TimeZoneRule from = historicRules[getInt(typeMapData[ttidx])]; 985 long startTime = transitionTimes64[ttidx+1] * Grego.MILLIS_PER_SECOND; 986 987 // The transitions loaded from zoneinfo.res may contain non-transition data 988 if (from.getName().equals(to.getName()) && from.getRawOffset() == to.getRawOffset() 989 && from.getDSTSavings() == to.getDSTSavings()) { 990 return getNextTransition(startTime, false); 991 } 992 993 return new TimeZoneTransition(startTime, from, to); 994 } 995 } 996 return null; 997 } 998 999 /* (non-Javadoc) 1000 * @see android.icu.util.BasicTimeZone#getPreviousTransition(long, boolean) 1001 */ 1002 @Override 1003 public TimeZoneTransition getPreviousTransition(long base, boolean inclusive) { 1004 initTransitionRules(); 1005 1006 if (finalZone != null) { 1007 if (inclusive && base == firstFinalTZTransition.getTime()) { 1008 return firstFinalTZTransition; 1009 } else if (base > firstFinalTZTransition.getTime()) { 1010 if (finalZone.useDaylightTime()) { 1011 //return finalZone.getPreviousTransition(base, inclusive); 1012 return finalZoneWithStartYear.getPreviousTransition(base, inclusive); 1013 } else { 1014 return firstFinalTZTransition; 1015 } 1016 } 1017 } 1018 1019 if (historicRules != null) { 1020 // Find a historical transition 1021 int ttidx = transitionCount - 1; 1022 for (; ttidx >= firstTZTransitionIdx; ttidx--) { 1023 long t = transitionTimes64[ttidx] * Grego.MILLIS_PER_SECOND; 1024 if (base > t || (inclusive && base == t)) { 1025 break; 1026 } 1027 } 1028 if (ttidx < firstTZTransitionIdx) { 1029 // No more transitions 1030 return null; 1031 } else if (ttidx == firstTZTransitionIdx) { 1032 return firstTZTransition; 1033 } else { 1034 // Create a TimeZoneTransition 1035 TimeZoneRule to = historicRules[getInt(typeMapData[ttidx])]; 1036 TimeZoneRule from = historicRules[getInt(typeMapData[ttidx-1])]; 1037 long startTime = transitionTimes64[ttidx] * Grego.MILLIS_PER_SECOND; 1038 1039 // The transitions loaded from zoneinfo.res may contain non-transition data 1040 if (from.getName().equals(to.getName()) && from.getRawOffset() == to.getRawOffset() 1041 && from.getDSTSavings() == to.getDSTSavings()) { 1042 return getPreviousTransition(startTime, false); 1043 } 1044 1045 return new TimeZoneTransition(startTime, from, to); 1046 } 1047 } 1048 return null; 1049 } 1050 1051 /* (non-Javadoc) 1052 * @see android.icu.util.BasicTimeZone#getTimeZoneRules() 1053 */ 1054 @Override 1055 public TimeZoneRule[] getTimeZoneRules() { 1056 initTransitionRules(); 1057 int size = 1; 1058 if (historicRules != null) { 1059 // historicRules may contain null entries when original zoneinfo data 1060 // includes non transition data. 1061 for (int i = 0; i < historicRules.length; i++) { 1062 if (historicRules[i] != null) { 1063 size++; 1064 } 1065 } 1066 } 1067 if (finalZone != null) { 1068 if (finalZone.useDaylightTime()) { 1069 size += 2; 1070 } else { 1071 size++; 1072 } 1073 } 1074 1075 TimeZoneRule[] rules = new TimeZoneRule[size]; 1076 int idx = 0; 1077 rules[idx++] = initialRule; 1078 1079 if (historicRules != null) { 1080 for (int i = 0; i < historicRules.length; i++) { 1081 if (historicRules[i] != null) { 1082 rules[idx++] = historicRules[i]; 1083 } 1084 } 1085 } 1086 1087 if (finalZone != null) { 1088 if (finalZone.useDaylightTime()) { 1089 TimeZoneRule[] stzr = finalZoneWithStartYear.getTimeZoneRules(); 1090 // Adding only transition rules 1091 rules[idx++] = stzr[1]; 1092 rules[idx++] = stzr[2]; 1093 } else { 1094 // Create a TimeArrayTimeZoneRule at finalMillis 1095 rules[idx++] = new TimeArrayTimeZoneRule(getID() + "(STD)", finalZone.getRawOffset(), 0, 1096 new long[] {(long)finalStartMillis}, DateTimeRule.UTC_TIME); 1097 } 1098 } 1099 return rules; 1100 } 1101 1102 private transient InitialTimeZoneRule initialRule; 1103 private transient TimeZoneTransition firstTZTransition; 1104 private transient int firstTZTransitionIdx; 1105 private transient TimeZoneTransition firstFinalTZTransition; 1106 private transient TimeArrayTimeZoneRule[] historicRules; 1107 private transient SimpleTimeZone finalZoneWithStartYear; // hack 1108 1109 private transient boolean transitionRulesInitialized; 1110 1111 private synchronized void initTransitionRules() { 1112 if (transitionRulesInitialized) { 1113 return; 1114 } 1115 1116 initialRule = null; 1117 firstTZTransition = null; 1118 firstFinalTZTransition = null; 1119 historicRules = null; 1120 firstTZTransitionIdx = 0; 1121 finalZoneWithStartYear = null; 1122 1123 String stdName = getID() + "(STD)"; 1124 String dstName = getID() + "(DST)"; 1125 1126 int raw, dst; 1127 1128 // Create initial rule 1129 raw = initialRawOffset() * Grego.MILLIS_PER_SECOND; 1130 dst = initialDstOffset() * Grego.MILLIS_PER_SECOND; 1131 initialRule = new InitialTimeZoneRule((dst == 0 ? stdName : dstName), raw, dst); 1132 1133 if (transitionCount > 0) { 1134 int transitionIdx, typeIdx; 1135 1136 // We probably no longer need to check the first "real" transition 1137 // here, because the new tzcode remove such transitions already. 1138 // For now, keeping this code for just in case. Feb 19, 2010 Yoshito 1139 for (transitionIdx = 0; transitionIdx < transitionCount; transitionIdx++) { 1140 if (getInt(typeMapData[transitionIdx]) != 0) { // type 0 is the initial type 1141 break; 1142 } 1143 firstTZTransitionIdx++; 1144 } 1145 if (transitionIdx == transitionCount) { 1146 // Actually no transitions... 1147 } else { 1148 // Build historic rule array 1149 long[] times = new long[transitionCount]; 1150 for (typeIdx = 0; typeIdx < typeCount; typeIdx++) { 1151 // Gather all start times for each pair of offsets 1152 int nTimes = 0; 1153 for (transitionIdx = firstTZTransitionIdx; transitionIdx < transitionCount; transitionIdx++) { 1154 if (typeIdx == getInt(typeMapData[transitionIdx])) { 1155 long tt = transitionTimes64[transitionIdx] * Grego.MILLIS_PER_SECOND; 1156 if (tt < finalStartMillis) { 1157 // Exclude transitions after finalMillis 1158 times[nTimes++] = tt; 1159 } 1160 } 1161 } 1162 if (nTimes > 0) { 1163 long[] startTimes = new long[nTimes]; 1164 System.arraycopy(times, 0, startTimes, 0, nTimes); 1165 // Create a TimeArrayTimeZoneRule 1166 raw = typeOffsets[typeIdx*2]*Grego.MILLIS_PER_SECOND; 1167 dst = typeOffsets[typeIdx*2 + 1]*Grego.MILLIS_PER_SECOND; 1168 if (historicRules == null) { 1169 historicRules = new TimeArrayTimeZoneRule[typeCount]; 1170 } 1171 historicRules[typeIdx] = new TimeArrayTimeZoneRule((dst == 0 ? stdName : dstName), 1172 raw, dst, startTimes, DateTimeRule.UTC_TIME); 1173 } 1174 } 1175 1176 // Create initial transition 1177 typeIdx = getInt(typeMapData[firstTZTransitionIdx]); 1178 firstTZTransition = new TimeZoneTransition(transitionTimes64[firstTZTransitionIdx] * Grego.MILLIS_PER_SECOND, 1179 initialRule, historicRules[typeIdx]); 1180 1181 } 1182 } 1183 1184 if (finalZone != null) { 1185 // Get the first occurrence of final rule starts 1186 long startTime = (long)finalStartMillis; 1187 TimeZoneRule firstFinalRule; 1188 if (finalZone.useDaylightTime()) { 1189 /* 1190 * Note: When an OlsonTimeZone is constructed, we should set the final year 1191 * as the start year of finalZone. However, the boundary condition used for 1192 * getting offset from finalZone has some problems. So setting the start year 1193 * in the finalZone will cause a problem. For now, we do not set the valid 1194 * start year when the construction time and create a clone and set the 1195 * start year when extracting rules. 1196 */ 1197 finalZoneWithStartYear = (SimpleTimeZone)finalZone.clone(); 1198 finalZoneWithStartYear.setStartYear(finalStartYear); 1199 1200 TimeZoneTransition tzt = finalZoneWithStartYear.getNextTransition(startTime, false); 1201 firstFinalRule = tzt.getTo(); 1202 startTime = tzt.getTime(); 1203 } else { 1204 finalZoneWithStartYear = finalZone; 1205 firstFinalRule = new TimeArrayTimeZoneRule(finalZone.getID(), 1206 finalZone.getRawOffset(), 0, new long[] {startTime}, DateTimeRule.UTC_TIME); 1207 } 1208 TimeZoneRule prevRule = null; 1209 if (transitionCount > 0) { 1210 prevRule = historicRules[getInt(typeMapData[transitionCount - 1])]; 1211 } 1212 if (prevRule == null) { 1213 // No historic transitions, but only finalZone available 1214 prevRule = initialRule; 1215 } 1216 firstFinalTZTransition = new TimeZoneTransition(startTime, prevRule, firstFinalRule); 1217 } 1218 1219 transitionRulesInitialized = true; 1220 } 1221 1222 // Note: This class does not support back level serialization compatibility 1223 // very well. ICU 4.4 introduced the 64bit transition data. It is probably 1224 // possible to implement this class to make old version of ICU to deserialize 1225 // object stream serialized by ICU 4.4+. However, such implementation will 1226 // introduce unnecessary complexity other than serialization support. 1227 // I decided to provide minimum level of backward compatibility, which 1228 // only support ICU 4.4+ to create an instance of OlsonTimeZone by reloading 1229 // the zone rules from bundles. ICU 4.2 or older version of ICU cannot 1230 // deserialize object stream created by ICU 4.4+. Yoshito -Feb 22, 2010 1231 1232 private static final int currentSerialVersion = 1; 1233 private int serialVersionOnStream = currentSerialVersion; 1234 1235 private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { 1236 stream.defaultReadObject(); 1237 1238 if (serialVersionOnStream < 1) { 1239 // No version - 4.2 or older 1240 // Just reloading the rule from bundle 1241 boolean initialized = false; 1242 String tzid = getID(); 1243 if (tzid != null) { 1244 try { 1245 UResourceBundle top = UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, 1246 ZONEINFORES, ICUResourceBundle.ICU_DATA_CLASS_LOADER); 1247 UResourceBundle res = ZoneMeta.openOlsonResource(top, tzid); 1248 construct(top, res); 1249 if (finalZone != null){ 1250 finalZone.setID(tzid); 1251 } 1252 initialized = true; 1253 } catch (Exception ignored) { 1254 // throw away 1255 } 1256 } 1257 if (!initialized) { 1258 // final resort 1259 constructEmpty(); 1260 } 1261 } 1262 1263 // need to rebuild transition rules when requested 1264 transitionRulesInitialized = false; 1265 } 1266 1267 // Freezable stuffs 1268 private transient volatile boolean isFrozen = false; 1269 1270 /* (non-Javadoc) 1271 * @see android.icu.util.TimeZone#isFrozen() 1272 */ 1273 @Override 1274 public boolean isFrozen() { 1275 return isFrozen; 1276 } 1277 1278 /* (non-Javadoc) 1279 * @see android.icu.util.TimeZone#freeze() 1280 */ 1281 @Override 1282 public TimeZone freeze() { 1283 isFrozen = true; 1284 return this; 1285 } 1286 1287 /* (non-Javadoc) 1288 * @see android.icu.util.TimeZone#cloneAsThawed() 1289 */ 1290 @Override 1291 public TimeZone cloneAsThawed() { 1292 OlsonTimeZone tz = (OlsonTimeZone)super.cloneAsThawed(); 1293 if (finalZone != null) { 1294 // TODO Do we really need this? 1295 finalZone.setID(getID()); 1296 tz.finalZone = (SimpleTimeZone) finalZone.clone(); 1297 } 1298 1299 // Following data are read-only and never changed. 1300 // Therefore, shallow copies should be sufficient. 1301 // 1302 // transitionTimes64 1303 // typeMapData 1304 // typeOffsets 1305 1306 tz.isFrozen = false; 1307 return tz; 1308 } 1309 } 1310