/*
*******************************************************************************
* Copyright (C) 1997-2003, International Business Machines Corporation and    *
* others. All Rights Reserved.                                                *
*******************************************************************************
*
* File SIMPLETZ.H
*
* Modification History:
*
*   Date        Name        Description
*   12/05/96    clhuang     Creation.
*   04/21/97    aliu        Fixed miscellaneous bugs found by inspection and
*                           testing.
*   07/29/97    aliu        Ported source bodies back from Java version with
*                           numerous feature enhancements and bug fixes.
*   08/10/98    stephen     JDK 1.2 sync.
*   09/17/98    stephen     Fixed getOffset() for last hour of year and DST
*   12/02/99    aliu        Added TimeMode and constructor and setStart/EndRule
*                           methods that take TimeMode. Whitespace cleanup.
********************************************************************************
*/

#include "unicode/utypes.h"

#if !UCONFIG_NO_FORMATTING

#include "unicode/simpletz.h"
#include "unicode/gregocal.h"

U_NAMESPACE_BEGIN

UOBJECT_DEFINE_RTTI_IMPLEMENTATION(SimpleTimeZone)

// WARNING: assumes that no rule is measured from the end of February,
// since we don't handle leap years. Could handle assuming always
// Gregorian, since we know they didn't have daylight time when
// Gregorian calendar started.
const int8_t SimpleTimeZone::STATICMONTHLENGTH[] = {31,29,31,30,31,30,31,31,30,31,30,31};

// *****************************************************************************
// class SimpleTimeZone
// *****************************************************************************


SimpleTimeZone::SimpleTimeZone(int32_t rawOffsetGMT, const UnicodeString& ID)
:   TimeZone(ID),
    startMonth(0),
    startDay(0),
    startDayOfWeek(0),
    startTime(0),
    startTimeMode(WALL_TIME),
    endTimeMode(WALL_TIME),
    endMonth(0),
    endDay(0),
    endDayOfWeek(0),
    endTime(0),
    startYear(0),
    rawOffset(rawOffsetGMT),
    useDaylight(FALSE),
    startMode(DOM_MODE),
    endMode(DOM_MODE),
    dstSavings(U_MILLIS_PER_HOUR)
{
}

// -------------------------------------

SimpleTimeZone::SimpleTimeZone(int32_t rawOffsetGMT, const UnicodeString& ID,
    int8_t savingsStartMonth, int8_t savingsStartDay,
    int8_t savingsStartDayOfWeek, int32_t savingsStartTime,
    int8_t savingsEndMonth, int8_t savingsEndDay,
    int8_t savingsEndDayOfWeek, int32_t savingsEndTime,
    UErrorCode& status)
:   TimeZone(ID)
{
    construct(rawOffsetGMT,
              savingsStartMonth, savingsStartDay, savingsStartDayOfWeek,
              savingsStartTime, WALL_TIME,
              savingsEndMonth, savingsEndDay, savingsEndDayOfWeek,
              savingsEndTime, WALL_TIME,
              U_MILLIS_PER_HOUR, status);
}

// -------------------------------------

SimpleTimeZone::SimpleTimeZone(int32_t rawOffsetGMT, const UnicodeString& ID,
    int8_t savingsStartMonth, int8_t savingsStartDay,
    int8_t savingsStartDayOfWeek, int32_t savingsStartTime,
    int8_t savingsEndMonth, int8_t savingsEndDay,
    int8_t savingsEndDayOfWeek, int32_t savingsEndTime,
    int32_t savingsDST, UErrorCode& status)
:   TimeZone(ID)
{
    construct(rawOffsetGMT,
              savingsStartMonth, savingsStartDay, savingsStartDayOfWeek,
              savingsStartTime, WALL_TIME,
              savingsEndMonth, savingsEndDay, savingsEndDayOfWeek,
              savingsEndTime, WALL_TIME,
              savingsDST, status);
}

// -------------------------------------

SimpleTimeZone::SimpleTimeZone(int32_t rawOffsetGMT, const UnicodeString& ID,
    int8_t savingsStartMonth, int8_t savingsStartDay,
    int8_t savingsStartDayOfWeek, int32_t savingsStartTime,
    TimeMode savingsStartTimeMode,
    int8_t savingsEndMonth, int8_t savingsEndDay,
    int8_t savingsEndDayOfWeek, int32_t savingsEndTime,
    TimeMode savingsEndTimeMode,
    int32_t savingsDST, UErrorCode& status)
