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