You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

563 lines
21 KiB

// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
*******************************************************************************
* Copyright (C) 2007-2013, International Business Machines Corporation and
* others. All Rights Reserved.
*******************************************************************************
*/
#include "unicode/utypes.h"
#if !UCONFIG_NO_FORMATTING
#include "unicode/basictz.h"
#include "gregoimp.h"
#include "uvector.h"
#include "cmemory.h"
U_NAMESPACE_BEGIN
#define MILLIS_PER_YEAR (365*24*60*60*1000.0)
BasicTimeZone::BasicTimeZone()
: TimeZone() {
}
BasicTimeZone::BasicTimeZone(const UnicodeString &id)
: TimeZone(id) {
}
BasicTimeZone::BasicTimeZone(const BasicTimeZone& source)
: TimeZone(source) {
}
BasicTimeZone::~BasicTimeZone() {
}
UBool
BasicTimeZone::hasEquivalentTransitions(const BasicTimeZone& tz, UDate start, UDate end,
UBool ignoreDstAmount, UErrorCode& status) const {
if (U_FAILURE(status)) {
return FALSE;
}
if (hasSameRules(tz)) {
return TRUE;
}
// Check the offsets at the start time
int32_t raw1, raw2, dst1, dst2;
getOffset(start, FALSE, raw1, dst1, status);
if (U_FAILURE(status)) {
return FALSE;
}
tz.getOffset(start, FALSE, raw2, dst2, status);
if (U_FAILURE(status)) {
return FALSE;
}
if (ignoreDstAmount) {
if ((raw1 + dst1 != raw2 + dst2)
|| (dst1 != 0 && dst2 == 0)
|| (dst1 == 0 && dst2 != 0)) {
return FALSE;
}
} else {
if (raw1 != raw2 || dst1 != dst2) {
return FALSE;
}
}
// Check transitions in the range
UDate time = start;
TimeZoneTransition tr1, tr2;
while (TRUE) {
UBool avail1 = getNextTransition(time, FALSE, tr1);
UBool avail2 = tz.getNextTransition(time, FALSE, tr2);
if (ignoreDstAmount) {
// Skip a transition which only differ the amount of DST savings
while (TRUE) {
if (avail1
&& tr1.getTime() <= end
&& (tr1.getFrom()->getRawOffset() + tr1.getFrom()->getDSTSavings()
== tr1.getTo()->getRawOffset() + tr1.getTo()->getDSTSavings())
&& (tr1.getFrom()->getDSTSavings() != 0 && tr1.getTo()->getDSTSavings() != 0)) {
getNextTransition(tr1.getTime(), FALSE, tr1);
} else {
break;
}
}
while (TRUE) {
if (avail2
&& tr2.getTime() <= end
&& (tr2.getFrom()->getRawOffset() + tr2.getFrom()->getDSTSavings()
== tr2.getTo()->getRawOffset() + tr2.getTo()->getDSTSavings())
&& (tr2.getFrom()->getDSTSavings() != 0 && tr2.getTo()->getDSTSavings() != 0)) {
tz.getNextTransition(tr2.getTime(), FALSE, tr2);
} else {
break;
}
}
}
UBool inRange1 = (avail1 && tr1.getTime() <= end);
UBool inRange2 = (avail2 && tr2.getTime() <= end);
if (!inRange1 && !inRange2) {
// No more transition in the range
break;
}
if (!inRange1 || !inRange2) {
return FALSE;
}
if (tr1.getTime() != tr2.getTime()) {
return FALSE;
}
if (ignoreDstAmount) {
if (tr1.getTo()->getRawOffset() + tr1.getTo()->getDSTSavings()
!= tr2.getTo()->getRawOffset() + tr2.getTo()->getDSTSavings()
|| (tr1.getTo()->getDSTSavings() != 0 && tr2.getTo()->getDSTSavings() == 0)
|| (tr1.getTo()->getDSTSavings() == 0 && tr2.getTo()->getDSTSavings() != 0)) {
return FALSE;
}
} else {
if (tr1.getTo()->getRawOffset() != tr2.getTo()->getRawOffset() ||
tr1.getTo()->getDSTSavings() != tr2.getTo()->getDSTSavings()) {
return FALSE;
}
}
time = tr1.getTime();
}
return TRUE;
}
void
BasicTimeZone::getSimpleRulesNear(UDate date, InitialTimeZoneRule*& initial,
AnnualTimeZoneRule*& std, AnnualTimeZoneRule*& dst, UErrorCode& status) const {
initial = NULL;
std = NULL;
dst = NULL;
if (U_FAILURE(status)) {
return;
}
int32_t initialRaw, initialDst;
UnicodeString initialName;
AnnualTimeZoneRule *ar1 = NULL;
AnnualTimeZoneRule *ar2 = NULL;
UnicodeString name;
UBool avail;
TimeZoneTransition tr;
// Get the next transition
avail = getNextTransition(date, FALSE, tr);
if (avail) {
tr.getFrom()->getName(initialName);
initialRaw = tr.getFrom()->getRawOffset();
initialDst = tr.getFrom()->getDSTSavings();
// Check if the next transition is either DST->STD or STD->DST and
// within roughly 1 year from the specified date
UDate nextTransitionTime = tr.getTime();
if (((tr.getFrom()->getDSTSavings() == 0 && tr.getTo()->getDSTSavings() != 0)
|| (tr.getFrom()->getDSTSavings() != 0 && tr.getTo()->getDSTSavings() == 0))
&& (date + MILLIS_PER_YEAR > nextTransitionTime)) {
int32_t year, month, dom, dow, doy, mid;
UDate d;
// Get local wall time for the next transition time
Grego::timeToFields(nextTransitionTime + initialRaw + initialDst,
year, month, dom, dow, doy, mid);
int32_t weekInMonth = Grego::dayOfWeekInMonth(year, month, dom);
// Create DOW rule
DateTimeRule *dtr = new DateTimeRule(month, weekInMonth, dow, mid, DateTimeRule::WALL_TIME);
tr.getTo()->getName(name);
// Note: SimpleTimeZone does not support raw offset change.
// So we always use raw offset of the given time for the rule,
// even raw offset is changed. This will result that the result
// zone to return wrong offset after the transition.
// When we encounter such case, we do not inspect next next
// transition for another rule.
ar1 = new AnnualTimeZoneRule(name, initialRaw, tr.getTo()->getDSTSavings(),
dtr, year, AnnualTimeZoneRule::MAX_YEAR);
if (tr.getTo()->getRawOffset() == initialRaw) {
// Get the next next transition
avail = getNextTransition(nextTransitionTime, FALSE, tr);
if (avail) {
// Check if the next next transition is either DST->STD or STD->DST
// and within roughly 1 year from the next transition
if (((tr.getFrom()->getDSTSavings() == 0 && tr.getTo()->getDSTSavings() != 0)
|| (tr.getFrom()->getDSTSavings() != 0 && tr.getTo()->getDSTSavings() == 0))
&& nextTransitionTime + MILLIS_PER_YEAR > tr.getTime()) {
// Get local wall time for the next transition time
Grego::timeToFields(tr.getTime() + tr.getFrom()->getRawOffset() + tr.getFrom()->getDSTSavings(),
year, month, dom, dow, doy, mid);
weekInMonth = Grego::dayOfWeekInMonth(year, month, dom);
// Generate another DOW rule
dtr = new DateTimeRule(month, weekInMonth, dow, mid, DateTimeRule::WALL_TIME);
tr.getTo()->getName(name);
ar2 = new AnnualTimeZoneRule(name, tr.getTo()->getRawOffset(), tr.getTo()->getDSTSavings(),
dtr, year - 1, AnnualTimeZoneRule::MAX_YEAR);
// Make sure this rule can be applied to the specified date
avail = ar2->getPreviousStart(date, tr.getFrom()->getRawOffset(), tr.getFrom()->getDSTSavings(), TRUE, d);
if (!avail || d > date
|| initialRaw != tr.getTo()->getRawOffset()
|| initialDst != tr.getTo()->getDSTSavings()) {
// We cannot use this rule as the second transition rule
delete ar2;
ar2 = NULL;
}
}
}
}
if (ar2 == NULL) {
// Try previous transition
avail = getPreviousTransition(date, TRUE, tr);
if (avail) {
// Check if the previous transition is either DST->STD or STD->DST.
// The actual transition time does not matter here.
if ((tr.getFrom()->getDSTSavings() == 0 && tr.getTo()->getDSTSavings() != 0)
|| (tr.getFrom()->getDSTSavings() != 0 && tr.getTo()->getDSTSavings() == 0)) {
// Generate another DOW rule
Grego::timeToFields(tr.getTime() + tr.getFrom()->getRawOffset() + tr.getFrom()->getDSTSavings(),
year, month, dom, dow, doy, mid);
weekInMonth = Grego::dayOfWeekInMonth(year, month, dom);
dtr = new DateTimeRule(month, weekInMonth, dow, mid, DateTimeRule::WALL_TIME);
tr.getTo()->getName(name);
// second rule raw/dst offsets should match raw/dst offsets
// at the given time
ar2 = new AnnualTimeZoneRule(name, initialRaw, initialDst,
dtr, ar1->getStartYear() - 1, AnnualTimeZoneRule::MAX_YEAR);
// Check if this rule start after the first rule after the specified date
avail = ar2->getNextStart(date, tr.getFrom()->getRawOffset(), tr.getFrom()->getDSTSavings(), FALSE, d);
if (!avail || d <= nextTransitionTime) {
// We cannot use this rule as the second transition rule
delete ar2;
ar2 = NULL;
}
}
}
}
if (ar2 == NULL) {
// Cannot find a good pair of AnnualTimeZoneRule
delete ar1;
ar1 = NULL;
} else {
// The initial rule should represent the rule before the previous transition
ar1->getName(initialName);
initialRaw = ar1->getRawOffset();
initialDst = ar1->getDSTSavings();
}
}
}
else {
// Try the previous one
avail = getPreviousTransition(date, TRUE, tr);
if (avail) {
tr.getTo()->getName(initialName);
initialRaw = tr.getTo()->getRawOffset();
initialDst = tr.getTo()->getDSTSavings();
} else {
// No transitions in the past. Just use the current offsets
getOffset(date, FALSE, initialRaw, initialDst, status);
if (U_FAILURE(status)) {
return;
}
}
}
// Set the initial rule
initial = new InitialTimeZoneRule(initialName, initialRaw, initialDst);
// Set the standard and daylight saving rules
if (ar1 != NULL && ar2 != NULL) {
if (ar1->getDSTSavings() != 0) {
dst = ar1;
std = ar2;
} else {
std = ar1;
dst = ar2;
}
}
}
void
BasicTimeZone::getTimeZoneRulesAfter(UDate start, InitialTimeZoneRule*& initial,
UVector*& transitionRules, UErrorCode& status) const {
if (U_FAILURE(status)) {
return;
}
const InitialTimeZoneRule *orgini;
const TimeZoneRule **orgtrs = NULL;
TimeZoneTransition tzt;
UBool avail;
UVector *orgRules = NULL;
int32_t ruleCount;
TimeZoneRule *r = NULL;
UBool *done = NULL;
InitialTimeZoneRule *res_initial = NULL;
UVector *filteredRules = NULL;
UnicodeString name;
int32_t i;
UDate time, t;
UDate *newTimes = NULL;
UDate firstStart;
UBool bFinalStd = FALSE, bFinalDst = FALSE;
// Original transition rules
ruleCount = countTransitionRules(status);
if (U_FAILURE(status)) {
return;
}
orgRules = new UVector(ruleCount, status);
if (U_FAILURE(status)) {
return;
}
orgtrs = (const TimeZoneRule**)uprv_malloc(sizeof(TimeZoneRule*)*ruleCount);
if (orgtrs == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
goto error;
}
getTimeZoneRules(orgini, orgtrs, ruleCount, status);
if (U_FAILURE(status)) {
goto error;
}
for (i = 0; i < ruleCount; i++) {
orgRules->addElement(orgtrs[i]->clone(), status);
if (U_FAILURE(status)) {
goto error;
}
}
uprv_free(orgtrs);
orgtrs = NULL;
avail = getPreviousTransition(start, TRUE, tzt);
if (!avail) {
// No need to filter out rules only applicable to time before the start
initial = orgini->clone();
transitionRules = orgRules;
return;
}
done = (UBool*)uprv_malloc(sizeof(UBool)*ruleCount);
if (done == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
goto error;
}
filteredRules = new UVector(status);
if (U_FAILURE(status)) {
goto error;
}
// Create initial rule
tzt.getTo()->getName(name);
res_initial = new InitialTimeZoneRule(name, tzt.getTo()->getRawOffset(),
tzt.getTo()->getDSTSavings());
// Mark rules which does not need to be processed
for (i = 0; i < ruleCount; i++) {
r = (TimeZoneRule*)orgRules->elementAt(i);
avail = r->getNextStart(start, res_initial->getRawOffset(), res_initial->getDSTSavings(), FALSE, time);
done[i] = !avail;
}
time = start;
while (!bFinalStd || !bFinalDst) {
avail = getNextTransition(time, FALSE, tzt);
if (!avail) {
break;
}
UDate updatedTime = tzt.getTime();
if (updatedTime == time) {
// Can get here if rules for start & end of daylight time have exactly
// the same time.
// TODO: fix getNextTransition() to prevent it?
status = U_INVALID_STATE_ERROR;
goto error;
}
time = updatedTime;
const TimeZoneRule *toRule = tzt.getTo();
for (i = 0; i < ruleCount; i++) {
r = (TimeZoneRule*)orgRules->elementAt(i);
if (*r == *toRule) {
break;
}
}
if (i >= ruleCount) {
// This case should never happen
status = U_INVALID_STATE_ERROR;
goto error;
}
if (done[i]) {
continue;
}
const TimeArrayTimeZoneRule *tar = dynamic_cast<const TimeArrayTimeZoneRule *>(toRule);
const AnnualTimeZoneRule *ar;
if (tar != NULL) {
// Get the previous raw offset and DST savings before the very first start time
TimeZoneTransition tzt0;
t = start;
while (TRUE) {
avail = getNextTransition(t, FALSE, tzt0);
if (!avail) {
break;
}
if (*(tzt0.getTo()) == *tar) {
break;
}
t = tzt0.getTime();
}
if (avail) {
// Check if the entire start times to be added
tar->getFirstStart(tzt.getFrom()->getRawOffset(), tzt.getFrom()->getDSTSavings(), firstStart);
if (firstStart > start) {
// Just add the rule as is
filteredRules->addElement(tar->clone(), status);
if (U_FAILURE(status)) {
goto error;
}
} else {
// Colllect transitions after the start time
int32_t startTimes;
DateTimeRule::TimeRuleType timeType;
int32_t idx;
startTimes = tar->countStartTimes();
timeType = tar->getTimeType();
for (idx = 0; idx < startTimes; idx++) {
tar->getStartTimeAt(idx, t);
if (timeType == DateTimeRule::STANDARD_TIME) {
t -= tzt.getFrom()->getRawOffset();
}
if (timeType == DateTimeRule::WALL_TIME) {
t -= tzt.getFrom()->getDSTSavings();
}
if (t > start) {
break;
}
}
int32_t asize = startTimes - idx;
if (asize > 0) {
newTimes = (UDate*)uprv_malloc(sizeof(UDate) * asize);
if (newTimes == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
goto error;
}
for (int32_t newidx = 0; newidx < asize; newidx++) {
tar->getStartTimeAt(idx + newidx, newTimes[newidx]);
if (U_FAILURE(status)) {
uprv_free(newTimes);
newTimes = NULL;
goto error;
}
}
tar->getName(name);
TimeArrayTimeZoneRule *newTar = new TimeArrayTimeZoneRule(name,
tar->getRawOffset(), tar->getDSTSavings(), newTimes, asize, timeType);
uprv_free(newTimes);
filteredRules->addElement(newTar, status);
if (U_FAILURE(status)) {
goto error;
}
}
}
}
} else if ((ar = dynamic_cast<const AnnualTimeZoneRule *>(toRule)) != NULL) {
ar->getFirstStart(tzt.getFrom()->getRawOffset(), tzt.getFrom()->getDSTSavings(), firstStart);
if (firstStart == tzt.getTime()) {
// Just add the rule as is
filteredRules->addElement(ar->clone(), status);
if (U_FAILURE(status)) {
goto error;
}
} else {
// Calculate the transition year
int32_t year, month, dom, dow, doy, mid;
Grego::timeToFields(tzt.getTime(), year, month, dom, dow, doy, mid);
// Re-create the rule
ar->getName(name);
AnnualTimeZoneRule *newAr = new AnnualTimeZoneRule(name, ar->getRawOffset(), ar->getDSTSavings(),
*(ar->getRule()), year, ar->getEndYear());
filteredRules->addElement(newAr, status);
if (U_FAILURE(status)) {
goto error;
}
}
// check if this is a final rule
if (ar->getEndYear() == AnnualTimeZoneRule::MAX_YEAR) {
// After bot final standard and dst rules are processed,
// exit this while loop.
if (ar->getDSTSavings() == 0) {
bFinalStd = TRUE;
} else {
bFinalDst = TRUE;
}
}
}
done[i] = TRUE;
}
// Set the results
if (orgRules != NULL) {
while (!orgRules->isEmpty()) {
r = (TimeZoneRule*)orgRules->orphanElementAt(0);
delete r;
}
delete orgRules;
}
if (done != NULL) {
uprv_free(done);
}
initial = res_initial;
transitionRules = filteredRules;
return;
error:
if (orgtrs != NULL) {
uprv_free(orgtrs);
}
if (orgRules != NULL) {
while (!orgRules->isEmpty()) {
r = (TimeZoneRule*)orgRules->orphanElementAt(0);
delete r;
}
delete orgRules;
}
if (done != NULL) {
if (filteredRules != NULL) {
while (!filteredRules->isEmpty()) {
r = (TimeZoneRule*)filteredRules->orphanElementAt(0);
delete r;
}
delete filteredRules;
}
delete res_initial;
uprv_free(done);
}
initial = NULL;
transitionRules = NULL;
}
void
BasicTimeZone::getOffsetFromLocal(UDate /*date*/, int32_t /*nonExistingTimeOpt*/, int32_t /*duplicatedTimeOpt*/,
int32_t& /*rawOffset*/, int32_t& /*dstOffset*/, UErrorCode& status) const {
if (U_FAILURE(status)) {
return;
}
status = U_UNSUPPORTED_ERROR;
}
U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */
//eof