:   TimeZone(ID)
{
    construct(rawOffsetGMT,
              savingsStartMonth, savingsStartDay, savingsStartDayOfWeek,
              savingsStartTime, savingsStartTimeMode,
              savingsEndMonth, savingsEndDay, savingsEndDayOfWeek,
              savingsEndTime, savingsEndTimeMode,
              savingsDST, status);
}

/**
 * Internal construction method.
 */
void SimpleTimeZone::construct(int32_t rawOffsetGMT,
                               int8_t savingsStartMonth,
                               int8_t savingsStartDay,
                               int8_t savingsStartDayOfWeek,
                               int32_t savingsStartTime,
                               TimeMode savingsStartTimeMode,
                               int8_t savingsEndMonth,
                               int8_t savingsEndDay,
                               int8_t savingsEndDayOfWeek,
                               int32_t savingsEndTime,
                               TimeMode savingsEndTimeMode,
                               int32_t savingsDST,
                               UErrorCode& status)
{
    this->rawOffset      = rawOffsetGMT;
    this->startMonth     = savingsStartMonth;
    this->startDay       = savingsStartDay;
    this->startDayOfWeek = savingsStartDayOfWeek;
    this->startTime      = savingsStartTime;
    this->startTimeMode  = savingsStartTimeMode;
    this->endMonth       = savingsEndMonth;
    this->endDay         = savingsEndDay;
    this->endDayOfWeek   = savingsEndDayOfWeek;
    this->endTime        = savingsEndTime;
    this->endTimeMode    = savingsEndTimeMode;
    this->dstSavings     = savingsDST;
    this->startYear      = 0;
    this->startMode      = DOM_MODE;
    this->endMode        = DOM_MODE;

    decodeRules(status);

    if (savingsDST <= 0) {
        status = U_ILLEGAL_ARGUMENT_ERROR;
    }
}

// -------------------------------------

SimpleTimeZone::~SimpleTimeZone()
{
}

// -------------------------------------

// Called by TimeZone::createDefault(), then clone() inside a Mutex - be careful.
SimpleTimeZone::SimpleTimeZone(const SimpleTimeZone &source)
:   TimeZone(source)
{
    *this = source;
}

// -------------------------------------

// Called by TimeZone::createDefault(), then clone() inside a Mutex - be careful.
SimpleTimeZone &
SimpleTimeZone::operator=(const SimpleTimeZone &right)
{
    if (this != &right)
    {
        TimeZone::operator=(right);
        rawOffset      = right.rawOffset;
        startMonth     = right.startMonth;
        startDay       = right.startDay;
        startDayOfWeek = right.startDayOfWeek;
        startTime      = right.startTime;
        startTimeMode  = right.startTimeMode;
        startMode      = right.startMode;
        endMonth       = right.endMonth;
        endDay         = right.endDay;
        endDayOfWeek   = right.endDayOfWeek;
        endTime        = right.endTime;
        endTimeMode    = right.endTimeMode;
        endMode        = right.endMode;
        startYear      = right.startYear;
        dstSavings     = right.dstSavings;
        useDaylight    = right.useDaylight;
    }
    return *this;
}

// -------------------------------------

UBool
SimpleTimeZone::operator==(const TimeZone& that) const
{
    return ((this == &that) ||
            (getDynamicClassID() == that.getDynamicClassID() &&
            TimeZone::operator==(that) &&
            hasSameRules(that)));
}

// -------------------------------------

// Called by TimeZone::createDefault() inside a Mutex - be careful.
TimeZone*
SimpleTimeZone::clone() const
{
    return new SimpleTimeZone(*this);
}

// -------------------------------------

/**
 * Sets the daylight savings starting year, that is, the year this time zone began
 * observing its specified daylight savings time rules.  The time zone is considered
 * not to observe daylight savings time prior to that year; SimpleTimeZone doesn't
 * support historical daylight-savings-time rules.
 * @param year the daylight savings starting year.
 */
void
SimpleTimeZone::setStartYear(int32_t year)
{
    startYear = year;
}

// -------------------------------------

