// © 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 = nullptr; std = nullptr; dst = nullptr; if (U_FAILURE(status)) { return; } int32_t initialRaw, initialDst; UnicodeString initialName; AnnualTimeZoneRule *ar1 = nullptr; AnnualTimeZoneRule *ar2 = nullptr; 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, status); if (U_FAILURE(status)) return; 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, status); if (U_FAILURE(status)) return; 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 = nullptr; } } } } if (ar2 == nullptr) { // 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, status); if (U_FAILURE(status)) return; 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 = nullptr; } } } } if (ar2 == nullptr) { // Cannot find a good pair of AnnualTimeZoneRule delete ar1; ar1 = nullptr; } 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 != nullptr && ar2 != nullptr) { 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 orgRules( new UVector(uprv_deleteUObject, nullptr, ruleCount, status), status); if (U_FAILURE(status)) { return; } LocalMemory orgtrs( static_cast(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 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 done(static_cast(uprv_malloc(sizeof(bool)*ruleCount))); if (done.isNull()) { status = U_MEMORY_ALLOCATION_ERROR; return; } LocalPointer filteredRules( new UVector(uprv_deleteUObject, nullptr, status), status); if (U_FAILURE(status)) { return; } // Create initial rule tzt.getTo()->getName(name); LocalPointer 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(toRule); const AnnualTimeZoneRule *ar; if (tar != nullptr) { // 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 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 newTimes(static_cast(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 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(toRule)) != nullptr) { ar->getFirstStart(tzt.getFrom()->getRawOffset(), tzt.getFrom()->getDSTSavings(), firstStart); if (firstStart == tzt.getTime()) { // Just add the rule as is LocalPointer 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, status); if (U_FAILURE(status)) { return; } // Re-create the rule ar->getName(name); LocalPointer 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