| SimpleTimeZone.java |
1 /*
2 * %W% %E%
3 *
4 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
5 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6 */
7
8 /*
9 * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved
10 * (C) Copyright IBM Corp. 1996 - All Rights Reserved
11 *
12 * The original version of this source code and documentation is copyrighted
13 * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
14 * materials are provided under terms of a License Agreement between Taligent
15 * and Sun. This technology is protected by multiple US and International
16 * patents. This notice and attribution to Taligent may not be removed.
17 * Taligent is a registered trademark of Taligent, Inc.
18 *
19 */
20
21 package java.util;
22
23 import java.io.ObjectInputStream;
24 import java.io.ObjectOutputStream;
25 import java.io.IOException;
26 import sun.util.calendar.CalendarSystem;
27 import sun.util.calendar.CalendarUtils;
28 import sun.util.calendar.BaseCalendar;
29 import sun.util.calendar.Gregorian;
30
31 /**
32 * <code>SimpleTimeZone</code> is a concrete subclass of <code>TimeZone</code>
33 * that represents a time zone for use with a Gregorian calendar.
34 * The class holds an offset from GMT, called <em>raw offset</em>, and start
35 * and end rules for a daylight saving time schedule. Since it only holds
36 * single values for each, it cannot handle historical changes in the offset
37 * from GMT and the daylight saving schedule, except that the {@link
38 * #setStartYear setStartYear} method can specify the year when the daylight
39 * saving time schedule starts in effect.
40 * <p>
41 * To construct a <code>SimpleTimeZone</code> with a daylight saving time
42 * schedule, the schedule can be described with a set of rules,
43 * <em>start-rule</em> and <em>end-rule</em>. A day when daylight saving time
44 * starts or ends is specified by a combination of <em>month</em>,
45 * <em>day-of-month</em>, and <em>day-of-week</em> values. The <em>month</em>
46 * value is represented by a Calendar {@link Calendar#MONTH MONTH} field
47 * value, such as {@link Calendar#MARCH}. The <em>day-of-week</em> value is
48 * represented by a Calendar {@link Calendar#DAY_OF_WEEK DAY_OF_WEEK} value,
49 * such as {@link Calendar#SUNDAY SUNDAY}. The meanings of value combinations
50 * are as follows.
51 *
52 * <ul>
53 * <li><b>Exact day of month</b><br>
54 * To specify an exact day of month, set the <em>month</em> and
55 * <em>day-of-month</em> to an exact value, and <em>day-of-week</em> to zero. For
56 * example, to specify March 1, set the <em>month</em> to {@link Calendar#MARCH
57 * MARCH}, <em>day-of-month</em> to 1, and <em>day-of-week</em> to 0.</li>
58 *
59 * <li><b>Day of week on or after day of month</b><br>
60 * To specify a day of week on or after an exact day of month, set the
61 * <em>month</em> to an exact month value, <em>day-of-month</em> to the day on
62 * or after which the rule is applied, and <em>day-of-week</em> to a negative {@link
63 * Calendar#DAY_OF_WEEK DAY_OF_WEEK} field value. For example, to specify the
64 * second Sunday of April, set <em>month</em> to {@link Calendar#APRIL APRIL},
65 * <em>day-of-month</em> to 8, and <em>day-of-week</em> to <code>-</code>{@link
66 * Calendar#SUNDAY SUNDAY}.</li>
67 *
68 * <li><b>Day of week on or before day of month</b><br>
69 * To specify a day of the week on or before an exact day of the month, set
70 * <em>day-of-month</em> and <em>day-of-week</em> to a negative value. For
71 * example, to specify the last Wednesday on or before the 21st of March, set
72 * <em>month</em> to {@link Calendar#MARCH MARCH}, <em>day-of-month</em> is -21
73 * and <em>day-of-week</em> is <code>-</code>{@link Calendar#WEDNESDAY WEDNESDAY}. </li>
74 *
75 * <li><b>Last day-of-week of month</b><br>
76 * To specify, the last day-of-week of the month, set <em>day-of-week</em> to a
77 * {@link Calendar#DAY_OF_WEEK DAY_OF_WEEK} value and <em>day-of-month</em> to
78 * -1. For example, to specify the last Sunday of October, set <em>month</em>
79 * to {@link Calendar#OCTOBER OCTOBER}, <em>day-of-week</em> to {@link
80 * Calendar#SUNDAY SUNDAY} and <em>day-of-month</em> to -1. </li>
81 *
82 * </ul>
83 * The time of the day at which daylight saving time starts or ends is
84 * specified by a millisecond value within the day. There are three kinds of
85 * <em>mode</em>s to specify the time: {@link #WALL_TIME}, {@link
86 * #STANDARD_TIME} and {@link #UTC_TIME}. For example, if daylight
87 * saving time ends
88 * at 2:00 am in the wall clock time, it can be specified by 7200000
89 * milliseconds in the {@link #WALL_TIME} mode. In this case, the wall clock time
90 * for an <em>end-rule</em> means the same thing as the daylight time.
91 * <p>
92 * The following are examples of parameters for constructing time zone objects.
93 * <pre><code>
94 * // Base GMT offset: -8:00
95 * // DST starts: at 2:00am in standard time
96 * // on the first Sunday in April
97 * // DST ends: at 2:00am in daylight time
98 * // on the last Sunday in October
99 * // Save: 1 hour
100 * SimpleTimeZone(-28800000,
101 * "America/Los_Angeles",
102 * Calendar.APRIL, 1, -Calendar.SUNDAY,
103 * 7200000,
104 * Calendar.OCTOBER, -1, Calendar.SUNDAY,
105 * 7200000,
106 * 3600000)
107 *
108 * // Base GMT offset: +1:00
109 * // DST starts: at 1:00am in UTC time
110 * // on the last Sunday in March
111 * // DST ends: at 1:00am in UTC time
112 * // on the last Sunday in October
113 * // Save: 1 hour
114 * SimpleTimeZone(3600000,
115 * "Europe/Paris",
116 * Calendar.MARCH, -1, Calendar.SUNDAY,
117 * 3600000, SimpleTimeZone.UTC_TIME,
118 * Calendar.OCTOBER, -1, Calendar.SUNDAY,
119 * 3600000, SimpleTimeZone.UTC_TIME,
120 * 3600000)
121 * </code></pre>
122 * These parameter rules are also applicable to the set rule methods, such as
123 * <code>setStartRule</code>.
124 *
125 * @since 1.1
126 * @see Calendar
127 * @see GregorianCalendar
128 * @see TimeZone
129 * @version %I% %G%
130 * @author David Goldsmith, Mark Davis, Chen-Lieh Huang, Alan Liu
131 */
132
133 public class SimpleTimeZone extends TimeZone {
134 /**
135 * Constructs a SimpleTimeZone with the given base time zone offset from GMT
136 * and time zone ID with no daylight saving time schedule.
137 *
138 * @param rawOffset The base time zone offset in milliseconds to GMT.
139 * @param ID The time zone name that is given to this instance.
140 */
141 public SimpleTimeZone(int rawOffset, String ID)
142 {
143 this.rawOffset = rawOffset;
144 setID (ID);
145 dstSavings = millisPerHour; // In case user sets rules later
146 }
147
148 /**
149 * Constructs a SimpleTimeZone with the given base time zone offset from
150 * GMT, time zone ID, and rules for starting and ending the daylight
151 * time.
152 * Both <code>startTime</code> and <code>endTime</code> are specified to be
153 * represented in the wall clock time. The amount of daylight saving is
154 * assumed to be 3600000 milliseconds (i.e., one hour). This constructor is
155 * equivalent to:
156 * <pre><code>
157 * SimpleTimeZone(rawOffset,
158 * ID,
159 * startMonth,
160 * startDay,
161 * startDayOfWeek,
162 * startTime,
163 * SimpleTimeZone.{@link #WALL_TIME},
164 * endMonth,
165 * endDay,
166 * endDayOfWeek,
167 * endTime,
168 * SimpleTimeZone.{@link #WALL_TIME},
169 * 3600000)
170 * </code></pre>
171 *
172 * @param rawOffset The given base time zone offset from GMT.
173 * @param ID The time zone ID which is given to this object.
174 * @param startMonth The daylight saving time starting month. Month is
175 * a {@link Calendar#MONTH MONTH} field value (0-based. e.g., 0
176 * for January).
177 * @param startDay The day of the month on which the daylight saving time starts.
178 * See the class description for the special cases of this parameter.
179 * @param startDayOfWeek The daylight saving time starting day-of-week.
180 * See the class description for the special cases of this parameter.
181 * @param startTime The daylight saving time starting time in local wall clock
182 * time (in milliseconds within the day), which is local
183 * standard time in this case.
184 * @param endMonth The daylight saving time ending month. Month is
185 * a {@link Calendar#MONTH MONTH} field
186 * value (0-based. e.g., 9 for October).
187 * @param endDay The day of the month on which the daylight saving time ends.
188 * See the class description for the special cases of this parameter.
189 * @param endDayOfWeek The daylight saving time ending day-of-week.
190 * See the class description for the special cases of this parameter.
191 * @param endTime The daylight saving ending time in local wall clock time,
192 * (in milliseconds within the day) which is local daylight
193 * time in this case.
194 * @exception IllegalArgumentException if the month, day, dayOfWeek, or time
195 * parameters are out of range for the start or end rule
196 */
197 public SimpleTimeZone(int rawOffset, String ID,
198 int startMonth, int startDay, int startDayOfWeek, int startTime,
199 int endMonth, int endDay, int endDayOfWeek, int endTime)
200 {
201 this(rawOffset, ID,
202 startMonth, startDay, startDayOfWeek, startTime, WALL_TIME,
203 endMonth, endDay, endDayOfWeek, endTime, WALL_TIME,
204 millisPerHour);
205 }
206
207 /**
208 * Constructs a SimpleTimeZone with the given base time zone offset from
209 * GMT, time zone ID, and rules for starting and ending the daylight
210 * time.
211 * Both <code>startTime</code> and <code>endTime</code> are assumed to be
212 * represented in the wall clock time. This constructor is equivalent to:
213 * <pre><code>
214 * SimpleTimeZone(rawOffset,
215 * ID,
216 * startMonth,
217 * startDay,
218 * startDayOfWeek,
219 * startTime,
220 * SimpleTimeZone.{@link #WALL_TIME},
221 * endMonth,
222 * endDay,
223 * endDayOfWeek,
224 * endTime,
225 * SimpleTimeZone.{@link #WALL_TIME},
226 * dstSavings)
227 * </code></pre>
228 *
229 * @param rawOffset The given base time zone offset from GMT.
230 * @param ID The time zone ID which is given to this object.
231 * @param startMonth The daylight saving time starting month. Month is
232 * a {@link Calendar#MONTH MONTH} field
233 * value (0-based. e.g., 0 for January).
234 * @param startDay The day of the month on which the daylight saving time starts.
235 * See the class description for the special cases of this parameter.
236 * @param startDayOfWeek The daylight saving time starting day-of-week.
237 * See the class description for the special cases of this parameter.
238 * @param startTime The daylight saving time starting time in local wall clock
239 * time, which is local standard time in this case.
240 * @param endMonth The daylight saving time ending month. Month is
241 * a {@link Calendar#MONTH MONTH} field
242 * value (0-based. e.g., 9 for October).
243 * @param endDay The day of the month on which the daylight saving time ends.
244 * See the class description for the special cases of this parameter.
245 * @param endDayOfWeek The daylight saving time ending day-of-week.
246 * See the class description for the special cases of this parameter.
247 * @param endTime The daylight saving ending time in local wall clock time,
248 * which is local daylight time in this case.
249 * @param dstSavings The amount of time in milliseconds saved during
250 * daylight saving time.
251 * @exception IllegalArgumentException if the month, day, dayOfWeek, or time
252 * parameters are out of range for the start or end rule
253 * @since 1.2
254 */
255 public SimpleTimeZone(int rawOffset, String ID,
256 int startMonth, int startDay, int startDayOfWeek, int startTime,
257 int endMonth, int endDay, int endDayOfWeek, int endTime,
258 int dstSavings)
259 {
260 this(rawOffset, ID,
261 startMonth, startDay, startDayOfWeek, startTime, WALL_TIME,
262 endMonth, endDay, endDayOfWeek, endTime, WALL_TIME,
263 dstSavings);
264 }
265
266 /**
267 * Constructs a SimpleTimeZone with the given base time zone offset from
268 * GMT, time zone ID, and rules for starting and ending the daylight
269 * time.
270 * This constructor takes the full set of the start and end rules
271 * parameters, including modes of <code>startTime</code> and
272 * <code>endTime</code>. The mode specifies either {@link #WALL_TIME wall
273 * time} or {@link #STANDARD_TIME standard time} or {@link #UTC_TIME UTC
274 * time}.
275 *
276 * @param rawOffset The given base time zone offset from GMT.
277 * @param ID The time zone ID which is given to this object.
278 * @param startMonth The daylight saving time starting month. Month is
279 * a {@link Calendar#MONTH MONTH} field
280 * value (0-based. e.g., 0 for January).
281 * @param startDay The day of the month on which the daylight saving time starts.
282 * See the class description for the special cases of this parameter.
283 * @param startDayOfWeek The daylight saving time starting day-of-week.
284 * See the class description for the special cases of this parameter.
285 * @param startTime The daylight saving time starting time in the time mode
286 * specified by <code>startTimeMode</code>.
287 * @param startTimeMode The mode of the start time specified by startTime.
288 * @param endMonth The daylight saving time ending month. Month is
289 * a {@link Calendar#MONTH MONTH} field
290 * value (0-based. e.g., 9 for October).
291 * @param endDay The day of the month on which the daylight saving time ends.
292 * See the class description for the special cases of this parameter.
293 * @param endDayOfWeek The daylight saving time ending day-of-week.
294 * See the class description for the special cases of this parameter.
295 * @param endTime The daylight saving ending time in time time mode
296 * specified by <code>endTimeMode</code>.
297 * @param endTimeMode The mode of the end time specified by endTime
298 * @param dstSavings The amount of time in milliseconds saved during
299 * daylight saving time.
300 *
301 * @exception IllegalArgumentException if the month, day, dayOfWeek, time more, or
302 * time parameters are out of range for the start or end rule, or if a time mode
303 * value is invalid.
304 *
305 * @see #WALL_TIME
306 * @see #STANDARD_TIME
307 * @see #UTC_TIME
308 *
309 * @since 1.4
310 */
311 public SimpleTimeZone(int rawOffset, String ID,
312 int startMonth, int startDay, int startDayOfWeek,
313 int startTime, int startTimeMode,
314 int endMonth, int endDay, int endDayOfWeek,
315 int endTime, int endTimeMode,
316 int dstSavings) {
317
318 setID(ID);
319 this.rawOffset = rawOffset;
320 this.startMonth = startMonth;
321 this.startDay = startDay;
322 this.startDayOfWeek = startDayOfWeek;
323 this.startTime = startTime;
324 this.startTimeMode = startTimeMode;
325 this.endMonth = endMonth;
326 this.endDay = endDay;
327 this.endDayOfWeek = endDayOfWeek;
328 this.endTime = endTime;
329 this.endTimeMode = endTimeMode;
330 this.dstSavings = dstSavings;
331
332 // this.useDaylight is set by decodeRules
333 decodeRules();
334 if (dstSavings <= 0) {
335 throw new IllegalArgumentException("Illegal daylight saving value: " + dstSavings);
336 }
337 }
338
339 /**
340 * Sets the daylight saving time starting year.
341 *
342 * @param year The daylight saving starting year.
343 */
344 public void setStartYear(int year)
345 {
346 startYear = year;
347 invalidateCache();
348 }
349
350 /**
351 * Sets the daylight saving time start rule. For example, if daylight saving
352 * time starts on the first Sunday in April at 2 am in local wall clock
353 * time, you can set the start rule by calling:
354 * <pre><code>setStartRule(Calendar.APRIL, 1, Calendar.SUNDAY, 2*60*60*1000);</code></pre>
355 *
356 * @param startMonth The daylight saving time starting month. Month is
357 * a {@link Calendar#MONTH MONTH} field
358 * value (0-based. e.g., 0 for January).
359 * @param startDay The day of the month on which the daylight saving time starts.
360 * See the class description for the special cases of this parameter.
361 * @param startDayOfWeek The daylight saving time starting day-of-week.
362 * See the class description for the special cases of this parameter.
363 * @param startTime The daylight saving time starting time in local wall clock
364 * time, which is local standard time in this case.
365 * @exception IllegalArgumentException if the <code>startMonth</code>, <code>startDay</code>,
366 * <code>startDayOfWeek</code>, or <code>startTime</code> parameters are out of range
367 */
368 public void setStartRule(int startMonth, int startDay, int startDayOfWeek, int startTime)
369 {
370 this.startMonth = startMonth;
371 this.startDay = startDay;
372 this.startDayOfWeek = startDayOfWeek;
373 this.startTime = startTime;
374 startTimeMode = WALL_TIME;
375 decodeStartRule();
376 invalidateCache();
377 }
378
379 /**
380 * Sets the daylight saving time start rule to a fixed date within a month.
381 * This method is equivalent to:
382 * <pre><code>setStartRule(startMonth, startDay, 0, startTime)</code></pre>
383 *
384 * @param startMonth The daylight saving time starting month. Month is
385 * a {@link Calendar#MONTH MONTH} field
386 * value (0-based. e.g., 0 for January).
387 * @param startDay The day of the month on which the daylight saving time starts.
388 * @param startTime The daylight saving time starting time in local wall clock
389 * time, which is local standard time in this case.
390 * See the class description for the special cases of this parameter.
391 * @exception IllegalArgumentException if the <code>startMonth</code>,
392 * <code>startDayOfMonth</code>, or <code>startTime</code> parameters are out of range
393 * @since 1.2
394 */
395 public void setStartRule(int startMonth, int startDay, int startTime) {
396 setStartRule(startMonth, startDay, 0, startTime);
397 }
398
399 /**
400 * Sets the daylight saving time start rule to a weekday before or after the given date within
401 * a month, e.g., the first Monday on or after the 8th.
402 *
403 * @param startMonth The daylight saving time starting month. Month is
404 * a {@link Calendar#MONTH MONTH} field
405 * value (0-based. e.g., 0 for January).
406 * @param startDay The day of the month on which the daylight saving time starts.
407 * @param startDayOfWeek The daylight saving time starting day-of-week.
408 * @param startTime The daylight saving time starting time in local wall clock
409 * time, which is local standard time in this case.
410 * @param after If true, this rule selects the first <code>dayOfWeek</code> on or
411 * <em>after</em> <code>dayOfMonth</code>. If false, this rule
412 * selects the last <code>dayOfWeek</code> on or <em>before</em>
413 * <code>dayOfMonth</code>.
414 * @exception IllegalArgumentException if the <code>startMonth</code>, <code>startDay</code>,
415 * <code>startDayOfWeek</code>, or <code>startTime</code> parameters are out of range
416 * @since 1.2
417 */
418 public void setStartRule(int startMonth, int startDay, int startDayOfWeek,
419 int startTime, boolean after)
420 {
421 // TODO: this method doesn't check the initial values of dayOfMonth or dayOfWeek.
422 if (after) {
423 setStartRule(startMonth, startDay, -startDayOfWeek, startTime);
424 } else {
425 setStartRule(startMonth, -startDay, -startDayOfWeek, startTime);
426 }
427 }
428
429 /**
430 * Sets the daylight saving time end rule. For example, if daylight saving time
431 * ends on the last Sunday in October at 2 am in wall clock time,
432 * you can set the end rule by calling:
433 * <code>setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2*60*60*1000);</code>
434 *
435 * @param endMonth The daylight saving time ending month. Month is
436 * a {@link Calendar#MONTH MONTH} field
437 * value (0-based. e.g., 9 for October).
438 * @param endDay The day of the month on which the daylight saving time ends.
439 * See the class description for the special cases of this parameter.
440 * @param endDayOfWeek The daylight saving time ending day-of-week.
441 * See the class description for the special cases of this parameter.
442 * @param endTime The daylight saving ending time in local wall clock time,
443 * (in milliseconds within the day) which is local daylight
444 * time in this case.
445 * @exception IllegalArgumentException if the <code>endMonth</code>, <code>endDay</code>,
446 * <code>endDayOfWeek</code>, or <code>endTime</code> parameters are out of range
447 */
448 public void setEndRule(int endMonth, int endDay, int endDayOfWeek,
449 int endTime)
450 {
451 this.endMonth = endMonth;
452 this.endDay = endDay;
453 this.endDayOfWeek = endDayOfWeek;
454 this.endTime = endTime;
455 this.endTimeMode = WALL_TIME;
456 decodeEndRule();
457 invalidateCache();
458 }
459
460 /**
461 * Sets the daylight saving time end rule to a fixed date within a month.
462 * This method is equivalent to:
463 * <pre><code>setEndRule(endMonth, endDay, 0, endTime)</code></pre>
464 *
465 * @param endMonth The daylight saving time ending month. Month is
466 * a {@link Calendar#MONTH MONTH} field
467 * value (0-based. e.g., 9 for October).
468 * @param endDay The day of the month on which the daylight saving time ends.
469 * @param endTime The daylight saving ending time in local wall clock time,
470 * (in milliseconds within the day) which is local daylight
471 * time in this case.
472 * @exception IllegalArgumentException the <code>endMonth</code>, <code>endDay</code>,
473 * or <code>endTime</code> parameters are out of range
474 * @since 1.2
475 */
476 public void setEndRule(int endMonth, int endDay, int endTime)
477 {
478 setEndRule(endMonth, endDay, 0, endTime);
479 }
480
481 /**
482 * Sets the daylight saving time end rule to a weekday before or after the given date within
483 * a month, e.g., the first Monday on or after the 8th.
484 *
485 * @param endMonth The daylight saving time ending month. Month is
486 * a {@link Calendar#MONTH MONTH} field
487 * value (0-based. e.g., 9 for October).
488 * @param endDay The day of the month on which the daylight saving time ends.
489 * @param endDayOfWeek The daylight saving time ending day-of-week.
490 * @param endTime The daylight saving ending time in local wall clock time,
491 * (in milliseconds within the day) which is local daylight
492 * time in this case.
493 * @param after If true, this rule selects the first <code>endDayOfWeek</code> on
494 * or <em>after</em> <code>endDay</code>. If false, this rule
495 * selects the last <code>endDayOfWeek</code> on or before
496 * <code>endDay</code> of the month.
497 * @exception IllegalArgumentException the <code>endMonth</code>, <code>endDay</code>,
498 * <code>endDayOfWeek</code>, or <code>endTime</code> parameters are out of range
499 * @since 1.2
500 */
501 public void setEndRule(int endMonth, int endDay, int endDayOfWeek, int endTime, boolean after)
502 {
503 if (after) {
504 setEndRule(endMonth, endDay, -endDayOfWeek, endTime);
505 } else {
506 setEndRule(endMonth, -endDay, -endDayOfWeek, endTime);
507 }
508 }
509
510 /**
511 * Returns the offset of this time zone from UTC at the given
512 * time. If daylight saving time is in effect at the given time,
513 * the offset value is adjusted with the amount of daylight
514 * saving.
515 *
516 * @param date the time at which the time zone offset is found
517 * @return the amount of time in milliseconds to add to UTC to get
518 * local time.
519 * @since 1.4
520 */
521 public int getOffset(long date) {
522 return getOffsets(date, null);
523 }
524
525 /**
526 * @see TimeZone#getOffsets
527 */
528 int getOffsets(long date, int[] offsets) {
529 int offset = rawOffset;
530
531 computeOffset:
532 if (useDaylight) {
533 synchronized (this) {
534 if (cacheStart != 0) {
535 if (date >= cacheStart && date < cacheEnd) {
536 offset += dstSavings;
537 break computeOffset;
538 }
539 }
540 }
541 BaseCalendar cal = date >= GregorianCalendar.DEFAULT_GREGORIAN_CUTOVER ?
542 gcal : (BaseCalendar) CalendarSystem.forName("julian");
543 BaseCalendar.Date cdate = (BaseCalendar.Date) cal.newCalendarDate(TimeZone.NO_TIMEZONE);
544 // Get the year in local time
545 cal.getCalendarDate(date + rawOffset, cdate);
546 int year = cdate.getNormalizedYear();
547 if (year >= startYear) {
548 // Clear time elements for the transition calculations
549 cdate.setTimeOfDay(0, 0, 0, 0);
550 offset = getOffset(cal, cdate, year, date);
551 }
552 }
553
554 if (offsets != null) {
555 offsets[0] = rawOffset;
556 offsets[1] = offset - rawOffset;
557 }
558 return offset;
559 }
560
561 /**
562 * Returns the difference in milliseconds between local time and
563 * UTC, taking into account both the raw offset and the effect of
564 * daylight saving, for the specified date and time. This method
565 * assumes that the start and end month are distinct. It also
566 * uses a default {@link GregorianCalendar} object as its
567 * underlying calendar, such as for determining leap years. Do
568 * not use the result of this method with a calendar other than a
569 * default <code>GregorianCalendar</code>.
570 *
571 * <p><em>Note: In general, clients should use
572 * <code>Calendar.get(ZONE_OFFSET) + Calendar.get(DST_OFFSET)</code>
573 * instead of calling this method.</em>
574 *
575 * @param era The era of the given date.
576 * @param year The year in the given date.
577 * @param month The month in the given date. Month is 0-based. e.g.,
578 * 0 for January.
579 * @param day The day-in-month of the given date.
580 * @param dayOfWeek The day-of-week of the given date.
581 * @param millis The milliseconds in day in <em>standard</em> local time.
582 * @return The milliseconds to add to UTC to get local time.
583 * @exception IllegalArgumentException the <code>era</code>,
584 * <code>month</code>, <code>day</code>, <code>dayOfWeek</code>,
585 * or <code>millis</code> parameters are out of range
586 */
587 public int getOffset(int era, int year, int month, int day, int dayOfWeek,
588 int millis)
589 {
590 if (era != GregorianCalendar.AD && era != GregorianCalendar.BC) {
591 throw new IllegalArgumentException("Illegal era " + era);
592 }
593
594 int y = year;
595 if (era == GregorianCalendar.BC) {
596 // adjust y with the GregorianCalendar-style year numbering.
597 y = 1 - y;
598 }
599
600 // If the year isn't representable with the 64-bit long
601 // integer in milliseconds, convert the year to an
602 // equivalent year. This is required to pass some JCK test cases
603 // which are actually useless though because the specified years
604 // can't be supported by the Java time system.
605 if (y >= 292278994) {
606 y = 2800 + y % 2800;
607 } else if (y <= -292269054) {
608 // y %= 28 also produces an equivalent year, but positive
609 // year numbers would be convenient to use the UNIX cal
610 // command.
611 y = (int) CalendarUtils.mod((long) y, 28);
612 }
613
614 // convert year to its 1-based month value
615 int m = month + 1;
616
617 // First, calculate time as a Gregorian date.
618 BaseCalendar cal = gcal;
619 BaseCalendar.Date cdate = (BaseCalendar.Date) cal.newCalendarDate(TimeZone.NO_TIMEZONE);
620 cdate.setDate(y, m, day);
621 long time = cal.getTime(cdate); // normalize cdate
622 time += millis - rawOffset; // UTC time
623
624 // If the time value represents a time before the default
625 // Gregorian cutover, recalculate time using the Julian
626 // calendar system. For the Julian calendar system, the
627 // normalized year numbering is ..., -2 (BCE 2), -1 (BCE 1),
628 // 1, 2 ... which is different from the GregorianCalendar
629 // style year numbering (..., -1, 0 (BCE 1), 1, 2, ...).
630 if (time < GregorianCalendar.DEFAULT_GREGORIAN_CUTOVER) {
631 cal = (BaseCalendar) CalendarSystem.forName("julian");
632 cdate = (BaseCalendar.Date) cal.newCalendarDate(TimeZone.NO_TIMEZONE);
633 cdate.setNormalizedDate(y, m, day);
634 time = cal.getTime(cdate) + millis - rawOffset;
635 }
636
637 if ((cdate.getNormalizedYear() != y)
638 || (cdate.getMonth() != m)
639 || (cdate.getDayOfMonth() != day)
640 // The validation should be cdate.getDayOfWeek() ==
641 // dayOfWeek. However, we don't check dayOfWeek for
642 // compatibility.
643 || (dayOfWeek < Calendar.SUNDAY || dayOfWeek > Calendar.SATURDAY)
644 || (millis < 0 || millis >= (24*60*60*1000))) {
645 throw new IllegalArgumentException();
646 }
647
648 if (!useDaylight || year < startYear || era != GregorianCalendar.CE) {
649 return rawOffset;
650 }
651
652 return getOffset(cal, cdate, y, time);
653 }
654
655 private int getOffset(BaseCalendar cal, BaseCalendar.Date cdate, int year, long time) {
656 synchronized (this) {
657 if (cacheStart != 0) {
658 if (time >= cacheStart && time < cacheEnd) {
659 return rawOffset + dstSavings;
660 }
661 if (year == cacheYear) {
662 return rawOffset;
663 }
664 }
665 }
666
667 long start = getStart(cal, cdate, year);
668 long end = getEnd(cal, cdate, year);
669 int offset = rawOffset;
670 if (start <= end) {
671 if (time >= start && time < end) {
672 offset += dstSavings;
673 }
674 synchronized (this) {
675 cacheYear = year;
676 cacheStart = start;
677 cacheEnd = end;
678 }
679 } else {
680 if (time < end) {
681 // TODO: support Gregorian cutover. The previous year
682 // may be in the other calendar system.
683 start = getStart(cal, cdate, year - 1);
684 if (time >= start) {
685 offset += dstSavings;
686 }
687 } else if (time >= start) {
688 // TODO: support Gregorian cutover. The next year
689 // may be in the other calendar system.
690 end = getEnd(cal, cdate, year + 1);
691 if (time < end) {
692 offset += dstSavings;
693 }
694 }
695 if (start <= end) {
696 synchronized (this) {
697 // The start and end transitions are in multiple years.
698 cacheYear = (long) startYear - 1;
699 cacheStart = start;
700 cacheEnd = end;
701 }
702 }
703 }
704 return offset;
705 }
706
707 private long getStart(BaseCalendar cal, BaseCalendar.Date cdate, int year) {
708 int time = startTime;
709 if (startTimeMode != UTC_TIME) {
710 time -= rawOffset;
711 }
712 return getTransition(cal, cdate, startMode, year, startMonth, startDay,
713 startDayOfWeek, time);
714 }
715
716 private long getEnd(BaseCalendar cal, BaseCalendar.Date cdate, int year) {
717 int time = endTime;
718 if (endTimeMode != UTC_TIME) {
719 time -= rawOffset;
720 }
721 if (endTimeMode == WALL_TIME) {
722 time -= dstSavings;
723 }
724 return getTransition(cal, cdate, endMode, year, endMonth, endDay,
725 endDayOfWeek, time);
726 }
727
728 private long getTransition(BaseCalendar cal, BaseCalendar.Date cdate,
729 int mode, int year, int month, int dayOfMonth,
730 int dayOfWeek, int timeOfDay) {
731 cdate.setNormalizedYear(year);
732 cdate.setMonth(month + 1);
733 switch (mode) {
734 case DOM_MODE:
735 cdate.setDayOfMonth(dayOfMonth);
736 break;
737
738 case DOW_IN_MONTH_MODE:
739 cdate.setDayOfMonth(1);
740 if (dayOfMonth < 0) {
741 cdate.setDayOfMonth(cal.getMonthLength(cdate));
742 }
743 cdate = (BaseCalendar.Date) cal.getNthDayOfWeek(dayOfMonth, dayOfWeek, cdate);
744 break;
745
746 case DOW_GE_DOM_MODE:
747 cdate.setDayOfMonth(dayOfMonth);
748 cdate = (BaseCalendar.Date) cal.getNthDayOfWeek(1, dayOfWeek, cdate);
749 break;
750
751 case DOW_LE_DOM_MODE:
752 cdate.setDayOfMonth(dayOfMonth);
753 cdate = (BaseCalendar.Date) cal.getNthDayOfWeek(-1, dayOfWeek, cdate);
754 break;
755 }
756 return cal.getTime(cdate) + timeOfDay;
757 }
758
759 /**
760 * Gets the GMT offset for this time zone.
761 * @return the GMT offset value in milliseconds
762 * @see #setRawOffset
763 */
764 public int getRawOffset()
765 {
766 // The given date will be taken into account while
767 // we have the historical time zone data in place.
768 return rawOffset;
769 }
770
771 /**
772 * Sets the base time zone offset to GMT.
773 * This is the offset to add to UTC to get local time.
774 * @see #getRawOffset
775 */
776 public void setRawOffset(int offsetMillis)
777 {
778 this.rawOffset = offsetMillis;
779 }
780
781 /**
782 * Sets the amount of time in milliseconds that the clock is advanced
783 * during daylight saving time.
784 * @param millisSavedDuringDST the number of milliseconds the time is
785 * advanced with respect to standard time when the daylight saving time rules
786 * are in effect. A positive number, typically one hour (3600000).
787 * @see #getDSTSavings
788 * @since 1.2
789 */
790 public void setDSTSavings(int millisSavedDuringDST) {
791 if (millisSavedDuringDST <= 0) {
792 throw new IllegalArgumentException("Illegal daylight saving value: "
793 + millisSavedDuringDST);
794 }
795 dstSavings = millisSavedDuringDST;
796 }
797
798 /**
799 * Returns the amount of time in milliseconds that the clock is
800 * advanced during daylight saving time.
801 *
802 * @return the number of milliseconds the time is advanced with
803 * respect to standard time when the daylight saving rules are in
804 * effect, or 0 (zero) if this time zone doesn't observe daylight
805 * saving time.
806 *
807 * @see #setDSTSavings
808 * @since 1.2
809 */
810 public int getDSTSavings() {
811 if (useDaylight) {
812 return dstSavings;
813 }
814 return 0;
815 }
816
817 /**
818 * Queries if this time zone uses daylight saving time.
819 * @return true if this time zone uses daylight saving time;
820 * false otherwise.
821 */
822 public boolean useDaylightTime()
823 {
824 return useDaylight;
825 }
826
827 /**
828 * Queries if the given date is in daylight saving time.
829 * @return true if daylight saving time is in effective at the
830 * given date; false otherwise.
831 */
832 public boolean inDaylightTime(Date date)
833 {
834 return (getOffset(date.getTime()) != rawOffset);
835 }
836
837 /**
838 * Returns a clone of this <code>SimpleTimeZone</code> instance.
839 * @return a clone of this instance.
840 */
841 public Object clone()
842 {
843 return super.clone();
844 }
845
846 /**
847 * Generates the hash code for the SimpleDateFormat object.
848 * @return the hash code for this object
849 */
850 public synchronized int hashCode()
851 {
852 return startMonth ^ startDay ^ startDayOfWeek ^ startTime ^
853 endMonth ^ endDay ^ endDayOfWeek ^ endTime ^ rawOffset;
854 }
855
856 /**
857 * Compares the equality of two <code>SimpleTimeZone</code> objects.
858 *
859 * @param obj The <code>SimpleTimeZone</code> object to be compared with.
860 * @return True if the given <code>obj</code> is the same as this
861 * <code>SimpleTimeZone</code> object; false otherwise.
862 */
863 public boolean equals(Object obj)
864 {
865 if (this == obj) {
866 return true;
867 }
868 if (!(obj instanceof SimpleTimeZone)) {
869 return false;
870 }
871
872 SimpleTimeZone that = (SimpleTimeZone) obj;
873
874 return getID().equals(that.getID()) &&
875 hasSameRules(that);
876 }
877
878 /**
879 * Returns <code>true</code> if this zone has the same rules and offset as another zone.
880 * @param other the TimeZone object to be compared with
881 * @return <code>true</code> if the given zone is a SimpleTimeZone and has the
882 * same rules and offset as this one
883 * @since 1.2
884 */
885 public boolean hasSameRules(TimeZone other) {
886 if (this == other) {
887 return true;
888 }
889 if (!(other instanceof SimpleTimeZone)) {
890 return false;
891 }
892 SimpleTimeZone that = (SimpleTimeZone) other;
893 return rawOffset == that.rawOffset &&
894 useDaylight == that.useDaylight &&
895 (!useDaylight
896 // Only check rules if using DST
897 || (dstSavings == that.dstSavings &&
898 startMode == that.startMode &&
899 startMonth == that.startMonth &&
900 startDay == that.startDay &&
901 startDayOfWeek == that.startDayOfWeek &&
902 startTime == that.startTime &&
903 startTimeMode == that.startTimeMode &&
904 endMode == that.endMode &&
905 endMonth == that.endMonth &&
906 endDay == that.endDay &&
907 endDayOfWeek == that.endDayOfWeek &&
908 endTime == that.endTime &&
909 endTimeMode == that.endTimeMode &&
910 startYear == that.startYear));
911 }
912
913 /**
914 * Returns a string representation of this time zone.
915 * @return a string representation of this time zone.
916 */
917 public String toString() {
918 return getClass().getName() +
919 "[id=" + getID() +
920 ",offset=" + rawOffset +
921 ",dstSavings=" + dstSavings +
922 ",useDaylight=" + useDaylight +
923 ",startYear=" + startYear +
924 ",startMode=" + startMode +
925 ",startMonth=" + startMonth +
926 ",startDay=" + startDay +
927 ",startDayOfWeek=" + startDayOfWeek +
928 ",startTime=" + startTime +
929 ",startTimeMode=" + startTimeMode +
930 ",endMode=" + endMode +
931 ",endMonth=" + endMonth +
932 ",endDay=" + endDay +
933 ",endDayOfWeek=" + endDayOfWeek +
934 ",endTime=" + endTime +
935 ",endTimeMode=" + endTimeMode + ']';
936 }
937
938 // =======================privates===============================
939
940 /**
941 * The month in which daylight saving time starts. This value must be
942 * between <code>Calendar.JANUARY</code> and
943 * <code>Calendar.DECEMBER</code> inclusive. This value must not equal
944 * <code>endMonth</code>.
945 * <p>If <code>useDaylight</code> is false, this value is ignored.
946 * @serial
947 */
948 private int startMonth;
949
950 /**
951 * This field has two possible interpretations:
952 * <dl>
953 * <dt><code>startMode == DOW_IN_MONTH</code></dt>
954 * <dd>
955 * <code>startDay</code> indicates the day of the month of
956 * <code>startMonth</code> on which daylight
957 * saving time starts, from 1 to 28, 30, or 31, depending on the
958 * <code>startMonth</code>.
959 * </dd>
960 * <dt><code>startMode != DOW_IN_MONTH</code></dt>
961 * <dd>
962 * <code>startDay</code> indicates which <code>startDayOfWeek</code> in th
963 * month <code>startMonth</code> daylight
964 * saving time starts on. For example, a value of +1 and a
965 * <code>startDayOfWeek</code> of <code>Calendar.SUNDAY</code> indicates the
966 * first Sunday of <code>startMonth</code>. Likewise, +2 would indicate the
967 * second Sunday, and -1 the last Sunday. A value of 0 is illegal.
968 * </dd>
969 * </dl>
970 * <p>If <code>useDaylight</code> is false, this value is ignored.
971 * @serial
972 */
973 private int startDay;
974
975 /**
976 * The day of the week on which daylight saving time starts. This value
977 * must be between <code>Calendar.SUNDAY</code> and
978 * <code>Calendar.SATURDAY</code> inclusive.
979 * <p>If <code>useDaylight</code> is false or
980 * <code>startMode == DAY_OF_MONTH</code>, this value is ignored.
981 * @serial
982 */
983 private int startDayOfWeek;
984
985 /**
986 * The time in milliseconds after midnight at which daylight saving
987 * time starts. This value is expressed as wall time, standard time,
988 * or UTC time, depending on the setting of <code>startTimeMode</code>.
989 * <p>If <code>useDaylight</code> is false, this value is ignored.
990 * @serial
991 */
992 private int startTime;
993
994 /**
995 * The format of startTime, either WALL_TIME, STANDARD_TIME, or UTC_TIME.
996 * @serial
997 * @since 1.3
998 */
999 private int startTimeMode;
1000
1001 /**
1002 * The month in which daylight saving time ends. This value must be
1003 * between <code>Calendar.JANUARY</code> and
1004 * <code>Calendar.UNDECIMBER</code>. This value must not equal
1005 * <code>startMonth</code>.
1006 * <p>If <code>useDaylight</code> is false, this value is ignored.
1007 * @serial
1008 */
1009 private int endMonth;
1010
1011 /**
1012 * This field has two possible interpretations:
1013 * <dl>
1014 * <dt><code>endMode == DOW_IN_MONTH</code></dt>
1015 * <dd>
1016 * <code>endDay</code> indicates the day of the month of
1017 * <code>endMonth</code> on which daylight
1018 * saving time ends, from 1 to 28, 30, or 31, depending on the
1019 * <code>endMonth</code>.
1020 * </dd>
1021 * <dt><code>endMode != DOW_IN_MONTH</code></dt>
1022 * <dd>
1023 * <code>endDay</code> indicates which <code>endDayOfWeek</code> in th
1024 * month <code>endMonth</code> daylight
1025 * saving time ends on. For example, a value of +1 and a
1026 * <code>endDayOfWeek</code> of <code>Calendar.SUNDAY</code> indicates the
1027 * first Sunday of <code>endMonth</code>. Likewise, +2 would indicate the
1028 * second Sunday, and -1 the last Sunday. A value of 0 is illegal.
1029 * </dd>
1030 * </dl>
1031 * <p>If <code>useDaylight</code> is false, this value is ignored.
1032 * @serial
1033 */
1034 private int endDay;
1035
1036 /**
1037 * The day of the week on which daylight saving time ends. This value
1038 * must be between <code>Calendar.SUNDAY</code> and
1039 * <code>Calendar.SATURDAY</code> inclusive.
1040 * <p>If <code>useDaylight</code> is false or
1041 * <code>endMode == DAY_OF_MONTH</code>, this value is ignored.
1042 * @serial
1043 */
1044 private int endDayOfWeek;
1045
1046 /**
1047 * The time in milliseconds after midnight at which daylight saving
1048 * time ends. This value is expressed as wall time, standard time,
1049 * or UTC time, depending on the setting of <code>endTimeMode</code>.
1050 * <p>If <code>useDaylight</code> is false, this value is ignored.
1051 * @serial
1052 */
1053 private int endTime;
1054
1055 /**
1056 * The format of endTime, either <code>WALL_TIME</code>,
1057 * <code>STANDARD_TIME</code>, or <code>UTC_TIME</code>.
1058 * @serial
1059 * @since 1.3
1060 */
1061 private int endTimeMode;
1062
1063 /**
1064 * The year in which daylight saving time is first observed. This is an {@link GregorianCalendar#AD AD}
1065 * value. If this value is less than 1 then daylight saving time is observed
1066 * for all <code>AD</code> years.
1067 * <p>If <code>useDaylight</code> is false, this value is ignored.
1068 * @serial
1069 */
1070 private int startYear;
1071
1072 /**
1073 * The offset in milliseconds between this zone and GMT. Negative offsets
1074 * are to the west of Greenwich. To obtain local <em>standard</em> time,
1075 * add the offset to GMT time. To obtain local wall time it may also be
1076 * necessary to add <code>dstSavings</code>.
1077 * @serial
1078 */
1079 private int rawOffset;
1080
1081 /**
1082 * A boolean value which is true if and only if this zone uses daylight
1083 * saving time. If this value is false, several other fields are ignored.
1084 * @serial
1085 */
1086 private boolean useDaylight=false; // indicate if this time zone uses DST
1087
1088 private static final int millisPerHour = 60*60*1000;
1089 private static final int millisPerDay = 24*millisPerHour;
1090
1091 /**
1092 * This field was serialized in JDK 1.1, so we have to keep it that way
1093 * to maintain serialization compatibility. However, there's no need to
1094 * recreate the array each time we create a new time zone.
1095 * @serial An array of bytes containing the values {31, 28, 31, 30, 31, 30,
1096 * 31, 31, 30, 31, 30, 31}. This is ignored as of the Java 2 platform v1.2, however, it must
1097 * be streamed out for compatibility with JDK 1.1.
1098 */
1099 private final byte monthLength[] = staticMonthLength;
1100 private final static byte staticMonthLength[] = {31,28,31,30,31,30,31,31,30,31,30,31};
1101 private final static byte staticLeapMonthLength[] = {31,29,31,30,31,30,31,31,30,31,30,31};
1102
1103 /**
1104 * Variables specifying the mode of the start rule. Takes the following
1105 * values:
1106 * <dl>
1107 * <dt><code>DOM_MODE</code></dt>
1108 * <dd>
1109 * Exact day of week; e.g., March 1.
1110 * </dd>
1111 * <dt><code>DOW_IN_MONTH_MODE</code></dt>
1112 * <dd>
1113 * Day of week in month; e.g., last Sunday in March.
1114 * </dd>
1115 * <dt><code>DOW_GE_DOM_MODE</code></dt>
1116 * <dd>
1117 * Day of week after day of month; e.g., Sunday on or after March 15.
1118 * </dd>
1119 * <dt><code>DOW_LE_DOM_MODE</code></dt>
1120 * <dd>
1121 * Day of week before day of month; e.g., Sunday on or before March 15.
1122 * </dd>
1123 * </dl>
1124 * The setting of this field affects the interpretation of the
1125 * <code>startDay</code> field.
1126 * <p>If <code>useDaylight</code> is false, this value is ignored.
1127 * @serial
1128 * @since 1.1.4
1129 */
1130 private int startMode;
1131
1132 /**
1133 * Variables specifying the mode of the end rule. Takes the following
1134 * values:
1135 * <dl>
1136 * <dt><code>DOM_MODE</code></dt>
1137 * <dd>
1138 * Exact day of week; e.g., March 1.
1139 * </dd>
1140 * <dt><code>DOW_IN_MONTH_MODE</code></dt>
1141 * <dd>
1142 * Day of week in month; e.g., last Sunday in March.
1143 * </dd>
1144 * <dt><code>DOW_GE_DOM_MODE</code></dt>
1145 * <dd>
1146 * Day of week after day of month; e.g., Sunday on or after March 15.
1147 * </dd>
1148 * <dt><code>DOW_LE_DOM_MODE</code></dt>
1149 * <dd>
1150 * Day of week before day of month; e.g., Sunday on or before March 15.
1151 * </dd>
1152 * </dl>
1153 * The setting of this field affects the interpretation of the
1154 * <code>endDay</code> field.
1155 * <p>If <code>useDaylight</code> is false, this value is ignored.
1156 * @serial
1157 * @since 1.1.4
1158 */
1159 private int endMode;
1160
1161 /**
1162 * A positive value indicating the amount of time saved during DST in
1163 * milliseconds.
1164 * Typically one hour (3600000); sometimes 30 minutes (1800000).
1165 * <p>If <code>useDaylight</code> is false, this value is ignored.
1166 * @serial
1167 * @since 1.1.4
1168 */
1169 private int dstSavings;
1170
1171 private static final Gregorian gcal = CalendarSystem.getGregorianCalendar();
1172
1173 /**
1174 * Cache values representing a single period of daylight saving
1175 * time. When the cache values are valid, cacheStart is the start
1176 * time (inclusive) of daylight saving time and cacheEnd is the
1177 * end time (exclusive).
1178 *
1179 * cacheYear has a year value if both cacheStart and cacheEnd are
1180 * in the same year. cacheYear is set to startYear - 1 if
1181 * cacheStart and cacheEnd are in different years. cacheStart is 0
1182 * if the cache values are void. cacheYear is a long to support
1183 * Integer.MIN_VALUE - 1 (JCK requirement).
1184 */
1185 private transient long cacheYear;
1186 private transient long cacheStart;
1187 private transient long cacheEnd;
1188
1189 /**
1190 * Constants specifying values of startMode and endMode.
1191 */
1192 private static final int DOM_MODE = 1; // Exact day of month, "Mar 1"
1193 private static final int DOW_IN_MONTH_MODE = 2; // Day of week in month, "lastSun"
1194 private static final int DOW_GE_DOM_MODE = 3; // Day of week after day of month, "Sun>=15"
1195 private static final int DOW_LE_DOM_MODE = 4; // Day of week before day of month, "Sun<=21"
1196
1197 /**
1198 * Constant for a mode of start or end time specified as wall clock
1199 * time. Wall clock time is standard time for the onset rule, and
1200 * daylight time for the end rule.
1201 * @since 1.4
1202 */
1203 public static final int WALL_TIME = 0; // Zero for backward compatibility
1204
1205 /**
1206 * Constant for a mode of start or end time specified as standard time.
1207 * @since 1.4
1208 */
1209 public static final int STANDARD_TIME = 1;
1210
1211 /**
1212 * Constant for a mode of start or end time specified as UTC. European
1213 * Union rules are specified as UTC time, for example.
1214 * @since 1.4
1215 */
1216 public static final int UTC_TIME = 2;
1217
1218 // Proclaim compatibility with 1.1
1219 static final long serialVersionUID = -403250971215465050L;
1220
1221 // the internal serial version which says which version was written
1222 // - 0 (default) for version up to JDK 1.1.3
1223 // - 1 for version from JDK 1.1.4, which includes 3 new fields
1224 // - 2 for JDK 1.3, which includes 2 new fields
1225 static final int currentSerialVersion = 2;
1226
1227 /**
1228 * The version of the serialized data on the stream. Possible values:
1229 * <dl>
1230 * <dt><b>0</b> or not present on stream</dt>
1231 * <dd>
1232 * JDK 1.1.3 or earlier.
1233 * </dd>
1234 * <dt><b>1</b></dt>
1235 * <dd>
1236 * JDK 1.1.4 or later. Includes three new fields: <code>startMode</code>,
1237 * <code>endMode</code>, and <code>dstSavings</code>.
1238 * </dd>
1239 * <dt><b>2</b></dt>
1240 * <dd>
1241 * JDK 1.3 or later. Includes two new fields: <code>startTimeMode</code>
1242 * and <code>endTimeMode</code>.
1243 * </dd>
1244 * </dl>
1245 * When streaming out this class, the most recent format
1246 * and the highest allowable <code>serialVersionOnStream</code>
1247 * is written.
1248 * @serial
1249 * @since 1.1.4
1250 */
1251 private int serialVersionOnStream = currentSerialVersion;
1252
1253 synchronized private void invalidateCache() {
1254 cacheYear = startYear - 1;
1255 cacheStart = cacheEnd = 0;
1256 }
1257
1258 //----------------------------------------------------------------------
1259 // Rule representation
1260 //
1261 // We represent the following flavors of rules:
1262 // 5 the fifth of the month
1263 // lastSun the last Sunday in the month
1264 // lastMon the last Monday in the month
1265 // Sun>=8 first Sunday on or after the eighth
1266 // Sun<=25 last Sunday on or before the 25th
1267 // This is further complicated by the fact that we need to remain
1268 // backward compatible with the 1.1 FCS. Finally, we need to minimize
1269 // API changes. In order to satisfy these requirements, we support
1270 // three representation systems, and we translate between them.
1271 //
1272 // INTERNAL REPRESENTATION
1273 // This is the format SimpleTimeZone objects take after construction or
1274 // streaming in is complete. Rules are represented directly, using an
1275 // unencoded format. We will discuss the start rule only below; the end
1276 // rule is analogous.
1277 // startMode Takes on enumerated values DAY_OF_MONTH,
1278 // DOW_IN_MONTH, DOW_AFTER_DOM, or DOW_BEFORE_DOM.
1279 // startDay The day of the month, or for DOW_IN_MONTH mode, a
1280 // value indicating which DOW, such as +1 for first,
1281 // +2 for second, -1 for last, etc.
1282 // startDayOfWeek The day of the week. Ignored for DAY_OF_MONTH.
1283 //
1284 // ENCODED REPRESENTATION
1285 // This is the format accepted by the constructor and by setStartRule()
1286 // and setEndRule(). It uses various combinations of positive, negative,
1287 // and zero values to encode the different rules. This representation
1288 // allows us to specify all the different rule flavors without altering
1289 // the API.
1290 // MODE startMonth startDay startDayOfWeek
1291 // DOW_IN_MONTH_MODE >=0 !=0 >0
1292 // DOM_MODE >=0 >0 ==0
1293 // DOW_GE_DOM_MODE >=0 >0 <0
1294 // DOW_LE_DOM_MODE >=0 <0 <0
1295 // (no DST) don't care ==0 don't care
1296 //
1297 // STREAMED REPRESENTATION
1298 // We must retain binary compatibility with the 1.1 FCS. The 1.1 code only
1299 // handles DOW_IN_MONTH_MODE and non-DST mode, the latter indicated by the
1300 // flag useDaylight. When we stream an object out, we translate into an
1301 // approximate DOW_IN_MONTH_MODE representation so the object can be parsed
1302 // and used by 1.1 code. Following that, we write out the full
1303 // representation separately so that contemporary code can recognize and
1304 // parse it. The full representation is written in a "packed" format,
1305 // consisting of a version number, a length, and an array of bytes. Future
1306 // versions of this class may specify different versions. If they wish to
1307 // include additional data, they should do so by storing them after the
1308 // packed representation below.
1309 //----------------------------------------------------------------------
1310
1311 /**
1312 * Given a set of encoded rules in startDay and startDayOfMonth, decode
1313 * them and set the startMode appropriately. Do the same for endDay and
1314 * endDayOfMonth. Upon entry, the day of week variables may be zero or
1315 * negative, in order to indicate special modes. The day of month
1316 * variables may also be negative. Upon exit, the mode variables will be
1317 * set, and the day of week and day of month variables will be positive.
1318 * This method also recognizes a startDay or endDay of zero as indicating
1319 * no DST.
1320 */
1321 private void decodeRules()
1322 {
1323 decodeStartRule();
1324 decodeEndRule();
1325 }
1326
1327 /**
1328 * Decode the start rule and validate the parameters. The parameters are
1329 * expected to be in encoded form, which represents the various rule modes
1330 * by negating or zeroing certain values. Representation formats are:
1331 * <p>
1332 * <pre>
1333 * DOW_IN_MONTH DOM DOW>=DOM DOW<=DOM no DST
1334 * ------------ ----- -------- -------- ----------
1335 * month 0..11 same same same don't care
1336 * day -5..5 1..31 1..31 -1..-31 0
1337 * dayOfWeek 1..7 0 -1..-7 -1..-7 don't care
1338 * time 0..ONEDAY same same same don't care
1339 * </pre>
1340 * The range for month does not include UNDECIMBER since this class is
1341 * really specific to GregorianCalendar, which does not use that month.
1342 * The range for time includes ONEDAY (vs. ending at ONEDAY-1) because the
1343 * end rule is an exclusive limit point. That is, the range of times that
1344 * are in DST include those >= the start and < the end. For this reason,
1345 * it should be possible to specify an end of ONEDAY in order to include the
1346 * entire day. Although this is equivalent to time 0 of the following day,
1347 * it's not always possible to specify that, for example, on December 31.
1348 * While arguably the start range should still be 0..ONEDAY-1, we keep
1349 * the start and end ranges the same for consistency.
1350 */
1351 private void decodeStartRule() {
1352 useDaylight = (startDay != 0) && (endDay != 0);
1353 if (startDay != 0) {
1354 if (startMonth < Calendar.JANUARY || startMonth > Calendar.DECEMBER) {
1355 throw new IllegalArgumentException(
1356 "Illegal start month " + startMonth);
1357 }
1358 if (startTime < 0 || startTime > millisPerDay) {
1359 throw new IllegalArgumentException(
1360 "Illegal start time " + startTime);
1361 }
1362 if (startDayOfWeek == 0) {
1363 startMode = DOM_MODE;
1364 } else {
1365 if (startDayOfWeek > 0) {
1366 startMode = DOW_IN_MONTH_MODE;
1367 } else {
1368 startDayOfWeek = -startDayOfWeek;
1369 if (startDay > 0) {
1370 startMode = DOW_GE_DOM_MODE;
1371 } else {
1372 startDay = -startDay;
1373 startMode = DOW_LE_DOM_MODE;
1374 }
1375 }
1376 if (startDayOfWeek > Calendar.SATURDAY) {
1377 throw new IllegalArgumentException(
1378 "Illegal start day of week " + startDayOfWeek);
1379 }
1380 }
1381 if (startMode == DOW_IN_MONTH_MODE) {
1382 if (startDay < -5 || startDay > 5) {
1383 throw new IllegalArgumentException(
1384 "Illegal start day of week in month " + startDay);
1385 }
1386 } else if (startDay < 1 || startDay > staticMonthLength[startMonth]) {
1387 throw new IllegalArgumentException(
1388 "Illegal start day " + startDay);
1389 }
1390 }
1391 }
1392
1393 /**
1394 * Decode the end rule and validate the parameters. This method is exactly
1395 * analogous to decodeStartRule().
1396 * @see decodeStartRule
1397 */
1398 private void decodeEndRule() {
1399 useDaylight = (startDay != 0) && (endDay != 0);
1400 if (endDay != 0) {
1401 if (endMonth < Calendar.JANUARY || endMonth > Calendar.DECEMBER) {
1402 throw new IllegalArgumentException(
1403 "Illegal end month " + endMonth);
1404 }
1405 if (endTime < 0 || endTime > millisPerDay) {
1406 throw new IllegalArgumentException(
1407 "Illegal end time " + endTime);
1408 }
1409 if (endDayOfWeek == 0) {
1410 endMode = DOM_MODE;
1411 } else {
1412 if (endDayOfWeek > 0) {
1413 endMode = DOW_IN_MONTH_MODE;
1414 } else {
1415 endDayOfWeek = -endDayOfWeek;
1416 if (endDay > 0) {
1417 endMode = DOW_GE_DOM_MODE;
1418 } else {
1419 endDay = -endDay;
1420 endMode = DOW_LE_DOM_MODE;
1421 }
1422 }
1423 if (endDayOfWeek > Calendar.SATURDAY) {
1424 throw new IllegalArgumentException(
1425 "Illegal end day of week " + endDayOfWeek);
1426 }
1427 }
1428 if (endMode == DOW_IN_MONTH_MODE) {
1429 if (endDay < -5 || endDay > 5) {
1430 throw new IllegalArgumentException(
1431 "Illegal end day of week in month " + endDay);
1432 }
1433 } else if (endDay < 1 || endDay > staticMonthLength[endMonth]) {
1434 throw new IllegalArgumentException(
1435 "Illegal end day " + endDay);
1436 }
1437 }
1438 }
1439
1440 /**
1441 * Make rules compatible to 1.1 FCS code. Since 1.1 FCS code only understands
1442 * day-of-week-in-month rules, we must modify other modes of rules to their
1443 * approximate equivalent in 1.1 FCS terms. This method is used when streaming
1444 * out objects of this class. After it is called, the rules will be modified,
1445 * with a possible loss of information. startMode and endMode will NOT be
1446 * altered, even though semantically they should be set to DOW_IN_MONTH_MODE,
1447 * since the rule modification is only intended to be temporary.
1448 */
1449 private void makeRulesCompatible()
1450 {
1451 switch (startMode) {
1452 case DOM_MODE:
1453 startDay = 1 + (startDay / 7);
1454 startDayOfWeek = Calendar.SUNDAY;
1455 break;
1456
1457 case DOW_GE_DOM_MODE:
1458 // A day-of-month of 1 is equivalent to DOW_IN_MONTH_MODE
1459 // that is, Sun>=1 == firstSun.
1460 if (startDay != 1) {
1461 startDay = 1 + (startDay / 7);
1462 }
1463 break;
1464
1465 case DOW_LE_DOM_MODE:
1466 if (startDay >= 30) {
1467 startDay = -1;
1468 } else {
1469 startDay = 1 + (startDay / 7);
1470 }
1471 break;
1472 }
1473
1474 switch (endMode) {
1475 case DOM_MODE:
1476 endDay = 1 + (endDay / 7);
1477 endDayOfWeek = Calendar.SUNDAY;
1478 break;
1479
1480 case DOW_GE_DOM_MODE:
1481 // A day-of-month of 1 is equivalent to DOW_IN_MONTH_MODE
1482 // that is, Sun>=1 == firstSun.
1483 if (endDay != 1) {
1484 endDay = 1 + (endDay / 7);
1485 }
1486 break;
1487
1488 case DOW_LE_DOM_MODE:
1489 if (endDay >= 30) {
1490 endDay = -1;
1491 } else {
1492 endDay = 1 + (endDay / 7);
1493 }
1494 break;
1495 }
1496
1497 /*
1498 * Adjust the start and end times to wall time. This works perfectly
1499 * well unless it pushes into the next or previous day. If that
1500 * happens, we attempt to adjust the day rule somewhat crudely. The day
1501 * rules have been forced into DOW_IN_MONTH mode already, so we change
1502 * the day of week to move forward or back by a day. It's possible to
1503 * make a more refined adjustment of the original rules first, but in
1504 * most cases this extra effort will go to waste once we adjust the day
1505 * rules anyway.
1506 */
1507 switch (startTimeMode) {
1508 case UTC_TIME:
1509 startTime += rawOffset;
1510 break;
1511 }
1512 while (startTime < 0) {
1513 startTime += millisPerDay;
1514 startDayOfWeek = 1 + ((startDayOfWeek+5) % 7); // Back 1 day
1515 }
1516 while (startTime >= millisPerDay) {
1517 startTime -= millisPerDay;
1518 startDayOfWeek = 1 + (startDayOfWeek % 7); // Forward 1 day
1519 }
1520
1521 switch (endTimeMode) {
1522 case UTC_TIME:
1523 endTime += rawOffset + dstSavings;
1524 break;
1525 case STANDARD_TIME:
1526 endTime += dstSavings;
1527 }
1528 while (endTime < 0) {
1529 endTime += millisPerDay;
1530 endDayOfWeek = 1 + ((endDayOfWeek+5) % 7); // Back 1 day
1531 }
1532 while (endTime >= millisPerDay) {
1533 endTime -= millisPerDay;
1534 endDayOfWeek = 1 + (endDayOfWeek % 7); // Forward 1 day
1535 }
1536 }
1537
1538 /**
1539 * Pack the start and end rules into an array of bytes. Only pack
1540 * data which is not preserved by makeRulesCompatible.
1541 */
1542 private byte[] packRules()
1543 {
1544 byte[] rules = new byte[6];
1545 rules[0] = (byte)startDay;
1546 rules[1] = (byte)startDayOfWeek;
1547 rules[2] = (byte)endDay;
1548 rules[3] = (byte)endDayOfWeek;
1549
1550 // As of serial version 2, include time modes
1551 rules[4] = (byte)startTimeMode;
1552 rules[5] = (byte)endTimeMode;
1553
1554 return rules;
1555 }
1556
1557 /**
1558 * Given an array of bytes produced by packRules, interpret them
1559 * as the start and end rules.
1560 */
1561 private void unpackRules(byte[] rules)
1562 {
1563 startDay = rules[0];
1564 startDayOfWeek = rules[1];
1565 endDay = rules[2];
1566 endDayOfWeek = rules[3];
1567
1568 // As of serial version 2, include time modes
1569 if (rules.length >= 6) {
1570 startTimeMode = rules[4];
1571 endTimeMode = rules[5];
1572 }
1573 }
1574
1575 /**
1576 * Pack the start and end times into an array of bytes. This is required
1577 * as of serial version 2.
1578 */
1579 private int[] packTimes() {
1580 int[] times = new int[2];
1581 times[0] = startTime;
1582 times[1] = endTime;
1583 return times;
1584 }
1585
1586 /**
1587 * Unpack the start and end times from an array of bytes. This is required
1588 * as of serial version 2.
1589 */
1590 private void unpackTimes(int[] times) {
1591 startTime = times[0];
1592 endTime = times[1];
1593 }
1594
1595 /**
1596 * Save the state of this object to a stream (i.e., serialize it).
1597 *
1598 * @serialData We write out two formats, a JDK 1.1 compatible format, using
1599 * <code>DOW_IN_MONTH_MODE</code> rules, in the required section, followed
1600 * by the full rules, in packed format, in the optional section. The
1601 * optional section will be ignored by JDK 1.1 code upon stream in.
1602 * <p> Contents of the optional section: The length of a byte array is
1603 * emitted (int); this is 4 as of this release. The byte array of the given
1604 * length is emitted. The contents of the byte array are the true values of
1605 * the fields <code>startDay</code>, <code>startDayOfWeek</code>,
1606 * <code>endDay</code>, and <code>endDayOfWeek</code>. The values of these
1607 * fields in the required section are approximate values suited to the rule
1608 * mode <code>DOW_IN_MONTH_MODE</code>, which is the only mode recognized by
1609 * JDK 1.1.
1610 */
1611 private void writeObject(ObjectOutputStream stream)
1612 throws IOException
1613 {
1614 // Construct a binary rule
1615 byte[] rules = packRules();
1616 int[] times = packTimes();
1617
1618 // Convert to 1.1 FCS rules. This step may cause us to lose information.
1619 makeRulesCompatible();
1620
1621 // Write out the 1.1 FCS rules
1622 stream.defaultWriteObject();
1623
1624 // Write out the binary rules in the optional data area of the stream.
1625 stream.writeInt(rules.length);
1626 stream.write(rules);
1627 stream.writeObject(times);
1628
1629 // Recover the original rules. This recovers the information lost
1630 // by makeRulesCompatible.
1631 unpackRules(rules);
1632 unpackTimes(times);
1633 }
1634
1635 /**
1636 * Reconstitute this object from a stream (i.e., deserialize it).
1637 *
1638 * We handle both JDK 1.1
1639 * binary formats and full formats with a packed byte array.
1640 */
1641 private void readObject(ObjectInputStream stream)
1642 throws IOException, ClassNotFoundException
1643 {
1644 stream.defaultReadObject();
1645
1646 if (serialVersionOnStream < 1) {
1647 // Fix a bug in the 1.1 SimpleTimeZone code -- namely,
1648 // startDayOfWeek and endDayOfWeek were usually uninitialized. We can't do
1649 // too much, so we assume SUNDAY, which actually works most of the time.
1650 if (startDayOfWeek == 0) {
1651 startDayOfWeek = Calendar.SUNDAY;
1652 }
1653 if (endDayOfWeek == 0) {
1654 endDayOfWeek = Calendar.SUNDAY;
1655 }
1656
1657 // The variables dstSavings, startMode, and endMode are post-1.1, so they
1658 // won't be present if we're reading from a 1.1 stream. Fix them up.
1659 startMode = endMode = DOW_IN_MONTH_MODE;
1660 dstSavings = millisPerHour;
1661 } else {
1662 // For 1.1.4, in addition to the 3 new instance variables, we also
1663 // store the actual rules (which have not be made compatible with 1.1)
1664 // in the optional area. Read them in here and parse them.
1665 int length = stream.readInt();
1666 byte[] rules = new byte[length];
1667 stream.readFully(rules);
1668 unpackRules(rules);
1669 }
1670
1671 if (serialVersionOnStream >= 2) {
1672 int[] times = (int[]) stream.readObject();
1673 unpackTimes(times);
1674 }
1675
1676 serialVersionOnStream = currentSerialVersion;
1677 }
1678}
1679