540 lines
21 KiB
C++
540 lines
21 KiB
C++
// © 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;
|
|
TimeZoneTransition tzt;
|
|
bool avail;
|
|
int32_t ruleCount;
|
|
TimeZoneRule *r = nullptr;
|
|
UnicodeString name;
|
|
int32_t i;
|
|
UDate time, t;
|
|
UDate firstStart;
|
|
UBool bFinalStd = false, bFinalDst = false;
|
|
|
|
initial = nullptr;
|
|
transitionRules = nullptr;
|
|
|
|
// Original transition rules
|
|
ruleCount = countTransitionRules(status);
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
LocalPointer<UVector> orgRules(
|
|
new UVector(uprv_deleteUObject, nullptr, ruleCount, status), status);
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
LocalMemory<const TimeZoneRule *> orgtrs(
|
|
static_cast<const TimeZoneRule **>(uprv_malloc(sizeof(TimeZoneRule*)*ruleCount)));
|
|
if (orgtrs.isNull()) {
|
|
status = U_MEMORY_ALLOCATION_ERROR;
|
|
return;
|
|
}
|
|
getTimeZoneRules(orgini, &orgtrs[0], ruleCount, status);
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
for (i = 0; i < ruleCount; i++) {
|
|
LocalPointer<TimeZoneRule> lpRule(orgtrs[i]->clone(), status);
|
|
orgRules->adoptElement(lpRule.orphan(), status);
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
avail = getPreviousTransition(start, TRUE, tzt);
|
|
if (!avail) {
|
|
// No need to filter out rules only applicable to time before the start
|
|
initial = orgini->clone();
|
|
if (initial == nullptr) {
|
|
status = U_MEMORY_ALLOCATION_ERROR;
|
|
return;
|
|
}
|
|
transitionRules = orgRules.orphan();
|
|
return;
|
|
}
|
|
|
|
LocalMemory<bool> done(static_cast<bool *>(uprv_malloc(sizeof(bool)*ruleCount)));
|
|
if (done.isNull()) {
|
|
status = U_MEMORY_ALLOCATION_ERROR;
|
|
return;
|
|
}
|
|
LocalPointer<UVector> filteredRules(
|
|
new UVector(uprv_deleteUObject, nullptr, status), status);
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
|
|
// Create initial rule
|
|
tzt.getTo()->getName(name);
|
|
LocalPointer<InitialTimeZoneRule> res_initial(
|
|
new InitialTimeZoneRule(name, tzt.getTo()->getRawOffset(), tzt.getTo()->getDSTSavings()), status);
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
|
|
// 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;
|
|
return;
|
|
}
|
|
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;
|
|
return;
|
|
}
|
|
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
|
|
LocalPointer<TimeArrayTimeZoneRule> lpTar(tar->clone(), status);
|
|
filteredRules->adoptElement(lpTar.orphan(), status);
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
} else {
|
|
// Collect 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;
|
|
}
|
|
}
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
int32_t asize = startTimes - idx;
|
|
if (asize > 0) {
|
|
LocalMemory<UDate> newTimes(static_cast<UDate *>(uprv_malloc(sizeof(UDate) * asize)));
|
|
if (newTimes.isNull()) {
|
|
status = U_MEMORY_ALLOCATION_ERROR;
|
|
return;
|
|
}
|
|
for (int32_t newidx = 0; newidx < asize; newidx++) {
|
|
tar->getStartTimeAt(idx + newidx, newTimes[newidx]);
|
|
}
|
|
tar->getName(name);
|
|
LocalPointer<TimeArrayTimeZoneRule> newTar(new TimeArrayTimeZoneRule(
|
|
name, tar->getRawOffset(), tar->getDSTSavings(), &newTimes[0], asize, timeType), status);
|
|
filteredRules->adoptElement(newTar.orphan(), status);
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} 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
|
|
LocalPointer<AnnualTimeZoneRule> arClone(ar->clone(), status);
|
|
filteredRules->adoptElement(arClone.orphan(), status);
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
} 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);
|
|
LocalPointer<AnnualTimeZoneRule> newAr(new AnnualTimeZoneRule(name, ar->getRawOffset(), ar->getDSTSavings(),
|
|
*(ar->getRule()), year, ar->getEndYear()), status);
|
|
filteredRules->adoptElement(newAr.orphan(), status);
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
}
|
|
// 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
|
|
initial = res_initial.orphan();
|
|
transitionRules = filteredRules.orphan();
|
|
return;
|
|
}
|
|
|
|
void
|
|
BasicTimeZone::getOffsetFromLocal(UDate /*date*/, UTimeZoneLocalOption /*nonExistingTimeOpt*/,
|
|
UTimeZoneLocalOption /*duplicatedTimeOpt*/,
|
|
int32_t& /*rawOffset*/, int32_t& /*dstOffset*/,
|
|
UErrorCode& status) const {
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
status = U_UNSUPPORTED_ERROR;
|
|
}
|
|
|
|
void BasicTimeZone::getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt,
|
|
int32_t& rawOffset, int32_t& dstOffset,
|
|
UErrorCode& status) const {
|
|
getOffsetFromLocal(date, (UTimeZoneLocalOption)nonExistingTimeOpt,
|
|
(UTimeZoneLocalOption)duplicatedTimeOpt, rawOffset, dstOffset, status);
|
|
}
|
|
|
|
U_NAMESPACE_END
|
|
|
|
#endif /* #if !UCONFIG_NO_FORMATTING */
|
|
|
|
//eof
|