/**
 * Sets the daylight savings starting rule. For example, in the U.S., Daylight Savings
 * Time starts at the first Sunday in April, at 2 AM in standard time.
 * Therefore, you can set the start rule by calling:
 * setStartRule(TimeFields.APRIL, 1, TimeFields.SUNDAY, 2*60*60*1000);
 * The dayOfWeekInMonth and dayOfWeek parameters together specify how to calculate
 * the exact starting date.  Their exact meaning depend on their respective signs,
 * allowing various types of rules to be constructed, as follows:<ul>
 *   <li>If both dayOfWeekInMonth and dayOfWeek are positive, they specify the
 *       day of week in the month (e.g., (2, WEDNESDAY) is the second Wednesday
 *       of the month).
 *   <li>If dayOfWeek is positive and dayOfWeekInMonth is negative, they specify
 *       the day of week in the month counting backward from the end of the month.
 *       (e.g., (-1, MONDAY) is the last Monday in the month)
 *   <li>If dayOfWeek is zero and dayOfWeekInMonth is positive, dayOfWeekInMonth
 *       specifies the day of the month, regardless of what day of the week it is.
 *       (e.g., (10, 0) is the tenth day of the month)
 *   <li>If dayOfWeek is zero and dayOfWeekInMonth is negative, dayOfWeekInMonth
 *       specifies the day of the month counting backward from the end of the
 *       month, regardless of what day of the week it is (e.g., (-2, 0) is the
 *       next-to-last day of the month).
 *   <li>If dayOfWeek is negative and dayOfWeekInMonth is positive, they specify the
 *       first specified day of the week on or after the specfied day of the month.
 *       (e.g., (15, -SUNDAY) is the first Sunday after the 15th of the month
 *       [or the 15th itself if the 15th is a Sunday].)
 *   <li>If dayOfWeek and DayOfWeekInMonth are both negative, they specify the
 *       last specified day of the week on or before the specified day of the month.
 *       (e.g., (-20, -TUESDAY) is the last Tuesday before the 20th of the month
 *       [or the 20th itself if the 20th is a Tuesday].)</ul>
 * @param month the daylight savings starting month. Month is 0-based.
 * eg, 0 for January.
 * @param dayOfWeekInMonth the daylight savings starting
 * day-of-week-in-month. Please see the member description for an example.
 * @param dayOfWeek the daylight savings starting day-of-week. Please see
 * the member description for an example.
 * @param time the daylight savings starting time. Please see the member
 * description for an example.
 */
 
void
SimpleTimeZone::setStartRule(int32_t month, int32_t dayOfWeekInMonth, int32_t dayOfWeek,
                             int32_t time, TimeMode mode, UErrorCode& status)
{
    startMonth     = (int8_t)month;
    startDay       = (int8_t)dayOfWeekInMonth;
    startDayOfWeek = (int8_t)dayOfWeek;
    startTime      = time;
    startTimeMode  = mode;
    decodeStartRule(status);
}

// -------------------------------------

void 
SimpleTimeZone::setStartRule(int32_t month, int32_t dayOfMonth, 
                             int32_t time, TimeMode mode, UErrorCode& status) 
{
    setStartRule(month, dayOfMonth, 0, time, mode, status);
}

// -------------------------------------

void 
SimpleTimeZone::setStartRule(int32_t month, int32_t dayOfMonth, int32_t dayOfWeek, 
                             int32_t time, TimeMode mode, UBool after, UErrorCode& status)
{
    setStartRule(month, after ? dayOfMonth : -dayOfMonth,
                 -dayOfWeek, time, mode, status);
}

// -------------------------------------

/**
 * Sets the daylight savings ending rule. For example, in the U.S., Daylight
 * Savings Time ends at the last (-1) Sunday in October, at 2 AM in standard time.
 * Therefore, you can set the end rule by calling:
 * setEndRule(TimeFields.OCTOBER, -1, TimeFields.SUNDAY, 2*60*60*1000);
 * Various other types of rules can be specified by manipulating the dayOfWeek
 * and dayOfWeekInMonth parameters.  For complete details, see the documentation
 * for setStartRule().
 * @param month the daylight savings ending month. Month is 0-based.
 * eg, 0 for January.
 * @param dayOfWeekInMonth the daylight savings ending
 * day-of-week-in-month. See setStartRule() for a complete explanation.
 * @param dayOfWeek the daylight savings ending day-of-week. See setStartRule()
 * for a complete explanation.
 * @param time the daylight savings ending time. Please see the member
 * description for an example.
 */

