2 *******************************************************************************
\r
3 * Copyright (C) 2005-2009, International Business Machines Corporation and *
\r
4 * others. All Rights Reserved. *
\r
5 *******************************************************************************
\r
7 package com.ibm.icu.impl;
\r
9 import java.util.Arrays;
\r
10 import java.util.Date;
\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
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
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
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
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
47 * b. Simple zone (array, length 3). The three subelements are:
\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
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
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
66 * c. Simple zone with aliases (array, length 4). This is like a
\r
67 * simple zone, but also contains a fourth element:
\r
69 * iv. An intvector of aliases. This list includes this zone
\r
70 * itself, and lists all aliases of this zone.
\r
72 * d. Complex zone (array, length 5). This is like a simple zone,
\r
73 * but contains two more elements:
\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
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
84 * e. Complex zone with aliases (array, length 6). This is like a
\r
85 * complex zone, but also contains a sixth element:
\r
87 * vi. An intvector of aliases. This list includes this zone
\r
88 * itself, and lists all aliases of this zone.
\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
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
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
115 public class OlsonTimeZone extends BasicTimeZone {
\r
117 // Generated by serialver from JDK 1.4.1_01
\r
118 static final long serialVersionUID = -6281977362477515376L;
\r
120 private static final boolean ASSERT = false;
\r
123 * @see com.ibm.icu.util.TimeZone#getOffset(int, int, int, int, int, int)
\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
129 return getOffset(era, year, month, day, dayOfWeek, milliseconds, Grego.monthLength(year, month));
\r
136 public int getOffset(int era, int year, int month,int dom, int dow, int millis, int monthLength){
\r
138 if ((era != GregorianCalendar.AD && era != GregorianCalendar.BC)
\r
139 || month < Calendar.JANUARY
\r
140 || month > Calendar.DECEMBER
\r
142 || dom > monthLength
\r
143 || dow < Calendar.SUNDAY
\r
144 || dow > Calendar.SATURDAY
\r
146 || millis >= Grego.MILLIS_PER_DAY
\r
147 || monthLength < 28
\r
148 || monthLength > 31) {
\r
149 throw new IllegalArgumentException();
\r
152 if (era == GregorianCalendar.BC) {
\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
161 // Compute local epoch millis from input fields
\r
162 long time = Grego.fieldsToDay(year, month, dom) * Grego.MILLIS_PER_DAY + millis;
\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
170 * @see com.ibm.icu.util.TimeZone#setRawOffset(int)
\r
172 public void setRawOffset(int offsetMillis) {
\r
173 if (getRawOffset() == offsetMillis) {
\r
176 long current = System.currentTimeMillis();
\r
178 if (current < finalMillis) {
\r
179 SimpleTimeZone stz = new SimpleTimeZone(offsetMillis, getID());
\r
181 boolean bDst = useDaylightTime();
\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
191 currentRules = getSimpleTimeZoneRulesNear(tzt.getTime() - 1);
\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
204 if (offset1 > offset2) {
\r
205 start = r1.getRule();
\r
206 end = r2.getRule();
\r
207 sav = offset1 - offset2;
\r
209 start = r2.getRule();
\r
210 end = r1.getRule();
\r
211 sav = offset2 - offset1;
\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
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
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
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
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
245 finalZone.setRawOffset(offsetMillis);
\r
248 transitionRulesInitialized = false;
\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
257 other.transitionTimes = (int[])transitionTimes.clone();
\r
258 other.typeData = (byte[])typeData.clone();
\r
259 other.typeOffsets = (int[])typeOffsets.clone();
\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
273 getHistoricalOffset(date, local,
\r
274 LOCAL_FORMER, LOCAL_LATTER, offsets);
\r
281 * @deprecated This API is ICU internal only.
\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
288 getHistoricalOffset(date, true, nonExistingTimeOpt, duplicatedTimeOpt, offsets);
\r
293 * @see com.ibm.icu.util.TimeZone#getRawOffset()
\r
295 public int getRawOffset() {
\r
296 int[] ret = new int[2];
\r
297 getOffset( System.currentTimeMillis(), false, ret);
\r
302 * @see com.ibm.icu.util.TimeZone#useDaylightTime()
\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
313 if (year > finalYear) { // [sic] >, not >=; see above
\r
314 return (finalZone != null && finalZone.useDaylightTime());
\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
321 // Return TRUE if DST is observed at any time during the current
\r
323 for (int i = 0; i < transitionCount; ++i) {
\r
324 if (transitionTimes[i] >= limit) {
\r
327 if ((transitionTimes[i] >= start && dstOffset(typeData[i]) != 0)
\r
328 || (transitionTimes[i] > start && i > 0 && dstOffset(typeData[i - 1]) != 0)) {
\r
337 * Returns the amount of time to be added to local standard time
\r
338 * to get local wall clock time.
\r
340 public int getDSTSavings() {
\r
341 if(finalZone!=null){
\r
342 return finalZone.getDSTSavings();
\r
344 return super.getDSTSavings();
\r
348 * @see com.ibm.icu.util.TimeZone#inDaylightTime(java.util.Date)
\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
357 * @see com.ibm.icu.util.TimeZone#hasSameRules(com.ibm.icu.util.TimeZone)
\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
366 if (!(other instanceof OlsonTimeZone)) {
\r
367 // We cannot reasonably compare rules in different types
\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
378 if (o.finalZone == null
\r
379 || finalYear != o.finalYear
\r
380 || !(finalZone.hasSameRules(o.finalZone))) {
\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
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
401 private void constructEmpty(){
\r
402 transitionCount = 0;
\r
404 transitionTimes = typeOffsets = new int[]{0,0};
\r
405 typeData = new byte[2];
\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
415 public OlsonTimeZone(UResourceBundle top, UResourceBundle res){
\r
416 construct(top, res);
\r
419 private void construct(UResourceBundle top, UResourceBundle res){
\r
421 if ((top == null || res == null)) {
\r
422 throw new IllegalArgumentException();
\r
424 if(DEBUG) System.out.println("OlsonTimeZone(" + res.getKey() +")");
\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
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
443 // Transitions list may be empty
\r
444 UResourceBundle r = res.get(0);
\r
445 transitionTimes = r.getIntVector();
\r
447 if ((transitionTimes.length<0 || transitionTimes.length>0x7FFF) ) {
\r
448 throw new IllegalArgumentException("Invalid Format");
\r
450 transitionCount = (int) transitionTimes.length;
\r
452 // Type offsets list must be of even size, with size >= 2
\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
458 typeCount = (int) typeOffsets.length >> 1;
\r
460 // Type data must be of the same size as the transitions list
\r
462 typeData = r.getBinary().array();
\r
463 if (typeData.length != transitionCount) {
\r
464 throw new IllegalArgumentException("Invalid Format");
\r
467 // Process final rule and data, if any
\r
469 String ruleid = res.getString(3);
\r
471 int[] data = r.getIntVector();
\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
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
498 data[5], data[6], data[7],
\r
499 data[8] * Grego.MILLIS_PER_SECOND,
\r
501 data[10] * Grego.MILLIS_PER_SECOND);
\r
503 throw new IllegalArgumentException("Invalid Format");
\r
506 throw new IllegalArgumentException("Invalid Format");
\r
511 public OlsonTimeZone(){
\r
514 finalYear = Integer.MAX_VALUE;
\r
515 finalMillis = Double.MAX_VALUE;
\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
531 public void setID(String id){
\r
532 if(finalZone!= null){
\r
533 finalZone.setID(id);
\r
536 transitionRulesInitialized = false;
\r
539 private static final int UNSIGNED_BYTE_MASK =0xFF;
\r
541 private int getInt(byte val){
\r
542 return (int)(UNSIGNED_BYTE_MASK & val);
\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
552 for (i = transitionCount - 1; i > 0; --i) {
\r
553 int transition = transitionTimes[i];
\r
555 int offsetBefore = zoneOffset(getInt(typeData[i-1]));
\r
556 boolean dstBefore = dstOffset(getInt(typeData[i-1])) != 0;
\r
558 int offsetAfter = zoneOffset(getInt(typeData[i]));
\r
559 boolean dstAfter = dstOffset(getInt(typeData[i])) != 0;
\r
561 boolean dstToStd = dstBefore && !dstAfter;
\r
562 boolean stdToDst = !dstBefore && dstAfter;
\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
575 // Interprets the time with rule before the transition,
\r
576 // default for non-existing time range
\r
577 transition += offsetAfter;
\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
590 // Interprets the time with rule after the transition,
\r
591 // default for duplicated local time range
\r
592 transition += offsetAfter;
\r
597 if (sec >= transition) {
\r
602 if (ASSERT) Assert.assrt("i>=0 && i<transitionCount", i>=0 && i<transitionCount);
\r
604 // Check invariants for GMT times; if these pass for GMT times
\r
605 // the local logic should be working too.
\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
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
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
625 private int zoneOffset(int index){
\r
627 return typeOffsets[index] + typeOffsets[index+1];
\r
630 private int rawOffset(int index){
\r
631 return typeOffsets[(int)(index << 1)];
\r
634 private int dstOffset(int index){
\r
635 return typeOffsets[(int)((index << 1) + 1)];
\r
639 public String toString() {
\r
640 StringBuffer buf = new StringBuffer();
\r
641 buf.append(super.toString());
\r
643 buf.append("transitionCount=" + transitionCount);
\r
644 buf.append(",typeCount=" + typeCount);
\r
645 buf.append(",transitionTimes=");
\r
646 if (transitionTimes != null) {
\r
648 for (int i = 0; i < transitionTimes.length; ++i) {
\r
652 buf.append(Integer.toString(transitionTimes[i]));
\r
656 buf.append("null");
\r
658 buf.append(",typeOffsets=");
\r
659 if (typeOffsets != null) {
\r
661 for (int i = 0; i < typeOffsets.length; ++i) {
\r
665 buf.append(Integer.toString(typeOffsets[i]));
\r
669 buf.append("null");
\r
671 buf.append(",finalYear=" + finalYear);
\r
672 buf.append(",finalMillis=" + finalMillis);
\r
673 buf.append(",finalZone=" + finalZone);
\r
676 return buf.toString();
\r
680 * Number of transitions, 0..~370
\r
682 private int transitionCount;
\r
685 * Number of types, 1..255
\r
687 private int typeCount;
\r
690 * Time of each transition in seconds from 1970 epoch.
\r
691 * Length is transitionCount int32_t's.
\r
693 private int[] transitionTimes; // alias into res; do not delete
\r
696 * Offset from GMT in seconds for each type.
\r
697 * Length is typeCount int32_t's.
\r
699 private int[] typeOffsets; // alias into res; do not delete
\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
706 private byte[] typeData; // alias into res; do not delete
\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
714 private int finalYear = Integer.MAX_VALUE;
\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
721 private double finalMillis = Double.MAX_VALUE;
\r
724 * A SimpleTimeZone that governs the behavior for years > finalYear.
\r
725 * If and only if finalYear == INT32_MAX then finalZone == 0.
\r
727 private SimpleTimeZone finalZone = null; // owned, may be NULL
\r
729 private static final boolean DEBUG = ICUDebug.enabled("olson");
\r
730 private static final int SECONDS_PER_DAY = 24*60*60;
\r
732 private static UResourceBundle loadRule(UResourceBundle top, String ruleid) {
\r
733 UResourceBundle r = top.get("Rules");
\r
738 public boolean equals(Object obj){
\r
739 if (!super.equals(obj)) return false; // super does class check
\r
741 OlsonTimeZone z = (OlsonTimeZone) obj;
\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
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
767 for(int i=0; i<transitionTimes.length; i++){
\r
768 ret+=transitionTimes[i]^(transitionTimes[i]>>>8);
\r
770 for(int i=0; i<typeOffsets.length; i++){
\r
771 ret+=typeOffsets[i]^(typeOffsets[i]>>>8);
\r
773 for(int i=0; i<typeData.length; i++){
\r
774 ret+=typeData[i] & UNSIGNED_BYTE_MASK;
\r
779 private void readObject(ObjectInputStream s) throws IOException {
\r
780 s.defaultReadObject();
\r
781 // customized deserialization code
\r
783 // followed by code to update the object, if necessary
\r
789 // BasicTimeZone methods
\r
793 * @see com.ibm.icu.util.BasicTimeZone#getNextTransition(long, boolean)
\r
795 public TimeZoneTransition getNextTransition(long base, boolean inclusive) {
\r
796 initTransitionRules();
\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
806 // No more transitions
\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
820 if (ttidx == transitionCount - 1) {
\r
821 return firstFinalTZTransition;
\r
822 } else if (ttidx < firstTZTransitionIdx) {
\r
823 return firstTZTransition;
\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
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
836 return new TimeZoneTransition(startTime, from, to);
\r
843 * @see com.ibm.icu.util.BasicTimeZone#getPreviousTransition(long, boolean)
\r
845 public TimeZoneTransition getPreviousTransition(long base, boolean inclusive) {
\r
846 initTransitionRules();
\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
856 return firstFinalTZTransition;
\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
870 if (ttidx < firstTZTransitionIdx) {
\r
871 // No more transitions
\r
873 } else if (ttidx == firstTZTransitionIdx) {
\r
874 return firstTZTransition;
\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
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
887 return new TimeZoneTransition(startTime, from, to);
\r
894 * @see com.ibm.icu.util.BasicTimeZone#getTimeZoneRules()
\r
896 public TimeZoneRule[] getTimeZoneRules() {
\r
897 initTransitionRules();
\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
908 if (finalZone != null) {
\r
909 if (finalZone.useDaylightTime()) {
\r
916 TimeZoneRule[] rules = new TimeZoneRule[size];
\r
918 rules[idx++] = initialRule;
\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
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
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
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
950 private transient boolean transitionRulesInitialized;
\r
952 private synchronized void initTransitionRules() {
\r
953 if (transitionRulesInitialized) {
\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
964 String stdName = getID() + "(STD)";
\r
965 String dstName = getID() + "(DST)";
\r
968 if (transitionCount > 0) {
\r
969 int transitionIdx, typeIdx;
\r
971 // Note: Since 2007c, the very first transition data is a dummy entry
\r
972 // added for resolving a offset calculation problem.
\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
980 for (transitionIdx = 1; transitionIdx < transitionCount; transitionIdx++) {
\r
981 firstTZTransitionIdx++;
\r
982 if (typeIdx != getInt(typeData[transitionIdx])) {
\r
986 if (transitionIdx == transitionCount) {
\r
987 // Actually no transitions...
\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
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
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
1012 historicRules[typeIdx] = new TimeArrayTimeZoneRule((dst == 0 ? stdName : dstName),
\r
1013 raw, dst, startTimes, DateTimeRule.UTC_TIME);
\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
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
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
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
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
1050 TimeZoneTransition tzt = finalZoneWithStartYear.getNextTransition(startTime, false);
\r
1051 firstFinalRule = tzt.getTo();
\r
1052 startTime = tzt.getTime();
\r
1054 finalZoneWithStartYear = finalZone;
\r
1055 firstFinalRule = new TimeArrayTimeZoneRule(finalZone.getID(),
\r
1056 finalZone.getRawOffset(), 0, new long[] {startTime}, DateTimeRule.UTC_TIME);
\r
1058 TimeZoneRule prevRule = null;
\r
1059 if (transitionCount > 0) {
\r
1060 prevRule = historicRules[getInt(typeData[transitionCount - 1])];
\r
1062 if (prevRule == null) {
\r
1063 // No historic transitions, but only finalZone available
\r
1064 prevRule = initialRule;
\r
1066 firstFinalTZTransition = new TimeZoneTransition(startTime, prevRule, firstFinalRule);
\r
1069 transitionRulesInitialized = true;
\r