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