/* ******************************************************************************* * 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