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) 2007-2014, International Business Machines Corporation and * 6 * others. All Rights Reserved. * 7 ******************************************************************************* 8 */ 9 package com.ibm.icu.util; 10 import java.util.ArrayList; 11 import java.util.BitSet; 12 import java.util.Date; 13 import java.util.List; 14 15 import com.ibm.icu.impl.Grego; 16 17 /** 18 * <code>RuleBasedTimeZone</code> is a concrete subclass of <code>TimeZone</code> that allows users to define 19 * custom historic time transition rules. 20 * 21 * @see com.ibm.icu.util.TimeZoneRule 22 * 23 * @stable ICU 3.8 24 */ 25 public class RuleBasedTimeZone extends BasicTimeZone { 26 27 private static final long serialVersionUID = 7580833058949327935L; 28 29 private final InitialTimeZoneRule initialRule; 30 private List<TimeZoneRule> historicRules; 31 private AnnualTimeZoneRule[] finalRules; 32 33 private transient List<TimeZoneTransition> historicTransitions; 34 private transient boolean upToDate; 35 36 /** 37 * Constructs a <code>RuleBasedTimeZone</code> object with the ID and the 38 * <code>InitialTimeZoneRule</code> 39 * 40 * @param id The time zone ID. 41 * @param initialRule The initial time zone rule. 42 * 43 * @stable ICU 3.8 44 */ 45 public RuleBasedTimeZone(String id, InitialTimeZoneRule initialRule) { 46 super(id); 47 this.initialRule = initialRule; 48 } 49 50 /** 51 * Adds the <code>TimeZoneRule</code> which represents time transitions. 52 * The <code>TimeZoneRule</code> must have start times, that is, the result 53 * of {@link com.ibm.icu.util.TimeZoneRule#isTransitionRule()} must be true. 54 * Otherwise, <code>IllegalArgumentException</code> is thrown. 55 * 56 * @param rule The <code>TimeZoneRule</code>. 57 * 58 * @stable ICU 3.8 59 */ 60 public void addTransitionRule(TimeZoneRule rule) { 61 if (isFrozen()) { 62 throw new UnsupportedOperationException("Attempt to modify a frozen RuleBasedTimeZone instance."); 63 } 64 if (!rule.isTransitionRule()) { 65 throw new IllegalArgumentException("Rule must be a transition rule"); 66 } 67 if (rule instanceof AnnualTimeZoneRule 68 && ((AnnualTimeZoneRule)rule).getEndYear() == AnnualTimeZoneRule.MAX_YEAR) { 69 // One of the final rules applicable in future forever 70 if (finalRules == null) { 71 finalRules = new AnnualTimeZoneRule[2]; 72 finalRules[0] = (AnnualTimeZoneRule)rule; 73 } else if (finalRules[1] == null) { 74 finalRules[1] = (AnnualTimeZoneRule)rule; 75 } else { 76 // Only a pair of AnnualTimeZoneRule is allowed. 77 throw new IllegalStateException("Too many final rules"); 78 } 79 } else { 80 // If this is not a final rule, add it to the historic rule list 81 if (historicRules == null) { 82 historicRules = new ArrayList<TimeZoneRule>(); 83 } 84 historicRules.add(rule); 85 } 86 // Mark dirty, so transitions are recalculated when offset information is 87 // accessed next time. 88 upToDate = false; 89 } 90 91 /** 92 * {@inheritDoc} 93 * 94 * @stable ICU 3.8 95 */ 96 @Override 97 public int getOffset(int era, int year, int month, int day, int dayOfWeek, 98 int milliseconds) { 99 if (era == GregorianCalendar.BC) { 100 // Convert to extended year 101 year = 1 - year; 102 } 103 long time = Grego.fieldsToDay(year, month, day) * Grego.MILLIS_PER_DAY + milliseconds; 104 int[] offsets = new int[2]; 105 getOffset(time, true, LOCAL_DST, LOCAL_STD, offsets); 106 return (offsets[0] + offsets[1]); 107 } 108 109 /** 110 * {@inheritDoc} 111 * 112 * @stable ICU 3.8 113 */ 114 @Override 115 public void getOffset(long time, boolean local, int[] offsets) { 116 getOffset(time, local, LOCAL_FORMER, LOCAL_LATTER, offsets); 117 } 118 119 /** 120 * {@inheritDoc} 121 * @internal 122 * @deprecated This API is ICU internal only. 123 */ 124 @Deprecated 125 @Override 126 public void getOffsetFromLocal(long date, 127 int nonExistingTimeOpt, int duplicatedTimeOpt, int[] offsets) { 128 getOffset(date, true, nonExistingTimeOpt, duplicatedTimeOpt, offsets); 129 } 130 131 /** 132 * {@inheritDoc} 133 * 134 * @stable ICU 3.8 135 */ 136 @Override 137 public int getRawOffset() { 138 // Note: This implementation returns standard GMT offset 139 // as of current time. 140 long now = System.currentTimeMillis(); 141 int[] offsets = new int[2]; 142 getOffset(now, false, offsets); 143 return offsets[0]; 144 } 145 146 /** 147 * {@inheritDoc} 148 * 149 * @stable ICU 3.8 150 */ 151 @Override 152 public boolean inDaylightTime(Date date) { 153 int[] offsets = new int[2]; 154 getOffset(date.getTime(), false, offsets); 155 return (offsets[1] != 0); 156 } 157 158 /** 159 * {@inheritDoc} 160 * 161 * @stable ICU 3.8 162 */ 163 @Override 164 ///CLOVER:OFF 165 public void setRawOffset(int offsetMillis) { 166 // TODO: Do nothing for now.. 167 throw new UnsupportedOperationException("setRawOffset in RuleBasedTimeZone is not supported."); 168 } 169 ///CLOVER:ON 170 171 /** 172 * {@inheritDoc} 173 * 174 * @stable ICU 3.8 175 */ 176 @Override 177 public boolean useDaylightTime() { 178 // Note: This implementation returns true when 179 // daylight saving time is used as of now or 180 // after the next transition. 181 long now = System.currentTimeMillis(); 182 int[] offsets = new int[2]; 183 getOffset(now, false, offsets); 184 if (offsets[1] != 0) { 185 return true; 186 } 187 // If DST is not used now, check if DST is used after the next transition 188 TimeZoneTransition tt = getNextTransition(now, false); 189 if (tt != null && tt.getTo().getDSTSavings() != 0) { 190 return true; 191 } 192 return false; 193 } 194 195 /** 196 * {@inheritDoc} 197 * @stable ICU 49 198 */ 199 @Override 200 public boolean observesDaylightTime() { 201 long time = System.currentTimeMillis(); 202 203 // Check if daylight saving time is observed now. 204 int[] offsets = new int[2]; 205 getOffset(time, false, offsets); 206 if (offsets[1] != 0) { 207 return true; 208 } 209 210 // If DST is not used now, check if DST is used after each transition. 211 BitSet checkFinals = finalRules == null ? null : new BitSet(finalRules.length); 212 while (true) { 213 TimeZoneTransition tt = getNextTransition(time, false); 214 if (tt == null) { 215 // no more transition 216 break; 217 } 218 TimeZoneRule toRule = tt.getTo(); 219 if (toRule.getDSTSavings() != 0) { 220 return true; 221 } 222 if (checkFinals != null) { 223 // final rules exist - check if we saw all of them 224 for (int i = 0; i < finalRules.length; i++) { 225 if (finalRules[i].equals(toRule)) { 226 checkFinals.set(i); 227 } 228 } 229 if (checkFinals.cardinality() == finalRules.length) { 230 // already saw all final rules 231 break; 232 } 233 } 234 time = tt.getTime(); 235 } 236 return false; 237 } 238 239 /** 240 * {@inheritDoc} 241 * 242 * @stable ICU 3.8 243 */ 244 @Override 245 public boolean hasSameRules(TimeZone other) { 246 if (this == other) { 247 return true; 248 } 249 250 if (!(other instanceof RuleBasedTimeZone)) { 251 // We cannot reasonably compare rules in different types 252 return false; 253 } 254 RuleBasedTimeZone otherRBTZ = (RuleBasedTimeZone)other; 255 256 // initial rule 257 if (!initialRule.isEquivalentTo(otherRBTZ.initialRule)) { 258 return false; 259 } 260 261 // final rules 262 if (finalRules != null && otherRBTZ.finalRules != null) { 263 for (int i = 0; i < finalRules.length; i++) { 264 if (finalRules[i] == null && otherRBTZ.finalRules[i] == null) { 265 continue; 266 } 267 if (finalRules[i] != null && otherRBTZ.finalRules[i] != null 268 && finalRules[i].isEquivalentTo(otherRBTZ.finalRules[i])) { 269 continue; 270 271 } 272 return false; 273 } 274 } else if (finalRules != null || otherRBTZ.finalRules != null) { 275 return false; 276 } 277 278 // historic rules 279 if (historicRules != null && otherRBTZ.historicRules != null) { 280 if (historicRules.size() != otherRBTZ.historicRules.size()) { 281 return false; 282 } 283 for (TimeZoneRule rule : historicRules) { 284 boolean foundSameRule = false; 285 for (TimeZoneRule orule : otherRBTZ.historicRules) { 286 if (rule.isEquivalentTo(orule)) { 287 foundSameRule = true; 288 break; 289 } 290 } 291 if (!foundSameRule) { 292 return false; 293 } 294 } 295 } else if (historicRules != null || otherRBTZ.historicRules != null) { 296 return false; 297 } 298 return true; 299 } 300 301 // BasicTimeZone methods 302 303 /** 304 * {@inheritDoc} 305 * 306 * @stable ICU 3.8 307 */ 308 @Override 309 public TimeZoneRule[] getTimeZoneRules() { 310 int size = 1; 311 if (historicRules != null) { 312 size += historicRules.size(); 313 } 314 315 if (finalRules != null) { 316 if (finalRules[1] != null) { 317 size += 2; 318 } else { 319 size++; 320 } 321 } 322 TimeZoneRule[] rules = new TimeZoneRule[size]; 323 rules[0] = initialRule; 324 325 int idx = 1; 326 if (historicRules != null) { 327 for (; idx < historicRules.size() + 1; idx++) { 328 rules[idx] = historicRules.get(idx - 1); 329 } 330 } 331 if (finalRules != null) { 332 rules[idx++] = finalRules[0]; 333 if (finalRules[1] != null) { 334 rules[idx] = finalRules[1]; 335 } 336 } 337 return rules; 338 } 339 340 /** 341 * {@inheritDoc} 342 * 343 * @stable ICU 3.8 344 */ 345 @Override 346 public TimeZoneTransition getNextTransition(long base, boolean inclusive) { 347 complete(); 348 if (historicTransitions == null) { 349 return null; 350 } 351 boolean isFinal = false; 352 TimeZoneTransition result; 353 TimeZoneTransition tzt = historicTransitions.get(0); 354 long tt = tzt.getTime(); 355 if (tt > base || (inclusive && tt == base)) { 356 result = tzt; 357 } else { 358 int idx = historicTransitions.size() - 1; 359 tzt = historicTransitions.get(idx); 360 tt = tzt.getTime(); 361 if (inclusive && tt == base) { 362 result = tzt; 363 } else if (tt <= base) { 364 if (finalRules != null) { 365 // Find a transion time with finalRules 366 Date start0 = finalRules[0].getNextStart(base, 367 finalRules[1].getRawOffset(), finalRules[1].getDSTSavings(), inclusive); 368 Date start1 = finalRules[1].getNextStart(base, 369 finalRules[0].getRawOffset(), finalRules[0].getDSTSavings(), inclusive); 370 371 if (start1.after(start0)) { 372 tzt = new TimeZoneTransition(start0.getTime(), finalRules[1], finalRules[0]); 373 } else { 374 tzt = new TimeZoneTransition(start1.getTime(), finalRules[0], finalRules[1]); 375 } 376 result = tzt; 377 isFinal = true; 378 } else { 379 return null; 380 } 381 } else { 382 // Find a transition within the historic transitions 383 idx--; 384 TimeZoneTransition prev = tzt; 385 while (idx > 0) { 386 tzt = historicTransitions.get(idx); 387 tt = tzt.getTime(); 388 if (tt < base || (!inclusive && tt == base)) { 389 break; 390 } 391 idx--; 392 prev = tzt; 393 } 394 result = prev; 395 } 396 } 397 // For now, this implementation ignore transitions with only zone name changes. 398 TimeZoneRule from = result.getFrom(); 399 TimeZoneRule to = result.getTo(); 400 if (from.getRawOffset() == to.getRawOffset() 401 && from.getDSTSavings() == to.getDSTSavings()) { 402 // No offset changes. Try next one if not final 403 if (isFinal) { 404 return null; 405 } else { 406 result = getNextTransition(result.getTime(), false /* always exclusive */); 407 } 408 } 409 return result; 410 } 411 412 /** 413 * {@inheritDoc} 414 * 415 * @stable ICU 3.8 416 */ 417 @Override 418 public TimeZoneTransition getPreviousTransition(long base, boolean inclusive) { 419 complete(); 420 if (historicTransitions == null) { 421 return null; 422 } 423 TimeZoneTransition result; 424 TimeZoneTransition tzt = historicTransitions.get(0); 425 long tt = tzt.getTime(); 426 if (inclusive && tt == base) { 427 result = tzt; 428 } else if (tt >= base) { 429 return null; 430 } else { 431 int idx = historicTransitions.size() - 1; 432 tzt = historicTransitions.get(idx); 433 tt = tzt.getTime(); 434 if (inclusive && tt == base) { 435 result = tzt; 436 } else if (tt < base) { 437 if (finalRules != null) { 438 // Find a transion time with finalRules 439 Date start0 = finalRules[0].getPreviousStart(base, 440 finalRules[1].getRawOffset(), finalRules[1].getDSTSavings(), inclusive); 441 Date start1 = finalRules[1].getPreviousStart(base, 442 finalRules[0].getRawOffset(), finalRules[0].getDSTSavings(), inclusive); 443 444 if (start1.before(start0)) { 445 tzt = new TimeZoneTransition(start0.getTime(), finalRules[1], finalRules[0]); 446 } else { 447 tzt = new TimeZoneTransition(start1.getTime(), finalRules[0], finalRules[1]); 448 } 449 } 450 result = tzt; 451 } else { 452 // Find a transition within the historic transitions 453 idx--; 454 while (idx >= 0) { 455 tzt = historicTransitions.get(idx); 456 tt = tzt.getTime(); 457 if (tt < base || (inclusive && tt == base)) { 458 break; 459 } 460 idx--; 461 } 462 result = tzt; 463 } 464 } 465 // For now, this implementation ignore transitions with only zone name changes. 466 TimeZoneRule from = result.getFrom(); 467 TimeZoneRule to = result.getTo(); 468 if (from.getRawOffset() == to.getRawOffset() 469 && from.getDSTSavings() == to.getDSTSavings()) { 470 // No offset changes. Try previous one 471 result = getPreviousTransition(result.getTime(), false /* always exclusive */); 472 } 473 return result; 474 } 475 476 /** 477 * {@inheritDoc} 478 * @stable ICU 3.8 479 */ 480 @Override 481 public Object clone() { 482 if (isFrozen()) { 483 return this; 484 } 485 return cloneAsThawed(); 486 } 487 488 // private stuff 489 490 /* 491 * Resolve historic transition times and update fields used for offset 492 * calculation. 493 */ 494 private void complete() { 495 if (upToDate) { 496 // No rules were added since last time. 497 return; 498 } 499 500 // Make sure either no final rules or a pair of AnnualTimeZoneRules 501 // are available. 502 if (finalRules != null && finalRules[1] == null) { 503 throw new IllegalStateException("Incomplete final rules"); 504 } 505 506 // Create a TimezoneTransition and add to the list 507 if (historicRules != null || finalRules != null) { 508 TimeZoneRule curRule = initialRule; 509 long lastTransitionTime = Grego.MIN_MILLIS; 510 511 // Build the transition array which represents historical time zone 512 // transitions. 513 if (historicRules != null) { 514 BitSet done = new BitSet(historicRules.size()); // for skipping rules already processed 515 516 while (true) { 517 int curStdOffset = curRule.getRawOffset(); 518 int curDstSavings = curRule.getDSTSavings(); 519 long nextTransitionTime = Grego.MAX_MILLIS; 520 TimeZoneRule nextRule = null; 521 Date d; 522 long tt; 523 524 for (int i = 0; i < historicRules.size(); i++) { 525 if (done.get(i)) { 526 continue; 527 } 528 TimeZoneRule r = historicRules.get(i); 529 d = r.getNextStart(lastTransitionTime, curStdOffset, curDstSavings, false); 530 if (d == null) { 531 // No more transitions from this rule - skip this rule next time 532 done.set(i); 533 } else { 534 if (r == curRule || 535 (r.getName().equals(curRule.getName()) 536 && r.getRawOffset() == curRule.getRawOffset() 537 && r.getDSTSavings() == curRule.getDSTSavings())) { 538 continue; 539 } 540 tt = d.getTime(); 541 if (tt < nextTransitionTime) { 542 nextTransitionTime = tt; 543 nextRule = r; 544 } 545 } 546 } 547 548 if (nextRule == null) { 549 // Check if all historic rules are done 550 boolean bDoneAll = true; 551 for (int j = 0; j < historicRules.size(); j++) { 552 if (!done.get(j)) { 553 bDoneAll = false; 554 break; 555 } 556 } 557 if (bDoneAll) { 558 break; 559 } 560 } 561 562 if (finalRules != null) { 563 // Check if one of final rules has earlier transition date 564 for (int i = 0; i < 2 /* finalRules.length */; i++) { 565 if (finalRules[i] == curRule) { 566 continue; 567 } 568 d = finalRules[i].getNextStart(lastTransitionTime, curStdOffset, curDstSavings, false); 569 if (d != null) { 570 tt = d.getTime(); 571 if (tt < nextTransitionTime) { 572 nextTransitionTime = tt; 573 nextRule = finalRules[i]; 574 } 575 } 576 } 577 } 578 579 if (nextRule == null) { 580 // Nothing more 581 break; 582 } 583 584 if (historicTransitions == null) { 585 historicTransitions = new ArrayList<TimeZoneTransition>(); 586 } 587 historicTransitions.add(new TimeZoneTransition(nextTransitionTime, curRule, nextRule)); 588 lastTransitionTime = nextTransitionTime; 589 curRule = nextRule; 590 } 591 } 592 if (finalRules != null) { 593 if (historicTransitions == null) { 594 historicTransitions = new ArrayList<TimeZoneTransition>(); 595 } 596 // Append the first transition for each 597 Date d0 = finalRules[0].getNextStart(lastTransitionTime, curRule.getRawOffset(), curRule.getDSTSavings(), false); 598 Date d1 = finalRules[1].getNextStart(lastTransitionTime, curRule.getRawOffset(), curRule.getDSTSavings(), false); 599 if (d1.after(d0)) { 600 historicTransitions.add(new TimeZoneTransition(d0.getTime(), curRule, finalRules[0])); 601 d1 = finalRules[1].getNextStart(d0.getTime(), finalRules[0].getRawOffset(), finalRules[0].getDSTSavings(), false); 602 historicTransitions.add(new TimeZoneTransition(d1.getTime(), finalRules[0], finalRules[1])); 603 } else { 604 historicTransitions.add(new TimeZoneTransition(d1.getTime(), curRule, finalRules[1])); 605 d0 = finalRules[0].getNextStart(d1.getTime(), finalRules[1].getRawOffset(), finalRules[1].getDSTSavings(), false); 606 historicTransitions.add(new TimeZoneTransition(d0.getTime(), finalRules[1], finalRules[0])); 607 } 608 } 609 } 610 upToDate = true; 611 } 612 613 /* 614 * getOffset internal implementation 615 */ 616 private void getOffset(long time, boolean local, int NonExistingTimeOpt, int DuplicatedTimeOpt, int[] offsets) { 617 complete(); 618 TimeZoneRule rule = null; 619 if (historicTransitions == null) { 620 rule = initialRule; 621 } else { 622 long tstart = getTransitionTime(historicTransitions.get(0), 623 local, NonExistingTimeOpt, DuplicatedTimeOpt); 624 if (time < tstart) { 625 rule = initialRule; 626 } else { 627 int idx = historicTransitions.size() - 1; 628 long tend = getTransitionTime(historicTransitions.get(idx), 629 local, NonExistingTimeOpt, DuplicatedTimeOpt); 630 if (time > tend) { 631 if (finalRules != null) { 632 rule = findRuleInFinal(time, local, NonExistingTimeOpt, DuplicatedTimeOpt); 633 } 634 if (rule == null) { 635 // no final rules or the given time is before the first transition 636 // specified by the final rules -> use the last rule 637 rule = (historicTransitions.get(idx)).getTo(); 638 } 639 } else { 640 // Find a historical transition 641 while (idx >= 0) { 642 if (time >= getTransitionTime(historicTransitions.get(idx), 643 local, NonExistingTimeOpt, DuplicatedTimeOpt)) { 644 break; 645 } 646 idx--; 647 } 648 rule = (historicTransitions.get(idx)).getTo(); 649 } 650 } 651 } 652 offsets[0] = rule.getRawOffset(); 653 offsets[1] = rule.getDSTSavings(); 654 } 655 656 /* 657 * Find a time zone rule applicable to the specified time 658 */ 659 private TimeZoneRule findRuleInFinal(long time, boolean local, int NonExistingTimeOpt, int DuplicatedTimeOpt) { 660 if (finalRules == null || finalRules.length != 2 || finalRules[0] == null || finalRules[1] == null) { 661 return null; 662 } 663 664 Date start0, start1; 665 long base; 666 int localDelta; 667 668 base = time; 669 if (local) { 670 localDelta = getLocalDelta(finalRules[1].getRawOffset(), finalRules[1].getDSTSavings(), 671 finalRules[0].getRawOffset(), finalRules[0].getDSTSavings(), 672 NonExistingTimeOpt, DuplicatedTimeOpt); 673 base -= localDelta; 674 } 675 start0 = finalRules[0].getPreviousStart(base, finalRules[1].getRawOffset(), finalRules[1].getDSTSavings(), true); 676 677 base = time; 678 if (local) { 679 localDelta = getLocalDelta(finalRules[0].getRawOffset(), finalRules[0].getDSTSavings(), 680 finalRules[1].getRawOffset(), finalRules[1].getDSTSavings(), 681 NonExistingTimeOpt, DuplicatedTimeOpt); 682 base -= localDelta; 683 } 684 start1 = finalRules[1].getPreviousStart(base, finalRules[0].getRawOffset(), finalRules[0].getDSTSavings(), true); 685 686 if (start0 == null || start1 == null) { 687 if (start0 != null) { 688 return finalRules[0]; 689 } else if (start1 != null) { 690 return finalRules[1]; 691 } 692 // Both rules take effect after the given time 693 return null; 694 } 695 696 return start0.after(start1) ? finalRules[0] : finalRules[1]; 697 } 698 699 /* 700 * Get the transition time in local wall clock 701 */ 702 private static long getTransitionTime(TimeZoneTransition tzt, boolean local, 703 int NonExistingTimeOpt, int DuplicatedTimeOpt) { 704 long time = tzt.getTime(); 705 if (local) { 706 time += getLocalDelta(tzt.getFrom().getRawOffset(), tzt.getFrom().getDSTSavings(), 707 tzt.getTo().getRawOffset(), tzt.getTo().getDSTSavings(), 708 NonExistingTimeOpt, DuplicatedTimeOpt); 709 } 710 return time; 711 } 712 713 /* 714 * Returns amount of local time adjustment used for checking rule transitions 715 */ 716 private static int getLocalDelta(int rawBefore, int dstBefore, int rawAfter, int dstAfter, 717 int NonExistingTimeOpt, int DuplicatedTimeOpt) { 718 int delta = 0; 719 720 int offsetBefore = rawBefore + dstBefore; 721 int offsetAfter = rawAfter + dstAfter; 722 723 boolean dstToStd = (dstBefore != 0) && (dstAfter == 0); 724 boolean stdToDst = (dstBefore == 0) && (dstAfter != 0); 725 726 if (offsetAfter - offsetBefore >= 0) { 727 // Positive transition, which makes a non-existing local time range 728 if (((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_STD && dstToStd) 729 || ((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_DST && stdToDst)) { 730 delta = offsetBefore; 731 } else if (((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_STD && stdToDst) 732 || ((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_DST && dstToStd)) { 733 delta = offsetAfter; 734 } else if ((NonExistingTimeOpt & FORMER_LATTER_MASK) == LOCAL_LATTER) { 735 delta = offsetBefore; 736 } else { 737 // Interprets the time with rule before the transition, 738 // default for non-existing time range 739 delta = offsetAfter; 740 } 741 } else { 742 // Negative transition, which makes a duplicated local time range 743 if (((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_STD && dstToStd) 744 || ((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_DST && stdToDst)) { 745 delta = offsetAfter; 746 } else if (((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_STD && stdToDst) 747 || ((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_DST && dstToStd)) { 748 delta = offsetBefore; 749 } else if ((DuplicatedTimeOpt & FORMER_LATTER_MASK) == LOCAL_FORMER) { 750 delta = offsetBefore; 751 } else { 752 // Interprets the time with rule after the transition, 753 // default for duplicated local time range 754 delta = offsetAfter; 755 } 756 } 757 return delta; 758 } 759 760 // Freezable stuffs 761 private volatile transient boolean isFrozen = false; 762 763 /** 764 * {@inheritDoc} 765 * @stable ICU 49 766 */ 767 public boolean isFrozen() { 768 return isFrozen; 769 } 770 771 /** 772 * {@inheritDoc} 773 * @stable ICU 49 774 */ 775 public TimeZone freeze() { 776 complete(); 777 isFrozen = true; 778 return this; 779 } 780 781 /** 782 * {@inheritDoc} 783 * @stable ICU 49 784 */ 785 public TimeZone cloneAsThawed() { 786 RuleBasedTimeZone tz = (RuleBasedTimeZone)super.cloneAsThawed(); 787 if (historicRules != null) { 788 tz.historicRules = new ArrayList<TimeZoneRule>(historicRules); // rules are immutable 789 } 790 if (finalRules != null) { 791 tz.finalRules = finalRules.clone(); 792 } 793 tz.isFrozen = false; 794 return tz; 795 } 796 } 797 798