Source for java.util.SimpleTimeZone

   1: /* java.util.SimpleTimeZone
   2:    Copyright (C) 1998, 1999, 2000, 2003, 2004, 2005  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., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 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: /**
  43:  * This class represents a simple time zone offset and handles
  44:  * daylight savings.  It can only handle one daylight savings rule, so
  45:  * it can't represent historical changes.
  46:  *
  47:  * This object is tightly bound to the Gregorian calendar.  It assumes
  48:  * a regular seven days week, and the month lengths are that of the
  49:  * Gregorian Calendar.  It can only handle daylight savings for years
  50:  * lying in the AD era.
  51:  *
  52:  * @see Calendar
  53:  * @see GregorianCalendar
  54:  * @author Jochen Hoenicke
  55:  */
  56: public class SimpleTimeZone extends TimeZone
  57: {
  58:   /**
  59:    * The raw time zone offset in milliseconds to GMT, ignoring
  60:    * daylight savings.
  61:    * @serial
  62:    */
  63:   private int rawOffset;
  64: 
  65:   /**
  66:    * True, if this timezone uses daylight savings, false otherwise.
  67:    * @serial
  68:    */
  69:   private boolean useDaylight;
  70: 
  71:   /**
  72:    * The daylight savings offset.  This is a positive offset in
  73:    * milliseconds with respect to standard time.  Typically this
  74:    * is one hour, but for some time zones this may be half an hour.
  75:    * @serial
  76:    * @since JDK1.1.4
  77:    */
  78:   private int dstSavings = 60 * 60 * 1000;
  79: 
  80:   /**
  81:    * The first year, in which daylight savings rules applies.
  82:    * @serial
  83:    */
  84:   private int startYear;
  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:    * This variable specifies the mode that startTime is specified in.  By
 152:    * default it is WALL_TIME, but can also be STANDARD_TIME or UTC_TIME.  For
 153:    * startTime, STANDARD_TIME and WALL_TIME are equivalent.
 154:    * @serial
 155:    */
 156:   private int startTimeMode = WALL_TIME;
 157: 
 158:   /**
 159:    * The month in which daylight savings ends.  This is one of the
 160:    * constants Calendar.JANUARY, ..., Calendar.DECEMBER.
 161:    * @serial
 162:    */
 163:   private int endMonth;
 164: 
 165:   /**
 166:    * This variable gives the mode for the end of daylight savings rule.
 167:    * It can take the same values as startMode.
 168:    * @serial
 169:    * @see #startMode
 170:    */
 171:   private int endMode;
 172: 
 173:   /**
 174:    * This variable can have different meanings.  See startMode for details
 175:    * @serial
 176:    * @see #startMode
 177:    */
 178:   private int endDay;
 179: 
 180:   /**
 181:    * This variable specifies the day of week the change takes place.  If
 182:    * endMode == DOM_MODE, this is undefined.
 183:    * @serial
 184:    * @see #startMode
 185:    */
 186:   private int endDayOfWeek;
 187: 
 188:   /**
 189:    * This variable specifies the time of change back to standard time.
 190:    * This time is given in milliseconds after midnight local
 191:    * standard time.
 192:    * @serial
 193:    */
 194:   private int endTime;
 195: 
 196:   /**
 197:    * This variable specifies the mode that endTime is specified in.  By
 198:    * default it is WALL_TIME, but can also be STANDARD_TIME or UTC_TIME.
 199:    * @serial
 200:    */
 201:   private int endTimeMode = WALL_TIME;
 202: 
 203:   /**
 204:    * This variable points to a deprecated array from JDK 1.1.  It is
 205:    * ignored in JDK 1.2 but streamed out for compatibility with JDK 1.1.
 206:    * The array contains the lengths of the months in the year and is
 207:    * assigned from a private static final field to avoid allocating
 208:    * the array for every instance of the object.
 209:    * Note that static final fields are not serialized.
 210:    * @serial
 211:    */
 212:   private byte[] monthLength = monthArr;
 213:   private static final byte[] monthArr = 
 214:                                          {
 215:                                            31, 28, 31, 30, 31, 30, 31, 31, 30,
 216:                                            31, 30, 31
 217:                                          };
 218: 
 219:   /**
 220:    * The version of the serialized data on the stream.
 221:    * <dl>
 222:    * <dt>0 or not present on stream</dt>
 223:    * <dd> JDK 1.1.3 or earlier, only provides this fields:
 224:    * rawOffset, startDay, startDayOfWeek, startMonth, startTime,
 225:    * startYear, endDay, endDayOfWeek, endMonth, endTime
 226:    * </dd>
 227:    * <dd> JDK 1.1.4 or later. This includes three new fields, namely
 228:    * startMode, endMode and dstSavings.  And there is a optional section
 229:    * as described in writeObject.
 230:    * </dd>
 231:    * </dl>
 232:    *
 233:    * XXX - JDK 1.2 Beta 4 docu states 1.1.4, but my 1.1.5 has the old
 234:    * version.
 235:    *
 236:    * When streaming out this class it is always written in the latest
 237:    * version.
 238:    * @serial
 239:    * @since JDK1.1.4
 240:    */
 241:   private int serialVersionOnStream = 2;
 242:   private static final long serialVersionUID = -403250971215465050L;
 243: 
 244:   /**
 245:    * Constant to indicate that start and end times are specified in standard
 246:    * time, without adjusting for daylight savings.
 247:    */
 248:   public static final int STANDARD_TIME = 1;
 249: 
 250:   /**
 251:    * Constant to indicate that start and end times are specified in wall
 252:    * time, adjusting for daylight savings.  This is the default.
 253:    */
 254:   public static final int WALL_TIME = 0;
 255: 
 256:   /**
 257:    * Constant to indicate that start and end times are specified in UTC.
 258:    */
 259:   public static final int UTC_TIME = 2;
 260: 
 261:   /**
 262:    * Create a <code>SimpleTimeZone</code> with the given time offset
 263:    * from GMT and without daylight savings.
 264:    * @param rawOffset the time offset from GMT in milliseconds.
 265:    * @param id The identifier of this time zone.
 266:    */
 267:   public SimpleTimeZone(int rawOffset, String id)
 268:   {
 269:     this.rawOffset = rawOffset;
 270:     setID(id);
 271:     useDaylight = false;
 272:     startYear = 0;
 273:   }
 274: 
 275:   /**
 276:    * Create a <code>SimpleTimeZone</code> with the given time offset
 277:    * from GMT and with daylight savings.  The start/end parameters
 278:    * can have different meaning (replace WEEKDAY with a real day of
 279:    * week). Only the first two meanings were supported by earlier
 280:    * versions of jdk.
 281:    *
 282:    * <dl>
 283:    * <dt><code>day &gt; 0, dayOfWeek = Calendar.WEEKDAY</code></dt>
 284:    * <dd>The start/end of daylight savings is on the <code>day</code>-th
 285:    * <code>WEEKDAY</code> in the given month. </dd>
 286:    * <dt><code>day &lt; 0, dayOfWeek = Calendar.WEEKDAY</code></dt>
 287:    * <dd>The start/end of daylight savings is on the <code>-day</code>-th
 288:    * <code>WEEKDAY</code> counted from the <i>end</i> of the month. </dd>
 289:    * <dt><code>day &gt; 0, dayOfWeek = 0</code></dt>
 290:    * <dd>The start/end of daylight is on the <code>day</code>-th day of
 291:    * the month. </dd>
 292:    * <dt><code>day &gt; 0, dayOfWeek = -Calendar.WEEKDAY</code></dt>
 293:    * <dd>The start/end of daylight is on the first WEEKDAY on or after
 294:    * the <code>day</code>-th day of the month.  You must make sure that
 295:    * this day lies in the same month. </dd>
 296:    * <dt><code>day &lt; 0, dayOfWeek = -Calendar.WEEKDAY</code></dt>
 297:    * <dd>The start/end of daylight is on the first WEEKDAY on or
 298:    * <i>before</i> the <code>-day</code>-th day of the month.  You
 299:    * must make sure that this day lies in the same month. </dd>
 300:    * </dl>
 301:    *
 302:    * If you give a non existing month, a day that is zero, or too big,
 303:    * or a dayOfWeek that is too big,  the result is undefined.
 304:    *
 305:    * The start rule must have a different month than the end rule.
 306:    * This restriction shouldn't hurt for all possible time zones.
 307:    *
 308:    * @param rawOffset The time offset from GMT in milliseconds.
 309:    * @param id  The identifier of this time zone.
 310:    * @param startMonth The start month of daylight savings; use the
 311:    * constants in Calendar.
 312:    * @param startDayOfWeekInMonth A day in month or a day of week number, as
 313:    * described above.
 314:    * @param startDayOfWeek The start rule day of week; see above.
 315:    * @param startTime A time in millis in standard time.
 316:    * @param endMonth The end month of daylight savings; use the
 317:    * constants in Calendar.
 318:    * @param endDayOfWeekInMonth A day in month or a day of week number, as
 319:    * described above.
 320:    * @param endDayOfWeek The end rule day of week; see above.
 321:    * @param endTime A time in millis in standard time.
 322:    * @throws IllegalArgumentException if parameters are invalid or out of
 323:    * range.
 324:    */
 325:   public SimpleTimeZone(int rawOffset, String id, int startMonth,
 326:                         int startDayOfWeekInMonth, int startDayOfWeek,
 327:                         int startTime, int endMonth, int endDayOfWeekInMonth,
 328:                         int endDayOfWeek, int endTime)
 329:   {
 330:     this.rawOffset = rawOffset;
 331:     setID(id);
 332:     useDaylight = true;
 333: 
 334:     setStartRule(startMonth, startDayOfWeekInMonth, startDayOfWeek, startTime);
 335:     setEndRule(endMonth, endDayOfWeekInMonth, endDayOfWeek, endTime);
 336:     if (startMonth == endMonth)
 337:       throw new IllegalArgumentException("startMonth and endMonth must be different");
 338:     this.startYear = 0;
 339:   }
 340: 
 341:   /**
 342:    * This constructs a new SimpleTimeZone that supports a daylight savings
 343:    * rule.  The parameter are the same as for the constructor above, except
 344:    * there is the additional dstSavaings parameter.
 345:    *
 346:    * @param dstSavings the amount of savings for daylight savings
 347:    * time in milliseconds.  This must be positive.
 348:    * @since 1.2
 349:    */
 350:   public SimpleTimeZone(int rawOffset, String id, int startMonth,
 351:                         int startDayOfWeekInMonth, int startDayOfWeek,
 352:                         int startTime, int endMonth, int endDayOfWeekInMonth,
 353:                         int endDayOfWeek, int endTime, int dstSavings)
 354:   {
 355:     this(rawOffset, id, startMonth, startDayOfWeekInMonth, startDayOfWeek,
 356:          startTime, endMonth, endDayOfWeekInMonth, endDayOfWeek, endTime);
 357: 
 358:     this.dstSavings = dstSavings;
 359:   }
 360: 
 361:   /**
 362:    * This constructs a new SimpleTimeZone that supports a daylight savings
 363:    * rule.  The parameter are the same as for the constructor above, except
 364:    * there are the additional startTimeMode, endTimeMode, and dstSavings
 365:    * parameters.
 366:    *
 367:    * @param startTimeMode the mode that start times are specified in.  One of
 368:    * WALL_TIME, STANDARD_TIME, or UTC_TIME.
 369:    * @param endTimeMode the mode that end times are specified in.  One of
 370:    * WALL_TIME, STANDARD_TIME, or UTC_TIME.
 371:    * @param dstSavings the amount of savings for daylight savings
 372:    * time in milliseconds.  This must be positive.
 373:    * @throws IllegalArgumentException if parameters are invalid or out of
 374:    * range.
 375:    * @since 1.4
 376:    */
 377:   public SimpleTimeZone(int rawOffset, String id, int startMonth,
 378:                         int startDayOfWeekInMonth, int startDayOfWeek,
 379:                         int startTime, int startTimeMode, int endMonth,
 380:                         int endDayOfWeekInMonth, int endDayOfWeek,
 381:                         int endTime, int endTimeMode, int dstSavings)
 382:   {
 383:     this.rawOffset = rawOffset;
 384:     setID(id);
 385:     useDaylight = true;
 386: 
 387:     if (startTimeMode < WALL_TIME || startTimeMode > UTC_TIME)
 388:       throw new IllegalArgumentException("startTimeMode must be one of WALL_TIME, STANDARD_TIME, or UTC_TIME");
 389:     if (endTimeMode < WALL_TIME || endTimeMode > UTC_TIME)
 390:       throw new IllegalArgumentException("endTimeMode must be one of WALL_TIME, STANDARD_TIME, or UTC_TIME");
 391:     this.startTimeMode = startTimeMode;
 392:     this.endTimeMode = endTimeMode;
 393: 
 394:     setStartRule(startMonth, startDayOfWeekInMonth, startDayOfWeek, startTime);
 395:     setEndRule(endMonth, endDayOfWeekInMonth, endDayOfWeek, endTime);
 396:     if (startMonth == endMonth)
 397:       throw new IllegalArgumentException("startMonth and endMonth must be different");
 398:     this.startYear = 0;
 399: 
 400:     this.dstSavings = dstSavings;
 401:   }
 402: 
 403:   /**
 404:    * Sets the first year, where daylight savings applies.  The daylight
 405:    * savings rule never apply for years in the BC era.  Note that this
 406:    * is gregorian calendar specific.
 407:    * @param year the start year.
 408:    */
 409:   public void setStartYear(int year)
 410:   {
 411:     startYear = year;
 412:     useDaylight = true;
 413:   }
 414: 
 415:   /**
 416:    * Checks if the month, day, dayOfWeek arguments are in range and
 417:    * returns the mode of the rule.
 418:    * @param month the month parameter as in the constructor
 419:    * @param day the day parameter as in the constructor
 420:    * @param dayOfWeek the day of week parameter as in the constructor
 421:    * @return the mode of this rule see startMode.
 422:    * @exception IllegalArgumentException if parameters are out of range.
 423:    * @see #SimpleTimeZone(int, String, int, int, int, int, int, int, int, int)
 424:    * @see #startMode
 425:    */
 426:   private int checkRule(int month, int day, int dayOfWeek)
 427:   {
 428:     if (month < 0 || month > 11)
 429:       throw new IllegalArgumentException("month out of range");
 430: 
 431:     int daysInMonth = getDaysInMonth(month, 1);
 432:     if (dayOfWeek == 0)
 433:       {
 434:     if (day <= 0 || day > daysInMonth)
 435:       throw new IllegalArgumentException("day out of range");
 436:     return DOM_MODE;
 437:       }
 438:     else if (dayOfWeek > 0)
 439:       {
 440:     if (Math.abs(day) > (daysInMonth + 6) / 7)
 441:       throw new IllegalArgumentException("dayOfWeekInMonth out of range");
 442:     if (dayOfWeek > Calendar.SATURDAY)
 443:       throw new IllegalArgumentException("dayOfWeek out of range");
 444:     return DOW_IN_MONTH_MODE;
 445:       }
 446:     else
 447:       {
 448:     if (day == 0 || Math.abs(day) > daysInMonth)
 449:       throw new IllegalArgumentException("day out of range");
 450:     if (dayOfWeek < -Calendar.SATURDAY)
 451:       throw new IllegalArgumentException("dayOfWeek out of range");
 452:     if (day < 0)
 453:       return DOW_LE_DOM_MODE;
 454:     else
 455:       return DOW_GE_DOM_MODE;
 456:       }
 457:   }
 458: 
 459:   /**
 460:    * Sets the daylight savings start rule.  You must also set the
 461:    * end rule with <code>setEndRule</code> or the result of
 462:    * getOffset is undefined.  For the parameters see the ten-argument
 463:    * constructor above.
 464:    *
 465:    * @param month The month where daylight savings start, zero
 466:    * based.  You should use the constants in Calendar.
 467:    * @param day A day of month or day of week in month.
 468:    * @param dayOfWeek The day of week where daylight savings start.
 469:    * @param time The time in milliseconds standard time where daylight
 470:    * savings start.
 471:    * @exception IllegalArgumentException if parameters are out of range.
 472:    * @see SimpleTimeZone
 473:    */
 474:   public void setStartRule(int month, int day, int dayOfWeek, int time)
 475:   {
 476:     this.startMode = checkRule(month, day, dayOfWeek);
 477:     this.startMonth = month;
 478:     this.startDay = day;
 479:     this.startDayOfWeek = Math.abs(dayOfWeek);
 480:     if (this.startTimeMode == WALL_TIME || this.startTimeMode == STANDARD_TIME)
 481:       this.startTime = time;
 482:     else
 483:       // Convert from UTC to STANDARD
 484:       this.startTime = time + this.rawOffset;
 485:     useDaylight = true;
 486:   }
 487: 
 488:   /**
 489:    * Sets the daylight savings start rule.  You must also set the
 490:    * end rule with <code>setEndRule</code> or the result of
 491:    * getOffset is undefined.  For the parameters see the ten-argument
 492:    * constructor above.
 493:    *
 494:    * Note that this API isn't incredibly well specified.  It appears that the
 495:    * after flag must override the parameters, since normally, the day and
 496:    * dayofweek can select this.  I.e., if day < 0 and dayOfWeek < 0, on or
 497:    * before mode is chosen.  But if after == true, this implementation
 498:    * overrides the signs of the other arguments.  And if dayOfWeek == 0, it
 499:    * falls back to the behavior in the other APIs.  I guess this should be
 500:    * checked against Sun's implementation.
 501:    *
 502:    * @param month The month where daylight savings start, zero
 503:    * based.  You should use the constants in Calendar.
 504:    * @param day A day of month or day of week in month.
 505:    * @param dayOfWeek The day of week where daylight savings start.
 506:    * @param time The time in milliseconds standard time where daylight
 507:    * savings start.
 508:    * @param after If true, day and dayOfWeek specify first day of week on or
 509:    * after day, else first day of week on or before.
 510:    * @since 1.2
 511:    * @see SimpleTimeZone
 512:    */
 513:   public void setStartRule(int month, int day, int dayOfWeek, int time,
 514:                            boolean after)
 515:   {
 516:     // FIXME: XXX: Validate that checkRule and offset processing work with on
 517:     // or before mode.
 518:     this.startDay = after ? Math.abs(day) : -Math.abs(day);
 519:     this.startDayOfWeek = after ? Math.abs(dayOfWeek) : -Math.abs(dayOfWeek);
 520:     this.startMode = (dayOfWeek != 0)
 521:                      ? (after ? DOW_GE_DOM_MODE : DOW_LE_DOM_MODE)
 522:                      : checkRule(month, day, dayOfWeek);
 523:     this.startDay = Math.abs(this.startDay);
 524:     this.startDayOfWeek = Math.abs(this.startDayOfWeek);
 525: 
 526:     this.startMonth = month;
 527: 
 528:     if (this.startTimeMode == WALL_TIME || this.startTimeMode == STANDARD_TIME)
 529:       this.startTime = time;
 530:     else
 531:       // Convert from UTC to STANDARD
 532:       this.startTime = time + this.rawOffset;
 533:     useDaylight = true;
 534:   }
 535: 
 536:   /**
 537:    * Sets the daylight savings start rule.  You must also set the
 538:    * end rule with <code>setEndRule</code> or the result of
 539:    * getOffset is undefined.  For the parameters see the ten-argument
 540:    * constructor above.
 541:    *
 542:    * @param month The month where daylight savings start, zero
 543:    * based.  You should use the constants in Calendar.
 544:    * @param day A day of month or day of week in month.
 545:    * @param time The time in milliseconds standard time where daylight
 546:    * savings start.
 547:    * @see SimpleTimeZone
 548:    * @since 1.2
 549:    */
 550:   public void setStartRule(int month, int day, int time)
 551:   {
 552:     setStartRule(month, day, 0, time);
 553:   }
 554: 
 555:   /**
 556:    * Sets the daylight savings end rule.  You must also set the
 557:    * start rule with <code>setStartRule</code> or the result of
 558:    * getOffset is undefined. For the parameters see the ten-argument
 559:    * constructor above.
 560:    *
 561:    * @param month The end month of daylight savings.
 562:    * @param day A day in month, or a day of week in month.
 563:    * @param dayOfWeek A day of week, when daylight savings ends.
 564:    * @param time A time in millis in standard time.
 565:    * @see #setStartRule(int, int, int, int)
 566:    */
 567:   public void setEndRule(int month, int day, int dayOfWeek, int time)
 568:   {
 569:     this.endMode = checkRule(month, day, dayOfWeek);
 570:     this.endMonth = month;
 571:     this.endDay = day;
 572:     this.endDayOfWeek = Math.abs(dayOfWeek);
 573:     if (this.endTimeMode == WALL_TIME)
 574:       this.endTime = time;
 575:     else if (this.endTimeMode == STANDARD_TIME)
 576:       // Convert from STANDARD to DST
 577:       this.endTime = time + this.dstSavings;
 578:     else
 579:       // Convert from UTC to DST
 580:       this.endTime = time + this.rawOffset + this.dstSavings;
 581:     useDaylight = true;
 582:   }
 583: 
 584:   /**
 585:    * Sets the daylight savings end rule.  You must also set the
 586:    * start rule with <code>setStartRule</code> or the result of
 587:    * getOffset is undefined. For the parameters see the ten-argument
 588:    * constructor above.
 589:    *
 590:    * Note that this API isn't incredibly well specified.  It appears that the
 591:    * after flag must override the parameters, since normally, the day and
 592:    * dayofweek can select this.  I.e., if day < 0 and dayOfWeek < 0, on or
 593:    * before mode is chosen.  But if after == true, this implementation
 594:    * overrides the signs of the other arguments.  And if dayOfWeek == 0, it
 595:    * falls back to the behavior in the other APIs.  I guess this should be
 596:    * checked against Sun's implementation.
 597:    *
 598:    * @param month The end month of daylight savings.
 599:    * @param day A day in month, or a day of week in month.
 600:    * @param dayOfWeek A day of week, when daylight savings ends.
 601:    * @param time A time in millis in standard time.
 602:    * @param after If true, day and dayOfWeek specify first day of week on or
 603:    * after day, else first day of week on or before.
 604:    * @since 1.2
 605:    * @see #setStartRule(int, int, int, int, boolean)
 606:    */
 607:   public void setEndRule(int month, int day, int dayOfWeek, int time,
 608:                          boolean after)
 609:   {
 610:     // FIXME: XXX: Validate that checkRule and offset processing work with on
 611:     // or before mode.
 612:     this.endDay = after ? Math.abs(day) : -Math.abs(day);
 613:     this.endDayOfWeek = after ? Math.abs(dayOfWeek) : -Math.abs(dayOfWeek);
 614:     this.endMode = (dayOfWeek != 0)
 615:                    ? (after ? DOW_GE_DOM_MODE : DOW_LE_DOM_MODE)
 616:                    : checkRule(month, day, dayOfWeek);
 617:     this.endDay = Math.abs(this.endDay);
 618:     this.endDayOfWeek = Math.abs(endDayOfWeek);
 619: 
 620:     this.endMonth = month;
 621: 
 622:     if (this.endTimeMode == WALL_TIME)
 623:       this.endTime = time;
 624:     else if (this.endTimeMode == STANDARD_TIME)
 625:       // Convert from STANDARD to DST
 626:       this.endTime = time + this.dstSavings;
 627:     else
 628:       // Convert from UTC to DST
 629:       this.endTime = time + this.rawOffset + this.dstSavings;
 630:     useDaylight = true;
 631:   }
 632: 
 633:   /**
 634:    * Sets the daylight savings end rule.  You must also set the
 635:    * start rule with <code>setStartRule</code> or the result of
 636:    * getOffset is undefined. For the parameters see the ten-argument
 637:    * constructor above.
 638:    *
 639:    * @param month The end month of daylight savings.
 640:    * @param day A day in month, or a day of week in month.
 641:    * @param time A time in millis in standard time.
 642:    * @see #setStartRule(int, int, int)
 643:    */
 644:   public void setEndRule(int month, int day, int time)
 645:   {
 646:     setEndRule(month, day, 0, time);
 647:   }
 648: 
 649:   /**
 650:    * Gets the time zone offset, for current date, modified in case of
 651:    * daylight savings.  This is the offset to add to UTC to get the local
 652:    * time.
 653:    *
 654:    * In the standard JDK the results given by this method may result in
 655:    * inaccurate results at the end of February or the beginning of March.
 656:    * To avoid this, you should use Calendar instead:
 657:    * <code>offset = cal.get(Calendar.ZONE_OFFSET)
 658:    * + cal.get(Calendar.DST_OFFSET);</code>
 659:    *
 660:    * This version doesn't suffer this inaccuracy.
 661:    *
 662:    * The arguments don't follow the approach for setting start and end rules.
 663:    * The day must be a positive number and dayOfWeek must be a positive value
 664:    * from Calendar.  dayOfWeek is redundant, but must match the other values
 665:    * or an inaccurate result may be returned.
 666:    *
 667:    * @param era the era of the given date
 668:    * @param year the year of the given date
 669:    * @param month the month of the given date, 0 for January.
 670:    * @param day the day of month
 671:    * @param dayOfWeek the day of week; this must match the other fields.
 672:    * @param millis the millis in the day (in local standard time)
 673:    * @return the time zone offset in milliseconds.
 674:    * @throws IllegalArgumentException if arguments are incorrect.
 675:    */
 676:   public int getOffset(int era, int year, int month, int day, int dayOfWeek,
 677:                        int millis)
 678:   {
 679:     int daysInMonth = getDaysInMonth(month, year);
 680:     if (day < 1 || day > daysInMonth)
 681:       throw new IllegalArgumentException("day out of range");
 682:     if (dayOfWeek < Calendar.SUNDAY || dayOfWeek > Calendar.SATURDAY)
 683:       throw new IllegalArgumentException("dayOfWeek out of range");
 684:     if (month < Calendar.JANUARY || month > Calendar.DECEMBER)
 685:       throw new IllegalArgumentException("month out of range:" + month);
 686: 
 687:     // This method is called by Calendar, so we mustn't use that class.
 688:     int daylightSavings = 0;
 689:     if (useDaylight && era == GregorianCalendar.AD && year >= startYear)
 690:       {
 691:     // This does only work for Gregorian calendars :-(
 692:     // This is mainly because setStartYear doesn't take an era.
 693:     boolean afterStart = ! isBefore(year, month, day, dayOfWeek, millis,
 694:                                     startMode, startMonth, startDay,
 695:                                     startDayOfWeek, startTime);
 696:     boolean beforeEnd = isBefore(year, month, day, dayOfWeek,
 697:                      millis + dstSavings,
 698:                                  endMode, endMonth, endDay, endDayOfWeek,
 699:                                  endTime);
 700: 
 701:     if (startMonth < endMonth)
 702:       // use daylight savings, if the date is after the start of
 703:       // savings, and before the end of savings.
 704:       daylightSavings = afterStart && beforeEnd ? dstSavings : 0;
 705:     else
 706:       // use daylight savings, if the date is before the end of
 707:       // savings, or after the start of savings.
 708:       daylightSavings = beforeEnd || afterStart ? dstSavings : 0;
 709:       }
 710:     return rawOffset + daylightSavings;
 711:   }
 712: 
 713:   /**
 714:    * Returns the time zone offset to GMT in milliseconds, ignoring
 715:    * day light savings.
 716:    * @return the time zone offset.
 717:    */
 718:   public int getRawOffset()
 719:   {
 720:     return rawOffset;
 721:   }
 722: 
 723:   /**
 724:    * Sets the standard time zone offset to GMT.
 725:    * @param rawOffset The time offset from GMT in milliseconds.
 726:    */
 727:   public void setRawOffset(int rawOffset)
 728:   {
 729:     this.rawOffset = rawOffset;
 730:   }
 731: 
 732:   /**
 733:    * Gets the daylight savings offset.  This is a positive offset in
 734:    * milliseconds with respect to standard time.  Typically this
 735:    * is one hour, but for some time zones this may be half an our.
 736:    * @return the daylight savings offset in milliseconds.
 737:    *
 738:    * @since 1.2
 739:    */
 740:   public int getDSTSavings()
 741:   {
 742:     return dstSavings;
 743:   }
 744: 
 745:   /**
 746:    * Sets the daylight savings offset.  This is a positive offset in
 747:    * milliseconds with respect to standard time.
 748:    *
 749:    * @param dstSavings the daylight savings offset in milliseconds.
 750:    *
 751:    * @since 1.2
 752:    */
 753:   public void setDSTSavings(int dstSavings)
 754:   {
 755:     if (dstSavings <= 0)
 756:       throw new IllegalArgumentException("illegal value for dstSavings");
 757: 
 758:     this.dstSavings = dstSavings;
 759:   }
 760: 
 761:   /**
 762:    * Returns if this time zone uses daylight savings time.
 763:    * @return true, if we use daylight savings time, false otherwise.
 764:    */
 765:   public boolean useDaylightTime()
 766:   {
 767:     return useDaylight;
 768:   }
 769: 
 770:   /**
 771:    * Returns the number of days in the given month.
 772:    * Uses gregorian rules prior to 1582 (The default and earliest cutover)
 773:    * @param month The month, zero based; use one of the Calendar constants.
 774:    * @param year  The year.
 775:    */
 776:   private int getDaysInMonth(int month, int year)
 777:   {
 778:     if (month == Calendar.FEBRUARY)
 779:       {
 780:     if ((year & 3) != 0)
 781:       return 28;
 782: 
 783:     // Assume default Gregorian cutover, 
 784:     // all years prior to this must be Julian
 785:     if (year < 1582)
 786:       return 29;
 787: 
 788:     // Gregorian rules 
 789:     return ((year % 100) != 0 || (year % 400) == 0) ? 29 : 28;
 790:       }
 791:     else
 792:       return monthArr[month];
 793:   }
 794: 
 795:   /**
 796:    * Checks if the date given in calXXXX, is before the change between
 797:    * dst and standard time.
 798:    * @param calYear the year of the date to check (for leap day checking).
 799:    * @param calMonth the month of the date to check.
 800:    * @param calDayOfMonth the day of month of the date to check.
 801:    * @param calDayOfWeek the day of week of the date to check.
 802:    * @param calMillis the millis of day of the date to check (standard time).
 803:    * @param mode  the change mode; same semantic as startMode.
 804:    * @param month the change month; same semantic as startMonth.
 805:    * @param day   the change day; same semantic as startDay.
 806:    * @param dayOfWeek the change day of week;
 807:    * @param millis the change time in millis since midnight standard time.
 808:    * same semantic as startDayOfWeek.
 809:    * @return true, if cal is before the change, false if cal is on
 810:    * or after the change.
 811:    */
 812:   private boolean isBefore(int calYear, int calMonth, int calDayOfMonth,
 813:                            int calDayOfWeek, int calMillis, int mode,
 814:                            int month, int day, int dayOfWeek, int millis)
 815:   {
 816:     // This method is called by Calendar, so we mustn't use that class.
 817:     // We have to do all calculations by hand.
 818:     // check the months:
 819:     // XXX - this is not correct:
 820:     // for the DOW_GE_DOM and DOW_LE_DOM modes the change date may
 821:     // be in a different month.
 822:     if (calMonth != month)
 823:       return calMonth < month;
 824: 
 825:     // check the day:
 826:     switch (mode)
 827:       {
 828:       case DOM_MODE:
 829:     if (calDayOfMonth != day)
 830:       return calDayOfMonth < day;
 831:     break;
 832:       case DOW_IN_MONTH_MODE:
 833:         {
 834:       // This computes the day of month of the day of type
 835:       // "dayOfWeek" that lies in the same (sunday based) week as cal.
 836:       calDayOfMonth += (dayOfWeek - calDayOfWeek);
 837: 
 838:       // Now we convert it to 7 based number (to get a one based offset
 839:       // after dividing by 7).  If we count from the end of the
 840:       // month, we get want a -7 based number counting the days from 
 841:       // the end:
 842:       if (day < 0)
 843:         calDayOfMonth -= getDaysInMonth(calMonth, calYear) + 7;
 844:       else
 845:         calDayOfMonth += 6;
 846: 
 847:       //  day > 0                    day < 0
 848:       //  S  M  T  W  T  F  S        S  M  T  W  T  F  S
 849:       //     7  8  9 10 11 12         -36-35-34-33-32-31
 850:       // 13 14 15 16 17 18 19      -30-29-28-27-26-25-24
 851:       // 20 21 22 23 24 25 26      -23-22-21-20-19-18-17
 852:       // 27 28 29 30 31 32 33      -16-15-14-13-12-11-10
 853:       // 34 35 36                   -9 -8 -7
 854:       // Now we calculate the day of week in month:
 855:       int week = calDayOfMonth / 7;
 856: 
 857:       //  day > 0                    day < 0
 858:       //  S  M  T  W  T  F  S        S  M  T  W  T  F  S
 859:       //     1  1  1  1  1  1          -5 -5 -4 -4 -4 -4
 860:       //  1  2  2  2  2  2  2       -4 -4 -4 -3 -3 -3 -3
 861:       //  2  3  3  3  3  3  3       -3 -3 -3 -2 -2 -2 -2
 862:       //  3  4  4  4  4  4  4       -2 -2 -2 -1 -1 -1 -1
 863:       //  4  5  5                   -1 -1 -1
 864:       if (week != day)
 865:         return week < day;
 866: 
 867:       if (calDayOfWeek != dayOfWeek)
 868:         return calDayOfWeek < dayOfWeek;
 869: 
 870:       // daylight savings starts/ends  on the given day.
 871:       break;
 872:         }
 873:       case DOW_LE_DOM_MODE:
 874:     // The greatest sunday before or equal December, 12
 875:     // is the same as smallest sunday after or equal December, 6.
 876:     day = Math.abs(day) - 6;
 877:       case DOW_GE_DOM_MODE:
 878:     // Calculate the day of month of the day of type
 879:     // "dayOfWeek" that lies before (or on) the given date.
 880:     calDayOfMonth -= (calDayOfWeek < dayOfWeek ? 7 : 0) + calDayOfWeek
 881:     - dayOfWeek;
 882:     if (calDayOfMonth < day)
 883:       return true;
 884:     if (calDayOfWeek != dayOfWeek || calDayOfMonth >= day + 7)
 885:       return false;
 886: 
 887:     // now we have the same day
 888:     break;
 889:       }
 890: 
 891:     // the millis decides:
 892:     return (calMillis < millis);
 893:   }
 894: 
 895:   /**
 896:    * Determines if the given date is in daylight savings time.
 897:    * @return true, if it is in daylight savings time, false otherwise.
 898:    */
 899:   public boolean inDaylightTime(Date date)
 900:   {
 901:     Calendar cal = Calendar.getInstance(this);
 902:     cal.setTime(date);
 903:     return (cal.get(Calendar.DST_OFFSET) != 0);
 904:   }
 905: 
 906:   /**
 907:    * Generates the hashCode for the SimpleDateFormat object.  It is
 908:    * the rawOffset, possibly, if useDaylightSavings is true, xored
 909:    * with startYear, startMonth, startDayOfWeekInMonth, ..., endTime.
 910:    */
 911:   public synchronized int hashCode()
 912:   {
 913:     return rawOffset
 914:            ^ (useDaylight
 915:               ? startMonth ^ startDay ^ startDayOfWeek ^ startTime ^ endMonth
 916:               ^ endDay ^ endDayOfWeek ^ endTime : 0);
 917:   }
 918: 
 919:   public synchronized boolean equals(Object o)
 920:   {
 921:     if (this == o)
 922:       return true;
 923:     if (! (o instanceof SimpleTimeZone))
 924:       return false;
 925:     SimpleTimeZone zone = (SimpleTimeZone) o;
 926:     if (zone.hashCode() != hashCode() || ! getID().equals(zone.getID())
 927:         || rawOffset != zone.rawOffset || useDaylight != zone.useDaylight)
 928:       return false;
 929:     if (! useDaylight)
 930:       return true;
 931:     return (startYear == zone.startYear && startMonth == zone.startMonth
 932:            && startDay == zone.startDay
 933:            && startDayOfWeek == zone.startDayOfWeek
 934:            && startTime == zone.startTime
 935:            && startTimeMode == zone.startTimeMode && endMonth == zone.endMonth
 936:            && endDay == zone.endDay && endDayOfWeek == zone.endDayOfWeek
 937:            && endTime == zone.endTime && endTimeMode == zone.endTimeMode);
 938:   }
 939: 
 940:   /**
 941:    * Test if the other time zone uses the same rule and only
 942:    * possibly differs in ID.  This implementation for this particular
 943:    * class will return true if the other object is a SimpleTimeZone,
 944:    * the raw offsets and useDaylight are identical and if useDaylight
 945:    * is true, also the start and end datas are identical.
 946:    * @return true if this zone uses the same rule.
 947:    */
 948:   public boolean hasSameRules(TimeZone other)
 949:   {
 950:     if (this == other)
 951:       return true;
 952:     if (! (other instanceof SimpleTimeZone))
 953:       return false;
 954:     SimpleTimeZone zone = (SimpleTimeZone) other;
 955:     if (zone.hashCode() != hashCode() || rawOffset != zone.rawOffset
 956:         || useDaylight != zone.useDaylight)
 957:       return false;
 958:     if (! useDaylight)
 959:       return true;
 960:     return (startYear == zone.startYear && startMonth == zone.startMonth
 961:            && startDay == zone.startDay
 962:            && startDayOfWeek == zone.startDayOfWeek
 963:            && startTime == zone.startTime
 964:            && startTimeMode == zone.startTimeMode && endMonth == zone.endMonth
 965:            && endDay == zone.endDay && endDayOfWeek == zone.endDayOfWeek
 966:            && endTime == zone.endTime && endTimeMode == zone.endTimeMode);
 967:   }
 968: 
 969:   /**
 970:    * Returns a string representation of this SimpleTimeZone object.
 971:    * @return a string representation of this SimpleTimeZone object.
 972:    */
 973:   public String toString()
 974:   {
 975:     // the test for useDaylight is an incompatibility to jdk1.2, but
 976:     // I think this shouldn't hurt.
 977:     return getClass().getName() + "[" + "id=" + getID() + ",offset="
 978:            + rawOffset + ",dstSavings=" + dstSavings + ",useDaylight="
 979:            + useDaylight
 980:            + (useDaylight
 981:               ? ",startYear=" + startYear + ",startMode=" + startMode
 982:               + ",startMonth=" + startMonth + ",startDay=" + startDay
 983:               + ",startDayOfWeek=" + startDayOfWeek + ",startTime="
 984:               + startTime + ",startTimeMode=" + startTimeMode + ",endMode="
 985:               + endMode + ",endMonth=" + endMonth + ",endDay=" + endDay
 986:               + ",endDayOfWeek=" + endDayOfWeek + ",endTime=" + endTime
 987:               + ",endTimeMode=" + endTimeMode : "") + "]";
 988:   }
 989: 
 990:   /**
 991:    * Reads a serialized simple time zone from stream.
 992:    * @see #writeObject
 993:    */
 994:   private void readObject(java.io.ObjectInputStream input)
 995:     throws java.io.IOException, ClassNotFoundException
 996:   {
 997:     input.defaultReadObject();
 998:     if (serialVersionOnStream == 0)
 999:       {
1000:     // initialize the new fields to default values.
1001:     dstSavings = 60 * 60 * 1000;
1002:     endMode = DOW_IN_MONTH_MODE;
1003:     startMode = DOW_IN_MONTH_MODE;
1004:     startTimeMode = WALL_TIME;
1005:     endTimeMode = WALL_TIME;
1006:     serialVersionOnStream = 2;
1007:       }
1008:     else
1009:       {
1010:     int length = input.readInt();
1011:     byte[] byteArray = new byte[length];
1012:     input.read(byteArray, 0, length);
1013:     if (length >= 4)
1014:       {
1015:         // Lets hope that Sun does extensions to the serialized
1016:         // form in a sane manner.
1017:         startDay = byteArray[0];
1018:         startDayOfWeek = byteArray[1];
1019:         endDay = byteArray[2];
1020:         endDayOfWeek = byteArray[3];
1021:       }
1022:       }
1023:   }
1024: 
1025:   /**
1026:    * Serializes this object to a stream.  @serialdata The object is
1027:    * first written in the old JDK 1.1 format, so that it can be read
1028:    * by by the old classes.  This means, that the
1029:    * <code>start/endDay(OfWeek)</code>-Fields are written in the
1030:    * DOW_IN_MONTH_MODE rule, since this was the only supported rule
1031:    * in 1.1.
1032:    *
1033:    * In the optional section, we write first the length of an byte
1034:    * array as int and afterwards the byte array itself.  The byte
1035:    * array contains in this release four elements, namely the real
1036:    * startDay, startDayOfWeek endDay, endDayOfWeek in that Order.
1037:    * These fields are needed, because for compatibility reasons only
1038:    * approximative values are written to the required section, as
1039:    * described above.
1040:    */
1041:   private void writeObject(java.io.ObjectOutputStream output)
1042:     throws java.io.IOException
1043:   {
1044:     byte[] byteArray = new byte[]
1045:                        {
1046:                          (byte) startDay, (byte) startDayOfWeek, (byte) endDay,
1047:                          (byte) endDayOfWeek
1048:                        };
1049: 
1050:     /* calculate the approximation for JDK 1.1 */
1051:     switch (startMode)
1052:       {
1053:       case DOM_MODE:
1054:     startDayOfWeek = Calendar.SUNDAY; // random day of week
1055: 
1056:       // fall through
1057:       case DOW_GE_DOM_MODE:
1058:       case DOW_LE_DOM_MODE:
1059:     startDay = (startDay + 6) / 7;
1060:       }
1061:     switch (endMode)
1062:       {
1063:       case DOM_MODE:
1064:     endDayOfWeek = Calendar.SUNDAY;
1065: 
1066:       // fall through
1067:       case DOW_GE_DOM_MODE:
1068:       case DOW_LE_DOM_MODE:
1069:     endDay = (endDay + 6) / 7;
1070:       }
1071: 
1072:     // the required part:
1073:     output.defaultWriteObject();
1074:     // the optional part:
1075:     output.writeInt(byteArray.length);
1076:     output.write(byteArray, 0, byteArray.length);
1077:   }
1078: }