Source for java.util.SimpleTimeZone

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