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