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