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