X-Git-Url: https://git.saurik.com/apple/icu.git/blobdiff_plain/374ca955a76ecab1204ca8bfa63ff9238d998416..4f1e1a09ce4daed860e35d359ce2fceccb0764e8:/icuSources/i18n/simpletz.cpp?ds=sidebyside diff --git a/icuSources/i18n/simpletz.cpp b/icuSources/i18n/simpletz.cpp index 393d339d..57a7ba8e 100644 --- a/icuSources/i18n/simpletz.cpp +++ b/icuSources/i18n/simpletz.cpp @@ -1,25 +1,29 @@ +// © 2016 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html /* -******************************************************************************* -* 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. -******************************************************************************** -*/ + ******************************************************************************* + * 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" @@ -27,24 +31,38 @@ #include "unicode/simpletz.h" #include "unicode/gregocal.h" +#include "unicode/smpdtfmt.h" + +#include "gregoimp.h" +#include "umutex.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 +// Use only for decodeStartRule() and decodeEndRule() where the year is not +// available. Set February to 29 days to accomodate 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 UChar DST_STR[] = {0x0028,0x0044,0x0053,0x0054,0x0029,0}; // "(DST)" +static const UChar STD_STR[] = {0x0028,0x0053,0x0054,0x0044,0x0029,0}; // "(STD)" + + // ***************************************************************************** // class SimpleTimeZone // ***************************************************************************** SimpleTimeZone::SimpleTimeZone(int32_t rawOffsetGMT, const UnicodeString& ID) -: TimeZone(ID), +: BasicTimeZone(ID), startMonth(0), startDay(0), startDayOfWeek(0), @@ -62,6 +80,7 @@ SimpleTimeZone::SimpleTimeZone(int32_t rawOffsetGMT, const UnicodeString& ID) endMode(DOM_MODE), dstSavings(U_MILLIS_PER_HOUR) { + clearTransitionRules(); } // ------------------------------------- @@ -72,8 +91,9 @@ SimpleTimeZone::SimpleTimeZone(int32_t rawOffsetGMT, const UnicodeString& ID, int8_t savingsEndMonth, int8_t savingsEndDay, int8_t savingsEndDayOfWeek, int32_t savingsEndTime, UErrorCode& status) -: TimeZone(ID) +: BasicTimeZone(ID) { + clearTransitionRules(); construct(rawOffsetGMT, savingsStartMonth, savingsStartDay, savingsStartDayOfWeek, savingsStartTime, WALL_TIME, @@ -90,8 +110,9 @@ SimpleTimeZone::SimpleTimeZone(int32_t rawOffsetGMT, const UnicodeString& ID, int8_t savingsEndMonth, int8_t savingsEndDay, int8_t savingsEndDayOfWeek, int32_t savingsEndTime, int32_t savingsDST, UErrorCode& status) -: TimeZone(ID) +: BasicTimeZone(ID) { + clearTransitionRules(); construct(rawOffsetGMT, savingsStartMonth, savingsStartDay, savingsStartDayOfWeek, savingsStartTime, WALL_TIME, @@ -110,8 +131,9 @@ SimpleTimeZone::SimpleTimeZone(int32_t rawOffsetGMT, const UnicodeString& ID, int8_t savingsEndDayOfWeek, int32_t savingsEndTime, TimeMode savingsEndTimeMode, int32_t savingsDST, UErrorCode& status) -: TimeZone(ID) +: BasicTimeZone(ID) { + clearTransitionRules(); construct(rawOffsetGMT, savingsStartMonth, savingsStartDay, savingsStartDayOfWeek, savingsStartTime, savingsStartTimeMode, @@ -155,7 +177,7 @@ void SimpleTimeZone::construct(int32_t rawOffsetGMT, decodeRules(status); - if (savingsDST <= 0) { + if (savingsDST == 0) { status = U_ILLEGAL_ARGUMENT_ERROR; } } @@ -164,13 +186,14 @@ void SimpleTimeZone::construct(int32_t rawOffsetGMT, SimpleTimeZone::~SimpleTimeZone() { + deleteTransitionRules(); } // ------------------------------------- // Called by TimeZone::createDefault(), then clone() inside a Mutex - be careful. SimpleTimeZone::SimpleTimeZone(const SimpleTimeZone &source) -: TimeZone(source) +: BasicTimeZone(source) { *this = source; } @@ -200,6 +223,7 @@ SimpleTimeZone::operator=(const SimpleTimeZone &right) startYear = right.startYear; dstSavings = right.dstSavings; useDaylight = right.useDaylight; + clearTransitionRules(); } return *this; } @@ -210,7 +234,7 @@ UBool SimpleTimeZone::operator==(const TimeZone& that) const { return ((this == &that) || - (getDynamicClassID() == that.getDynamicClassID() && + (typeid(*this) == typeid(that) && TimeZone::operator==(that) && hasSameRules(that))); } @@ -237,6 +261,7 @@ void SimpleTimeZone::setStartYear(int32_t year) { startYear = year; + transitionRulesInitialized = FALSE; } // ------------------------------------- @@ -290,6 +315,7 @@ SimpleTimeZone::setStartRule(int32_t month, int32_t dayOfWeekInMonth, int32_t da startTime = time; startTimeMode = mode; decodeStartRule(status); + transitionRulesInitialized = FALSE; } // ------------------------------------- @@ -341,6 +367,7 @@ SimpleTimeZone::setEndRule(int32_t month, int32_t dayOfWeekInMonth, int32_t dayO endTime = time; endTimeMode = mode; decodeEndRule(status); + transitionRulesInitialized = FALSE; } // ------------------------------------- @@ -368,7 +395,7 @@ 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 + // 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 @@ -380,14 +407,15 @@ SimpleTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day, return 0; } - return getOffset(era, year, month, day, dayOfWeek, millis, STATICMONTHLENGTH[month], status); + 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 indexing into STATICMONTHLENGTH. This + 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 @@ -400,11 +428,13 @@ SimpleTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day, return -1; } - // TODO FIX We don't handle leap years yet! - int32_t prevMonthLength = (month >= 1) ? STATICMONTHLENGTH[month - 1] : 31; - + // 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, - monthLength, prevMonthLength, status); + Grego::monthLength(year, month), + Grego::previousMonthLength(year, month), + status); } int32_t @@ -477,6 +507,55 @@ SimpleTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day, return result; } +void +SimpleTimeZone::getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt, + int32_t& rawOffsetGMT, int32_t& savingsDST, UErrorCode& status) const { + if (U_FAILURE(status)) { + return; + } + + rawOffsetGMT = getRawOffset(); + int32_t year, month, dom, dow; + double day = uprv_floor(date / U_MILLIS_PER_DAY); + int32_t millis = (int32_t) (date - day * U_MILLIS_PER_DAY); + + Grego::dayToFields(day, year, month, dom, dow); + + 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 = uprv_floor(date / U_MILLIS_PER_DAY); + millis = (int32_t) (date - day * U_MILLIS_PER_DAY); + Grego::dayToFields(day, year, month, dom, dow); + savingsDST = getOffset(GregorianCalendar::AD, year, month, dom, + (uint8_t) dow, millis, + Grego::monthLength(year, month), + status) - rawOffsetGMT; + } +} + // ------------------------------------- /** @@ -527,6 +606,12 @@ SimpleTimeZone::compareToRule(int8_t month, int8_t monthLen, int8_t prevMonthLen // 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 @@ -546,11 +631,9 @@ SimpleTimeZone::compareToRule(int8_t month, int8_t monthLen, int8_t prevMonthLen // 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 + // (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 - @@ -595,6 +678,7 @@ void SimpleTimeZone::setRawOffset(int32_t offsetMillis) { rawOffset = offsetMillis; + transitionRulesInitialized = FALSE; } // ------------------------------------- @@ -602,12 +686,13 @@ SimpleTimeZone::setRawOffset(int32_t offsetMillis) void SimpleTimeZone::setDSTSavings(int32_t millisSavedDuringDST, UErrorCode& status) { - if (millisSavedDuringDST <= 0) { + if (millisSavedDuringDST == 0) { status = U_ILLEGAL_ARGUMENT_ERROR; } else { dstSavings = millisSavedDuringDST; } + transitionRulesInitialized = FALSE; } // ------------------------------------- @@ -661,7 +746,7 @@ UBool SimpleTimeZone::hasSameRules(const TimeZone& other) const { if (this == &other) return TRUE; - if (other.getDynamicClassID() != SimpleTimeZone::getStaticClassID()) return FALSE; + if (typeid(*this) != typeid(other)) return FALSE; SimpleTimeZone *that = (SimpleTimeZone*)&other; return rawOffset == that->rawOffset && useDaylight == that->useDaylight && @@ -822,7 +907,7 @@ SimpleTimeZone::decodeStartRule(UErrorCode& status) status = U_ILLEGAL_ARGUMENT_ERROR; return; } - } else if (startDay > STATICMONTHLENGTH[startMonth]) { + } else if (startDay<1 || startDay > STATICMONTHLENGTH[startMonth]) { status = U_ILLEGAL_ARGUMENT_ERROR; return; } @@ -877,13 +962,299 @@ SimpleTimeZone::decodeEndRule(UErrorCode& status) status = U_ILLEGAL_ARGUMENT_ERROR; return; } - } else if (endDay > STATICMONTHLENGTH[endMonth]) { + } 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((const TimeZoneRule&)*dstRule); + result.setTo((const TimeZoneRule&)*stdRule); + return TRUE; + } + if (dstAvail && (!stdAvail || dstDate < stdDate)) { + result.setTime(dstDate); + result.setFrom((const TimeZoneRule&)*stdRule); + result.setTo((const TimeZoneRule&)*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((const TimeZoneRule&)*dstRule); + result.setTo((const TimeZoneRule&)*stdRule); + return TRUE; + } + if (dstAvail && (!stdAvail || dstDate > stdDate)) { + result.setTime(dstDate); + result.setFrom((const TimeZoneRule&)*stdRule); + result.setTo((const TimeZoneRule&)*dstRule); + return TRUE; + } + return FALSE; +} + +void +SimpleTimeZone::clearTransitionRules(void) { + initialRule = NULL; + firstTransition = NULL; + stdRule = NULL; + dstRule = NULL; + transitionRulesInitialized = FALSE; +} + +void +SimpleTimeZone::deleteTransitionRules(void) { + if (initialRule != NULL) { + delete initialRule; + } + if (firstTransition != NULL) { + delete firstTransition; + } + if (stdRule != NULL) { + delete stdRule; + } + if (dstRule != NULL) { + 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. + */ +static UMutex gLock = U_MUTEX_INITIALIZER; + +void +SimpleTimeZone::checkTransitionRules(UErrorCode& status) const { + if (U_FAILURE(status)) { + return; + } + 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 == NULL) { + 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 == NULL) { + 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 == NULL) { + 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 == NULL) { + 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 == NULL) { + 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 == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + deleteTransitionRules(); + return; + } + firstTransition = new TimeZoneTransition(firstDstStart, *initialRule, *dstRule); + } + if (firstTransition == NULL) { + 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 == NULL) { + 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 != NULL) { + if (cnt < trscount) { + trsrules[cnt++] = stdRule; + } + if (cnt < trscount) { + trsrules[cnt++] = dstRule; + } + } + trscount = cnt; +} + + U_NAMESPACE_END #endif /* #if !UCONFIG_NO_FORMATTING */