| // © 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 |
| |
| #if defined(STARBOARD) |
| #include "starboard/client_porting/poem/string_poem.h" |
| #endif // defined(STARBOARD) |
| #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 |