void
SimpleTimeZone::setEndRule(int32_t month, int32_t dayOfWeekInMonth, int32_t dayOfWeek,
                           int32_t time, TimeMode mode, UErrorCode& status)
{
    endMonth     = (int8_t)month;
    endDay       = (int8_t)dayOfWeekInMonth;
    endDayOfWeek = (int8_t)dayOfWeek;
    endTime      = time;
    endTimeMode  = mode;
    decodeEndRule(status);
}

// -------------------------------------

void 
SimpleTimeZone::setEndRule(int32_t month, int32_t dayOfMonth, 
                           int32_t time, TimeMode mode, UErrorCode& status)
{
    setEndRule(month, dayOfMonth, 0, time, mode, status);
}

// -------------------------------------

void 
SimpleTimeZone::setEndRule(int32_t month, int32_t dayOfMonth, int32_t dayOfWeek, 
                           int32_t time, TimeMode mode, UBool after, UErrorCode& status)
{
    setEndRule(month, after ? dayOfMonth : -dayOfMonth,
               -dayOfWeek, time, mode, status);
}

// -------------------------------------

int32_t
SimpleTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day,
                          uint8_t dayOfWeek, int32_t millis, UErrorCode& status) const
{
    // Check the month before indexing into STATICMONTHLENGTH. This
    // duplicates the test that occurs in the 7-argument getOffset(),
    // however, this is unavoidable. We don't mind because this method, in
    // fact, should not be called; internal code should always call the
    // 7-argument getOffset(), and outside code should use Calendar.get(int
    // field) with fields ZONE_OFFSET and DST_OFFSET. We can't get rid of
    // this method because it's public API. - liu 8/10/98
    if(month < UCAL_JANUARY || month > UCAL_DECEMBER) {
        status = U_ILLEGAL_ARGUMENT_ERROR;
        return 0;
    }

    return getOffset(era, year, month, day, dayOfWeek, millis, STATICMONTHLENGTH[month], status);
}

int32_t 
SimpleTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day,
                          uint8_t dayOfWeek, int32_t millis, 
                          int32_t monthLength, UErrorCode& status) const {
    // Check the month before indexing into STATICMONTHLENGTH. This
    // duplicates a test that occurs in the 9-argument getOffset(),
    // however, this is unavoidable. We don't mind because this method, in
    // fact, should not be called; internal code should always call the
    // 9-argument getOffset(), and outside code should use Calendar.get(int
    // field) with fields ZONE_OFFSET and DST_OFFSET. We can't get rid of
    // this method because it's public API. - liu 8/10/98
    if (month < UCAL_JANUARY
        || month > UCAL_DECEMBER) {
        status = U_ILLEGAL_ARGUMENT_ERROR;
        return -1;
    }

    // TODO FIX We don't handle leap years yet!
    int32_t prevMonthLength = (month >= 1) ? STATICMONTHLENGTH[month - 1] : 31;

    return getOffset(era, year, month, day, dayOfWeek, millis,
                     monthLength, prevMonthLength, status);
}

