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
26package sun.util.calendar;
27
28import java.io.IOException;
29import java.io.ObjectInputStream;
30import java.util.Date;
31import java.util.Map;
32import java.util.SimpleTimeZone;
33import 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
63public 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 rawOffset.
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        }
297        int offset = getLastRawOffset();
298        if (offsets != null) {
299            offsets[0] = offset;
300            offsets[1] = 0;
301        }
302        return offset;
303    }
304
305    private int getTransitionIndex(long date, int type) {
306        int low = 0;
307        int high = transitions.length - 1;
308
309        while (low <= high) {
310            int mid = (low + high) / 2;
311            long val = transitions[mid];
312            long midVal = val >> TRANSITION_NSHIFT; // sign extended
313            if (type != UTC_TIME) {
314                midVal += offsets[(int)(val & OFFSET_MASK)]; // wall time
315            }
316            if (type == STANDARD_TIME) {
317                int dstIndex = (int)((val >>> DST_NSHIFT) & 0xfL);
318                if (dstIndex != 0) {
319                    midVal -= offsets[dstIndex]; // make it standard time
320                }
321            }
322
323            if (midVal < date) {
324                low = mid + 1;
325            } else if (midVal > date) {
326                high = mid - 1;
327            } else {
328                return mid;
329            }
330        }
331
332        // if beyond the transitions, returns that index.
333        if (low >= transitions.length) {
334            return low;
335        }
336        return low - 1;
337    }
338
339   /**
340     * Returns the difference in milliseconds between local time and
341     * UTC, taking into account both the raw offset and the effect of
342     * daylight savings, for the specified date and time.  This method
343     * assumes that the start and end month are distinct.  This method
344     * assumes a Gregorian calendar for calculations.
345     * <p>
346     * <em>Note: In general, clients should use
347     * {@link Calendar#ZONE_OFFSET Calendar.get(ZONE_OFFSET)} +
348     * {@link Calendar#DST_OFFSET Calendar.get(DST_OFFSET)}
349     * instead of calling this method.</em>
350     *
351     * @param era       The era of the given date. The value must be either
352     *                  GregorianCalendar.AD or GregorianCalendar.BC.
353     * @param year      The year in the given date.
354     * @param month     The month in the given date. Month is 0-based. e.g.,
355     *                  0 for January.
356     * @param day       The day-in-month of the given date.
357     * @param dayOfWeek The day-of-week of the given date.
358     * @param milliseconds The milliseconds in day in <em>standard</em> local time.
359     * @return The milliseconds to add to UTC to get local time.
360     */
361    public int getOffset(int era, int year, int month, int day,
362                         int dayOfWeek, int milliseconds) {
363        if (milliseconds < 0 || milliseconds >= AbstractCalendar.DAY_IN_MILLIS) {
364            throw new IllegalArgumentException();
365        }
366
367        if (era == java.util.GregorianCalendar.BC) { // BC
368            year = 1 - year;
369        } else if (era != java.util.GregorianCalendar.AD) {
370            throw new IllegalArgumentException();
371        }
372
373        Gregorian gcal = CalendarSystem.getGregorianCalendar();
374        CalendarDate date = gcal.newCalendarDate(null);
375        date.setDate(year, month + 1, day);
376        if (gcal.validate(date) == false) {
377            throw new IllegalArgumentException();
378        }
379
380        // bug-for-bug compatible argument checking
381        if (dayOfWeek < java.util.GregorianCalendar.SUNDAY
382            || dayOfWeek > java.util.GregorianCalendar.SATURDAY) {
383            throw new IllegalArgumentException();
384        }
385
386        if (transitions == null) {
387            return getLastRawOffset();
388        }
389
390        long dateInMillis = gcal.getTime(date) + milliseconds;
391        dateInMillis -= (long) rawOffset; // make it UTC
392        return getOffsets(dateInMillis, null, UTC_TIME);
393    }
394
395    /**
396     * Sets the base time zone offset from GMT. This operation
397     * modifies all the transitions of this ZoneInfo object, including
398     * historical ones, if applicable.
399     *
400     * @param offsetMillis the base time zone offset to GMT.
401     * @see getRawOffset
402     */
403    public synchronized void setRawOffset(int offsetMillis) {
404        if (offsetMillis == rawOffset + rawOffsetDiff) {
405            return;
406        }
407        rawOffsetDiff = offsetMillis - rawOffset;
408        if (lastRule != null) {
409            lastRule.setRawOffset(offsetMillis);
410        }
411        dirty = true;
412    }
413
414    /**
415     * Returns the GMT offset of the current date. This GMT offset
416     * value is not modified during Daylight Saving Time.
417     *
418     * @return the GMT offset value in milliseconds to add to UTC time
419     * to get local standard time
420     */
421    public int getRawOffset() {
422        if (!willGMTOffsetChange) {
423            return rawOffset + rawOffsetDiff;
424        }
425
426        int[] offsets = new int[2];
427        getOffsets(System.currentTimeMillis(), offsets, UTC_TIME);
428        return offsets[0];
429    }
430
431    public boolean isDirty() {
432        return dirty;
433    }
434
435    private int getLastRawOffset() {
436        return rawOffset + rawOffsetDiff;
437    }
438
439    /**
440     * Queries if this time zone uses Daylight Saving Time in the last known rule.
441     */
442    public boolean useDaylightTime() {
443        return (simpleTimeZoneParams != null);
444    }
445
446    @Override
447    public boolean observesDaylightTime() {
448        if (simpleTimeZoneParams != null) {
449            return true;
450        }
451        if (transitions == null) {
452            return false;
453        }
454
455        // Look up the transition table to see if it's in DST right
456        // now or if there's any standard-to-daylight transition at
457        // any future.
458        long utc = System.currentTimeMillis() - rawOffsetDiff;
459        int index = getTransitionIndex(utc, UTC_TIME);
460
461        // before transitions in the transition table
462        if (index < 0) {
463            return false;
464        }
465
466        // the time is in the table range.
467        for (int i = index; i < transitions.length; i++) {
468            if ((transitions[i] & DST_MASK) != 0) {
469                return true;
470            }
471        }
472        // No further DST is observed.
473        return false;
474    }
475
476    /**
477     * Queries if the specified date is in Daylight Saving Time.
478     */
479    public boolean inDaylightTime(Date date) {
480        if (date == null) {
481            throw new NullPointerException();
482        }
483
484        if (transitions == null) {
485            return false;
486        }
487
488        long utc = date.getTime() - rawOffsetDiff;
489        int index = getTransitionIndex(utc, UTC_TIME);
490
491        // before transitions in the transition table
492        if (index < 0) {
493            return false;
494        }
495
496        // the time is in the table range.
497        if (index < transitions.length) {
498            return (transitions[index] & DST_MASK) != 0;
499        }
500
501        // beyond the transition table
502        SimpleTimeZone tz = getLastRule();
503        if (tz != null) {
504            return tz.inDaylightTime(date);
505       }
506        return false;
507    }
508
509    /**
510     * Returns the amount of time in milliseconds that the clock is advanced
511     * during daylight saving time is in effect in its last daylight saving time rule.
512     *
513     * @return the number of milliseconds the time is advanced with respect to
514     * standard time when daylight saving time is in effect.
515     */
516    public int getDSTSavings() {
517        return dstSavings;
518    }
519
520//    /**
521//     * @return the last year in the transition table or -1 if this
522//     * time zone doesn't observe any daylight saving time.
523//     */
524//    public int getMaxTransitionYear() {
525//      if (transitions == null) {
526//          return -1;
527//      }
528//      long val = transitions[transitions.length - 1];
529//      int offset = this.offsets[(int)(val & OFFSET_MASK)] + rawOffsetDiff;
530//      val = (val >> TRANSITION_NSHIFT) + offset;
531//      CalendarDate lastDate = Gregorian.getCalendarDate(val);
532//      return lastDate.getYear();
533//    }
534
535    /**
536     * Returns a string representation of this time zone.
537     * @return the string
538     */
539    public String toString() {
540        return getClass().getName() +
541            "[id=\"" + getID() + "\"" +
542            ",offset=" + getLastRawOffset() +
543            ",dstSavings=" + dstSavings +
544            ",useDaylight=" + useDaylightTime() +
545            ",transitions=" + ((transitions != null) ? transitions.length : 0) +
546            ",lastRule=" + (lastRule == null ? getLastRuleInstance() : lastRule) +
547            "]";
548    }
549
550    /**
551     * Gets all available IDs supported in the Java run-time.
552     *
553     * @return an array of time zone IDs.
554     */
555    public static String[] getAvailableIDs() {
556        return ZoneInfoFile.getZoneIds();
557    }
558
559    /**
560     * Gets all available IDs that have the same value as the
561     * specified raw GMT offset.
562     *
563     * @param rawOffset the GMT offset in milliseconds. This
564     * value should not include any daylight saving time.
565     *
566     * @return an array of time zone IDs.
567     */
568    public static String[] getAvailableIDs(int rawOffset) {
569        return ZoneInfoFile.getZoneIds(rawOffset);
570    }
571
572    /**
573     * Gets the ZoneInfo for the given ID.
574     *
575     * @param ID the ID for a ZoneInfo. See TimeZone for detail.
576     *
577     * @return the specified ZoneInfo object, or null if there is no
578     * time zone of the ID.
579     */
580    public static TimeZone getTimeZone(String ID) {
581        return ZoneInfoFile.getZoneInfo(ID);
582    }
583
584    private transient SimpleTimeZone lastRule;
585
586    /**
587     * Returns a SimpleTimeZone object representing the last GMT
588     * offset and DST schedule or null if this time zone doesn't
589     * observe DST.
590     */
591    private synchronized SimpleTimeZone getLastRule() {
592        if (lastRule == null) {
593            lastRule = getLastRuleInstance();
594        }
595        return lastRule;
596    }
597
598    /**
599     * Returns a SimpleTimeZone object that represents the last
600     * known daylight saving time rules.
601     *
602     * @return a SimpleTimeZone object or null if this time zone
603     * doesn't observe DST.
604     */
605    public SimpleTimeZone getLastRuleInstance() {
606        if (simpleTimeZoneParams == null) {
607            return null;
608        }
609        if (simpleTimeZoneParams.length == 10) {
610            return new SimpleTimeZone(getLastRawOffset(), getID(),
611                                      simpleTimeZoneParams[0],
612                                      simpleTimeZoneParams[1],
613                                      simpleTimeZoneParams[2],
614                                      simpleTimeZoneParams[3],
615                                      simpleTimeZoneParams[4],
616                                      simpleTimeZoneParams[5],
617                                      simpleTimeZoneParams[6],
618                                      simpleTimeZoneParams[7],
619                                      simpleTimeZoneParams[8],
620                                      simpleTimeZoneParams[9],
621                                      dstSavings);
622        }
623        return new SimpleTimeZone(getLastRawOffset(), getID(),
624                                  simpleTimeZoneParams[0],
625                                  simpleTimeZoneParams[1],
626                                  simpleTimeZoneParams[2],
627                                  simpleTimeZoneParams[3],
628                                  simpleTimeZoneParams[4],
629                                  simpleTimeZoneParams[5],
630                                  simpleTimeZoneParams[6],
631                                  simpleTimeZoneParams[7],
632                                  dstSavings);
633    }
634
635    /**
636     * Returns a copy of this <code>ZoneInfo</code>.
637     */
638    public Object clone() {
639        ZoneInfo zi = (ZoneInfo) super.clone();
640        zi.lastRule = null;
641        return zi;
642    }
643
644    /**
645     * Returns a hash code value calculated from the GMT offset and
646     * transitions.
647     * @return a hash code of this time zone
648     */
649    public int hashCode() {
650        return getLastRawOffset() ^ checksum;
651    }
652
653    /**
654     * Compares the equity of two ZoneInfo objects.
655     *
656     * @param obj the object to be compared with
657     * @return true if given object is same as this ZoneInfo object,
658     * false otherwise.
659     */
660    public boolean equals(Object obj) {
661        if (this == obj) {
662            return true;
663        }
664        if (!(obj instanceof ZoneInfo)) {
665            return false;
666        }
667        ZoneInfo that = (ZoneInfo) obj;
668        return (getID().equals(that.getID())
669                && (getLastRawOffset() == that.getLastRawOffset())
670                && (checksum == that.checksum));
671    }
672
673    /**
674     * Returns true if this zone has the same raw GMT offset value and
675     * transition table as another zone info. If the specified
676     * TimeZone object is not a ZoneInfo instance, this method returns
677     * true if the specified TimeZone object has the same raw GMT
678     * offset value with no daylight saving time.
679     *
680     * @param other the ZoneInfo object to be compared with
681     * @return true if the given <code>TimeZone</code> has the same
682     * GMT offset and transition information; false, otherwise.
683     */
684    public boolean hasSameRules(TimeZone other) {
685        if (this == other) {
686            return true;
687        }
688        if (other == null) {
689            return false;
690        }
691        if (!(other instanceof ZoneInfo)) {
692            if (getRawOffset() != other.getRawOffset()) {
693                return false;
694            }
695            // if both have the same raw offset and neither observes
696            // DST, they have the same rule.
697            if ((transitions == null)
698                && (useDaylightTime() == false)
699                && (other.useDaylightTime() == false)) {
700                return true;
701            }
702            return false;
703        }
704        if (getLastRawOffset() != ((ZoneInfo)other).getLastRawOffset()) {
705            return false;
706        }
707        return (checksum == ((ZoneInfo)other).checksum);
708    }
709
710    /**
711     * Returns a Map from alias time zone IDs to their standard
712     * time zone IDs.
713     *
714     * @return the Map that holds the mappings from alias time zone IDs
715     *    to their standard time zone IDs, or null if
716     *    <code>ZoneInfoMappings</code> file is not available.
717     */
718    public static Map<String, String> getAliasTable() {
719         return ZoneInfoFile.getAliasMap();
720    }
721
722    private void readObject(ObjectInputStream stream)
723            throws IOException, ClassNotFoundException {
724        stream.defaultReadObject();
725        // We don't know how this object from 1.4.x or earlier has
726        // been mutated. So it should always be marked as `dirty'.
727        dirty = true;
728    }
729}
730