+// © 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"
#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)
-// 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),
endMode(DOM_MODE),
dstSavings(U_MILLIS_PER_HOUR)
{
+ clearTransitionRules();
}
// -------------------------------------
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,
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,
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,
decodeRules(status);
- if (savingsDST <= 0) {
+ 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)
-: TimeZone(source)
+: BasicTimeZone(source)
{
*this = source;
}
startYear = right.startYear;
dstSavings = right.dstSavings;
useDaylight = right.useDaylight;
+ clearTransitionRules();
}
return *this;
}
SimpleTimeZone::operator==(const TimeZone& that) const
{
return ((this == &that) ||
- (getDynamicClassID() == that.getDynamicClassID() &&
+ (typeid(*this) == typeid(that) &&
TimeZone::operator==(that) &&
hasSameRules(that)));
}
SimpleTimeZone::setStartYear(int32_t year)
{
startYear = year;
+ transitionRulesInitialized = FALSE;
}
// -------------------------------------
startTime = time;
startTimeMode = mode;
decodeStartRule(status);
+ transitionRulesInitialized = FALSE;
}
// -------------------------------------
endTime = time;
endTimeMode = mode;
decodeEndRule(status);
+ transitionRulesInitialized = FALSE;
}
// -------------------------------------
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
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
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
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;
+ }
+}
+
// -------------------------------------
/**
// 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
// 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 -
SimpleTimeZone::setRawOffset(int32_t offsetMillis)
{
rawOffset = offsetMillis;
+ transitionRulesInitialized = FALSE;
}
// -------------------------------------
void
SimpleTimeZone::setDSTSavings(int32_t millisSavedDuringDST, UErrorCode& status)
{
- if (millisSavedDuringDST <= 0) {
+ if (millisSavedDuringDST == 0) {
status = U_ILLEGAL_ARGUMENT_ERROR;
}
else {
dstSavings = millisSavedDuringDST;
}
+ transitionRulesInitialized = FALSE;
}
// -------------------------------------
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 &&
status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
- } else if (startDay > STATICMONTHLENGTH[startMonth]) {
+ } else if (startDay<1 || startDay > STATICMONTHLENGTH[startMonth]) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
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.
+ */
+
+void
+SimpleTimeZone::checkTransitionRules(UErrorCode& status) const {
+ if (U_FAILURE(status)) {
+ return;
+ }
+ static UMutex *gLock = STATIC_NEW(UMutex);
+ umtx_lock(gLock);
+ if (!transitionRulesInitialized) {
+ SimpleTimeZone *ncThis = const_cast<SimpleTimeZone*>(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 */