1 /* java.util.SimpleTimeZone
2    Copyright (C) 1998, 1999, 2000, 2003 Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19 02111-1307 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 
39 package java.util;
40 
41 /**
42  * This class represents a simple time zone offset and handles
43  * daylight savings.  It can only handle one daylight savings rule, so
44  * it can't represent historical changes.
45  *
46  * This object is tightly bound to the Gregorian calendar.  It assumes
47  * a regular seven days week, and the month lengths are that of the
48  * Gregorian Calendar.  It can only handle daylight savings for years
49  * lying in the AD era.
50  *
51  * @see Calendar
52  * @see GregorianCalender
53  * @author Jochen Hoenicke
54  */
55 public class SimpleTimeZone extends TimeZone
56 {
57   /**
58    * The raw time zone offset in milliseconds to GMT, ignoring
59    * daylight savings.
60    * @serial
61    */
62   private int rawOffset;
63 
64   /**
65    * True, if this timezone uses daylight savings, false otherwise.
66    * @serial
67    */
68   private boolean useDaylight;
69 
70   /**
71    * The daylight savings offset.  This is a positive offset in
72    * milliseconds with respect to standard time.  Typically this
73    * is one hour, but for some time zones this may be half an our.
74    * @serial
75    * @since JDK1.1.4
76    */
77   private int dstSavings = 60 * 60 * 1000;
78 
79   /**
80    * The first year, in which daylight savings rules applies.
81    * @serial
82    */
83   private int startYear;
84 
85   private static final int DOM_MODE = 1;
86   private static final int DOW_IN_MONTH_MODE = 2;
87   private static final int DOW_GE_DOM_MODE = 3;
88   private static final int DOW_LE_DOM_MODE = 4;
89 
90   /**
91    * The mode of the start rule. This takes one of the following values:
92    * <dl>
93    * <dt>DOM_MODE (1)</dt>
94    * <dd> startDay contains the day in month of the start date,
95    * startDayOfWeek is unused. </dd>
96    * <dt>DOW_IN_MONTH_MODE (2)</dt>
97    * <dd> The startDay gives the day of week in month, and
98    * startDayOfWeek the day of week.  For example startDay=2 and
99    * startDayOfWeek=Calender.SUNDAY specifies that the change is on
100    * the second sunday in that month.  You must make sure, that this
101    * day always exists (ie. don't specify the 5th sunday).
102    * </dd>
103    * <dt>DOW_GE_DOM_MODE (3)</dt>
104    * <dd> The start is on the first startDayOfWeek on or after
105    * startDay.  For example startDay=13 and
106    * startDayOfWeek=Calendar.FRIDAY specifies that the daylight
107    * savings start on the first FRIDAY on or after the 13th of that
108    * Month. Make sure that the change is always in the given month, or
109    * the result is undefined.
110    * </dd>
111    * <dt>DOW_LE_DOM_MONTH (4)</dt>
112    * <dd> The start is on the first startDayOfWeek on or before the
113    * startDay.  Make sure that the change is always in the given
114    * month, or the result is undefined.
115    </dd>
116    * </dl>
117    * @serial */
118   private int startMode;
119 
120   /**
121    * The month in which daylight savings start.  This is one of the
122    * constants Calendar.JANUARY, ..., Calendar.DECEMBER.
123    * @serial
124    */
125   private int startMonth;
126 
127   /**
128    * This variable can have different meanings.  See startMode for details
129    * @see #startMode;
130    * @serial
131    */
132   private int startDay;
133 
134   /**
135    * This variable specifies the day of week the change takes place.  If
136    * startMode == DOM_MODE, this is undefined.
137    * @serial
138    * @see #startMode;
139    */
140   private int startDayOfWeek;
141 
142   /**
143    * This variable specifies the time of change to daylight savings.
144    * This time is given in milliseconds after midnight local
145    * standard time.
146    * @serial
147    */
148   private int startTime;
149 
150   /**
151    * The month in which daylight savings ends.  This is one of the
152    * constants Calendar.JANUARY, ..., Calendar.DECEMBER.
153    * @serial
154    */
155   private int endMonth;
156 
157   /**
158    * This variable gives the mode for the end of daylight savings rule.
159    * It can take the same values as startMode.
160    * @serial
161    * @see #startMode
162    */
163   private int endMode;
164 
165   /**
166    * This variable can have different meanings.  See startMode for details
167    * @serial
168    * @see #startMode;
169    */
170   private int endDay;
171 
172   /**
173    * This variable specifies the day of week the change takes place.  If
174    * endMode == DOM_MODE, this is undefined.
175    * @serial
176    * @see #startMode;
177    */
178   private int endDayOfWeek;
179 
180   /**
181    * This variable specifies the time of change back to standard time.
182    * This time is given in milliseconds after midnight local
183    * standard time.
184    * @serial
185    */
186   private int endTime;
187 
188   /**
189    * This variable points to a deprecated array from JDK 1.1.  It is
190    * ignored in JDK 1.2 but streamed out for compatibility with JDK 1.1.
191    * The array contains the lengths of the months in the year and is
192    * assigned from a private static final field to avoid allocating
193    * the array for every instance of the object.
194    * Note that static final fields are not serialized.
195    * @serial
196    */
197   private byte[] monthLength = monthArr;
198   private static final byte[] monthArr =
199     {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
200 
201   /**
202    * The version of the serialized data on the stream.
203    * <dl>
204    * <dt>0 or not present on stream</dt>
205    * <dd> JDK 1.1.3 or earlier, only provides this fields:
206    * rawOffset, startDay, startDayOfWeek, startMonth, startTime,
207    * startYear, endDay, endDayOfWeek, endMonth, endTime
208    * </dd>
209    * <dd> JDK 1.1.4 or later. This includes three new fields, namely
210    * startMode, endMode and dstSavings.  And there is a optional section
211    * as described in writeObject.
212    * </dd>
213    *
214    * XXX - JDK 1.2 Beta 4 docu states 1.1.4, but my 1.1.5 has the old
215    * version.
216    *
217    * When streaming out this class it is always written in the latest
218    * version.
219    * @serial
220    * @since JDK1.1.4
221    */
222   private int serialVersionOnStream = 1;
223 
224   private static final long serialVersionUID = -403250971215465050L;
225 
226   /**
227    * Create a <code>SimpleTimeZone</code> with the given time offset
228    * from GMT and without daylight savings.
229    * @param rawOffset the time offset from GMT in milliseconds.
230    * @param id The identifier of this time zone.
231    */
SimpleTimeZone(int rawOffset, String id)232   public SimpleTimeZone(int rawOffset, String id)
233   {
234     this.rawOffset = rawOffset;
235     setID(id);
236     useDaylight = false;
237     startYear = 0;
238   }
239 
240   /**
241    * Create a <code>SimpleTimeZone</code> with the given time offset
242    * from GMT and with daylight savings.  The start/end parameters
243    * can have different meaning (replace WEEKDAY with a real day of
244    * week). Only the first two meanings were supported by earlier
245    * versions of jdk.
246    *
247    * <dl>
248    * <dt><code>day &gt; 0, dayOfWeek = Calendar.WEEKDAY</code></dt>
249    * <dd>The start/end of daylight savings is on the <code>day</code>-th
250    * <code>WEEKDAY</code> in the given month. </dd>
251    * <dt><code>day &lt; 0, dayOfWeek = Calendar.WEEKDAY</code></dt>
252    * <dd>The start/end of daylight savings is on the <code>-day</code>-th
253    * <code>WEEKDAY</code> counted from the <i>end</i> of the month. </dd>
254    * <dt><code>day &gt; 0, dayOfWeek = 0</code></dt>
255    * <dd>The start/end of daylight is on the <code>day</code>-th day of
256    * the month. </dd>
257    * <dt><code>day &gt; 0, dayOfWeek = -Calendar.WEEKDAY</code></dt>
258    * <dd>The start/end of daylight is on the first WEEKDAY on or after
259    * the <code>day</code>-th day of the month.  You must make sure that
260    * this day lies in the same month. </dd>
261    * <dt><code>day &lt; 0, dayOfWeek = -Calendar.WEEKDAY</code></dt>
262    * <dd>The start/end of daylight is on the first WEEKDAY on or
263    * <i>before</i> the <code>-day</code>-th day of the month.  You
264    * must make sure that this day lies in the same month. </dd>
265    * </dl>
266    *
267    * If you give a non existing month, a day that is zero, or too big,
268    * or a dayOfWeek that is too big,  the result is undefined.
269    *
270    * The start rule must have a different month than the end rule.
271    * This restriction shouldn't hurt for all possible time zones.
272    *
273    * @param rawOffset The time offset from GMT in milliseconds.
274    * @param id  The identifier of this time zone.
275    * @param startMonth The start month of daylight savings; use the
276    * constants in Calendar.
277    * @param startday A day in month or a day of week number, as
278    * described above.
279    * @param startDayOfWeek The start rule day of week; see above.
280    * @param startTime A time in millis in standard time.
281    * @param endMonth The end month of daylight savings; use the
282    * constants in Calendar.
283    * @param endday A day in month or a day of week number, as
284    * described above.
285    * @param endDayOfWeek The end rule day of week; see above.
286    * @param endTime A time in millis in standard time.  */
SimpleTimeZone(int rawOffset, String id, int startMonth, int startDayOfWeekInMonth, int startDayOfWeek, int startTime, int endMonth, int endDayOfWeekInMonth, int endDayOfWeek, int endTime)287   public SimpleTimeZone(int rawOffset, String id,
288 			int startMonth, int startDayOfWeekInMonth,
289 			int startDayOfWeek, int startTime,
290 			int endMonth, int endDayOfWeekInMonth,
291 			int endDayOfWeek, int endTime)
292   {
293     this.rawOffset = rawOffset;
294     setID(id);
295     useDaylight = true;
296 
297     setStartRule(startMonth, startDayOfWeekInMonth,
298 		 startDayOfWeek, startTime);
299     setEndRule(endMonth, endDayOfWeekInMonth, endDayOfWeek, endTime);
300     if (startMonth == endMonth)
301       throw new IllegalArgumentException
302 	("startMonth and endMonth must be different");
303     this.startYear = 0;
304   }
305 
306   /**
307    * This constructs a new SimpleTimeZone that supports a daylight savings
308    * rule.  The parameter are the same as for the constructor above, except
309    * there is the additional dstSavaings parameter.
310    *
311    * @param dstSavings the amount of savings for daylight savings
312    * time in milliseconds.  This must be positive.
313    */
SimpleTimeZone(int rawOffset, String id, int startMonth, int startDayOfWeekInMonth, int startDayOfWeek, int startTime, int endMonth, int endDayOfWeekInMonth, int endDayOfWeek, int endTime, int dstSavings)314   public SimpleTimeZone(int rawOffset, String id,
315 			int startMonth, int startDayOfWeekInMonth,
316 			int startDayOfWeek, int startTime,
317 			int endMonth, int endDayOfWeekInMonth,
318 			int endDayOfWeek, int endTime, int dstSavings)
319   {
320     this(rawOffset, id,
321 	 startMonth, startDayOfWeekInMonth, startDayOfWeek, startTime,
322 	 endMonth, endDayOfWeekInMonth, endDayOfWeek, endTime);
323 
324     this.dstSavings = dstSavings;
325   }
326 
327   /**
328    * Sets the first year, where daylight savings applies.  The daylight
329    * savings rule never apply for years in the BC era.  Note that this
330    * is gregorian calendar specific.
331    * @param year the start year.
332    */
setStartYear(int year)333   public void setStartYear(int year)
334   {
335     startYear = year;
336     useDaylight = true;
337   }
338 
339   /**
340    * Checks if the month, day, dayOfWeek arguments are in range and
341    * returns the mode of the rule.
342    * @param month the month parameter as in the constructor
343    * @param day the day parameter as in the constructor
344    * @param dayOfWeek the day of week parameter as in the constructor
345    * @return the mode of this rule see startMode.
346    * @exception IllegalArgumentException if parameters are out of range.
347    * @see #SimpleTimeZone(int, String, int, int, int, int, int, int, int, int)
348    * @see #startMode
349    */
checkRule(int month, int day, int dayOfWeek)350   private int checkRule(int month, int day, int dayOfWeek)
351   {
352     int daysInMonth = getDaysInMonth(month, 1);
353     if (dayOfWeek == 0)
354       {
355 	if (day <= 0 || day > daysInMonth)
356 	  throw new IllegalArgumentException("day out of range");
357 	return DOM_MODE;
358       }
359     else if (dayOfWeek > 0)
360       {
361 	if (Math.abs(day) > (daysInMonth + 6) / 7)
362 	  throw new IllegalArgumentException("dayOfWeekInMonth out of range");
363 	if (dayOfWeek > Calendar.SATURDAY)
364 	  throw new IllegalArgumentException("dayOfWeek out of range");
365 	return DOW_IN_MONTH_MODE;
366       }
367     else
368       {
369 	if (day == 0 || Math.abs(day) > daysInMonth)
370 	  throw new IllegalArgumentException("day out of range");
371 	if (dayOfWeek < -Calendar.SATURDAY)
372 	  throw new IllegalArgumentException("dayOfWeek out of range");
373 	if (day < 0)
374 	  return DOW_LE_DOM_MODE;
375 	else
376 	  return DOW_GE_DOM_MODE;
377       }
378   }
379 
380 
381   /**
382    * Sets the daylight savings start rule.  You must also set the
383    * end rule with <code>setEndRule</code> or the result of
384    * getOffset is undefined.  For the parameters see the ten-argument
385    * constructor above.
386    *
387    * @param month The month where daylight savings start, zero
388    * based.  You should use the constants in Calendar.
389    * @param day A day of month or day of week in month.
390    * @param dayOfWeek The day of week where daylight savings start.
391    * @param time The time in milliseconds standard time where daylight
392    * savings start.
393    * @see SimpleTimeZone
394    */
setStartRule(int month, int day, int dayOfWeek, int time)395   public void setStartRule(int month, int day, int dayOfWeek, int time)
396   {
397     this.startMode = checkRule(month, day, dayOfWeek);
398     this.startMonth = month;
399     // FIXME: XXX: JDK 1.2 allows negative values and has 2 new variations
400     // of this method.
401     this.startDay = Math.abs(day);
402     this.startDayOfWeek = Math.abs(dayOfWeek);
403     this.startTime = time;
404     useDaylight = true;
405   }
406 
407   /**
408    * Sets the daylight savings end rule.  You must also set the
409    * start rule with <code>setStartRule</code> or the result of
410    * getOffset is undefined. For the parameters see the ten-argument
411    * constructor above.
412    *
413    * @param rawOffset The time offset from GMT.
414    * @param id  The identifier of this time zone.
415    * @param month The end month of daylight savings.
416    * @param day A day in month, or a day of week in month.
417    * @param dayOfWeek A day of week, when daylight savings ends.
418    * @param time A time in millis in standard time.
419    * @see #setStartRule
420    */
setEndRule(int month, int day, int dayOfWeek, int time)421   public void setEndRule(int month, int day, int dayOfWeek, int time)
422   {
423     this.endMode = checkRule(month, day, dayOfWeek);
424     this.endMonth = month;
425     // FIXME: XXX: JDK 1.2 allows negative values and has 2 new variations
426     // of this method.
427     this.endDay = Math.abs(day);
428     this.endDayOfWeek = Math.abs(dayOfWeek);
429     this.endTime = time;
430     useDaylight = true;
431   }
432 
433   /**
434    * Gets the time zone offset, for current date, modified in case of
435    * daylight savings.  This is the offset to add to UTC to get the local
436    * time.
437    *
438    * In the standard JDK the results given by this method may result in
439    * inaccurate results at the end of February or the beginning of March.
440    * To avoid this, you should use Calendar instead:
441    * <code>offset = cal.get(Calendar.ZONE_OFFSET)
442    * + cal.get(Calendar.DST_OFFSET);</code>
443    *
444    * You could also use in
445    *
446    * This version doesn't suffer this inaccuracy.
447    *
448    * @param era the era of the given date
449    * @param year the year of the given date
450    * @param month the month of the given date, 0 for January.
451    * @param day the day of month
452    * @param dayOfWeek the day of week; this must be matching the
453    * other fields.
454    * @param millis the millis in the day (in local standard time)
455    * @return the time zone offset in milliseconds.  */
getOffset(int era, int year, int month, int day, int dayOfWeek, int millis)456   public int getOffset(int era, int year, int month,
457 		       int day, int dayOfWeek, int millis)
458   {
459     // This method is called by Calendar, so we mustn't use that class.
460     int daylightSavings = 0;
461     if (useDaylight && era == GregorianCalendar.AD && year >= startYear)
462       {
463 	// This does only work for Gregorian calendars :-(
464 	// This is mainly because setStartYear doesn't take an era.
465 
466 	boolean afterStart = !isBefore(year, month, day, dayOfWeek, millis,
467 				       startMode, startMonth,
468 				       startDay, startDayOfWeek, startTime);
469 	boolean beforeEnd = isBefore(year, month, day, dayOfWeek, millis,
470 				     endMode, endMonth,
471 				     endDay, endDayOfWeek, endTime);
472 
473 	if (startMonth < endMonth)
474 	  {
475 	    // use daylight savings, if the date is after the start of
476 	    // savings, and before the end of savings.
477 	    daylightSavings = afterStart && beforeEnd ? dstSavings : 0;
478 	  }
479 	else
480 	  {
481 	    // use daylight savings, if the date is before the end of
482 	    // savings, or after the start of savings.
483 	    daylightSavings = beforeEnd || afterStart ? dstSavings : 0;
484 	  }
485       }
486     return rawOffset + daylightSavings;
487   }
488 
489   /**
490    * Returns the time zone offset to GMT in milliseconds, ignoring
491    * day light savings.
492    * @return the time zone offset.
493    */
getRawOffset()494   public int getRawOffset()
495   {
496     return rawOffset;
497   }
498 
499   /**
500    * Sets the standard time zone offset to GMT.
501    * @param rawOffset The time offset from GMT in milliseconds.
502    */
setRawOffset(int rawOffset)503   public void setRawOffset(int rawOffset)
504   {
505     this.rawOffset = rawOffset;
506   }
507 
508   /**
509    * Gets the daylight savings offset.  This is a positive offset in
510    * milliseconds with respect to standard time.  Typically this
511    * is one hour, but for some time zones this may be half an our.
512    * @return the daylight savings offset in milliseconds.
513    *
514    * @since 1.2
515    */
getDSTSavings()516   public int getDSTSavings()
517   {
518     return dstSavings;
519   }
520 
521   /**
522    * Sets the daylight savings offset.  This is a positive offset in
523    * milliseconds with respect to standard time.
524    *
525    * @param dstSavings the daylight savings offset in milliseconds.
526    *
527    * @since 1.2
528    */
setDSTSavings(int dstSavings)529   public void setDSTSavings(int dstSavings)
530   {
531     if (dstSavings <= 0)
532       throw new IllegalArgumentException("illegal value for dstSavings");
533 
534     this.dstSavings = dstSavings;
535   }
536 
537   /**
538    * Returns if this time zone uses daylight savings time.
539    * @return true, if we use daylight savings time, false otherwise.
540    */
useDaylightTime()541   public boolean useDaylightTime()
542   {
543     return useDaylight;
544   }
545 
546   /**
547    * Returns the number of days in the given month.  It does always
548    * use the Gregorian leap year rule.
549    * @param month The month, zero based; use one of the Calendar constants.
550    * @param year  The year.
551    */
getDaysInMonth(int month, int year)552   private int getDaysInMonth(int month, int year)
553   {
554     // Most of this is copied from GregorianCalendar.getActualMaximum()
555     if (month == Calendar.FEBRUARY)
556       {
557 	return ((year & 3) == 0 && (year % 100 != 0 || year % 400 == 0))
558 	  ? 29 : 28;
559       }
560     else if (month < Calendar.AUGUST)
561         return 31 - (month & 1);
562     else
563       return 30 + (month & 1);
564   }
565 
566   /**
567    * Checks if the date given in calXXXX, is before the change between
568    * dst and standard time.
569    * @param calYear the year of the date to check (for leap day cheking).
570    * @param calMonth the month of the date to check.
571    * @param calDay the day of month of the date to check.
572    * @param calDayOfWeek the day of week of the date to check.
573    * @param calMillis the millis of day of the date to check (standard time).
574    * @param mode  the change mode; same semantic as startMode.
575    * @param month the change month; same semantic as startMonth.
576    * @param day   the change day; same semantic as startDay.
577    * @param dayOfWeek the change day of week;
578    * @param millis the change time in millis since midnight standard time.
579    * same semantic as startDayOfWeek.
580    * @return true, if cal is before the change, false if cal is on
581    * or after the change.
582    */
isBefore(int calYear, int calMonth, int calDayOfMonth, int calDayOfWeek, int calMillis, int mode, int month, int day, int dayOfWeek, int millis)583   private boolean isBefore(int calYear,
584 			   int calMonth, int calDayOfMonth, int calDayOfWeek,
585 			   int calMillis, int mode, int month,
586 			   int day, int dayOfWeek, int millis)
587   {
588 
589     // This method is called by Calendar, so we mustn't use that class.
590     // We have to do all calculations by hand.
591 
592     // check the months:
593 
594     // XXX - this is not correct:
595     // for the DOW_GE_DOM and DOW_LE_DOM modes the change date may
596     // be in a different month.
597     if (calMonth != month)
598       return calMonth < month;
599 
600     // check the day:
601     switch (mode)
602       {
603       case DOM_MODE:
604 	if (calDayOfMonth != day)
605 	  return calDayOfMonth < day;
606 	break;
607       case DOW_IN_MONTH_MODE:
608 	{
609 	  // This computes the day of month of the day of type
610 	  // "dayOfWeek" that lies in the same (sunday based) week as cal.
611 	  calDayOfMonth += (dayOfWeek - calDayOfWeek);
612 
613 	  // Now we convert it to 7 based number (to get a one based offset
614 	  // after dividing by 7).  If we count from the end of the
615 	  // month, we get want a -7 based number counting the days from
616 	  // the end:
617 
618 	  if (day < 0)
619 	    calDayOfMonth -= getDaysInMonth(calMonth, calYear) + 7;
620 	  else
621 	    calDayOfMonth += 6;
622 
623 	  //  day > 0                    day < 0
624 	  //  S  M  T  W  T  F  S        S  M  T  W  T  F  S
625 	  //     7  8  9 10 11 12         -36-35-34-33-32-31
626 	  // 13 14 15 16 17 18 19      -30-29-28-27-26-25-24
627 	  // 20 21 22 23 24 25 26      -23-22-21-20-19-18-17
628 	  // 27 28 29 30 31 32 33      -16-15-14-13-12-11-10
629 	  // 34 35 36                   -9 -8 -7
630 
631 	  // Now we calculate the day of week in month:
632 	  int week = calDayOfMonth / 7;
633 	  //  day > 0                    day < 0
634 	  //  S  M  T  W  T  F  S        S  M  T  W  T  F  S
635 	  //     1  1  1  1  1  1          -5 -5 -4 -4 -4 -4
636 	  //  1  2  2  2  2  2  2       -4 -4 -4 -3 -3 -3 -3
637 	  //  2  3  3  3  3  3  3       -3 -3 -3 -2 -2 -2 -2
638 	  //  3  4  4  4  4  4  4       -2 -2 -2 -1 -1 -1 -1
639 	  //  4  5  5                   -1 -1 -1
640 
641 	  if (week != day)
642 	    return week < day;
643 
644 	  if (calDayOfWeek != dayOfWeek)
645 	    return calDayOfWeek < dayOfWeek;
646 
647 	  // daylight savings starts/ends  on the given day.
648 	  break;
649 	}
650 
651       case DOW_LE_DOM_MODE:
652 	// The greatest sunday before or equal December, 12
653 	// is the same as smallest sunday after or equal December, 6.
654 	day -= 6;
655 
656       case DOW_GE_DOM_MODE:
657 
658 	// Calculate the day of month of the day of type
659 	// "dayOfWeek" that lies before (or on) the given date.
660 	calDayOfMonth -= (calDayOfWeek < dayOfWeek ? 7 : 0)
661 	  + calDayOfWeek - dayOfWeek;
662 	if (calDayOfMonth < day)
663 	  return true;
664 	if (calDayOfWeek != dayOfWeek || calDayOfMonth >= day + 7)
665 	  return false;
666 	// now we have the same day
667 	break;
668       }
669     // the millis decides:
670     return (calMillis < millis);
671   }
672 
673   /**
674    * Determines if the given date is in daylight savings time.
675    * @return true, if it is in daylight savings time, false otherwise.
676    */
inDaylightTime(Date date)677   public boolean inDaylightTime(Date date)
678   {
679     Calendar cal = Calendar.getInstance(this);
680     cal.setTime(date);
681     return (cal.get(Calendar.DST_OFFSET) != 0);
682   }
683 
684   /**
685    * Generates the hashCode for the SimpleDateFormat object.  It is
686    * the rawOffset, possibly, if useDaylightSavings is true, xored
687    * with startYear, startMonth, startDayOfWeekInMonth, ..., endTime.
688    */
hashCode()689   public synchronized int hashCode()
690   {
691     return rawOffset ^
692       (useDaylight ?
693        startMonth ^ startDay ^ startDayOfWeek ^ startTime
694        ^ endMonth ^ endDay ^ endDayOfWeek ^ endTime : 0);
695   }
696 
equals(Object o)697   public synchronized boolean equals(Object o)
698   {
699     if (this == o)
700       return true;
701     if (!(o instanceof SimpleTimeZone))
702       return false;
703     SimpleTimeZone zone = (SimpleTimeZone) o;
704     if (zone.hashCode() != hashCode()
705 	|| !getID().equals(zone.getID())
706 	|| rawOffset != zone.rawOffset || useDaylight != zone.useDaylight)
707       return false;
708     if (!useDaylight)
709       return true;
710     return (startYear == zone.startYear
711 	    && startMonth == zone.startMonth
712 	    && startDay == zone.startDay
713 	    && startDayOfWeek == zone.startDayOfWeek
714 	    && startTime == zone.startTime
715 	    && endMonth == zone.endMonth
716 	    && endDay == zone.endDay
717 	    && endDayOfWeek == zone.endDayOfWeek
718 	    && endTime == zone.endTime);
719   }
720 
721   /**
722    * Test if the other time zone uses the same rule and only
723    * possibly differs in ID.  This implementation for this particular
724    * class will return true if the other object is a SimpleTimeZone,
725    * the raw offsets and useDaylight are identical and if useDaylight
726    * is true, also the start and end datas are identical.
727    * @return true if this zone uses the same rule.
728    */
hasSameRules(TimeZone other)729   public boolean hasSameRules(TimeZone other)
730   {
731     if (this == other)
732       return true;
733     if (!(other instanceof SimpleTimeZone))
734       return false;
735     SimpleTimeZone zone = (SimpleTimeZone) other;
736     if (zone.hashCode() != hashCode()
737 	|| rawOffset != zone.rawOffset || useDaylight != zone.useDaylight)
738       return false;
739     if (!useDaylight)
740       return true;
741     return (startYear == zone.startYear
742 	    && startMonth == zone.startMonth
743 	    && startDay == zone.startDay
744 	    && startDayOfWeek == zone.startDayOfWeek
745 	    && startTime == zone.startTime
746 	    && endMonth == zone.endMonth
747 	    && endDay == zone.endDay
748 	    && endDayOfWeek == zone.endDayOfWeek && endTime == zone.endTime);
749   }
750 
751   /**
752    * Returns a string representation of this SimpleTimeZone object.
753    * @return a string representation of this SimpleTimeZone object.
754    */
toString()755   public String toString()
756   {
757     // the test for useDaylight is an incompatibility to jdk1.2, but
758     // I think this shouldn't hurt.
759     return getClass().getName() + "["
760       + "id=" + getID()
761       + ",offset=" + rawOffset
762       + ",dstSavings=" + dstSavings
763       + ",useDaylight=" + useDaylight
764       + (useDaylight ?
765 	 ",startYear=" + startYear
766 	 + ",startMode=" + startMode
767 	 + ",startMonth=" + startMonth
768 	 + ",startDay=" + startDay
769 	 + ",startDayOfWeek=" + startDayOfWeek
770 	 + ",startTime=" + startTime
771 	 + ",endMode=" + endMode
772 	 + ",endMonth=" + endMonth
773 	 + ",endDay=" + endDay
774 	 + ",endDayOfWeek=" + endDayOfWeek
775 	 + ",endTime=" + endTime : "") + "]";
776   }
777 
778   /**
779    * Reads a serialized simple time zone from stream.
780    * @see #writeObject
781    */
readObject(java.io.ObjectInputStream input)782   private void readObject(java.io.ObjectInputStream input)
783     throws java.io.IOException, ClassNotFoundException
784   {
785     input.defaultReadObject();
786     if (serialVersionOnStream == 0)
787       {
788 	// initialize the new fields to default values.
789 	dstSavings = 60 * 60 * 1000;
790 	endMode = DOW_IN_MONTH_MODE;
791 	startMode = DOW_IN_MONTH_MODE;
792 	serialVersionOnStream = 1;
793       }
794     else
795       {
796 	int length = input.readInt();
797 	byte[] byteArray = new byte[length];
798 	input.read(byteArray, 0, length);
799 	if (length >= 4)
800 	  {
801 	    // Lets hope that Sun does extensions to the serialized
802 	    // form in a sane manner.
803 	    startDay = byteArray[0];
804 	    startDayOfWeek = byteArray[1];
805 	    endDay = byteArray[2];
806 	    endDayOfWeek = byteArray[3];
807 	  }
808       }
809   }
810 
811   /**
812    * Serializes this object to a stream.  @serialdata The object is
813    * first written in the old JDK 1.1 format, so that it can be read
814    * by by the old classes.  This means, that the
815    * <code>start/endDay(OfWeek)</code>-Fields are written in the
816    * DOW_IN_MONTH_MODE rule, since this was the only supported rule
817    * in 1.1.
818    *
819    * In the optional section, we write first the length of an byte
820    * array as int and afterwards the byte array itself.  The byte
821    * array contains in this release four elements, namely the real
822    * startDay, startDayOfWeek endDay, endDayOfWeek in that Order.
823    * These fields are needed, because for compatibility reasons only
824    * approximative values are written to the required section, as
825    * described above.
826    */
writeObject(java.io.ObjectOutputStream output)827   private void writeObject(java.io.ObjectOutputStream output)
828     throws java.io.IOException
829   {
830     byte[] byteArray = new byte[]
831     {
832       (byte) startDay, (byte) startDayOfWeek,
833 	(byte) endDay, (byte) endDayOfWeek};
834 
835     /* calculate the approximation for JDK 1.1 */
836     switch (startMode)
837       {
838       case DOM_MODE:
839 	startDayOfWeek = Calendar.SUNDAY;	// random day of week
840 	// fall through
841       case DOW_GE_DOM_MODE:
842       case DOW_LE_DOM_MODE:
843 	startDay = (startDay + 6) / 7;
844       }
845     switch (endMode)
846       {
847       case DOM_MODE:
848 	endDayOfWeek = Calendar.SUNDAY;
849 	// fall through
850       case DOW_GE_DOM_MODE:
851       case DOW_LE_DOM_MODE:
852 	endDay = (endDay + 6) / 7;
853       }
854 
855     // the required part:
856     output.defaultWriteObject();
857     // the optional part:
858     output.writeInt(byteArray.length);
859     output.write(byteArray, 0, byteArray.length);
860   }
861 }
862