Frames | No Frames |
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 > 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 < 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 > 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 > 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 < 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: }