1// © 2016 and later: Unicode, Inc. and others.
2// License & terms of use: http://www.unicode.org/copyright.html#License
3 /*
4*   Copyright (C) 1996-2016, International Business Machines
5*   Corporation and others.  All Rights Reserved.
6*/
7
8package com.ibm.icu.util;
9
10import java.io.IOException;
11import java.util.Date;
12
13import com.ibm.icu.impl.Grego;
14
15/**
16 * {@icuenhanced java.util.SimpleTimeZone}.{@icu _usage_}
17 *
18 * <p><code>SimpleTimeZone</code> is a concrete subclass of <code>TimeZone</code>
19 * that represents a time zone for use with a Gregorian calendar. This
20 * class does not handle historical changes.
21 *
22 * <p>Use a negative value for <code>dayOfWeekInMonth</code> to indicate that
23 * <code>SimpleTimeZone</code> should count from the end of the month backwards.  For
24 * example, if Daylight Savings Time starts or ends at the last Sunday in a month, use
25 * <code>dayOfWeekInMonth = -1</code> along with <code>dayOfWeek = Calendar.SUNDAY</code>
26 * to specify the rule.
27 *
28 * @see      Calendar
29 * @see      GregorianCalendar
30 * @see      TimeZone
31 * @author   Deborah Goldsmith, Mark Davis, Chen-Lieh Huang, Alan Liu
32 * @stable ICU 2.0
33 */
34public class SimpleTimeZone extends BasicTimeZone {
35    private static final long serialVersionUID = -7034676239311322769L;
36
37    /**
38     * Constant for a mode of start or end time specified as local wall time.
39     * @stable ICU 3.8
40     */
41    public static final int WALL_TIME = 0;
42
43    /**
44     * Constant for a mode of start or end time specified as local standard time.
45     * @stable ICU 3.8
46     */
47    public static final int STANDARD_TIME = 1;
48
49    /**
50     * Constant for a mode of start or end time specified as UTC.
51     * @stable ICU 3.8
52     */
53    public static final int UTC_TIME = 2;
54
55    /**
56     * Constructs a SimpleTimeZone with the given base time zone offset from GMT
57     * and time zone ID. Timezone IDs can be obtained from
58     * TimeZone.getAvailableIDs. Normally you should use TimeZone.getDefault to
59     * construct a TimeZone.
60     *
61     * @param rawOffset  The given base time zone offset to GMT.
62     * @param ID         The time zone ID which is obtained from
63     *                   TimeZone.getAvailableIDs.
64     * @stable ICU 2.0
65     */
66    public SimpleTimeZone(int rawOffset, String ID) {
67        super(ID);
68        construct(rawOffset, 0, 0, 0,
69                0, WALL_TIME,
70                0, 0, 0,
71                0, WALL_TIME,
72                Grego.MILLIS_PER_HOUR);
73    }
74
75    /**
76     * Constructs a SimpleTimeZone with the given base time zone offset from
77     * GMT, time zone ID, time to start and end the daylight time. Timezone IDs
78     * can be obtained from TimeZone.getAvailableIDs. Normally you should use
79     * TimeZone.getDefault to create a TimeZone. For a time zone that does not
80     * use daylight saving time, do not use this constructor; instead you should
81     * use SimpleTimeZone(rawOffset, ID).
82     *
83     * By default, this constructor specifies day-of-week-in-month rules. That
84     * is, if the startDay is 1, and the startDayOfWeek is SUNDAY, then this
85     * indicates the first Sunday in the startMonth. A startDay of -1 likewise
86     * indicates the last Sunday. However, by using negative or zero values for
87     * certain parameters, other types of rules can be specified.
88     *
89     * Day of month. To specify an exact day of the month, such as March 1, set
90     * startDayOfWeek to zero.
91     *
92     * Day of week after day of month. To specify the first day of the week
93     * occurring on or after an exact day of the month, make the day of the week
94     * negative. For example, if startDay is 5 and startDayOfWeek is -MONDAY,
95     * this indicates the first Monday on or after the 5th day of the
96     * startMonth.
97     *
98     * Day of week before day of month. To specify the last day of the week
99     * occurring on or before an exact day of the month, make the day of the
100     * week and the day of the month negative. For example, if startDay is -21
101     * and startDayOfWeek is -WEDNESDAY, this indicates the last Wednesday on or
102     * before the 21st of the startMonth.
103     *
104     * The above examples refer to the startMonth, startDay, and startDayOfWeek;
105     * the same applies for the endMonth, endDay, and endDayOfWeek.
106     *
107     * @param rawOffset       The given base time zone offset to GMT.
108     * @param ID              The time zone ID which is obtained from
109     *                        TimeZone.getAvailableIDs.
110     * @param startMonth      The daylight savings starting month. Month is
111     *                        0-based. eg, 0 for January.
112     * @param startDay        The daylight savings starting
113     *                        day-of-week-in-month. Please see the member
114     *                        description for an example.
115     * @param startDayOfWeek  The daylight savings starting day-of-week. Please
116     *                        see the member description for an example.
117     * @param startTime       The daylight savings starting time in local wall
118     *                        time, which is standard time in this case. Please see the
119     *                        member description for an example.
120     * @param endMonth        The daylight savings ending month. Month is
121     *                        0-based. eg, 0 for January.
122     * @param endDay          The daylight savings ending day-of-week-in-month.
123     *                        Please see the member description for an example.
124     * @param endDayOfWeek    The daylight savings ending day-of-week. Please
125     *                        see the member description for an example.
126     * @param endTime         The daylight savings ending time in local wall time,
127     *                        which is daylight time in this case. Please see the
128     *                        member description for an example.
129     * @throws IllegalArgumentException the month, day, dayOfWeek, or time
130     * parameters are out of range for the start or end rule
131     * @stable ICU 2.0
132     */
133    public SimpleTimeZone(int rawOffset, String ID,
134                          int startMonth, int startDay, int startDayOfWeek, int startTime,
135                          int endMonth, int endDay, int endDayOfWeek, int endTime) {
136        super(ID);
137        construct(rawOffset,
138                startMonth, startDay, startDayOfWeek,
139                startTime, WALL_TIME,
140                endMonth, endDay, endDayOfWeek,
141                endTime, WALL_TIME,
142                Grego.MILLIS_PER_HOUR);
143    }
144
145    /**
146     * Constructs a SimpleTimeZone with the given base time zone offset from
147     * GMT, time zone ID, time and its mode to start and end the daylight time.
148     * The mode specifies either {@link #WALL_TIME} or {@link #STANDARD_TIME}
149     * or {@link #UTC_TIME}.
150     *
151     * @param rawOffset       The given base time zone offset to GMT.
152     * @param ID              The time zone ID which is obtained from
153     *                        TimeZone.getAvailableIDs.
154     * @param startMonth      The daylight savings starting month. Month is
155     *                        0-based. eg, 0 for January.
156     * @param startDay        The daylight savings starting
157     *                        day-of-week-in-month. Please see the member
158     *                        description for an example.
159     * @param startDayOfWeek  The daylight savings starting day-of-week. Please
160     *                        see the member description for an example.
161     * @param startTime       The daylight savings starting time in local wall
162     *                        time, which is standard time in this case. Please see the
163     *                        member description for an example.
164     * @param startTimeMode   The mode of the start time specified by startTime.
165     * @param endMonth        The daylight savings ending month. Month is
166     *                        0-based. eg, 0 for January.
167     * @param endDay          The daylight savings ending day-of-week-in-month.
168     *                        Please see the member description for an example.
169     * @param endDayOfWeek    The daylight savings ending day-of-week. Please
170     *                        see the member description for an example.
171     * @param endTime         The daylight savings ending time in local wall time,
172     *                        which is daylight time in this case. Please see the
173     *                        member description for an example.
174     * @param endTimeMode     The mode of the end time specified by endTime.
175     * @param dstSavings      The amount of time in ms saved during DST.
176     * @throws IllegalArgumentException the month, day, dayOfWeek, or time
177     * parameters are out of range for the start or end rule
178     * @stable ICU 3.8
179     */
180    public SimpleTimeZone(int rawOffset,  String ID,
181                          int startMonth, int startDay,
182                          int startDayOfWeek, int startTime,
183                          int startTimeMode,
184                          int endMonth, int endDay,
185                          int endDayOfWeek, int endTime,
186                          int endTimeMode,int dstSavings){
187        super(ID);
188        construct(rawOffset,
189                  startMonth, startDay, startDayOfWeek,
190                  startTime, startTimeMode,
191                  endMonth, endDay, endDayOfWeek,
192                  endTime, endTimeMode,
193                  dstSavings);
194    }
195
196    /**
197     * Constructor.  This constructor is identical to the 10-argument
198     * constructor, but also takes a dstSavings parameter.
199     * @param rawOffset       The given base time zone offset to GMT.
200     * @param ID              The time zone ID which is obtained from
201     *                        TimeZone.getAvailableIDs.
202     * @param startMonth      The daylight savings starting month. Month is
203     *                        0-based. eg, 0 for January.
204     * @param startDay        The daylight savings starting
205     *                        day-of-week-in-month. Please see the member
206     *                        description for an example.
207     * @param startDayOfWeek  The daylight savings starting day-of-week. Please
208     *                        see the member description for an example.
209     * @param startTime       The daylight savings starting time in local wall
210     *                        time, which is standard time in this case. Please see the
211     *                        member description for an example.
212     * @param endMonth        The daylight savings ending month. Month is
213     *                        0-based. eg, 0 for January.
214     * @param endDay          The daylight savings ending day-of-week-in-month.
215     *                        Please see the member description for an example.
216     * @param endDayOfWeek    The daylight savings ending day-of-week. Please
217     *                        see the member description for an example.
218     * @param endTime         The daylight savings ending time in local wall time,
219     *                        which is daylight time in this case. Please see the
220     *                        member description for an example.
221     * @param dstSavings      The amount of time in ms saved during DST.
222     * @throws IllegalArgumentException the month, day, dayOfWeek, or time
223     * parameters are out of range for the start or end rule
224     * @stable ICU 2.0
225     */
226    public SimpleTimeZone(int rawOffset, String ID,
227                          int startMonth, int startDay, int startDayOfWeek, int startTime,
228                          int endMonth, int endDay, int endDayOfWeek, int endTime,
229                          int dstSavings) {
230        super(ID);
231        construct(rawOffset,
232                startMonth, startDay, startDayOfWeek,
233                startTime, WALL_TIME,
234                endMonth, endDay, endDayOfWeek,
235                endTime, WALL_TIME,
236                dstSavings);
237    }
238
239    /**
240     * {@inheritDoc}
241     *
242     * @stable ICU 3.8
243     */
244    @Override
245    public void setID(String ID) {
246        if (isFrozen()) {
247            throw new UnsupportedOperationException("Attempt to modify a frozen SimpleTimeZone instance.");
248        }
249        super.setID(ID);
250        transitionRulesInitialized = false;
251    }
252
253    /**
254     * Overrides TimeZone
255     * Sets the base time zone offset to GMT.
256     * This is the offset to add "to" UTC to get local time.
257     * @param offsetMillis the raw offset of the time zone
258     * @stable ICU 2.0
259     */
260    @Override
261    public void setRawOffset(int offsetMillis) {
262        if (isFrozen()) {
263            throw new UnsupportedOperationException("Attempt to modify a frozen SimpleTimeZone instance.");
264        }
265
266        raw = offsetMillis;
267        transitionRulesInitialized = false;
268    }
269
270    /**
271     * Overrides TimeZone
272     * Gets the GMT offset for this time zone.
273     * @return the raw offset
274     * @stable ICU 2.0
275     */
276    @Override
277    public int getRawOffset() {
278        return raw;
279    }
280
281    /**
282     * Sets the daylight savings starting year.
283     *
284     * @param year  The daylight savings starting year.
285     * @stable ICU 2.0
286     */
287    public void setStartYear(int year) {
288        if (isFrozen()) {
289            throw new UnsupportedOperationException("Attempt to modify a frozen SimpleTimeZone instance.");
290        }
291
292        getSTZInfo().sy = year;
293        this.startYear = year;
294        transitionRulesInitialized = false;
295    }
296
297    /**
298     * Sets the daylight savings starting rule. For example, Daylight Savings
299     * Time starts at the second Sunday in March, at 2 AM in standard time.
300     * Therefore, you can set the start rule by calling:
301     * setStartRule(Calendar.MARCH, 2, Calendar.SUNDAY, 2*60*60*1000);
302     *
303     * @param month             The daylight savings starting month. Month is
304     *                          0-based. eg, 0 for January.
305     * @param dayOfWeekInMonth  The daylight savings starting
306     *                          day-of-week-in-month. Please see the member
307     *                          description for an example.
308     * @param dayOfWeek         The daylight savings starting day-of-week.
309     *                          Please see the member description for an
310     *                          example.
311     * @param time              The daylight savings starting time in local wall
312     *                          time, which is standard time in this case. Please see
313     *                          the member description for an example.
314     * @throws IllegalArgumentException the month, dayOfWeekInMonth,
315     * dayOfWeek, or time parameters are out of range
316     * @stable ICU 2.0
317     */
318    public void setStartRule(int month, int dayOfWeekInMonth, int dayOfWeek,
319                             int time) {
320        if (isFrozen()) {
321            throw new UnsupportedOperationException("Attempt to modify a frozen SimpleTimeZone instance.");
322        }
323
324        getSTZInfo().setStart(month, dayOfWeekInMonth, dayOfWeek, time, -1, false);
325        setStartRule(month, dayOfWeekInMonth, dayOfWeek, time, WALL_TIME);
326    }
327
328    /**
329     * Sets the daylight savings starting rule. For example, in the U.S., Daylight Savings
330     * Time starts at the second Sunday in March, at 2 AM in standard time.
331     * Therefore, you can set the start rule by calling:
332     * <code>setStartRule(Calendar.MARCH, 2, Calendar.SUNDAY, 2*60*60*1000);</code>
333     * The dayOfWeekInMonth and dayOfWeek parameters together specify how to calculate
334     * the exact starting date.  Their exact meaning depend on their respective signs,
335     * allowing various types of rules to be constructed, as follows:<ul>
336     *   <li>If both dayOfWeekInMonth and dayOfWeek are positive, they specify the
337     *       day of week in the month (e.g., (2, WEDNESDAY) is the second Wednesday
338     *       of the month).
339     *   <li>If dayOfWeek is positive and dayOfWeekInMonth is negative, they specify
340     *       the day of week in the month counting backward from the end of the month.
341     *       (e.g., (-1, MONDAY) is the last Monday in the month)
342     *   <li>If dayOfWeek is zero and dayOfWeekInMonth is positive, dayOfWeekInMonth
343     *       specifies the day of the month, regardless of what day of the week it is.
344     *       (e.g., (10, 0) is the tenth day of the month)
345     *   <li>If dayOfWeek is zero and dayOfWeekInMonth is negative, dayOfWeekInMonth
346     *       specifies the day of the month counting backward from the end of the
347     *       month, regardless of what day of the week it is (e.g., (-2, 0) is the
348     *       next-to-last day of the month).
349     *   <li>If dayOfWeek is negative and dayOfWeekInMonth is positive, they specify the
350     *       first specified day of the week on or after the specfied day of the month.
351     *       (e.g., (15, -SUNDAY) is the first Sunday after the 15th of the month
352     *       [or the 15th itself if the 15th is a Sunday].)
353     *   <li>If dayOfWeek and DayOfWeekInMonth are both negative, they specify the
354     *       last specified day of the week on or before the specified day of the month.
355     *       (e.g., (-20, -TUESDAY) is the last Tuesday before the 20th of the month
356     *       [or the 20th itself if the 20th is a Tuesday].)</ul>
357     * @param month the daylight savings starting month. Month is 0-based.
358     * eg, 0 for January.
359     * @param dayOfWeekInMonth the daylight savings starting
360     * day-of-week-in-month. Please see the member description for an example.
361     * @param dayOfWeek the daylight savings starting day-of-week. Please see
362     * the member description for an example.
363     * @param time the daylight savings starting time. Please see the member
364     * description for an example.
365     */
366    private void setStartRule(int month, int dayOfWeekInMonth, int dayOfWeek, int time, int mode) {
367        assert (!isFrozen());
368
369        startMonth     =  month;
370        startDay       = dayOfWeekInMonth;
371        startDayOfWeek = dayOfWeek;
372        startTime      = time;
373        startTimeMode  = mode;
374        decodeStartRule();
375
376        transitionRulesInitialized = false;
377    }
378
379    /**
380     * Sets the DST start rule to a fixed date within a month.
381     *
382     * @param month         The month in which this rule occurs (0-based).
383     * @param dayOfMonth    The date in that month (1-based).
384     * @param time          The time of that day (number of millis after midnight)
385     *                      when DST takes effect in local wall time, which is
386     *                      standard time in this case.
387     * @throws IllegalArgumentException the month,
388     * dayOfMonth, or time parameters are out of range
389     * @stable ICU 2.0
390     */
391    public void setStartRule(int month, int dayOfMonth, int time) {
392        if (isFrozen()) {
393            throw new UnsupportedOperationException("Attempt to modify a frozen SimpleTimeZone instance.");
394        }
395
396        getSTZInfo().setStart(month, -1, -1, time, dayOfMonth, false);
397        setStartRule(month, dayOfMonth, 0, time, WALL_TIME);
398    }
399
400    /**
401     * Sets the DST start rule to a weekday before or after a give date within
402     * a month, e.g., the first Monday on or after the 8th.
403     *
404     * @param month         The month in which this rule occurs (0-based).
405     * @param dayOfMonth    A date within that month (1-based).
406     * @param dayOfWeek     The day of the week on which this rule occurs.
407     * @param time          The time of that day (number of millis after midnight)
408     *                      when DST takes effect in local wall time, which is
409     *                      standard time in this case.
410     * @param after         If true, this rule selects the first dayOfWeek on
411     *                      or after dayOfMonth.  If false, this rule selects
412     *                      the last dayOfWeek on or before dayOfMonth.
413     * @throws IllegalArgumentException the month, dayOfMonth,
414     * dayOfWeek, or time parameters are out of range
415     * @stable ICU 2.0
416     */
417    public void setStartRule(int month, int dayOfMonth, int dayOfWeek, int time, boolean after) {
418        if (isFrozen()) {
419            throw new UnsupportedOperationException("Attempt to modify a frozen SimpleTimeZone instance.");
420        }
421
422        getSTZInfo().setStart(month, -1, dayOfWeek, time, dayOfMonth, after);
423        setStartRule(month, after ? dayOfMonth : -dayOfMonth,
424                -dayOfWeek, time, WALL_TIME);
425    }
426
427    /**
428     * Sets the daylight savings ending rule. For example, if Daylight Savings Time
429     * ends at the last (-1) Sunday in October, at 2 AM in standard time,
430     * you can set the end rule by calling:
431     * <code>setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2*60*60*1000);</code>
432     *
433     * @param month             The daylight savings ending month. Month is
434     *                          0-based. eg, 0 for January.
435     * @param dayOfWeekInMonth  The daylight savings ending
436     *                          day-of-week-in-month. Please see the member
437     *                          description for an example.
438     * @param dayOfWeek         The daylight savings ending day-of-week. Please
439     *                          see the member description for an example.
440     * @param time              The daylight savings ending time in local wall time,
441     *                          which is daylight time in this case. Please see the
442     *                          member description for an example.
443     * @throws IllegalArgumentException the month, dayOfWeekInMonth,
444     * dayOfWeek, or time parameters are out of range
445     * @stable ICU 2.0
446     */
447    public void setEndRule(int month, int dayOfWeekInMonth, int dayOfWeek, int time) {
448        if (isFrozen()) {
449            throw new UnsupportedOperationException("Attempt to modify a frozen SimpleTimeZone instance.");
450        }
451
452        getSTZInfo().setEnd(month, dayOfWeekInMonth, dayOfWeek, time, -1, false);
453        setEndRule(month, dayOfWeekInMonth, dayOfWeek, time, WALL_TIME);
454    }
455
456    /**
457     * Sets the DST end rule to a fixed date within a month.
458     *
459     * @param month         The month in which this rule occurs (0-based).
460     * @param dayOfMonth    The date in that month (1-based).
461     * @param time          The time of that day (number of millis after midnight)
462     *                      when DST ends in local wall time, which is daylight
463     *                      time in this case.
464     * @throws IllegalArgumentException the month,
465     * dayOfMonth, or time parameters are out of range
466     * @stable ICU 2.0
467     */
468    public void setEndRule(int month, int dayOfMonth, int time) {
469        if (isFrozen()) {
470            throw new UnsupportedOperationException("Attempt to modify a frozen SimpleTimeZone instance.");
471        }
472
473        getSTZInfo().setEnd(month, -1, -1, time, dayOfMonth, false);
474        setEndRule(month, dayOfMonth, WALL_TIME, time);
475    }
476
477    /**
478     * Sets the DST end rule to a weekday before or after a give date within
479     * a month, e.g., the first Monday on or after the 8th.
480     *
481     * @param month         The month in which this rule occurs (0-based).
482     * @param dayOfMonth    A date within that month (1-based).
483     * @param dayOfWeek     The day of the week on which this rule occurs.
484     * @param time          The time of that day (number of millis after midnight)
485     *                      when DST ends in local wall time, which is daylight
486     *                      time in this case.
487     * @param after         If true, this rule selects the first dayOfWeek on
488     *                      or after dayOfMonth.  If false, this rule selects
489     *                      the last dayOfWeek on or before dayOfMonth.
490     * @throws IllegalArgumentException the month, dayOfMonth,
491     * dayOfWeek, or time parameters are out of range
492     * @stable ICU 2.0
493     */
494    public void setEndRule(int month, int dayOfMonth, int dayOfWeek, int time, boolean after) {
495        if (isFrozen()) {
496            throw new UnsupportedOperationException("Attempt to modify a frozen SimpleTimeZone instance.");
497        }
498
499        getSTZInfo().setEnd(month, -1, dayOfWeek, time, dayOfMonth, after);
500        setEndRule(month, dayOfMonth, dayOfWeek, time, WALL_TIME, after);
501    }
502
503    private void setEndRule(int month, int dayOfMonth, int dayOfWeek,
504                                                int time, int mode, boolean after){
505        assert (!isFrozen());
506        setEndRule(month, after ? dayOfMonth : -dayOfMonth, -dayOfWeek, time, mode);
507    }
508
509    /**
510     * Sets the daylight savings ending rule. For example, in the U.S., Daylight
511     * Savings Time ends at the first Sunday in November, at 2 AM in standard time.
512     * Therefore, you can set the end rule by calling:
513     * setEndRule(Calendar.NOVEMBER, 1, Calendar.SUNDAY, 2*60*60*1000);
514     * Various other types of rules can be specified by manipulating the dayOfWeek
515     * and dayOfWeekInMonth parameters.  For complete details, see the documentation
516     * for setStartRule().
517     * @param month the daylight savings ending month. Month is 0-based.
518     * eg, 0 for January.
519     * @param dayOfWeekInMonth the daylight savings ending
520     * day-of-week-in-month. See setStartRule() for a complete explanation.
521     * @param dayOfWeek the daylight savings ending day-of-week. See setStartRule()
522     * for a complete explanation.
523     * @param time the daylight savings ending time. Please see the member
524     * description for an example.
525     */
526    private void setEndRule(int month, int dayOfWeekInMonth, int dayOfWeek, int time, int mode){
527        assert (!isFrozen());
528
529        endMonth     = month;
530        endDay       = dayOfWeekInMonth;
531        endDayOfWeek = dayOfWeek;
532        endTime      = time;
533        endTimeMode  = mode;
534        decodeEndRule();
535
536        transitionRulesInitialized = false;
537    }
538
539    /**
540     * Sets the amount of time in ms that the clock is advanced during DST.
541     * @param millisSavedDuringDST the number of milliseconds the time is
542     * advanced with respect to standard time when the daylight savings rules
543     * are in effect. A positive number, typically one hour (3600000).
544     * @stable ICU 2.0
545     */
546    public void setDSTSavings(int millisSavedDuringDST) {
547        if (isFrozen()) {
548            throw new UnsupportedOperationException("Attempt to modify a frozen SimpleTimeZone instance.");
549        }
550
551        if (millisSavedDuringDST <= 0) {
552            throw new IllegalArgumentException();
553        }
554        dst = millisSavedDuringDST;
555
556        transitionRulesInitialized = false;
557    }
558
559    /**
560     * Returns the amount of time in ms that the clock is advanced during DST.
561     * @return the number of milliseconds the time is
562     * advanced with respect to standard time when the daylight savings rules
563     * are in effect. A positive number, typically one hour (3600000).
564     * @stable ICU 2.0
565     */
566    @Override
567    public int getDSTSavings() {
568        return dst;
569    }
570
571    /**
572     * Returns the java.util.SimpleTimeZone that this class wraps.
573     *
574    java.util.SimpleTimeZone unwrapSTZ() {
575        return (java.util.SimpleTimeZone) unwrap();
576    }
577    */
578
579    // on JDK 1.4 and later, can't deserialize a SimpleTimeZone as a SimpleTimeZone...
580    private void readObject(java.io.ObjectInputStream in) throws IOException,
581        ClassNotFoundException {
582        in.defaultReadObject();
583        /*
584        String id = getID();
585        if (id!=null && !(zone instanceof java.util.SimpleTimeZone && zone.getID().equals(id))) {
586            // System.out.println("*** readjust " + zone.getClass().getName() +
587            // " " + zone.getID() + " ***");
588            java.util.SimpleTimeZone stz =
589                new java.util.SimpleTimeZone(raw, id);
590            if (dst != 0) {
591                stz.setDSTSavings(dst);
592                // if it is 0, then there shouldn't be start/end rules and the default
593                // behavior should be no dst
594            }
595
596            if (xinfo != null) {
597                xinfo.applyTo(stz);
598            }
599            zoneJDK = stz;
600        }
601        */
602        /* set all instance variables in this object
603         * to the values in zone
604         */
605         if (xinfo != null) {
606             xinfo.applyTo(this);
607         }
608    }
609
610    /**
611     * Returns a string representation of this object.
612     * @return  a string representation of this object
613     * @stable ICU 3.6
614     */
615    @Override
616    public String toString() {
617        return "SimpleTimeZone: " + getID();
618    }
619
620    private STZInfo getSTZInfo() {
621        if (xinfo == null) {
622            xinfo = new STZInfo();
623        }
624        return xinfo;
625    }
626
627    //  Use only for decodeStartRule() and decodeEndRule() where the year is not
628    //  available. Set February to 29 days to accomodate rules with that date
629    //  and day-of-week-on-or-before-that-date mode (DOW_LE_DOM_MODE).
630    //  The compareToRule() method adjusts to February 28 in non-leap years.
631    //
632    //  For actual getOffset() calculations, use TimeZone::monthLength() and
633    //  TimeZone::previousMonthLength() which take leap years into account.
634    //  We handle leap years assuming always
635    //  Gregorian, since we know they didn't have daylight time when
636    //  Gregorian calendar started.
637    private final static byte staticMonthLength[] = {31,29,31,30,31,30,31,31,30,31,30,31};
638
639    /**
640     * {@inheritDoc}
641     * @stable ICU 2.0
642     */
643    @Override
644    public int getOffset(int era, int year, int month, int day,
645                         int dayOfWeek, int millis)
646    {
647        // Check the month before calling Grego.monthLength(). This
648        // duplicates the test that occurs in the 7-argument getOffset(),
649        // however, this is unavoidable. We don't mind because this method, in
650        // fact, should not be called; internal code should always call the
651        // 7-argument getOffset(), and outside code should use Calendar.get(int
652        // field) with fields ZONE_OFFSET and DST_OFFSET. We can't get rid of
653        // this method because it's public API. - liu 8/10/98
654        if(month < Calendar.JANUARY || month > Calendar.DECEMBER) {
655            throw new IllegalArgumentException();
656        }
657
658        return getOffset(era, year, month, day, dayOfWeek, millis, Grego.monthLength(year, month));
659    }
660
661    /**
662     * @internal
663     * @deprecated This API is ICU internal only.
664     */
665    @Deprecated
666    public int getOffset(int era, int year, int month, int day,
667                              int dayOfWeek, int millis,
668                              int monthLength)  {
669        // Check the month before calling Grego.monthLength(). This
670        // duplicates a test that occurs in the 9-argument getOffset(),
671        // however, this is unavoidable. We don't mind because this method, in
672        // fact, should not be called; internal code should always call the
673        // 9-argument getOffset(), and outside code should use Calendar.get(int
674        // field) with fields ZONE_OFFSET and DST_OFFSET. We can't get rid of
675        // this method because it's public API. - liu 8/10/98
676        if(month < Calendar.JANUARY || month > Calendar.DECEMBER) {
677            throw new IllegalArgumentException();
678        }
679
680        return getOffset(era, year, month, day, dayOfWeek, millis,
681                         Grego.monthLength(year, month), Grego.previousMonthLength(year, month));
682    }
683
684    private int getOffset(int era, int year, int month, int day,
685                  int dayOfWeek, int millis,
686                  int monthLength, int prevMonthLength ){
687
688        if (true) {
689            /* Use this parameter checking code for normal operation.  Only one
690             * of these two blocks should actually get compiled into the class
691             * file.  */
692            if ((era != GregorianCalendar.AD && era != GregorianCalendar.BC)
693                || month < Calendar.JANUARY
694                || month > Calendar.DECEMBER
695                || day < 1
696                || day > monthLength
697                || dayOfWeek < Calendar.SUNDAY
698                || dayOfWeek > Calendar.SATURDAY
699                || millis < 0
700                || millis >= Grego.MILLIS_PER_DAY
701                || monthLength < 28
702                || monthLength > 31
703                || prevMonthLength < 28
704                || prevMonthLength > 31) {
705                throw new IllegalArgumentException();
706            }
707        }
708        //Eclipse stated the following is "dead code"
709        /*else {
710            // This parameter checking code is better for debugging, but
711            // overkill for normal operation.  Only one of these two blocks
712            // should actually get compiled into the class file.
713            if (era != GregorianCalendar.AD && era != GregorianCalendar.BC) {
714                throw new IllegalArgumentException("Illegal era " + era);
715            }
716            if (month < Calendar.JANUARY
717                || month > Calendar.DECEMBER) {
718                throw new IllegalArgumentException("Illegal month " + month);
719            }
720            if (day < 1
721                || day > monthLength) {
722                throw new IllegalArgumentException("Illegal day " + day+" max month len: "+monthLength);
723            }
724            if (dayOfWeek < Calendar.SUNDAY
725                || dayOfWeek > Calendar.SATURDAY) {
726                throw new IllegalArgumentException("Illegal day of week " + dayOfWeek);
727            }
728            if (millis < 0
729                || millis >= Grego.MILLIS_PER_DAY) {
730                throw new IllegalArgumentException("Illegal millis " + millis);
731            }
732            if (monthLength < 28
733                || monthLength > 31) {
734                throw new IllegalArgumentException("Illegal month length " + monthLength);
735            }
736            if (prevMonthLength < 28
737                || prevMonthLength > 31) {
738                throw new IllegalArgumentException("Illegal previous month length " + prevMonthLength);
739            }
740        }*/
741
742        int result = raw;
743
744        // Bail out if we are before the onset of daylight savings time
745        if (!useDaylight || year < startYear || era != GregorianCalendar.AD) return result;
746
747        // Check for southern hemisphere.  We assume that the start and end
748        // month are different.
749        boolean southern = (startMonth > endMonth);
750
751        // Compare the date to the starting and ending rules.+1 = date>rule, -1
752        // = date<rule, 0 = date==rule.
753        int startCompare = compareToRule(month, monthLength, prevMonthLength,
754                                         day, dayOfWeek, millis,
755                                         startTimeMode == UTC_TIME ? -raw : 0,
756                                         startMode, startMonth, startDayOfWeek,
757                                         startDay, startTime);
758        int endCompare = 0;
759
760        /* We don't always have to compute endCompare.  For many instances,
761         * startCompare is enough to determine if we are in DST or not.  In the
762         * northern hemisphere, if we are before the start rule, we can't have
763         * DST.  In the southern hemisphere, if we are after the start rule, we
764         * must have DST.  This is reflected in the way the next if statement
765         * (not the one immediately following) short circuits. */
766        if (southern != (startCompare >= 0)) {
767            /* For the ending rule comparison, we add the dstSavings to the millis
768             * passed in to convert them from standard to wall time.  We then must
769             * normalize the millis to the range 0..millisPerDay-1. */
770            endCompare = compareToRule(month, monthLength, prevMonthLength,
771                                       day, dayOfWeek, millis,
772                                       endTimeMode == WALL_TIME ? dst :
773                                        (endTimeMode == UTC_TIME ? -raw : 0),
774                                       endMode, endMonth, endDayOfWeek,
775                                       endDay, endTime);
776        }
777
778        // Check for both the northern and southern hemisphere cases.  We
779        // assume that in the northern hemisphere, the start rule is before the
780        // end rule within the calendar year, and vice versa for the southern
781        // hemisphere.
782        if ((!southern && (startCompare >= 0 && endCompare < 0)) ||
783            (southern && (startCompare >= 0 || endCompare < 0)))
784            result += dst;
785
786        return result;
787    }
788
789    /**
790     * {@inheritDoc}
791     * @internal
792     * @deprecated This API is ICU internal only.
793     */
794    @Override
795    @Deprecated
796    public void getOffsetFromLocal(long date,
797            int nonExistingTimeOpt, int duplicatedTimeOpt, int[] offsets) {
798        offsets[0] = getRawOffset();
799        int fields[] = new int[6];
800        Grego.timeToFields(date, fields);
801        offsets[1] = getOffset(GregorianCalendar.AD,
802              fields[0], fields[1], fields[2],
803              fields[3], fields[5]) - offsets[0];
804
805        boolean recalc = false;
806
807        // Now, we need some adjustment
808        if (offsets[1] > 0) {
809            if ((nonExistingTimeOpt & STD_DST_MASK) == LOCAL_STD
810                || (nonExistingTimeOpt & STD_DST_MASK) != LOCAL_DST
811                && (nonExistingTimeOpt & FORMER_LATTER_MASK) != LOCAL_LATTER) {
812                date -= getDSTSavings();
813                recalc = true;
814            }
815        } else {
816            if ((duplicatedTimeOpt & STD_DST_MASK) == LOCAL_DST
817                || (duplicatedTimeOpt & STD_DST_MASK) != LOCAL_STD
818                && (duplicatedTimeOpt & FORMER_LATTER_MASK) == LOCAL_FORMER) {
819                date -= getDSTSavings();
820                recalc = true;
821            }
822        }
823
824        if (recalc) {
825            Grego.timeToFields(date, fields);
826            offsets[1] = getOffset(GregorianCalendar.AD,
827                    fields[0], fields[1], fields[2],
828                    fields[3], fields[5]) - offsets[0];
829        }
830    }
831
832    private static final int
833        DOM_MODE = 1,
834        DOW_IN_MONTH_MODE=2,
835        DOW_GE_DOM_MODE=3,
836        DOW_LE_DOM_MODE=4;
837
838    /**
839     * Compare a given date in the year to a rule. Return 1, 0, or -1, depending
840     * on whether the date is after, equal to, or before the rule date. The
841     * millis are compared directly against the ruleMillis, so any
842     * standard-daylight adjustments must be handled by the caller.
843     *
844     * @return  1 if the date is after the rule date, -1 if the date is before
845     *          the rule date, or 0 if the date is equal to the rule date.
846     */
847    private int compareToRule(int month, int monthLen, int prevMonthLen,
848                                  int dayOfMonth,
849                                  int dayOfWeek, int millis, int millisDelta,
850                                  int ruleMode, int ruleMonth, int ruleDayOfWeek,
851                                  int ruleDay, int ruleMillis)
852    {
853        // Make adjustments for startTimeMode and endTimeMode
854
855        millis += millisDelta;
856
857        while (millis >= Grego.MILLIS_PER_DAY) {
858            millis -= Grego.MILLIS_PER_DAY;
859            ++dayOfMonth;
860            dayOfWeek = 1 + (dayOfWeek % 7); // dayOfWeek is one-based
861            if (dayOfMonth > monthLen) {
862                dayOfMonth = 1;
863                /* When incrementing the month, it is desirable to overflow
864                 * from DECEMBER to DECEMBER+1, since we use the result to
865                 * compare against a real month. Wraparound of the value
866                 * leads to bug 4173604. */
867                ++month;
868            }
869        }
870        /*
871         * For some reasons, Sun Java 6 on Solaris/Linux has a problem with
872         * the while loop below (at least Java 6 up to build 1.6.0_02-b08).
873         * It looks the JRE messes up the variable 'millis' while executing
874         * the code in the while block.  The problem is not reproduced with
875         * JVM option -Xint, that is, it is likely a bug of the HotSpot
876         * adaptive compiler.  Moving 'millis += Grego.MILLIS_PER_DAY'
877         * to the end of this while block seems to resolve the problem.
878         * See ticket#5887 about the problem in detail.
879         */
880        while (millis < 0) {
881            //millis += Grego.MILLIS_PER_DAY;
882            --dayOfMonth;
883            dayOfWeek = 1 + ((dayOfWeek+5) % 7); // dayOfWeek is one-based
884            if (dayOfMonth < 1) {
885                dayOfMonth = prevMonthLen;
886                --month;
887            }
888            millis += Grego.MILLIS_PER_DAY;
889        }
890
891        if (month < ruleMonth) return -1;
892        else if (month > ruleMonth) return 1;
893
894        int ruleDayOfMonth = 0;
895
896        // Adjust the ruleDay to the monthLen, for non-leap year February 29 rule days.
897        if (ruleDay > monthLen) {
898            ruleDay = monthLen;
899        }
900
901        switch (ruleMode)
902        {
903        case DOM_MODE:
904            ruleDayOfMonth = ruleDay;
905            break;
906        case DOW_IN_MONTH_MODE:
907            // In this case ruleDay is the day-of-week-in-month
908            if (ruleDay > 0)
909                ruleDayOfMonth = 1 + (ruleDay - 1) * 7 +
910                    (7 + ruleDayOfWeek - (dayOfWeek - dayOfMonth + 1)) % 7;
911            else // Assume ruleDay < 0 here
912            {
913                ruleDayOfMonth = monthLen + (ruleDay + 1) * 7 -
914                    (7 + (dayOfWeek + monthLen - dayOfMonth) - ruleDayOfWeek) % 7;
915            }
916            break;
917        case DOW_GE_DOM_MODE:
918            ruleDayOfMonth = ruleDay +
919                (49 + ruleDayOfWeek - ruleDay - dayOfWeek + dayOfMonth) % 7;
920            break;
921        case DOW_LE_DOM_MODE:
922            ruleDayOfMonth = ruleDay -
923                (49 - ruleDayOfWeek + ruleDay + dayOfWeek - dayOfMonth) % 7;
924            // Note at this point ruleDayOfMonth may be <1, although it will
925            // be >=1 for well-formed rules.
926            break;
927        }
928
929        if (dayOfMonth < ruleDayOfMonth) return -1;
930        else if (dayOfMonth > ruleDayOfMonth) return 1;
931
932        if (millis < ruleMillis){
933                return -1;
934        }else if (millis > ruleMillis){
935                return 1;
936        }else{
937                return 0;
938        }
939    }
940
941    // data needed for streaming mutated SimpleTimeZones in JDK14
942    private int raw;// the TimeZone's raw GMT offset
943    private int dst = 3600000;
944    private STZInfo xinfo = null;
945    private int startMonth, startDay, startDayOfWeek;   // the month, day, DOW, and time DST starts
946    private int startTime;
947    private int startTimeMode, endTimeMode; // Mode for startTime, endTime; see TimeMode
948    private int endMonth, endDay, endDayOfWeek; // the month, day, DOW, and time DST ends
949    private int endTime;
950    private int startYear;  // the year these DST rules took effect
951    private boolean useDaylight; // flag indicating whether this TimeZone uses DST
952    private int startMode, endMode;   // flags indicating what kind of rules the DST rules are
953
954    /**
955     * Overrides TimeZone
956     * Queries if this time zone uses Daylight Saving Time.
957     * @stable ICU 2.0
958     */
959    @Override
960    public boolean useDaylightTime(){
961        return useDaylight;
962    }
963
964    /**
965     * {@inheritDoc}
966     * @stable ICU 49
967     */
968    @Override
969    public boolean observesDaylightTime() {
970        return useDaylight;
971    }
972
973    /**
974     * Overrides TimeZone
975     * Queries if the give date is in Daylight Saving Time.
976     * @stable ICU 2.0
977     */
978    @Override
979    public boolean inDaylightTime(Date date){
980        GregorianCalendar gc = new GregorianCalendar(this);
981        gc.setTime(date);
982        return gc.inDaylightTime();
983    }
984
985    /**
986     * Internal construction method.
987     */
988    private void construct(int _raw,
989                           int _startMonth,
990                           int _startDay,
991                           int _startDayOfWeek,
992                           int _startTime,
993                           int _startTimeMode,
994                           int _endMonth,
995                           int _endDay,
996                           int _endDayOfWeek,
997                           int _endTime,
998                           int _endTimeMode,
999                           int _dst) {
1000        raw            = _raw;
1001        startMonth     = _startMonth;
1002        startDay       = _startDay;
1003        startDayOfWeek = _startDayOfWeek;
1004        startTime      = _startTime;
1005        startTimeMode  = _startTimeMode;
1006        endMonth       = _endMonth;
1007        endDay         = _endDay;
1008        endDayOfWeek   = _endDayOfWeek;
1009        endTime        = _endTime;
1010        endTimeMode    = _endTimeMode;
1011        dst            = _dst;
1012        startYear      = 0;
1013        startMode      = DOM_MODE;
1014        endMode        = DOM_MODE;
1015
1016        decodeRules();
1017
1018        if (_dst <= 0) {
1019            throw new IllegalArgumentException();
1020        }
1021    }
1022    private void decodeRules(){
1023        decodeStartRule();
1024        decodeEndRule();
1025    }
1026
1027    /**
1028     * Decode the start rule and validate the parameters.  The parameters are
1029     * expected to be in encoded form, which represents the various rule modes
1030     * by negating or zeroing certain values.  Representation formats are:
1031     * <p>
1032     * <pre>
1033     *            DOW_IN_MONTH  DOM    DOW>=DOM  DOW<=DOM  no DST
1034     *            ------------  -----  --------  --------  ----------
1035     * month       0..11        same    same      same     don't care
1036     * day        -5..5         1..31   1..31    -1..-31   0
1037     * dayOfWeek   1..7         0      -1..-7    -1..-7    don't care
1038     * time        0..ONEDAY    same    same      same     don't care
1039     * </pre>
1040     * The range for month does not include UNDECIMBER since this class is
1041     * really specific to GregorianCalendar, which does not use that month.
1042     * The range for time includes ONEDAY (vs. ending at ONEDAY-1) because the
1043     * end rule is an exclusive limit point.  That is, the range of times that
1044     * are in DST include those >= the start and < the end.  For this reason,
1045     * it should be possible to specify an end of ONEDAY in order to include the
1046     * entire day.  Although this is equivalent to time 0 of the following day,
1047     * it's not always possible to specify that, for example, on December 31.
1048     * While arguably the start range should still be 0..ONEDAY-1, we keep
1049     * the start and end ranges the same for consistency.
1050     */
1051    private void decodeStartRule() {
1052        useDaylight = (startDay != 0) && (endDay != 0);
1053        if (useDaylight && dst == 0) {
1054            dst = Grego.MILLIS_PER_DAY;
1055        }
1056        if (startDay != 0) {
1057            if (startMonth < Calendar.JANUARY || startMonth > Calendar.DECEMBER) {
1058                throw new IllegalArgumentException();
1059            }
1060            if (startTime < 0 || startTime > Grego.MILLIS_PER_DAY ||
1061                startTimeMode < WALL_TIME || startTimeMode > UTC_TIME) {
1062                throw new IllegalArgumentException();
1063            }
1064            if (startDayOfWeek == 0) {
1065                startMode = DOM_MODE;
1066            } else {
1067                if (startDayOfWeek > 0) {
1068                    startMode = DOW_IN_MONTH_MODE;
1069                } else {
1070                    startDayOfWeek = -startDayOfWeek;
1071                    if (startDay > 0) {
1072                        startMode = DOW_GE_DOM_MODE;
1073                    } else {
1074                        startDay = -startDay;
1075                        startMode = DOW_LE_DOM_MODE;
1076                    }
1077                }
1078                if (startDayOfWeek > Calendar.SATURDAY) {
1079                    throw new IllegalArgumentException();
1080                }
1081            }
1082            if (startMode == DOW_IN_MONTH_MODE) {
1083                if (startDay < -5 || startDay > 5) {
1084                    throw new IllegalArgumentException();
1085                }
1086            } else if (startDay < 1 || startDay > staticMonthLength[startMonth]) {
1087                throw new IllegalArgumentException();
1088            }
1089        }
1090    }
1091
1092    /**
1093     * Decode the end rule and validate the parameters.  This method is exactly
1094     * analogous to decodeStartRule().
1095     * @see #decodeStartRule
1096     */
1097    private void decodeEndRule() {
1098        useDaylight = (startDay != 0) && (endDay != 0);
1099        if (useDaylight && dst == 0) {
1100            dst = Grego.MILLIS_PER_DAY;
1101        }
1102        if (endDay != 0) {
1103            if (endMonth < Calendar.JANUARY || endMonth > Calendar.DECEMBER) {
1104                throw new IllegalArgumentException();
1105            }
1106            if (endTime < 0 || endTime > Grego.MILLIS_PER_DAY ||
1107                endTimeMode < WALL_TIME || endTimeMode > UTC_TIME) {
1108                throw new IllegalArgumentException();
1109            }
1110            if (endDayOfWeek == 0) {
1111                endMode = DOM_MODE;
1112            } else {
1113                if (endDayOfWeek > 0) {
1114                    endMode = DOW_IN_MONTH_MODE;
1115                } else {
1116                    endDayOfWeek = -endDayOfWeek;
1117                    if (endDay > 0) {
1118                        endMode = DOW_GE_DOM_MODE;
1119                    } else {
1120                        endDay = -endDay;
1121                        endMode = DOW_LE_DOM_MODE;
1122                    }
1123                }
1124                if (endDayOfWeek > Calendar.SATURDAY) {
1125                    throw new IllegalArgumentException();
1126                }
1127            }
1128            if (endMode == DOW_IN_MONTH_MODE) {
1129                if (endDay < -5 || endDay > 5) {
1130                    throw new IllegalArgumentException();
1131                }
1132            } else if (endDay<1 || endDay > staticMonthLength[endMonth]) {
1133                throw new IllegalArgumentException();
1134            }
1135        }
1136    }
1137
1138    /**
1139     * Overrides equals.
1140     * @return true if obj is a SimpleTimeZone equivalent to this
1141     * @stable ICU 3.6
1142     */
1143    @Override
1144    public boolean equals(Object obj){
1145        if (this == obj) return true;
1146        if (obj == null || getClass() != obj.getClass()) return false;
1147        SimpleTimeZone that = (SimpleTimeZone) obj;
1148        return raw     == that.raw &&
1149            useDaylight     == that.useDaylight &&
1150            idEquals(getID(),that.getID()) &&
1151            (!useDaylight
1152             // Only check rules if using DST
1153             || (dst            == that.dst &&
1154                 startMode      == that.startMode &&
1155                 startMonth     == that.startMonth &&
1156                 startDay       == that.startDay &&
1157                 startDayOfWeek == that.startDayOfWeek &&
1158                 startTime      == that.startTime &&
1159                 startTimeMode  == that.startTimeMode &&
1160                 endMode        == that.endMode &&
1161                 endMonth       == that.endMonth &&
1162                 endDay         == that.endDay &&
1163                 endDayOfWeek   == that.endDayOfWeek &&
1164                 endTime        == that.endTime &&
1165                 endTimeMode    == that.endTimeMode &&
1166                 startYear      == that.startYear ));
1167
1168    }
1169    private boolean idEquals(String id1, String id2){
1170        if(id1==null && id2==null){
1171            return true;
1172        }
1173        if(id1!=null && id2!=null){
1174            return id1.equals(id2);
1175        }
1176        return false;
1177    }
1178
1179    /**
1180     * Overrides hashCode.
1181     * @stable ICU 3.6
1182     */
1183    @Override
1184    public int hashCode(){
1185        int ret = super.hashCode()
1186                    + raw ^ (raw >>> 8)
1187                    + (useDaylight ? 0 : 1);
1188        if(!useDaylight){
1189                ret += dst ^ (dst >>> 10) +
1190                        startMode ^ (startMode>>>11) +
1191                        startMonth ^ (startMonth>>>12) +
1192                        startDay ^ (startDay>>>13) +
1193                        startDayOfWeek ^ (startDayOfWeek>>>14) +
1194                        startTime ^ (startTime>>>15) +
1195                        startTimeMode ^ (startTimeMode>>>16) +
1196                        endMode ^ (endMode>>>17) +
1197                        endMonth ^ (endMonth>>>18) +
1198                        endDay ^ (endDay>>>19) +
1199                        endDayOfWeek ^ (endDayOfWeek>>>20) +
1200                        endTime ^ (endTime>>>21) +
1201                        endTimeMode ^ (endTimeMode>>>22) +
1202                        startYear ^ (startYear>>>23);
1203        }
1204                return ret;
1205    }
1206
1207    /**
1208     * Overrides clone.
1209     * @stable ICU 3.6
1210     */
1211    @Override
1212    public Object clone() {
1213        if (isFrozen()) {
1214            return this;
1215        }
1216        return cloneAsThawed();
1217    }
1218
1219    /**
1220     * Returns true if this zone has the same rules and offset as another zone.
1221     * @param othr the TimeZone object to be compared with
1222     * @return true if the given zone has the same rules and offset as this one
1223     * @stable ICU 2.0
1224     */
1225    @Override
1226    public boolean hasSameRules(TimeZone othr) {
1227        if (this == othr) {
1228            return true;
1229        }
1230        if(!(othr instanceof SimpleTimeZone)){
1231            return false;
1232        }
1233        SimpleTimeZone other = (SimpleTimeZone)othr;
1234        return other != null &&
1235        raw     == other.raw &&
1236        useDaylight     == other.useDaylight &&
1237        (!useDaylight
1238         // Only check rules if using DST
1239         || (dst     == other.dst &&
1240             startMode      == other.startMode &&
1241             startMonth     == other.startMonth &&
1242             startDay       == other.startDay &&
1243             startDayOfWeek == other.startDayOfWeek &&
1244             startTime      == other.startTime &&
1245             startTimeMode  == other.startTimeMode &&
1246             endMode        == other.endMode &&
1247             endMonth       == other.endMonth &&
1248             endDay         == other.endDay &&
1249             endDayOfWeek   == other.endDayOfWeek &&
1250             endTime        == other.endTime &&
1251             endTimeMode    == other.endTimeMode &&
1252             startYear      == other.startYear));
1253    }
1254
1255    // BasicTimeZone methods
1256
1257    /**
1258     * {@inheritDoc}
1259     * @stable ICU 3.8
1260     */
1261    @Override
1262    public TimeZoneTransition getNextTransition(long base, boolean inclusive) {
1263        if (!useDaylight) {
1264            return null;
1265        }
1266
1267        initTransitionRules();
1268        long firstTransitionTime = firstTransition.getTime();
1269        if (base < firstTransitionTime || (inclusive && base == firstTransitionTime)) {
1270            return firstTransition;
1271        }
1272        Date stdDate = stdRule.getNextStart(base, dstRule.getRawOffset(), dstRule.getDSTSavings(),
1273                                            inclusive);
1274        Date dstDate = dstRule.getNextStart(base, stdRule.getRawOffset(), stdRule.getDSTSavings(),
1275                                            inclusive);
1276        if (stdDate != null && (dstDate == null || stdDate.before(dstDate))) {
1277            return new TimeZoneTransition(stdDate.getTime(), dstRule, stdRule);
1278        }
1279        if (dstDate != null && (stdDate == null || dstDate.before(stdDate))) {
1280            return new TimeZoneTransition(dstDate.getTime(), stdRule, dstRule);
1281        }
1282        return null;
1283    }
1284
1285    /**
1286     * {@inheritDoc}
1287     * @stable ICU 3.8
1288     */
1289    @Override
1290    public TimeZoneTransition getPreviousTransition(long base, boolean inclusive) {
1291        if (!useDaylight) {
1292            return null;
1293        }
1294
1295        initTransitionRules();
1296        long firstTransitionTime = firstTransition.getTime();
1297        if (base < firstTransitionTime || (!inclusive && base == firstTransitionTime)) {
1298            return null;
1299        }
1300        Date stdDate = stdRule.getPreviousStart(base, dstRule.getRawOffset(),
1301                                                dstRule.getDSTSavings(), inclusive);
1302        Date dstDate = dstRule.getPreviousStart(base, stdRule.getRawOffset(),
1303                                                stdRule.getDSTSavings(), inclusive);
1304        if (stdDate != null && (dstDate == null || stdDate.after(dstDate))) {
1305            return new TimeZoneTransition(stdDate.getTime(), dstRule, stdRule);
1306        }
1307        if (dstDate != null && (stdDate == null || dstDate.after(stdDate))) {
1308            return new TimeZoneTransition(dstDate.getTime(), stdRule, dstRule);
1309        }
1310        return null;
1311    }
1312
1313    /**
1314     * {@inheritDoc}
1315     * @stable ICU 3.8
1316     */
1317    @Override
1318    public TimeZoneRule[] getTimeZoneRules() {
1319        initTransitionRules();
1320
1321        int size = useDaylight ? 3 : 1;
1322        TimeZoneRule[] rules = new TimeZoneRule[size];
1323        rules[0] = initialRule;
1324        if (useDaylight) {
1325            rules[1] = stdRule;
1326            rules[2] = dstRule;
1327        }
1328        return rules;
1329    }
1330
1331    private transient boolean transitionRulesInitialized;
1332    private transient InitialTimeZoneRule initialRule;
1333    private transient TimeZoneTransition firstTransition;
1334    private transient AnnualTimeZoneRule stdRule;
1335    private transient AnnualTimeZoneRule dstRule;
1336
1337    private synchronized void initTransitionRules() {
1338        if (transitionRulesInitialized) {
1339            return;
1340        }
1341        if (useDaylight) {
1342            DateTimeRule dtRule = null;
1343            int timeRuleType;
1344            long firstStdStart, firstDstStart;
1345
1346            // Create a TimeZoneRule for daylight saving time
1347            timeRuleType = (startTimeMode == STANDARD_TIME) ? DateTimeRule.STANDARD_TIME :
1348                ((startTimeMode == UTC_TIME) ? DateTimeRule.UTC_TIME : DateTimeRule.WALL_TIME);
1349            switch (startMode) {
1350            case DOM_MODE:
1351             dtRule = new DateTimeRule(startMonth, startDay, startTime, timeRuleType);
1352             break;
1353            case DOW_IN_MONTH_MODE:
1354             dtRule = new DateTimeRule(startMonth, startDay, startDayOfWeek, startTime,
1355                                       timeRuleType);
1356             break;
1357            case DOW_GE_DOM_MODE:
1358             dtRule = new DateTimeRule(startMonth, startDay, startDayOfWeek, true, startTime,
1359                                       timeRuleType);
1360             break;
1361            case DOW_LE_DOM_MODE:
1362             dtRule = new DateTimeRule(startMonth, startDay, startDayOfWeek, false, startTime,
1363                                       timeRuleType);
1364             break;
1365            }
1366            // For now, use ID + "(DST)" as the name
1367            dstRule = new AnnualTimeZoneRule(getID() + "(DST)", getRawOffset(), getDSTSavings(),
1368                 dtRule, startYear, AnnualTimeZoneRule.MAX_YEAR);
1369
1370            // Calculate the first DST start time
1371            firstDstStart = dstRule.getFirstStart(getRawOffset(), 0).getTime();
1372
1373            // Create a TimeZoneRule for standard time
1374            timeRuleType = (endTimeMode == STANDARD_TIME) ? DateTimeRule.STANDARD_TIME :
1375                ((endTimeMode == UTC_TIME) ? DateTimeRule.UTC_TIME : DateTimeRule.WALL_TIME);
1376            switch (endMode) {
1377            case DOM_MODE:
1378                dtRule = new DateTimeRule(endMonth, endDay, endTime, timeRuleType);
1379                break;
1380            case DOW_IN_MONTH_MODE:
1381                dtRule = new DateTimeRule(endMonth, endDay, endDayOfWeek, endTime, timeRuleType);
1382                break;
1383            case DOW_GE_DOM_MODE:
1384                dtRule = new DateTimeRule(endMonth, endDay, endDayOfWeek, true, endTime,
1385                                          timeRuleType);
1386                break;
1387            case DOW_LE_DOM_MODE:
1388                dtRule = new DateTimeRule(endMonth, endDay, endDayOfWeek, false, endTime,
1389                                          timeRuleType);
1390                break;
1391            }
1392            // For now, use ID + "(STD)" as the name
1393            stdRule = new AnnualTimeZoneRule(getID() + "(STD)", getRawOffset(), 0,
1394                    dtRule, startYear, AnnualTimeZoneRule.MAX_YEAR);
1395
1396            // Calculate the first STD start time
1397            firstStdStart = stdRule.getFirstStart(getRawOffset(), dstRule.getDSTSavings()).getTime();
1398
1399            // Create a TimeZoneRule for initial time
1400            if (firstStdStart < firstDstStart) {
1401                initialRule = new InitialTimeZoneRule(getID() + "(DST)", getRawOffset(),
1402                                                      dstRule.getDSTSavings());
1403                firstTransition = new TimeZoneTransition(firstStdStart, initialRule, stdRule);
1404            } else {
1405                initialRule = new InitialTimeZoneRule(getID() + "(STD)", getRawOffset(), 0);
1406                firstTransition = new TimeZoneTransition(firstDstStart, initialRule, dstRule);
1407            }
1408
1409        } else {
1410            // Create a TimeZoneRule for initial time
1411            initialRule = new InitialTimeZoneRule(getID(), getRawOffset(), 0);
1412        }
1413        transitionRulesInitialized = true;
1414    }
1415
1416    // Freezable stuffs
1417    private volatile transient boolean isFrozen = false;
1418
1419    /**
1420     * {@inheritDoc}
1421     * @stable ICU 49
1422     */
1423    @Override
1424    public boolean isFrozen() {
1425        return isFrozen;
1426    }
1427
1428    /**
1429     * {@inheritDoc}
1430     * @stable ICU 49
1431     */
1432    @Override
1433    public TimeZone freeze() {
1434        isFrozen = true;
1435        return this;
1436    }
1437
1438    /**
1439     * {@inheritDoc}
1440     * @stable ICU 49
1441     */
1442    @Override
1443    public TimeZone cloneAsThawed() {
1444        SimpleTimeZone tz = (SimpleTimeZone)super.cloneAsThawed();
1445        tz.isFrozen = false;
1446        return tz;
1447    }
1448}
1449