Home | History | Annotate | Download | only in util
      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