michael@0: /* michael@0: ********************************************************************** michael@0: * Copyright (c) 2003-2013, International Business Machines michael@0: * Corporation and others. All Rights Reserved. michael@0: ********************************************************************** michael@0: * Author: Alan Liu michael@0: * Created: July 21 2003 michael@0: * Since: ICU 2.8 michael@0: ********************************************************************** michael@0: */ michael@0: michael@0: #include "utypeinfo.h" // for 'typeid' to work michael@0: michael@0: #include "olsontz.h" michael@0: michael@0: #if !UCONFIG_NO_FORMATTING michael@0: michael@0: #include "unicode/ures.h" michael@0: #include "unicode/simpletz.h" michael@0: #include "unicode/gregocal.h" michael@0: #include "gregoimp.h" michael@0: #include "cmemory.h" michael@0: #include "uassert.h" michael@0: #include "uvector.h" michael@0: #include // DBL_MAX michael@0: #include "uresimp.h" // struct UResourceBundle michael@0: #include "zonemeta.h" michael@0: #include "umutex.h" michael@0: michael@0: #ifdef U_DEBUG_TZ michael@0: # include michael@0: # include "uresimp.h" // for debugging michael@0: michael@0: static void debug_tz_loc(const char *f, int32_t l) michael@0: { michael@0: fprintf(stderr, "%s:%d: ", f, l); michael@0: } michael@0: michael@0: static void debug_tz_msg(const char *pat, ...) michael@0: { michael@0: va_list ap; michael@0: va_start(ap, pat); michael@0: vfprintf(stderr, pat, ap); michael@0: fflush(stderr); michael@0: } michael@0: // must use double parens, i.e.: U_DEBUG_TZ_MSG(("four is: %d",4)); michael@0: #define U_DEBUG_TZ_MSG(x) {debug_tz_loc(__FILE__,__LINE__);debug_tz_msg x;} michael@0: #else michael@0: #define U_DEBUG_TZ_MSG(x) michael@0: #endif michael@0: michael@0: static UBool arrayEqual(const void *a1, const void *a2, int32_t size) { michael@0: if (a1 == NULL && a2 == NULL) { michael@0: return TRUE; michael@0: } michael@0: if ((a1 != NULL && a2 == NULL) || (a1 == NULL && a2 != NULL)) { michael@0: return FALSE; michael@0: } michael@0: if (a1 == a2) { michael@0: return TRUE; michael@0: } michael@0: michael@0: return (uprv_memcmp(a1, a2, size) == 0); michael@0: } michael@0: michael@0: U_NAMESPACE_BEGIN michael@0: michael@0: #define kTRANS "trans" michael@0: #define kTRANSPRE32 "transPre32" michael@0: #define kTRANSPOST32 "transPost32" michael@0: #define kTYPEOFFSETS "typeOffsets" michael@0: #define kTYPEMAP "typeMap" michael@0: #define kLINKS "links" michael@0: #define kFINALRULE "finalRule" michael@0: #define kFINALRAW "finalRaw" michael@0: #define kFINALYEAR "finalYear" michael@0: michael@0: #define SECONDS_PER_DAY (24*60*60) michael@0: michael@0: static const int32_t ZEROS[] = {0,0}; michael@0: michael@0: UOBJECT_DEFINE_RTTI_IMPLEMENTATION(OlsonTimeZone) michael@0: michael@0: /** michael@0: * Default constructor. Creates a time zone with an empty ID and michael@0: * a fixed GMT offset of zero. michael@0: */ michael@0: /*OlsonTimeZone::OlsonTimeZone() : finalYear(INT32_MAX), finalMillis(DBL_MAX), finalZone(0), transitionRulesInitialized(FALSE) { michael@0: clearTransitionRules(); michael@0: constructEmpty(); michael@0: }*/ michael@0: michael@0: /** michael@0: * Construct a GMT+0 zone with no transitions. This is done when a michael@0: * constructor fails so the resultant object is well-behaved. michael@0: */ michael@0: void OlsonTimeZone::constructEmpty() { michael@0: canonicalID = NULL; michael@0: michael@0: transitionCountPre32 = transitionCount32 = transitionCountPost32 = 0; michael@0: transitionTimesPre32 = transitionTimes32 = transitionTimesPost32 = NULL; michael@0: michael@0: typeMapData = NULL; michael@0: michael@0: typeCount = 1; michael@0: typeOffsets = ZEROS; michael@0: michael@0: finalZone = NULL; michael@0: } michael@0: michael@0: /** michael@0: * Construct from a resource bundle michael@0: * @param top the top-level zoneinfo resource bundle. This is used michael@0: * to lookup the rule that `res' may refer to, if there is one. michael@0: * @param res the resource bundle of the zone to be constructed michael@0: * @param ec input-output error code michael@0: */ michael@0: OlsonTimeZone::OlsonTimeZone(const UResourceBundle* top, michael@0: const UResourceBundle* res, michael@0: const UnicodeString& tzid, michael@0: UErrorCode& ec) : michael@0: BasicTimeZone(tzid), finalZone(NULL) michael@0: { michael@0: clearTransitionRules(); michael@0: U_DEBUG_TZ_MSG(("OlsonTimeZone(%s)\n", ures_getKey((UResourceBundle*)res))); michael@0: if ((top == NULL || res == NULL) && U_SUCCESS(ec)) { michael@0: ec = U_ILLEGAL_ARGUMENT_ERROR; michael@0: } michael@0: if (U_SUCCESS(ec)) { michael@0: // TODO -- clean up -- Doesn't work if res points to an alias michael@0: // // TODO remove nonconst casts below when ures_* API is fixed michael@0: // setID(ures_getKey((UResourceBundle*) res)); // cast away const michael@0: michael@0: int32_t len; michael@0: UResourceBundle r; michael@0: ures_initStackObject(&r); michael@0: michael@0: // Pre-32bit second transitions michael@0: ures_getByKey(res, kTRANSPRE32, &r, &ec); michael@0: transitionTimesPre32 = ures_getIntVector(&r, &len, &ec); michael@0: transitionCountPre32 = len >> 1; michael@0: if (ec == U_MISSING_RESOURCE_ERROR) { michael@0: // No pre-32bit transitions michael@0: transitionTimesPre32 = NULL; michael@0: transitionCountPre32 = 0; michael@0: ec = U_ZERO_ERROR; michael@0: } else if (U_SUCCESS(ec) && (len < 0 || len > 0x7FFF || (len & 1) != 0) /* len must be even */) { michael@0: ec = U_INVALID_FORMAT_ERROR; michael@0: } michael@0: michael@0: // 32bit second transitions michael@0: ures_getByKey(res, kTRANS, &r, &ec); michael@0: transitionTimes32 = ures_getIntVector(&r, &len, &ec); michael@0: transitionCount32 = len; michael@0: if (ec == U_MISSING_RESOURCE_ERROR) { michael@0: // No 32bit transitions michael@0: transitionTimes32 = NULL; michael@0: transitionCount32 = 0; michael@0: ec = U_ZERO_ERROR; michael@0: } else if (U_SUCCESS(ec) && (len < 0 || len > 0x7FFF)) { michael@0: ec = U_INVALID_FORMAT_ERROR; michael@0: } michael@0: michael@0: // Post-32bit second transitions michael@0: ures_getByKey(res, kTRANSPOST32, &r, &ec); michael@0: transitionTimesPost32 = ures_getIntVector(&r, &len, &ec); michael@0: transitionCountPost32 = len >> 1; michael@0: if (ec == U_MISSING_RESOURCE_ERROR) { michael@0: // No pre-32bit transitions michael@0: transitionTimesPost32 = NULL; michael@0: transitionCountPost32 = 0; michael@0: ec = U_ZERO_ERROR; michael@0: } else if (U_SUCCESS(ec) && (len < 0 || len > 0x7FFF || (len & 1) != 0) /* len must be even */) { michael@0: ec = U_INVALID_FORMAT_ERROR; michael@0: } michael@0: michael@0: // Type offsets list must be of even size, with size >= 2 michael@0: ures_getByKey(res, kTYPEOFFSETS, &r, &ec); michael@0: typeOffsets = ures_getIntVector(&r, &len, &ec); michael@0: if (U_SUCCESS(ec) && (len < 2 || len > 0x7FFE || (len & 1) != 0)) { michael@0: ec = U_INVALID_FORMAT_ERROR; michael@0: } michael@0: typeCount = (int16_t) len >> 1; michael@0: michael@0: // Type map data must be of the same size as the transition count michael@0: typeMapData = NULL; michael@0: if (transitionCount() > 0) { michael@0: ures_getByKey(res, kTYPEMAP, &r, &ec); michael@0: typeMapData = ures_getBinary(&r, &len, &ec); michael@0: if (ec == U_MISSING_RESOURCE_ERROR) { michael@0: // no type mapping data michael@0: ec = U_INVALID_FORMAT_ERROR; michael@0: } else if (U_SUCCESS(ec) && len != transitionCount()) { michael@0: ec = U_INVALID_FORMAT_ERROR; michael@0: } michael@0: } michael@0: michael@0: // Process final rule and data, if any michael@0: const UChar *ruleIdUStr = ures_getStringByKey(res, kFINALRULE, &len, &ec); michael@0: ures_getByKey(res, kFINALRAW, &r, &ec); michael@0: int32_t ruleRaw = ures_getInt(&r, &ec); michael@0: ures_getByKey(res, kFINALYEAR, &r, &ec); michael@0: int32_t ruleYear = ures_getInt(&r, &ec); michael@0: if (U_SUCCESS(ec)) { michael@0: UnicodeString ruleID(TRUE, ruleIdUStr, len); michael@0: UResourceBundle *rule = TimeZone::loadRule(top, ruleID, NULL, ec); michael@0: const int32_t *ruleData = ures_getIntVector(rule, &len, &ec); michael@0: if (U_SUCCESS(ec) && len == 11) { michael@0: UnicodeString emptyStr; michael@0: finalZone = new SimpleTimeZone( michael@0: ruleRaw * U_MILLIS_PER_SECOND, michael@0: emptyStr, michael@0: (int8_t)ruleData[0], (int8_t)ruleData[1], (int8_t)ruleData[2], michael@0: ruleData[3] * U_MILLIS_PER_SECOND, michael@0: (SimpleTimeZone::TimeMode) ruleData[4], michael@0: (int8_t)ruleData[5], (int8_t)ruleData[6], (int8_t)ruleData[7], michael@0: ruleData[8] * U_MILLIS_PER_SECOND, michael@0: (SimpleTimeZone::TimeMode) ruleData[9], michael@0: ruleData[10] * U_MILLIS_PER_SECOND, ec); michael@0: if (finalZone == NULL) { michael@0: ec = U_MEMORY_ALLOCATION_ERROR; michael@0: } else { michael@0: finalStartYear = ruleYear; michael@0: michael@0: // Note: Setting finalStartYear to the finalZone is problematic. When a date is around michael@0: // year boundary, SimpleTimeZone may return false result when DST is observed at the michael@0: // beginning of year. We could apply safe margin (day or two), but when one of recurrent michael@0: // rules falls around year boundary, it could return false result. Without setting the michael@0: // start year, finalZone works fine around the year boundary of the start year. michael@0: michael@0: // finalZone->setStartYear(finalStartYear); michael@0: michael@0: michael@0: // Compute the millis for Jan 1, 0:00 GMT of the finalYear michael@0: michael@0: // Note: finalStartMillis is used for detecting either if michael@0: // historic transition data or finalZone to be used. In an michael@0: // extreme edge case - for example, two transitions fall into michael@0: // small windows of time around the year boundary, this may michael@0: // result incorrect offset computation. But I think it will michael@0: // never happen practically. Yoshito - Feb 20, 2010 michael@0: finalStartMillis = Grego::fieldsToDay(finalStartYear, 0, 1) * U_MILLIS_PER_DAY; michael@0: } michael@0: } else { michael@0: ec = U_INVALID_FORMAT_ERROR; michael@0: } michael@0: ures_close(rule); michael@0: } else if (ec == U_MISSING_RESOURCE_ERROR) { michael@0: // No final zone michael@0: ec = U_ZERO_ERROR; michael@0: } michael@0: ures_close(&r); michael@0: michael@0: // initialize canonical ID michael@0: canonicalID = ZoneMeta::getCanonicalCLDRID(tzid, ec); michael@0: } michael@0: michael@0: if (U_FAILURE(ec)) { michael@0: constructEmpty(); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Copy constructor michael@0: */ michael@0: OlsonTimeZone::OlsonTimeZone(const OlsonTimeZone& other) : michael@0: BasicTimeZone(other), finalZone(0) { michael@0: *this = other; michael@0: } michael@0: michael@0: /** michael@0: * Assignment operator michael@0: */ michael@0: OlsonTimeZone& OlsonTimeZone::operator=(const OlsonTimeZone& other) { michael@0: canonicalID = other.canonicalID; michael@0: michael@0: transitionTimesPre32 = other.transitionTimesPre32; michael@0: transitionTimes32 = other.transitionTimes32; michael@0: transitionTimesPost32 = other.transitionTimesPost32; michael@0: michael@0: transitionCountPre32 = other.transitionCountPre32; michael@0: transitionCount32 = other.transitionCount32; michael@0: transitionCountPost32 = other.transitionCountPost32; michael@0: michael@0: typeCount = other.typeCount; michael@0: typeOffsets = other.typeOffsets; michael@0: typeMapData = other.typeMapData; michael@0: michael@0: delete finalZone; michael@0: finalZone = (other.finalZone != 0) ? michael@0: (SimpleTimeZone*) other.finalZone->clone() : 0; michael@0: michael@0: finalStartYear = other.finalStartYear; michael@0: finalStartMillis = other.finalStartMillis; michael@0: michael@0: clearTransitionRules(); michael@0: michael@0: return *this; michael@0: } michael@0: michael@0: /** michael@0: * Destructor michael@0: */ michael@0: OlsonTimeZone::~OlsonTimeZone() { michael@0: deleteTransitionRules(); michael@0: delete finalZone; michael@0: } michael@0: michael@0: /** michael@0: * Returns true if the two TimeZone objects are equal. michael@0: */ michael@0: UBool OlsonTimeZone::operator==(const TimeZone& other) const { michael@0: return ((this == &other) || michael@0: (typeid(*this) == typeid(other) && michael@0: TimeZone::operator==(other) && michael@0: hasSameRules(other))); michael@0: } michael@0: michael@0: /** michael@0: * TimeZone API. michael@0: */ michael@0: TimeZone* OlsonTimeZone::clone() const { michael@0: return new OlsonTimeZone(*this); michael@0: } michael@0: michael@0: /** michael@0: * TimeZone API. michael@0: */ michael@0: int32_t OlsonTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, michael@0: int32_t dom, uint8_t dow, michael@0: int32_t millis, UErrorCode& ec) const { michael@0: if (month < UCAL_JANUARY || month > UCAL_DECEMBER) { michael@0: if (U_SUCCESS(ec)) { michael@0: ec = U_ILLEGAL_ARGUMENT_ERROR; michael@0: } michael@0: return 0; michael@0: } else { michael@0: return getOffset(era, year, month, dom, dow, millis, michael@0: Grego::monthLength(year, month), michael@0: ec); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * TimeZone API. michael@0: */ michael@0: int32_t OlsonTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, michael@0: int32_t dom, uint8_t dow, michael@0: int32_t millis, int32_t monthLength, michael@0: UErrorCode& ec) const { michael@0: if (U_FAILURE(ec)) { michael@0: return 0; michael@0: } michael@0: michael@0: if ((era != GregorianCalendar::AD && era != GregorianCalendar::BC) michael@0: || month < UCAL_JANUARY michael@0: || month > UCAL_DECEMBER michael@0: || dom < 1 michael@0: || dom > monthLength michael@0: || dow < UCAL_SUNDAY michael@0: || dow > UCAL_SATURDAY michael@0: || millis < 0 michael@0: || millis >= U_MILLIS_PER_DAY michael@0: || monthLength < 28 michael@0: || monthLength > 31) { michael@0: ec = U_ILLEGAL_ARGUMENT_ERROR; michael@0: return 0; michael@0: } michael@0: michael@0: if (era == GregorianCalendar::BC) { michael@0: year = -year; michael@0: } michael@0: michael@0: if (finalZone != NULL && year >= finalStartYear) { michael@0: return finalZone->getOffset(era, year, month, dom, dow, michael@0: millis, monthLength, ec); michael@0: } michael@0: michael@0: // Compute local epoch millis from input fields michael@0: UDate date = (UDate)(Grego::fieldsToDay(year, month, dom) * U_MILLIS_PER_DAY + millis); michael@0: int32_t rawoff, dstoff; michael@0: getHistoricalOffset(date, TRUE, kDaylight, kStandard, rawoff, dstoff); michael@0: return rawoff + dstoff; michael@0: } michael@0: michael@0: /** michael@0: * TimeZone API. michael@0: */ michael@0: void OlsonTimeZone::getOffset(UDate date, UBool local, int32_t& rawoff, michael@0: int32_t& dstoff, UErrorCode& ec) const { michael@0: if (U_FAILURE(ec)) { michael@0: return; michael@0: } michael@0: if (finalZone != NULL && date >= finalStartMillis) { michael@0: finalZone->getOffset(date, local, rawoff, dstoff, ec); michael@0: } else { michael@0: getHistoricalOffset(date, local, kFormer, kLatter, rawoff, dstoff); michael@0: } michael@0: } michael@0: michael@0: void michael@0: OlsonTimeZone::getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt, michael@0: int32_t& rawoff, int32_t& dstoff, UErrorCode& ec) const { michael@0: if (U_FAILURE(ec)) { michael@0: return; michael@0: } michael@0: if (finalZone != NULL && date >= finalStartMillis) { michael@0: finalZone->getOffsetFromLocal(date, nonExistingTimeOpt, duplicatedTimeOpt, rawoff, dstoff, ec); michael@0: } else { michael@0: getHistoricalOffset(date, TRUE, nonExistingTimeOpt, duplicatedTimeOpt, rawoff, dstoff); michael@0: } michael@0: } michael@0: michael@0: michael@0: /** michael@0: * TimeZone API. michael@0: */ michael@0: void OlsonTimeZone::setRawOffset(int32_t /*offsetMillis*/) { michael@0: // We don't support this operation, since OlsonTimeZones are michael@0: // immutable (except for the ID, which is in the base class). michael@0: michael@0: // Nothing to do! michael@0: } michael@0: michael@0: /** michael@0: * TimeZone API. michael@0: */ michael@0: int32_t OlsonTimeZone::getRawOffset() const { michael@0: UErrorCode ec = U_ZERO_ERROR; michael@0: int32_t raw, dst; michael@0: getOffset((double) uprv_getUTCtime() * U_MILLIS_PER_SECOND, michael@0: FALSE, raw, dst, ec); michael@0: return raw; michael@0: } michael@0: michael@0: #if defined U_DEBUG_TZ michael@0: void printTime(double ms) { michael@0: int32_t year, month, dom, dow; michael@0: double millis=0; michael@0: double days = ClockMath::floorDivide(((double)ms), (double)U_MILLIS_PER_DAY, millis); michael@0: michael@0: Grego::dayToFields(days, year, month, dom, dow); michael@0: U_DEBUG_TZ_MSG((" getHistoricalOffset: time %.1f (%04d.%02d.%02d+%.1fh)\n", ms, michael@0: year, month+1, dom, (millis/kOneHour))); michael@0: } michael@0: #endif michael@0: michael@0: int64_t michael@0: OlsonTimeZone::transitionTimeInSeconds(int16_t transIdx) const { michael@0: U_ASSERT(transIdx >= 0 && transIdx < transitionCount()); michael@0: michael@0: if (transIdx < transitionCountPre32) { michael@0: return (((int64_t)((uint32_t)transitionTimesPre32[transIdx << 1])) << 32) michael@0: | ((int64_t)((uint32_t)transitionTimesPre32[(transIdx << 1) + 1])); michael@0: } michael@0: michael@0: transIdx -= transitionCountPre32; michael@0: if (transIdx < transitionCount32) { michael@0: return (int64_t)transitionTimes32[transIdx]; michael@0: } michael@0: michael@0: transIdx -= transitionCount32; michael@0: return (((int64_t)((uint32_t)transitionTimesPost32[transIdx << 1])) << 32) michael@0: | ((int64_t)((uint32_t)transitionTimesPost32[(transIdx << 1) + 1])); michael@0: } michael@0: michael@0: // Maximum absolute offset in seconds (86400 seconds = 1 day) michael@0: // getHistoricalOffset uses this constant as safety margin of michael@0: // quick zone transition checking. michael@0: #define MAX_OFFSET_SECONDS 86400 michael@0: michael@0: void michael@0: OlsonTimeZone::getHistoricalOffset(UDate date, UBool local, michael@0: int32_t NonExistingTimeOpt, int32_t DuplicatedTimeOpt, michael@0: int32_t& rawoff, int32_t& dstoff) const { michael@0: U_DEBUG_TZ_MSG(("getHistoricalOffset(%.1f, %s, %d, %d, raw, dst)\n", michael@0: date, local?"T":"F", NonExistingTimeOpt, DuplicatedTimeOpt)); michael@0: #if defined U_DEBUG_TZ michael@0: printTime(date*1000.0); michael@0: #endif michael@0: int16_t transCount = transitionCount(); michael@0: michael@0: if (transCount > 0) { michael@0: double sec = uprv_floor(date / U_MILLIS_PER_SECOND); michael@0: if (!local && sec < transitionTimeInSeconds(0)) { michael@0: // Before the first transition time michael@0: rawoff = initialRawOffset() * U_MILLIS_PER_SECOND; michael@0: dstoff = initialDstOffset() * U_MILLIS_PER_SECOND; michael@0: } else { michael@0: // Linear search from the end is the fastest approach, since michael@0: // most lookups will happen at/near the end. michael@0: int16_t transIdx; michael@0: for (transIdx = transCount - 1; transIdx >= 0; transIdx--) { michael@0: int64_t transition = transitionTimeInSeconds(transIdx); michael@0: michael@0: if (local && (sec >= (transition - MAX_OFFSET_SECONDS))) { michael@0: int32_t offsetBefore = zoneOffsetAt(transIdx - 1); michael@0: UBool dstBefore = dstOffsetAt(transIdx - 1) != 0; michael@0: michael@0: int32_t offsetAfter = zoneOffsetAt(transIdx); michael@0: UBool dstAfter = dstOffsetAt(transIdx) != 0; michael@0: michael@0: UBool dstToStd = dstBefore && !dstAfter; michael@0: UBool stdToDst = !dstBefore && dstAfter; michael@0: michael@0: if (offsetAfter - offsetBefore >= 0) { michael@0: // Positive transition, which makes a non-existing local time range michael@0: if (((NonExistingTimeOpt & kStdDstMask) == kStandard && dstToStd) michael@0: || ((NonExistingTimeOpt & kStdDstMask) == kDaylight && stdToDst)) { michael@0: transition += offsetBefore; michael@0: } else if (((NonExistingTimeOpt & kStdDstMask) == kStandard && stdToDst) michael@0: || ((NonExistingTimeOpt & kStdDstMask) == kDaylight && dstToStd)) { michael@0: transition += offsetAfter; michael@0: } else if ((NonExistingTimeOpt & kFormerLatterMask) == kLatter) { michael@0: transition += offsetBefore; michael@0: } else { michael@0: // Interprets the time with rule before the transition, michael@0: // default for non-existing time range michael@0: transition += offsetAfter; michael@0: } michael@0: } else { michael@0: // Negative transition, which makes a duplicated local time range michael@0: if (((DuplicatedTimeOpt & kStdDstMask) == kStandard && dstToStd) michael@0: || ((DuplicatedTimeOpt & kStdDstMask) == kDaylight && stdToDst)) { michael@0: transition += offsetAfter; michael@0: } else if (((DuplicatedTimeOpt & kStdDstMask) == kStandard && stdToDst) michael@0: || ((DuplicatedTimeOpt & kStdDstMask) == kDaylight && dstToStd)) { michael@0: transition += offsetBefore; michael@0: } else if ((DuplicatedTimeOpt & kFormerLatterMask) == kFormer) { michael@0: transition += offsetBefore; michael@0: } else { michael@0: // Interprets the time with rule after the transition, michael@0: // default for duplicated local time range michael@0: transition += offsetAfter; michael@0: } michael@0: } michael@0: } michael@0: if (sec >= transition) { michael@0: break; michael@0: } michael@0: } michael@0: // transIdx could be -1 when local=true michael@0: rawoff = rawOffsetAt(transIdx) * U_MILLIS_PER_SECOND; michael@0: dstoff = dstOffsetAt(transIdx) * U_MILLIS_PER_SECOND; michael@0: } michael@0: } else { michael@0: // No transitions, single pair of offsets only michael@0: rawoff = initialRawOffset() * U_MILLIS_PER_SECOND; michael@0: dstoff = initialDstOffset() * U_MILLIS_PER_SECOND; michael@0: } michael@0: U_DEBUG_TZ_MSG(("getHistoricalOffset(%.1f, %s, %d, %d, raw, dst) - raw=%d, dst=%d\n", michael@0: date, local?"T":"F", NonExistingTimeOpt, DuplicatedTimeOpt, rawoff, dstoff)); michael@0: } michael@0: michael@0: /** michael@0: * TimeZone API. michael@0: */ michael@0: UBool OlsonTimeZone::useDaylightTime() const { michael@0: // If DST was observed in 1942 (for example) but has never been michael@0: // observed from 1943 to the present, most clients will expect michael@0: // this method to return FALSE. This method determines whether michael@0: // DST is in use in the current year (at any point in the year) michael@0: // and returns TRUE if so. michael@0: michael@0: UDate current = uprv_getUTCtime(); michael@0: if (finalZone != NULL && current >= finalStartMillis) { michael@0: return finalZone->useDaylightTime(); michael@0: } michael@0: michael@0: int32_t year, month, dom, dow, doy, mid; michael@0: Grego::timeToFields(current, year, month, dom, dow, doy, mid); michael@0: michael@0: // Find start of this year, and start of next year michael@0: double start = Grego::fieldsToDay(year, 0, 1) * SECONDS_PER_DAY; michael@0: double limit = Grego::fieldsToDay(year+1, 0, 1) * SECONDS_PER_DAY; michael@0: michael@0: // Return TRUE if DST is observed at any time during the current michael@0: // year. michael@0: for (int16_t i = 0; i < transitionCount(); ++i) { michael@0: double transition = (double)transitionTimeInSeconds(i); michael@0: if (transition >= limit) { michael@0: break; michael@0: } michael@0: if ((transition >= start && dstOffsetAt(i) != 0) michael@0: || (transition > start && dstOffsetAt(i - 1) != 0)) { michael@0: return TRUE; michael@0: } michael@0: } michael@0: return FALSE; michael@0: } michael@0: int32_t michael@0: OlsonTimeZone::getDSTSavings() const{ michael@0: if (finalZone != NULL){ michael@0: return finalZone->getDSTSavings(); michael@0: } michael@0: return TimeZone::getDSTSavings(); michael@0: } michael@0: /** michael@0: * TimeZone API. michael@0: */ michael@0: UBool OlsonTimeZone::inDaylightTime(UDate date, UErrorCode& ec) const { michael@0: int32_t raw, dst; michael@0: getOffset(date, FALSE, raw, dst, ec); michael@0: return dst != 0; michael@0: } michael@0: michael@0: UBool michael@0: OlsonTimeZone::hasSameRules(const TimeZone &other) const { michael@0: if (this == &other) { michael@0: return TRUE; michael@0: } michael@0: const OlsonTimeZone* z = dynamic_cast(&other); michael@0: if (z == NULL) { michael@0: return FALSE; michael@0: } michael@0: michael@0: // [sic] pointer comparison: typeMapData points into michael@0: // memory-mapped or DLL space, so if two zones have the same michael@0: // pointer, they are equal. michael@0: if (typeMapData == z->typeMapData) { michael@0: return TRUE; michael@0: } michael@0: michael@0: // If the pointers are not equal, the zones may still michael@0: // be equal if their rules and transitions are equal michael@0: if ((finalZone == NULL && z->finalZone != NULL) michael@0: || (finalZone != NULL && z->finalZone == NULL) michael@0: || (finalZone != NULL && z->finalZone != NULL && *finalZone != *z->finalZone)) { michael@0: return FALSE; michael@0: } michael@0: michael@0: if (finalZone != NULL) { michael@0: if (finalStartYear != z->finalStartYear || finalStartMillis != z->finalStartMillis) { michael@0: return FALSE; michael@0: } michael@0: } michael@0: if (typeCount != z->typeCount michael@0: || transitionCountPre32 != z->transitionCountPre32 michael@0: || transitionCount32 != z->transitionCount32 michael@0: || transitionCountPost32 != z->transitionCountPost32) { michael@0: return FALSE; michael@0: } michael@0: michael@0: return michael@0: arrayEqual(transitionTimesPre32, z->transitionTimesPre32, sizeof(transitionTimesPre32[0]) * transitionCountPre32 << 1) michael@0: && arrayEqual(transitionTimes32, z->transitionTimes32, sizeof(transitionTimes32[0]) * transitionCount32) michael@0: && arrayEqual(transitionTimesPost32, z->transitionTimesPost32, sizeof(transitionTimesPost32[0]) * transitionCountPost32 << 1) michael@0: && arrayEqual(typeOffsets, z->typeOffsets, sizeof(typeOffsets[0]) * typeCount << 1) michael@0: && arrayEqual(typeMapData, z->typeMapData, sizeof(typeMapData[0]) * transitionCount()); michael@0: } michael@0: michael@0: void michael@0: OlsonTimeZone::clearTransitionRules(void) { michael@0: initialRule = NULL; michael@0: firstTZTransition = NULL; michael@0: firstFinalTZTransition = NULL; michael@0: historicRules = NULL; michael@0: historicRuleCount = 0; michael@0: finalZoneWithStartYear = NULL; michael@0: firstTZTransitionIdx = 0; michael@0: transitionRulesInitOnce.reset(); michael@0: } michael@0: michael@0: void michael@0: OlsonTimeZone::deleteTransitionRules(void) { michael@0: if (initialRule != NULL) { michael@0: delete initialRule; michael@0: } michael@0: if (firstTZTransition != NULL) { michael@0: delete firstTZTransition; michael@0: } michael@0: if (firstFinalTZTransition != NULL) { michael@0: delete firstFinalTZTransition; michael@0: } michael@0: if (finalZoneWithStartYear != NULL) { michael@0: delete finalZoneWithStartYear; michael@0: } michael@0: if (historicRules != NULL) { michael@0: for (int i = 0; i < historicRuleCount; i++) { michael@0: if (historicRules[i] != NULL) { michael@0: delete historicRules[i]; michael@0: } michael@0: } michael@0: uprv_free(historicRules); michael@0: } michael@0: clearTransitionRules(); michael@0: } michael@0: michael@0: /* michael@0: * Lazy transition rules initializer michael@0: */ michael@0: michael@0: static void U_CALLCONV initRules(OlsonTimeZone *This, UErrorCode &status) { michael@0: This->initTransitionRules(status); michael@0: } michael@0: michael@0: void michael@0: OlsonTimeZone::checkTransitionRules(UErrorCode& status) const { michael@0: OlsonTimeZone *ncThis = const_cast(this); michael@0: umtx_initOnce(ncThis->transitionRulesInitOnce, &initRules, ncThis, status); michael@0: } michael@0: michael@0: void michael@0: OlsonTimeZone::initTransitionRules(UErrorCode& status) { michael@0: if(U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: deleteTransitionRules(); michael@0: UnicodeString tzid; michael@0: getID(tzid); michael@0: michael@0: UnicodeString stdName = tzid + UNICODE_STRING_SIMPLE("(STD)"); michael@0: UnicodeString dstName = tzid + UNICODE_STRING_SIMPLE("(DST)"); michael@0: michael@0: int32_t raw, dst; michael@0: michael@0: // Create initial rule michael@0: raw = initialRawOffset() * U_MILLIS_PER_SECOND; michael@0: dst = initialDstOffset() * U_MILLIS_PER_SECOND; michael@0: initialRule = new InitialTimeZoneRule((dst == 0 ? stdName : dstName), raw, dst); michael@0: // Check to make sure initialRule was created michael@0: if (initialRule == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: deleteTransitionRules(); michael@0: return; michael@0: } michael@0: michael@0: int32_t transCount = transitionCount(); michael@0: if (transCount > 0) { michael@0: int16_t transitionIdx, typeIdx; michael@0: michael@0: // We probably no longer need to check the first "real" transition michael@0: // here, because the new tzcode remove such transitions already. michael@0: // For now, keeping this code for just in case. Feb 19, 2010 Yoshito michael@0: firstTZTransitionIdx = 0; michael@0: for (transitionIdx = 0; transitionIdx < transCount; transitionIdx++) { michael@0: if (typeMapData[transitionIdx] != 0) { // type 0 is the initial type michael@0: break; michael@0: } michael@0: firstTZTransitionIdx++; michael@0: } michael@0: if (transitionIdx == transCount) { michael@0: // Actually no transitions... michael@0: } else { michael@0: // Build historic rule array michael@0: UDate* times = (UDate*)uprv_malloc(sizeof(UDate)*transCount); /* large enough to store all transition times */ michael@0: if (times == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: deleteTransitionRules(); michael@0: return; michael@0: } michael@0: for (typeIdx = 0; typeIdx < typeCount; typeIdx++) { michael@0: // Gather all start times for each pair of offsets michael@0: int32_t nTimes = 0; michael@0: for (transitionIdx = firstTZTransitionIdx; transitionIdx < transCount; transitionIdx++) { michael@0: if (typeIdx == (int16_t)typeMapData[transitionIdx]) { michael@0: UDate tt = (UDate)transitionTime(transitionIdx); michael@0: if (finalZone == NULL || tt <= finalStartMillis) { michael@0: // Exclude transitions after finalMillis michael@0: times[nTimes++] = tt; michael@0: } michael@0: } michael@0: } michael@0: if (nTimes > 0) { michael@0: // Create a TimeArrayTimeZoneRule michael@0: raw = typeOffsets[typeIdx << 1] * U_MILLIS_PER_SECOND; michael@0: dst = typeOffsets[(typeIdx << 1) + 1] * U_MILLIS_PER_SECOND; michael@0: if (historicRules == NULL) { michael@0: historicRuleCount = typeCount; michael@0: historicRules = (TimeArrayTimeZoneRule**)uprv_malloc(sizeof(TimeArrayTimeZoneRule*)*historicRuleCount); michael@0: if (historicRules == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: deleteTransitionRules(); michael@0: uprv_free(times); michael@0: return; michael@0: } michael@0: for (int i = 0; i < historicRuleCount; i++) { michael@0: // Initialize TimeArrayTimeZoneRule pointers as NULL michael@0: historicRules[i] = NULL; michael@0: } michael@0: } michael@0: historicRules[typeIdx] = new TimeArrayTimeZoneRule((dst == 0 ? stdName : dstName), michael@0: raw, dst, times, nTimes, DateTimeRule::UTC_TIME); michael@0: // Check for memory allocation error michael@0: if (historicRules[typeIdx] == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: deleteTransitionRules(); michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: uprv_free(times); michael@0: michael@0: // Create initial transition michael@0: typeIdx = (int16_t)typeMapData[firstTZTransitionIdx]; michael@0: firstTZTransition = new TimeZoneTransition((UDate)transitionTime(firstTZTransitionIdx), michael@0: *initialRule, *historicRules[typeIdx]); michael@0: // Check to make sure firstTZTransition was created. michael@0: if (firstTZTransition == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: deleteTransitionRules(); michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: if (finalZone != NULL) { michael@0: // Get the first occurence of final rule starts michael@0: UDate startTime = (UDate)finalStartMillis; michael@0: TimeZoneRule *firstFinalRule = NULL; michael@0: michael@0: if (finalZone->useDaylightTime()) { michael@0: /* michael@0: * Note: When an OlsonTimeZone is constructed, we should set the final year michael@0: * as the start year of finalZone. However, the bounday condition used for michael@0: * getting offset from finalZone has some problems. michael@0: * For now, we do not set the valid start year when the construction time michael@0: * and create a clone and set the start year when extracting rules. michael@0: */ michael@0: finalZoneWithStartYear = (SimpleTimeZone*)finalZone->clone(); michael@0: // Check to make sure finalZone was actually cloned. michael@0: if (finalZoneWithStartYear == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: deleteTransitionRules(); michael@0: return; michael@0: } michael@0: finalZoneWithStartYear->setStartYear(finalStartYear); michael@0: michael@0: TimeZoneTransition tzt; michael@0: finalZoneWithStartYear->getNextTransition(startTime, false, tzt); michael@0: firstFinalRule = tzt.getTo()->clone(); michael@0: // Check to make sure firstFinalRule received proper clone. michael@0: if (firstFinalRule == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: deleteTransitionRules(); michael@0: return; michael@0: } michael@0: startTime = tzt.getTime(); michael@0: } else { michael@0: // final rule with no transitions michael@0: finalZoneWithStartYear = (SimpleTimeZone*)finalZone->clone(); michael@0: // Check to make sure finalZone was actually cloned. michael@0: if (finalZoneWithStartYear == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: deleteTransitionRules(); michael@0: return; michael@0: } michael@0: finalZone->getID(tzid); michael@0: firstFinalRule = new TimeArrayTimeZoneRule(tzid, michael@0: finalZone->getRawOffset(), 0, &startTime, 1, DateTimeRule::UTC_TIME); michael@0: // Check firstFinalRule was properly created. michael@0: if (firstFinalRule == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: deleteTransitionRules(); michael@0: return; michael@0: } michael@0: } michael@0: TimeZoneRule *prevRule = NULL; michael@0: if (transCount > 0) { michael@0: prevRule = historicRules[typeMapData[transCount - 1]]; michael@0: } michael@0: if (prevRule == NULL) { michael@0: // No historic transitions, but only finalZone available michael@0: prevRule = initialRule; michael@0: } michael@0: firstFinalTZTransition = new TimeZoneTransition(); michael@0: // Check to make sure firstFinalTZTransition was created before dereferencing michael@0: if (firstFinalTZTransition == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: deleteTransitionRules(); michael@0: return; michael@0: } michael@0: firstFinalTZTransition->setTime(startTime); michael@0: firstFinalTZTransition->adoptFrom(prevRule->clone()); michael@0: firstFinalTZTransition->adoptTo(firstFinalRule); michael@0: } michael@0: } michael@0: michael@0: UBool michael@0: OlsonTimeZone::getNextTransition(UDate base, UBool inclusive, TimeZoneTransition& result) const { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: checkTransitionRules(status); michael@0: if (U_FAILURE(status)) { michael@0: return FALSE; michael@0: } michael@0: michael@0: if (finalZone != NULL) { michael@0: if (inclusive && base == firstFinalTZTransition->getTime()) { michael@0: result = *firstFinalTZTransition; michael@0: return TRUE; michael@0: } else if (base >= firstFinalTZTransition->getTime()) { michael@0: if (finalZone->useDaylightTime()) { michael@0: //return finalZone->getNextTransition(base, inclusive, result); michael@0: return finalZoneWithStartYear->getNextTransition(base, inclusive, result); michael@0: } else { michael@0: // No more transitions michael@0: return FALSE; michael@0: } michael@0: } michael@0: } michael@0: if (historicRules != NULL) { michael@0: // Find a historical transition michael@0: int16_t transCount = transitionCount(); michael@0: int16_t ttidx = transCount - 1; michael@0: for (; ttidx >= firstTZTransitionIdx; ttidx--) { michael@0: UDate t = (UDate)transitionTime(ttidx); michael@0: if (base > t || (!inclusive && base == t)) { michael@0: break; michael@0: } michael@0: } michael@0: if (ttidx == transCount - 1) { michael@0: if (firstFinalTZTransition != NULL) { michael@0: result = *firstFinalTZTransition; michael@0: return TRUE; michael@0: } else { michael@0: return FALSE; michael@0: } michael@0: } else if (ttidx < firstTZTransitionIdx) { michael@0: result = *firstTZTransition; michael@0: return TRUE; michael@0: } else { michael@0: // Create a TimeZoneTransition michael@0: TimeZoneRule *to = historicRules[typeMapData[ttidx + 1]]; michael@0: TimeZoneRule *from = historicRules[typeMapData[ttidx]]; michael@0: UDate startTime = (UDate)transitionTime(ttidx+1); michael@0: michael@0: // The transitions loaded from zoneinfo.res may contain non-transition data michael@0: UnicodeString fromName, toName; michael@0: from->getName(fromName); michael@0: to->getName(toName); michael@0: if (fromName == toName && from->getRawOffset() == to->getRawOffset() michael@0: && from->getDSTSavings() == to->getDSTSavings()) { michael@0: return getNextTransition(startTime, false, result); michael@0: } michael@0: result.setTime(startTime); michael@0: result.adoptFrom(from->clone()); michael@0: result.adoptTo(to->clone()); michael@0: return TRUE; michael@0: } michael@0: } michael@0: return FALSE; michael@0: } michael@0: michael@0: UBool michael@0: OlsonTimeZone::getPreviousTransition(UDate base, UBool inclusive, TimeZoneTransition& result) const { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: checkTransitionRules(status); michael@0: if (U_FAILURE(status)) { michael@0: return FALSE; michael@0: } michael@0: michael@0: if (finalZone != NULL) { michael@0: if (inclusive && base == firstFinalTZTransition->getTime()) { michael@0: result = *firstFinalTZTransition; michael@0: return TRUE; michael@0: } else if (base > firstFinalTZTransition->getTime()) { michael@0: if (finalZone->useDaylightTime()) { michael@0: //return finalZone->getPreviousTransition(base, inclusive, result); michael@0: return finalZoneWithStartYear->getPreviousTransition(base, inclusive, result); michael@0: } else { michael@0: result = *firstFinalTZTransition; michael@0: return TRUE; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (historicRules != NULL) { michael@0: // Find a historical transition michael@0: int16_t ttidx = transitionCount() - 1; michael@0: for (; ttidx >= firstTZTransitionIdx; ttidx--) { michael@0: UDate t = (UDate)transitionTime(ttidx); michael@0: if (base > t || (inclusive && base == t)) { michael@0: break; michael@0: } michael@0: } michael@0: if (ttidx < firstTZTransitionIdx) { michael@0: // No more transitions michael@0: return FALSE; michael@0: } else if (ttidx == firstTZTransitionIdx) { michael@0: result = *firstTZTransition; michael@0: return TRUE; michael@0: } else { michael@0: // Create a TimeZoneTransition michael@0: TimeZoneRule *to = historicRules[typeMapData[ttidx]]; michael@0: TimeZoneRule *from = historicRules[typeMapData[ttidx-1]]; michael@0: UDate startTime = (UDate)transitionTime(ttidx); michael@0: michael@0: // The transitions loaded from zoneinfo.res may contain non-transition data michael@0: UnicodeString fromName, toName; michael@0: from->getName(fromName); michael@0: to->getName(toName); michael@0: if (fromName == toName && from->getRawOffset() == to->getRawOffset() michael@0: && from->getDSTSavings() == to->getDSTSavings()) { michael@0: return getPreviousTransition(startTime, false, result); michael@0: } michael@0: result.setTime(startTime); michael@0: result.adoptFrom(from->clone()); michael@0: result.adoptTo(to->clone()); michael@0: return TRUE; michael@0: } michael@0: } michael@0: return FALSE; michael@0: } michael@0: michael@0: int32_t michael@0: OlsonTimeZone::countTransitionRules(UErrorCode& status) const { michael@0: if (U_FAILURE(status)) { michael@0: return 0; michael@0: } michael@0: checkTransitionRules(status); michael@0: if (U_FAILURE(status)) { michael@0: return 0; michael@0: } michael@0: michael@0: int32_t count = 0; michael@0: if (historicRules != NULL) { michael@0: // historicRules may contain null entries when original zoneinfo data michael@0: // includes non transition data. michael@0: for (int32_t i = 0; i < historicRuleCount; i++) { michael@0: if (historicRules[i] != NULL) { michael@0: count++; michael@0: } michael@0: } michael@0: } michael@0: if (finalZone != NULL) { michael@0: if (finalZone->useDaylightTime()) { michael@0: count += 2; michael@0: } else { michael@0: count++; michael@0: } michael@0: } michael@0: return count; michael@0: } michael@0: michael@0: void michael@0: OlsonTimeZone::getTimeZoneRules(const InitialTimeZoneRule*& initial, michael@0: const TimeZoneRule* trsrules[], michael@0: int32_t& trscount, michael@0: UErrorCode& status) const { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: checkTransitionRules(status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: michael@0: // Initial rule michael@0: initial = initialRule; michael@0: michael@0: // Transition rules michael@0: int32_t cnt = 0; michael@0: if (historicRules != NULL && trscount > cnt) { michael@0: // historicRules may contain null entries when original zoneinfo data michael@0: // includes non transition data. michael@0: for (int32_t i = 0; i < historicRuleCount; i++) { michael@0: if (historicRules[i] != NULL) { michael@0: trsrules[cnt++] = historicRules[i]; michael@0: if (cnt >= trscount) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: if (finalZoneWithStartYear != NULL && trscount > cnt) { michael@0: const InitialTimeZoneRule *tmpini; michael@0: int32_t tmpcnt = trscount - cnt; michael@0: finalZoneWithStartYear->getTimeZoneRules(tmpini, &trsrules[cnt], tmpcnt, status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: cnt += tmpcnt; michael@0: } michael@0: // Set the result length michael@0: trscount = cnt; michael@0: } michael@0: michael@0: U_NAMESPACE_END michael@0: michael@0: #endif // !UCONFIG_NO_FORMATTING michael@0: michael@0: //eof