int32_t 
SimpleTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day,
                          uint8_t dayOfWeek, int32_t millis, 
                          int32_t monthLength, int32_t prevMonthLength,
                          UErrorCode& status) const
{
    if(U_FAILURE(status)) return 0;

    if ((era != GregorianCalendar::AD && era != GregorianCalendar::BC)
        || month < UCAL_JANUARY
        || month > UCAL_DECEMBER
        || day < 1
        || day > monthLength
        || dayOfWeek < UCAL_SUNDAY
        || dayOfWeek > UCAL_SATURDAY
        || millis < 0
        || millis >= U_MILLIS_PER_DAY
        || monthLength < 28
        || monthLength > 31
        || prevMonthLength < 28
        || prevMonthLength > 31) {
        status = U_ILLEGAL_ARGUMENT_ERROR;
        return -1;
    }

    int32_t result = rawOffset;

    // Bail out if we are before the onset of daylight savings time
    if(!useDaylight || year < startYear || era != GregorianCalendar::AD) 
        return result;

    // Check for southern hemisphere.  We assume that the start and end
    // month are different.
    UBool southern = (startMonth > endMonth);

    // Compare the date to the starting and ending rules.+1 = date>rule, -1
    // = date<rule, 0 = date==rule.
    int32_t startCompare = compareToRule((int8_t)month, (int8_t)monthLength, (int8_t)prevMonthLength,
                                         (int8_t)day, (int8_t)dayOfWeek, millis,
                                         startTimeMode == UTC_TIME ? -rawOffset : 0,
                                         startMode, (int8_t)startMonth, (int8_t)startDayOfWeek,
                                         (int8_t)startDay, startTime);
    int32_t endCompare = 0;

    /* We don't always have to compute endCompare.  For many instances,
     * startCompare is enough to determine if we are in DST or not.  In the
     * northern hemisphere, if we are before the start rule, we can't have
     * DST.  In the southern hemisphere, if we are after the start rule, we
     * must have DST.  This is reflected in the way the next if statement
     * (not the one immediately following) short circuits. */
    if(southern != (startCompare >= 0)) {
        endCompare = compareToRule((int8_t)month, (int8_t)monthLength, (int8_t)prevMonthLength,
                                   (int8_t)day, (int8_t)dayOfWeek, millis,
                                   endTimeMode == WALL_TIME ? dstSavings :
                                    (endTimeMode == UTC_TIME ? -rawOffset : 0),
                                   endMode, (int8_t)endMonth, (int8_t)endDayOfWeek,
                                   (int8_t)endDay, endTime);
    }

    // Check for both the northern and southern hemisphere cases.  We
    // assume that in the northern hemisphere, the start rule is before the
    // end rule within the calendar year, and vice versa for the southern
    // hemisphere.
    if ((!southern && (startCompare >= 0 && endCompare < 0)) ||
        (southern && (startCompare >= 0 || endCompare < 0)))
        result += dstSavings;

    return result;
}

// -------------------------------------

/**
 * Compare a given date in the year to a rule. Return 1, 0, or -1, depending
 * on whether the date is after, equal to, or before the rule date. The
 * millis are compared directly against the ruleMillis, so any
 * standard-daylight adjustments must be handled by the caller.
 *
 * @return  1 if the date is after the rule date, -1 if the date is before
 *          the rule date, or 0 if the date is equal to the rule date.
 */
int32_t 
SimpleTimeZone::compareToRule(int8_t month, int8_t monthLen, int8_t prevMonthLen,
                              int8_t dayOfMonth,
                              int8_t dayOfWeek, int32_t millis, int32_t millisDelta,
                              EMode ruleMode, int8_t ruleMonth, int8_t ruleDayOfWeek,
                              int8_t ruleDay, int32_t ruleMillis)
{
    // Make adjustments for startTimeMode and endTimeMode
    millis += millisDelta;
    while (millis >= U_MILLIS_PER_DAY) {
        millis -= U_MILLIS_PER_DAY;
        ++dayOfMonth;
        dayOfWeek = (int8_t)(1 + (dayOfWeek % 7)); // dayOfWeek is one-based
        if (dayOfMonth > monthLen) {
            dayOfMonth = 1;
            /* When incrementing the month, it is desirible to overflow
             * from DECEMBER to DECEMBER+1, since we use the result to
             * compare against a real month. Wraparound of the value
             * leads to bug 4173604. */
            ++month;
        }
    }
    while (millis < 0) {
        millis += U_MILLIS_PER_DAY;
        --dayOfMonth;
        dayOfWeek = (int8_t)(1 + ((dayOfWeek+5) % 7)); // dayOfWeek is one-based
        if (dayOfMonth < 1) {
            dayOfMonth = prevMonthLen;
            --month;
        }
    }

    // first compare months.  If they're different, we don't have to worry about days
    // and times
    if (month < ruleMonth) return -1;
    else if (month > ruleMonth) return 1;

    // calculate the actual day of month for the rule
    int32_t ruleDayOfMonth = 0;
    switch (ruleMode)
    {
    // if the mode is day-of-month, the day of month is given
    case DOM_MODE:
        ruleDayOfMonth = ruleDay;
        break;

    // if the mode is day-of-week-in-month, calculate the day-of-month from it
    case DOW_IN_MONTH_MODE:
        // In this case ruleDay is the day-of-week-in-month (this code is using
        // the dayOfWeek and dayOfMonth parameters to figure out the day-of-week
        // of the first day of the month, so it's trusting that they're really
        // consistent with each other)
        if (ruleDay > 0)
            ruleDayOfMonth = 1 + (ruleDay - 1) * 7 +
                (7 + ruleDayOfWeek - (dayOfWeek - dayOfMonth + 1)) % 7;
        
        // if ruleDay is negative (we assume it's not zero here), we have to do
        // the same calculation figuring backward from the last day of the month.
        // (STATICMONTHLENGTH gives us that last day.  We don't take leap years
        // into account, so this may not work right for February.)
        else
        {
             // (again, this code is trusting that dayOfMonth and dayOfMonth are
            // consistent with each other here, since we're using them to figure
            // the day of week of the first of the month)
            ruleDayOfMonth = monthLen + (ruleDay + 1) * 7 -
                (7 + (dayOfWeek + monthLen - dayOfMonth) - ruleDayOfWeek) % 7;
        }
        break;

    case DOW_GE_DOM_MODE:
        ruleDayOfMonth = ruleDay +
            (49 + ruleDayOfWeek - ruleDay - dayOfWeek + dayOfMonth) % 7;
        break;

    case DOW_LE_DOM_MODE:
        ruleDayOfMonth = ruleDay -
            (49 - ruleDayOfWeek + ruleDay + dayOfWeek - dayOfMonth) % 7;
        // Note at this point ruleDayOfMonth may be <1, although it will
        // be >=1 for well-formed rules.
        break;
    }

    // now that we have a real day-in-month for the rule, we can compare days...
    if (dayOfMonth < ruleDayOfMonth) return -1;
    else if (dayOfMonth > ruleDayOfMonth) return 1;

    // ...and if they're equal, we compare times
    if (millis < ruleMillis) return -1;
    else if (millis > ruleMillis) return 1;
    else return 0;
}

