// © 2016 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html /* ******************************************************************************* * Copyright (C) 1997-2013, 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 "utypeinfo.h" // for 'typeid' to work #include "unicode/utypes.h" #if !UCONFIG_NO_FORMATTING #include "unicode/simpletz.h" #include "unicode/gregocal.h" #include "unicode/smpdtfmt.h" #include "cmemory.h" #include "gregoimp.h" #include "umutex.h" U_NAMESPACE_BEGIN UOBJECT_DEFINE_RTTI_IMPLEMENTATION(SimpleTimeZone) // Use only for decodeStartRule() and decodeEndRule() where the year is not // available. Set February to 29 days to accommodate rules with that date // and day-of-week-on-or-before-that-date mode (DOW_LE_DOM_MODE). // The compareToRule() method adjusts to February 28 in non-leap years. // // For actual getOffset() calculations, use Grego::monthLength() and // Grego::previousMonthLength() which take leap years into account. // We handle leap years 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}; static const char16_t DST_STR[] = {0x0028,0x0044,0x0053,0x0054,0x0029,0}; // "(DST)" static const char16_t STD_STR[] = {0x0028,0x0053,0x0054,0x0044,0x0029,0}; // "(STD)" // ***************************************************************************** // class SimpleTimeZone // ***************************************************************************** SimpleTimeZone::SimpleTimeZone(int32_t rawOffsetGMT, const UnicodeString& ID) : BasicTimeZone(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) { clearTransitionRules(); } // ------------------------------------- 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) : BasicTimeZone(ID) { clearTransitionRules(); 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) : BasicTimeZone(ID) { clearTransitionRules(); 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) : BasicTimeZone(ID) { clearTransitionRules(); 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() { deleteTransitionRules(); } // ------------------------------------- // Called by TimeZone::createDefault(), then clone() inside a Mutex - be careful. SimpleTimeZone::SimpleTimeZone(const SimpleTimeZone &source) : BasicTimeZone(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; clearTransitionRules(); } return *this; } // ------------------------------------- bool SimpleTimeZone::operator==(const TimeZone& that) const { return ((this == &that) || (typeid(*this) == typeid(that) && TimeZone::operator==(that) && hasSameRules(that))); } // ------------------------------------- // Called by TimeZone::createDefault() inside a Mutex - be careful. SimpleTimeZone* 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; transitionRulesInitialized = false; } // ------------------------------------- /** * 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: * @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); transitionRulesInitialized = false; } // ------------------------------------- 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); transitionRulesInitialized = false; } // ------------------------------------- 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 calling Grego::monthLength(). 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, Grego::monthLength(year, 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 calling Grego::monthLength(). 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; } // We ignore monthLength because it can be derived from year and month. // This is so that February in leap years is calculated correctly. // We keep this argument in this function for backwards compatibility. return getOffset(era, year, month, day, dayOfWeek, millis, Grego::monthLength(year, month), Grego::previousMonthLength(year, 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, 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= 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; } void SimpleTimeZone::getOffsetFromLocal(UDate date, UTimeZoneLocalOption nonExistingTimeOpt, UTimeZoneLocalOption duplicatedTimeOpt, int32_t& rawOffsetGMT, int32_t& savingsDST, UErrorCode& status) const { if (U_FAILURE(status)) { return; } rawOffsetGMT = getRawOffset(); int32_t year, month, dom, dow, millis; int32_t day = ClockMath::floorDivide(date, U_MILLIS_PER_DAY, &millis); Grego::dayToFields(day, year, month, dom, dow, status); if (U_FAILURE(status)) return; savingsDST = getOffset(GregorianCalendar::AD, year, month, dom, (uint8_t) dow, millis, Grego::monthLength(year, month), status) - rawOffsetGMT; if (U_FAILURE(status)) { return; } UBool recalc = false; // Now we need some adjustment if (savingsDST > 0) { if ((nonExistingTimeOpt & kStdDstMask) == kStandard || ((nonExistingTimeOpt & kStdDstMask) != kDaylight && (nonExistingTimeOpt & kFormerLatterMask) != kLatter)) { date -= getDSTSavings(); recalc = true; } } else { if ((duplicatedTimeOpt & kStdDstMask) == kDaylight || ((duplicatedTimeOpt & kStdDstMask) != kStandard && (duplicatedTimeOpt & kFormerLatterMask) == kFormer)) { date -= getDSTSavings(); recalc = true; } } if (recalc) { day = ClockMath::floorDivide(date, U_MILLIS_PER_DAY, &millis); Grego::dayToFields(day, year, month, dom, dow, status); if (U_FAILURE(status)) return; savingsDST = getOffset(GregorianCalendar::AD, year, month, dom, (uint8_t) dow, millis, Grego::monthLength(year, month), status) - rawOffsetGMT; } } // ------------------------------------- /** * 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 desirable 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; // Adjust the ruleDay to the monthLen, for non-leap year February 29 rule days. if (ruleDay > monthLen) { ruleDay = monthLen; } 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. else { // (again, this code is trusting that dayOfWeek 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; transitionRulesInitialized = false; } // ------------------------------------- void SimpleTimeZone::setDSTSavings(int32_t millisSavedDuringDST, UErrorCode& status) { if (millisSavedDuringDST == 0) { status = U_ILLEGAL_ARGUMENT_ERROR; } else { dstSavings = millisSavedDuringDST; } transitionRulesInitialized = false; } // ------------------------------------- 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 nullptr */ 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 (typeid(*this) != typeid(other)) 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 < 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<1 || 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<1 || endDay > STATICMONTHLENGTH[endMonth]) { status = U_ILLEGAL_ARGUMENT_ERROR; return; } } } UBool SimpleTimeZone::getNextTransition(UDate base, UBool inclusive, TimeZoneTransition& result) const { if (!useDaylight) { return false; } UErrorCode status = U_ZERO_ERROR; checkTransitionRules(status); if (U_FAILURE(status)) { return false; } UDate firstTransitionTime = firstTransition->getTime(); if (base < firstTransitionTime || (inclusive && base == firstTransitionTime)) { result = *firstTransition; } UDate stdDate, dstDate; UBool stdAvail = stdRule->getNextStart(base, dstRule->getRawOffset(), dstRule->getDSTSavings(), inclusive, stdDate); UBool dstAvail = dstRule->getNextStart(base, stdRule->getRawOffset(), stdRule->getDSTSavings(), inclusive, dstDate); if (stdAvail && (!dstAvail || stdDate < dstDate)) { result.setTime(stdDate); result.setFrom(*dstRule); result.setTo(*stdRule); return true; } if (dstAvail && (!stdAvail || dstDate < stdDate)) { result.setTime(dstDate); result.setFrom(*stdRule); result.setTo(*dstRule); return true; } return false; } UBool SimpleTimeZone::getPreviousTransition(UDate base, UBool inclusive, TimeZoneTransition& result) const { if (!useDaylight) { return false; } UErrorCode status = U_ZERO_ERROR; checkTransitionRules(status); if (U_FAILURE(status)) { return false; } UDate firstTransitionTime = firstTransition->getTime(); if (base < firstTransitionTime || (!inclusive && base == firstTransitionTime)) { return false; } UDate stdDate, dstDate; UBool stdAvail = stdRule->getPreviousStart(base, dstRule->getRawOffset(), dstRule->getDSTSavings(), inclusive, stdDate); UBool dstAvail = dstRule->getPreviousStart(base, stdRule->getRawOffset(), stdRule->getDSTSavings(), inclusive, dstDate); if (stdAvail && (!dstAvail || stdDate > dstDate)) { result.setTime(stdDate); result.setFrom(*dstRule); result.setTo(*stdRule); return true; } if (dstAvail && (!stdAvail || dstDate > stdDate)) { result.setTime(dstDate); result.setFrom(*stdRule); result.setTo(*dstRule); return true; } return false; } void SimpleTimeZone::clearTransitionRules() { initialRule = nullptr; firstTransition = nullptr; stdRule = nullptr; dstRule = nullptr; transitionRulesInitialized = false; } void SimpleTimeZone::deleteTransitionRules() { if (initialRule != nullptr) { delete initialRule; } if (firstTransition != nullptr) { delete firstTransition; } if (stdRule != nullptr) { delete stdRule; } if (dstRule != nullptr) { delete dstRule; } clearTransitionRules(); } /* * Lazy transition rules initializer * * Note On the removal of UMTX_CHECK from checkTransitionRules(): * * It would be faster to have a UInitOnce as part of a SimpleTimeZone object, * which would avoid needing to lock a mutex to check the initialization state. * But we can't easily because simpletz.h is a public header, and including * a UInitOnce as a member of SimpleTimeZone would publicly expose internal ICU headers. * * Alternatively we could have a pointer to a UInitOnce in the SimpleTimeZone object, * allocate it in the constructors. This would be a more intrusive change, but doable * if performance turns out to be an issue. */ void SimpleTimeZone::checkTransitionRules(UErrorCode& status) const { if (U_FAILURE(status)) { return; } static UMutex gLock; umtx_lock(&gLock); if (!transitionRulesInitialized) { SimpleTimeZone *ncThis = const_cast(this); ncThis->initTransitionRules(status); } umtx_unlock(&gLock); } void SimpleTimeZone::initTransitionRules(UErrorCode& status) { if (U_FAILURE(status)) { return; } if (transitionRulesInitialized) { return; } deleteTransitionRules(); UnicodeString tzid; getID(tzid); if (useDaylight) { DateTimeRule* dtRule; DateTimeRule::TimeRuleType timeRuleType; UDate firstStdStart, firstDstStart; // Create a TimeZoneRule for daylight saving time timeRuleType = (startTimeMode == STANDARD_TIME) ? DateTimeRule::STANDARD_TIME : ((startTimeMode == UTC_TIME) ? DateTimeRule::UTC_TIME : DateTimeRule::WALL_TIME); switch (startMode) { case DOM_MODE: dtRule = new DateTimeRule(startMonth, startDay, startTime, timeRuleType); break; case DOW_IN_MONTH_MODE: dtRule = new DateTimeRule(startMonth, startDay, startDayOfWeek, startTime, timeRuleType); break; case DOW_GE_DOM_MODE: dtRule = new DateTimeRule(startMonth, startDay, startDayOfWeek, true, startTime, timeRuleType); break; case DOW_LE_DOM_MODE: dtRule = new DateTimeRule(startMonth, startDay, startDayOfWeek, false, startTime, timeRuleType); break; default: status = U_INVALID_STATE_ERROR; return; } // Check for Null pointer if (dtRule == nullptr) { status = U_MEMORY_ALLOCATION_ERROR; return; } // For now, use ID + "(DST)" as the name dstRule = new AnnualTimeZoneRule(tzid+UnicodeString(DST_STR), getRawOffset(), getDSTSavings(), dtRule, startYear, AnnualTimeZoneRule::MAX_YEAR); // Check for Null pointer if (dstRule == nullptr) { status = U_MEMORY_ALLOCATION_ERROR; deleteTransitionRules(); return; } // Calculate the first DST start time dstRule->getFirstStart(getRawOffset(), 0, firstDstStart); // Create a TimeZoneRule for standard time timeRuleType = (endTimeMode == STANDARD_TIME) ? DateTimeRule::STANDARD_TIME : ((endTimeMode == UTC_TIME) ? DateTimeRule::UTC_TIME : DateTimeRule::WALL_TIME); switch (endMode) { case DOM_MODE: dtRule = new DateTimeRule(endMonth, endDay, endTime, timeRuleType); break; case DOW_IN_MONTH_MODE: dtRule = new DateTimeRule(endMonth, endDay, endDayOfWeek, endTime, timeRuleType); break; case DOW_GE_DOM_MODE: dtRule = new DateTimeRule(endMonth, endDay, endDayOfWeek, true, endTime, timeRuleType); break; case DOW_LE_DOM_MODE: dtRule = new DateTimeRule(endMonth, endDay, endDayOfWeek, false, endTime, timeRuleType); break; } // Check for Null pointer if (dtRule == nullptr) { status = U_MEMORY_ALLOCATION_ERROR; deleteTransitionRules(); return; } // For now, use ID + "(STD)" as the name stdRule = new AnnualTimeZoneRule(tzid+UnicodeString(STD_STR), getRawOffset(), 0, dtRule, startYear, AnnualTimeZoneRule::MAX_YEAR); //Check for Null pointer if (stdRule == nullptr) { status = U_MEMORY_ALLOCATION_ERROR; deleteTransitionRules(); return; } // Calculate the first STD start time stdRule->getFirstStart(getRawOffset(), dstRule->getDSTSavings(), firstStdStart); // Create a TimeZoneRule for initial time if (firstStdStart < firstDstStart) { initialRule = new InitialTimeZoneRule(tzid+UnicodeString(DST_STR), getRawOffset(), dstRule->getDSTSavings()); if (initialRule == nullptr) { status = U_MEMORY_ALLOCATION_ERROR; deleteTransitionRules(); return; } firstTransition = new TimeZoneTransition(firstStdStart, *initialRule, *stdRule); } else { initialRule = new InitialTimeZoneRule(tzid+UnicodeString(STD_STR), getRawOffset(), 0); if (initialRule == nullptr) { status = U_MEMORY_ALLOCATION_ERROR; deleteTransitionRules(); return; } firstTransition = new TimeZoneTransition(firstDstStart, *initialRule, *dstRule); } if (firstTransition == nullptr) { status = U_MEMORY_ALLOCATION_ERROR; deleteTransitionRules(); return; } } else { // Create a TimeZoneRule for initial time initialRule = new InitialTimeZoneRule(tzid, getRawOffset(), 0); // Check for null pointer. if (initialRule == nullptr) { status = U_MEMORY_ALLOCATION_ERROR; deleteTransitionRules(); return; } } transitionRulesInitialized = true; } int32_t SimpleTimeZone::countTransitionRules(UErrorCode& /*status*/) const { return (useDaylight) ? 2 : 0; } void SimpleTimeZone::getTimeZoneRules(const InitialTimeZoneRule*& initial, const TimeZoneRule* trsrules[], int32_t& trscount, UErrorCode& status) const { if (U_FAILURE(status)) { return; } checkTransitionRules(status); if (U_FAILURE(status)) { return; } initial = initialRule; int32_t cnt = 0; if (stdRule != nullptr) { if (cnt < trscount) { trsrules[cnt++] = stdRule; } if (cnt < trscount) { trsrules[cnt++] = dstRule; } } trscount = cnt; } U_NAMESPACE_END #endif /* #if !UCONFIG_NO_FORMATTING */ //eof