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