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