// -------------------------------------

int32_t
SimpleTimeZone::getRawOffset() const
{
    return rawOffset;
}

// -------------------------------------

void
SimpleTimeZone::setRawOffset(int32_t offsetMillis)
{
    rawOffset = offsetMillis;
}

// -------------------------------------

void 
SimpleTimeZone::setDSTSavings(int32_t millisSavedDuringDST, UErrorCode& status) 
{
    if (millisSavedDuringDST <= 0) {
        status = U_ILLEGAL_ARGUMENT_ERROR;
    }
    else {
        dstSavings = millisSavedDuringDST;
    }
}

// -------------------------------------

int32_t 
SimpleTimeZone::getDSTSavings() const
{
    return dstSavings;
}

// -------------------------------------

UBool
SimpleTimeZone::useDaylightTime() const
{
    return useDaylight;
}

// -------------------------------------

/**
 * Overrides TimeZone
 * Queries if the given date is in Daylight Savings Time.
 */
UBool SimpleTimeZone::inDaylightTime(UDate date, UErrorCode& status) const
{
    // This method is wasteful since it creates a new GregorianCalendar and
    // deletes it each time it is called.  However, this is a deprecated method
    // and provided only for Java compatibility as of 8/6/97 [LIU].
    if (U_FAILURE(status)) return FALSE;
    GregorianCalendar *gc = new GregorianCalendar(*this, status);
    /* test for NULL */
    if (gc == 0) {
        status = U_MEMORY_ALLOCATION_ERROR;
        return FALSE;
    }
    gc->setTime(date, status);
    UBool result = gc->inDaylightTime(status);
    delete gc;
    return result;
}

// -------------------------------------

/**
 * Return true if this zone has the same rules and offset as another zone.
 * @param other the TimeZone object to be compared with
 * @return true if the given zone has the same rules and offset as this one
 */
UBool 
SimpleTimeZone::hasSameRules(const TimeZone& other) const
{
    if (this == &other) return TRUE;
    if (other.getDynamicClassID() != SimpleTimeZone::getStaticClassID()) return FALSE;
    SimpleTimeZone *that = (SimpleTimeZone*)&other;
    return rawOffset     == that->rawOffset &&
        useDaylight     == that->useDaylight &&
        (!useDaylight
         // Only check rules if using DST
         || (dstSavings     == that->dstSavings &&
             startMode      == that->startMode &&
             startMonth     == that->startMonth &&
             startDay       == that->startDay &&
             startDayOfWeek == that->startDayOfWeek &&
             startTime      == that->startTime &&
             startTimeMode  == that->startTimeMode &&
             endMode        == that->endMode &&
             endMonth       == that->endMonth &&
             endDay         == that->endDay &&
             endDayOfWeek   == that->endDayOfWeek &&
             endTime        == that->endTime &&
             endTimeMode    == that->endTimeMode &&
             startYear      == that->startYear));
}

