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