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