1 /*
   2  * Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.util.calendar;
  27 
  28 import java.io.IOException;
  29 import java.io.ObjectInputStream;
  30 import java.lang.ref.SoftReference;
  31 import java.security.AccessController;
  32 import java.util.ArrayList;
  33 import java.util.Date;
  34 import java.util.List;
  35 import java.util.Locale;
  36 import java.util.Map;
  37 import java.util.SimpleTimeZone;
  38 import java.util.TimeZone;
  39 
  40 /**
  41  * <code>ZoneInfo</code> is an implementation subclass of {@link
  42  * java.util.TimeZone TimeZone} that represents GMT offsets and
  43  * daylight saving time transitions of a time zone.
  44  * <p>
  45  * The daylight saving time transitions are described in the {@link
  46  * #transitions transitions} table consisting of a chronological
  47  * sequence of transitions of GMT offset and/or daylight saving time
  48  * changes. Since all transitions are represented in UTC, in theory,
  49  * <code>ZoneInfo</code> can be used with any calendar systems except
  50  * for the {@link #getOffset(int,int,int,int,int,int) getOffset}
  51  * method that takes Gregorian calendar date fields.
  52  * <p>
  53  * This table covers transitions from 1900 until 2037 (as of version
  54  * 1.4), Before 1900, it assumes that there was no daylight saving
  55  * time and the <code>getOffset</code> methods always return the
  56  * {@link #getRawOffset} value. No Local Mean Time is supported. If a
  57  * specified date is beyond the transition table and this time zone is
  58  * supposed to observe daylight saving time in 2037, it delegates
  59  * operations to a {@link java.util.SimpleTimeZone SimpleTimeZone}
  60  * object created using the daylight saving time schedule as of 2037.
  61  * <p>
  62  * The date items, transitions, GMT offset(s), etc. are read from a database
  63  * file. See {@link ZoneInfoFile} for details.
  64  * @see java.util.SimpleTimeZone
  65  * @since 1.4
  66  */
  67 
  68 public class ZoneInfo extends TimeZone {
  69 
  70     private static final int UTC_TIME = 0;
  71     private static final int STANDARD_TIME = 1;
  72     private static final int WALL_TIME = 2;
  73 
  74     private static final long OFFSET_MASK = 0x0fL;
  75     private static final long DST_MASK = 0xf0L;
  76     private static final int DST_NSHIFT = 4;
  77     // this bit field is reserved for abbreviation support
  78     private static final long ABBR_MASK = 0xf00L;
  79     private static final int TRANSITION_NSHIFT = 12;
  80 
  81     // Flag for supporting JDK backward compatible IDs, such as "EST".
  82     static final boolean USE_OLDMAPPING;
  83     static {
  84       String oldmapping = AccessController.doPrivileged(
  85           new sun.security.action.GetPropertyAction("sun.timezone.ids.oldmapping", "false")).toLowerCase(Locale.ROOT);
  86       USE_OLDMAPPING = (oldmapping.equals("yes") || oldmapping.equals("true"));
  87     }
  88 
  89     // IDs having conflicting data between Olson and JDK 1.1
  90     static final String[] conflictingIDs = {
  91         "EST", "MST", "HST"
  92     };
  93 
  94     private static final CalendarSystem gcal = CalendarSystem.getGregorianCalendar();
  95 
  96     /**
  97      * The raw GMT offset in milliseconds between this zone and GMT.
  98      * Negative offsets are to the west of Greenwich.  To obtain local
  99      * <em>standard</em> time, add the offset to GMT time.
 100      * @serial
 101      */
 102     private int rawOffset;
 103 
 104     /**
 105      * Difference in milliseconds from the original GMT offset in case
 106      * the raw offset value has been modified by calling {@link
 107      * #setRawOffset}. The initial value is 0.
 108      * @serial
 109      */
 110     private int rawOffsetDiff = 0;
 111 
 112     /**
 113      * A CRC32 value of all pairs of transition time (in milliseconds
 114      * in <code>long</code>) in local time and its GMT offset (in
 115      * seconds in <code>int</code>) in the chronological order. Byte
 116      * values of each <code>long</code> and <code>int</code> are taken
 117      * in the big endian order (i.e., MSB to LSB).
 118      * @serial
 119      */
 120     private int checksum;
 121 
 122     /**
 123      * The amount of time in milliseconds saved during daylight saving
 124      * time. If <code>useDaylight</code> is false, this value is 0.
 125      * @serial
 126      */
 127     private int dstSavings;
 128 
 129     /**
 130      * This array describes transitions of GMT offsets of this time
 131      * zone, including both raw offset changes and daylight saving
 132      * time changes.
 133      * A long integer consists of four bit fields.
 134      * <ul>
 135      * <li>The most significant 52-bit field represents transition
 136      * time in milliseconds from Gregorian January 1 1970, 00:00:00
 137      * GMT.</li>
 138      * <li>The next 4-bit field is reserved and must be 0.</li>
 139      * <li>The next 4-bit field is an index value to {@link #offsets
 140      * offsets[]} for the amount of daylight saving at the
 141      * transition. If this value is zero, it means that no daylight
 142      * saving, not the index value zero.</li>
 143      * <li>The least significant 4-bit field is an index value to
 144      * {@link #offsets offsets[]} for <em>total</em> GMT offset at the
 145      * transition.</li>
 146      * </ul>
 147      * If this time zone doesn't observe daylight saving time and has
 148      * never changed any GMT offsets in the past, this value is null.
 149      * @serial
 150      */
 151     private long[] transitions;
 152 
 153     /**
 154      * This array holds all unique offset values in
 155      * milliseconds. Index values to this array are stored in the
 156      * transitions array elements.
 157      * @serial
 158      */
 159     private int[] offsets;
 160 
 161     /**
 162      * SimpleTimeZone parameter values. It has to have either 8 for
 163      * {@link java.util.SimpleTimeZone#SimpleTimeZone(int, String,
 164      * int, int , int , int , int , int , int , int , int) the
 165      * 11-argument SimpleTimeZone constructor} or 10 for {@link
 166      * java.util.SimpleTimeZone#SimpleTimeZone(int, String, int, int,
 167      * int , int , int , int , int , int , int, int, int) the
 168      * 13-argument SimpleTimeZone constructor} parameters.
 169      * @serial
 170      */
 171     private int[] simpleTimeZoneParams;
 172 
 173     /**
 174      * True if the raw GMT offset value would change after the time
 175      * zone data has been generated; false, otherwise. The default
 176      * value is false.
 177      * @serial
 178      */
 179     private boolean willGMTOffsetChange = false;
 180 
 181     /**
 182      * True if the object has been modified after its instantiation.
 183      */
 184     transient private boolean dirty = false;
 185 
 186     private static final long serialVersionUID = 2653134537216586139L;
 187 
 188     /**
 189      * A constructor.
 190      */
 191     public ZoneInfo() {
 192     }
 193 
 194     /**
 195      * A Constructor for CustomID.
 196      */
 197     public ZoneInfo(String ID, int rawOffset) {
 198         this(ID, rawOffset, 0, 0, null, null, null, false);
 199     }
 200 
 201     /**
 202      * Constructs a ZoneInfo instance.
 203      *
 204      * @param ID time zone name
 205      * @param rawOffset GMT offset in milliseconds
 206      * @param dstSavings daylight saving value in milliseconds or 0
 207      * (zero) if this time zone doesn't observe Daylight Saving Time.
 208      * @param checksum CRC32 value with all transitions table entry
 209      * values
 210      * @param transitions transition table
 211      * @param offsets offset value table
 212      * @param simpleTimeZoneParams parameter values for constructing
 213      * SimpleTimeZone
 214      * @param willGMTOffsetChange the value of willGMTOffsetChange
 215      */
 216     ZoneInfo(String ID,
 217              int rawOffset,
 218              int dstSavings,
 219              int checksum,
 220              long[] transitions,
 221              int[] offsets,
 222              int[] simpleTimeZoneParams,
 223              boolean willGMTOffsetChange) {
 224         setID(ID);
 225         this.rawOffset = rawOffset;
 226         this.dstSavings = dstSavings;
 227         this.checksum = checksum;
 228         this.transitions = transitions;
 229         this.offsets = offsets;
 230         this.simpleTimeZoneParams = simpleTimeZoneParams;
 231         this.willGMTOffsetChange = willGMTOffsetChange;
 232     }
 233 
 234     /**
 235      * Returns the difference in milliseconds between local time and UTC
 236      * of given time, taking into account both the raw offset and the
 237      * effect of daylight savings.
 238      *
 239      * @param date the milliseconds in UTC
 240      * @return the milliseconds to add to UTC to get local wall time
 241      */
 242     public int getOffset(long date) {
 243         return getOffsets(date, null, UTC_TIME);
 244     }
 245 
 246     public int getOffsets(long utc, int[] offsets) {
 247         return getOffsets(utc, offsets, UTC_TIME);
 248     }
 249 
 250     public int getOffsetsByStandard(long standard, int[] offsets) {
 251         return getOffsets(standard, offsets, STANDARD_TIME);
 252     }
 253 
 254     public int getOffsetsByWall(long wall, int[] offsets) {
 255         return getOffsets(wall, offsets, WALL_TIME);
 256     }
 257 
 258     private int getOffsets(long date, int[] offsets, int type) {
 259         // if dst is never observed, there is no transition.
 260         if (transitions == null) {
 261             int offset = getLastRawOffset();
 262             if (offsets != null) {
 263                 offsets[0] = offset;
 264                 offsets[1] = 0;
 265             }
 266             return offset;
 267         }
 268 
 269         date -= rawOffsetDiff;
 270         int index = getTransitionIndex(date, type);
 271 
 272         // prior to the transition table, returns the raw offset.
 273         // FIXME: should support LMT.
 274         if (index < 0) {
 275             int offset = getLastRawOffset();
 276             if (offsets != null) {
 277                 offsets[0] = offset;
 278                 offsets[1] = 0;
 279             }
 280             return offset;
 281         }
 282 
 283         if (index < transitions.length) {
 284             long val = transitions[index];
 285             int offset = this.offsets[(int)(val & OFFSET_MASK)] + rawOffsetDiff;
 286             if (offsets != null) {
 287                 int dst = (int)((val >>> DST_NSHIFT) & 0xfL);
 288                 int save = (dst == 0) ? 0 : this.offsets[dst];
 289                 offsets[0] = offset - save;
 290                 offsets[1] = save;
 291             }
 292             return offset;
 293         }
 294 
 295         // beyond the transitions, delegate to SimpleTimeZone if there
 296         // is a rule; otherwise, return rawOffset.
 297         SimpleTimeZone tz = getLastRule();
 298         if (tz != null) {
 299             int rawoffset = tz.getRawOffset();
 300             long msec = date;
 301             if (type != UTC_TIME) {
 302                 msec -= rawOffset;
 303             }
 304             int dstoffset = tz.getOffset(msec) - rawOffset;
 305 
 306             // Check if it's in a standard-to-daylight transition.
 307             if (dstoffset > 0 && tz.getOffset(msec - dstoffset) == rawoffset) {
 308                 dstoffset = 0;
 309             }
 310 
 311             if (offsets != null) {
 312                 offsets[0] = rawoffset;
 313                 offsets[1] = dstoffset;
 314             }
 315             return rawoffset + dstoffset;
 316         }
 317         int offset = getLastRawOffset();
 318         if (offsets != null) {
 319             offsets[0] = offset;
 320             offsets[1] = 0;
 321         }
 322         return offset;
 323     }
 324 
 325     private int getTransitionIndex(long date, int type) {
 326         int low = 0;
 327         int high = transitions.length - 1;
 328 
 329         while (low <= high) {
 330             int mid = (low + high) / 2;
 331             long val = transitions[mid];
 332             long midVal = val >> TRANSITION_NSHIFT; // sign extended
 333             if (type != UTC_TIME) {
 334                 midVal += offsets[(int)(val & OFFSET_MASK)]; // wall time
 335             }
 336             if (type == STANDARD_TIME) {
 337                 int dstIndex = (int)((val >>> DST_NSHIFT) & 0xfL);
 338                 if (dstIndex != 0) {
 339                     midVal -= offsets[dstIndex]; // make it standard time
 340                 }
 341             }
 342 
 343             if (midVal < date) {
 344                 low = mid + 1;
 345             } else if (midVal > date) {
 346                 high = mid - 1;
 347             } else {
 348                 return mid;
 349             }
 350         }
 351 
 352         // if beyond the transitions, returns that index.
 353         if (low >= transitions.length) {
 354             return low;
 355         }
 356         return low - 1;
 357     }
 358 
 359    /**
 360      * Returns the difference in milliseconds between local time and
 361      * UTC, taking into account both the raw offset and the effect of
 362      * daylight savings, for the specified date and time.  This method
 363      * assumes that the start and end month are distinct.  This method
 364      * assumes a Gregorian calendar for calculations.
 365      * <p>
 366      * <em>Note: In general, clients should use
 367      * {@link Calendar#ZONE_OFFSET Calendar.get(ZONE_OFFSET)} +
 368      * {@link Calendar#DST_OFFSET Calendar.get(DST_OFFSET)}
 369      * instead of calling this method.</em>
 370      *
 371      * @param era       The era of the given date. The value must be either
 372      *                  GregorianCalendar.AD or GregorianCalendar.BC.
 373      * @param year      The year in the given date.
 374      * @param month     The month in the given date. Month is 0-based. e.g.,
 375      *                  0 for January.
 376      * @param day       The day-in-month of the given date.
 377      * @param dayOfWeek The day-of-week of the given date.
 378      * @param millis    The milliseconds in day in <em>standard</em> local time.
 379      * @return The milliseconds to add to UTC to get local time.
 380      */
 381     public int getOffset(int era, int year, int month, int day,
 382                          int dayOfWeek, int milliseconds) {
 383         if (milliseconds < 0 || milliseconds >= AbstractCalendar.DAY_IN_MILLIS) {
 384             throw new IllegalArgumentException();
 385         }
 386 
 387         if (era == java.util.GregorianCalendar.BC) { // BC
 388             year = 1 - year;
 389         } else if (era != java.util.GregorianCalendar.AD) {
 390             throw new IllegalArgumentException();
 391         }
 392 
 393         CalendarDate date = gcal.newCalendarDate(null);
 394         date.setDate(year, month + 1, day);
 395         if (gcal.validate(date) == false) {
 396             throw new IllegalArgumentException();
 397         }
 398 
 399         // bug-for-bug compatible argument checking
 400         if (dayOfWeek < java.util.GregorianCalendar.SUNDAY
 401             || dayOfWeek > java.util.GregorianCalendar.SATURDAY) {
 402             throw new IllegalArgumentException();
 403         }
 404 
 405         if (transitions == null) {
 406             return getLastRawOffset();
 407         }
 408 
 409         long dateInMillis = gcal.getTime(date) + milliseconds;
 410         dateInMillis -= (long) rawOffset; // make it UTC
 411         return getOffsets(dateInMillis, null, UTC_TIME);
 412     }
 413 
 414     /**
 415      * Sets the base time zone offset from GMT. This operation
 416      * modifies all the transitions of this ZoneInfo object, including
 417      * historical ones, if applicable.
 418      *
 419      * @param offsetMillis the base time zone offset to GMT.
 420      * @see getRawOffset
 421      */
 422     public synchronized void setRawOffset(int offsetMillis) {
 423         if (offsetMillis == rawOffset + rawOffsetDiff) {
 424             return;
 425         }
 426         rawOffsetDiff = offsetMillis - rawOffset;
 427         if (lastRule != null) {
 428             lastRule.setRawOffset(offsetMillis);
 429         }
 430         dirty = true;
 431     }
 432 
 433     /**
 434      * Returns the GMT offset of the current date. This GMT offset
 435      * value is not modified during Daylight Saving Time.
 436      *
 437      * @return the GMT offset value in milliseconds to add to UTC time
 438      * to get local standard time
 439      */
 440     public int getRawOffset() {
 441         if (!willGMTOffsetChange) {
 442             return rawOffset + rawOffsetDiff;
 443         }
 444 
 445         int[] offsets = new int[2];
 446         getOffsets(System.currentTimeMillis(), offsets, UTC_TIME);
 447         return offsets[0];
 448     }
 449 
 450     public boolean isDirty() {
 451         return dirty;
 452     }
 453 
 454     private int getLastRawOffset() {
 455         return rawOffset + rawOffsetDiff;
 456     }
 457 
 458     /**
 459      * Queries if this time zone uses Daylight Saving Time in the last known rule.
 460      */
 461     public boolean useDaylightTime() {
 462         return (simpleTimeZoneParams != null);
 463     }
 464 
 465     @Override
 466     public boolean observesDaylightTime() {
 467         if (simpleTimeZoneParams != null) {
 468             return true;
 469         }
 470         if (transitions == null) {
 471             return false;
 472         }
 473 
 474         // Look up the transition table to see if it's in DST right
 475         // now or if there's any standard-to-daylight transition at
 476         // any future.
 477         long utc = System.currentTimeMillis() - rawOffsetDiff;
 478         int index = getTransitionIndex(utc, UTC_TIME);
 479 
 480         // before transitions in the transition table
 481         if (index < 0) {
 482             return false;
 483         }
 484 
 485         // the time is in the table range.
 486         for (int i = index; i < transitions.length; i++) {
 487             if ((transitions[i] & DST_MASK) != 0) {
 488                 return true;
 489             }
 490         }
 491         // No further DST is observed.
 492         return false;
 493     }
 494 
 495     /**
 496      * Queries if the specified date is in Daylight Saving Time.
 497      */
 498     public boolean inDaylightTime(Date date) {
 499         if (date == null) {
 500             throw new NullPointerException();
 501         }
 502 
 503         if (transitions == null) {
 504             return false;
 505         }
 506 
 507         long utc = date.getTime() - rawOffsetDiff;
 508         int index = getTransitionIndex(utc, UTC_TIME);
 509 
 510         // before transitions in the transition table
 511         if (index < 0) {
 512             return false;
 513         }
 514 
 515         // the time is in the table range.
 516         if (index < transitions.length) {
 517             return (transitions[index] & DST_MASK) != 0;
 518         }
 519 
 520         // beyond the transition table
 521         SimpleTimeZone tz = getLastRule();
 522         if (tz != null) {
 523             return tz.inDaylightTime(date);
 524         }
 525         return false;
 526     }
 527 
 528     /**
 529      * Returns the amount of time in milliseconds that the clock is advanced
 530      * during daylight saving time is in effect in its last daylight saving time rule.
 531      *
 532      * @return the number of milliseconds the time is advanced with respect to
 533      * standard time when daylight saving time is in effect.
 534      */
 535     public int getDSTSavings() {
 536         return dstSavings;
 537     }
 538 
 539 //    /**
 540 //     * @return the last year in the transition table or -1 if this
 541 //     * time zone doesn't observe any daylight saving time.
 542 //     */
 543 //    public int getMaxTransitionYear() {
 544 //      if (transitions == null) {
 545 //          return -1;
 546 //      }
 547 //      long val = transitions[transitions.length - 1];
 548 //      int offset = this.offsets[(int)(val & OFFSET_MASK)] + rawOffsetDiff;
 549 //      val = (val >> TRANSITION_NSHIFT) + offset;
 550 //      CalendarDate lastDate = Gregorian.getCalendarDate(val);
 551 //      return lastDate.getYear();
 552 //    }
 553 
 554     /**
 555      * Returns a string representation of this time zone.
 556      * @return the string
 557      */
 558     public String toString() {
 559         return getClass().getName() +
 560             "[id=\"" + getID() + "\"" +
 561             ",offset=" + getLastRawOffset() +
 562             ",dstSavings=" + dstSavings +
 563             ",useDaylight=" + useDaylightTime() +
 564             ",transitions=" + ((transitions != null) ? transitions.length : 0) +
 565             ",lastRule=" + (lastRule == null ? getLastRuleInstance() : lastRule) +
 566             "]";
 567     }
 568 
 569     /**
 570      * Gets all available IDs supported in the Java run-time.
 571      *
 572      * @return an array of time zone IDs.
 573      */
 574     public static String[] getAvailableIDs() {
 575         List<String> idList = ZoneInfoFile.getZoneIDs();
 576         List<String> excluded = ZoneInfoFile.getExcludedZones();
 577         if (excluded != null) {
 578             // List all zones from the idList and excluded lists
 579             List<String> list = new ArrayList<>(idList.size() + excluded.size());
 580             list.addAll(idList);
 581             list.addAll(excluded);
 582             idList = list;
 583         }
 584         String[] ids = new String[idList.size()];
 585         return idList.toArray(ids);
 586     }
 587 
 588     /**
 589      * Gets all available IDs that have the same value as the
 590      * specified raw GMT offset.
 591      *
 592      * @param rawOffset the GMT offset in milliseconds. This
 593      * value should not include any daylight saving time.
 594      *
 595      * @return an array of time zone IDs.
 596      */
 597     public static String[] getAvailableIDs(int rawOffset) {
 598         String[] result;
 599         List<String> matched = new ArrayList<>();
 600         List<String> IDs = ZoneInfoFile.getZoneIDs();
 601         int[] rawOffsets = ZoneInfoFile.getRawOffsets();
 602 
 603     loop:
 604         for (int index = 0; index < rawOffsets.length; index++) {
 605             if (rawOffsets[index] == rawOffset) {
 606                 byte[] indices = ZoneInfoFile.getRawOffsetIndices();
 607                 for (int i = 0; i < indices.length; i++) {
 608                     if (indices[i] == index) {
 609                         matched.add(IDs.get(i++));
 610                         while (i < indices.length && indices[i] == index) {
 611                             matched.add(IDs.get(i++));
 612                         }
 613                         break loop;
 614                     }
 615                 }
 616             }
 617         }
 618 
 619         // We need to add any zones from the excluded zone list that
 620         // currently have the same GMT offset as the specified
 621         // rawOffset. The zones returned by this method may not be
 622         // correct as of return to the caller if any GMT offset
 623         // transition is happening during this GMT offset checking...
 624         List<String> excluded = ZoneInfoFile.getExcludedZones();
 625         if (excluded != null) {
 626             for (String id : excluded) {
 627                 TimeZone zi = getTimeZone(id);
 628                 if (zi != null && zi.getRawOffset() == rawOffset) {
 629                     matched.add(id);
 630                 }
 631             }
 632         }
 633 
 634         result = new String[matched.size()];
 635         matched.toArray(result);
 636         return result;
 637     }
 638 
 639     /**
 640      * Gets the ZoneInfo for the given ID.
 641      *
 642      * @param ID the ID for a ZoneInfo. See TimeZone for detail.
 643      *
 644      * @return the specified ZoneInfo object, or null if there is no
 645      * time zone of the ID.
 646      */
 647     public static TimeZone getTimeZone(String ID) {
 648         String givenID = null;
 649 
 650         /*
 651          * If old JDK compatibility is specified, get the old alias
 652          * name.
 653          */
 654         if (USE_OLDMAPPING) {
 655             String compatibleID = TzIDOldMapping.MAP.get(ID);
 656             if (compatibleID != null) {
 657                 givenID = ID;
 658                 ID = compatibleID;
 659             }
 660         }
 661 
 662         ZoneInfo zi = ZoneInfoFile.getZoneInfo(ID);
 663         if (zi == null) {
 664             // if we can't create an object for the ID, try aliases.
 665             try {
 666                 Map<String, String> map = getAliasTable();
 667                 String alias = ID;
 668                 while ((alias = map.get(alias)) != null) {
 669                     zi = ZoneInfoFile.getZoneInfo(alias);
 670                     if (zi != null) {
 671                         zi.setID(ID);
 672                         zi = ZoneInfoFile.addToCache(ID, zi);
 673                         zi = (ZoneInfo) zi.clone();
 674                         break;
 675                     }
 676                 }
 677             } catch (Exception e) {
 678                 // ignore exceptions
 679             }
 680         }
 681 
 682         if (givenID != null && zi != null) {
 683             zi.setID(givenID);
 684         }
 685         return zi;
 686     }
 687 
 688     private transient SimpleTimeZone lastRule;
 689 
 690     /**
 691      * Returns a SimpleTimeZone object representing the last GMT
 692      * offset and DST schedule or null if this time zone doesn't
 693      * observe DST.
 694      */
 695     private synchronized SimpleTimeZone getLastRule() {
 696         if (lastRule == null) {
 697             lastRule = getLastRuleInstance();
 698         }
 699         return lastRule;
 700     }
 701 
 702     /**
 703      * Returns a SimpleTimeZone object that represents the last
 704      * known daylight saving time rules.
 705      *
 706      * @return a SimpleTimeZone object or null if this time zone
 707      * doesn't observe DST.
 708      */
 709     public SimpleTimeZone getLastRuleInstance() {
 710         if (simpleTimeZoneParams == null) {
 711             return null;
 712         }
 713         if (simpleTimeZoneParams.length == 10) {
 714             return new SimpleTimeZone(getLastRawOffset(), getID(),
 715                                       simpleTimeZoneParams[0],
 716                                       simpleTimeZoneParams[1],
 717                                       simpleTimeZoneParams[2],
 718                                       simpleTimeZoneParams[3],
 719                                       simpleTimeZoneParams[4],
 720                                       simpleTimeZoneParams[5],
 721                                       simpleTimeZoneParams[6],
 722                                       simpleTimeZoneParams[7],
 723                                       simpleTimeZoneParams[8],
 724                                       simpleTimeZoneParams[9],
 725                                       dstSavings);
 726         }
 727         return new SimpleTimeZone(getLastRawOffset(), getID(),
 728                                   simpleTimeZoneParams[0],
 729                                   simpleTimeZoneParams[1],
 730                                   simpleTimeZoneParams[2],
 731                                   simpleTimeZoneParams[3],
 732                                   simpleTimeZoneParams[4],
 733                                   simpleTimeZoneParams[5],
 734                                   simpleTimeZoneParams[6],
 735                                   simpleTimeZoneParams[7],
 736                                   dstSavings);
 737     }
 738 
 739     /**
 740      * Returns a copy of this <code>ZoneInfo</code>.
 741      */
 742     public Object clone() {
 743         ZoneInfo zi = (ZoneInfo) super.clone();
 744         zi.lastRule = null;
 745         return zi;
 746     }
 747 
 748     /**
 749      * Returns a hash code value calculated from the GMT offset and
 750      * transitions.
 751      * @return a hash code of this time zone
 752      */
 753     public int hashCode() {
 754         return getLastRawOffset() ^ checksum;
 755     }
 756 
 757     /**
 758      * Compares the equity of two ZoneInfo objects.
 759      *
 760      * @param obj the object to be compared with
 761      * @return true if given object is same as this ZoneInfo object,
 762      * false otherwise.
 763      */
 764     public boolean equals(Object obj) {
 765         if (this == obj) {
 766             return true;
 767         }
 768         if (!(obj instanceof ZoneInfo)) {
 769             return false;
 770         }
 771         ZoneInfo that = (ZoneInfo) obj;
 772         return (getID().equals(that.getID())
 773                 && (getLastRawOffset() == that.getLastRawOffset())
 774                 && (checksum == that.checksum));
 775     }
 776 
 777     /**
 778      * Returns true if this zone has the same raw GMT offset value and
 779      * transition table as another zone info. If the specified
 780      * TimeZone object is not a ZoneInfo instance, this method returns
 781      * true if the specified TimeZone object has the same raw GMT
 782      * offset value with no daylight saving time.
 783      *
 784      * @param other the ZoneInfo object to be compared with
 785      * @return true if the given <code>TimeZone</code> has the same
 786      * GMT offset and transition information; false, otherwise.
 787      */
 788     public boolean hasSameRules(TimeZone other) {
 789         if (this == other) {
 790             return true;
 791         }
 792         if (other == null) {
 793             return false;
 794         }
 795         if (!(other instanceof ZoneInfo)) {
 796             if (getRawOffset() != other.getRawOffset()) {
 797                 return false;
 798             }
 799             // if both have the same raw offset and neither observes
 800             // DST, they have the same rule.
 801             if ((transitions == null)
 802                 && (useDaylightTime() == false)
 803                 && (other.useDaylightTime() == false)) {
 804                 return true;
 805             }
 806             return false;
 807         }
 808         if (getLastRawOffset() != ((ZoneInfo)other).getLastRawOffset()) {
 809             return false;
 810         }
 811         return (checksum == ((ZoneInfo)other).checksum);
 812     }
 813 
 814     private static SoftReference<Map<String, String>> aliasTable;
 815 
 816     static Map<String, String> getCachedAliasTable() {
 817         Map<String, String> aliases = null;
 818 
 819         SoftReference<Map<String, String>> cache = aliasTable;
 820         if (cache != null) {
 821             aliases = cache.get();
 822         }
 823         return aliases;
 824     }
 825 
 826     /**
 827      * Returns a Map from alias time zone IDs to their standard
 828      * time zone IDs.
 829      *
 830      * @return the Map that holds the mappings from alias time zone IDs
 831      *    to their standard time zone IDs, or null if
 832      *    <code>ZoneInfoMappings</code> file is not available.
 833      */
 834      public synchronized static Map<String, String> getAliasTable() {
 835          Map<String, String> aliases = getCachedAliasTable();
 836          if (aliases == null) {
 837              aliases = ZoneInfoFile.getZoneAliases();
 838              if (aliases != null) {
 839                  if (!USE_OLDMAPPING) {
 840                      // Remove the conflicting IDs from the alias table.
 841                      for (String key : conflictingIDs) {
 842                          aliases.remove(key);
 843                      }
 844                  }
 845                  aliasTable = new SoftReference<Map<String, String>>(aliases);
 846              }
 847          }
 848          return aliases;
 849      }
 850 
 851     private void readObject(ObjectInputStream stream)
 852             throws IOException, ClassNotFoundException {
 853         stream.defaultReadObject();
 854         // We don't know how this object from 1.4.x or earlier has
 855         // been mutated. So it should always be marked as `dirty'.
 856         dirty = true;
 857     }
 858 }