1 /*
   2  * Copyright (c) 2000, 2013, 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.Arrays;
  34 import java.util.Date;
  35 import java.util.List;
  36 import java.util.Locale;
  37 import java.util.Map;
  38 import java.util.Set;
  39 import java.util.SimpleTimeZone;
  40 import java.util.TimeZone;
  41 
  42 /**
  43  * <code>ZoneInfo</code> is an implementation subclass of {@link
  44  * java.util.TimeZone TimeZone} that represents GMT offsets and
  45  * daylight saving time transitions of a time zone.
  46  * <p>
  47  * The daylight saving time transitions are described in the {@link
  48  * #transitions transitions} table consisting of a chronological
  49  * sequence of transitions of GMT offset and/or daylight saving time
  50  * changes. Since all transitions are represented in UTC, in theory,
  51  * <code>ZoneInfo</code> can be used with any calendar systems except
  52  * for the {@link #getOffset(int,int,int,int,int,int) getOffset}
  53  * method that takes Gregorian calendar date fields.
  54  * <p>
  55  * This table covers transitions from 1900 until 2037 (as of version
  56  * 1.4), Before 1900, it assumes that there was no daylight saving
  57  * time and the <code>getOffset</code> methods always return the
  58  * {@link #getRawOffset} value. No Local Mean Time is supported. If a
  59  * specified date is beyond the transition table and this time zone is
  60  * supposed to observe daylight saving time in 2037, it delegates
  61  * operations to a {@link java.util.SimpleTimeZone SimpleTimeZone}
  62  * object created using the daylight saving time schedule as of 2037.
  63  * <p>
  64  * The date items, transitions, GMT offset(s), etc. are read from a database
  65  * file. See {@link ZoneInfoFile} for details.
  66  * @see java.util.SimpleTimeZone
  67  * @since 1.4
  68  */
  69 
  70 public class ZoneInfo extends TimeZone {
  71 
  72     private static final int UTC_TIME = 0;
  73     private static final int STANDARD_TIME = 1;
  74     private static final int WALL_TIME = 2;
  75 
  76     private static final long OFFSET_MASK = 0x0fL;
  77     private static final long DST_MASK = 0xf0L;
  78     private static final int DST_NSHIFT = 4;
  79     // this bit field is reserved for abbreviation support
  80     private static final long ABBR_MASK = 0xf00L;
  81     private static final int TRANSITION_NSHIFT = 12;
  82 
  83     private static final CalendarSystem gcal = CalendarSystem.getGregorianCalendar();
  84 
  85     /**
  86      * The raw GMT offset in milliseconds between this zone and GMT.
  87      * Negative offsets are to the west of Greenwich.  To obtain local
  88      * <em>standard</em> time, add the offset to GMT time.
  89      * @serial
  90      */
  91     private int rawOffset;
  92 
  93     /**
  94      * Difference in milliseconds from the original GMT offset in case
  95      * the raw offset value has been modified by calling {@link
  96      * #setRawOffset}. The initial value is 0.
  97      * @serial
  98      */
  99     private int rawOffsetDiff = 0;
 100 
 101     /**
 102      * A CRC32 value of all pairs of transition time (in milliseconds
 103      * in <code>long</code>) in local time and its GMT offset (in
 104      * seconds in <code>int</code>) in the chronological order. Byte
 105      * values of each <code>long</code> and <code>int</code> are taken
 106      * in the big endian order (i.e., MSB to LSB).
 107      * @serial
 108      */
 109     private int checksum;
 110 
 111     /**
 112      * The amount of time in milliseconds saved during daylight saving
 113      * time. If <code>useDaylight</code> is false, this value is 0.
 114      * @serial
 115      */
 116     private int dstSavings;
 117 
 118     /**
 119      * This array describes transitions of GMT offsets of this time
 120      * zone, including both raw offset changes and daylight saving
 121      * time changes.
 122      * A long integer consists of four bit fields.
 123      * <ul>
 124      * <li>The most significant 52-bit field represents transition
 125      * time in milliseconds from Gregorian January 1 1970, 00:00:00
 126      * GMT.</li>
 127      * <li>The next 4-bit field is reserved and must be 0.</li>
 128      * <li>The next 4-bit field is an index value to {@link #offsets
 129      * offsets[]} for the amount of daylight saving at the
 130      * transition. If this value is zero, it means that no daylight
 131      * saving, not the index value zero.</li>
 132      * <li>The least significant 4-bit field is an index value to
 133      * {@link #offsets offsets[]} for <em>total</em> GMT offset at the
 134      * transition.</li>
 135      * </ul>
 136      * If this time zone doesn't observe daylight saving time and has
 137      * never changed any GMT offsets in the past, this value is null.
 138      * @serial
 139      */
 140     private long[] transitions;
 141 
 142     /**
 143      * This array holds all unique offset values in
 144      * milliseconds. Index values to this array are stored in the
 145      * transitions array elements.
 146      * @serial
 147      */
 148     private int[] offsets;
 149 
 150     /**
 151      * SimpleTimeZone parameter values. It has to have either 8 for
 152      * {@link java.util.SimpleTimeZone#SimpleTimeZone(int, String,
 153      * int, int , int , int , int , int , int , int , int) the
 154      * 11-argument SimpleTimeZone constructor} or 10 for {@link
 155      * java.util.SimpleTimeZone#SimpleTimeZone(int, String, int, int,
 156      * int , int , int , int , int , int , int, int, int) the
 157      * 13-argument SimpleTimeZone constructor} parameters.
 158      * @serial
 159      */
 160     private int[] simpleTimeZoneParams;
 161 
 162     /**
 163      * True if the raw GMT offset value would change after the time
 164      * zone data has been generated; false, otherwise. The default
 165      * value is false.
 166      * @serial
 167      */
 168     private boolean willGMTOffsetChange = false;
 169 
 170     /**
 171      * True if the object has been modified after its instantiation.
 172      */
 173     transient private boolean dirty = false;
 174 
 175     private static final long serialVersionUID = 2653134537216586139L;
 176 
 177     /**
 178      * A constructor.
 179      */
 180     public ZoneInfo() {
 181     }
 182 
 183     /**
 184      * A Constructor for CustomID.
 185      */
 186     public ZoneInfo(String ID, int rawOffset) {
 187         this(ID, rawOffset, 0, 0, null, null, null, false);
 188     }
 189 
 190     /**
 191      * Constructs a ZoneInfo instance.
 192      *
 193      * @param ID time zone name
 194      * @param rawOffset GMT offset in milliseconds
 195      * @param dstSavings daylight saving value in milliseconds or 0
 196      * (zero) if this time zone doesn't observe Daylight Saving Time.
 197      * @param checksum CRC32 value with all transitions table entry
 198      * values
 199      * @param transitions transition table
 200      * @param offsets offset value table
 201      * @param simpleTimeZoneParams parameter values for constructing
 202      * SimpleTimeZone
 203      * @param willGMTOffsetChange the value of willGMTOffsetChange
 204      */
 205     ZoneInfo(String ID,
 206              int rawOffset,
 207              int dstSavings,
 208              int checksum,
 209              long[] transitions,
 210              int[] offsets,
 211              int[] simpleTimeZoneParams,
 212              boolean willGMTOffsetChange) {
 213         setID(ID);
 214         this.rawOffset = rawOffset;
 215         this.dstSavings = dstSavings;
 216         this.checksum = checksum;
 217         this.transitions = transitions;
 218         this.offsets = offsets;
 219         this.simpleTimeZoneParams = simpleTimeZoneParams;
 220         this.willGMTOffsetChange = willGMTOffsetChange;
 221     }
 222 
 223     /**
 224      * Returns the difference in milliseconds between local time and UTC
 225      * of given time, taking into account both the raw offset and the
 226      * effect of daylight savings.
 227      *
 228      * @param date the milliseconds in UTC
 229      * @return the milliseconds to add to UTC to get local wall time
 230      */
 231     public int getOffset(long date) {
 232         return getOffsets(date, null, UTC_TIME);
 233     }
 234 
 235     public int getOffsets(long utc, int[] offsets) {
 236         return getOffsets(utc, offsets, UTC_TIME);
 237     }
 238 
 239     public int getOffsetsByStandard(long standard, int[] offsets) {
 240         return getOffsets(standard, offsets, STANDARD_TIME);
 241     }
 242 
 243     public int getOffsetsByWall(long wall, int[] offsets) {
 244         return getOffsets(wall, offsets, WALL_TIME);
 245     }
 246 
 247     private int getOffsets(long date, int[] offsets, int type) {
 248         // if dst is never observed, there is no transition.
 249         if (transitions == null) {
 250             int offset = getLastRawOffset();
 251             if (offsets != null) {
 252                 offsets[0] = offset;
 253                 offsets[1] = 0;
 254             }
 255             return offset;
 256         }
 257 
 258         date -= rawOffsetDiff;
 259         int index = getTransitionIndex(date, type);
 260 
 261         // prior to the transition table, returns the raw offset.
 262         // FIXME: should support LMT.
 263         if (index < 0) {
 264             int offset = getLastRawOffset();
 265             if (offsets != null) {
 266                 offsets[0] = offset;
 267                 offsets[1] = 0;
 268             }
 269             return offset;
 270         }
 271 
 272         if (index < transitions.length) {
 273             long val = transitions[index];
 274             int offset = this.offsets[(int)(val & OFFSET_MASK)] + rawOffsetDiff;
 275             if (offsets != null) {
 276                 int dst = (int)((val >>> DST_NSHIFT) & 0xfL);
 277                 int save = (dst == 0) ? 0 : this.offsets[dst];
 278                 offsets[0] = offset - save;
 279                 offsets[1] = save;
 280             }
 281             return offset;
 282         }
 283 
 284         // beyond the transitions, delegate to SimpleTimeZone if there
 285         // is a rule; otherwise, return rawOffset.
 286         SimpleTimeZone tz = getLastRule();
 287         if (tz != null) {
 288             int rawoffset = tz.getRawOffset();
 289             long msec = date;
 290             if (type != UTC_TIME) {
 291                 msec -= rawOffset;
 292             }
 293             int dstoffset = tz.getOffset(msec) - rawOffset;
 294 
 295             // Check if it's in a standard-to-daylight transition.
 296             if (dstoffset > 0 && tz.getOffset(msec - dstoffset) == rawoffset) {
 297                 dstoffset = 0;
 298             }
 299 
 300             if (offsets != null) {
 301                 offsets[0] = rawoffset;
 302                 offsets[1] = dstoffset;
 303             }
 304             return rawoffset + dstoffset;
 305         }
 306         int offset = getLastRawOffset();
 307         if (offsets != null) {
 308             offsets[0] = offset;
 309             offsets[1] = 0;
 310         }
 311         return offset;
 312     }
 313 
 314     private int getTransitionIndex(long date, int type) {
 315         int low = 0;
 316         int high = transitions.length - 1;
 317 
 318         while (low <= high) {
 319             int mid = (low + high) / 2;
 320             long val = transitions[mid];
 321             long midVal = val >> TRANSITION_NSHIFT; // sign extended
 322             if (type != UTC_TIME) {
 323                 midVal += offsets[(int)(val & OFFSET_MASK)]; // wall time
 324             }
 325             if (type == STANDARD_TIME) {
 326                 int dstIndex = (int)((val >>> DST_NSHIFT) & 0xfL);
 327                 if (dstIndex != 0) {
 328                     midVal -= offsets[dstIndex]; // make it standard time
 329                 }
 330             }
 331 
 332             if (midVal < date) {
 333                 low = mid + 1;
 334             } else if (midVal > date) {
 335                 high = mid - 1;
 336             } else {
 337                 return mid;
 338             }
 339         }
 340 
 341         // if beyond the transitions, returns that index.
 342         if (low >= transitions.length) {
 343             return low;
 344         }
 345         return low - 1;
 346     }
 347 
 348    /**
 349      * Returns the difference in milliseconds between local time and
 350      * UTC, taking into account both the raw offset and the effect of
 351      * daylight savings, for the specified date and time.  This method
 352      * assumes that the start and end month are distinct.  This method
 353      * assumes a Gregorian calendar for calculations.
 354      * <p>
 355      * <em>Note: In general, clients should use
 356      * {@link Calendar#ZONE_OFFSET Calendar.get(ZONE_OFFSET)} +
 357      * {@link Calendar#DST_OFFSET Calendar.get(DST_OFFSET)}
 358      * instead of calling this method.</em>
 359      *
 360      * @param era       The era of the given date. The value must be either
 361      *                  GregorianCalendar.AD or GregorianCalendar.BC.
 362      * @param year      The year in the given date.
 363      * @param month     The month in the given date. Month is 0-based. e.g.,
 364      *                  0 for January.
 365      * @param day       The day-in-month of the given date.
 366      * @param dayOfWeek The day-of-week of the given date.
 367      * @param millis    The milliseconds in day in <em>standard</em> local time.
 368      * @return The milliseconds to add to UTC to get local time.
 369      */
 370     public int getOffset(int era, int year, int month, int day,
 371                          int dayOfWeek, int milliseconds) {
 372         if (milliseconds < 0 || milliseconds >= AbstractCalendar.DAY_IN_MILLIS) {
 373             throw new IllegalArgumentException();
 374         }
 375 
 376         if (era == java.util.GregorianCalendar.BC) { // BC
 377             year = 1 - year;
 378         } else if (era != java.util.GregorianCalendar.AD) {
 379             throw new IllegalArgumentException();
 380         }
 381 
 382         CalendarDate date = gcal.newCalendarDate(null);
 383         date.setDate(year, month + 1, day);
 384         if (gcal.validate(date) == false) {
 385             throw new IllegalArgumentException();
 386         }
 387 
 388         // bug-for-bug compatible argument checking
 389         if (dayOfWeek < java.util.GregorianCalendar.SUNDAY
 390             || dayOfWeek > java.util.GregorianCalendar.SATURDAY) {
 391             throw new IllegalArgumentException();
 392         }
 393 
 394         if (transitions == null) {
 395             return getLastRawOffset();
 396         }
 397 
 398         long dateInMillis = gcal.getTime(date) + milliseconds;
 399         dateInMillis -= (long) rawOffset; // make it UTC
 400         return getOffsets(dateInMillis, null, UTC_TIME);
 401     }
 402 
 403     /**
 404      * Sets the base time zone offset from GMT. This operation
 405      * modifies all the transitions of this ZoneInfo object, including
 406      * historical ones, if applicable.
 407      *
 408      * @param offsetMillis the base time zone offset to GMT.
 409      * @see getRawOffset
 410      */
 411     public synchronized void setRawOffset(int offsetMillis) {
 412         if (offsetMillis == rawOffset + rawOffsetDiff) {
 413             return;
 414         }
 415         rawOffsetDiff = offsetMillis - rawOffset;
 416         if (lastRule != null) {
 417             lastRule.setRawOffset(offsetMillis);
 418         }
 419         dirty = true;
 420     }
 421 
 422     /**
 423      * Returns the GMT offset of the current date. This GMT offset
 424      * value is not modified during Daylight Saving Time.
 425      *
 426      * @return the GMT offset value in milliseconds to add to UTC time
 427      * to get local standard time
 428      */
 429     public int getRawOffset() {
 430         if (!willGMTOffsetChange) {
 431             return rawOffset + rawOffsetDiff;
 432         }
 433 
 434         int[] offsets = new int[2];
 435         getOffsets(System.currentTimeMillis(), offsets, UTC_TIME);
 436         return offsets[0];
 437     }
 438 
 439     public boolean isDirty() {
 440         return dirty;
 441     }
 442 
 443     private int getLastRawOffset() {
 444         return rawOffset + rawOffsetDiff;
 445     }
 446 
 447     /**
 448      * Queries if this time zone uses Daylight Saving Time in the last known rule.
 449      */
 450     public boolean useDaylightTime() {
 451         return (simpleTimeZoneParams != null);
 452     }
 453 
 454     @Override
 455     public boolean observesDaylightTime() {
 456         if (simpleTimeZoneParams != null) {
 457             return true;
 458         }
 459         if (transitions == null) {
 460             return false;
 461         }
 462 
 463         // Look up the transition table to see if it's in DST right
 464         // now or if there's any standard-to-daylight transition at
 465         // any future.
 466         long utc = System.currentTimeMillis() - rawOffsetDiff;
 467         int index = getTransitionIndex(utc, UTC_TIME);
 468 
 469         // before transitions in the transition table
 470         if (index < 0) {
 471             return false;
 472         }
 473 
 474         // the time is in the table range.
 475         for (int i = index; i < transitions.length; i++) {
 476             if ((transitions[i] & DST_MASK) != 0) {
 477                 return true;
 478             }
 479         }
 480         // No further DST is observed.
 481         return false;
 482     }
 483 
 484     /**
 485      * Queries if the specified date is in Daylight Saving Time.
 486      */
 487     public boolean inDaylightTime(Date date) {
 488         if (date == null) {
 489             throw new NullPointerException();
 490         }
 491 
 492         if (transitions == null) {
 493             return false;
 494         }
 495 
 496         long utc = date.getTime() - rawOffsetDiff;
 497         int index = getTransitionIndex(utc, UTC_TIME);
 498 
 499         // before transitions in the transition table
 500         if (index < 0) {
 501             return false;
 502         }
 503 
 504         // the time is in the table range.
 505         if (index < transitions.length) {
 506             return (transitions[index] & DST_MASK) != 0;
 507         }
 508 
 509         // beyond the transition table
 510         SimpleTimeZone tz = getLastRule();
 511         if (tz != null) {
 512             return tz.inDaylightTime(date);
 513        }
 514         return false;
 515     }
 516 
 517     /**
 518      * Returns the amount of time in milliseconds that the clock is advanced
 519      * during daylight saving time is in effect in its last daylight saving time rule.
 520      *
 521      * @return the number of milliseconds the time is advanced with respect to
 522      * standard time when daylight saving time is in effect.
 523      */
 524     public int getDSTSavings() {
 525         return dstSavings;
 526     }
 527 
 528 //    /**
 529 //     * @return the last year in the transition table or -1 if this
 530 //     * time zone doesn't observe any daylight saving time.
 531 //     */
 532 //    public int getMaxTransitionYear() {
 533 //      if (transitions == null) {
 534 //          return -1;
 535 //      }
 536 //      long val = transitions[transitions.length - 1];
 537 //      int offset = this.offsets[(int)(val & OFFSET_MASK)] + rawOffsetDiff;
 538 //      val = (val >> TRANSITION_NSHIFT) + offset;
 539 //      CalendarDate lastDate = Gregorian.getCalendarDate(val);
 540 //      return lastDate.getYear();
 541 //    }
 542 
 543     /**
 544      * Returns a string representation of this time zone.
 545      * @return the string
 546      */
 547     public String toString() {
 548         return getClass().getName() +
 549             "[id=\"" + getID() + "\"" +
 550             ",offset=" + getLastRawOffset() +
 551             ",dstSavings=" + dstSavings +
 552             ",useDaylight=" + useDaylightTime() +
 553             ",transitions=" + ((transitions != null) ? transitions.length : 0) +
 554             ",lastRule=" + (lastRule == null ? getLastRuleInstance() : lastRule) +
 555             "]";
 556     }
 557 
 558     /**
 559      * Gets all available IDs supported in the Java run-time.
 560      *
 561      * @return an array of time zone IDs.
 562      */
 563     public static String[] getAvailableIDs() {
 564         return ZoneInfoFile.getZoneIds();
 565     }
 566 
 567     /**
 568      * Gets all available IDs that have the same value as the
 569      * specified raw GMT offset.
 570      *
 571      * @param rawOffset the GMT offset in milliseconds. This
 572      * value should not include any daylight saving time.
 573      *
 574      * @return an array of time zone IDs.
 575      */
 576     public static String[] getAvailableIDs(int rawOffset) {
 577         return ZoneInfoFile.getZoneIds(rawOffset);
 578     }
 579 
 580     /**
 581      * Gets the ZoneInfo for the given ID.
 582      *
 583      * @param ID the ID for a ZoneInfo. See TimeZone for detail.
 584      *
 585      * @return the specified ZoneInfo object, or null if there is no
 586      * time zone of the ID.
 587      */
 588     public static TimeZone getTimeZone(String ID) {
 589         return ZoneInfoFile.getZoneInfo(ID);
 590     }
 591 
 592     private transient SimpleTimeZone lastRule;
 593 
 594     /**
 595      * Returns a SimpleTimeZone object representing the last GMT
 596      * offset and DST schedule or null if this time zone doesn't
 597      * observe DST.
 598      */
 599     private synchronized SimpleTimeZone getLastRule() {
 600         if (lastRule == null) {
 601             lastRule = getLastRuleInstance();
 602         }
 603         return lastRule;
 604     }
 605 
 606     /**
 607      * Returns a SimpleTimeZone object that represents the last
 608      * known daylight saving time rules.
 609      *
 610      * @return a SimpleTimeZone object or null if this time zone
 611      * doesn't observe DST.
 612      */
 613     public SimpleTimeZone getLastRuleInstance() {
 614         if (simpleTimeZoneParams == null) {
 615             return null;
 616         }
 617         if (simpleTimeZoneParams.length == 10) {
 618             return new SimpleTimeZone(getLastRawOffset(), getID(),
 619                                       simpleTimeZoneParams[0],
 620                                       simpleTimeZoneParams[1],
 621                                       simpleTimeZoneParams[2],
 622                                       simpleTimeZoneParams[3],
 623                                       simpleTimeZoneParams[4],
 624                                       simpleTimeZoneParams[5],
 625                                       simpleTimeZoneParams[6],
 626                                       simpleTimeZoneParams[7],
 627                                       simpleTimeZoneParams[8],
 628                                       simpleTimeZoneParams[9],
 629                                       dstSavings);
 630         }
 631         return new SimpleTimeZone(getLastRawOffset(), getID(),
 632                                   simpleTimeZoneParams[0],
 633                                   simpleTimeZoneParams[1],
 634                                   simpleTimeZoneParams[2],
 635                                   simpleTimeZoneParams[3],
 636                                   simpleTimeZoneParams[4],
 637                                   simpleTimeZoneParams[5],
 638                                   simpleTimeZoneParams[6],
 639                                   simpleTimeZoneParams[7],
 640                                   dstSavings);
 641     }
 642 
 643     /**
 644      * Returns a copy of this <code>ZoneInfo</code>.
 645      */
 646     public Object clone() {
 647         ZoneInfo zi = (ZoneInfo) super.clone();
 648         zi.lastRule = null;
 649         return zi;
 650     }
 651 
 652     /**
 653      * Returns a hash code value calculated from the GMT offset and
 654      * transitions.
 655      * @return a hash code of this time zone
 656      */
 657     public int hashCode() {
 658         return getLastRawOffset() ^ checksum;
 659     }
 660 
 661     /**
 662      * Compares the equity of two ZoneInfo objects.
 663      *
 664      * @param obj the object to be compared with
 665      * @return true if given object is same as this ZoneInfo object,
 666      * false otherwise.
 667      */
 668     public boolean equals(Object obj) {
 669         if (this == obj) {
 670             return true;
 671         }
 672         if (!(obj instanceof ZoneInfo)) {
 673             return false;
 674         }
 675         ZoneInfo that = (ZoneInfo) obj;
 676         return (getID().equals(that.getID())
 677                 && (getLastRawOffset() == that.getLastRawOffset())
 678                 && (checksum == that.checksum));
 679     }
 680 
 681     /**
 682      * Returns true if this zone has the same raw GMT offset value and
 683      * transition table as another zone info. If the specified
 684      * TimeZone object is not a ZoneInfo instance, this method returns
 685      * true if the specified TimeZone object has the same raw GMT
 686      * offset value with no daylight saving time.
 687      *
 688      * @param other the ZoneInfo object to be compared with
 689      * @return true if the given <code>TimeZone</code> has the same
 690      * GMT offset and transition information; false, otherwise.
 691      */
 692     public boolean hasSameRules(TimeZone other) {
 693         if (this == other) {
 694             return true;
 695         }
 696         if (other == null) {
 697             return false;
 698         }
 699         if (!(other instanceof ZoneInfo)) {
 700             if (getRawOffset() != other.getRawOffset()) {
 701                 return false;
 702             }
 703             // if both have the same raw offset and neither observes
 704             // DST, they have the same rule.
 705             if ((transitions == null)
 706                 && (useDaylightTime() == false)
 707                 && (other.useDaylightTime() == false)) {
 708                 return true;
 709             }
 710             return false;
 711         }
 712         if (getLastRawOffset() != ((ZoneInfo)other).getLastRawOffset()) {
 713             return false;
 714         }
 715         return (checksum == ((ZoneInfo)other).checksum);
 716     }
 717 
 718     /**
 719      * Returns a Map from alias time zone IDs to their standard
 720      * time zone IDs.
 721      *
 722      * @return the Map that holds the mappings from alias time zone IDs
 723      *    to their standard time zone IDs, or null if
 724      *    <code>ZoneInfoMappings</code> file is not available.
 725      */
 726     public static Map<String, String> getAliasTable() {
 727          return ZoneInfoFile.getAliasMap();
 728     }
 729 
 730     private void readObject(ObjectInputStream stream)
 731             throws IOException, ClassNotFoundException {
 732         stream.defaultReadObject();
 733         // We don't know how this object from 1.4.x or earlier has
 734         // been mutated. So it should always be marked as `dirty'.
 735         dirty = true;
 736     }
 737 }