1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package java.util;
19
20import java.io.IOException;
21import java.io.ObjectInputStream;
22import java.io.ObjectOutputStream;
23import java.io.ObjectStreamField;
24
25/**
26 * {@code SimpleTimeZone} is a concrete subclass of {@code TimeZone}
27 * that represents a time zone for use with a Gregorian calendar. This class
28 * does not handle historical changes.
29 * <p>
30 * Use a negative value for {@code dayOfWeekInMonth} to indicate that
31 * {@code SimpleTimeZone} should count from the end of the month
32 * backwards. For example, Daylight Savings Time ends at the last
33 * (dayOfWeekInMonth = -1) Sunday in October, at 2 AM in standard time.
34 *
35 * @see Calendar
36 * @see GregorianCalendar
37 * @see TimeZone
38 */
39public class SimpleTimeZone extends TimeZone {
40
41    private static final long serialVersionUID = -403250971215465050L;
42
43    private int rawOffset;
44
45    private int startYear, startMonth, startDay, startDayOfWeek, startTime;
46
47    private int endMonth, endDay, endDayOfWeek, endTime;
48
49    private int startMode, endMode;
50
51    private static final int DOM_MODE = 1, DOW_IN_MONTH_MODE = 2,
52            DOW_GE_DOM_MODE = 3, DOW_LE_DOM_MODE = 4;
53
54    /**
55     * The constant for representing a start or end time in GMT time mode.
56     */
57    public static final int UTC_TIME = 2;
58
59    /**
60     * The constant for representing a start or end time in standard local time mode,
61     * based on timezone's raw offset from GMT; does not include Daylight
62     * savings.
63     */
64    public static final int STANDARD_TIME = 1;
65
66    /**
67     * The constant for representing a start or end time in local wall clock time
68     * mode, based on timezone's adjusted offset from GMT; includes
69     * Daylight savings.
70     */
71    public static final int WALL_TIME = 0;
72
73    private boolean useDaylight;
74
75    private int dstSavings = 3600000;
76
77    /**
78     * Constructs a {@code SimpleTimeZone} with the given base time zone offset from GMT
79     * and time zone ID. Timezone IDs can be obtained from
80     * {@code TimeZone.getAvailableIDs}. Normally you should use {@code TimeZone.getDefault} to
81     * construct a {@code TimeZone}.
82     *
83     * @param offset
84     *            the given base time zone offset to GMT.
85     * @param name
86     *            the time zone ID which is obtained from
87     *            {@code TimeZone.getAvailableIDs}.
88     */
89    public SimpleTimeZone(int offset, final String name) {
90        setID(name);
91        rawOffset = offset;
92    }
93
94    /**
95     * Constructs a {@code SimpleTimeZone} with the given base time zone offset from GMT,
96     * time zone ID, and times to start and end the daylight savings time. Timezone IDs can
97     * be obtained from {@code TimeZone.getAvailableIDs}. Normally you should use
98     * {@code TimeZone.getDefault} to create a {@code TimeZone}. For a time zone that does not
99     * use daylight saving time, do not use this constructor; instead you should
100     * use {@code SimpleTimeZone(rawOffset, ID)}.
101     * <p>
102     * By default, this constructor specifies day-of-week-in-month rules. That
103     * is, if the {@code startDay} is 1, and the {@code startDayOfWeek} is {@code SUNDAY}, then this
104     * indicates the first Sunday in the {@code startMonth}. A {@code startDay} of -1 likewise
105     * indicates the last Sunday. However, by using negative or zero values for
106     * certain parameters, other types of rules can be specified.
107     * <p>
108     * Day of month: To specify an exact day of the month, such as March 1, set
109     * {@code startDayOfWeek} to zero.
110     * <p>
111     * Day of week after day of month: To specify the first day of the week
112     * occurring on or after an exact day of the month, make the day of the week
113     * negative. For example, if {@code startDay} is 5 and {@code startDayOfWeek} is {@code -MONDAY},
114     * this indicates the first Monday on or after the 5th day of the
115     * {@code startMonth}.
116     * <p>
117     * Day of week before day of month: To specify the last day of the week
118     * occurring on or before an exact day of the month, make the day of the
119     * week and the day of the month negative. For example, if {@code startDay} is {@code -21}
120     * and {@code startDayOfWeek} is {@code -WEDNESDAY}, this indicates the last Wednesday on or
121     * before the 21st of the {@code startMonth}.
122     * <p>
123     * The above examples refer to the {@code startMonth}, {@code startDay}, and {@code startDayOfWeek};
124     * the same applies for the {@code endMonth}, {@code endDay}, and {@code endDayOfWeek}.
125     * <p>
126     * The daylight savings time difference is set to the default value: one hour.
127     *
128     * @param offset
129     *            the given base time zone offset to GMT.
130     * @param name
131     *            the time zone ID which is obtained from
132     *            {@code TimeZone.getAvailableIDs}.
133     * @param startMonth
134     *            the daylight savings starting month. The month indexing is 0-based. eg, 0
135     *            for January.
136     * @param startDay
137     *            the daylight savings starting day-of-week-in-month. Please see
138     *            the member description for an example.
139     * @param startDayOfWeek
140     *            the daylight savings starting day-of-week. Please see the
141     *            member description for an example.
142     * @param startTime
143     *            the daylight savings starting time in local wall time, which
144     *            is standard time in this case. Please see the member
145     *            description for an example.
146     * @param endMonth
147     *            the daylight savings ending month. The month indexing is 0-based. eg, 0 for
148     *            January.
149     * @param endDay
150     *            the daylight savings ending day-of-week-in-month. Please see
151     *            the member description for an example.
152     * @param endDayOfWeek
153     *            the daylight savings ending day-of-week. Please see the member
154     *            description for an example.
155     * @param endTime
156     *            the daylight savings ending time in local wall time, which is
157     *            daylight time in this case. Please see the member description
158     *            for an example.
159     * @throws IllegalArgumentException
160     *             if the month, day, dayOfWeek, or time parameters are out of
161     *             range for the start or end rule.
162     */
163    public SimpleTimeZone(int offset, String name, int startMonth,
164            int startDay, int startDayOfWeek, int startTime, int endMonth,
165            int endDay, int endDayOfWeek, int endTime) {
166        this(offset, name, startMonth, startDay, startDayOfWeek, startTime,
167                endMonth, endDay, endDayOfWeek, endTime, 3600000);
168    }
169
170    /**
171     * Constructs a {@code SimpleTimeZone} with the given base time zone offset from GMT,
172     * time zone ID, times to start and end the daylight savings time, and
173     * the daylight savings time difference in milliseconds.
174     *
175     * @param offset
176     *            the given base time zone offset to GMT.
177     * @param name
178     *            the time zone ID which is obtained from
179     *            {@code TimeZone.getAvailableIDs}.
180     * @param startMonth
181     *            the daylight savings starting month. Month is 0-based. eg, 0
182     *            for January.
183     * @param startDay
184     *            the daylight savings starting day-of-week-in-month. Please see
185     *            the description of {@link #SimpleTimeZone(int, String, int, int, int, int, int, int, int, int)} for an example.
186     * @param startDayOfWeek
187     *            the daylight savings starting day-of-week. Please see the
188     *            description of {@link #SimpleTimeZone(int, String, int, int, int, int, int, int, int, int)} for an example.
189     * @param startTime
190     *            The daylight savings starting time in local wall time, which
191     *            is standard time in this case. Please see the description of
192     *            {@link #SimpleTimeZone(int, String, int, int, int, int, int, int, int, int)} for an example.
193     * @param endMonth
194     *            the daylight savings ending month. Month is 0-based. eg, 0 for
195     *            January.
196     * @param endDay
197     *            the daylight savings ending day-of-week-in-month. Please see
198     *            the description of {@link #SimpleTimeZone(int, String, int, int, int, int, int, int, int, int)} for an example.
199     * @param endDayOfWeek
200     *            the daylight savings ending day-of-week. Please see the description of
201     *            {@link #SimpleTimeZone(int, String, int, int, int, int, int, int, int, int)} for an example.
202     * @param endTime
203     *            the daylight savings ending time in local wall time, which is
204     *            daylight time in this case. Please see the description of {@link #SimpleTimeZone(int, String, int, int, int, int, int, int, int, int)}
205     *            for an example.
206     * @param daylightSavings
207     *            the daylight savings time difference in milliseconds.
208     * @throws IllegalArgumentException
209     *                if the month, day, dayOfWeek, or time parameters are out of
210     *                range for the start or end rule.
211     */
212    public SimpleTimeZone(int offset, String name, int startMonth,
213            int startDay, int startDayOfWeek, int startTime, int endMonth,
214            int endDay, int endDayOfWeek, int endTime, int daylightSavings) {
215        this(offset, name);
216        if (daylightSavings <= 0) {
217            throw new IllegalArgumentException("Invalid daylightSavings: " + daylightSavings);
218        }
219        dstSavings = daylightSavings;
220
221        this.startMonth = startMonth;
222        this.startDay = startDay;
223        this.startDayOfWeek = startDayOfWeek;
224        this.startTime = startTime;
225        setStartMode();
226        this.endMonth = endMonth;
227        this.endDay = endDay;
228        this.endDayOfWeek = endDayOfWeek;
229        this.endTime = endTime;
230        setEndMode();
231    }
232
233    /**
234     * Construct a {@code SimpleTimeZone} with the given base time zone offset from GMT,
235     * time zone ID, times to start and end the daylight savings time including a
236     * mode specifier, the daylight savings time difference in milliseconds.
237     * The mode specifies either {@link #WALL_TIME}, {@link #STANDARD_TIME}, or
238     * {@link #UTC_TIME}.
239     *
240     * @param offset
241     *            the given base time zone offset to GMT.
242     * @param name
243     *            the time zone ID which is obtained from
244     *            {@code TimeZone.getAvailableIDs}.
245     * @param startMonth
246     *            the daylight savings starting month. The month indexing is 0-based. eg, 0
247     *            for January.
248     * @param startDay
249     *            the daylight savings starting day-of-week-in-month. Please see
250     *            the description of {@link #SimpleTimeZone(int, String, int, int, int, int, int, int, int, int)} for an example.
251     * @param startDayOfWeek
252     *            the daylight savings starting day-of-week. Please see the
253     *            description of {@link #SimpleTimeZone(int, String, int, int, int, int, int, int, int, int)} for an example.
254     * @param startTime
255     *            the time of day in milliseconds on which daylight savings
256     *            time starts, based on the {@code startTimeMode}.
257     * @param startTimeMode
258     *            the mode (UTC, standard, or wall time) of the start time
259     *            value.
260     * @param endDay
261     *            the day of the week on which daylight savings time ends.
262     * @param endMonth
263     *            the daylight savings ending month. The month indexing is 0-based. eg, 0 for
264     *            January.
265     * @param endDayOfWeek
266     *            the daylight savings ending day-of-week. Please see the description of
267     *            {@link #SimpleTimeZone(int, String, int, int, int, int, int, int, int, int)} for an example.
268     * @param endTime
269     *            the time of day in milliseconds on which daylight savings
270     *            time ends, based on the {@code endTimeMode}.
271     * @param endTimeMode
272     *            the mode (UTC, standard, or wall time) of the end time value.
273     * @param daylightSavings
274     *            the daylight savings time difference in milliseconds.
275     * @throws IllegalArgumentException
276     *             if the month, day, dayOfWeek, or time parameters are out of
277     *             range for the start or end rule.
278     */
279    public SimpleTimeZone(int offset, String name, int startMonth,
280            int startDay, int startDayOfWeek, int startTime, int startTimeMode,
281            int endMonth, int endDay, int endDayOfWeek, int endTime,
282            int endTimeMode, int daylightSavings) {
283
284        this(offset, name, startMonth, startDay, startDayOfWeek, startTime,
285                endMonth, endDay, endDayOfWeek, endTime, daylightSavings);
286        startMode = startTimeMode;
287        endMode = endTimeMode;
288    }
289
290    /**
291     * Returns a new {@code SimpleTimeZone} with the same ID, {@code rawOffset} and daylight
292     * savings time rules as this SimpleTimeZone.
293     *
294     * @return a shallow copy of this {@code SimpleTimeZone}.
295     * @see java.lang.Cloneable
296     */
297    @Override
298    public Object clone() {
299        SimpleTimeZone zone = (SimpleTimeZone) super.clone();
300        return zone;
301    }
302
303    /**
304     * Compares the specified object to this {@code SimpleTimeZone} and returns whether they
305     * are equal. The object must be an instance of {@code SimpleTimeZone} and have the
306     * same internal data.
307     *
308     * @param object
309     *            the object to compare with this object.
310     * @return {@code true} if the specified object is equal to this
311     *         {@code SimpleTimeZone}, {@code false} otherwise.
312     * @see #hashCode
313     */
314    @Override
315    public boolean equals(Object object) {
316        if (!(object instanceof SimpleTimeZone)) {
317            return false;
318        }
319        SimpleTimeZone tz = (SimpleTimeZone) object;
320        return getID().equals(tz.getID())
321                && rawOffset == tz.rawOffset
322                && useDaylight == tz.useDaylight
323                && (!useDaylight || (startYear == tz.startYear
324                        && startMonth == tz.startMonth
325                        && startDay == tz.startDay && startMode == tz.startMode
326                        && startDayOfWeek == tz.startDayOfWeek
327                        && startTime == tz.startTime && endMonth == tz.endMonth
328                        && endDay == tz.endDay
329                        && endDayOfWeek == tz.endDayOfWeek
330                        && endTime == tz.endTime && endMode == tz.endMode && dstSavings == tz.dstSavings));
331    }
332
333    @Override
334    public int getDSTSavings() {
335        if (!useDaylight) {
336            return 0;
337        }
338        return dstSavings;
339    }
340
341    @Override
342    public int getOffset(int era, int year, int month, int day, int dayOfWeek, int time) {
343        if (era != GregorianCalendar.BC && era != GregorianCalendar.AD) {
344            throw new IllegalArgumentException("Invalid era: " + era);
345        }
346        checkRange(month, dayOfWeek, time);
347        if (month != Calendar.FEBRUARY || day != 29 || !isLeapYear(year)) {
348            checkDay(month, day);
349        }
350
351        if (!useDaylightTime() || era != GregorianCalendar.AD || year < startYear) {
352            return rawOffset;
353        }
354        if (endMonth < startMonth) {
355            if (month > endMonth && month < startMonth) {
356                return rawOffset;
357            }
358        } else {
359            if (month < startMonth || month > endMonth) {
360                return rawOffset;
361            }
362        }
363
364        int ruleDay = 0, daysInMonth, firstDayOfMonth = mod7(dayOfWeek - day);
365        if (month == startMonth) {
366            switch (startMode) {
367                case DOM_MODE:
368                    ruleDay = startDay;
369                    break;
370                case DOW_IN_MONTH_MODE:
371                    if (startDay >= 0) {
372                        ruleDay = mod7(startDayOfWeek - firstDayOfMonth) + 1
373                                + (startDay - 1) * 7;
374                    } else {
375                        daysInMonth = GregorianCalendar.DaysInMonth[startMonth];
376                        if (startMonth == Calendar.FEBRUARY && isLeapYear(
377                                year)) {
378                            daysInMonth += 1;
379                        }
380                        ruleDay = daysInMonth
381                                + 1
382                                + mod7(startDayOfWeek
383                                - (firstDayOfMonth + daysInMonth))
384                                + startDay * 7;
385                    }
386                    break;
387                case DOW_GE_DOM_MODE:
388                    ruleDay = startDay
389                            + mod7(startDayOfWeek
390                            - (firstDayOfMonth + startDay - 1));
391                    break;
392                case DOW_LE_DOM_MODE:
393                    ruleDay = startDay
394                            + mod7(startDayOfWeek
395                            - (firstDayOfMonth + startDay - 1));
396                    if (ruleDay != startDay) {
397                        ruleDay -= 7;
398                    }
399                    break;
400            }
401            if (ruleDay > day || ruleDay == day && time < startTime) {
402                return rawOffset;
403            }
404        }
405
406        int ruleTime = endTime - dstSavings;
407        int nextMonth = (month + 1) % 12;
408        if (month == endMonth || (ruleTime < 0 && nextMonth == endMonth)) {
409            switch (endMode) {
410                case DOM_MODE:
411                    ruleDay = endDay;
412                    break;
413                case DOW_IN_MONTH_MODE:
414                    if (endDay >= 0) {
415                        ruleDay = mod7(endDayOfWeek - firstDayOfMonth) + 1
416                                + (endDay - 1) * 7;
417                    } else {
418                        daysInMonth = GregorianCalendar.DaysInMonth[endMonth];
419                        if (endMonth == Calendar.FEBRUARY && isLeapYear(year)) {
420                            daysInMonth++;
421                        }
422                        ruleDay = daysInMonth
423                                + 1
424                                + mod7(endDayOfWeek
425                                - (firstDayOfMonth + daysInMonth)) + endDay
426                                * 7;
427                    }
428                    break;
429                case DOW_GE_DOM_MODE:
430                    ruleDay = endDay
431                            + mod7(
432                            endDayOfWeek - (firstDayOfMonth + endDay - 1));
433                    break;
434                case DOW_LE_DOM_MODE:
435                    ruleDay = endDay
436                            + mod7(
437                            endDayOfWeek - (firstDayOfMonth + endDay - 1));
438                    if (ruleDay != endDay) {
439                        ruleDay -= 7;
440                    }
441                    break;
442            }
443
444            int ruleMonth = endMonth;
445            if (ruleTime < 0) {
446                int changeDays = 1 - (ruleTime / 86400000);
447                ruleTime = (ruleTime % 86400000) + 86400000;
448                ruleDay -= changeDays;
449                if (ruleDay <= 0) {
450                    if (--ruleMonth < Calendar.JANUARY) {
451                        ruleMonth = Calendar.DECEMBER;
452                    }
453                    ruleDay += GregorianCalendar.DaysInMonth[ruleMonth];
454                    if (ruleMonth == Calendar.FEBRUARY && isLeapYear(year)) {
455                        ruleDay++;
456                    }
457                }
458            }
459
460            if (month == ruleMonth) {
461                if (ruleDay < day || ruleDay == day && time >= ruleTime) {
462                    return rawOffset;
463                }
464            } else if (nextMonth != ruleMonth) {
465                return rawOffset;
466            }
467        }
468        return rawOffset + dstSavings;
469    }
470
471    @Override
472    public int getOffset(long time) {
473        // Simplified variant of the ICU4J code.
474        if (!useDaylightTime()) {
475            return rawOffset;
476        }
477        int[] fields = Grego.timeToFields(time + rawOffset, null);
478        return getOffset(GregorianCalendar.AD, fields[0], fields[1], fields[2],
479                fields[3], fields[5]);
480    }
481
482    @Override
483    public int getRawOffset() {
484        return rawOffset;
485    }
486
487    /**
488     * Returns an integer hash code for the receiver. Objects which are equal
489     * return the same value for this method.
490     *
491     * @return the receiver's hash.
492     * @see #equals
493     */
494    @Override
495    public synchronized int hashCode() {
496        int hashCode = getID().hashCode() + rawOffset;
497        if (useDaylight) {
498            hashCode += startYear + startMonth + startDay + startDayOfWeek
499                    + startTime + startMode + endMonth + endDay + endDayOfWeek
500                    + endTime + endMode + dstSavings;
501        }
502        return hashCode;
503    }
504
505    @Override
506    public boolean hasSameRules(TimeZone zone) {
507        if (!(zone instanceof SimpleTimeZone)) {
508            return false;
509        }
510        SimpleTimeZone tz = (SimpleTimeZone) zone;
511        if (useDaylight != tz.useDaylight) {
512            return false;
513        }
514        if (!useDaylight) {
515            return rawOffset == tz.rawOffset;
516        }
517        return rawOffset == tz.rawOffset && dstSavings == tz.dstSavings
518                && startYear == tz.startYear && startMonth == tz.startMonth
519                && startDay == tz.startDay && startMode == tz.startMode
520                && startDayOfWeek == tz.startDayOfWeek
521                && startTime == tz.startTime && endMonth == tz.endMonth
522                && endDay == tz.endDay && endDayOfWeek == tz.endDayOfWeek
523                && endTime == tz.endTime && endMode == tz.endMode;
524    }
525
526    @Override public boolean inDaylightTime(Date time) {
527        return useDaylightTime() && getOffset(time.getTime()) != getRawOffset();
528    }
529
530    private boolean isLeapYear(int year) {
531        if (year > 1582) {
532            return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
533        }
534        return year % 4 == 0;
535    }
536
537    private int mod7(int num1) {
538        int rem = num1 % 7;
539        return (num1 < 0 && rem < 0) ? 7 + rem : rem;
540    }
541
542    /**
543     * Sets the daylight savings offset in milliseconds for this {@code SimpleTimeZone}.
544     *
545     * @param milliseconds
546     *            the daylight savings offset in milliseconds.
547     */
548    public void setDSTSavings(int milliseconds) {
549        if (milliseconds > 0) {
550            dstSavings = milliseconds;
551        } else {
552            throw new IllegalArgumentException();
553        }
554    }
555
556    private void checkRange(int month, int dayOfWeek, int time) {
557        if (month < Calendar.JANUARY || month > Calendar.DECEMBER) {
558            throw new IllegalArgumentException("Invalid month: " + month);
559        }
560        if (dayOfWeek < Calendar.SUNDAY || dayOfWeek > Calendar.SATURDAY) {
561            throw new IllegalArgumentException("Invalid day of week: " + dayOfWeek);
562        }
563        if (time < 0 || time >= 24 * 3600000) {
564            throw new IllegalArgumentException("Invalid time: " + time);
565        }
566    }
567
568    private void checkDay(int month, int day) {
569        if (day <= 0 || day > GregorianCalendar.DaysInMonth[month]) {
570            throw new IllegalArgumentException("Invalid day of month: " + day);
571        }
572    }
573
574    private void setEndMode() {
575        if (endDayOfWeek == 0) {
576            endMode = DOM_MODE;
577        } else if (endDayOfWeek < 0) {
578            endDayOfWeek = -endDayOfWeek;
579            if (endDay < 0) {
580                endDay = -endDay;
581                endMode = DOW_LE_DOM_MODE;
582            } else {
583                endMode = DOW_GE_DOM_MODE;
584            }
585        } else {
586            endMode = DOW_IN_MONTH_MODE;
587        }
588        useDaylight = startDay != 0 && endDay != 0;
589        if (endDay != 0) {
590            checkRange(endMonth, endMode == DOM_MODE ? 1 : endDayOfWeek,
591                    endTime);
592            if (endMode != DOW_IN_MONTH_MODE) {
593                checkDay(endMonth, endDay);
594            } else {
595                if (endDay < -5 || endDay > 5) {
596                    throw new IllegalArgumentException("Day of week in month: " + endDay);
597                }
598            }
599        }
600        if (endMode != DOM_MODE) {
601            endDayOfWeek--;
602        }
603    }
604
605    /**
606     * Sets the rule which specifies the end of daylight savings time.
607     *
608     * @param month
609     *            the {@code Calendar} month in which daylight savings time ends.
610     * @param dayOfMonth
611     *            the {@code Calendar} day of the month on which daylight savings time
612     *            ends.
613     * @param time
614     *            the time of day in milliseconds standard time on which
615     *            daylight savings time ends.
616     */
617    public void setEndRule(int month, int dayOfMonth, int time) {
618        endMonth = month;
619        endDay = dayOfMonth;
620        endDayOfWeek = 0; // Initialize this value for hasSameRules()
621        endTime = time;
622        setEndMode();
623    }
624
625    /**
626     * Sets the rule which specifies the end of daylight savings time.
627     *
628     * @param month
629     *            the {@code Calendar} month in which daylight savings time ends.
630     * @param day
631     *            the occurrence of the day of the week on which daylight
632     *            savings time ends.
633     * @param dayOfWeek
634     *            the {@code Calendar} day of the week on which daylight savings time
635     *            ends.
636     * @param time
637     *            the time of day in milliseconds standard time on which
638     *            daylight savings time ends.
639     */
640    public void setEndRule(int month, int day, int dayOfWeek, int time) {
641        endMonth = month;
642        endDay = day;
643        endDayOfWeek = dayOfWeek;
644        endTime = time;
645        setEndMode();
646    }
647
648    /**
649     * Sets the rule which specifies the end of daylight savings time.
650     *
651     * @param month
652     *            the {@code Calendar} month in which daylight savings time ends.
653     * @param day
654     *            the {@code Calendar} day of the month.
655     * @param dayOfWeek
656     *            the {@code Calendar} day of the week on which daylight savings time
657     *            ends.
658     * @param time
659     *            the time of day in milliseconds on which daylight savings time
660     *            ends.
661     * @param after
662     *            selects the day after or before the day of month.
663     */
664    public void setEndRule(int month, int day, int dayOfWeek, int time, boolean after) {
665        endMonth = month;
666        endDay = after ? day : -day;
667        endDayOfWeek = -dayOfWeek;
668        endTime = time;
669        setEndMode();
670    }
671
672    /**
673     * Sets the offset for standard time from GMT for this {@code SimpleTimeZone}.
674     *
675     * @param offset
676     *            the offset from GMT of standard time in milliseconds.
677     */
678    @Override
679    public void setRawOffset(int offset) {
680        rawOffset = offset;
681    }
682
683    private void setStartMode() {
684        if (startDayOfWeek == 0) {
685            startMode = DOM_MODE;
686        } else if (startDayOfWeek < 0) {
687            startDayOfWeek = -startDayOfWeek;
688            if (startDay < 0) {
689                startDay = -startDay;
690                startMode = DOW_LE_DOM_MODE;
691            } else {
692                startMode = DOW_GE_DOM_MODE;
693            }
694        } else {
695            startMode = DOW_IN_MONTH_MODE;
696        }
697        useDaylight = startDay != 0 && endDay != 0;
698        if (startDay != 0) {
699            checkRange(startMonth, startMode == DOM_MODE ? 1 : startDayOfWeek,
700                    startTime);
701            if (startMode != DOW_IN_MONTH_MODE) {
702                checkDay(startMonth, startDay);
703            } else {
704                if (startDay < -5 || startDay > 5) {
705                    throw new IllegalArgumentException("Day of week in month: " + startDay);
706                }
707            }
708        }
709        if (startMode != DOM_MODE) {
710            startDayOfWeek--;
711        }
712    }
713
714    /**
715     * Sets the rule which specifies the start of daylight savings time.
716     *
717     * @param month
718     *            the {@code Calendar} month in which daylight savings time starts.
719     * @param dayOfMonth
720     *            the {@code Calendar} day of the month on which daylight savings time
721     *            starts.
722     * @param time
723     *            the time of day in milliseconds on which daylight savings time
724     *            starts.
725     */
726    public void setStartRule(int month, int dayOfMonth, int time) {
727        startMonth = month;
728        startDay = dayOfMonth;
729        startDayOfWeek = 0; // Initialize this value for hasSameRules()
730        startTime = time;
731        setStartMode();
732    }
733
734    /**
735     * Sets the rule which specifies the start of daylight savings time.
736     *
737     * @param month
738     *            the {@code Calendar} month in which daylight savings time starts.
739     * @param day
740     *            the occurrence of the day of the week on which daylight
741     *            savings time starts.
742     * @param dayOfWeek
743     *            the {@code Calendar} day of the week on which daylight savings time
744     *            starts.
745     * @param time
746     *            the time of day in milliseconds on which daylight savings time
747     *            starts.
748     */
749    public void setStartRule(int month, int day, int dayOfWeek, int time) {
750        startMonth = month;
751        startDay = day;
752        startDayOfWeek = dayOfWeek;
753        startTime = time;
754        setStartMode();
755    }
756
757    /**
758     * Sets the rule which specifies the start of daylight savings time.
759     *
760     * @param month
761     *            the {@code Calendar} month in which daylight savings time starts.
762     * @param day
763     *            the {@code Calendar} day of the month.
764     * @param dayOfWeek
765     *            the {@code Calendar} day of the week on which daylight savings time
766     *            starts.
767     * @param time
768     *            the time of day in milliseconds on which daylight savings time
769     *            starts.
770     * @param after
771     *            selects the day after or before the day of month.
772     */
773    public void setStartRule(int month, int day, int dayOfWeek, int time, boolean after) {
774        startMonth = month;
775        startDay = after ? day : -day;
776        startDayOfWeek = -dayOfWeek;
777        startTime = time;
778        setStartMode();
779    }
780
781    /**
782     * Sets the starting year for daylight savings time in this {@code SimpleTimeZone}.
783     * Years before this start year will always be in standard time.
784     *
785     * @param year
786     *            the starting year.
787     */
788    public void setStartYear(int year) {
789        startYear = year;
790        useDaylight = true;
791    }
792
793    /**
794     * Returns the string representation of this {@code SimpleTimeZone}.
795     *
796     * @return the string representation of this {@code SimpleTimeZone}.
797     */
798    @Override
799    public String toString() {
800        return getClass().getName()
801                + "[id="
802                + getID()
803                + ",offset="
804                + rawOffset
805                + ",dstSavings="
806                + dstSavings
807                + ",useDaylight="
808                + useDaylight
809                + ",startYear="
810                + startYear
811                + ",startMode="
812                + startMode
813                + ",startMonth="
814                + startMonth
815                + ",startDay="
816                + startDay
817                + ",startDayOfWeek="
818                + (useDaylight && (startMode != DOM_MODE) ? startDayOfWeek + 1
819                        : 0) + ",startTime=" + startTime + ",endMode="
820                + endMode + ",endMonth=" + endMonth + ",endDay=" + endDay
821                + ",endDayOfWeek="
822                + (useDaylight && (endMode != DOM_MODE) ? endDayOfWeek + 1 : 0)
823                + ",endTime=" + endTime + "]";
824    }
825
826    @Override
827    public boolean useDaylightTime() {
828        return useDaylight;
829    }
830
831    private static final ObjectStreamField[] serialPersistentFields = {
832        new ObjectStreamField("dstSavings", int.class),
833        new ObjectStreamField("endDay", int.class),
834        new ObjectStreamField("endDayOfWeek", int.class),
835        new ObjectStreamField("endMode", int.class),
836        new ObjectStreamField("endMonth", int.class),
837        new ObjectStreamField("endTime", int.class),
838        new ObjectStreamField("monthLength", byte[].class),
839        new ObjectStreamField("rawOffset", int.class),
840        new ObjectStreamField("serialVersionOnStream", int.class),
841        new ObjectStreamField("startDay", int.class),
842        new ObjectStreamField("startDayOfWeek", int.class),
843        new ObjectStreamField("startMode", int.class),
844        new ObjectStreamField("startMonth", int.class),
845        new ObjectStreamField("startTime", int.class),
846        new ObjectStreamField("startYear", int.class),
847        new ObjectStreamField("useDaylight", boolean.class),
848    };
849
850    private void writeObject(ObjectOutputStream stream) throws IOException {
851        int sEndDay = endDay, sEndDayOfWeek = endDayOfWeek + 1, sStartDay = startDay, sStartDayOfWeek = startDayOfWeek + 1;
852        if (useDaylight
853                && (startMode != DOW_IN_MONTH_MODE || endMode != DOW_IN_MONTH_MODE)) {
854            Calendar cal = new GregorianCalendar(this);
855            if (endMode != DOW_IN_MONTH_MODE) {
856                cal.set(Calendar.MONTH, endMonth);
857                cal.set(Calendar.DATE, endDay);
858                sEndDay = cal.get(Calendar.DAY_OF_WEEK_IN_MONTH);
859                if (endMode == DOM_MODE) {
860                    sEndDayOfWeek = cal.getFirstDayOfWeek();
861                }
862            }
863            if (startMode != DOW_IN_MONTH_MODE) {
864                cal.set(Calendar.MONTH, startMonth);
865                cal.set(Calendar.DATE, startDay);
866                sStartDay = cal.get(Calendar.DAY_OF_WEEK_IN_MONTH);
867                if (startMode == DOM_MODE) {
868                    sStartDayOfWeek = cal.getFirstDayOfWeek();
869                }
870            }
871        }
872        ObjectOutputStream.PutField fields = stream.putFields();
873        fields.put("dstSavings", dstSavings);
874        fields.put("endDay", sEndDay);
875        fields.put("endDayOfWeek", sEndDayOfWeek);
876        fields.put("endMode", endMode);
877        fields.put("endMonth", endMonth);
878        fields.put("endTime", endTime);
879        fields.put("monthLength", GregorianCalendar.DaysInMonth);
880        fields.put("rawOffset", rawOffset);
881        fields.put("serialVersionOnStream", 1);
882        fields.put("startDay", sStartDay);
883        fields.put("startDayOfWeek", sStartDayOfWeek);
884        fields.put("startMode", startMode);
885        fields.put("startMonth", startMonth);
886        fields.put("startTime", startTime);
887        fields.put("startYear", startYear);
888        fields.put("useDaylight", useDaylight);
889        stream.writeFields();
890        stream.writeInt(4);
891        byte[] values = new byte[4];
892        values[0] = (byte) startDay;
893        values[1] = (byte) (startMode == DOM_MODE ? 0 : startDayOfWeek + 1);
894        values[2] = (byte) endDay;
895        values[3] = (byte) (endMode == DOM_MODE ? 0 : endDayOfWeek + 1);
896        stream.write(values);
897    }
898
899    private void readObject(ObjectInputStream stream) throws IOException,
900            ClassNotFoundException {
901        ObjectInputStream.GetField fields = stream.readFields();
902        rawOffset = fields.get("rawOffset", 0);
903        useDaylight = fields.get("useDaylight", false);
904        if (useDaylight) {
905            endMonth = fields.get("endMonth", 0);
906            endTime = fields.get("endTime", 0);
907            startMonth = fields.get("startMonth", 0);
908            startTime = fields.get("startTime", 0);
909            startYear = fields.get("startYear", 0);
910        }
911        if (fields.get("serialVersionOnStream", 0) == 0) {
912            if (useDaylight) {
913                startMode = endMode = DOW_IN_MONTH_MODE;
914                endDay = fields.get("endDay", 0);
915                endDayOfWeek = fields.get("endDayOfWeek", 0) - 1;
916                startDay = fields.get("startDay", 0);
917                startDayOfWeek = fields.get("startDayOfWeek", 0) - 1;
918            }
919        } else {
920            dstSavings = fields.get("dstSavings", 0);
921            if (useDaylight) {
922                endMode = fields.get("endMode", 0);
923                startMode = fields.get("startMode", 0);
924                int length = stream.readInt();
925                byte[] values = new byte[length];
926                stream.readFully(values);
927                if (length >= 4) {
928                    startDay = values[0];
929                    startDayOfWeek = values[1];
930                    if (startMode != DOM_MODE) {
931                        startDayOfWeek--;
932                    }
933                    endDay = values[2];
934                    endDayOfWeek = values[3];
935                    if (endMode != DOM_MODE) {
936                        endDayOfWeek--;
937                    }
938                }
939            }
940        }
941    }
942
943}
944