/*
*******************************************************************************
* Copyright (C) 1997-1999, 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/simpletz.h"
#include "unicode/gregocal.h"
#include "tzdat.h"
U_NAMESPACE_BEGIN
const char SimpleTimeZone::fgClassID = 0; // Value is irrelevant
// 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,28,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);
}
/**
* Construct from memory-mapped data. For private use by TimeZone.
*/
SimpleTimeZone::SimpleTimeZone(const StandardZone& stdZone,
const UnicodeString& ID)
: TimeZone(ID)
{
UErrorCode status = U_ZERO_ERROR;
construct(stdZone.gmtOffset,
0, 0, 0, 0, WALL_TIME,
0, 0, 0, 0, WALL_TIME,
0, status);
}
/**
* Construct from memory-mapped data. For private use by TimeZone.
*/
SimpleTimeZone::SimpleTimeZone(const DSTZone& dstZone,
const UnicodeString& ID)
: TimeZone(ID)
{
UErrorCode status = U_ZERO_ERROR;
construct(dstZone.gmtOffset,
dstZone.onsetRule.month, dstZone.onsetRule.dowim,
dstZone.onsetRule.dow,
dstZone.onsetRule.time * (int32_t)60000,
(TimeMode)dstZone.onsetRule.mode,
dstZone.ceaseRule.month, dstZone.ceaseRule.dowim,
dstZone.ceaseRule.dow,
dstZone.ceaseRule.time * (int32_t)60000,
(TimeMode)dstZone.ceaseRule.mode,
dstZone.dstSavings * (int32_t)60000, 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:
* - 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).
*
- 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)
*
- 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)
*
- 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).
*
- 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].)
*
- 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].)
* @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);
}
// -------------------------------------
// deprecated version
int32_t
SimpleTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day,
uint8_t dayOfWeek, int32_t millis) const
{
UErrorCode status = U_ZERO_ERROR;
return getOffset(era, year, month, day, dayOfWeek, millis, 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 < Calendar::JANUARY || month > Calendar::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 < Calendar::JANUARY
|| month > Calendar::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 < Calendar::JANUARY
|| month > Calendar::DECEMBER
|| day < 1
|| day > monthLength
|| dayOfWeek < Calendar::SUNDAY
|| dayOfWeek > Calendar::SATURDAY
|| millis < 0
|| millis >= U_MILLIS_PER_DAY
|| monthLength < 28
|| monthLength > 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= 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;
}
#ifdef ICU_SIMPLETIMEZONE_USE_DEPRECATES
// -------------------------------------
// deprecated
void
SimpleTimeZone::setDSTSavings(int32_t millisSavedDuringDST)
{
UErrorCode status = U_ZERO_ERROR;
setDSTSavings(millisSavedDuringDST, status);
}
#endif
// -------------------------------------
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);
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:
*
*
* 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
*
* 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 < Calendar::JANUARY || startMonth > Calendar::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 > Calendar::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 < Calendar::JANUARY || endMonth > Calendar::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 > Calendar::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
//eof