0001: /*
0002: *
0003: * @(#)SimpleTimeZone.java 1.47 06/10/10
0004: *
0005: * Portions Copyright 2000-2006 Sun Microsystems, Inc. All Rights
0006: * Reserved. Use is subject to license terms.
0007: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
0008: *
0009: * This program is free software; you can redistribute it and/or
0010: * modify it under the terms of the GNU General Public License version
0011: * 2 only, as published by the Free Software Foundation.
0012: *
0013: * This program is distributed in the hope that it will be useful, but
0014: * WITHOUT ANY WARRANTY; without even the implied warranty of
0015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0016: * General Public License version 2 for more details (a copy is
0017: * included at /legal/license.txt).
0018: *
0019: * You should have received a copy of the GNU General Public License
0020: * version 2 along with this work; if not, write to the Free Software
0021: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0022: * 02110-1301 USA
0023: *
0024: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
0025: * Clara, CA 95054 or visit www.sun.com if you need additional
0026: * information or have any questions.
0027: */
0028:
0029: /*
0030: * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved
0031: * (C) Copyright IBM Corp. 1996 - All Rights Reserved
0032: *
0033: * The original version of this source code and documentation is copyrighted
0034: * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
0035: * materials are provided under terms of a License Agreement between Taligent
0036: * and Sun. This technology is protected by multiple US and International
0037: * patents. This notice and attribution to Taligent may not be removed.
0038: * Taligent is a registered trademark of Taligent, Inc.
0039: *
0040: */
0041:
0042: package java.util;
0043:
0044: import java.io.ObjectInputStream;
0045: import java.io.ObjectOutputStream;
0046: import java.io.IOException;
0047: import sun.util.calendar.CalendarDate;
0048: import sun.util.calendar.Gregorian;
0049:
0050: /**
0051: * <code>SimpleTimeZone</code> is a concrete subclass of <code>TimeZone</code>
0052: * that represents a time zone for use with a Gregorian calendar.
0053: * The class holds an offset from GMT, called <em>raw offset</em>, and start
0054: * and end rules for a daylight saving time schedule. Since it only holds
0055: * single values for each, it cannot handle historical changes in the offset
0056: * from GMT and the daylight saving schedule, except that the {@link
0057: * #setStartYear setStartYear} method can specify the year when the daylight
0058: * saving time schedule starts in effect.
0059: * <p>
0060: * To construct a <code>SimpleTimeZone</code> with a daylight saving time
0061: * schedule, the schedule can be described with a set of rules,
0062: * <em>start-rule</em> and <em>end-rule</em>. A day when daylight saving time
0063: * starts or ends is specified by a combination of <em>month</em>,
0064: * <em>day-of-month</em>, and <em>day-of-week</em> values. The <em>month</em>
0065: * value is represented by a Calendar {@link Calendar#MONTH MONTH} field
0066: * value, such as {@link Calendar#MARCH}. The <em>day-of-week</em> value is
0067: * represented by a Calendar {@link Calendar#DAY_OF_WEEK DAY_OF_WEEK} value,
0068: * such as {@link Calendar#SUNDAY SUNDAY}. The meanings of value combinations
0069: * are as follows.
0070: *
0071: * <ul>
0072: * <li><b>Exact day of month</b><br>
0073: * To specify an exact day of month, set the <em>month</em> and
0074: * <em>day-of-month</em> to an exact value, and <em>day-of-week</em> to zero. For
0075: * example, to specify March 1, set the <em>month</em> to {@link Calendar#MARCH
0076: * MARCH}, <em>day-of-month</em> to 1, and <em>day-of-week</em> to 0.</li>
0077: *
0078: * <li><b>Day of week on or after day of month</b><br>
0079: * To specify a day of week on or after an exact day of month, set the
0080: * <em>month</em> to an exact month value, <em>day-of-month</em> to the day on
0081: * or after which the rule is applied, and <em>day-of-week</em> to a {@link
0082: * Calendar#DAY_OF_WEEK DAY_OF_WEEK} field value. For example, to specify the
0083: * second Sunday of April, set <em>month</em> to {@link Calendar#APRIL APRIL},
0084: * <em>day-of-month</em> to 8, and <em>day-of-week</em> to {@link
0085: * Calendar#SUNDAY SUNDAY}.</li>
0086: *
0087: * <li><b>Day of week on or before day of month</b><br>
0088: * To specify a day of the week on or before an exact day of the month, set
0089: * <em>day-of-month</em> and <em>day-of-week</em> to a negative value. For
0090: * example, to specify the last Wednesday on or before the 21st of March, set
0091: * <em>month</em> to {@link Calendar#MARCH MARCH}, <em>day-of-month</em> is -21
0092: * and <em>day-of-week</em> is {@link Calendar#WEDNESDAY -WEDNESDAY}. </li>
0093: *
0094: * <li><b>Last day-of-week of month</b><br>
0095: * To specify, the last day-of-week of the month, set <em>day-of-week</em> to a
0096: * {@link Calendar#DAY_OF_WEEK DAY_OF_WEEK} value and <em>day-of-month</em> to
0097: * -1. For example, to specify the last Sunday of October, set <em>month</em>
0098: * to {@link Calendar#OCTOBER OCTOBER}, <em>day-of-week</em> to {@link
0099: * Calendar#SUNDAY SUNDAY} and <em>day-of-month</em> to -1. </li>
0100: *
0101: * </ul>
0102: * The time of the day at which daylight saving time starts or ends is
0103: * specified by a millisecond value within the day. There are three kinds of
0104: * <em>mode</em>s to specify the time: {@link #WALL_TIME}, {@link
0105: * #STANDARD_TIME} and {@link #UTC_TIME}. For example, if daylight
0106: * saving time ends
0107: * at 2:00 am in the wall clock time, it can be specified by 7200000
0108: * milliseconds in the {@link #WALL_TIME} mode. In this case, the wall clock time
0109: * for an <em>end-rule</em> means the same thing as the daylight time.
0110: * <p>
0111: * The following are examples of parameters for constructing time zone objects.
0112: * <pre><code>
0113: * // Base GMT offset: -8:00
0114: * // DST starts: at 2:00am in standard time
0115: * // on the first Sunday in April
0116: * // DST ends: at 2:00am in daylight time
0117: * // on the last Sunday in October
0118: * // Save: 1 hour
0119: * SimpleTimeZone(-28800000,
0120: * "America/Los_Angeles",
0121: * Calendar.APRIL, 1, -Calendar.SUNDAY,
0122: * 7200000,
0123: * Calendar.OCTOBER, -1, Calendar.SUNDAY,
0124: * 7200000,
0125: * 3600000)
0126: *
0127: * // Base GMT offset: +1:00
0128: * // DST starts: at 1:00am in UTC time
0129: * // on the last Sunday in March
0130: * // DST ends: at 1:00am in UTC time
0131: * // on the last Sunday in October
0132: * // Save: 1 hour
0133: * SimpleTimeZone(3600000,
0134: * "Europe/Paris",
0135: * Calendar.MARCH, -1, Calendar.SUNDAY,
0136: * 3600000, SimpleTimeZone.UTC_TIME,
0137: * Calendar.OCTOBER, -1, Calendar.SUNDAY,
0138: * 3600000, SimpleTimeZone.UTC_TIME,
0139: * 3600000)
0140: * </code></pre>
0141: * These parameter rules are also applicable to the set rule methods, such as
0142: * <code>setStartRule</code>.
0143: *
0144: * @since 1.1
0145: * @see Calendar
0146: * @see GregorianCalendar
0147: * @see TimeZone
0148: * @version 1.47, 10/10/06
0149: * @author David Goldsmith, Mark Davis, Chen-Lieh Huang, Alan Liu
0150: */
0151:
0152: public class SimpleTimeZone extends TimeZone {
0153: /**
0154: * Constructs a SimpleTimeZone with the given base time zone offset from GMT
0155: * and time zone ID with no daylight saving time schedule.
0156: *
0157: * @param rawOffset The base time zone offset in milliseconds to GMT.
0158: * @param ID The time zone name that is given to this instance.
0159: */
0160: public SimpleTimeZone(int rawOffset, String ID) {
0161: this .rawOffset = rawOffset;
0162: setID(ID);
0163: dstSavings = millisPerHour; // In case user sets rules later
0164: }
0165:
0166: /**
0167: * Constructs a SimpleTimeZone with the given base time zone offset from
0168: * GMT, time zone ID, and rules for starting and ending the daylight
0169: * time.
0170: * Both <code>startTime</code> and <code>endTime</code> are specified to be
0171: * represented in the wall clock time. The amount of daylight saving is
0172: * assumed to be 3600000 milliseconds (i.e., one hour). This constructor is
0173: * equivalent to:
0174: * <pre><code>
0175: * SimpleTimeZone(rawOffset,
0176: * ID,
0177: * startMonth,
0178: * startDay,
0179: * startDayOfWeek,
0180: * startTime,
0181: * SimpleTimeZone.{@link #WALL_TIME},
0182: * endMonth,
0183: * endDay,
0184: * endDayOfWeek,
0185: * endTime,
0186: * SimpleTimeZone.{@link #WALL_TIME},
0187: * 3600000)
0188: * </code></pre>
0189: *
0190: * @param rawOffset The given base time zone offset from GMT.
0191: * @param ID The time zone ID which is given to this object.
0192: * @param startMonth The daylight saving time starting month. Month is
0193: * a {@link Calendar#MONTH MONTH} field value (0-based. e.g., 0
0194: * for January).
0195: * @param startDay The day of the month on which the daylight saving time starts.
0196: * See the class description for the special cases of this parameter.
0197: * @param startDayOfWeek The daylight saving time starting day-of-week.
0198: * See the class description for the special cases of this parameter.
0199: * @param startTime The daylight saving time starting time in local wall clock
0200: * time (in milliseconds within the day), which is local
0201: * standard time in this case.
0202: * @param endMonth The daylight saving time ending month. Month is
0203: * a {@link Calendar#MONTH MONTH} field
0204: * value (0-based. e.g., 9 for October).
0205: * @param endDay The day of the month on which the daylight saving time ends.
0206: * See the class description for the special cases of this parameter.
0207: * @param endDayOfWeek The daylight saving time ending day-of-week.
0208: * See the class description for the special cases of this parameter.
0209: * @param endTime The daylight saving ending time in local wall clock time,
0210: * (in milliseconds within the day) which is local daylight
0211: * time in this case.
0212: * @exception IllegalArgumentException if the month, day, dayOfWeek, or time
0213: * parameters are out of range for the start or end rule
0214: */
0215: public SimpleTimeZone(int rawOffset, String ID, int startMonth,
0216: int startDay, int startDayOfWeek, int startTime,
0217: int endMonth, int endDay, int endDayOfWeek, int endTime) {
0218: this (rawOffset, ID, startMonth, startDay, startDayOfWeek,
0219: startTime, WALL_TIME, endMonth, endDay, endDayOfWeek,
0220: endTime, WALL_TIME, millisPerHour);
0221: }
0222:
0223: /**
0224: * Constructs a SimpleTimeZone with the given base time zone offset from
0225: * GMT, time zone ID, and rules for starting and ending the daylight
0226: * time.
0227: * Both <code>startTime</code> and <code>endTime</code> are assumed to be
0228: * represented in the wall clock time. This constructor is equivalent to:
0229: * <pre><code>
0230: * SimpleTimeZone(rawOffset,
0231: * ID,
0232: * startMonth,
0233: * startDay,
0234: * startDayOfWeek,
0235: * startTime,
0236: * SimpleTimeZone.{@link #WALL_TIME},
0237: * endMonth,
0238: * endDay,
0239: * endDayOfWeek,
0240: * endTime,
0241: * SimpleTimeZone.{@link #WALL_TIME},
0242: * dstSavings)
0243: * </code></pre>
0244: *
0245: * @param rawOffset The given base time zone offset from GMT.
0246: * @param ID The time zone ID which is given to this object.
0247: * @param startMonth The daylight saving time starting month. Month is
0248: * a {@link Calendar#MONTH MONTH} field
0249: * value (0-based. e.g., 0 for January).
0250: * @param startDay The day of the month on which the daylight saving time starts.
0251: * See the class description for the special cases of this parameter.
0252: * @param startDayOfWeek The daylight saving time starting day-of-week.
0253: * See the class description for the special cases of this parameter.
0254: * @param startTime The daylight saving time starting time in local wall clock
0255: * time, which is local standard time in this case.
0256: * @param endMonth The daylight saving time ending month. Month is
0257: * a {@link Calendar#MONTH MONTH} field
0258: * value (0-based. e.g., 9 for October).
0259: * @param endDay The day of the month on which the daylight saving time ends.
0260: * See the class description for the special cases of this parameter.
0261: * @param endDayOfWeek The daylight saving time ending day-of-week.
0262: * See the class description for the special cases of this parameter.
0263: * @param endTime The daylight saving ending time in local wall clock time,
0264: * which is local daylight time in this case.
0265: * @param dstSavings The amount of time in milliseconds saved during
0266: * daylight saving time.
0267: * @exception IllegalArgumentException if the month, day, dayOfWeek, or time
0268: * parameters are out of range for the start or end rule
0269: * @since 1.2
0270: */
0271: public SimpleTimeZone(int rawOffset, String ID, int startMonth,
0272: int startDay, int startDayOfWeek, int startTime,
0273: int endMonth, int endDay, int endDayOfWeek, int endTime,
0274: int dstSavings) {
0275: this (rawOffset, ID, startMonth, startDay, startDayOfWeek,
0276: startTime, WALL_TIME, endMonth, endDay, endDayOfWeek,
0277: endTime, WALL_TIME, dstSavings);
0278: }
0279:
0280: /**
0281: * Constructs a SimpleTimeZone with the given base time zone offset from
0282: * GMT, time zone ID, and rules for starting and ending the daylight
0283: * time.
0284: * This constructor takes the full set of the start and end rules
0285: * parameters, including modes of <code>startTime</code> and
0286: * <code>endTime</code>. The mode specifies either {@link #WALL_TIME wall
0287: * time} or {@link #STANDARD_TIME standard time} or {@link #UTC_TIME UTC
0288: * time}.
0289: *
0290: * @param rawOffset The given base time zone offset from GMT.
0291: * @param ID The time zone ID which is given to this object.
0292: * @param startMonth The daylight saving time starting month. Month is
0293: * a {@link Calendar#MONTH MONTH} field
0294: * value (0-based. e.g., 0 for January).
0295: * @param startDay The day of the month on which the daylight saving time starts.
0296: * See the class description for the special cases of this parameter.
0297: * @param startDayOfWeek The daylight saving time starting day-of-week.
0298: * See the class description for the special cases of this parameter.
0299: * @param startTime The daylight saving time starting time in the time mode
0300: * specified by <code>startTimeMode</code>.
0301: * @param startTimeMode The mode of the start time specified by startTime.
0302: * @param endMonth The daylight saving time ending month. Month is
0303: * a {@link Calendar#MONTH MONTH} field
0304: * value (0-based. e.g., 9 for October).
0305: * @param endDay The day of the month on which the daylight saving time ends.
0306: * See the class description for the special cases of this parameter.
0307: * @param endDayOfWeek The daylight saving time ending day-of-week.
0308: * See the class description for the special cases of this parameter.
0309: * @param endTime The daylight saving ending time in time time mode
0310: * specified by <code>endTimeMode</code>.
0311: * @param endTimeMode The mode of the end time specified by endTime
0312: * @param dstSavings The amount of time in milliseconds saved during
0313: * daylight saving time.
0314: *
0315: * @exception IllegalArgumentException if the month, day, dayOfWeek, time more, or
0316: * time parameters are out of range for the start or end rule, or if a time mode
0317: * value is invalid.
0318: *
0319: * @see #WALL_TIME
0320: * @see #STANDARD_TIME
0321: * @see #UTC_TIME
0322: *
0323: * @since 1.4
0324: */
0325: public SimpleTimeZone(int rawOffset, String ID, int startMonth,
0326: int startDay, int startDayOfWeek, int startTime,
0327: int startTimeMode, int endMonth, int endDay,
0328: int endDayOfWeek, int endTime, int endTimeMode,
0329: int dstSavings) {
0330:
0331: // Workaround fix for 4278609 (JCK failure)
0332: if (endMonth == Calendar.JANUARY && endDay == 1
0333: && endDayOfWeek == 0 && endTime == 0
0334: && endTimeMode == WALL_TIME && dstSavings > 0) {
0335: endMonth = Calendar.DECEMBER;
0336: endDay = 31;
0337: endTime = (24 * 60 * 60 * 1000) - dstSavings;
0338: endTimeMode = STANDARD_TIME;
0339: }
0340:
0341: setID(ID);
0342: this .rawOffset = rawOffset;
0343: this .startMonth = startMonth;
0344: this .startDay = startDay;
0345: this .startDayOfWeek = startDayOfWeek;
0346: this .startTime = startTime;
0347: this .startTimeMode = startTimeMode;
0348: this .endMonth = endMonth;
0349: this .endDay = endDay;
0350: this .endDayOfWeek = endDayOfWeek;
0351: this .endTime = endTime;
0352: this .endTimeMode = endTimeMode;
0353: this .dstSavings = dstSavings;
0354:
0355: // this.useDaylight is set by decodeRules
0356: decodeRules();
0357: if (dstSavings <= 0) {
0358: throw new IllegalArgumentException(
0359: "Illegal daylight saving value: " + dstSavings);
0360: }
0361: }
0362:
0363: /**
0364: * Sets the daylight saving time starting year.
0365: *
0366: * @param year The daylight saving starting year.
0367: */
0368: public void setStartYear(int year) {
0369: startYear = year;
0370: }
0371:
0372: /**
0373: * Sets the daylight saving time start rule. For example, if daylight saving
0374: * time starts on the first Sunday in April at 2 am in local wall clock
0375: * time, you can set the start rule by calling:
0376: * <pre><code>setStartRule(Calendar.APRIL, 1, Calendar.SUNDAY, 2*60*60*1000);</code></pre>
0377: *
0378: * @param startMonth The daylight saving time starting month. Month is
0379: * a {@link Calendar#MONTH MONTH} field
0380: * value (0-based. e.g., 0 for January).
0381: * @param startDay The day of the month on which the daylight saving time starts.
0382: * See the class description for the special cases of this parameter.
0383: * @param startDayOfWeek The daylight saving time starting day-of-week.
0384: * See the class description for the special cases of this parameter.
0385: * @param startTime The daylight saving time starting time in local wall clock
0386: * time, which is local standard time in this case.
0387: * @exception IllegalArgumentException if the <code>startMonth</code>, <code>startDay</code>,
0388: * <code>startDayOfWeek</code>, or <code>startTime</code> parameters are out of range
0389: */
0390: public void setStartRule(int startMonth, int startDay,
0391: int startDayOfWeek, int startTime) {
0392: this .startMonth = startMonth;
0393: this .startDay = startDay;
0394: this .startDayOfWeek = startDayOfWeek;
0395: this .startTime = startTime;
0396: startTimeMode = WALL_TIME;
0397: // useDaylight = true; // Set by decodeRules
0398: decodeStartRule();
0399: }
0400:
0401: /**
0402: * Sets the daylight saving time start rule to a fixed date within a month.
0403: * This method is equivalent to:
0404: * <pre><code>setStartRule(startMonth, startDay, 0, startTime)</code></pre>
0405: *
0406: * @param startMonth The daylight saving time starting month. Month is
0407: * a {@link Calendar#MONTH MONTH} field
0408: * value (0-based. e.g., 0 for January).
0409: * @param startDay The day of the month on which the daylight saving time starts.
0410: * @param startTime The daylight saving time starting time in local wall clock
0411: * time, which is local standard time in this case.
0412: * See the class description for the special cases of this parameter.
0413: * @exception IllegalArgumentException if the <code>startMonth</code>,
0414: * <code>startDayOfMonth</code>, or <code>startTime</code> parameters are out of range
0415: * @since 1.2
0416: */
0417: public void setStartRule(int startMonth, int startDay, int startTime) {
0418: setStartRule(startMonth, startDay, 0, startTime);
0419: }
0420:
0421: /**
0422: * Sets the daylight saving time start rule to a weekday before or after the given date within
0423: * a month, e.g., the first Monday on or after the 8th.
0424: *
0425: * @param startMonth The daylight saving time starting month. Month is
0426: * a {@link Calendar#MONTH MONTH} field
0427: * value (0-based. e.g., 0 for January).
0428: * @param startDay The day of the month on which the daylight saving time starts.
0429: * @param startDayOfWeek The daylight saving time starting day-of-week.
0430: * @param startTime The daylight saving time starting time in local wall clock
0431: * time, which is local standard time in this case.
0432: * @param after If true, this rule selects the first <code>dayOfWeek</code> on or
0433: * <em>after</em> <code>dayOfMonth</code>. If false, this rule
0434: * selects the last <code>dayOfWeek</code> on or <em>before</em>
0435: * <code>dayOfMonth</code>.
0436: * @exception IllegalArgumentException if the <code>startMonth</code>, <code>startDay</code>,
0437: * <code>startDayOfWeek</code>, or <code>startTime</code> parameters are out of range
0438: * @since 1.2
0439: */
0440: public void setStartRule(int startMonth, int startDay,
0441: int startDayOfWeek, int startTime, boolean after) {
0442: // TODO: this method doesn't check the initial values of dayOfMonth or dsyOfWeek.
0443: if (after) {
0444: setStartRule(startMonth, startDay, -startDayOfWeek,
0445: startTime);
0446: } else {
0447: setStartRule(startMonth, -startDay, -startDayOfWeek,
0448: startTime);
0449: }
0450: }
0451:
0452: /**
0453: * Sets the daylight saving time end rule. For example, if daylight saving time
0454: * ends on the last Sunday in October at 2 am in wall clock time,
0455: * you can set the end rule by calling:
0456: * <code>setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2*60*60*1000);</code>
0457: *
0458: * @param endMonth The daylight saving time ending month. Month is
0459: * a {@link Calendar#MONTH MONTH} field
0460: * value (0-based. e.g., 9 for October).
0461: * @param endDay The day of the month on which the daylight saving time ends.
0462: * See the class description for the special cases of this parameter.
0463: * @param endDayOfWeek The daylight saving time ending day-of-week.
0464: * See the class description for the special cases of this parameter.
0465: * @param endTime The daylight saving ending time in local wall clock time,
0466: * (in milliseconds within the day) which is local daylight
0467: * time in this case.
0468: * @exception IllegalArgumentException if the <code>endMonth</code>, <code>endDay</code>,
0469: * <code>endDayOfWeek</code>, or <code>endTime</code> parameters are out of range
0470: */
0471: public void setEndRule(int endMonth, int endDay, int endDayOfWeek,
0472: int endTime) {
0473: this .endMonth = endMonth;
0474: this .endDay = endDay;
0475: this .endDayOfWeek = endDayOfWeek;
0476: this .endTime = endTime;
0477: this .endTimeMode = WALL_TIME;
0478: // useDaylight = true; // Set by decodeRules
0479: decodeEndRule();
0480: }
0481:
0482: /**
0483: * Sets the daylight saving time end rule to a fixed date within a month.
0484: * This method is equivalent to:
0485: * <pre><code>setEndRule(endMonth, endDay, 0, endTime)</code></pre>
0486: *
0487: * @param endMonth The daylight saving time ending month. Month is
0488: * a {@link Calendar#MONTH MONTH} field
0489: * value (0-based. e.g., 9 for October).
0490: * @param endDay The day of the month on which the daylight saving time ends.
0491: * @param endTime The daylight saving ending time in local wall clock time,
0492: * (in milliseconds within the day) which is local daylight
0493: * time in this case.
0494: * @exception IllegalArgumentException the <code>endMonth</code>, <code>endDay</code>,
0495: * or <code>endTime</code> parameters are out of range
0496: * @since 1.2
0497: */
0498: public void setEndRule(int endMonth, int endDay, int endTime) {
0499: setEndRule(endMonth, endDay, 0, endTime);
0500: }
0501:
0502: /**
0503: * Sets the daylight saving time end rule to a weekday before or after the given date within
0504: * a month, e.g., the first Monday on or after the 8th.
0505: *
0506: * @param endMonth The daylight saving time ending month. Month is
0507: * a {@link Calendar#MONTH MONTH} field
0508: * value (0-based. e.g., 9 for October).
0509: * @param endDay The day of the month on which the daylight saving time ends.
0510: * @param endDayOfWeek The daylight saving time ending day-of-week.
0511: * @param endTime The daylight saving ending time in local wall clock time,
0512: * (in milliseconds within the day) which is local daylight
0513: * time in this case.
0514: * @param after If true, this rule selects the first <code>endDayOfWeek</code> on
0515: * or <em>after</em> <code>endDay</code>. If false, this rule
0516: * selects the last <code>endDayOfWeek</code> on or before
0517: * <code>endDay</code> of the month.
0518: * @exception IllegalArgumentException the <code>endMonth</code>, <code>endDay</code>,
0519: * <code>endDayOfWeek</code>, or <code>endTime</code> parameters are out of range
0520: * @since 1.2
0521: */
0522: public void setEndRule(int endMonth, int endDay, int endDayOfWeek,
0523: int endTime, boolean after) {
0524: if (after) {
0525: setEndRule(endMonth, endDay, -endDayOfWeek, endTime);
0526: } else {
0527: setEndRule(endMonth, -endDay, -endDayOfWeek, endTime);
0528: }
0529: }
0530:
0531: /**
0532: * Returns the offset of this time zone from UTC at the given
0533: * time. If daylight saving time is in effect at the given time,
0534: * the offset value is adjusted with the amount of daylight
0535: * saving.
0536: *
0537: * @param date the time at which the time zone offset is found
0538: * @return the amount of time in milliseconds to add to UTC to get
0539: * local time.
0540: * @since 1.4
0541: */
0542: public int getOffset(long date) {
0543: return getOffsets(date, null);
0544: }
0545:
0546: /**
0547: * @see TimeZone#getOffsets
0548: */
0549: int getOffsets(long date, int[] offsets) {
0550: int offset;
0551:
0552: calc: {
0553: if (!useDaylight) {
0554: offset = rawOffset;
0555: break calc;
0556: }
0557:
0558: // get standard local time
0559: CalendarDate cdate = Gregorian.getCalendarDate(date
0560: + rawOffset);
0561:
0562: int year = cdate.getYear();
0563: // if it's BC, assume no DST.
0564: if (year <= 0) {
0565: offset = rawOffset;
0566: break calc;
0567: }
0568: int month = cdate.getMonth();
0569: int monthLength = staticMonthLength[month];
0570: int prevMonthLength = staticMonthLength[(month + Calendar.DECEMBER) % 12];
0571: if (Gregorian.isLeapYear(year)) {
0572: if (month == Calendar.FEBRUARY) {
0573: ++monthLength;
0574: } else if (month == Calendar.MARCH) {
0575: ++prevMonthLength;
0576: }
0577: }
0578: offset = getOffset(GregorianCalendar.AD, year, month, cdate
0579: .getDate(), cdate.getDayOfWeek(), cdate
0580: .getTimeOfDay(), monthLength, prevMonthLength);
0581: }
0582:
0583: if (offsets != null) {
0584: offsets[0] = rawOffset;
0585: offsets[1] = offset - rawOffset;
0586: }
0587: return offset;
0588: }
0589:
0590: /**
0591: * Returns the difference in milliseconds between local time and
0592: * UTC, taking into account both the raw offset and the effect of
0593: * daylight saving, for the specified date and time. This method
0594: * assumes that the start and end month are distinct. It also
0595: * uses a default {@link GregorianCalendar} object as its
0596: * underlying calendar, such as for determining leap years. Do
0597: * not use the result of this method with a calendar other than a
0598: * default <code>GregorianCalendar</code>.
0599: *
0600: * <p><em>Note: In general, clients should use
0601: * <code>Calendar.get(ZONE_OFFSET) + Calendar.get(DST_OFFSET)</code>
0602: * instead of calling this method.</em>
0603: *
0604: * @param era The era of the given date.
0605: * @param year The year in the given date.
0606: * @param month The month in the given date. Month is 0-based. e.g.,
0607: * 0 for January.
0608: * @param day The day-in-month of the given date.
0609: * @param dayOfWeek The day-of-week of the given date.
0610: * @param millis The milliseconds in day in <em>standard</em> local time.
0611: * @return The milliseconds to add to UTC to get local time.
0612: * @exception IllegalArgumentException the <code>era</code>,
0613: * <code>month</code>, <code>day</code>, <code>dayOfWeek</code>,
0614: * or <code>millis</code> parameters are out of range
0615: */
0616: public int getOffset(int era, int year, int month, int day,
0617: int dayOfWeek, int millis) {
0618: // Check the month before indexing into staticMonthLength. This
0619: // duplicates the test that occurs in the 7-argument getOffset(),
0620: // however, this is unavoidable. We don't mind because this method, in
0621: // fact, should not be called; internal code should always call the
0622: // 7-argument getOffset(), and outside code should use Calendar.get(int
0623: // field) with fields ZONE_OFFSET and DST_OFFSET. We can't get rid of
0624: // this method because it's public API. - liu 8/10/98
0625: if (month < Calendar.JANUARY || month > Calendar.DECEMBER) {
0626: throw new IllegalArgumentException("Illegal month " + month);
0627: }
0628:
0629: int monthLength, prevMonthLength;
0630: if ((era == GregorianCalendar.AD) && Gregorian.isLeapYear(year)) {
0631: monthLength = staticLeapMonthLength[month];
0632: prevMonthLength = (month > 1) ? staticLeapMonthLength[month - 1]
0633: : 31;
0634: } else {
0635: monthLength = staticMonthLength[month];
0636: prevMonthLength = (month > 1) ? staticMonthLength[month - 1]
0637: : 31;
0638: }
0639:
0640: if (true) {
0641: /* Use this parameter checking code for normal operation. Only one
0642: * of these two blocks should actually get compiled into the class
0643: * file. */
0644: if ((era != GregorianCalendar.AD && era != GregorianCalendar.BC)
0645: || month < Calendar.JANUARY
0646: || month > Calendar.DECEMBER
0647: || day < 1
0648: || day > monthLength
0649: || dayOfWeek < Calendar.SUNDAY
0650: || dayOfWeek > Calendar.SATURDAY
0651: || millis < 0
0652: || millis >= millisPerDay) {
0653: throw new IllegalArgumentException();
0654: }
0655: } else {
0656: /* This parameter checking code is better for debugging, but
0657: * overkill for normal operation. Only one of these two blocks
0658: * should actually get compiled into the class file. */
0659: if (era != GregorianCalendar.AD
0660: && era != GregorianCalendar.BC) {
0661: throw new IllegalArgumentException("Illegal era " + era);
0662: }
0663: if (month < Calendar.JANUARY || month > Calendar.DECEMBER) {
0664: throw new IllegalArgumentException("Illegal month "
0665: + month);
0666: }
0667: if (day < 1 || day > monthLength) {
0668: throw new IllegalArgumentException("Illegal day " + day);
0669: }
0670: if (dayOfWeek < Calendar.SUNDAY
0671: || dayOfWeek > Calendar.SATURDAY) {
0672: throw new IllegalArgumentException(
0673: "Illegal day of week " + dayOfWeek);
0674: }
0675: if (millis < 0 || millis >= millisPerDay) {
0676: throw new IllegalArgumentException("Illegal millis "
0677: + millis);
0678: }
0679: }
0680:
0681: return getOffset(era, year, month, day, dayOfWeek, millis,
0682: monthLength, prevMonthLength);
0683: }
0684:
0685: /**
0686: * Gets offset, for current date, modified in case of
0687: * daylight saving time. This is the offset to add <em>to</em> UTC to get local time.
0688: * Gets the time zone offset, for current date, modified in case of daylight
0689: * saving time. This is the offset to add to UTC to get local time. Assume
0690: * that the start and end month are distinct.
0691: * @param era The era of the given date.
0692: * @param year The year in the given date.
0693: * @param month The month in the given date. Month is 0-based. e.g.,
0694: * 0 for January.
0695: * @param day The day-in-month of the given date.
0696: * @param dayOfWeek The day-of-week of the given date.
0697: * @param millis The milliseconds in day in <em>standard</em> local time.
0698: * @param monthLength The length of the given month in days.
0699: * @param prevMonthLength The length of the previous month in days.
0700: * @return The offset to add to GMT to get local time.
0701: * @exception IllegalArgumentException the era, month, day,
0702: * dayOfWeek, millis, or monthLength parameters are out of range
0703: */
0704: private int getOffset(int era, int year, int month, int day,
0705: int dayOfWeek, int millis, int monthLength,
0706: int prevMonthLength) {
0707: int result = rawOffset;
0708:
0709: // Bail out if we are before the onset of daylight saving time
0710: if (!useDaylight || year < startYear
0711: || era != GregorianCalendar.AD) {
0712: return result;
0713: }
0714:
0715: // Check for southern hemisphere. We assume that the start and end
0716: // month are different.
0717: boolean southern = (startMonth > endMonth);
0718:
0719: // Compare the date to the starting and ending rules.+1 = date>rule, -1
0720: // = date<rule, 0 = date==rule.
0721: int startCompare = compareToRule(month, monthLength,
0722: prevMonthLength, day, dayOfWeek, millis,
0723: startTimeMode == UTC_TIME ? -rawOffset : 0, startMode,
0724: startMonth, startDayOfWeek, startDay, startTime);
0725: int endCompare = 0;
0726:
0727: /* We don't always have to compute endCompare. For many instances,
0728: * startCompare is enough to determine if we are in DST or not. In the
0729: * northern hemisphere, if we are before the start rule, we can't have
0730: * DST. In the southern hemisphere, if we are after the start rule, we
0731: * must have DST. This is reflected in the way the next if statement
0732: * (not the one immediately following) short circuits. */
0733: if (southern != (startCompare >= 0)) {
0734: /* For the ending rule comparison, we add the dstSavings to the millis
0735: * passed in to convert them from standard to wall time. We then must
0736: * normalize the millis to the range 0..millisPerDay-1. */
0737: endCompare = compareToRule(
0738: month,
0739: monthLength,
0740: prevMonthLength,
0741: day,
0742: dayOfWeek,
0743: millis,
0744: endTimeMode == WALL_TIME ? dstSavings
0745: : (endTimeMode == UTC_TIME ? -rawOffset : 0),
0746: endMode, endMonth, endDayOfWeek, endDay, endTime);
0747: }
0748:
0749: // Check for both the northern and southern hemisphere cases. We
0750: // assume that in the northern hemisphere, the start rule is before the
0751: // end rule within the calendar year, and vice versa for the southern
0752: // hemisphere.
0753: if ((!southern && (startCompare >= 0 && endCompare < 0))
0754: || (southern && (startCompare >= 0 || endCompare < 0))) {
0755: result += dstSavings;
0756: }
0757:
0758: return result;
0759: }
0760:
0761: /**
0762: * Compares the given date in the year to the given rule and returns 1, 0,
0763: * or -1, depending on whether the date is after, equal to, or before the
0764: * rule date. The millis are compared directly against the ruleMillis, so
0765: * any standard-daylight adjustments must be handled by the caller.
0766: *
0767: * @return 1 if the date is after the rule date, -1 if the date is before
0768: * the rule date, or 0 if the date is equal to the rule date.
0769: */
0770: private static int compareToRule(int month, int monthLen,
0771: int prevMonthLen, int dayOfMonth, int dayOfWeek,
0772: int millis, int millisDelta, int ruleMode, int ruleMonth,
0773: int ruleDayOfWeek, int ruleDay, int ruleMillis) {
0774: // Make adjustments for startTimeMode and endTimeMode
0775: millis += millisDelta;
0776: while (millis >= millisPerDay) {
0777: millis -= millisPerDay;
0778: ++dayOfMonth;
0779: dayOfWeek = 1 + (dayOfWeek % 7); // dayOfWeek is one-based
0780: if (dayOfMonth > monthLen) {
0781: dayOfMonth = 1;
0782: /* When incrementing the month, it is desirable to overflow
0783: * from DECEMBER to DECEMBER+1, since we use the result to
0784: * compare against a real month. Wraparound of the value
0785: * leads to bug 4173604. */
0786: ++month;
0787: }
0788: }
0789: while (millis < 0) {
0790: millis += millisPerDay;
0791: --dayOfMonth;
0792: dayOfWeek = 1 + ((dayOfWeek + 5) % 7); // dayOfWeek is one-based
0793: if (dayOfMonth < 1) {
0794: dayOfMonth = prevMonthLen;
0795: --month;
0796: }
0797: }
0798:
0799: if (month < ruleMonth) {
0800: return -1;
0801: }
0802: if (month > ruleMonth) {
0803: return 1;
0804: }
0805:
0806: int ruleDayOfMonth = 0;
0807: switch (ruleMode) {
0808: case DOM_MODE:
0809: ruleDayOfMonth = ruleDay;
0810: break;
0811:
0812: case DOW_IN_MONTH_MODE:
0813: // In this case ruleDay is the day-of-week-in-month
0814: if (ruleDay > 0) {
0815: ruleDayOfMonth = 1
0816: + (ruleDay - 1)
0817: * 7
0818: + (7 + ruleDayOfWeek - (dayOfWeek - dayOfMonth + 1))
0819: % 7;
0820: } else {
0821: // Assume ruleDay < 0 here
0822: ruleDayOfMonth = monthLen
0823: + (ruleDay + 1)
0824: * 7
0825: - (7 + (dayOfWeek + monthLen - dayOfMonth) - ruleDayOfWeek)
0826: % 7;
0827: }
0828: break;
0829:
0830: case DOW_GE_DOM_MODE:
0831: ruleDayOfMonth = ruleDay
0832: + (49 + ruleDayOfWeek - ruleDay - dayOfWeek + dayOfMonth)
0833: % 7;
0834: break;
0835:
0836: case DOW_LE_DOM_MODE:
0837: ruleDayOfMonth = ruleDay
0838: - (49 - ruleDayOfWeek + ruleDay + dayOfWeek - dayOfMonth)
0839: % 7;
0840: // Note at this point ruleDayOfMonth may be <1, although it will
0841: // be >=1 for well-formed rules.
0842: break;
0843: }
0844:
0845: if (dayOfMonth < ruleDayOfMonth) {
0846: return -1;
0847: }
0848: if (dayOfMonth > ruleDayOfMonth) {
0849: return 1;
0850: }
0851:
0852: if (millis < ruleMillis) {
0853: return -1;
0854: }
0855: if (millis > ruleMillis) {
0856: return 1;
0857: }
0858: return 0;
0859: }
0860:
0861: /**
0862: * Gets the GMT offset for this time zone.
0863: * @return the GMT offset value in milliseconds
0864: * @see #setRawOffset
0865: */
0866: public int getRawOffset() {
0867: // The given date will be taken into account while
0868: // we have the historical time zone data in place.
0869: return rawOffset;
0870: }
0871:
0872: /**
0873: * Sets the base time zone offset to GMT.
0874: * This is the offset to add to UTC to get local time.
0875: * @see #getRawOffset
0876: */
0877: public void setRawOffset(int offsetMillis) {
0878: this .rawOffset = offsetMillis;
0879: }
0880:
0881: /**
0882: * Sets the amount of time in milliseconds that the clock is advanced
0883: * during daylight saving time.
0884: * @param millisSavedDuringDST the number of milliseconds the time is
0885: * advanced with respect to standard time when the daylight saving time rules
0886: * are in effect. A positive number, typically one hour (3600000).
0887: * @see #getDSTSavings
0888: * @since 1.2
0889: */
0890: public void setDSTSavings(int millisSavedDuringDST) {
0891: if (millisSavedDuringDST <= 0) {
0892: throw new IllegalArgumentException(
0893: "Illegal daylight saving value: "
0894: + millisSavedDuringDST);
0895: }
0896: dstSavings = millisSavedDuringDST;
0897: }
0898:
0899: /**
0900: * Returns the amount of time in milliseconds that the clock is
0901: * advanced during daylight saving time.
0902: *
0903: * @return the number of milliseconds the time is advanced with
0904: * respect to standard time when the daylight saving rules are in
0905: * effect, or 0 (zero) if this time zone doesn't observe daylight
0906: * saving time.
0907: *
0908: * @see #setDSTSavings
0909: * @since 1.2
0910: */
0911: public int getDSTSavings() {
0912: if (useDaylight) {
0913: return dstSavings;
0914: }
0915: return 0;
0916: }
0917:
0918: /**
0919: * Queries if this time zone uses daylight saving time.
0920: * @return true if this time zone uses daylight saving time;
0921: * false otherwise.
0922: */
0923: public boolean useDaylightTime() {
0924: return useDaylight;
0925: }
0926:
0927: /**
0928: * Queries if the given date is in daylight saving time.
0929: * @return true if daylight saving time is in effective at the
0930: * given date; false otherwise.
0931: */
0932: public boolean inDaylightTime(Date date) {
0933: return (getOffset(date.getTime()) != rawOffset);
0934: }
0935:
0936: /**
0937: * Returns a clone of this <code>SimpleTimeZone</code> instance.
0938: * @return a clone of this instance.
0939: */
0940: public Object clone() {
0941: return super .clone();
0942: }
0943:
0944: /**
0945: * Generates the hash code for the SimpleDateFormat object.
0946: * @return the hash code for this object
0947: */
0948: public synchronized int hashCode() {
0949: return startMonth ^ startDay ^ startDayOfWeek ^ startTime
0950: ^ endMonth ^ endDay ^ endDayOfWeek ^ endTime
0951: ^ rawOffset;
0952: }
0953:
0954: /**
0955: * Compares the equality of two <code>SimpleTimeZone</code> objects.
0956: *
0957: * @param obj The <code>SimpleTimeZone</code> object to be compared with.
0958: * @return True if the given <code>obj</code> is the same as this
0959: * <code>SimpleTimeZone</code> object; false otherwise.
0960: */
0961: public boolean equals(Object obj) {
0962: if (this == obj) {
0963: return true;
0964: }
0965: if (!(obj instanceof SimpleTimeZone)) {
0966: return false;
0967: }
0968:
0969: SimpleTimeZone that = (SimpleTimeZone) obj;
0970:
0971: return getID().equals(that.getID()) && hasSameRules(that);
0972: }
0973:
0974: /**
0975: * Returns true if this zone has the same rules and offset as another zone.
0976: * @param other the TimeZone object to be compared with
0977: * @return true if the given zone is a SimpleTimeZone and has the
0978: * same rules and offset as this one
0979: * @since 1.2
0980: */
0981: public boolean hasSameRules(TimeZone other) {
0982: if (this == other) {
0983: return true;
0984: }
0985: if (!(other instanceof SimpleTimeZone)) {
0986: return false;
0987: }
0988: SimpleTimeZone that = (SimpleTimeZone) other;
0989: return rawOffset == that.rawOffset
0990: && useDaylight == that.useDaylight && (!useDaylight
0991: // Only check rules if using DST
0992: || (dstSavings == that.dstSavings
0993: && startMode == that.startMode
0994: && startMonth == that.startMonth
0995: && startDay == that.startDay
0996: && startDayOfWeek == that.startDayOfWeek
0997: && startTime == that.startTime
0998: && startTimeMode == that.startTimeMode
0999: && endMode == that.endMode
1000: && endMonth == that.endMonth
1001: && endDay == that.endDay
1002: && endDayOfWeek == that.endDayOfWeek
1003: && endTime == that.endTime
1004: && endTimeMode == that.endTimeMode && startYear == that.startYear));
1005: }
1006:
1007: /**
1008: * Returns a string representation of this time zone.
1009: * @return a string representation of this time zone.
1010: */
1011: public String toString() {
1012: return getClass().getName() + "[id=" + getID() + ",offset="
1013: + rawOffset + ",dstSavings=" + dstSavings
1014: + ",useDaylight=" + useDaylight + ",startYear="
1015: + startYear + ",startMode=" + startMode
1016: + ",startMonth=" + startMonth + ",startDay=" + startDay
1017: + ",startDayOfWeek=" + startDayOfWeek + ",startTime="
1018: + startTime + ",startTimeMode=" + startTimeMode
1019: + ",endMode=" + endMode + ",endMonth=" + endMonth
1020: + ",endDay=" + endDay + ",endDayOfWeek=" + endDayOfWeek
1021: + ",endTime=" + endTime + ",endTimeMode=" + endTimeMode
1022: + ']';
1023: }
1024:
1025: // =======================privates===============================
1026:
1027: /**
1028: * The month in which daylight saving time starts. This value must be
1029: * between <code>Calendar.JANUARY</code> and
1030: * <code>Calendar.DECEMBER</code> inclusive. This value must not equal
1031: * <code>endMonth</code>.
1032: * <p>If <code>useDaylight</code> is false, this value is ignored.
1033: * @serial
1034: */
1035: private int startMonth;
1036:
1037: /**
1038: * This field has two possible interpretations:
1039: * <dl>
1040: * <dt><code>startMode == DOW_IN_MONTH</code></dt>
1041: * <dd>
1042: * <code>startDay</code> indicates the day of the month of
1043: * <code>startMonth</code> on which daylight
1044: * saving time starts, from 1 to 28, 30, or 31, depending on the
1045: * <code>startMonth</code>.
1046: * </dd>
1047: * <dt><code>startMode != DOW_IN_MONTH</code></dt>
1048: * <dd>
1049: * <code>startDay</code> indicates which <code>startDayOfWeek</code> in th
1050: * month <code>startMonth</code> daylight
1051: * saving time starts on. For example, a value of +1 and a
1052: * <code>startDayOfWeek</code> of <code>Calendar.SUNDAY</code> indicates the
1053: * first Sunday of <code>startMonth</code>. Likewise, +2 would indicate the
1054: * second Sunday, and -1 the last Sunday. A value of 0 is illegal.
1055: * </dd>
1056: * </ul>
1057: * <p>If <code>useDaylight</code> is false, this value is ignored.
1058: * @serial
1059: */
1060: private int startDay;
1061:
1062: /**
1063: * The day of the week on which daylight saving time starts. This value
1064: * must be between <code>Calendar.SUNDAY</code> and
1065: * <code>Calendar.SATURDAY</code> inclusive.
1066: * <p>If <code>useDaylight</code> is false or
1067: * <code>startMode == DAY_OF_MONTH</code>, this value is ignored.
1068: * @serial
1069: */
1070: private int startDayOfWeek;
1071:
1072: /**
1073: * The time in milliseconds after midnight at which daylight saving
1074: * time starts. This value is expressed as wall time, standard time,
1075: * or UTC time, depending on the setting of <code>startTimeMode</code>.
1076: * <p>If <code>useDaylight</code> is false, this value is ignored.
1077: * @serial
1078: */
1079: private int startTime;
1080:
1081: /**
1082: * The format of startTime, either WALL_TIME, STANDARD_TIME, or UTC_TIME.
1083: * @serial
1084: * @since 1.3
1085: */
1086: private int startTimeMode;
1087:
1088: /**
1089: * The month in which daylight saving time ends. This value must be
1090: * between <code>Calendar.JANUARY</code> and
1091: * <code>Calendar.UNDECIMBER</code>. This value must not equal
1092: * <code>startMonth</code>.
1093: * <p>If <code>useDaylight</code> is false, this value is ignored.
1094: * @serial
1095: */
1096: private int endMonth;
1097:
1098: /**
1099: * This field has two possible interpretations:
1100: * <dl>
1101: * <dt><code>endMode == DOW_IN_MONTH</code></dt>
1102: * <dd>
1103: * <code>endDay</code> indicates the day of the month of
1104: * <code>endMonth</code> on which daylight
1105: * saving time ends, from 1 to 28, 30, or 31, depending on the
1106: * <code>endMonth</code>.
1107: * </dd>
1108: * <dt><code>endMode != DOW_IN_MONTH</code></dt>
1109: * <dd>
1110: * <code>endDay</code> indicates which <code>endDayOfWeek</code> in th
1111: * month <code>endMonth</code> daylight
1112: * saving time ends on. For example, a value of +1 and a
1113: * <code>endDayOfWeek</code> of <code>Calendar.SUNDAY</code> indicates the
1114: * first Sunday of <code>endMonth</code>. Likewise, +2 would indicate the
1115: * second Sunday, and -1 the last Sunday. A value of 0 is illegal.
1116: * </dd>
1117: * </ul>
1118: * <p>If <code>useDaylight</code> is false, this value is ignored.
1119: * @serial
1120: */
1121: private int endDay;
1122:
1123: /**
1124: * The day of the week on which daylight saving time ends. This value
1125: * must be between <code>Calendar.SUNDAY</code> and
1126: * <code>Calendar.SATURDAY</code> inclusive.
1127: * <p>If <code>useDaylight</code> is false or
1128: * <code>endMode == DAY_OF_MONTH</code>, this value is ignored.
1129: * @serial
1130: */
1131: private int endDayOfWeek;
1132:
1133: /**
1134: * The time in milliseconds after midnight at which daylight saving
1135: * time ends. This value is expressed as wall time, standard time,
1136: * or UTC time, depending on the setting of <code>endTimeMode</code>.
1137: * <p>If <code>useDaylight</code> is false, this value is ignored.
1138: * @serial
1139: */
1140: private int endTime;
1141:
1142: /**
1143: * The format of endTime, either WALL_TIME, STANDARD_TIME, or UTC_TIME.
1144: * @serial
1145: * @since 1.3
1146: */
1147: private int endTimeMode;
1148:
1149: /**
1150: * The year in which daylight saving time is first observed. This is an AD
1151: * value. If this value is less than 1 then daylight saving time is observed
1152: * for all AD years.
1153: * <p>If <code>useDaylight</code> is false, this value is ignored.
1154: * @serial
1155: */
1156: private int startYear;
1157:
1158: /**
1159: * The offset in milliseconds between this zone and GMT. Negative offsets
1160: * are to the west of Greenwich. To obtain local <em>standard</em> time,
1161: * add the offset to GMT time. To obtain local wall time it may also be
1162: * necessary to add <code>dstSavings</code>.
1163: * @serial
1164: */
1165: private int rawOffset;
1166:
1167: /**
1168: * A boolean value which is true if and only if this zone uses daylight
1169: * saving time. If this value is false, several other fields are ignored.
1170: * @serial
1171: */
1172: private boolean useDaylight = false; // indicate if this time zone uses DST
1173:
1174: private static final int millisPerHour = 60 * 60 * 1000;
1175: private static final int millisPerDay = 24 * millisPerHour;
1176:
1177: /**
1178: * This field was serialized in JDK 1.1, so we have to keep it that way
1179: * to maintain serialization compatibility. However, there's no need to
1180: * recreate the array each time we create a new time zone.
1181: * @serial An array of bytes containing the values {31, 28, 31, 30, 31, 30,
1182: * 31, 31, 30, 31, 30, 31}. This is ignored as of the Java 2 platform v1.2, however, it must
1183: * be streamed out for compatibility with JDK 1.1.
1184: */
1185: private final byte monthLength[] = staticMonthLength;
1186: private final static byte staticMonthLength[] = { 31, 28, 31, 30,
1187: 31, 30, 31, 31, 30, 31, 30, 31 };
1188: private final static byte staticLeapMonthLength[] = { 31, 29, 31,
1189: 30, 31, 30, 31, 31, 30, 31, 30, 31 };
1190:
1191: /**
1192: * Variables specifying the mode of the start rule. Takes the following
1193: * values:
1194: * <dl>
1195: * <dt><code>DOM_MODE</code></dt>
1196: * <dd>
1197: * Exact day of week; e.g., March 1.
1198: * </dd>
1199: * <dt><code>DOW_IN_MONTH_MODE</code></dt>
1200: * <dd>
1201: * Day of week in month; e.g., last Sunday in March.
1202: * </dd>
1203: * <dt><code>DOW_GE_DOM_MODE</code></dt>
1204: * <dd>
1205: * Day of week after day of month; e.g., Sunday on or after March 15.
1206: * </dd>
1207: * <dt><code>DOW_LE_DOM_MODE</code></dt>
1208: * <dd>
1209: * Day of week before day of month; e.g., Sunday on or before March 15.
1210: * </dd>
1211: * </dl>
1212: * The setting of this field affects the interpretation of the
1213: * <code>startDay</code> field.
1214: * <p>If <code>useDaylight</code> is false, this value is ignored.
1215: * @serial
1216: * @since 1.1.4
1217: */
1218: private int startMode;
1219:
1220: /**
1221: * Variables specifying the mode of the end rule. Takes the following
1222: * values:
1223: * <dl>
1224: * <dt><code>DOM_MODE</code></dt>
1225: * <dd>
1226: * Exact day of week; e.g., March 1.
1227: * </dd>
1228: * <dt><code>DOW_IN_MONTH_MODE</code></dt>
1229: * <dd>
1230: * Day of week in month; e.g., last Sunday in March.
1231: * </dd>
1232: * <dt><code>DOW_GE_DOM_MODE</code></dt>
1233: * <dd>
1234: * Day of week after day of month; e.g., Sunday on or after March 15.
1235: * </dd>
1236: * <dt><code>DOW_LE_DOM_MODE</code></dt>
1237: * <dd>
1238: * Day of week before day of month; e.g., Sunday on or before March 15.
1239: * </dd>
1240: * </dl>
1241: * The setting of this field affects the interpretation of the
1242: * <code>endDay</code> field.
1243: * <p>If <code>useDaylight</code> is false, this value is ignored.
1244: * @serial
1245: * @since 1.1.4
1246: */
1247: private int endMode;
1248:
1249: /**
1250: * A positive value indicating the amount of time saved during DST in
1251: * milliseconds.
1252: * Typically one hour (3600000); sometimes 30 minutes (1800000).
1253: * <p>If <code>useDaylight</code> is false, this value is ignored.
1254: * @serial
1255: * @since 1.1.4
1256: */
1257: private int dstSavings;
1258:
1259: /**
1260: * Constants specifying values of startMode and endMode.
1261: */
1262: private static final int DOM_MODE = 1; // Exact day of month, "Mar 1"
1263: private static final int DOW_IN_MONTH_MODE = 2; // Day of week in month, "lastSun"
1264: private static final int DOW_GE_DOM_MODE = 3; // Day of week after day of month, "Sun>=15"
1265: private static final int DOW_LE_DOM_MODE = 4; // Day of week before day of month, "Sun<=21"
1266:
1267: /**
1268: * Constant for a mode of start or end time specified as wall clock
1269: * time. Wall clock time is standard time for the onset rule, and
1270: * daylight time for the end rule.
1271: * @since 1.4
1272: */
1273: public static final int WALL_TIME = 0; // Zero for backward compatibility
1274:
1275: /**
1276: * Constant for a mode of start or end time specified as standard time.
1277: * @since 1.4
1278: */
1279: public static final int STANDARD_TIME = 1;
1280:
1281: /**
1282: * Constant for a mode of start or end time specified as UTC. European
1283: * Union rules are specified as UTC time, for example.
1284: * @since 1.4
1285: */
1286: public static final int UTC_TIME = 2;
1287:
1288: // Proclaim compatibility with 1.1
1289: static final long serialVersionUID = -403250971215465050L;
1290:
1291: // the internal serial version which says which version was written
1292: // - 0 (default) for version up to JDK 1.1.3
1293: // - 1 for version from JDK 1.1.4, which includes 3 new fields
1294: // - 2 for JDK 1.3, which includes 2 new fields
1295: static final int currentSerialVersion = 2;
1296:
1297: /**
1298: * The version of the serialized data on the stream. Possible values:
1299: * <dl>
1300: * <dt><b>0</b> or not present on stream</dt>
1301: * <dd>
1302: * JDK 1.1.3 or earlier.
1303: * </dd>
1304: * <dt><b>1</b></dt>
1305: * <dd>
1306: * JDK 1.1.4 or later. Includes three new fields: <code>startMode</code>,
1307: * <code>endMode</code>, and <code>dstSavings</code>.
1308: * </dd>
1309: * <dt><b>2</b></dt>
1310: * <dd>
1311: * JDK 1.3 or later. Includes two new fields: <code>startTimeMode</code>
1312: * and <code>endTimeMode</code>.
1313: * </dd>
1314: * </dl>
1315: * When streaming out this class, the most recent format
1316: * and the highest allowable <code>serialVersionOnStream</code>
1317: * is written.
1318: * @serial
1319: * @since 1.1.4
1320: */
1321: private int serialVersionOnStream = currentSerialVersion;
1322:
1323: //----------------------------------------------------------------------
1324: // Rule representation
1325: //
1326: // We represent the following flavors of rules:
1327: // 5 the fifth of the month
1328: // lastSun the last Sunday in the month
1329: // lastMon the last Monday in the month
1330: // Sun>=8 first Sunday on or after the eighth
1331: // Sun<=25 last Sunday on or before the 25th
1332: // This is further complicated by the fact that we need to remain
1333: // backward compatible with the 1.1. Finally, we need to minimize
1334: // API changes. In order to satisfy these requirements, we support
1335: // three representation systems, and we translate between them.
1336: //
1337: // INTERNAL REPRESENTATION
1338: // This is the format SimpleTimeZone objects take after construction or
1339: // streaming in is complete. Rules are represented directly, using an
1340: // unencoded format. We will discuss the start rule only below; the end
1341: // rule is analogous.
1342: // startMode Takes on enumerated values DAY_OF_MONTH,
1343: // DOW_IN_MONTH, DOW_AFTER_DOM, or DOW_BEFORE_DOM.
1344: // startDay The day of the month, or for DOW_IN_MONTH mode, a
1345: // value indicating which DOW, such as +1 for first,
1346: // +2 for second, -1 for last, etc.
1347: // startDayOfWeek The day of the week. Ignored for DAY_OF_MONTH.
1348: //
1349: // ENCODED REPRESENTATION
1350: // This is the format accepted by the constructor and by setStartRule()
1351: // and setEndRule(). It uses various combinations of positive, negative,
1352: // and zero values to encode the different rules. This representation
1353: // allows us to specify all the different rule flavors without altering
1354: // the API.
1355: // MODE startMonth startDay startDayOfWeek
1356: // DOW_IN_MONTH_MODE >=0 !=0 >0
1357: // DOM_MODE >=0 >0 ==0
1358: // DOW_GE_DOM_MODE >=0 >0 <0
1359: // DOW_LE_DOM_MODE >=0 <0 <0
1360: // (no DST) don't care ==0 don't care
1361: //
1362: // STREAMED REPRESENTATION
1363: // We must retain binary compatibility with the 1.1. The 1.1 code only
1364: // handles DOW_IN_MONTH_MODE and non-DST mode, the latter indicated by the
1365: // flag useDaylight. When we stream an object out, we translate into an
1366: // approximate DOW_IN_MONTH_MODE representation so the object can be parsed
1367: // and used by 1.1 code. Following that, we write out the full
1368: // representation separately so that contemporary code can recognize and
1369: // parse it. The full representation is written in a "packed" format,
1370: // consisting of a version number, a length, and an array of bytes. Future
1371: // versions of this class may specify different versions. If they wish to
1372: // include additional data, they should do so by storing them after the
1373: // packed representation below.
1374: //----------------------------------------------------------------------
1375:
1376: /**
1377: * Given a set of encoded rules in startDay and startDayOfMonth, decode
1378: * them and set the startMode appropriately. Do the same for endDay and
1379: * endDayOfMonth. Upon entry, the day of week variables may be zero or
1380: * negative, in order to indicate special modes. The day of month
1381: * variables may also be negative. Upon exit, the mode variables will be
1382: * set, and the day of week and day of month variables will be positive.
1383: * This method also recognizes a startDay or endDay of zero as indicating
1384: * no DST.
1385: */
1386: private void decodeRules() {
1387: decodeStartRule();
1388: decodeEndRule();
1389: }
1390:
1391: /**
1392: * Decode the start rule and validate the parameters. The parameters are
1393: * expected to be in encoded form, which represents the various rule modes
1394: * by negating or zeroing certain values. Representation formats are:
1395: * <p>
1396: * <pre>
1397: * DOW_IN_MONTH DOM DOW>=DOM DOW<=DOM no DST
1398: * ------------ ----- -------- -------- ----------
1399: * month 0..11 same same same don't care
1400: * day -5..5 1..31 1..31 -1..-31 0
1401: * dayOfWeek 1..7 0 -1..-7 -1..-7 don't care
1402: * time 0..ONEDAY same same same don't care
1403: * </pre>
1404: * The range for month does not include UNDECIMBER since this class is
1405: * really specific to GregorianCalendar, which does not use that month.
1406: * The range for time includes ONEDAY (vs. ending at ONEDAY-1) because the
1407: * end rule is an exclusive limit point. That is, the range of times that
1408: * are in DST include those >= the start and < the end. For this reason,
1409: * it should be possible to specify an end of ONEDAY in order to include the
1410: * entire day. Although this is equivalent to time 0 of the following day,
1411: * it's not always possible to specify that, for example, on December 31.
1412: * While arguably the start range should still be 0..ONEDAY-1, we keep
1413: * the start and end ranges the same for consistency.
1414: */
1415: private void decodeStartRule() {
1416: useDaylight = (startDay != 0) && (endDay != 0);
1417: if (startDay != 0) {
1418: if (startMonth < Calendar.JANUARY
1419: || startMonth > Calendar.DECEMBER) {
1420: throw new IllegalArgumentException(
1421: "Illegal start month " + startMonth);
1422: }
1423: if (startTime < 0 || startTime >= millisPerDay) {
1424: throw new IllegalArgumentException(
1425: "Illegal start time " + startTime);
1426: }
1427: if (startDayOfWeek == 0) {
1428: startMode = DOM_MODE;
1429: } else {
1430: if (startDayOfWeek > 0) {
1431: startMode = DOW_IN_MONTH_MODE;
1432: } else {
1433: startDayOfWeek = -startDayOfWeek;
1434: if (startDay > 0) {
1435: startMode = DOW_GE_DOM_MODE;
1436: } else {
1437: startDay = -startDay;
1438: startMode = DOW_LE_DOM_MODE;
1439: }
1440: }
1441: if (startDayOfWeek > Calendar.SATURDAY) {
1442: throw new IllegalArgumentException(
1443: "Illegal start day of week "
1444: + startDayOfWeek);
1445: }
1446: }
1447: if (startMode == DOW_IN_MONTH_MODE) {
1448: if (startDay < -5 || startDay > 5) {
1449: throw new IllegalArgumentException(
1450: "Illegal start day of week in month "
1451: + startDay);
1452: }
1453: } else if (startDay < 1
1454: || startDay > staticMonthLength[startMonth]) {
1455: throw new IllegalArgumentException("Illegal start day "
1456: + startDay);
1457: }
1458: }
1459: }
1460:
1461: /**
1462: * Decode the end rule and validate the parameters. This method is exactly
1463: * analogous to decodeStartRule().
1464: * @see decodeStartRule
1465: */
1466: private void decodeEndRule() {
1467: useDaylight = (startDay != 0) && (endDay != 0);
1468: if (endDay != 0) {
1469: if (endMonth < Calendar.JANUARY
1470: || endMonth > Calendar.DECEMBER) {
1471: throw new IllegalArgumentException("Illegal end month "
1472: + endMonth);
1473: }
1474: if (endTime < 0 || endTime >= millisPerDay) {
1475: throw new IllegalArgumentException("Illegal end time "
1476: + endTime);
1477: }
1478: if (endDayOfWeek == 0) {
1479: endMode = DOM_MODE;
1480: } else {
1481: if (endDayOfWeek > 0) {
1482: endMode = DOW_IN_MONTH_MODE;
1483: } else {
1484: endDayOfWeek = -endDayOfWeek;
1485: if (endDay > 0) {
1486: endMode = DOW_GE_DOM_MODE;
1487: } else {
1488: endDay = -endDay;
1489: endMode = DOW_LE_DOM_MODE;
1490: }
1491: }
1492: if (endDayOfWeek > Calendar.SATURDAY) {
1493: throw new IllegalArgumentException(
1494: "Illegal end day of week " + endDayOfWeek);
1495: }
1496: }
1497: if (endMode == DOW_IN_MONTH_MODE) {
1498: if (endDay < -5 || endDay > 5) {
1499: throw new IllegalArgumentException(
1500: "Illegal end day of week in month "
1501: + endDay);
1502: }
1503: } else if (endDay < 1
1504: || endDay > staticMonthLength[endMonth]) {
1505: throw new IllegalArgumentException("Illegal end day "
1506: + endDay);
1507: }
1508: }
1509: }
1510:
1511: /**
1512: * Make rules compatible to 1.1 code. Since 1.1 code only understands
1513: * day-of-week-in-month rules, we must modify other modes of rules to their
1514: * approximate equivalent in 1.1 terms. This method is used when streaming
1515: * out objects of this class. After it is called, the rules will be modified,
1516: * with a possible loss of information. startMode and endMode will NOT be
1517: * altered, even though semantically they should be set to DOW_IN_MONTH_MODE,
1518: * since the rule modification is only intended to be temporary.
1519: */
1520: private void makeRulesCompatible() {
1521: switch (startMode) {
1522: case DOM_MODE:
1523: startDay = 1 + (startDay / 7);
1524: startDayOfWeek = Calendar.SUNDAY;
1525: break;
1526:
1527: case DOW_GE_DOM_MODE:
1528: // A day-of-month of 1 is equivalent to DOW_IN_MONTH_MODE
1529: // that is, Sun>=1 == firstSun.
1530: if (startDay != 1) {
1531: startDay = 1 + (startDay / 7);
1532: }
1533: break;
1534:
1535: case DOW_LE_DOM_MODE:
1536: if (startDay >= 30) {
1537: startDay = -1;
1538: } else {
1539: startDay = 1 + (startDay / 7);
1540: }
1541: break;
1542: }
1543:
1544: switch (endMode) {
1545: case DOM_MODE:
1546: endDay = 1 + (endDay / 7);
1547: endDayOfWeek = Calendar.SUNDAY;
1548: break;
1549:
1550: case DOW_GE_DOM_MODE:
1551: // A day-of-month of 1 is equivalent to DOW_IN_MONTH_MODE
1552: // that is, Sun>=1 == firstSun.
1553: if (endDay != 1) {
1554: endDay = 1 + (endDay / 7);
1555: }
1556: break;
1557:
1558: case DOW_LE_DOM_MODE:
1559: if (endDay >= 30) {
1560: endDay = -1;
1561: } else {
1562: endDay = 1 + (endDay / 7);
1563: }
1564: break;
1565: }
1566:
1567: /*
1568: * Adjust the start and end times to wall time. This works perfectly
1569: * well unless it pushes into the next or previous day. If that
1570: * happens, we attempt to adjust the day rule somewhat crudely. The day
1571: * rules have been forced into DOW_IN_MONTH mode already, so we change
1572: * the day of week to move forward or back by a day. It's possible to
1573: * make a more refined adjustment of the original rules first, but in
1574: * most cases this extra effort will go to waste once we adjust the day
1575: * rules anyway.
1576: */
1577: switch (startTimeMode) {
1578: case UTC_TIME:
1579: startTime += rawOffset;
1580: break;
1581: }
1582: while (startTime < 0) {
1583: startTime += millisPerDay;
1584: startDayOfWeek = 1 + ((startDayOfWeek + 5) % 7); // Back 1 day
1585: }
1586: while (startTime >= millisPerDay) {
1587: startTime -= millisPerDay;
1588: startDayOfWeek = 1 + (startDayOfWeek % 7); // Forward 1 day
1589: }
1590:
1591: switch (endTimeMode) {
1592: case UTC_TIME:
1593: endTime += rawOffset + dstSavings;
1594: break;
1595: case STANDARD_TIME:
1596: endTime += dstSavings;
1597: }
1598: while (endTime < 0) {
1599: endTime += millisPerDay;
1600: endDayOfWeek = 1 + ((endDayOfWeek + 5) % 7); // Back 1 day
1601: }
1602: while (endTime >= millisPerDay) {
1603: endTime -= millisPerDay;
1604: endDayOfWeek = 1 + (endDayOfWeek % 7); // Forward 1 day
1605: }
1606: }
1607:
1608: /**
1609: * Pack the start and end rules into an array of bytes. Only pack
1610: * data which is not preserved by makeRulesCompatible.
1611: */
1612: private byte[] packRules() {
1613: byte[] rules = new byte[6];
1614: rules[0] = (byte) startDay;
1615: rules[1] = (byte) startDayOfWeek;
1616: rules[2] = (byte) endDay;
1617: rules[3] = (byte) endDayOfWeek;
1618:
1619: // As of serial version 2, include time modes
1620: rules[4] = (byte) startTimeMode;
1621: rules[5] = (byte) endTimeMode;
1622:
1623: return rules;
1624: }
1625:
1626: /**
1627: * Given an array of bytes produced by packRules, interpret them
1628: * as the start and end rules.
1629: */
1630: private void unpackRules(byte[] rules) {
1631: startDay = rules[0];
1632: startDayOfWeek = rules[1];
1633: endDay = rules[2];
1634: endDayOfWeek = rules[3];
1635:
1636: // As of serial version 2, include time modes
1637: if (rules.length >= 6) {
1638: startTimeMode = rules[4];
1639: endTimeMode = rules[5];
1640: }
1641: }
1642:
1643: /**
1644: * Pack the start and end times into an array of bytes. This is required
1645: * as of serial version 2.
1646: */
1647: private int[] packTimes() {
1648: int[] times = new int[2];
1649: times[0] = startTime;
1650: times[1] = endTime;
1651: return times;
1652: }
1653:
1654: /**
1655: * Unpack the start and end times from an array of bytes. This is required
1656: * as of serial version 2.
1657: */
1658: private void unpackTimes(int[] times) {
1659: startTime = times[0];
1660: endTime = times[1];
1661: }
1662:
1663: /**
1664: * Save the state of this object to a stream (i.e., serialize it).
1665: *
1666: * @serialData We write out two formats, a JDK 1.1 compatible format, using
1667: * <code>DOW_IN_MONTH_MODE</code> rules, in the required section, followed
1668: * by the full rules, in packed format, in the optional section. The
1669: * optional section will be ignored by JDK 1.1 code upon stream in.
1670: * <p> Contents of the optional section: The length of a byte array is
1671: * emitted (int); this is 4 as of this release. The byte array of the given
1672: * length is emitted. The contents of the byte array are the true values of
1673: * the fields <code>startDay</code>, <code>startDayOfWeek</code>,
1674: * <code>endDay</code>, and <code>endDayOfWeek</code>. The values of these
1675: * fields in the required section are approximate values suited to the rule
1676: * mode <code>DOW_IN_MONTH_MODE</code>, which is the only mode recognized by
1677: * JDK 1.1.
1678: */
1679: private void writeObject(ObjectOutputStream stream)
1680: throws IOException {
1681: // Construct a binary rule
1682: byte[] rules = packRules();
1683: int[] times = packTimes();
1684:
1685: // Convert to 1.1 rules. This step may cause us to lose information.
1686: makeRulesCompatible();
1687:
1688: // Write out the 1.1 rules
1689: stream.defaultWriteObject();
1690:
1691: // Write out the binary rules in the optional data area of the stream.
1692: stream.writeInt(rules.length);
1693: stream.write(rules);
1694: stream.writeObject(times);
1695:
1696: // Recover the original rules. This recovers the information lost
1697: // by makeRulesCompatible.
1698: unpackRules(rules);
1699: unpackTimes(times);
1700: }
1701:
1702: /**
1703: * Reconstitute this object from a stream (i.e., deserialize it).
1704: *
1705: * We handle both JDK 1.1
1706: * binary formats and full formats with a packed byte array.
1707: */
1708: private void readObject(ObjectInputStream stream)
1709: throws IOException, ClassNotFoundException {
1710: stream.defaultReadObject();
1711:
1712: if (serialVersionOnStream < 1) {
1713: // Fix a bug in the 1.1 SimpleTimeZone code -- namely,
1714: // startDayOfWeek and endDayOfWeek were usually uninitialized. We can't do
1715: // too much, so we assume SUNDAY, which actually works most of the time.
1716: if (startDayOfWeek == 0) {
1717: startDayOfWeek = Calendar.SUNDAY;
1718: }
1719: if (endDayOfWeek == 0) {
1720: endDayOfWeek = Calendar.SUNDAY;
1721: }
1722:
1723: // The variables dstSavings, startMode, and endMode are post-1.1, so they
1724: // won't be present if we're reading from a 1.1 stream. Fix them up.
1725: startMode = endMode = DOW_IN_MONTH_MODE;
1726: dstSavings = millisPerHour;
1727: } else {
1728: // For 1.1.4, in addition to the 3 new instance variables, we also
1729: // store the actual rules (which have not be made compatible with 1.1)
1730: // in the optional area. Read them in here and parse them.
1731: int length = stream.readInt();
1732: byte[] rules = new byte[length];
1733: stream.readFully(rules);
1734: unpackRules(rules);
1735: }
1736:
1737: if (serialVersionOnStream >= 2) {
1738: int[] times = (int[]) stream.readObject();
1739: unpackTimes(times);
1740: }
1741:
1742: serialVersionOnStream = currentSerialVersion;
1743: }
1744: }
|