home *** CD-ROM | disk | FTP | other *** search
/ Apple Developer Connection Student Program / ADC Tools Sampler CD Disk 3 1999.iso / Metrowerks CodeWarrior / Java Support / Java_Source / Java2 / src / java / util / SimpleTimeZone.java < prev    next >
Encoding:
Java Source  |  1999-05-28  |  53.0 KB  |  1,281 lines  |  [TEXT/CWIE]

  1. /*
  2.  * @(#)SimpleTimeZone.java    1.29 98/09/23
  3.  *
  4.  * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved
  5.  * (C) Copyright IBM Corp. 1996 - All Rights Reserved
  6.  *
  7.  * Portions copyright (c) 1996-1998 Sun Microsystems, Inc. All Rights Reserved.
  8.  *
  9.  *   The original version of this source code and documentation is copyrighted
  10.  * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
  11.  * materials are provided under terms of a License Agreement between Taligent
  12.  * and Sun. This technology is protected by multiple US and International
  13.  * patents. This notice and attribution to Taligent may not be removed.
  14.  *   Taligent is a registered trademark of Taligent, Inc.
  15.  *
  16.  * Permission to use, copy, modify, and distribute this software
  17.  * and its documentation for NON-COMMERCIAL purposes and without
  18.  * fee is hereby granted provided that this copyright notice
  19.  * appears in all copies. Please refer to the file "copyright.html"
  20.  * for further important copyright and licensing information.
  21.  *
  22.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
  23.  * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
  24.  * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  25.  * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
  26.  * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
  27.  * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
  28.  *
  29.  */
  30.  
  31. package java.util;
  32.  
  33. import java.io.ObjectInputStream;
  34. import java.io.ObjectOutputStream;
  35. import java.io.IOException;
  36.  
  37. /**
  38.  * <code>SimpleTimeZone</code> is a concrete subclass of <code>TimeZone</code>
  39.  * that represents a time zone for use with a Gregorian calendar. This
  40.  * class does not handle historical changes.
  41.  *
  42.  * <P>
  43.  * Use a negative value for <code>dayOfWeekInMonth</code> to indicate that
  44.  * <code>SimpleTimeZone</code> should count from the end of the month backwards.
  45.  * For example, Daylight Savings Time ends at the last
  46.  * (dayOfWeekInMonth = -1) Sunday in October, at 2 AM in standard time.
  47.  *
  48.  * @see      Calendar
  49.  * @see      GregorianCalendar
  50.  * @see      TimeZone
  51.  * @version  1.29 09/23/98
  52.  * @author   David Goldsmith, Mark Davis, Chen-Lieh Huang, Alan Liu
  53.  */
  54. public class SimpleTimeZone extends TimeZone {
  55.     /**
  56.      * Constructs a SimpleTimeZone with the given base time zone offset from GMT
  57.      * and time zone ID. Timezone IDs can be obtained from
  58.      * TimeZone.getAvailableIDs. Normally you should use TimeZone.getDefault to
  59.      * construct a TimeZone.
  60.      *
  61.      * @param rawOffset  The given base time zone offset to GMT.
  62.      * @param ID         The time zone ID which is obtained from
  63.      *                   TimeZone.getAvailableIDs.
  64.      */
  65.     public SimpleTimeZone(int rawOffset, String ID)
  66.     {
  67.         this.rawOffset = rawOffset;
  68.         setID (ID);
  69.         dstSavings = millisPerHour; // In case user sets rules later
  70.     }
  71.  
  72.     /**
  73.      * Construct a SimpleTimeZone with the given base time zone offset from
  74.      * GMT, time zone ID, time to start and end the daylight time. Timezone IDs
  75.      * can be obtained from TimeZone.getAvailableIDs. Normally you should use
  76.      * TimeZone.getDefault to create a TimeZone. For a time zone that does not
  77.      * use daylight saving time, do not use this constructor; instead you should
  78.      * use SimpleTimeZone(rawOffset, ID).
  79.      *
  80.      * By default, this constructor specifies day-of-week-in-month rules. That
  81.      * is, if the startDay is 1, and the startDayOfWeek is SUNDAY, then this
  82.      * indicates the first Sunday in the startMonth. A startDay of -1 likewise
  83.      * indicates the last Sunday. However, by using negative or zero values for
  84.      * certain parameters, other types of rules can be specified.
  85.      *
  86.      * Day of month. To specify an exact day of the month, such as March 1, set
  87.      * startDayOfWeek to zero.
  88.      *
  89.      * Day of week after day of month. To specify the first day of the week
  90.      * occurring on or after an exact day of the month, make the day of the week
  91.      * negative. For example, if startDay is 5 and startDayOfWeek is -MONDAY,
  92.      * this indicates the first Monday on or after the 5th day of the
  93.      * startMonth.
  94.      *
  95.      * Day of week before day of month. To specify the last day of the week
  96.      * occurring on or before an exact day of the month, make the day of the
  97.      * week and the day of the month negative. For example, if startDay is -21
  98.      * and startDayOfWeek is -WEDNESDAY, this indicates the last Wednesday on or
  99.      * before the 21st of the startMonth.
  100.      *
  101.      * The above examples refer to the startMonth, startDay, and startDayOfWeek;
  102.      * the same applies for the endMonth, endDay, and endDayOfWeek.
  103.      *
  104.      * @param rawOffset       The given base time zone offset to GMT.
  105.      * @param ID              The time zone ID which is obtained from
  106.      *                        TimeZone.getAvailableIDs.
  107.      * @param startMonth      The daylight savings starting month. Month is
  108.      *                        0-based. eg, 0 for January.
  109.      * @param startDay        The daylight savings starting
  110.      *                        day-of-week-in-month. Please see the member
  111.      *                        description for an example.
  112.      * @param startDayOfWeek  The daylight savings starting day-of-week. Please
  113.      *                        see the member description for an example.
  114.      * @param startTime       The daylight savings starting time in local wall
  115.      *                        time, which is standard time in this case. Please see the
  116.      *                        member description for an example.
  117.      * @param endMonth        The daylight savings ending month. Month is
  118.      *                        0-based. eg, 0 for January.
  119.      * @param endDay          The daylight savings ending day-of-week-in-month.
  120.      *                        Please see the member description for an example.
  121.      * @param endDayOfWeek    The daylight savings ending day-of-week. Please
  122.      *                        see the member description for an example.
  123.      * @param endTime         The daylight savings ending time in local wall time,
  124.      *                        which is daylight time in this case. Please see the
  125.      *                        member description for an example.
  126.      * @exception IllegalArgumentException the month, day, dayOfWeek, or time
  127.      * parameters are out of range for the start or end rule
  128.      */
  129.     public SimpleTimeZone(int rawOffset, String ID,
  130.                           int startMonth, int startDay, int startDayOfWeek, int startTime,
  131.                           int endMonth, int endDay, int endDayOfWeek, int endTime)
  132.     {
  133.         this(rawOffset, ID,
  134.              startMonth, startDay, startDayOfWeek, startTime,
  135.              endMonth, endDay, endDayOfWeek, endTime,
  136.              millisPerHour);
  137.     }
  138.  
  139.     /**
  140.      * Constructor.  This constructor is identical to the 10-argument
  141.      * constructor, but also takes a dstSavings parameter.
  142.      * @param dstSavings   The amount of time in ms saved during DST.
  143.      * @exception IllegalArgumentException the month, day, dayOfWeek, or time
  144.      * parameters are out of range for the start or end rule
  145.      */
  146.     public SimpleTimeZone(int rawOffset, String ID,
  147.                           int startMonth, int startDay, int startDayOfWeek, int startTime,
  148.                           int endMonth, int endDay, int endDayOfWeek, int endTime,
  149.                           int dstSavings)
  150.     {
  151.         setID(ID);
  152.         this.rawOffset      = rawOffset;
  153.         this.startMonth     = startMonth;
  154.         this.startDay       = startDay;
  155.         this.startDayOfWeek = startDayOfWeek;
  156.         this.startTime      = startTime;
  157.         this.endMonth       = endMonth;
  158.         this.endDay         = endDay;
  159.         this.endDayOfWeek   = endDayOfWeek;
  160.         this.endTime        = endTime;
  161.         this.dstSavings     = dstSavings;
  162.         // this.useDaylight    = true; // Set by decodeRules
  163.         decodeRules();
  164.         if (dstSavings <= 0) {
  165.             throw new IllegalArgumentException("Illegal DST savings");
  166.         }
  167.     }
  168.  
  169.     /**
  170.      * Sets the daylight savings starting year.
  171.      *
  172.      * @param year  The daylight savings starting year.
  173.      */
  174.     public void setStartYear(int year)
  175.     {
  176.         startYear = year;
  177.     }
  178.  
  179.     /**
  180.      * Sets the daylight savings starting rule. For example, Daylight Savings
  181.      * Time starts at the first Sunday in April, at 2 AM in standard time.
  182.      * Therefore, you can set the start rule by calling:
  183.      * setStartRule(TimeFields.APRIL, 1, TimeFields.SUNDAY, 2*60*60*1000);
  184.      *
  185.      * @param month             The daylight savings starting month. Month is
  186.      *                          0-based. eg, 0 for January.
  187.      * @param dayOfWeekInMonth  The daylight savings starting
  188.      *                          day-of-week-in-month. Please see the member
  189.      *                          description for an example.
  190.      * @param dayOfWeek         The daylight savings starting day-of-week.
  191.      *                          Please see the member description for an
  192.      *                          example.
  193.      * @param time              The daylight savings starting time in local wall
  194.      *                          time, which is standard time in this case. Please see
  195.      *                          the member description for an example.
  196.      * @exception IllegalArgumentException the month, dayOfWeekInMonth,
  197.      * dayOfWeek, or time parameters are out of range
  198.      */
  199.     public void setStartRule(int month, int dayOfWeekInMonth, int dayOfWeek,
  200.                              int time)
  201.     {
  202.         startMonth = month;
  203.         startDay = dayOfWeekInMonth;
  204.         startDayOfWeek = dayOfWeek;
  205.         startTime = time;
  206.         // useDaylight = true; // Set by decodeRules
  207.         decodeStartRule();
  208.     }
  209.  
  210.     /**
  211.      * Sets the DST start rule to a fixed date within a month.
  212.      *
  213.      * @param month         The month in which this rule occurs (0-based).
  214.      * @param dayOfMonth    The date in that month (1-based).
  215.      * @param time          The time of that day (number of millis after midnight)
  216.      *                      when DST takes effect in local wall time, which is
  217.      *                      standard time in this case.
  218.      * @exception IllegalArgumentException the month,
  219.      * dayOfMonth, or time parameters are out of range
  220.      */
  221.     public void setStartRule(int month, int dayOfMonth, int time) {
  222.         setStartRule(month, dayOfMonth, 0, time);
  223.     }
  224.  
  225.     /**
  226.      * Sets the DST start rule to a weekday before or after a give date within
  227.      * a month, e.g., the first Monday on or after the 8th.
  228.      *
  229.      * @param month         The month in which this rule occurs (0-based).
  230.      * @param dayOfMonth    A date within that month (1-based).
  231.      * @param dayOfWeek     The day of the week on which this rule occurs.
  232.      * @param time          The time of that day (number of millis after midnight)
  233.      *                      when DST takes effect in local wall time, which is
  234.      *                      standard time in this case.
  235.      * @param after         If true, this rule selects the first dayOfWeek on
  236.      *                      or after dayOfMonth.  If false, this rule selects
  237.      *                      the last dayOfWeek on or before dayOfMonth.
  238.      * @exception IllegalArgumentException the month, dayOfMonth,
  239.      * dayOfWeek, or time parameters are out of range
  240.      */
  241.     public void setStartRule(int month, int dayOfMonth, int dayOfWeek, int time, boolean after)
  242.     {
  243.         if (after)
  244.             setStartRule(month, dayOfMonth, -dayOfWeek, time);
  245.         else
  246.             setStartRule(month, -dayOfMonth, -dayOfWeek, time);
  247.     }
  248.  
  249.     /**
  250.      * Sets the daylight savings ending rule. For example, Daylight Savings Time
  251.      * ends at the last (-1) Sunday in October, at 2 AM in standard time.
  252.      * Therefore, you can set the end rule by calling:
  253.      * setEndRule(TimeFields.OCTOBER, -1, TimeFields.SUNDAY, 2*60*60*1000);
  254.      *
  255.      * @param month             The daylight savings ending month. Month is
  256.      *                          0-based. eg, 0 for January.
  257.      * @param dayOfWeekInMonth  The daylight savings ending
  258.      *                          day-of-week-in-month. Please see the member
  259.      *                          description for an example.
  260.      * @param dayOfWeek         The daylight savings ending day-of-week. Please
  261.      *                          see the member description for an example.
  262.      * @param time              The daylight savings ending time in local wall time,
  263.      *                          which is daylight time in this case. Please see the
  264.      *                          member description for an example.
  265.      * @exception IllegalArgumentException the month, dayOfWeekInMonth,
  266.      * dayOfWeek, or time parameters are out of range
  267.      */
  268.     public void setEndRule(int month, int dayOfWeekInMonth, int dayOfWeek,
  269.                            int time)
  270.     {
  271.         endMonth = month;
  272.         endDay = dayOfWeekInMonth;
  273.         endDayOfWeek = dayOfWeek;
  274.         endTime = time;
  275.         // useDaylight = true; // Set by decodeRules
  276.         decodeEndRule();
  277.     }
  278.  
  279.     /**
  280.      * Sets the DST end rule to a fixed date within a month.
  281.      *
  282.      * @param month         The month in which this rule occurs (0-based).
  283.      * @param dayOfMonth    The date in that month (1-based).
  284.      * @param time          The time of that day (number of millis after midnight)
  285.      *                      when DST ends in local wall time, which is daylight
  286.      *                      time in this case.
  287.      * @exception IllegalArgumentException the month,
  288.      * dayOfMonth, or time parameters are out of range
  289.      */
  290.     public void setEndRule(int month, int dayOfMonth, int time)
  291.     {
  292.         setEndRule(month, dayOfMonth, 0, time);
  293.     }
  294.  
  295.     /**
  296.      * Sets the DST end rule to a weekday before or after a give date within
  297.      * a month, e.g., the first Monday on or after the 8th.
  298.      *
  299.      * @param month         The month in which this rule occurs (0-based).
  300.      * @param dayOfMonth    A date within that month (1-based).
  301.      * @param dayOfWeek     The day of the week on which this rule occurs.
  302.      * @param time          The time of that day (number of millis after midnight)
  303.      *                      when DST ends in local wall time, which is daylight
  304.      *                      time in this case.
  305.      * @param after         If true, this rule selects the first dayOfWeek on
  306.      *                      or after dayOfMonth.  If false, this rule selects
  307.      *                      the last dayOfWeek on or before dayOfMonth.
  308.      * @exception IllegalArgumentException the month, dayOfMonth,
  309.      * dayOfWeek, or time parameters are out of range
  310.      */
  311.     public void setEndRule(int month, int dayOfMonth, int dayOfWeek, int time, boolean after)
  312.     {
  313.         if (after)
  314.             setEndRule(month, dayOfMonth, -dayOfWeek, time);
  315.         else
  316.             setEndRule(month, -dayOfMonth, -dayOfWeek, time);
  317.     }
  318.  
  319.     /**
  320.      * Overrides TimeZone
  321.      * Gets offset, for current date, modified in case of
  322.      * daylight savings. This is the offset to add *to* UTC to get local time.
  323.      * Gets the time zone offset, for current date, modified in case of daylight
  324.      * savings. This is the offset to add *to* UTC to get local time. Assume
  325.      * that the start and end month are distinct. This method may return incorrect
  326.      * results for rules that start at the end of February (e.g., last Sunday in
  327.      * February) or the beginning of March (e.g., March 1). To avoid such
  328.      * inaccuracies, use <code>Calendar.get(ZONE_OFFSET) +
  329.      * Calendar.get(DST_OFFSET)</code> instead.
  330.      *
  331.      * @param era           The era of the given date.
  332.      * @param year          The year in the given date.
  333.      * @param month         The month in the given date. Month is 0-based. e.g.,
  334.      *                      0 for January.
  335.      * @param day           The day-in-month of the given date.
  336.      * @param dayOfWeek     The day-of-week of the given date.
  337.      * @param millis        The milliseconds in day in <em>standard</em> local time.
  338.      * @return              The offset to add *to* GMT to get local time.
  339.      * @exception IllegalArgumentException the era, month, day,
  340.      * dayOfWeek, or millis parameters are out of range
  341.      */
  342.     public int getOffset(int era, int year, int month, int day, int dayOfWeek,
  343.                          int millis)
  344.     {
  345.         // Check the month before indexing into staticMonthLength. This
  346.     // duplicates the test that occurs in the 7-argument getOffset(),
  347.     // however, this is unavoidable. We don't mind because this method, in
  348.     // fact, should not be called; internal code should always call the
  349.     // 7-argument getOffset(), and outside code should use Calendar.get(int
  350.     // field) with fields ZONE_OFFSET and DST_OFFSET. We can't get rid of
  351.     // this method because it's public API. - liu 8/10/98
  352.         if (month < Calendar.JANUARY
  353.             || month > Calendar.DECEMBER) {
  354.             throw new IllegalArgumentException("Illegal month " + month);
  355.         }
  356.         return getOffset(era, year, month, day, dayOfWeek, millis,
  357.              staticMonthLength[month]);
  358.     }
  359.  
  360.     /**
  361.      * Gets offset, for current date, modified in case of
  362.      * daylight savings. This is the offset to add <em>to</em> UTC to get local time.
  363.      * Gets the time zone offset, for current date, modified in case of daylight
  364.      * savings. This is the offset to add *to* UTC to get local time. Assume
  365.      * that the start and end month are distinct.
  366.      * @param era           The era of the given date.
  367.      * @param year          The year in the given date.
  368.      * @param month         The month in the given date. Month is 0-based. e.g.,
  369.      *                      0 for January.
  370.      * @param day           The day-in-month of the given date.
  371.      * @param dayOfWeek     The day-of-week of the given date.
  372.      * @param millis        The milliseconds in day in <em>standard</em> local time.
  373.      * @param monthLength   The length of the given month in days.
  374.      * @return              The offset to add *to* GMT to get local time.
  375.      * @exception IllegalArgumentException the era, month, day,
  376.      * dayOfWeek, millis, or monthLength parameters are out of range
  377.      */
  378.     int getOffset(int era, int year, int month, int day, int dayOfWeek,
  379.                   int millis, int monthLength) {
  380.         if (true) {
  381.             /* Use this parameter checking code for normal operation.  Only one
  382.              * of these two blocks should actually get compiled into the class
  383.              * file.  */
  384.             if ((era != GregorianCalendar.AD && era != GregorianCalendar.BC)
  385.                 || month < Calendar.JANUARY
  386.                 || month > Calendar.DECEMBER
  387.                 || day < 1
  388.                 || day > monthLength
  389.                 || dayOfWeek < Calendar.SUNDAY
  390.                 || dayOfWeek > Calendar.SATURDAY
  391.                 || millis < 0
  392.                 || millis >= millisPerDay
  393.                 || monthLength < 28
  394.                 || monthLength > 31) {
  395.                 throw new IllegalArgumentException();
  396.             }
  397.         } else {
  398.             /* This parameter checking code is better for debugging, but
  399.              * overkill for normal operation.  Only one of these two blocks
  400.              * should actually get compiled into the class file.  */
  401.             if (era != GregorianCalendar.AD && era != GregorianCalendar.BC) {
  402.                 throw new IllegalArgumentException("Illegal era " + era);
  403.             }
  404.             if (month < Calendar.JANUARY
  405.                 || month > Calendar.DECEMBER) {
  406.                 throw new IllegalArgumentException("Illegal month " + month);
  407.             }
  408.             if (day < 1
  409.                 || day > monthLength) {
  410.                 throw new IllegalArgumentException("Illegal day " + day);
  411.             }
  412.             if (dayOfWeek < Calendar.SUNDAY
  413.                 || dayOfWeek > Calendar.SATURDAY) {
  414.                 throw new IllegalArgumentException("Illegal day of week " + dayOfWeek);
  415.             }
  416.             if (millis < 0
  417.                 || millis >= millisPerDay) {
  418.                 throw new IllegalArgumentException("Illegal millis " + millis);
  419.             }
  420.             if (monthLength < 28
  421.                 || monthLength > 31) {
  422.                 throw new IllegalArgumentException("Illegal month length " + monthLength);
  423.             }
  424.         }
  425.  
  426.         int result = rawOffset;
  427.  
  428.         // Bail out if we are before the onset of daylight savings time
  429.         if (!useDaylight || year < startYear || era != GregorianCalendar.AD) return result;
  430.  
  431.         // Check for southern hemisphere.  We assume that the start and end
  432.         // month are different.
  433.         boolean southern = (startMonth > endMonth);
  434.  
  435.         // Compare the date to the starting and ending rules.+1 = date>rule, -1
  436.         // = date<rule, 0 = date==rule.
  437.         int startCompare = compareToRule(month, monthLength, day, dayOfWeek, millis,
  438.                                          startMode, startMonth, startDayOfWeek,
  439.                                          startDay, startTime);
  440.         int endCompare = 0;
  441.  
  442.         /* We don't always have to compute endCompare.  For many instances,
  443.          * startCompare is enough to determine if we are in DST or not.  In the
  444.          * northern hemisphere, if we are before the start rule, we can't have
  445.          * DST.  In the southern hemisphere, if we are after the start rule, we
  446.          * must have DST.  This is reflected in the way the next if statement
  447.          * (not the one immediately following) short circuits. */
  448.         if (southern != (startCompare >= 0)) {
  449.             /* For the ending rule comparison, we add the dstSavings to the millis
  450.              * passed in to convert them from standard to wall time.  We then must
  451.              * normalize the millis to the range 0..millisPerDay-1. */
  452.             millis += dstSavings; // Assume dstSavings > 0
  453.             while (millis >= millisPerDay) {
  454.                 millis -= millisPerDay;
  455.                 ++day;
  456.                 dayOfWeek = 1 + (dayOfWeek % 7); // Assume dayOfWeek is one-based
  457.                 if (day > monthLength) {
  458.                     day = 1;
  459.                     /* When incrementing the month, it is desirible to overflow
  460.                      * from DECEMBER to DECEMBER+1, since we use the result to
  461.                      * compare against a real month. Wraparound of the value
  462.                      * leads to bug 4173604. */
  463.                     ++month;
  464.                 }
  465.             }
  466.             endCompare = compareToRule(month, monthLength, day, dayOfWeek, millis,
  467.                                        endMode, endMonth, endDayOfWeek,
  468.                                        endDay, endTime);
  469.         }
  470.  
  471.         // Check for both the northern and southern hemisphere cases.  We
  472.         // assume that in the northern hemisphere, the start rule is before the
  473.         // end rule within the calendar year, and vice versa for the southern
  474.         // hemisphere.
  475.         if ((!southern && (startCompare >= 0 && endCompare < 0)) ||
  476.             (southern && (startCompare >= 0 || endCompare < 0)))
  477.             result += dstSavings;
  478.  
  479.         return result;
  480.     }
  481.  
  482.     /**
  483.      * Compare a given date in the year to a rule. Return 1, 0, or -1, depending
  484.      * on whether the date is after, equal to, or before the rule date. The
  485.      * millis are compared directly against the ruleMillis, so any
  486.      * standard-daylight adjustments must be handled by the caller.
  487.      *
  488.      * @return  1 if the date is after the rule date, -1 if the date is before
  489.      *          the rule date, or 0 if the date is equal to the rule date.
  490.      */
  491.     private static int compareToRule(int month, int monthLen, int dayOfMonth,
  492.                                      int dayOfWeek, int millis,
  493.                                      int ruleMode, int ruleMonth, int ruleDayOfWeek,
  494.                                      int ruleDay, int ruleMillis)
  495.     {
  496.         if (month < ruleMonth) return -1;
  497.         else if (month > ruleMonth) return 1;
  498.  
  499.         int ruleDayOfMonth = 0;
  500.         switch (ruleMode)
  501.         {
  502.         case DOM_MODE:
  503.             ruleDayOfMonth = ruleDay;
  504.             break;
  505.         case DOW_IN_MONTH_MODE:
  506.             // In this case ruleDay is the day-of-week-in-month
  507.             if (ruleDay > 0)
  508.                 ruleDayOfMonth = 1 + (ruleDay - 1) * 7 +
  509.                     (7 + ruleDayOfWeek - (dayOfWeek - dayOfMonth + 1)) % 7;
  510.             else // Assume ruleDay < 0 here
  511.             {
  512.                 ruleDayOfMonth = monthLen + (ruleDay + 1) * 7 -
  513.                     (7 + (dayOfWeek + monthLen - dayOfMonth) - ruleDayOfWeek) % 7;
  514.             }
  515.             break;
  516.         case DOW_GE_DOM_MODE:
  517.             ruleDayOfMonth = ruleDay +
  518.                 (49 + ruleDayOfWeek - ruleDay - dayOfWeek + dayOfMonth) % 7;
  519.             break;
  520.         case DOW_LE_DOM_MODE:
  521.             ruleDayOfMonth = ruleDay -
  522.                 (49 - ruleDayOfWeek + ruleDay + dayOfWeek - dayOfMonth) % 7;
  523.             // Note at this point ruleDayOfMonth may be <1, although it will
  524.             // be >=1 for well-formed rules.
  525.             break;
  526.         }
  527.  
  528.         if (dayOfMonth < ruleDayOfMonth) return -1;
  529.         else if (dayOfMonth > ruleDayOfMonth) return 1;
  530.  
  531.         if (millis < ruleMillis) return -1;
  532.         else if (millis > ruleMillis) return 1;
  533.         else return 0;
  534.     }
  535.  
  536.     /**
  537.      * Overrides TimeZone
  538.      * Gets the GMT offset for this time zone.
  539.      */
  540.     public int getRawOffset()
  541.     {
  542.         // The given date will be taken into account while
  543.         // we have the historical time zone data in place.
  544.         return rawOffset;
  545.     }
  546.  
  547.     /**
  548.      * Overrides TimeZone
  549.      * Sets the base time zone offset to GMT.
  550.      * This is the offset to add *to* UTC to get local time.
  551.      * Please see TimeZone.setRawOffset for descriptions on the parameter.
  552.      */
  553.     public void setRawOffset(int offsetMillis)
  554.     {
  555.         this.rawOffset = offsetMillis;
  556.     }
  557.  
  558.     /**
  559.      * Sets the amount of time in ms that the clock is advanced during DST.
  560.      * @param millisSavedDuringDST the number of milliseconds the time is
  561.      * advanced with respect to standard time when the daylight savings rules
  562.      * are in effect. A positive number, typically one hour (3600000).
  563.      */
  564.     public void setDSTSavings(int millisSavedDuringDST) {
  565.         dstSavings = millisSavedDuringDST;
  566.         if (dstSavings <= 0) {
  567.             throw new IllegalArgumentException("Illegal DST savings");
  568.         }
  569.     }
  570.  
  571.     /**
  572.      * Returns the amount of time in ms that the clock is advanced during DST.
  573.      * @return the number of milliseconds the time is
  574.      * advanced with respect to standard time when the daylight savings rules
  575.      * are in effect. A positive number, typically one hour (3600000).
  576.      */
  577.     public int getDSTSavings() {
  578.         return dstSavings;
  579.     }
  580.  
  581.     /**
  582.      * Overrides TimeZone
  583.      * Queries if this time zone uses Daylight Savings Time.
  584.      */
  585.     public boolean useDaylightTime()
  586.     {
  587.         return useDaylight;
  588.     }
  589.  
  590.     /**
  591.      * Overrides TimeZone
  592.      * Queries if the given date is in Daylight Savings Time.
  593.      */
  594.     public boolean inDaylightTime(Date date)
  595.     {
  596.         GregorianCalendar gc = new GregorianCalendar(this);
  597.         gc.setTime(date);
  598.         return gc.inDaylightTime();
  599.     }
  600.  
  601.     /**
  602.      * Overrides Cloneable
  603.      */
  604.     public Object clone()
  605.     {
  606.         return super.clone();
  607.         // other fields are bit-copied
  608.     }
  609.  
  610.     /**
  611.      * Override hashCode.
  612.      * Generates the hash code for the SimpleDateFormat object
  613.      */
  614.     public synchronized int hashCode()
  615.     {
  616.         return startMonth ^ startDay ^ startDayOfWeek ^ startTime ^
  617.             endMonth ^ endDay ^ endDayOfWeek ^ endTime ^ rawOffset;
  618.     }
  619.  
  620.     /**
  621.      * Compares the equality of two SimpleTimeZone objects.
  622.      *
  623.      * @param obj  The SimpleTimeZone object to be compared with.
  624.      * @return     True if the given obj is the same as this SimpleTimeZone
  625.      *             object; false otherwise.
  626.      */
  627.     public boolean equals(Object obj)
  628.     {
  629.         if (this == obj)
  630.             return true;
  631.         if (!(obj instanceof SimpleTimeZone))
  632.             return false;
  633.  
  634.         SimpleTimeZone that = (SimpleTimeZone) obj;
  635.  
  636.         return getID().equals(that.getID()) &&
  637.             hasSameRules(that);
  638.     }
  639.  
  640.     /**
  641.      * Return true if this zone has the same rules and offset as another zone.
  642.      * @param other the TimeZone object to be compared with
  643.      * @return true if the given zone has the same rules and offset as this one
  644.      */
  645.     public boolean hasSameRules(TimeZone other) {
  646.         if (this == other) return true;
  647.         if (!(other instanceof SimpleTimeZone)) return false;
  648.         SimpleTimeZone that = (SimpleTimeZone) other;
  649.         return rawOffset == that.rawOffset &&
  650.             useDaylight == that.useDaylight &&
  651.             (!useDaylight
  652.              // Only check rules if using DST
  653.              || (dstSavings == that.dstSavings &&
  654.                  startMode == that.startMode &&
  655.                  startMonth == that.startMonth &&
  656.                  startDay == that.startDay &&
  657.                  startDayOfWeek == that.startDayOfWeek &&
  658.                  startTime == that.startTime &&
  659.                  endMode == that.endMode &&
  660.                  endMonth == that.endMonth &&
  661.                  endDay == that.endDay &&
  662.                  endDayOfWeek == that.endDayOfWeek &&
  663.                  endTime == that.endTime &&
  664.                  startYear == that.startYear));
  665.     }
  666.  
  667.     /**
  668.      * Return a string representation of this time zone.
  669.      * @return  a string representation of this time zone.
  670.      */
  671.     public String toString() {
  672.         return getClass().getName() +
  673.             "[id=" + getID() +
  674.             ",offset=" + rawOffset +
  675.             ",dstSavings=" + dstSavings +
  676.             ",useDaylight=" + useDaylight +
  677.             ",startYear=" + startYear +
  678.             ",startMode=" + startMode +
  679.             ",startMonth=" + startMonth +
  680.             ",startDay=" + startDay +
  681.             ",startDayOfWeek=" + startDayOfWeek +
  682.             ",startTime=" + startTime +
  683.             ",endMode=" + endMode +
  684.             ",endMonth=" + endMonth +
  685.             ",endDay=" + endDay +
  686.             ",endDayOfWeek=" + endDayOfWeek +
  687.             ",endTime=" + endTime + ']';
  688.     }
  689.  
  690.     // =======================privates===============================
  691.  
  692.     /**
  693.      * The month in which daylight savings time starts.  This value must be
  694.      * between <code>Calendar.JANUARY</code> and
  695.      * <code>Calendar.DECEMBER</code> inclusive.  This value must not equal
  696.      * <code>endMonth</code>.
  697.      * <p>If <code>useDaylight</code> is false, this value is ignored.
  698.      * @serial
  699.      */
  700.     private int startMonth;
  701.  
  702.     /**
  703.      * This field has two possible interpretations:
  704.      * <dl>
  705.      * <dt><code>startMode == DOW_IN_MONTH</code></dt>
  706.      * <dd>
  707.      * <code>startDay</code> indicates the day of the month of
  708.      * <code>startMonth</code> on which daylight
  709.      * savings time starts, from 1 to 28, 30, or 31, depending on the
  710.      * <code>startMonth</code>.
  711.      * </dd>
  712.      * <dt><code>startMode != DOW_IN_MONTH</code></dt>
  713.      * <dd>
  714.      * <code>startDay</code> indicates which <code>startDayOfWeek</code> in th
  715.      * month <code>startMonth</code> daylight
  716.      * savings time starts on.  For example, a value of +1 and a
  717.      * <code>startDayOfWeek</code> of <code>Calendar.SUNDAY</code> indicates the
  718.      * first Sunday of <code>startMonth</code>.  Likewise, +2 would indicate the
  719.      * second Sunday, and -1 the last Sunday.  A value of 0 is illegal.
  720.      * </dd>
  721.      * </ul>
  722.      * <p>If <code>useDaylight</code> is false, this value is ignored.
  723.      * @serial
  724.      */
  725.     private int startDay;
  726.  
  727.     /**
  728.      * The day of the week on which daylight savings time starts.  This value
  729.      * must be between <code>Calendar.SUNDAY</code> and
  730.      * <code>Calendar.SATURDAY</code> inclusive.
  731.      * <p>If <code>useDaylight</code> is false or
  732.      * <code>startMode == DAY_OF_MONTH</code>, this value is ignored.
  733.      * @serial
  734.      */
  735.     private int startDayOfWeek;
  736.  
  737.     /**
  738.      * The time in milliseconds after midnight at which daylight savings
  739.      * time starts.  This value is expressed as <em>wall time</em>, which means
  740.      * it is compared to <em>standard</em> time for the daylight savings start.
  741.      * <p>If <code>useDaylight</code> is false, this value is ignored.
  742.      * @serial
  743.      */
  744.     private int startTime;
  745.  
  746.     /**
  747.      * The month in which daylight savings time ends.  This value must be
  748.      * between <code>Calendar.JANUARY</code> and
  749.      * <code>Calendar.UNDECIMBER</code>.  This value must not equal
  750.      * <code>startMonth</code>.
  751.      * <p>If <code>useDaylight</code> is false, this value is ignored.
  752.      * @serial
  753.      */
  754.     private int endMonth;
  755.  
  756.     /**
  757.      * This field has two possible interpretations:
  758.      * <dl>
  759.      * <dt><code>endMode == DOW_IN_MONTH</code></dt>
  760.      * <dd>
  761.      * <code>endDay</code> indicates the day of the month of
  762.      * <code>endMonth</code> on which daylight
  763.      * savings time ends, from 1 to 28, 30, or 31, depending on the
  764.      * <code>endMonth</code>.
  765.      * </dd>
  766.      * <dt><code>endMode != DOW_IN_MONTH</code></dt>
  767.      * <dd>
  768.      * <code>endDay</code> indicates which <code>endDayOfWeek</code> in th
  769.      * month <code>endMonth</code> daylight
  770.      * savings time ends on.  For example, a value of +1 and a
  771.      * <code>endDayOfWeek</code> of <code>Calendar.SUNDAY</code> indicates the
  772.      * first Sunday of <code>endMonth</code>.  Likewise, +2 would indicate the
  773.      * second Sunday, and -1 the last Sunday.  A value of 0 is illegal.
  774.      * </dd>
  775.      * </ul>
  776.      * <p>If <code>useDaylight</code> is false, this value is ignored.
  777.      * @serial
  778.      */
  779.     private int endDay;
  780.  
  781.     /**
  782.      * The day of the week on which daylight savings time ends.  This value
  783.      * must be between <code>Calendar.SUNDAY</code> and
  784.      * <code>Calendar.SATURDAY</code> inclusive.
  785.      * <p>If <code>useDaylight</code> is false or
  786.      * <code>endMode == DAY_OF_MONTH</code>, this value is ignored.
  787.      * @serial
  788.      */
  789.     private int endDayOfWeek;
  790.  
  791.     /**
  792.      * The time in milliseconds after midnight at which daylight savings
  793.      * time ends.  This value is expressed as <em>wall time</em>, which means
  794.      * it is compared to <em>daylight</em> time for the daylight savings end.
  795.      * <p>If <code>useDaylight</code> is false, this value is ignored.
  796.      * @serial
  797.      */
  798.     private int endTime;
  799.  
  800.     /**
  801.      * The year in which daylight savings time is first observed.  This is an AD
  802.      * value.  If this value is less than 1 then daylight savings is observed
  803.      * for all AD years.
  804.      * <p>If <code>useDaylight</code> is false, this value is ignored.
  805.      * @serial
  806.      */
  807.     private int startYear;
  808.  
  809.     /**
  810.      * The offset in milliseconds between this zone and GMT.  Negative offsets
  811.      * are to the west of Greenwich.  To obtain local <em>standard</em> time,
  812.      * add the offset to GMT time.  To obtain local wall time it may also be
  813.      * necessary to add <code>dstSavings</code>.
  814.      * @serial
  815.      */
  816.     private int rawOffset;
  817.  
  818.     /**
  819.      * A boolean value which is true if and only if this zone uses daylight
  820.      * savings time.  If this value is false, several other fields are ignored.
  821.      * @serial
  822.      */
  823.     private boolean useDaylight=false; // indicate if this time zone uses DST
  824.  
  825.     private static final int millisPerHour = 60*60*1000;
  826.     private static final int millisPerDay  = 24*millisPerHour;
  827.  
  828.     /**
  829.      * This field was serialized in JDK 1.1, so we have to keep it that way
  830.      * to maintain serialization compatibility. However, there's no need to
  831.      * recreate the array each time we create a new time zone.
  832.      * @serial An array of bytes containing the values {31, 28, 31, 30, 31, 30,
  833.      * 31, 31, 30, 31, 30, 31}.  This is ignored as of JDK 1.2, however, it must
  834.      * be streamed out for compatibility with JDK 1.1.
  835.      */
  836.     private final byte monthLength[] = staticMonthLength;
  837.     private final static byte staticMonthLength[] = {31,28,31,30,31,30,31,31,30,31,30,31};
  838.  
  839.     /**
  840.      * Variables specifying the mode of the start rule.  Takes the following
  841.      * values:
  842.      * <dl>
  843.      * <dt><code>DOM_MODE</code></dt>
  844.      * <dd>
  845.      * Exact day of week; e.g., March 1.
  846.      * </dd>
  847.      * <dt><code>DOW_IN_MONTH_MODE</code></dt>    
  848.      * <dd>
  849.      * Day of week in month; e.g., last Sunday in March.
  850.      * </dd>
  851.      * <dt><code>DOW_GE_DOM_MODE</code></dt>
  852.      * <dd>
  853.      * Day of week after day of month; e.g., Sunday on or after March 15.
  854.      * </dd>
  855.      * <dt><code>DOW_LE_DOM_MODE</code></dt>
  856.      * <dd>
  857.      * Day of week before day of month; e.g., Sunday on or before March 15.
  858.      * </dd>
  859.      * </dl>
  860.      * The setting of this field affects the interpretation of the
  861.      * <code>startDay</code> field.
  862.      * <p>If <code>useDaylight</code> is false, this value is ignored.
  863.      * @serial
  864.      * @since JDK1.1.4
  865.      */
  866.     private int startMode;
  867.  
  868.     /**
  869.      * Variables specifying the mode of the end rule.  Takes the following
  870.      * values:
  871.      * <dl>
  872.      * <dt><code>DOM_MODE</code></dt>
  873.      * <dd>
  874.      * Exact day of week; e.g., March 1.
  875.      * </dd>
  876.      * <dt><code>DOW_IN_MONTH_MODE</code></dt>    
  877.      * <dd>
  878.      * Day of week in month; e.g., last Sunday in March.
  879.      * </dd>
  880.      * <dt><code>DOW_GE_DOM_MODE</code></dt>
  881.      * <dd>
  882.      * Day of week after day of month; e.g., Sunday on or after March 15.
  883.      * </dd>
  884.      * <dt><code>DOW_LE_DOM_MODE</code></dt>
  885.      * <dd>
  886.      * Day of week before day of month; e.g., Sunday on or before March 15.
  887.      * </dd>
  888.      * </dl>
  889.      * The setting of this field affects the interpretation of the
  890.      * <code>endDay</code> field.
  891.      * <p>If <code>useDaylight</code> is false, this value is ignored.
  892.      * @serial
  893.      * @since JDK1.1.4
  894.      */
  895.     private int endMode;
  896.  
  897.     /**
  898.      * A positive value indicating the amount of time saved during DST in
  899.      * milliseconds.
  900.      * Typically one hour (3600000); sometimes 30 minutes (1800000).
  901.      * <p>If <code>useDaylight</code> is false, this value is ignored.
  902.      * @serial
  903.      * @since JDK1.1.4
  904.      */
  905.     private int dstSavings;
  906.  
  907.     /**
  908.      * Constants specifying values of startMode and endMode.
  909.      */
  910.     private static final int DOM_MODE          = 1; // Exact day of month, "Mar 1"
  911.     private static final int DOW_IN_MONTH_MODE = 2; // Day of week in month, "lastSun"
  912.     private static final int DOW_GE_DOM_MODE   = 3; // Day of week after day of month, "Sun>=15"
  913.     private static final int DOW_LE_DOM_MODE   = 4; // Day of week before day of month, "Sun<=21"
  914.  
  915.     // Proclaim compatibility with 1.1
  916.     static final long serialVersionUID = -403250971215465050L;
  917.  
  918.     // the internal serial version which says which version was written
  919.     // - 0 (default) for version up to JDK 1.1.3
  920.     // - 1 for version from JDK 1.1.4, which includes 3 new fields
  921.     static final int currentSerialVersion = 1;
  922.  
  923.     /**
  924.      * The version of the serialized data on the stream.  Possible values:
  925.      * <dl>
  926.      * <dt><b>0</b> or not present on stream</dt>
  927.      * <dd>
  928.      * JDK 1.1.3 or earlier.
  929.      * </dd>
  930.      * <dt><b>1</b></dt>
  931.      * <dd>
  932.      * JDK 1.1.4 or later.  Includes three new fields: <code>startMode</code>,
  933.      * <code>endMode</code>, and <code>dstSavings</code>.
  934.      * </dd>
  935.      * </dl>
  936.      * When streaming out this class, the most recent format
  937.      * and the highest allowable <code>serialVersionOnStream</code>
  938.      * is written.
  939.      * @serial
  940.      * @since JDK1.1.4
  941.      */
  942.     private int serialVersionOnStream = currentSerialVersion;
  943.  
  944.     //----------------------------------------------------------------------
  945.     // Rule representation
  946.     //
  947.     // We represent the following flavors of rules:
  948.     //       5        the fifth of the month
  949.     //       lastSun  the last Sunday in the month
  950.     //       lastMon  the last Monday in the month
  951.     //       Sun>=8   first Sunday on or after the eighth
  952.     //       Sun<=25  last Sunday on or before the 25th
  953.     // This is further complicated by the fact that we need to remain
  954.     // backward compatible with the 1.1 FCS.  Finally, we need to minimize
  955.     // API changes.  In order to satisfy these requirements, we support
  956.     // three representation systems, and we translate between them.
  957.     //
  958.     // INTERNAL REPRESENTATION
  959.     // This is the format SimpleTimeZone objects take after construction or
  960.     // streaming in is complete.  Rules are represented directly, using an
  961.     // unencoded format.  We will discuss the start rule only below; the end
  962.     // rule is analogous.
  963.     //   startMode      Takes on enumerated values DAY_OF_MONTH,
  964.     //                  DOW_IN_MONTH, DOW_AFTER_DOM, or DOW_BEFORE_DOM.
  965.     //   startDay       The day of the month, or for DOW_IN_MONTH mode, a
  966.     //                  value indicating which DOW, such as +1 for first,
  967.     //                  +2 for second, -1 for last, etc.
  968.     //   startDayOfWeek The day of the week.  Ignored for DAY_OF_MONTH.
  969.     //
  970.     // ENCODED REPRESENTATION
  971.     // This is the format accepted by the constructor and by setStartRule()
  972.     // and setEndRule().  It uses various combinations of positive, negative,
  973.     // and zero values to encode the different rules.  This representation
  974.     // allows us to specify all the different rule flavors without altering
  975.     // the API.
  976.     //   MODE              startMonth    startDay    startDayOfWeek
  977.     //   DOW_IN_MONTH_MODE >=0           !=0         >0
  978.     //   DOM_MODE          >=0           >0          ==0
  979.     //   DOW_GE_DOM_MODE   >=0           >0          <0
  980.     //   DOW_LE_DOM_MODE   >=0           <0          <0
  981.     //   (no DST)          don't care    ==0         don't care
  982.     //
  983.     // STREAMED REPRESENTATION
  984.     // We must retain binary compatibility with the 1.1 FCS.  The 1.1 code only
  985.     // handles DOW_IN_MONTH_MODE and non-DST mode, the latter indicated by the
  986.     // flag useDaylight.  When we stream an object out, we translate into an
  987.     // approximate DOW_IN_MONTH_MODE representation so the object can be parsed
  988.     // and used by 1.1 code.  Following that, we write out the full
  989.     // representation separately so that contemporary code can recognize and
  990.     // parse it.  The full representation is written in a "packed" format,
  991.     // consisting of a version number, a length, and an array of bytes.  Future
  992.     // versions of this class may specify different versions.  If they wish to
  993.     // include additional data, they should do so by storing them after the
  994.     // packed representation below.
  995.     //----------------------------------------------------------------------
  996.  
  997.     /**
  998.      * Given a set of encoded rules in startDay and startDayOfMonth, decode
  999.      * them and set the startMode appropriately.  Do the same for endDay and
  1000.      * endDayOfMonth.  Upon entry, the day of week variables may be zero or
  1001.      * negative, in order to indicate special modes.  The day of month
  1002.      * variables may also be negative.  Upon exit, the mode variables will be
  1003.      * set, and the day of week and day of month variables will be positive.
  1004.      * This method also recognizes a startDay or endDay of zero as indicating
  1005.      * no DST.
  1006.      */
  1007.     private void decodeRules()
  1008.     {
  1009.         decodeStartRule();
  1010.         decodeEndRule();
  1011.     }
  1012.  
  1013.     /**
  1014.      * Decode the start rule and validate the parameters.  The parameters are
  1015.      * expected to be in encoded form, which represents the various rule modes
  1016.      * by negating or zeroing certain values.  Representation formats are:
  1017.      * <p>
  1018.      * <pre>
  1019.      *            DOW_IN_MONTH  DOM    DOW>=DOM  DOW<=DOM  no DST
  1020.      *            ------------  -----  --------  --------  ----------
  1021.      * month       0..11        same    same      same     don't care
  1022.      * day        -5..5         1..31   1..31    -1..-31   0
  1023.      * dayOfWeek   1..7         0      -1..-7    -1..-7    don't care
  1024.      * time        0..ONEDAY    same    same      same     don't care
  1025.      * </pre>
  1026.      * The range for month does not include UNDECIMBER since this class is
  1027.      * really specific to GregorianCalendar, which does not use that month.
  1028.      * The range for time includes ONEDAY (vs. ending at ONEDAY-1) because the
  1029.      * end rule is an exclusive limit point.  That is, the range of times that
  1030.      * are in DST include those >= the start and < the end.  For this reason,
  1031.      * it should be possible to specify an end of ONEDAY in order to include the
  1032.      * entire day.  Although this is equivalent to time 0 of the following day,
  1033.      * it's not always possible to specify that, for example, on December 31.
  1034.      * While arguably the start range should still be 0..ONEDAY-1, we keep
  1035.      * the start and end ranges the same for consistency.
  1036.      */
  1037.     private void decodeStartRule() {
  1038.         useDaylight = (startDay != 0) && (endDay != 0);
  1039.         if (startDay != 0) {
  1040.             if (startMonth < Calendar.JANUARY || startMonth > Calendar.DECEMBER) {
  1041.                 throw new IllegalArgumentException(
  1042.                         "Illegal start month " + startMonth);
  1043.             }
  1044.             if (startTime < 0 || startTime > millisPerDay) {
  1045.                 throw new IllegalArgumentException(
  1046.                         "Illegal start time " + startTime);
  1047.             }
  1048.             if (startDayOfWeek == 0) {
  1049.                 startMode = DOM_MODE;
  1050.             } else {
  1051.                 if (startDayOfWeek > 0) {
  1052.                     startMode = DOW_IN_MONTH_MODE;
  1053.                 } else {
  1054.                     startDayOfWeek = -startDayOfWeek;
  1055.                     if (startDay > 0) {
  1056.                         startMode = DOW_GE_DOM_MODE;
  1057.                     } else {
  1058.                         startDay = -startDay;
  1059.                         startMode = DOW_LE_DOM_MODE;
  1060.                     }
  1061.                 }
  1062.                 if (startDayOfWeek > Calendar.SATURDAY) {
  1063.                     throw new IllegalArgumentException(
  1064.                            "Illegal start day of week " + startDayOfWeek);
  1065.                 }
  1066.             }
  1067.             if (startMode == DOW_IN_MONTH_MODE) {
  1068.                 if (startDay < -5 || startDay > 5) {
  1069.                     throw new IllegalArgumentException(
  1070.                             "Illegal start day of week in month " + startDay);
  1071.                 }
  1072.             } else if (startDay > staticMonthLength[startMonth]) {
  1073.                 throw new IllegalArgumentException(
  1074.                         "Illegal start day " + startDay);
  1075.             }
  1076.         }
  1077.     }
  1078.  
  1079.     /**
  1080.      * Decode the end rule and validate the parameters.  This method is exactly
  1081.      * analogous to decodeStartRule().
  1082.      * @see decodeStartRule
  1083.      */
  1084.     private void decodeEndRule() {
  1085.         useDaylight = (startDay != 0) && (endDay != 0);
  1086.         if (endDay != 0) {
  1087.             if (endMonth < Calendar.JANUARY || endMonth > Calendar.DECEMBER) {
  1088.                 throw new IllegalArgumentException(
  1089.                         "Illegal end month " + endMonth);
  1090.             }
  1091.             if (endTime < 0 || endTime > millisPerDay) {
  1092.                 throw new IllegalArgumentException(
  1093.                         "Illegal end time " + endTime);
  1094.             }
  1095.             if (endDayOfWeek == 0) {
  1096.                 endMode = DOM_MODE;
  1097.             } else {
  1098.                 if (endDayOfWeek > 0) {
  1099.                     endMode = DOW_IN_MONTH_MODE;
  1100.                 } else {
  1101.                     endDayOfWeek = -endDayOfWeek;
  1102.                     if (endDay > 0) {
  1103.                         endMode = DOW_GE_DOM_MODE;
  1104.                     } else {
  1105.                         endDay = -endDay;
  1106.                         endMode = DOW_LE_DOM_MODE;
  1107.                     }
  1108.                 }
  1109.                 if (endDayOfWeek > Calendar.SATURDAY) {
  1110.                     throw new IllegalArgumentException(
  1111.                            "Illegal end day of week " + endDayOfWeek);
  1112.                 }
  1113.             }
  1114.             if (endMode == DOW_IN_MONTH_MODE) {
  1115.                 if (endDay < -5 || endDay > 5) {
  1116.                     throw new IllegalArgumentException(
  1117.                             "Illegal end day of week in month " + endDay);
  1118.                 }
  1119.             } else if (endDay > staticMonthLength[endMonth]) {
  1120.                 throw new IllegalArgumentException(
  1121.                         "Illegal end day " + endDay);
  1122.             }
  1123.         }
  1124.     }
  1125.  
  1126.     /**
  1127.      * Make rules compatible to 1.1 FCS code.  Since 1.1 FCS code only understands
  1128.      * day-of-week-in-month rules, we must modify other modes of rules to their
  1129.      * approximate equivalent in 1.1 FCS terms.  This method is used when streaming
  1130.      * out objects of this class.  After it is called, the rules will be modified,
  1131.      * with a possible loss of information.  startMode and endMode will NOT be
  1132.      * altered, even though semantically they should be set to DOW_IN_MONTH_MODE,
  1133.      * since the rule modification is only intended to be temporary.
  1134.      */
  1135.     private void makeRulesCompatible()
  1136.     {
  1137.         switch (startMode)
  1138.         {
  1139.         case DOM_MODE:
  1140.             startDay = 1 + (startDay / 7);
  1141.             startDayOfWeek = Calendar.SUNDAY;
  1142.             break;
  1143.         case DOW_GE_DOM_MODE:
  1144.             // A day-of-month of 1 is equivalent to DOW_IN_MONTH_MODE
  1145.             // that is, Sun>=1 == firstSun.
  1146.             if (startDay != 1)
  1147.                 startDay = 1 + (startDay / 7);
  1148.             break;
  1149.         case DOW_LE_DOM_MODE:
  1150.             if (startDay >= 30)
  1151.                 startDay = -1;
  1152.             else
  1153.                 startDay = 1 + (startDay / 7);
  1154.             break;
  1155.         }
  1156.  
  1157.         switch (endMode)
  1158.         {
  1159.         case DOM_MODE:
  1160.             endDay = 1 + (endDay / 7);
  1161.             endDayOfWeek = Calendar.SUNDAY;
  1162.             break;
  1163.         case DOW_GE_DOM_MODE:
  1164.             // A day-of-month of 1 is equivalent to DOW_IN_MONTH_MODE
  1165.             // that is, Sun>=1 == firstSun.
  1166.             if (endDay != 1)
  1167.                 endDay = 1 + (endDay / 7);
  1168.             break;
  1169.         case DOW_LE_DOM_MODE:
  1170.             if (endDay >= 30)
  1171.                 endDay = -1;
  1172.             else
  1173.                 endDay = 1 + (endDay / 7);
  1174.             break;
  1175.         }
  1176.     }
  1177.  
  1178.     /**
  1179.      * Pack the start and end rules into an array of bytes.  Only pack
  1180.      * data which is not preserved by makeRulesCompatible.
  1181.      */
  1182.     private byte[] packRules()
  1183.     {
  1184.         byte[] rules = new byte[4];
  1185.         rules[0] = (byte)startDay;
  1186.         rules[1] = (byte)startDayOfWeek;
  1187.         rules[2] = (byte)endDay;
  1188.         rules[3] = (byte)endDayOfWeek;
  1189.         return rules;
  1190.     }
  1191.  
  1192.     /**
  1193.      * Given an array of bytes produced by packRules, interpret them
  1194.      * as the start and end rules.
  1195.      */
  1196.     private void unpackRules(byte[] rules)
  1197.     {
  1198.         startDay       = rules[0];
  1199.         startDayOfWeek = rules[1];
  1200.         endDay         = rules[2];
  1201.         endDayOfWeek   = rules[3];
  1202.     }
  1203.  
  1204.     /**
  1205.      * Save the state of this object to a stream (i.e., serialize it).
  1206.      *
  1207.      * @serialData We write out two formats, a JDK 1.1 compatible format, using
  1208.      * <code>DOW_IN_MONTH_MODE</code> rules, in the required section, followed
  1209.      * by the full rules, in packed format, in the optional section.  The
  1210.      * optional section will be ignored by JDK 1.1 code upon stream in.
  1211.      * <p> Contents of the optional section: The length of a byte array is
  1212.      * emitted (int); this is 4 as of this release. The byte array of the given
  1213.      * length is emitted. The contents of the byte array are the true values of
  1214.      * the fields <code>startDay</code>, <code>startDayOfWeek</code>,
  1215.      * <code>endDay</code>, and <code>endDayOfWeek</code>.  The values of these
  1216.      * fields in the required section are approximate values suited to the rule
  1217.      * mode <code>DOW_IN_MONTH_MODE</code>, which is the only mode recognized by
  1218.      * JDK 1.1.
  1219.      */
  1220.     private void writeObject(ObjectOutputStream stream)
  1221.          throws IOException
  1222.     {
  1223.         // Construct a binary rule
  1224.         byte[] rules = packRules();
  1225.  
  1226.         // Convert to 1.1 FCS rules.  This step may cause us to lose information.
  1227.         makeRulesCompatible();
  1228.  
  1229.         // Write out the 1.1 FCS rules
  1230.         stream.defaultWriteObject();
  1231.  
  1232.         // Write out the binary rules in the optional data area of the stream.
  1233.         stream.writeInt(rules.length);
  1234.         stream.write(rules);
  1235.  
  1236.         // Recover the original rules.  This recovers the information lost
  1237.         // by makeRulesCompatible.
  1238.         unpackRules(rules);
  1239.     }
  1240.  
  1241.     /**
  1242.      * Reconstitute this object from a stream (i.e., deserialize it).
  1243.      *
  1244.      * We handle both JDK 1.1
  1245.      * binary formats and full formats with a packed byte array.
  1246.      */
  1247.     private void readObject(ObjectInputStream stream)
  1248.          throws IOException, ClassNotFoundException
  1249.     {
  1250.         stream.defaultReadObject();
  1251.  
  1252.         if (serialVersionOnStream < 1)
  1253.         {
  1254.             // Fix a bug in the 1.1 SimpleTimeZone code -- namely,
  1255.             // startDayOfWeek and endDayOfWeek were usually uninitialized.  We can't do
  1256.             // too much, so we assume SUNDAY, which actually works most of the time.
  1257.             if (startDayOfWeek == 0) startDayOfWeek = Calendar.SUNDAY;
  1258.             if (endDayOfWeek == 0) endDayOfWeek = Calendar.SUNDAY;
  1259.  
  1260.             // The variables dstSavings, startMode, and endMode are post-1.1, so they
  1261.             // won't be present if we're reading from a 1.1 stream.  Fix them up.
  1262.             startMode = endMode = DOW_IN_MONTH_MODE;
  1263.             dstSavings = millisPerHour;
  1264.         }
  1265.         else
  1266.         {
  1267.             // For 1.1.4, in addition to the 3 new instance variables, we also
  1268.             // store the actual rules (which have not be made compatible with 1.1)
  1269.             // in the optional area.  Read them in here and parse them.
  1270.             int length = stream.readInt();
  1271.             byte[] rules = new byte[length];
  1272.             stream.readFully(rules);
  1273.             unpackRules(rules);
  1274.         }
  1275.  
  1276.         serialVersionOnStream = currentSerialVersion;
  1277.     }
  1278. }
  1279.  
  1280. //eof
  1281.