2 *******************************************************************************
3 * Copyright (C) 2005-2013, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 *******************************************************************************
7 package com.ibm.icu.impl;
9 import java.io.IOException;
10 import java.io.ObjectInputStream;
11 import java.util.Arrays;
12 import java.util.Date;
13 import java.util.MissingResourceException;
15 import com.ibm.icu.util.AnnualTimeZoneRule;
16 import com.ibm.icu.util.BasicTimeZone;
17 import com.ibm.icu.util.Calendar;
18 import com.ibm.icu.util.DateTimeRule;
19 import com.ibm.icu.util.GregorianCalendar;
20 import com.ibm.icu.util.InitialTimeZoneRule;
21 import com.ibm.icu.util.SimpleTimeZone;
22 import com.ibm.icu.util.TimeArrayTimeZoneRule;
23 import com.ibm.icu.util.TimeZone;
24 import com.ibm.icu.util.TimeZoneRule;
25 import com.ibm.icu.util.TimeZoneTransition;
26 import com.ibm.icu.util.UResourceBundle;
29 * A time zone based on the Olson tz database. Olson time zones change
30 * behavior over time. The raw offset, rules, presence or absence of
31 * daylight savings time, and even the daylight savings amount can all
34 * This class uses a resource bundle named "zoneinfo". Zoneinfo is a
35 * table containing different kinds of resources. In several places,
36 * zones are referred to using integers. A zone's integer is a number
37 * from 0..n-1, where n is the number of zones, with the zones sorted
38 * in lexicographic order.
40 * 1. Zones. These have keys corresponding to the Olson IDs, e.g.,
41 * "Asia/Shanghai". Each resource describes the behavior of the given
42 * zone. Zones come in two different formats.
44 * a. Zone (table). A zone is a table resource contains several
45 * type of resources below:
47 * - typeOffsets:intvector (Required)
49 * Sets of UTC raw/dst offset pairs in seconds. Entries at
50 * 2n represents raw offset and 2n+1 represents dst offset
51 * paired with the raw offset at 2n. The very first pair represents
52 * the initial zone offset (before the first transition) always.
54 * - trans:intvector (Optional)
56 * List of transition times represented by 32bit seconds from the
57 * epoch (1970-01-01T00:00Z) in ascending order.
59 * - transPre32/transPost32:intvector (Optional)
61 * List of transition times before/after 32bit minimum seconds.
62 * Each time is represented by a pair of 32bit integer.
64 * - typeMap:bin (Optional)
66 * Array of bytes representing the mapping between each transition
67 * time (transPre32/trans/transPost32) and its corresponding offset
70 * - finalRule:string (Optional)
72 * If a recurrent transition rule is applicable to a zone forever
73 * after the final transition time, finalRule represents the rule
76 * - finalRaw:int (Optional)
78 * When finalRule is available, finalRaw is required and specifies
79 * the raw (base) offset of the rule.
81 * - finalYear:int (Optional)
83 * When finalRule is available, finalYear is required and specifies
84 * the start year of the rule.
86 * - links:intvector (Optional)
88 * When this zone data is shared with other zones, links specifies
89 * all zones including the zone itself. Each zone is referenced by
92 * b. Link (int, length 1). A link zone is an int resource. The
93 * integer is the zone number of the target zone. The key of this
94 * resource is an alternate name for the target zone. This data
95 * is corresponding to Link data in the tz database.
98 * 2. Rules. These have keys corresponding to the Olson rule IDs,
99 * with an underscore prepended, e.g., "_EU". Each resource describes
100 * the behavior of the given rule using an intvector, containing the
101 * onset list, the cessation list, and the DST savings. The onset and
102 * cessation lists consist of the month, dowim, dow, time, and time
103 * mode. The end result is that the 11 integers describing the rule
104 * can be passed directly into the SimpleTimeZone 13-argument
105 * constructor (the other two arguments will be the raw offset, taken
106 * from the complex zone element 5, and the ID string, which is not
107 * used), with the times and the DST savings multiplied by 1000 to
108 * scale from seconds to milliseconds.
110 * 3. Regions. An array specifies mapping between zones and regions.
111 * Each item is either a 2-letter ISO country code or "001"
112 * (UN M.49 - World). This data is generated from "zone.tab"
113 * in the tz database.
115 public class OlsonTimeZone extends BasicTimeZone {
117 // Generated by serialver from JDK 1.4.1_01
118 static final long serialVersionUID = -6281977362477515376L;
121 * @see com.ibm.icu.util.TimeZone#getOffset(int, int, int, int, int, int)
124 public int getOffset(int era, int year, int month, int day, int dayOfWeek, int milliseconds) {
125 if (month < Calendar.JANUARY || month > Calendar.DECEMBER) {
126 throw new IllegalArgumentException("Month is not in the legal range: " +month);
128 return getOffset(era, year, month, day, dayOfWeek, milliseconds, Grego.monthLength(year, month));
135 public int getOffset(int era, int year, int month,int dom, int dow, int millis, int monthLength){
137 if ((era != GregorianCalendar.AD && era != GregorianCalendar.BC)
138 || month < Calendar.JANUARY
139 || month > Calendar.DECEMBER
142 || dow < Calendar.SUNDAY
143 || dow > Calendar.SATURDAY
145 || millis >= Grego.MILLIS_PER_DAY
147 || monthLength > 31) {
148 throw new IllegalArgumentException();
151 if (era == GregorianCalendar.BC) {
155 if (finalZone != null && year >= finalStartYear) {
156 return finalZone.getOffset(era, year, month, dom, dow, millis);
159 // Compute local epoch millis from input fields
160 long time = Grego.fieldsToDay(year, month, dom) * Grego.MILLIS_PER_DAY + millis;
162 int[] offsets = new int[2];
163 getHistoricalOffset(time, true, LOCAL_DST, LOCAL_STD, offsets);
164 return offsets[0] + offsets[1];
168 * @see com.ibm.icu.util.TimeZone#setRawOffset(int)
171 public void setRawOffset(int offsetMillis) {
173 throw new UnsupportedOperationException("Attempt to modify a frozen OlsonTimeZone instance.");
176 if (getRawOffset() == offsetMillis) {
179 long current = System.currentTimeMillis();
181 if (current < finalStartMillis) {
182 SimpleTimeZone stz = new SimpleTimeZone(offsetMillis, getID());
184 boolean bDst = useDaylightTime();
186 TimeZoneRule[] currentRules = getSimpleTimeZoneRulesNear(current);
187 if (currentRules.length != 3) {
188 // DST was observed at the beginning of this year, so useDaylightTime
189 // returned true. getSimpleTimeZoneRulesNear requires at least one
190 // future transition for making a pair of rules. This implementation
191 // rolls back the time before the latest offset transition.
192 TimeZoneTransition tzt = getPreviousTransition(current, false);
194 currentRules = getSimpleTimeZoneRulesNear(tzt.getTime() - 1);
197 if (currentRules.length == 3
198 && (currentRules[1] instanceof AnnualTimeZoneRule)
199 && (currentRules[2] instanceof AnnualTimeZoneRule)) {
200 // A pair of AnnualTimeZoneRule
201 AnnualTimeZoneRule r1 = (AnnualTimeZoneRule)currentRules[1];
202 AnnualTimeZoneRule r2 = (AnnualTimeZoneRule)currentRules[2];
203 DateTimeRule start, end;
204 int offset1 = r1.getRawOffset() + r1.getDSTSavings();
205 int offset2 = r2.getRawOffset() + r2.getDSTSavings();
207 if (offset1 > offset2) {
208 start = r1.getRule();
210 sav = offset1 - offset2;
212 start = r2.getRule();
214 sav = offset2 - offset1;
216 // getSimpleTimeZoneRulesNear always return rules using DOW / WALL_TIME
217 stz.setStartRule(start.getRuleMonth(), start.getRuleWeekInMonth(), start.getRuleDayOfWeek(),
218 start.getRuleMillisInDay());
219 stz.setEndRule(end.getRuleMonth(), end.getRuleWeekInMonth(), end.getRuleDayOfWeek(),
220 end.getRuleMillisInDay());
221 // set DST saving amount and start year
222 stz.setDSTSavings(sav);
224 // This could only happen if last rule is DST
225 // and the rule used forever. For example, Asia/Dhaka
226 // in tzdata2009i stays in DST forever.
228 // Hack - set DST starting at midnight on Jan 1st,
229 // ending 23:59:59.999 on Dec 31st
230 stz.setStartRule(0, 1, 0);
231 stz.setEndRule(11, 31, Grego.MILLIS_PER_DAY - 1);
235 int[] fields = Grego.timeToFields(current, null);
237 finalStartYear = fields[0];
238 finalStartMillis = Grego.fieldsToDay(fields[0], 0, 1);
241 // we probably do not need to set start year of final rule
242 // to finalzone itself, but we always do this for now.
243 stz.setStartYear(finalStartYear);
249 finalZone.setRawOffset(offsetMillis);
252 transitionRulesInitialized = false;
256 public Object clone() {
260 return cloneAsThawed();
267 public void getOffset(long date, boolean local, int[] offsets) {
268 if (finalZone != null && date >= finalStartMillis) {
269 finalZone.getOffset(date, local, offsets);
271 getHistoricalOffset(date, local,
272 LOCAL_FORMER, LOCAL_LATTER, offsets);
279 * @deprecated This API is ICU internal only.
282 public void getOffsetFromLocal(long date,
283 int nonExistingTimeOpt, int duplicatedTimeOpt, int[] offsets) {
284 if (finalZone != null && date >= finalStartMillis) {
285 finalZone.getOffsetFromLocal(date, nonExistingTimeOpt, duplicatedTimeOpt, offsets);
287 getHistoricalOffset(date, true, nonExistingTimeOpt, duplicatedTimeOpt, offsets);
292 * @see com.ibm.icu.util.TimeZone#getRawOffset()
295 public int getRawOffset() {
296 int[] ret = new int[2];
297 getOffset(System.currentTimeMillis(), false, ret);
302 * @see com.ibm.icu.util.TimeZone#useDaylightTime()
305 public boolean useDaylightTime() {
306 // If DST was observed in 1942 (for example) but has never been
307 // observed from 1943 to the present, most clients will expect
308 // this method to return FALSE. This method determines whether
309 // DST is in use in the current year (at any point in the year)
310 // and returns TRUE if so.
311 long current = System.currentTimeMillis();
313 if (finalZone != null && current >= finalStartMillis) {
314 return (finalZone != null && finalZone.useDaylightTime());
317 int[] fields = Grego.timeToFields(current, null);
319 // Find start of this year, and start of next year
320 long start = Grego.fieldsToDay(fields[0], 0, 1) * SECONDS_PER_DAY;
321 long limit = Grego.fieldsToDay(fields[0] + 1, 0, 1) * SECONDS_PER_DAY;
323 // Return TRUE if DST is observed at any time during the current
325 for (int i = 0; i < transitionCount; ++i) {
326 if (transitionTimes64[i] >= limit) {
329 if ((transitionTimes64[i] >= start && dstOffsetAt(i) != 0)
330 || (transitionTimes64[i] > start && i > 0 && dstOffsetAt(i - 1) != 0)) {
338 * @see com.ibm.icu.util.TimeZone#observesDaylightTime()
341 public boolean observesDaylightTime() {
342 long current = System.currentTimeMillis();
344 if (finalZone != null) {
345 if (finalZone.useDaylightTime()) {
347 } else if (current >= finalStartMillis) {
352 // Return TRUE if DST is observed at any future time
353 long currentSec = Grego.floorDivide(current, Grego.MILLIS_PER_SECOND);
354 int trsIdx = transitionCount - 1;
355 if (dstOffsetAt(trsIdx) != 0) {
358 while (trsIdx >= 0) {
359 if (transitionTimes64[trsIdx] <= currentSec) {
362 if (dstOffsetAt(trsIdx - 1) != 0) {
370 * Returns the amount of time to be added to local standard time
371 * to get local wall clock time.
374 public int getDSTSavings() {
375 if (finalZone != null){
376 return finalZone.getDSTSavings();
378 return super.getDSTSavings();
382 * @see com.ibm.icu.util.TimeZone#inDaylightTime(java.util.Date)
385 public boolean inDaylightTime(Date date) {
386 int[] temp = new int[2];
387 getOffset(date.getTime(), false, temp);
392 * @see com.ibm.icu.util.TimeZone#hasSameRules(com.ibm.icu.util.TimeZone)
395 public boolean hasSameRules(TimeZone other) {
399 // The super class implementation only check raw offset and
400 // use of daylight saving time.
401 if (!super.hasSameRules(other)) {
405 if (!(other instanceof OlsonTimeZone)) {
406 // We cannot reasonably compare rules in different types
411 OlsonTimeZone o = (OlsonTimeZone)other;
412 if (finalZone == null) {
413 if (o.finalZone != null) {
417 if (o.finalZone == null
418 || finalStartYear != o.finalStartYear
419 || !(finalZone.hasSameRules(o.finalZone))) {
424 // Note: The code below actually fails to compare two equivalent rules in
425 // different representation properly.
426 if (transitionCount != o.transitionCount ||
427 !Arrays.equals(transitionTimes64, o.transitionTimes64) ||
428 typeCount != o.typeCount ||
429 !Arrays.equals(typeMapData, o.typeMapData) ||
430 !Arrays.equals(typeOffsets, o.typeOffsets)){
437 * Returns the canonical ID of this system time zone
439 public String getCanonicalID() {
440 if (canonicalID == null) {
442 if (canonicalID == null) {
443 canonicalID = getCanonicalID(getID());
445 assert(canonicalID != null);
446 if (canonicalID == null) {
447 // This should never happen...
448 canonicalID = getID();
457 * Construct a GMT+0 zone with no transitions. This is done when a
458 * constructor fails so the resultant object is well-behaved.
460 private void constructEmpty(){
462 transitionTimes64 = null;
466 typeOffsets = new int[]{0,0};
468 finalStartYear = Integer.MAX_VALUE;
469 finalStartMillis = Double.MAX_VALUE;
471 transitionRulesInitialized = false;
475 * Construct from a resource bundle
476 * @param top the top-level zoneinfo resource bundle. This is used
477 * to lookup the rule that `res' may refer to, if there is one.
478 * @param res the resource bundle of the zone to be constructed
479 * @param id time zone ID
481 public OlsonTimeZone(UResourceBundle top, UResourceBundle res, String id){
486 private void construct(UResourceBundle top, UResourceBundle res){
488 if ((top == null || res == null)) {
489 throw new IllegalArgumentException();
491 if(DEBUG) System.out.println("OlsonTimeZone(" + res.getKey() +")");
494 int[] transPre32, trans32, transPost32;
495 transPre32 = trans32 = transPost32 = null;
499 // Pre-32bit second transitions
501 r = res.get("transPre32");
502 transPre32 = r.getIntVector();
503 if (transPre32.length % 2 != 0) {
504 // elements in the pre-32bit must be an even number
505 throw new IllegalArgumentException("Invalid Format");
507 transitionCount += transPre32.length / 2;
508 } catch (MissingResourceException e) {
509 // Pre-32bit transition data is optional
512 // 32bit second transitions
514 r = res.get("trans");
515 trans32 = r.getIntVector();
516 transitionCount += trans32.length;
517 } catch (MissingResourceException e) {
518 // 32bit transition data is optional
521 // Post-32bit second transitions
523 r = res.get("transPost32");
524 transPost32 = r.getIntVector();
525 if (transPost32.length % 2 != 0) {
526 // elements in the post-32bit must be an even number
527 throw new IllegalArgumentException("Invalid Format");
529 transitionCount += transPost32.length / 2;
530 } catch (MissingResourceException e) {
531 // Post-32bit transition data is optional
534 if (transitionCount > 0) {
535 transitionTimes64 = new long[transitionCount];
537 if (transPre32 != null) {
538 for (int i = 0; i < transPre32.length / 2; i++, idx++) {
539 transitionTimes64[idx] =
540 (((long)transPre32[i * 2]) & 0x00000000FFFFFFFFL) << 32
541 | (((long)transPre32[i * 2 + 1]) & 0x00000000FFFFFFFFL);
544 if (trans32 != null) {
545 for (int i = 0; i < trans32.length; i++, idx++) {
546 transitionTimes64[idx] = (long)trans32[i];
549 if (transPost32 != null) {
550 for (int i = 0; i < transPost32.length / 2; i++, idx++) {
551 transitionTimes64[idx] =
552 (((long)transPost32[i * 2]) & 0x00000000FFFFFFFFL) << 32
553 | (((long)transPost32[i * 2 + 1]) & 0x00000000FFFFFFFFL);
557 transitionTimes64 = null;
560 // Type offsets list must be of even size, with size >= 2
561 r = res.get("typeOffsets");
562 typeOffsets = r.getIntVector();
563 if ((typeOffsets.length < 2 || typeOffsets.length > 0x7FFE || typeOffsets.length % 2 != 0)) {
564 throw new IllegalArgumentException("Invalid Format");
566 typeCount = typeOffsets.length / 2;
568 // Type map data must be of the same size as the transition count
569 if (transitionCount > 0) {
570 r = res.get("typeMap");
571 typeMapData = r.getBinary(null);
572 if (typeMapData.length != transitionCount) {
573 throw new IllegalArgumentException("Invalid Format");
579 // Process final rule and data, if any
581 finalStartYear = Integer.MAX_VALUE;
582 finalStartMillis = Double.MAX_VALUE;
584 String ruleID = null;
586 ruleID = res.getString("finalRule");
588 r = res.get("finalRaw");
589 int ruleRaw = r.getInt() * Grego.MILLIS_PER_SECOND;
590 r = loadRule(top, ruleID);
591 int[] ruleData = r.getIntVector();
593 if (ruleData == null || ruleData.length != 11) {
594 throw new IllegalArgumentException("Invalid Format");
596 finalZone = new SimpleTimeZone(ruleRaw, "",
597 ruleData[0], ruleData[1], ruleData[2],
598 ruleData[3] * Grego.MILLIS_PER_SECOND,
600 ruleData[5], ruleData[6], ruleData[7],
601 ruleData[8] * Grego.MILLIS_PER_SECOND,
603 ruleData[10] * Grego.MILLIS_PER_SECOND);
605 r = res.get("finalYear");
606 finalStartYear = r.getInt();
608 // Note: Setting finalStartYear to the finalZone is problematic. When a date is around
609 // year boundary, SimpleTimeZone may return false result when DST is observed at the
610 // beginning of year. We could apply safe margin (day or two), but when one of recurrent
611 // rules falls around year boundary, it could return false result. Without setting the
612 // start year, finalZone works fine around the year boundary of the start year.
614 // finalZone.setStartYear(finalStartYear);
616 // Compute the millis for Jan 1, 0:00 GMT of the finalYear
618 // Note: finalStartMillis is used for detecting either if
619 // historic transition data or finalZone to be used. In an
620 // extreme edge case - for example, two transitions fall into
621 // small windows of time around the year boundary, this may
622 // result incorrect offset computation. But I think it will
623 // never happen practically. Yoshito - Feb 20, 2010
624 finalStartMillis = Grego.fieldsToDay(finalStartYear, 0, 1) * Grego.MILLIS_PER_DAY;
625 } catch (MissingResourceException e) {
626 if (ruleID != null) {
627 // ruleID is found, but missing other data required for
628 // creating finalZone
629 throw new IllegalArgumentException("Invalid Format");
634 // This constructor is used for testing purpose only
635 public OlsonTimeZone(String id){
637 UResourceBundle top = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME,
638 ZONEINFORES, ICUResourceBundle.ICU_DATA_CLASS_LOADER);
639 UResourceBundle res = ZoneMeta.openOlsonResource(top, id);
641 if (finalZone != null){
647 * @see com.ibm.icu.util.TimeZone#setID(java.lang.String)
650 public void setID(String id){
652 throw new UnsupportedOperationException("Attempt to modify a frozen OlsonTimeZone instance.");
655 // Before updating the ID, preserve the original ID's canonical ID.
656 if (canonicalID == null) {
657 canonicalID = getCanonicalID(getID());
658 assert(canonicalID != null);
659 if (canonicalID == null) {
660 // This should never happen...
661 canonicalID = getID();
665 if (finalZone != null){
669 transitionRulesInitialized = false;
672 // Maximum absolute offset in seconds = 1 day.
673 // getHistoricalOffset uses this constant as safety margin of
674 // quick zone transition checking.
675 private static final int MAX_OFFSET_SECONDS = 86400; // 60 * 60 * 24;
677 private void getHistoricalOffset(long date, boolean local,
678 int NonExistingTimeOpt, int DuplicatedTimeOpt, int[] offsets) {
679 if (transitionCount != 0) {
680 long sec = Grego.floorDivide(date, Grego.MILLIS_PER_SECOND);
681 if (!local && sec < transitionTimes64[0]) {
682 // Before the first transition time
683 offsets[0] = initialRawOffset() * Grego.MILLIS_PER_SECOND;
684 offsets[1] = initialDstOffset() * Grego.MILLIS_PER_SECOND;
686 // Linear search from the end is the fastest approach, since
687 // most lookups will happen at/near the end.
689 for (transIdx = transitionCount - 1; transIdx >= 0; transIdx--) {
690 long transition = transitionTimes64[transIdx];
691 if (local && (sec >= (transition - MAX_OFFSET_SECONDS))) {
692 int offsetBefore = zoneOffsetAt(transIdx - 1);
693 boolean dstBefore = dstOffsetAt(transIdx - 1) != 0;
695 int offsetAfter = zoneOffsetAt(transIdx);
696 boolean dstAfter = dstOffsetAt(transIdx) != 0;
698 boolean dstToStd = dstBefore && !dstAfter;
699 boolean stdToDst = !dstBefore && dstAfter;
701 if (offsetAfter - offsetBefore >= 0) {
702 // Positive transition, which makes a non-existing local time range
703 if (((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_STD && dstToStd)
704 || ((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_DST && stdToDst)) {
705 transition += offsetBefore;
706 } else if (((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_STD && stdToDst)
707 || ((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_DST && dstToStd)) {
708 transition += offsetAfter;
709 } else if ((NonExistingTimeOpt & FORMER_LATTER_MASK) == LOCAL_LATTER) {
710 transition += offsetBefore;
712 // Interprets the time with rule before the transition,
713 // default for non-existing time range
714 transition += offsetAfter;
717 // Negative transition, which makes a duplicated local time range
718 if (((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_STD && dstToStd)
719 || ((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_DST && stdToDst)) {
720 transition += offsetAfter;
721 } else if (((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_STD && stdToDst)
722 || ((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_DST && dstToStd)) {
723 transition += offsetBefore;
724 } else if ((DuplicatedTimeOpt & FORMER_LATTER_MASK) == LOCAL_FORMER) {
725 transition += offsetBefore;
727 // Interprets the time with rule after the transition,
728 // default for duplicated local time range
729 transition += offsetAfter;
733 if (sec >= transition) {
737 // transIdx could be -1 when local=true
738 offsets[0] = rawOffsetAt(transIdx) * Grego.MILLIS_PER_SECOND;
739 offsets[1] = dstOffsetAt(transIdx) * Grego.MILLIS_PER_SECOND;
742 // No transitions, single pair of offsets only
743 offsets[0] = initialRawOffset() * Grego.MILLIS_PER_SECOND;
744 offsets[1] = initialDstOffset() * Grego.MILLIS_PER_SECOND;
748 private int getInt(byte val){
753 * Following 3 methods return an offset at the given transition time index.
754 * When the index is negative, return the initial offset.
756 private int zoneOffsetAt(int transIdx) {
757 int typeIdx = transIdx >= 0 ? getInt(typeMapData[transIdx]) * 2 : 0;
758 return typeOffsets[typeIdx] + typeOffsets[typeIdx + 1];
761 private int rawOffsetAt(int transIdx) {
762 int typeIdx = transIdx >= 0 ? getInt(typeMapData[transIdx]) * 2 : 0;
763 return typeOffsets[typeIdx];
766 private int dstOffsetAt(int transIdx) {
767 int typeIdx = transIdx >= 0 ? getInt(typeMapData[transIdx]) * 2 : 0;
768 return typeOffsets[typeIdx + 1];
771 private int initialRawOffset() {
772 return typeOffsets[0];
775 private int initialDstOffset() {
776 return typeOffsets[1];
781 public String toString() {
782 StringBuilder buf = new StringBuilder();
783 buf.append(super.toString());
785 buf.append("transitionCount=" + transitionCount);
786 buf.append(",typeCount=" + typeCount);
787 buf.append(",transitionTimes=");
788 if (transitionTimes64 != null) {
790 for (int i = 0; i < transitionTimes64.length; ++i) {
794 buf.append(Long.toString(transitionTimes64[i]));
800 buf.append(",typeOffsets=");
801 if (typeOffsets != null) {
803 for (int i = 0; i < typeOffsets.length; ++i) {
807 buf.append(Integer.toString(typeOffsets[i]));
813 buf.append(",typeMapData=");
814 if (typeMapData != null) {
816 for (int i = 0; i < typeMapData.length; ++i) {
820 buf.append(Byte.toString(typeMapData[i]));
825 buf.append(",finalStartYear=" + finalStartYear);
826 buf.append(",finalStartMillis=" + finalStartMillis);
827 buf.append(",finalZone=" + finalZone);
830 return buf.toString();
834 * Number of transitions, 0..~370
836 private int transitionCount;
839 * Number of types, 1..255
841 private int typeCount;
844 * Time of each transition in seconds from 1970 epoch.
846 private long[] transitionTimes64;
849 * Offset from GMT in seconds for each type.
850 * Length is equal to typeCount
852 private int[] typeOffsets;
855 * Type description data, consisting of transitionCount uint8_t
856 * type indices (from 0..typeCount-1).
857 * Length is equal to transitionCount
859 private byte[] typeMapData;
862 * For year >= finalStartYear, the finalZone will be used.
864 private int finalStartYear = Integer.MAX_VALUE;
867 * For date >= finalStartMillis, the finalZone will be used.
869 private double finalStartMillis = Double.MAX_VALUE;
872 * A SimpleTimeZone that governs the behavior for years >= finalYear.
873 * If and only if finalYear == INT32_MAX then finalZone == 0.
875 private SimpleTimeZone finalZone = null; // owned, may be NULL
878 * The canonical ID of this zone. Initialized when {@link #getCanonicalID()}
879 * is invoked first time, or {@link #setID(String)} is called.
881 private volatile String canonicalID = null;
883 private static final String ZONEINFORES = "zoneinfo64";
885 private static final boolean DEBUG = ICUDebug.enabled("olson");
886 private static final int SECONDS_PER_DAY = 24*60*60;
888 private static UResourceBundle loadRule(UResourceBundle top, String ruleid) {
889 UResourceBundle r = top.get("Rules");
895 public boolean equals(Object obj){
896 if (!super.equals(obj)) return false; // super does class check
898 OlsonTimeZone z = (OlsonTimeZone) obj;
900 return (Utility.arrayEquals(typeMapData, z.typeMapData) ||
901 // If the pointers are not equal, the zones may still
902 // be equal if their rules and transitions are equal
903 (finalStartYear == z.finalStartYear &&
904 // Don't compare finalMillis; if finalYear is ==, so is finalMillis
905 ((finalZone == null && z.finalZone == null) ||
906 (finalZone != null && z.finalZone != null &&
907 finalZone.equals(z.finalZone)) &&
908 transitionCount == z.transitionCount &&
909 typeCount == z.typeCount &&
910 Utility.arrayEquals(transitionTimes64, z.transitionTimes64) &&
911 Utility.arrayEquals(typeOffsets, z.typeOffsets) &&
912 Utility.arrayEquals(typeMapData, z.typeMapData)
918 public int hashCode(){
919 int ret = (int) (finalStartYear ^ (finalStartYear>>>4) +
920 transitionCount ^ (transitionCount>>>6) +
921 typeCount ^ (typeCount>>>8) +
922 Double.doubleToLongBits(finalStartMillis)+
923 (finalZone == null ? 0 : finalZone.hashCode()) +
925 if (transitionTimes64 != null) {
926 for(int i=0; i<transitionTimes64.length; i++){
927 ret+=transitionTimes64[i]^(transitionTimes64[i]>>>8);
930 for(int i=0; i<typeOffsets.length; i++){
931 ret+=typeOffsets[i]^(typeOffsets[i]>>>8);
933 if (typeMapData != null) {
934 for(int i=0; i<typeMapData.length; i++){
935 ret+=typeMapData[i] & 0xFF;
942 // BasicTimeZone methods
946 * @see com.ibm.icu.util.BasicTimeZone#getNextTransition(long, boolean)
949 public TimeZoneTransition getNextTransition(long base, boolean inclusive) {
950 initTransitionRules();
952 if (finalZone != null) {
953 if (inclusive && base == firstFinalTZTransition.getTime()) {
954 return firstFinalTZTransition;
955 } else if (base >= firstFinalTZTransition.getTime()) {
956 if (finalZone.useDaylightTime()) {
957 //return finalZone.getNextTransition(base, inclusive);
958 return finalZoneWithStartYear.getNextTransition(base, inclusive);
960 // No more transitions
965 if (historicRules != null) {
966 // Find a historical transition
967 int ttidx = transitionCount - 1;
968 for (; ttidx >= firstTZTransitionIdx; ttidx--) {
969 long t = transitionTimes64[ttidx] * Grego.MILLIS_PER_SECOND;
970 if (base > t || (!inclusive && base == t)) {
974 if (ttidx == transitionCount - 1) {
975 return firstFinalTZTransition;
976 } else if (ttidx < firstTZTransitionIdx) {
977 return firstTZTransition;
979 // Create a TimeZoneTransition
980 TimeZoneRule to = historicRules[getInt(typeMapData[ttidx + 1])];
981 TimeZoneRule from = historicRules[getInt(typeMapData[ttidx])];
982 long startTime = transitionTimes64[ttidx+1] * Grego.MILLIS_PER_SECOND;
984 // The transitions loaded from zoneinfo.res may contain non-transition data
985 if (from.getName().equals(to.getName()) && from.getRawOffset() == to.getRawOffset()
986 && from.getDSTSavings() == to.getDSTSavings()) {
987 return getNextTransition(startTime, false);
990 return new TimeZoneTransition(startTime, from, to);
997 * @see com.ibm.icu.util.BasicTimeZone#getPreviousTransition(long, boolean)
1000 public TimeZoneTransition getPreviousTransition(long base, boolean inclusive) {
1001 initTransitionRules();
1003 if (finalZone != null) {
1004 if (inclusive && base == firstFinalTZTransition.getTime()) {
1005 return firstFinalTZTransition;
1006 } else if (base > firstFinalTZTransition.getTime()) {
1007 if (finalZone.useDaylightTime()) {
1008 //return finalZone.getPreviousTransition(base, inclusive);
1009 return finalZoneWithStartYear.getPreviousTransition(base, inclusive);
1011 return firstFinalTZTransition;
1016 if (historicRules != null) {
1017 // Find a historical transition
1018 int ttidx = transitionCount - 1;
1019 for (; ttidx >= firstTZTransitionIdx; ttidx--) {
1020 long t = transitionTimes64[ttidx] * Grego.MILLIS_PER_SECOND;
1021 if (base > t || (inclusive && base == t)) {
1025 if (ttidx < firstTZTransitionIdx) {
1026 // No more transitions
1028 } else if (ttidx == firstTZTransitionIdx) {
1029 return firstTZTransition;
1031 // Create a TimeZoneTransition
1032 TimeZoneRule to = historicRules[getInt(typeMapData[ttidx])];
1033 TimeZoneRule from = historicRules[getInt(typeMapData[ttidx-1])];
1034 long startTime = transitionTimes64[ttidx] * Grego.MILLIS_PER_SECOND;
1036 // The transitions loaded from zoneinfo.res may contain non-transition data
1037 if (from.getName().equals(to.getName()) && from.getRawOffset() == to.getRawOffset()
1038 && from.getDSTSavings() == to.getDSTSavings()) {
1039 return getPreviousTransition(startTime, false);
1042 return new TimeZoneTransition(startTime, from, to);
1049 * @see com.ibm.icu.util.BasicTimeZone#getTimeZoneRules()
1052 public TimeZoneRule[] getTimeZoneRules() {
1053 initTransitionRules();
1055 if (historicRules != null) {
1056 // historicRules may contain null entries when original zoneinfo data
1057 // includes non transition data.
1058 for (int i = 0; i < historicRules.length; i++) {
1059 if (historicRules[i] != null) {
1064 if (finalZone != null) {
1065 if (finalZone.useDaylightTime()) {
1072 TimeZoneRule[] rules = new TimeZoneRule[size];
1074 rules[idx++] = initialRule;
1076 if (historicRules != null) {
1077 for (int i = 0; i < historicRules.length; i++) {
1078 if (historicRules[i] != null) {
1079 rules[idx++] = historicRules[i];
1084 if (finalZone != null) {
1085 if (finalZone.useDaylightTime()) {
1086 TimeZoneRule[] stzr = finalZoneWithStartYear.getTimeZoneRules();
1087 // Adding only transition rules
1088 rules[idx++] = stzr[1];
1089 rules[idx++] = stzr[2];
1091 // Create a TimeArrayTimeZoneRule at finalMillis
1092 rules[idx++] = new TimeArrayTimeZoneRule(getID() + "(STD)", finalZone.getRawOffset(), 0,
1093 new long[] {(long)finalStartMillis}, DateTimeRule.UTC_TIME);
1099 private transient InitialTimeZoneRule initialRule;
1100 private transient TimeZoneTransition firstTZTransition;
1101 private transient int firstTZTransitionIdx;
1102 private transient TimeZoneTransition firstFinalTZTransition;
1103 private transient TimeArrayTimeZoneRule[] historicRules;
1104 private transient SimpleTimeZone finalZoneWithStartYear; // hack
1106 private transient boolean transitionRulesInitialized;
1108 private synchronized void initTransitionRules() {
1109 if (transitionRulesInitialized) {
1114 firstTZTransition = null;
1115 firstFinalTZTransition = null;
1116 historicRules = null;
1117 firstTZTransitionIdx = 0;
1118 finalZoneWithStartYear = null;
1120 String stdName = getID() + "(STD)";
1121 String dstName = getID() + "(DST)";
1125 // Create initial rule
1126 raw = initialRawOffset() * Grego.MILLIS_PER_SECOND;
1127 dst = initialDstOffset() * Grego.MILLIS_PER_SECOND;
1128 initialRule = new InitialTimeZoneRule((dst == 0 ? stdName : dstName), raw, dst);
1130 if (transitionCount > 0) {
1131 int transitionIdx, typeIdx;
1133 // We probably no longer need to check the first "real" transition
1134 // here, because the new tzcode remove such transitions already.
1135 // For now, keeping this code for just in case. Feb 19, 2010 Yoshito
1136 for (transitionIdx = 0; transitionIdx < transitionCount; transitionIdx++) {
1137 if (getInt(typeMapData[transitionIdx]) != 0) { // type 0 is the initial type
1140 firstTZTransitionIdx++;
1142 if (transitionIdx == transitionCount) {
1143 // Actually no transitions...
1145 // Build historic rule array
1146 long[] times = new long[transitionCount];
1147 for (typeIdx = 0; typeIdx < typeCount; typeIdx++) {
1148 // Gather all start times for each pair of offsets
1150 for (transitionIdx = firstTZTransitionIdx; transitionIdx < transitionCount; transitionIdx++) {
1151 if (typeIdx == getInt(typeMapData[transitionIdx])) {
1152 long tt = transitionTimes64[transitionIdx] * Grego.MILLIS_PER_SECOND;
1153 if (tt < finalStartMillis) {
1154 // Exclude transitions after finalMillis
1155 times[nTimes++] = tt;
1160 long[] startTimes = new long[nTimes];
1161 System.arraycopy(times, 0, startTimes, 0, nTimes);
1162 // Create a TimeArrayTimeZoneRule
1163 raw = typeOffsets[typeIdx*2]*Grego.MILLIS_PER_SECOND;
1164 dst = typeOffsets[typeIdx*2 + 1]*Grego.MILLIS_PER_SECOND;
1165 if (historicRules == null) {
1166 historicRules = new TimeArrayTimeZoneRule[typeCount];
1168 historicRules[typeIdx] = new TimeArrayTimeZoneRule((dst == 0 ? stdName : dstName),
1169 raw, dst, startTimes, DateTimeRule.UTC_TIME);
1173 // Create initial transition
1174 typeIdx = getInt(typeMapData[firstTZTransitionIdx]);
1175 firstTZTransition = new TimeZoneTransition(transitionTimes64[firstTZTransitionIdx] * Grego.MILLIS_PER_SECOND,
1176 initialRule, historicRules[typeIdx]);
1181 if (finalZone != null) {
1182 // Get the first occurrence of final rule starts
1183 long startTime = (long)finalStartMillis;
1184 TimeZoneRule firstFinalRule;
1185 if (finalZone.useDaylightTime()) {
1187 * Note: When an OlsonTimeZone is constructed, we should set the final year
1188 * as the start year of finalZone. However, the boundary condition used for
1189 * getting offset from finalZone has some problems. So setting the start year
1190 * in the finalZone will cause a problem. For now, we do not set the valid
1191 * start year when the construction time and create a clone and set the
1192 * start year when extracting rules.
1194 finalZoneWithStartYear = (SimpleTimeZone)finalZone.clone();
1195 finalZoneWithStartYear.setStartYear(finalStartYear);
1197 TimeZoneTransition tzt = finalZoneWithStartYear.getNextTransition(startTime, false);
1198 firstFinalRule = tzt.getTo();
1199 startTime = tzt.getTime();
1201 finalZoneWithStartYear = finalZone;
1202 firstFinalRule = new TimeArrayTimeZoneRule(finalZone.getID(),
1203 finalZone.getRawOffset(), 0, new long[] {startTime}, DateTimeRule.UTC_TIME);
1205 TimeZoneRule prevRule = null;
1206 if (transitionCount > 0) {
1207 prevRule = historicRules[getInt(typeMapData[transitionCount - 1])];
1209 if (prevRule == null) {
1210 // No historic transitions, but only finalZone available
1211 prevRule = initialRule;
1213 firstFinalTZTransition = new TimeZoneTransition(startTime, prevRule, firstFinalRule);
1216 transitionRulesInitialized = true;
1219 // Note: This class does not support back level serialization compatibility
1220 // very well. ICU 4.4 introduced the 64bit transition data. It is probably
1221 // possible to implement this class to make old version of ICU to deserialize
1222 // object stream serialized by ICU 4.4+. However, such implementation will
1223 // introduce unnecessary complexity other than serialization support.
1224 // I decided to provide minimum level of backward compatibility, which
1225 // only support ICU 4.4+ to create an instance of OlsonTimeZone by reloading
1226 // the zone rules from bundles. ICU 4.2 or older version of ICU cannot
1227 // deserialize object stream created by ICU 4.4+. Yoshito -Feb 22, 2010
1229 private static final int currentSerialVersion = 1;
1230 private int serialVersionOnStream = currentSerialVersion;
1232 private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
1233 stream.defaultReadObject();
1235 if (serialVersionOnStream < 1) {
1236 // No version - 4.2 or older
1237 // Just reloading the rule from bundle
1238 boolean initialized = false;
1239 String tzid = getID();
1242 UResourceBundle top = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME,
1243 ZONEINFORES, ICUResourceBundle.ICU_DATA_CLASS_LOADER);
1244 UResourceBundle res = ZoneMeta.openOlsonResource(top, tzid);
1245 construct(top, res);
1246 if (finalZone != null){
1247 finalZone.setID(tzid);
1250 } catch (Exception e) {
1260 // need to rebuild transition rules when requested
1261 transitionRulesInitialized = false;
1265 private transient boolean isFrozen = false;
1268 * @see com.ibm.icu.util.TimeZone#isFrozen()
1270 public boolean isFrozen() {
1275 * @see com.ibm.icu.util.TimeZone#freeze()
1277 public TimeZone freeze() {
1283 * @see com.ibm.icu.util.TimeZone#cloneAsThawed()
1285 public TimeZone cloneAsThawed() {
1286 OlsonTimeZone tz = (OlsonTimeZone)super.cloneAsThawed();
1287 if (finalZone != null) {
1288 // TODO Do we really need this?
1289 finalZone.setID(getID());
1290 tz.finalZone = (SimpleTimeZone) finalZone.clone();
1293 // Following data are read-only and never changed.
1294 // Therefore, shallow copies should be sufficient.
1296 // transitionTimes64
1300 tz.isFrozen = false;