2 *******************************************************************************
\r
3 * Copyright (C) 2005-2010, International Business Machines Corporation and *
\r
4 * others. All Rights Reserved. *
\r
5 *******************************************************************************
\r
7 package com.ibm.icu.impl;
\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
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
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
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
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
44 * a. Zone (table). A zone is a table resource contains several
\r
45 * type of resources below:
\r
47 * - typeOffsets:intvector (Required)
\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
54 * - trans:intvector (Optional)
\r
56 * List of transition times represented by 32bit seconds from the
\r
57 * epoch (1970-01-01T00:00Z) in ascending order.
\r
59 * - transPre32/transPost32:intvector (Optional)
\r
61 * List of transition times before/after 32bit minimum seconds.
\r
62 * Each time is represented by a pair of 32bit integer.
\r
64 * - typeMap:bin (Optional)
\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
70 * - finalRule:string (Optional)
\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
76 * - finalRaw:int (Optional)
\r
78 * When finalRule is available, finalRaw is required and specifies
\r
79 * the raw (base) offset of the rule.
\r
81 * - finalYear:int (Optional)
\r
83 * When finalRule is available, finalYear is required and specifies
\r
84 * the start year of the rule.
\r
86 * - links:intvector (Optional)
\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
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
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
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
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
121 * @see com.ibm.icu.util.TimeZone#getOffset(int, int, int, int, int, int)
\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
127 return getOffset(era, year, month, day, dayOfWeek, milliseconds, Grego.monthLength(year, month));
\r
134 public int getOffset(int era, int year, int month,int dom, int dow, int millis, int monthLength){
\r
136 if ((era != GregorianCalendar.AD && era != GregorianCalendar.BC)
\r
137 || month < Calendar.JANUARY
\r
138 || month > Calendar.DECEMBER
\r
140 || dom > monthLength
\r
141 || dow < Calendar.SUNDAY
\r
142 || dow > Calendar.SATURDAY
\r
144 || millis >= Grego.MILLIS_PER_DAY
\r
145 || monthLength < 28
\r
146 || monthLength > 31) {
\r
147 throw new IllegalArgumentException();
\r
150 if (era == GregorianCalendar.BC) {
\r
154 if (finalZone != null && year >= finalStartYear) {
\r
155 return finalZone.getOffset(era, year, month, dom, dow, millis);
\r
158 // Compute local epoch millis from input fields
\r
159 long time = Grego.fieldsToDay(year, month, dom) * Grego.MILLIS_PER_DAY + millis;
\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
167 * @see com.ibm.icu.util.TimeZone#setRawOffset(int)
\r
169 public void setRawOffset(int offsetMillis) {
\r
170 if (getRawOffset() == offsetMillis) {
\r
173 long current = System.currentTimeMillis();
\r
175 if (current < finalStartMillis) {
\r
176 SimpleTimeZone stz = new SimpleTimeZone(offsetMillis, getID());
\r
178 boolean bDst = useDaylightTime();
\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
188 currentRules = getSimpleTimeZoneRulesNear(tzt.getTime() - 1);
\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
201 if (offset1 > offset2) {
\r
202 start = r1.getRule();
\r
203 end = r2.getRule();
\r
204 sav = offset1 - offset2;
\r
206 start = r2.getRule();
\r
207 end = r1.getRule();
\r
208 sav = offset2 - offset1;
\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
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
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
229 int[] fields = Grego.timeToFields(current, null);
\r
231 finalStartYear = fields[0];
\r
232 finalStartMillis = Grego.fieldsToDay(fields[0], 0, 1);
\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
243 finalZone.setRawOffset(offsetMillis);
\r
246 transitionRulesInitialized = false;
\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
256 // Following data are read-only and never changed.
\r
257 // Therefore, shallow copies should be sufficient.
\r
260 if (transitionTimes64 != null) {
\r
261 other.transitionTimes64 = transitionTimes64.clone();
\r
263 if (typeMapData != null) {
\r
264 other.typeMapData = typeMapData.clone();
\r
266 other.typeOffsets = typeOffsets.clone();
\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
279 getHistoricalOffset(date, local,
\r
280 LOCAL_FORMER, LOCAL_LATTER, offsets);
\r
287 * @deprecated This API is ICU internal only.
\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
294 getHistoricalOffset(date, true, nonExistingTimeOpt, duplicatedTimeOpt, offsets);
\r
299 * @see com.ibm.icu.util.TimeZone#getRawOffset()
\r
301 public int getRawOffset() {
\r
302 int[] ret = new int[2];
\r
303 getOffset(System.currentTimeMillis(), false, ret);
\r
308 * @see com.ibm.icu.util.TimeZone#useDaylightTime()
\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
318 if (finalZone != null && current >= finalStartMillis) {
\r
319 return (finalZone != null && finalZone.useDaylightTime());
\r
322 int[] fields = Grego.timeToFields(current, null);
\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
328 // Return TRUE if DST is observed at any time during the current
\r
330 for (int i = 0; i < transitionCount; ++i) {
\r
331 if (transitionTimes64[i] >= limit) {
\r
334 if ((transitionTimes64[i] >= start && dstOffsetAt(i) != 0)
\r
335 || (transitionTimes64[i] > start && i > 0 && dstOffsetAt(i - 1) != 0)) {
\r
344 * Returns the amount of time to be added to local standard time
\r
345 * to get local wall clock time.
\r
347 public int getDSTSavings() {
\r
348 if (finalZone != null){
\r
349 return finalZone.getDSTSavings();
\r
351 return super.getDSTSavings();
\r
355 * @see com.ibm.icu.util.TimeZone#inDaylightTime(java.util.Date)
\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
364 * @see com.ibm.icu.util.TimeZone#hasSameRules(com.ibm.icu.util.TimeZone)
\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
373 if (!(other instanceof OlsonTimeZone)) {
\r
374 // We cannot reasonably compare rules in different types
\r
378 // Check final zone
\r
379 OlsonTimeZone o = (OlsonTimeZone)other;
\r
380 if (finalZone == null) {
\r
381 if (o.finalZone != null) {
\r
385 if (o.finalZone == null
\r
386 || finalStartYear != o.finalStartYear
\r
387 || !(finalZone.hasSameRules(o.finalZone))) {
\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
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
408 private void constructEmpty(){
\r
409 transitionCount = 0;
\r
410 transitionTimes64 = null;
\r
411 typeMapData = null;
\r
414 typeOffsets = new int[]{0,0};
\r
416 finalStartYear = Integer.MAX_VALUE;
\r
417 finalStartMillis = Double.MAX_VALUE;
\r
419 transitionRulesInitialized = false;
\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
428 public OlsonTimeZone(UResourceBundle top, UResourceBundle res){
\r
429 construct(top, res);
\r
432 private void construct(UResourceBundle top, UResourceBundle res){
\r
434 if ((top == null || res == null)) {
\r
435 throw new IllegalArgumentException();
\r
437 if(DEBUG) System.out.println("OlsonTimeZone(" + res.getKey() +")");
\r
440 int[] transPre32, trans32, transPost32;
\r
441 transPre32 = trans32 = transPost32 = null;
\r
443 transitionCount = 0;
\r
445 // Pre-32bit second transitions
\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
453 transitionCount += transPre32.length / 2;
\r
454 } catch (MissingResourceException e) {
\r
455 // Pre-32bit transition data is optional
\r
458 // 32bit second transitions
\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
467 // Post-32bit second transitions
\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
475 transitionCount += transPost32.length / 2;
\r
476 } catch (MissingResourceException e) {
\r
477 // Post-32bit transition data is optional
\r
480 transitionTimes64 = new long[transitionCount];
\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
489 if (trans32 != null) {
\r
490 for (int i = 0; i < trans32.length; i++, idx++) {
\r
491 transitionTimes64[idx] = (long)trans32[i];
\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
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
508 typeCount = typeOffsets.length / 2;
\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
520 // Process final rule and data, if any
\r
522 finalStartYear = Integer.MAX_VALUE;
\r
523 finalStartMillis = Double.MAX_VALUE;
\r
525 String ruleID = null;
\r
527 ruleID = res.getString("finalRule");
\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
534 if (ruleData == null || ruleData.length != 11) {
\r
535 throw new IllegalArgumentException("Invalid Format");
\r
537 finalZone = new SimpleTimeZone(ruleRaw, "",
\r
538 ruleData[0], ruleData[1], ruleData[2],
\r
539 ruleData[3] * Grego.MILLIS_PER_SECOND,
\r
541 ruleData[5], ruleData[6], ruleData[7],
\r
542 ruleData[8] * Grego.MILLIS_PER_SECOND,
\r
544 ruleData[10] * Grego.MILLIS_PER_SECOND);
\r
546 r = res.get("finalYear");
\r
547 finalStartYear = r.getInt();
\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
555 // finalZone.setStartYear(finalStartYear);
\r
557 // Compute the millis for Jan 1, 0:00 GMT of the finalYear
\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
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
587 public void setID(String id){
\r
588 if (finalZone != null){
\r
589 finalZone.setID(id);
\r
592 transitionRulesInitialized = false;
\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
604 // Linear search from the end is the fastest approach, since
\r
605 // most lookups will happen at/near the end.
\r
607 for (transIdx = transitionCount - 1; transIdx >= 0; transIdx--) {
\r
608 long transition = transitionTimes64[transIdx];
\r
610 int offsetBefore = zoneOffsetAt(transIdx - 1);
\r
611 boolean dstBefore = dstOffsetAt(transIdx - 1) != 0;
\r
613 int offsetAfter = zoneOffsetAt(transIdx);
\r
614 boolean dstAfter = dstOffsetAt(transIdx) != 0;
\r
616 boolean dstToStd = dstBefore && !dstAfter;
\r
617 boolean stdToDst = !dstBefore && dstAfter;
\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
630 // Interprets the time with rule before the transition,
\r
631 // default for non-existing time range
\r
632 transition += offsetAfter;
\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
645 // Interprets the time with rule after the transition,
\r
646 // default for duplicated local time range
\r
647 transition += offsetAfter;
\r
651 if (sec >= transition) {
\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
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
666 private int getInt(byte val){
\r
667 return val & 0xFF;
\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
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
679 private int rawOffsetAt(int transIdx) {
\r
680 int typeIdx = transIdx >= 0 ? getInt(typeMapData[transIdx]) * 2 : 0;
\r
681 return typeOffsets[typeIdx];
\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
689 private int initialRawOffset() {
\r
690 return typeOffsets[0];
\r
693 private int initialDstOffset() {
\r
694 return typeOffsets[1];
\r
698 public String toString() {
\r
699 StringBuilder buf = new StringBuilder();
\r
700 buf.append(super.toString());
\r
702 buf.append("transitionCount=" + transitionCount);
\r
703 buf.append(",typeCount=" + typeCount);
\r
704 buf.append(",transitionTimes=");
\r
705 if (transitionTimes64 != null) {
\r
707 for (int i = 0; i < transitionTimes64.length; ++i) {
\r
711 buf.append(Long.toString(transitionTimes64[i]));
\r
715 buf.append("null");
\r
717 buf.append(",typeOffsets=");
\r
718 if (typeOffsets != null) {
\r
720 for (int i = 0; i < typeOffsets.length; ++i) {
\r
724 buf.append(Integer.toString(typeOffsets[i]));
\r
728 buf.append("null");
\r
730 buf.append(",finalStartYear=" + finalStartYear);
\r
731 buf.append(",finalStartMillis=" + finalStartMillis);
\r
732 buf.append(",finalZone=" + finalZone);
\r
735 return buf.toString();
\r
739 * Number of transitions, 0..~370
\r
741 private int transitionCount;
\r
744 * Number of types, 1..255
\r
746 private int typeCount;
\r
749 * Time of each transition in seconds from 1970 epoch.
\r
751 private long[] transitionTimes64;
\r
754 * Offset from GMT in seconds for each type.
\r
755 * Length is equal to typeCount
\r
757 private int[] typeOffsets; // alias into res; do not delete
\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
764 private byte[] typeMapData; // alias into res; do not delete
\r
767 * For year >= finalStartYear, the finalZone will be used.
\r
769 private int finalStartYear = Integer.MAX_VALUE;
\r
772 * For date >= finalStartMillis, the finalZone will be used.
\r
774 private double finalStartMillis = Double.MAX_VALUE;
\r
777 * A SimpleTimeZone that governs the behavior for years >= finalYear.
\r
778 * If and only if finalYear == INT32_MAX then finalZone == 0.
\r
780 private SimpleTimeZone finalZone = null; // owned, may be NULL
\r
782 private static final String ZONEINFORES = "zoneinfo64";
\r
784 private static final boolean DEBUG = ICUDebug.enabled("olson");
\r
785 private static final int SECONDS_PER_DAY = 24*60*60;
\r
787 private static UResourceBundle loadRule(UResourceBundle top, String ruleid) {
\r
788 UResourceBundle r = top.get("Rules");
\r
793 public boolean equals(Object obj){
\r
794 if (!super.equals(obj)) return false; // super does class check
\r
796 OlsonTimeZone z = (OlsonTimeZone) obj;
\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
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
822 for(int i=0; i<transitionTimes64.length; i++){
\r
823 ret+=transitionTimes64[i]^(transitionTimes64[i]>>>8);
\r
825 for(int i=0; i<typeOffsets.length; i++){
\r
826 ret+=typeOffsets[i]^(typeOffsets[i]>>>8);
\r
828 for(int i=0; i<typeMapData.length; i++){
\r
829 ret+=typeMapData[i] & 0xFF;
\r
835 // BasicTimeZone methods
\r
839 * @see com.ibm.icu.util.BasicTimeZone#getNextTransition(long, boolean)
\r
841 public TimeZoneTransition getNextTransition(long base, boolean inclusive) {
\r
842 initTransitionRules();
\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
852 // No more transitions
\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
866 if (ttidx == transitionCount - 1) {
\r
867 return firstFinalTZTransition;
\r
868 } else if (ttidx < firstTZTransitionIdx) {
\r
869 return firstTZTransition;
\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
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
882 return new TimeZoneTransition(startTime, from, to);
\r
889 * @see com.ibm.icu.util.BasicTimeZone#getPreviousTransition(long, boolean)
\r
891 public TimeZoneTransition getPreviousTransition(long base, boolean inclusive) {
\r
892 initTransitionRules();
\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
902 return firstFinalTZTransition;
\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
916 if (ttidx < firstTZTransitionIdx) {
\r
917 // No more transitions
\r
919 } else if (ttidx == firstTZTransitionIdx) {
\r
920 return firstTZTransition;
\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
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
933 return new TimeZoneTransition(startTime, from, to);
\r
940 * @see com.ibm.icu.util.BasicTimeZone#getTimeZoneRules()
\r
942 public TimeZoneRule[] getTimeZoneRules() {
\r
943 initTransitionRules();
\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
954 if (finalZone != null) {
\r
955 if (finalZone.useDaylightTime()) {
\r
962 TimeZoneRule[] rules = new TimeZoneRule[size];
\r
964 rules[idx++] = initialRule;
\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
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
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
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
996 private transient boolean transitionRulesInitialized;
\r
998 private synchronized void initTransitionRules() {
\r
999 if (transitionRulesInitialized) {
\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
1010 String stdName = getID() + "(STD)";
\r
1011 String dstName = getID() + "(DST)";
\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
1020 if (transitionCount > 0) {
\r
1021 int transitionIdx, typeIdx;
\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
1030 firstTZTransitionIdx++;
\r
1032 if (transitionIdx == transitionCount) {
\r
1033 // Actually no transitions...
\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
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
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
1058 historicRules[typeIdx] = new TimeArrayTimeZoneRule((dst == 0 ? stdName : dstName),
\r
1059 raw, dst, startTimes, DateTimeRule.UTC_TIME);
\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
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
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
1084 finalZoneWithStartYear = (SimpleTimeZone)finalZone.clone();
\r
1085 finalZoneWithStartYear.setStartYear(finalStartYear);
\r
1087 TimeZoneTransition tzt = finalZoneWithStartYear.getNextTransition(startTime, false);
\r
1088 firstFinalRule = tzt.getTo();
\r
1089 startTime = tzt.getTime();
\r
1091 finalZoneWithStartYear = finalZone;
\r
1092 firstFinalRule = new TimeArrayTimeZoneRule(finalZone.getID(),
\r
1093 finalZone.getRawOffset(), 0, new long[] {startTime}, DateTimeRule.UTC_TIME);
\r
1095 TimeZoneRule prevRule = null;
\r
1096 if (transitionCount > 0) {
\r
1097 prevRule = historicRules[getInt(typeMapData[transitionCount - 1])];
\r
1099 if (prevRule == null) {
\r
1100 // No historic transitions, but only finalZone available
\r
1101 prevRule = initialRule;
\r
1103 firstFinalTZTransition = new TimeZoneTransition(startTime, prevRule, firstFinalRule);
\r
1106 transitionRulesInitialized = true;
\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
1119 private static final int currentSerialVersion = 1;
\r
1120 private int serialVersionOnStream = currentSerialVersion;
\r
1122 private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
\r
1123 stream.defaultReadObject();
\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
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
1139 initialized = true;
\r
1140 } catch (Exception e) {
\r
1144 if (!initialized) {
\r
1150 // need to rebuild transition rules when requested
\r
1151 transitionRulesInitialized = false;
\r