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 ohos.global.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 ohos.global.icu.util.AnnualTimeZoneRule; 19 import ohos.global.icu.util.BasicTimeZone; 20 import ohos.global.icu.util.Calendar; 21 import ohos.global.icu.util.DateTimeRule; 22 import ohos.global.icu.util.GregorianCalendar; 23 import ohos.global.icu.util.InitialTimeZoneRule; 24 import ohos.global.icu.util.SimpleTimeZone; 25 import ohos.global.icu.util.TimeArrayTimeZoneRule; 26 import ohos.global.icu.util.TimeZone; 27 import ohos.global.icu.util.TimeZoneRule; 28 import ohos.global.icu.util.TimeZoneTransition; 29 import ohos.global.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 exposed on OHOS 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 ohos.global.icu.util.TimeZone#getOffset(int, int, int, int, int, int) 126 */ 127 @Override getOffset(int era, int year, int month, int day, int dayOfWeek, int milliseconds)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 */ getOffset(int era, int year, int month,int dom, int dow, int millis, int monthLength)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 ohos.global.icu.util.TimeZone#setRawOffset(int) 173 */ 174 @Override setRawOffset(int offsetMillis)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 clone()260 public Object clone() { 261 if (isFrozen()) { 262 return this; 263 } 264 return cloneAsThawed(); 265 } 266 267 /** 268 * TimeZone API. 269 */ 270 @Override getOffset(long date, boolean local, int[] offsets)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 getOffsetFromLocal(long date, int nonExistingTimeOpt, int duplicatedTimeOpt, int[] offsets)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 ohos.global.icu.util.TimeZone#getRawOffset() 295 */ 296 @Override getRawOffset()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 ohos.global.icu.util.TimeZone#useDaylightTime() 305 */ 306 @Override useDaylightTime()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 ohos.global.icu.util.TimeZone#observesDaylightTime() 341 */ 342 @Override observesDaylightTime()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 getDSTSavings()377 public int getDSTSavings() { 378 if (finalZone != null){ 379 return finalZone.getDSTSavings(); 380 } 381 return super.getDSTSavings(); 382 } 383 384 /* (non-Javadoc) 385 * @see ohos.global.icu.util.TimeZone#inDaylightTime(java.util.Date) 386 */ 387 @Override inDaylightTime(Date date)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 ohos.global.icu.util.TimeZone#hasSameRules(ohos.global.icu.util.TimeZone) 396 */ 397 @Override hasSameRules(TimeZone other)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 */ getCanonicalID()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 */ constructEmpty()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 */ OlsonTimeZone(UResourceBundle top, UResourceBundle res, String id)484 public OlsonTimeZone(UResourceBundle top, UResourceBundle res, String id){ 485 super(id); 486 construct(top, res, id); 487 } 488 construct(UResourceBundle top, UResourceBundle res, String id)489 private void construct(UResourceBundle top, UResourceBundle res, String id){ 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, id, 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 OlsonTimeZone(String id)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, id); 644 } 645 646 /* (non-Javadoc) 647 * @see ohos.global.icu.util.TimeZone#setID(java.lang.String) 648 */ 649 @Override setID(String id)650 public void setID(String id){ 651 if (isFrozen()) { 652 throw new UnsupportedOperationException("Attempt to modify a frozen OlsonTimeZone instance."); 653 } 654 655 // Before updating the ID, preserve the original ID's canonical ID. 656 if (canonicalID == null) { 657 canonicalID = getCanonicalID(getID()); 658 assert(canonicalID != null); 659 if (canonicalID == null) { 660 // This should never happen... 661 canonicalID = getID(); 662 } 663 } 664 665 if (finalZone != null){ 666 finalZone.setID(id); 667 } 668 super.setID(id); 669 transitionRulesInitialized = false; 670 } 671 672 // Maximum absolute offset in seconds = 1 day. 673 // getHistoricalOffset uses this constant as safety margin of 674 // quick zone transition checking. 675 private static final int MAX_OFFSET_SECONDS = 86400; // 60 * 60 * 24; 676 getHistoricalOffset(long date, boolean local, int NonExistingTimeOpt, int DuplicatedTimeOpt, int[] offsets)677 private void getHistoricalOffset(long date, boolean local, 678 int NonExistingTimeOpt, int DuplicatedTimeOpt, int[] offsets) { 679 if (transitionCount != 0) { 680 long sec = Grego.floorDivide(date, Grego.MILLIS_PER_SECOND); 681 if (!local && sec < transitionTimes64[0]) { 682 // Before the first transition time 683 offsets[0] = initialRawOffset() * Grego.MILLIS_PER_SECOND; 684 offsets[1] = initialDstOffset() * Grego.MILLIS_PER_SECOND; 685 } else { 686 // Linear search from the end is the fastest approach, since 687 // most lookups will happen at/near the end. 688 int transIdx; 689 for (transIdx = transitionCount - 1; transIdx >= 0; transIdx--) { 690 long transition = transitionTimes64[transIdx]; 691 if (local && (sec >= (transition - MAX_OFFSET_SECONDS))) { 692 int offsetBefore = zoneOffsetAt(transIdx - 1); 693 boolean dstBefore = dstOffsetAt(transIdx - 1) != 0; 694 695 int offsetAfter = zoneOffsetAt(transIdx); 696 boolean dstAfter = dstOffsetAt(transIdx) != 0; 697 698 boolean dstToStd = dstBefore && !dstAfter; 699 boolean stdToDst = !dstBefore && dstAfter; 700 701 if (offsetAfter - offsetBefore >= 0) { 702 // Positive transition, which makes a non-existing local time range 703 if (((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_STD && dstToStd) 704 || ((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_DST && stdToDst)) { 705 transition += offsetBefore; 706 } else if (((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_STD && stdToDst) 707 || ((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_DST && dstToStd)) { 708 transition += offsetAfter; 709 } else if ((NonExistingTimeOpt & FORMER_LATTER_MASK) == LOCAL_LATTER) { 710 transition += offsetBefore; 711 } else { 712 // Interprets the time with rule before the transition, 713 // default for non-existing time range 714 transition += offsetAfter; 715 } 716 } else { 717 // Negative transition, which makes a duplicated local time range 718 if (((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_STD && dstToStd) 719 || ((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_DST && stdToDst)) { 720 transition += offsetAfter; 721 } else if (((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_STD && stdToDst) 722 || ((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_DST && dstToStd)) { 723 transition += offsetBefore; 724 } else if ((DuplicatedTimeOpt & FORMER_LATTER_MASK) == LOCAL_FORMER) { 725 transition += offsetBefore; 726 } else { 727 // Interprets the time with rule after the transition, 728 // default for duplicated local time range 729 transition += offsetAfter; 730 } 731 } 732 } 733 if (sec >= transition) { 734 break; 735 } 736 } 737 // transIdx could be -1 when local=true 738 offsets[0] = rawOffsetAt(transIdx) * Grego.MILLIS_PER_SECOND; 739 offsets[1] = dstOffsetAt(transIdx) * Grego.MILLIS_PER_SECOND; 740 } 741 } else { 742 // No transitions, single pair of offsets only 743 offsets[0] = initialRawOffset() * Grego.MILLIS_PER_SECOND; 744 offsets[1] = initialDstOffset() * Grego.MILLIS_PER_SECOND; 745 } 746 } 747 getInt(byte val)748 private int getInt(byte val){ 749 return val & 0xFF; 750 } 751 752 /* 753 * Following 3 methods return an offset at the given transition time index. 754 * When the index is negative, return the initial offset. 755 */ zoneOffsetAt(int transIdx)756 private int zoneOffsetAt(int transIdx) { 757 int typeIdx = transIdx >= 0 ? getInt(typeMapData[transIdx]) * 2 : 0; 758 return typeOffsets[typeIdx] + typeOffsets[typeIdx + 1]; 759 } 760 rawOffsetAt(int transIdx)761 private int rawOffsetAt(int transIdx) { 762 int typeIdx = transIdx >= 0 ? getInt(typeMapData[transIdx]) * 2 : 0; 763 return typeOffsets[typeIdx]; 764 } 765 dstOffsetAt(int transIdx)766 private int dstOffsetAt(int transIdx) { 767 int typeIdx = transIdx >= 0 ? getInt(typeMapData[transIdx]) * 2 : 0; 768 return typeOffsets[typeIdx + 1]; 769 } 770 initialRawOffset()771 private int initialRawOffset() { 772 return typeOffsets[0]; 773 } 774 initialDstOffset()775 private int initialDstOffset() { 776 return typeOffsets[1]; 777 } 778 779 // temp 780 @Override toString()781 public String toString() { 782 StringBuilder buf = new StringBuilder(); 783 buf.append(super.toString()); 784 buf.append('['); 785 buf.append("transitionCount=" + transitionCount); 786 buf.append(",typeCount=" + typeCount); 787 buf.append(",transitionTimes="); 788 if (transitionTimes64 != null) { 789 buf.append('['); 790 for (int i = 0; i < transitionTimes64.length; ++i) { 791 if (i > 0) { 792 buf.append(','); 793 } 794 buf.append(Long.toString(transitionTimes64[i])); 795 } 796 buf.append(']'); 797 } else { 798 buf.append("null"); 799 } 800 buf.append(",typeOffsets="); 801 if (typeOffsets != null) { 802 buf.append('['); 803 for (int i = 0; i < typeOffsets.length; ++i) { 804 if (i > 0) { 805 buf.append(','); 806 } 807 buf.append(Integer.toString(typeOffsets[i])); 808 } 809 buf.append(']'); 810 } else { 811 buf.append("null"); 812 } 813 buf.append(",typeMapData="); 814 if (typeMapData != null) { 815 buf.append('['); 816 for (int i = 0; i < typeMapData.length; ++i) { 817 if (i > 0) { 818 buf.append(','); 819 } 820 buf.append(Byte.toString(typeMapData[i])); 821 } 822 } else { 823 buf.append("null"); 824 } 825 buf.append(",finalStartYear=" + finalStartYear); 826 buf.append(",finalStartMillis=" + finalStartMillis); 827 buf.append(",finalZone=" + finalZone); 828 buf.append(']'); 829 830 return buf.toString(); 831 } 832 833 /** 834 * Number of transitions, 0..~370 835 */ 836 private int transitionCount; 837 838 /** 839 * Number of types, 1..255 840 */ 841 private int typeCount; 842 843 /** 844 * Time of each transition in seconds from 1970 epoch. 845 */ 846 private long[] transitionTimes64; 847 848 /** 849 * Offset from GMT in seconds for each type. 850 * Length is equal to typeCount 851 */ 852 private int[] typeOffsets; 853 854 /** 855 * Type description data, consisting of transitionCount uint8_t 856 * type indices (from 0..typeCount-1). 857 * Length is equal to transitionCount 858 */ 859 private byte[] typeMapData; 860 861 /** 862 * For year >= finalStartYear, the finalZone will be used. 863 */ 864 private int finalStartYear = Integer.MAX_VALUE; 865 866 /** 867 * For date >= finalStartMillis, the finalZone will be used. 868 */ 869 private double finalStartMillis = Double.MAX_VALUE; 870 871 /** 872 * A SimpleTimeZone that governs the behavior for years >= finalYear. 873 * If and only if finalYear == INT32_MAX then finalZone == 0. 874 */ 875 private SimpleTimeZone finalZone = null; // owned, may be NULL 876 877 /** 878 * The canonical ID of this zone. Initialized when {@link #getCanonicalID()} 879 * is invoked first time, or {@link #setID(String)} is called. 880 */ 881 private volatile String canonicalID = null; 882 883 private static final String ZONEINFORES = "zoneinfo64"; 884 885 private static final boolean DEBUG = ICUDebug.enabled("olson"); 886 private static final int SECONDS_PER_DAY = 24*60*60; 887 loadRule(UResourceBundle top, String ruleid)888 private static UResourceBundle loadRule(UResourceBundle top, String ruleid) { 889 UResourceBundle r = top.get("Rules"); 890 r = r.get(ruleid); 891 return r; 892 } 893 894 @Override equals(Object obj)895 public boolean equals(Object obj){ 896 if (!super.equals(obj)) return false; // super does class check 897 898 OlsonTimeZone z = (OlsonTimeZone) obj; 899 900 return (Utility.arrayEquals(typeMapData, z.typeMapData) || 901 // If the pointers are not equal, the zones may still 902 // be equal if their rules and transitions are equal 903 (finalStartYear == z.finalStartYear && 904 // Don't compare finalMillis; if finalYear is ==, so is finalMillis 905 ((finalZone == null && z.finalZone == null) || 906 (finalZone != null && z.finalZone != null && 907 finalZone.equals(z.finalZone)) && 908 transitionCount == z.transitionCount && 909 typeCount == z.typeCount && 910 Utility.arrayEquals(transitionTimes64, z.transitionTimes64) && 911 Utility.arrayEquals(typeOffsets, z.typeOffsets) && 912 Utility.arrayEquals(typeMapData, z.typeMapData) 913 ))); 914 915 } 916 917 @Override hashCode()918 public int hashCode(){ 919 int ret = (int) (finalStartYear ^ (finalStartYear>>>4) + 920 transitionCount ^ (transitionCount>>>6) + 921 typeCount ^ (typeCount>>>8) + 922 Double.doubleToLongBits(finalStartMillis)+ 923 (finalZone == null ? 0 : finalZone.hashCode()) + 924 super.hashCode()); 925 if (transitionTimes64 != null) { 926 for(int i=0; i<transitionTimes64.length; i++){ 927 ret+=transitionTimes64[i]^(transitionTimes64[i]>>>8); 928 } 929 } 930 for(int i=0; i<typeOffsets.length; i++){ 931 ret+=typeOffsets[i]^(typeOffsets[i]>>>8); 932 } 933 if (typeMapData != null) { 934 for(int i=0; i<typeMapData.length; i++){ 935 ret+=typeMapData[i] & 0xFF; 936 } 937 } 938 return ret; 939 } 940 941 // 942 // BasicTimeZone methods 943 // 944 945 /* (non-Javadoc) 946 * @see ohos.global.icu.util.BasicTimeZone#getNextTransition(long, boolean) 947 */ 948 @Override getNextTransition(long base, boolean inclusive)949 public TimeZoneTransition getNextTransition(long base, boolean inclusive) { 950 initTransitionRules(); 951 952 if (finalZone != null) { 953 if (inclusive && base == firstFinalTZTransition.getTime()) { 954 return firstFinalTZTransition; 955 } else if (base >= firstFinalTZTransition.getTime()) { 956 if (finalZone.useDaylightTime()) { 957 //return finalZone.getNextTransition(base, inclusive); 958 return finalZoneWithStartYear.getNextTransition(base, inclusive); 959 } else { 960 // No more transitions 961 return null; 962 } 963 } 964 } 965 if (historicRules != null) { 966 // Find a historical transition 967 int ttidx = transitionCount - 1; 968 for (; ttidx >= firstTZTransitionIdx; ttidx--) { 969 long t = transitionTimes64[ttidx] * Grego.MILLIS_PER_SECOND; 970 if (base > t || (!inclusive && base == t)) { 971 break; 972 } 973 } 974 if (ttidx == transitionCount - 1) { 975 return firstFinalTZTransition; 976 } else if (ttidx < firstTZTransitionIdx) { 977 return firstTZTransition; 978 } else { 979 // Create a TimeZoneTransition 980 TimeZoneRule to = historicRules[getInt(typeMapData[ttidx + 1])]; 981 TimeZoneRule from = historicRules[getInt(typeMapData[ttidx])]; 982 long startTime = transitionTimes64[ttidx+1] * Grego.MILLIS_PER_SECOND; 983 984 // The transitions loaded from zoneinfo.res may contain non-transition data 985 if (from.getName().equals(to.getName()) && from.getRawOffset() == to.getRawOffset() 986 && from.getDSTSavings() == to.getDSTSavings()) { 987 return getNextTransition(startTime, false); 988 } 989 990 return new TimeZoneTransition(startTime, from, to); 991 } 992 } 993 return null; 994 } 995 996 /* (non-Javadoc) 997 * @see ohos.global.icu.util.BasicTimeZone#getPreviousTransition(long, boolean) 998 */ 999 @Override getPreviousTransition(long base, boolean inclusive)1000 public TimeZoneTransition getPreviousTransition(long base, boolean inclusive) { 1001 initTransitionRules(); 1002 1003 if (finalZone != null) { 1004 if (inclusive && base == firstFinalTZTransition.getTime()) { 1005 return firstFinalTZTransition; 1006 } else if (base > firstFinalTZTransition.getTime()) { 1007 if (finalZone.useDaylightTime()) { 1008 //return finalZone.getPreviousTransition(base, inclusive); 1009 return finalZoneWithStartYear.getPreviousTransition(base, inclusive); 1010 } else { 1011 return firstFinalTZTransition; 1012 } 1013 } 1014 } 1015 1016 if (historicRules != null) { 1017 // Find a historical transition 1018 int ttidx = transitionCount - 1; 1019 for (; ttidx >= firstTZTransitionIdx; ttidx--) { 1020 long t = transitionTimes64[ttidx] * Grego.MILLIS_PER_SECOND; 1021 if (base > t || (inclusive && base == t)) { 1022 break; 1023 } 1024 } 1025 if (ttidx < firstTZTransitionIdx) { 1026 // No more transitions 1027 return null; 1028 } else if (ttidx == firstTZTransitionIdx) { 1029 return firstTZTransition; 1030 } else { 1031 // Create a TimeZoneTransition 1032 TimeZoneRule to = historicRules[getInt(typeMapData[ttidx])]; 1033 TimeZoneRule from = historicRules[getInt(typeMapData[ttidx-1])]; 1034 long startTime = transitionTimes64[ttidx] * Grego.MILLIS_PER_SECOND; 1035 1036 // The transitions loaded from zoneinfo.res may contain non-transition data 1037 if (from.getName().equals(to.getName()) && from.getRawOffset() == to.getRawOffset() 1038 && from.getDSTSavings() == to.getDSTSavings()) { 1039 return getPreviousTransition(startTime, false); 1040 } 1041 1042 return new TimeZoneTransition(startTime, from, to); 1043 } 1044 } 1045 return null; 1046 } 1047 1048 /* (non-Javadoc) 1049 * @see ohos.global.icu.util.BasicTimeZone#getTimeZoneRules() 1050 */ 1051 @Override getTimeZoneRules()1052 public TimeZoneRule[] getTimeZoneRules() { 1053 initTransitionRules(); 1054 int size = 1; 1055 if (historicRules != null) { 1056 // historicRules may contain null entries when original zoneinfo data 1057 // includes non transition data. 1058 for (int i = 0; i < historicRules.length; i++) { 1059 if (historicRules[i] != null) { 1060 size++; 1061 } 1062 } 1063 } 1064 if (finalZone != null) { 1065 if (finalZone.useDaylightTime()) { 1066 size += 2; 1067 } else { 1068 size++; 1069 } 1070 } 1071 1072 TimeZoneRule[] rules = new TimeZoneRule[size]; 1073 int idx = 0; 1074 rules[idx++] = initialRule; 1075 1076 if (historicRules != null) { 1077 for (int i = 0; i < historicRules.length; i++) { 1078 if (historicRules[i] != null) { 1079 rules[idx++] = historicRules[i]; 1080 } 1081 } 1082 } 1083 1084 if (finalZone != null) { 1085 if (finalZone.useDaylightTime()) { 1086 TimeZoneRule[] stzr = finalZoneWithStartYear.getTimeZoneRules(); 1087 // Adding only transition rules 1088 rules[idx++] = stzr[1]; 1089 rules[idx++] = stzr[2]; 1090 } else { 1091 // Create a TimeArrayTimeZoneRule at finalMillis 1092 rules[idx++] = new TimeArrayTimeZoneRule(getID() + "(STD)", finalZone.getRawOffset(), 0, 1093 new long[] {(long)finalStartMillis}, DateTimeRule.UTC_TIME); 1094 } 1095 } 1096 return rules; 1097 } 1098 1099 private transient InitialTimeZoneRule initialRule; 1100 private transient TimeZoneTransition firstTZTransition; 1101 private transient int firstTZTransitionIdx; 1102 private transient TimeZoneTransition firstFinalTZTransition; 1103 private transient TimeArrayTimeZoneRule[] historicRules; 1104 private transient SimpleTimeZone finalZoneWithStartYear; // hack 1105 1106 private transient boolean transitionRulesInitialized; 1107 initTransitionRules()1108 private synchronized void initTransitionRules() { 1109 if (transitionRulesInitialized) { 1110 return; 1111 } 1112 1113 initialRule = null; 1114 firstTZTransition = null; 1115 firstFinalTZTransition = null; 1116 historicRules = null; 1117 firstTZTransitionIdx = 0; 1118 finalZoneWithStartYear = null; 1119 1120 String stdName = getID() + "(STD)"; 1121 String dstName = getID() + "(DST)"; 1122 1123 int raw, dst; 1124 1125 // Create initial rule 1126 raw = initialRawOffset() * Grego.MILLIS_PER_SECOND; 1127 dst = initialDstOffset() * Grego.MILLIS_PER_SECOND; 1128 initialRule = new InitialTimeZoneRule((dst == 0 ? stdName : dstName), raw, dst); 1129 1130 if (transitionCount > 0) { 1131 int transitionIdx, typeIdx; 1132 1133 // We probably no longer need to check the first "real" transition 1134 // here, because the new tzcode remove such transitions already. 1135 // For now, keeping this code for just in case. Feb 19, 2010 Yoshito 1136 for (transitionIdx = 0; transitionIdx < transitionCount; transitionIdx++) { 1137 if (getInt(typeMapData[transitionIdx]) != 0) { // type 0 is the initial type 1138 break; 1139 } 1140 firstTZTransitionIdx++; 1141 } 1142 if (transitionIdx == transitionCount) { 1143 // Actually no transitions... 1144 } else { 1145 // Build historic rule array 1146 long[] times = new long[transitionCount]; 1147 for (typeIdx = 0; typeIdx < typeCount; typeIdx++) { 1148 // Gather all start times for each pair of offsets 1149 int nTimes = 0; 1150 for (transitionIdx = firstTZTransitionIdx; transitionIdx < transitionCount; transitionIdx++) { 1151 if (typeIdx == getInt(typeMapData[transitionIdx])) { 1152 long tt = transitionTimes64[transitionIdx] * Grego.MILLIS_PER_SECOND; 1153 if (tt < finalStartMillis) { 1154 // Exclude transitions after finalMillis 1155 times[nTimes++] = tt; 1156 } 1157 } 1158 } 1159 if (nTimes > 0) { 1160 long[] startTimes = new long[nTimes]; 1161 System.arraycopy(times, 0, startTimes, 0, nTimes); 1162 // Create a TimeArrayTimeZoneRule 1163 raw = typeOffsets[typeIdx*2]*Grego.MILLIS_PER_SECOND; 1164 dst = typeOffsets[typeIdx*2 + 1]*Grego.MILLIS_PER_SECOND; 1165 if (historicRules == null) { 1166 historicRules = new TimeArrayTimeZoneRule[typeCount]; 1167 } 1168 historicRules[typeIdx] = new TimeArrayTimeZoneRule((dst == 0 ? stdName : dstName), 1169 raw, dst, startTimes, DateTimeRule.UTC_TIME); 1170 } 1171 } 1172 1173 // Create initial transition 1174 typeIdx = getInt(typeMapData[firstTZTransitionIdx]); 1175 firstTZTransition = new TimeZoneTransition(transitionTimes64[firstTZTransitionIdx] * Grego.MILLIS_PER_SECOND, 1176 initialRule, historicRules[typeIdx]); 1177 1178 } 1179 } 1180 1181 if (finalZone != null) { 1182 // Get the first occurrence of final rule starts 1183 long startTime = (long)finalStartMillis; 1184 TimeZoneRule firstFinalRule; 1185 if (finalZone.useDaylightTime()) { 1186 /* 1187 * Note: When an OlsonTimeZone is constructed, we should set the final year 1188 * as the start year of finalZone. However, the boundary condition used for 1189 * getting offset from finalZone has some problems. So setting the start year 1190 * in the finalZone will cause a problem. For now, we do not set the valid 1191 * start year when the construction time and create a clone and set the 1192 * start year when extracting rules. 1193 */ 1194 finalZoneWithStartYear = (SimpleTimeZone)finalZone.clone(); 1195 finalZoneWithStartYear.setStartYear(finalStartYear); 1196 1197 TimeZoneTransition tzt = finalZoneWithStartYear.getNextTransition(startTime, false); 1198 firstFinalRule = tzt.getTo(); 1199 startTime = tzt.getTime(); 1200 } else { 1201 finalZoneWithStartYear = finalZone; 1202 firstFinalRule = new TimeArrayTimeZoneRule(finalZone.getID(), 1203 finalZone.getRawOffset(), 0, new long[] {startTime}, DateTimeRule.UTC_TIME); 1204 } 1205 TimeZoneRule prevRule = null; 1206 if (transitionCount > 0) { 1207 prevRule = historicRules[getInt(typeMapData[transitionCount - 1])]; 1208 } 1209 if (prevRule == null) { 1210 // No historic transitions, but only finalZone available 1211 prevRule = initialRule; 1212 } 1213 firstFinalTZTransition = new TimeZoneTransition(startTime, prevRule, firstFinalRule); 1214 } 1215 1216 transitionRulesInitialized = true; 1217 } 1218 1219 // Note: This class does not support back level serialization compatibility 1220 // very well. ICU 4.4 introduced the 64bit transition data. It is probably 1221 // possible to implement this class to make old version of ICU to deserialize 1222 // object stream serialized by ICU 4.4+. However, such implementation will 1223 // introduce unnecessary complexity other than serialization support. 1224 // I decided to provide minimum level of backward compatibility, which 1225 // only support ICU 4.4+ to create an instance of OlsonTimeZone by reloading 1226 // the zone rules from bundles. ICU 4.2 or older version of ICU cannot 1227 // deserialize object stream created by ICU 4.4+. Yoshito -Feb 22, 2010 1228 1229 private static final int currentSerialVersion = 1; 1230 private int serialVersionOnStream = currentSerialVersion; 1231 readObject(ObjectInputStream stream)1232 private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { 1233 stream.defaultReadObject(); 1234 1235 if (serialVersionOnStream < 1) { 1236 // No version - 4.2 or older 1237 // Just reloading the rule from bundle 1238 boolean initialized = false; 1239 String tzid = getID(); 1240 if (tzid != null) { 1241 try { 1242 UResourceBundle top = UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, 1243 ZONEINFORES, ICUResourceBundle.ICU_DATA_CLASS_LOADER); 1244 UResourceBundle res = ZoneMeta.openOlsonResource(top, tzid); 1245 construct(top, res, tzid); 1246 initialized = true; 1247 } catch (Exception ignored) { 1248 // throw away 1249 } 1250 } 1251 if (!initialized) { 1252 // final resort 1253 constructEmpty(); 1254 } 1255 } 1256 1257 // need to rebuild transition rules when requested 1258 transitionRulesInitialized = false; 1259 } 1260 1261 // Freezable stuffs 1262 private transient volatile boolean isFrozen = false; 1263 1264 /* (non-Javadoc) 1265 * @see ohos.global.icu.util.TimeZone#isFrozen() 1266 */ 1267 @Override isFrozen()1268 public boolean isFrozen() { 1269 return isFrozen; 1270 } 1271 1272 /* (non-Javadoc) 1273 * @see ohos.global.icu.util.TimeZone#freeze() 1274 */ 1275 @Override freeze()1276 public TimeZone freeze() { 1277 isFrozen = true; 1278 return this; 1279 } 1280 1281 /* (non-Javadoc) 1282 * @see ohos.global.icu.util.TimeZone#cloneAsThawed() 1283 */ 1284 @Override cloneAsThawed()1285 public TimeZone cloneAsThawed() { 1286 OlsonTimeZone tz = (OlsonTimeZone)super.cloneAsThawed(); 1287 if (finalZone != null) { 1288 tz.finalZone = (SimpleTimeZone) finalZone.clone(); 1289 } 1290 1291 // Following data are read-only and never changed. 1292 // Therefore, shallow copies should be sufficient. 1293 // 1294 // transitionTimes64 1295 // typeMapData 1296 // typeOffsets 1297 1298 tz.isFrozen = false; 1299 return tz; 1300 } 1301 } 1302