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