// -------------------------------------

//----------------------------------------------------------------------
// Rule representation
//
// We represent the following flavors of rules:
//       5        the fifth of the month
//       lastSun  the last Sunday in the month
//       lastMon  the last Monday in the month
//       Sun>=8   first Sunday on or after the eighth
//       Sun<=25  last Sunday on or before the 25th
// This is further complicated by the fact that we need to remain
// backward compatible with the 1.1 FCS.  Finally, we need to minimize
// API changes.  In order to satisfy these requirements, we support
// three representation systems, and we translate between them.
//
// INTERNAL REPRESENTATION
// This is the format SimpleTimeZone objects take after construction or
// streaming in is complete.  Rules are represented directly, using an
// unencoded format.  We will discuss the start rule only below; the end
// rule is analogous.
//   startMode      Takes on enumerated values DAY_OF_MONTH,
//                  DOW_IN_MONTH, DOW_AFTER_DOM, or DOW_BEFORE_DOM.
//   startDay       The day of the month, or for DOW_IN_MONTH mode, a
//                  value indicating which DOW, such as +1 for first,
//                  +2 for second, -1 for last, etc.
//   startDayOfWeek The day of the week.  Ignored for DAY_OF_MONTH.
//
// ENCODED REPRESENTATION
// This is the format accepted by the constructor and by setStartRule()
// and setEndRule().  It uses various combinations of positive, negative,
// and zero values to encode the different rules.  This representation
// allows us to specify all the different rule flavors without altering
// the API.
//   MODE              startMonth    startDay    startDayOfWeek
//   DOW_IN_MONTH_MODE >=0           !=0         >0
//   DOM_MODE          >=0           >0          ==0
//   DOW_GE_DOM_MODE   >=0           >0          <0
//   DOW_LE_DOM_MODE   >=0           <0          <0
//   (no DST)          don't care    ==0         don't care
//
// STREAMED REPRESENTATION
// We must retain binary compatibility with the 1.1 FCS.  The 1.1 code only
// handles DOW_IN_MONTH_MODE and non-DST mode, the latter indicated by the
// flag useDaylight.  When we stream an object out, we translate into an
// approximate DOW_IN_MONTH_MODE representation so the object can be parsed
// and used by 1.1 code.  Following that, we write out the full
// representation separately so that contemporary code can recognize and
// parse it.  The full representation is written in a "packed" format,
// consisting of a version number, a length, and an array of bytes.  Future
// versions of this class may specify different versions.  If they wish to
// include additional data, they should do so by storing them after the
// packed representation below.
//----------------------------------------------------------------------

/**
 * Given a set of encoded rules in startDay and startDayOfMonth, decode
 * them and set the startMode appropriately.  Do the same for endDay and
 * endDayOfMonth.  Upon entry, the day of week variables may be zero or
 * negative, in order to indicate special modes.  The day of month
 * variables may also be negative.  Upon exit, the mode variables will be
 * set, and the day of week and day of month variables will be positive.
 * This method also recognizes a startDay or endDay of zero as indicating
 * no DST.
 */
void 
SimpleTimeZone::decodeRules(UErrorCode& status)
{
    decodeStartRule(status);
    decodeEndRule(status);
}

/**
 * Decode the start rule and validate the parameters.  The parameters are
 * expected to be in encoded form, which represents the various rule modes
 * by negating or zeroing certain values.  Representation formats are:
 * <p>
 * <pre>
 *            DOW_IN_MONTH  DOM    DOW>=DOM  DOW<=DOM  no DST
 *            ------------  -----  --------  --------  ----------
 * month       0..11        same    same      same     don't care
 * day        -5..5         1..31   1..31    -1..-31   0
 * dayOfWeek   1..7         0      -1..-7    -1..-7    don't care
 * time        0..ONEDAY    same    same      same     don't care
 * </pre>
 * The range for month does not include UNDECIMBER since this class is
 * really specific to GregorianCalendar, which does not use that month.
 * The range for time includes ONEDAY (vs. ending at ONEDAY-1) because the
 * end rule is an exclusive limit point.  That is, the range of times that
 * are in DST include those >= the start and < the end.  For this reason,
 * it should be possible to specify an end of ONEDAY in order to include the
 * entire day.  Although this is equivalent to time 0 of the following day,
 * it's not always possible to specify that, for example, on December 31.
 * While arguably the start range should still be 0..ONEDAY-1, we keep
 * the start and end ranges the same for consistency.
 */
