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