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