1 /*
2  * Copyright (c) 2000, 2021, 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      */
ZoneInfo()171     public ZoneInfo() {
172     }
173 
174     /**
175      * A Constructor for CustomID.
176      */
ZoneInfo(String ID, int rawOffset)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      */
ZoneInfo(String ID, int rawOffset, int dstSavings, int checksum, long[] transitions, int[] offsets, int[] simpleTimeZoneParams, boolean willGMTOffsetChange)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      */
getOffset(long date)222     public int getOffset(long date) {
223         return getOffsets(date, null, UTC_TIME);
224     }
225 
getOffsets(long utc, int[] offsets)226     public int getOffsets(long utc, int[] offsets) {
227         return getOffsets(utc, offsets, UTC_TIME);
228     }
229 
getOffsetsByStandard(long standard, int[] offsets)230     public int getOffsetsByStandard(long standard, int[] offsets) {
231         return getOffsets(standard, offsets, STANDARD_TIME);
232     }
233 
getOffsetsByWall(long wall, int[] offsets)234     public int getOffsetsByWall(long wall, int[] offsets) {
235         return getOffsets(wall, offsets, WALL_TIME);
236     }
237 
getOffsets(long date, int[] offsets, int type)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 && type == WALL_TIME) {
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 
getTransitionIndex(long date, int type)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      */
getOffset(int era, int year, int month, int day, int dayOfWeek, int milliseconds)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      */
setRawOffset(int offsetMillis)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      */
getRawOffset()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 
isDirty()436     public boolean isDirty() {
437         return dirty;
438     }
439 
getLastRawOffset()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      */
useDaylightTime()447     public boolean useDaylightTime() {
448         return (simpleTimeZoneParams != null);
449     }
450 
451     @Override
observesDaylightTime()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      */
inDaylightTime(Date date)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      */
getDSTSavings()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      */
toString()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      */
getAvailableIDs()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      */
getAvailableIDs(int rawOffset)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      */
getTimeZone(String ID)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      */
getLastRule()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      */
getLastRuleInstance()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      */
clone()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      */
hashCode()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      */
equals(Object obj)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      */
hasSameRules(TimeZone other)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      */
getAliasTable()725     public static Map<String, String> getAliasTable() {
726          return ZoneInfoFile.getAliasMap();
727     }
728 
readObject(ObjectInputStream stream)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 }
737