void 
SimpleTimeZone::decodeStartRule(UErrorCode& status) 
{
    if(U_FAILURE(status)) return;

    useDaylight = (UBool)((startDay != 0) && (endDay != 0) ? TRUE : FALSE);
    if (useDaylight && dstSavings == 0) {
        dstSavings = U_MILLIS_PER_HOUR;
    }
    if (startDay != 0) {
        if (startMonth < UCAL_JANUARY || startMonth > UCAL_DECEMBER) {
            status = U_ILLEGAL_ARGUMENT_ERROR;
            return;
        }
        if (startTime < 0 || startTime > U_MILLIS_PER_DAY ||
            startTimeMode < WALL_TIME || startTimeMode > UTC_TIME) {
            status = U_ILLEGAL_ARGUMENT_ERROR;
            return;
        }
        if (startDayOfWeek == 0) {
            startMode = DOM_MODE;
        } else {
            if (startDayOfWeek > 0) {
                startMode = DOW_IN_MONTH_MODE;
            } else {
                startDayOfWeek = (int8_t)-startDayOfWeek;
                if (startDay > 0) {
                    startMode = DOW_GE_DOM_MODE;
                } else {
                    startDay = (int8_t)-startDay;
                    startMode = DOW_LE_DOM_MODE;
                }
            }
            if (startDayOfWeek > UCAL_SATURDAY) {
                status = U_ILLEGAL_ARGUMENT_ERROR;
                return;
            }
        }
        if (startMode == DOW_IN_MONTH_MODE) {
            if (startDay < -5 || startDay > 5) {
                status = U_ILLEGAL_ARGUMENT_ERROR;
                return;
            }
        } else if (startDay > STATICMONTHLENGTH[startMonth]) {
            status = U_ILLEGAL_ARGUMENT_ERROR;
            return;
        }
    }
}

/**
 * Decode the end rule and validate the parameters.  This method is exactly
 * analogous to decodeStartRule().
 * @see decodeStartRule
 */
void 
SimpleTimeZone::decodeEndRule(UErrorCode& status) 
{
    if(U_FAILURE(status)) return;

    useDaylight = (UBool)((startDay != 0) && (endDay != 0) ? TRUE : FALSE);
    if (useDaylight && dstSavings == 0) {
        dstSavings = U_MILLIS_PER_HOUR;
    }
    if (endDay != 0) {
        if (endMonth < UCAL_JANUARY || endMonth > UCAL_DECEMBER) {
            status = U_ILLEGAL_ARGUMENT_ERROR;
            return;
        }
        if (endTime < 0 || endTime > U_MILLIS_PER_DAY ||
            endTimeMode < WALL_TIME || endTimeMode > UTC_TIME) {
            status = U_ILLEGAL_ARGUMENT_ERROR;
            return;
        }
        if (endDayOfWeek == 0) {
            endMode = DOM_MODE;
        } else {
            if (endDayOfWeek > 0) {
                endMode = DOW_IN_MONTH_MODE;
            } else {
                endDayOfWeek = (int8_t)-endDayOfWeek;
                if (endDay > 0) {
                    endMode = DOW_GE_DOM_MODE;
                } else {
                    endDay = (int8_t)-endDay;
                    endMode = DOW_LE_DOM_MODE;
                }
            }
            if (endDayOfWeek > UCAL_SATURDAY) {
                status = U_ILLEGAL_ARGUMENT_ERROR;
                return;
            }
        }
        if (endMode == DOW_IN_MONTH_MODE) {
            if (endDay < -5 || endDay > 5) {
                status = U_ILLEGAL_ARGUMENT_ERROR;
                return;
            }
        } else if (endDay > STATICMONTHLENGTH[endMonth]) {
            status = U_ILLEGAL_ARGUMENT_ERROR;
            return;
        }
    }
}

U_NAMESPACE_END

#endif /* #if !UCONFIG_NO_FORMATTING */

//eof
