michael@0: /* michael@0: ******************************************************************************* michael@0: * Copyright (C) 1997-2013, International Business Machines Corporation and michael@0: * others. All Rights Reserved. michael@0: ******************************************************************************* michael@0: * michael@0: * File TIMEZONE.CPP michael@0: * michael@0: * Modification History: michael@0: * michael@0: * Date Name Description michael@0: * 12/05/96 clhuang Creation. michael@0: * 04/21/97 aliu General clean-up and bug fixing. michael@0: * 05/08/97 aliu Fixed Hashtable code per code review. michael@0: * 07/09/97 helena Changed createInstance to createDefault. michael@0: * 07/29/97 aliu Updated with all-new list of 96 UNIX-derived michael@0: * TimeZones. Changed mechanism to load from static michael@0: * array rather than resource bundle. michael@0: * 07/07/1998 srl Bugfixes from the Java side: UTC GMT CAT NST michael@0: * Added getDisplayName API michael@0: * going to add custom parsing. michael@0: * michael@0: * ISSUES: michael@0: * - should getDisplayName cache something? michael@0: * - should custom time zones be cached? [probably] michael@0: * 08/10/98 stephen Brought getDisplayName() API in-line w/ conventions michael@0: * 08/19/98 stephen Changed createTimeZone() to never return 0 michael@0: * 09/02/98 stephen Added getOffset(monthLen) and hasSameRules() michael@0: * 09/15/98 stephen Added getStaticClassID() michael@0: * 02/22/99 stephen Removed character literals for EBCDIC safety michael@0: * 05/04/99 stephen Changed initDefault() for Mutex issues michael@0: * 07/12/99 helena HPUX 11 CC Port. michael@0: * 12/03/99 aliu Moved data out of static table into icudata.dll. michael@0: * Substantial rewrite of zone lookup, default zone, and michael@0: * available IDs code. Misc. cleanup. michael@0: *********************************************************************************/ michael@0: michael@0: #include "utypeinfo.h" // for 'typeid' to work michael@0: michael@0: #include "unicode/utypes.h" michael@0: #include "unicode/ustring.h" michael@0: #include "uassert.h" michael@0: #include "ustr_imp.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: static char gStrBuf[256]; michael@0: #define U_DEBUG_TZ_STR(x) u_austrncpy(gStrBuf,x,sizeof(gStrBuf)-1) 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: #if !UCONFIG_NO_FORMATTING michael@0: michael@0: #include "unicode/simpletz.h" michael@0: #include "unicode/calendar.h" michael@0: #include "unicode/gregocal.h" michael@0: #include "unicode/ures.h" michael@0: #include "unicode/tzfmt.h" michael@0: #include "unicode/numfmt.h" michael@0: #include "gregoimp.h" michael@0: #include "uresimp.h" // struct UResourceBundle michael@0: #include "olsontz.h" michael@0: #include "mutex.h" michael@0: #include "unicode/udata.h" michael@0: #include "ucln_in.h" michael@0: #include "cstring.h" michael@0: #include "cmemory.h" michael@0: #include "unicode/strenum.h" michael@0: #include "uassert.h" michael@0: #include "zonemeta.h" michael@0: michael@0: #define kZONEINFO "zoneinfo64" michael@0: #define kREGIONS "Regions" michael@0: #define kZONES "Zones" michael@0: #define kRULES "Rules" michael@0: #define kNAMES "Names" michael@0: #define kTZVERSION "TZVersion" michael@0: #define kLINKS "links" michael@0: #define kMAX_CUSTOM_HOUR 23 michael@0: #define kMAX_CUSTOM_MIN 59 michael@0: #define kMAX_CUSTOM_SEC 59 michael@0: #define MINUS 0x002D michael@0: #define PLUS 0x002B michael@0: #define ZERO_DIGIT 0x0030 michael@0: #define COLON 0x003A michael@0: michael@0: // Static data and constants michael@0: michael@0: static const UChar WORLD[] = {0x30, 0x30, 0x31, 0x00}; /* "001" */ michael@0: michael@0: static const UChar GMT_ID[] = {0x47, 0x4D, 0x54, 0x00}; /* "GMT" */ michael@0: static const UChar UNKNOWN_ZONE_ID[] = {0x45, 0x74, 0x63, 0x2F, 0x55, 0x6E, 0x6B, 0x6E, 0x6F, 0x77, 0x6E, 0x00}; /* "Etc/Unknown" */ michael@0: static const int32_t GMT_ID_LENGTH = 3; michael@0: static const int32_t UNKNOWN_ZONE_ID_LENGTH = 11; michael@0: michael@0: static icu::TimeZone* DEFAULT_ZONE = NULL; michael@0: static icu::UInitOnce gDefaultZoneInitOnce = U_INITONCE_INITIALIZER; michael@0: michael@0: static icu::TimeZone* _GMT = NULL; michael@0: static icu::TimeZone* _UNKNOWN_ZONE = NULL; michael@0: static icu::UInitOnce gStaticZonesInitOnce = U_INITONCE_INITIALIZER; michael@0: michael@0: static char TZDATA_VERSION[16]; michael@0: static icu::UInitOnce gTZDataVersionInitOnce = U_INITONCE_INITIALIZER; michael@0: michael@0: static int32_t* MAP_SYSTEM_ZONES = NULL; michael@0: static int32_t* MAP_CANONICAL_SYSTEM_ZONES = NULL; michael@0: static int32_t* MAP_CANONICAL_SYSTEM_LOCATION_ZONES = NULL; michael@0: michael@0: static int32_t LEN_SYSTEM_ZONES = 0; michael@0: static int32_t LEN_CANONICAL_SYSTEM_ZONES = 0; michael@0: static int32_t LEN_CANONICAL_SYSTEM_LOCATION_ZONES = 0; michael@0: michael@0: static icu::UInitOnce gSystemZonesInitOnce = U_INITONCE_INITIALIZER; michael@0: static icu::UInitOnce gCanonicalZonesInitOnce = U_INITONCE_INITIALIZER; michael@0: static icu::UInitOnce gCanonicalLocationZonesInitOnce = U_INITONCE_INITIALIZER; michael@0: michael@0: U_CDECL_BEGIN michael@0: static UBool U_CALLCONV timeZone_cleanup(void) michael@0: { michael@0: U_NAMESPACE_USE michael@0: delete DEFAULT_ZONE; michael@0: DEFAULT_ZONE = NULL; michael@0: gDefaultZoneInitOnce.reset(); michael@0: michael@0: delete _GMT; michael@0: _GMT = NULL; michael@0: delete _UNKNOWN_ZONE; michael@0: _UNKNOWN_ZONE = NULL; michael@0: gStaticZonesInitOnce.reset(); michael@0: michael@0: uprv_memset(TZDATA_VERSION, 0, sizeof(TZDATA_VERSION)); michael@0: gTZDataVersionInitOnce.reset(); michael@0: michael@0: LEN_SYSTEM_ZONES = 0; michael@0: uprv_free(MAP_SYSTEM_ZONES); michael@0: MAP_SYSTEM_ZONES = 0; michael@0: gSystemZonesInitOnce.reset(); michael@0: michael@0: LEN_CANONICAL_SYSTEM_ZONES = 0; michael@0: uprv_free(MAP_CANONICAL_SYSTEM_ZONES); michael@0: MAP_CANONICAL_SYSTEM_ZONES = 0; michael@0: gCanonicalZonesInitOnce.reset(); michael@0: michael@0: LEN_CANONICAL_SYSTEM_LOCATION_ZONES = 0; michael@0: uprv_free(MAP_CANONICAL_SYSTEM_LOCATION_ZONES); michael@0: MAP_CANONICAL_SYSTEM_LOCATION_ZONES = 0; michael@0: gCanonicalLocationZonesInitOnce.reset(); michael@0: michael@0: return TRUE; michael@0: } michael@0: U_CDECL_END michael@0: michael@0: U_NAMESPACE_BEGIN michael@0: michael@0: static int32_t findInStringArray(UResourceBundle* array, const UnicodeString& id, UErrorCode &status) michael@0: { michael@0: UnicodeString copy; michael@0: const UChar *u; michael@0: int32_t len; michael@0: michael@0: int32_t start = 0; michael@0: int32_t limit = ures_getSize(array); michael@0: int32_t mid; michael@0: int32_t lastMid = INT32_MAX; michael@0: if(U_FAILURE(status) || (limit < 1)) { michael@0: return -1; michael@0: } michael@0: U_DEBUG_TZ_MSG(("fisa: Looking for %s, between %d and %d\n", U_DEBUG_TZ_STR(UnicodeString(id).getTerminatedBuffer()), start, limit)); michael@0: michael@0: for (;;) { michael@0: mid = (int32_t)((start + limit) / 2); michael@0: if (lastMid == mid) { /* Have we moved? */ michael@0: break; /* We haven't moved, and it wasn't found. */ michael@0: } michael@0: lastMid = mid; michael@0: u = ures_getStringByIndex(array, mid, &len, &status); michael@0: if (U_FAILURE(status)) { michael@0: break; michael@0: } michael@0: U_DEBUG_TZ_MSG(("tz: compare to %s, %d .. [%d] .. %d\n", U_DEBUG_TZ_STR(u), start, mid, limit)); michael@0: copy.setTo(TRUE, u, len); michael@0: int r = id.compare(copy); michael@0: if(r==0) { michael@0: U_DEBUG_TZ_MSG(("fisa: found at %d\n", mid)); michael@0: return mid; michael@0: } else if(r<0) { michael@0: limit = mid; michael@0: } else { michael@0: start = mid; michael@0: } michael@0: } michael@0: U_DEBUG_TZ_MSG(("fisa: not found\n")); michael@0: return -1; michael@0: } michael@0: michael@0: /** michael@0: * Fetch a specific zone by name. Replaces the getByKey call. michael@0: * @param top Top timezone resource michael@0: * @param id Time zone ID michael@0: * @param oldbundle Bundle for reuse (or NULL). see 'ures_open()' michael@0: * @return the zone's bundle if found, or undefined if error. Reuses oldbundle. michael@0: */ michael@0: static UResourceBundle* getZoneByName(const UResourceBundle* top, const UnicodeString& id, UResourceBundle *oldbundle, UErrorCode& status) { michael@0: // load the Rules object michael@0: UResourceBundle *tmp = ures_getByKey(top, kNAMES, NULL, &status); michael@0: michael@0: // search for the string michael@0: int32_t idx = findInStringArray(tmp, id, status); michael@0: michael@0: if((idx == -1) && U_SUCCESS(status)) { michael@0: // not found michael@0: status = U_MISSING_RESOURCE_ERROR; michael@0: //ures_close(oldbundle); michael@0: //oldbundle = NULL; michael@0: } else { michael@0: U_DEBUG_TZ_MSG(("gzbn: oldbundle= size %d, type %d, %s\n", ures_getSize(tmp), ures_getType(tmp), u_errorName(status))); michael@0: tmp = ures_getByKey(top, kZONES, tmp, &status); // get Zones object from top michael@0: U_DEBUG_TZ_MSG(("gzbn: loaded ZONES, size %d, type %d, path %s %s\n", ures_getSize(tmp), ures_getType(tmp), ures_getPath(tmp), u_errorName(status))); michael@0: oldbundle = ures_getByIndex(tmp, idx, oldbundle, &status); // get nth Zone object michael@0: U_DEBUG_TZ_MSG(("gzbn: loaded z#%d, size %d, type %d, path %s, %s\n", idx, ures_getSize(oldbundle), ures_getType(oldbundle), ures_getPath(oldbundle), u_errorName(status))); michael@0: } michael@0: ures_close(tmp); michael@0: if(U_FAILURE(status)) { michael@0: //ures_close(oldbundle); michael@0: return NULL; michael@0: } else { michael@0: return oldbundle; michael@0: } michael@0: } michael@0: michael@0: michael@0: UResourceBundle* TimeZone::loadRule(const UResourceBundle* top, const UnicodeString& ruleid, UResourceBundle* oldbundle, UErrorCode& status) { michael@0: char key[64]; michael@0: ruleid.extract(0, sizeof(key)-1, key, (int32_t)sizeof(key)-1, US_INV); michael@0: U_DEBUG_TZ_MSG(("loadRule(%s)\n", key)); michael@0: UResourceBundle *r = ures_getByKey(top, kRULES, oldbundle, &status); michael@0: U_DEBUG_TZ_MSG(("loadRule(%s) -> kRULES [%s]\n", key, u_errorName(status))); michael@0: r = ures_getByKey(r, key, r, &status); michael@0: U_DEBUG_TZ_MSG(("loadRule(%s) -> item [%s]\n", key, u_errorName(status))); michael@0: return r; michael@0: } michael@0: michael@0: /** michael@0: * Given an ID, open the appropriate resource for the given time zone. michael@0: * Dereference aliases if necessary. michael@0: * @param id zone id michael@0: * @param res resource, which must be ready for use (initialized but not open) michael@0: * @param ec input-output error code michael@0: * @return top-level resource bundle michael@0: */ michael@0: static UResourceBundle* openOlsonResource(const UnicodeString& id, michael@0: UResourceBundle& res, michael@0: UErrorCode& ec) michael@0: { michael@0: #if U_DEBUG_TZ michael@0: char buf[128]; michael@0: id.extract(0, sizeof(buf)-1, buf, sizeof(buf), ""); michael@0: #endif michael@0: UResourceBundle *top = ures_openDirect(0, kZONEINFO, &ec); michael@0: U_DEBUG_TZ_MSG(("pre: res sz=%d\n", ures_getSize(&res))); michael@0: /* &res = */ getZoneByName(top, id, &res, ec); michael@0: // Dereference if this is an alias. Docs say result should be 1 michael@0: // but it is 0 in 2.8 (?). michael@0: U_DEBUG_TZ_MSG(("Loading zone '%s' (%s, size %d) - %s\n", buf, ures_getKey((UResourceBundle*)&res), ures_getSize(&res), u_errorName(ec))); michael@0: if (ures_getType(&res) == URES_INT) { michael@0: int32_t deref = ures_getInt(&res, &ec) + 0; michael@0: U_DEBUG_TZ_MSG(("getInt: %s - type is %d\n", u_errorName(ec), ures_getType(&res))); michael@0: UResourceBundle *ares = ures_getByKey(top, kZONES, NULL, &ec); // dereference Zones section michael@0: ures_getByIndex(ares, deref, &res, &ec); michael@0: ures_close(ares); michael@0: U_DEBUG_TZ_MSG(("alias to #%d (%s) - %s\n", deref, "??", u_errorName(ec))); michael@0: } else { michael@0: U_DEBUG_TZ_MSG(("not an alias - size %d\n", ures_getSize(&res))); michael@0: } michael@0: U_DEBUG_TZ_MSG(("%s - final status is %s\n", buf, u_errorName(ec))); michael@0: return top; michael@0: } michael@0: michael@0: // ------------------------------------- michael@0: michael@0: namespace { michael@0: michael@0: void U_CALLCONV initStaticTimeZones() { michael@0: // Initialize _GMT independently of other static data; it should michael@0: // be valid even if we can't load the time zone UDataMemory. michael@0: ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONE, timeZone_cleanup); michael@0: _UNKNOWN_ZONE = new SimpleTimeZone(0, UnicodeString(TRUE, UNKNOWN_ZONE_ID, UNKNOWN_ZONE_ID_LENGTH)); michael@0: _GMT = new SimpleTimeZone(0, UnicodeString(TRUE, GMT_ID, GMT_ID_LENGTH)); michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: const TimeZone& U_EXPORT2 michael@0: TimeZone::getUnknown() michael@0: { michael@0: umtx_initOnce(gStaticZonesInitOnce, &initStaticTimeZones); michael@0: return *_UNKNOWN_ZONE; michael@0: } michael@0: michael@0: const TimeZone* U_EXPORT2 michael@0: TimeZone::getGMT(void) michael@0: { michael@0: umtx_initOnce(gStaticZonesInitOnce, &initStaticTimeZones); michael@0: return _GMT; michael@0: } michael@0: michael@0: // ***************************************************************************** michael@0: // class TimeZone michael@0: // ***************************************************************************** michael@0: michael@0: UOBJECT_DEFINE_ABSTRACT_RTTI_IMPLEMENTATION(TimeZone) michael@0: michael@0: TimeZone::TimeZone() michael@0: : UObject(), fID() michael@0: { michael@0: } michael@0: michael@0: // ------------------------------------- michael@0: michael@0: TimeZone::TimeZone(const UnicodeString &id) michael@0: : UObject(), fID(id) michael@0: { michael@0: } michael@0: michael@0: // ------------------------------------- michael@0: michael@0: TimeZone::~TimeZone() michael@0: { michael@0: } michael@0: michael@0: // ------------------------------------- michael@0: michael@0: TimeZone::TimeZone(const TimeZone &source) michael@0: : UObject(source), fID(source.fID) michael@0: { michael@0: } michael@0: michael@0: // ------------------------------------- michael@0: michael@0: TimeZone & michael@0: TimeZone::operator=(const TimeZone &right) michael@0: { michael@0: if (this != &right) fID = right.fID; michael@0: return *this; michael@0: } michael@0: michael@0: // ------------------------------------- michael@0: michael@0: UBool michael@0: TimeZone::operator==(const TimeZone& that) const michael@0: { michael@0: return typeid(*this) == typeid(that) && michael@0: fID == that.fID; michael@0: } michael@0: michael@0: // ------------------------------------- michael@0: michael@0: namespace { michael@0: TimeZone* michael@0: createSystemTimeZone(const UnicodeString& id, UErrorCode& ec) { michael@0: if (U_FAILURE(ec)) { michael@0: return NULL; michael@0: } michael@0: TimeZone* z = 0; michael@0: UResourceBundle res; michael@0: ures_initStackObject(&res); michael@0: U_DEBUG_TZ_MSG(("pre-err=%s\n", u_errorName(ec))); michael@0: UResourceBundle *top = openOlsonResource(id, res, ec); michael@0: U_DEBUG_TZ_MSG(("post-err=%s\n", u_errorName(ec))); michael@0: if (U_SUCCESS(ec)) { michael@0: z = new OlsonTimeZone(top, &res, id, ec); michael@0: if (z == NULL) { michael@0: U_DEBUG_TZ_MSG(("cstz: olson time zone failed to initialize - err %s\n", u_errorName(ec))); michael@0: } michael@0: } michael@0: ures_close(&res); michael@0: ures_close(top); michael@0: if (U_FAILURE(ec)) { michael@0: U_DEBUG_TZ_MSG(("cstz: failed to create, err %s\n", u_errorName(ec))); michael@0: delete z; michael@0: z = 0; michael@0: } michael@0: return z; michael@0: } michael@0: michael@0: /** michael@0: * Lookup the given name in our system zone table. If found, michael@0: * instantiate a new zone of that name and return it. If not michael@0: * found, return 0. michael@0: */ michael@0: TimeZone* michael@0: createSystemTimeZone(const UnicodeString& id) { michael@0: UErrorCode ec = U_ZERO_ERROR; michael@0: return createSystemTimeZone(id, ec); michael@0: } michael@0: michael@0: } michael@0: michael@0: TimeZone* U_EXPORT2 michael@0: TimeZone::createTimeZone(const UnicodeString& ID) michael@0: { michael@0: /* We first try to lookup the zone ID in our system list. If this michael@0: * fails, we try to parse it as a custom string GMT[+-]hh:mm. If michael@0: * all else fails, we return GMT, which is probably not what the michael@0: * user wants, but at least is a functioning TimeZone object. michael@0: * michael@0: * We cannot return NULL, because that would break compatibility michael@0: * with the JDK. michael@0: */ michael@0: TimeZone* result = createSystemTimeZone(ID); michael@0: michael@0: if (result == 0) { michael@0: U_DEBUG_TZ_MSG(("failed to load system time zone with id - falling to custom")); michael@0: result = createCustomTimeZone(ID); michael@0: } michael@0: if (result == 0) { michael@0: U_DEBUG_TZ_MSG(("failed to load time zone with id - falling to Etc/Unknown(GMT)")); michael@0: result = getUnknown().clone(); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: // ------------------------------------- michael@0: michael@0: /** michael@0: * Initialize DEFAULT_ZONE from the system default time zone. michael@0: * Upon return, DEFAULT_ZONE will not be NULL, unless operator new() michael@0: * returns NULL. michael@0: */ michael@0: static void U_CALLCONV initDefault() michael@0: { michael@0: ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONE, timeZone_cleanup); michael@0: michael@0: // If setDefault() has already been called we can skip getting the michael@0: // default zone information from the system. michael@0: if (DEFAULT_ZONE != NULL) { michael@0: return; michael@0: } michael@0: michael@0: // We access system timezone data through TPlatformUtilities, michael@0: // including tzset(), timezone, and tzname[]. michael@0: int32_t rawOffset = 0; michael@0: const char *hostID; michael@0: michael@0: // First, try to create a system timezone, based michael@0: // on the string ID in tzname[0]. michael@0: michael@0: // NOTE: this code is safely single threaded, being only michael@0: // run via umtx_initOnce(). michael@0: // michael@0: // Some of the locale/timezone OS functions may not be thread safe, michael@0: // michael@0: // The operating system might actually use ICU to implement timezones. michael@0: // So we may have ICU calling ICU here, like on AIX. michael@0: // There shouldn't be a problem with this; initOnce does not hold a mutex michael@0: // while the init function is being run. michael@0: michael@0: uprv_tzset(); // Initialize tz... system data michael@0: michael@0: // Get the timezone ID from the host. This function should do michael@0: // any required host-specific remapping; e.g., on Windows this michael@0: // function maps the Date and Time control panel setting to an michael@0: // ICU timezone ID. michael@0: hostID = uprv_tzname(0); michael@0: michael@0: // Invert sign because UNIX semantics are backwards michael@0: rawOffset = uprv_timezone() * -U_MILLIS_PER_SECOND; michael@0: michael@0: TimeZone* default_zone = NULL; michael@0: michael@0: /* Make sure that the string is NULL terminated to prevent BoundsChecker/Purify warnings. */ michael@0: UnicodeString hostStrID(hostID, -1, US_INV); michael@0: hostStrID.append((UChar)0); michael@0: hostStrID.truncate(hostStrID.length()-1); michael@0: default_zone = createSystemTimeZone(hostStrID); michael@0: michael@0: #if U_PLATFORM_USES_ONLY_WIN32_API michael@0: // hostID points to a heap-allocated location on Windows. michael@0: uprv_free(const_cast(hostID)); michael@0: #endif michael@0: michael@0: int32_t hostIDLen = hostStrID.length(); michael@0: if (default_zone != NULL && rawOffset != default_zone->getRawOffset() michael@0: && (3 <= hostIDLen && hostIDLen <= 4)) michael@0: { michael@0: // Uh oh. This probably wasn't a good id. michael@0: // It was probably an ambiguous abbreviation michael@0: delete default_zone; michael@0: default_zone = NULL; michael@0: } michael@0: michael@0: // Construct a fixed standard zone with the host's ID michael@0: // and raw offset. michael@0: if (default_zone == NULL) { michael@0: default_zone = new SimpleTimeZone(rawOffset, hostStrID); michael@0: } michael@0: michael@0: // If we _still_ don't have a time zone, use GMT. michael@0: if (default_zone == NULL) { michael@0: const TimeZone* temptz = TimeZone::getGMT(); michael@0: // If we can't use GMT, get out. michael@0: if (temptz == NULL) { michael@0: return; michael@0: } michael@0: default_zone = temptz->clone(); michael@0: } michael@0: michael@0: // The only way for DEFAULT_ZONE to be non-null at this point is if the user michael@0: // made a thread-unsafe call to setDefault() or adoptDefault() in another michael@0: // thread while this thread was doing something that required getting the default. michael@0: U_ASSERT(DEFAULT_ZONE == NULL); michael@0: michael@0: DEFAULT_ZONE = default_zone; michael@0: } michael@0: michael@0: // ------------------------------------- michael@0: michael@0: TimeZone* U_EXPORT2 michael@0: TimeZone::createDefault() michael@0: { michael@0: umtx_initOnce(gDefaultZoneInitOnce, initDefault); michael@0: return (DEFAULT_ZONE != NULL) ? DEFAULT_ZONE->clone() : NULL; michael@0: } michael@0: michael@0: // ------------------------------------- michael@0: michael@0: void U_EXPORT2 michael@0: TimeZone::adoptDefault(TimeZone* zone) michael@0: { michael@0: if (zone != NULL) michael@0: { michael@0: TimeZone *old = DEFAULT_ZONE; michael@0: DEFAULT_ZONE = zone; michael@0: delete old; michael@0: ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONE, timeZone_cleanup); michael@0: } michael@0: } michael@0: // ------------------------------------- michael@0: michael@0: void U_EXPORT2 michael@0: TimeZone::setDefault(const TimeZone& zone) michael@0: { michael@0: adoptDefault(zone.clone()); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: michael@0: static void U_CALLCONV initMap(USystemTimeZoneType type, UErrorCode& ec) { michael@0: ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONE, timeZone_cleanup); michael@0: michael@0: UResourceBundle *res = ures_openDirect(0, kZONEINFO, &ec); michael@0: res = ures_getByKey(res, kNAMES, res, &ec); // dereference Zones section michael@0: if (U_SUCCESS(ec)) { michael@0: int32_t size = ures_getSize(res); michael@0: int32_t *m = (int32_t *)uprv_malloc(size * sizeof(int32_t)); michael@0: if (m == NULL) { michael@0: ec = U_MEMORY_ALLOCATION_ERROR; michael@0: } else { michael@0: int32_t numEntries = 0; michael@0: for (int32_t i = 0; i < size; i++) { michael@0: UnicodeString id = ures_getUnicodeStringByIndex(res, i, &ec); michael@0: if (U_FAILURE(ec)) { michael@0: break; michael@0: } michael@0: if (0 == id.compare(UNKNOWN_ZONE_ID, UNKNOWN_ZONE_ID_LENGTH)) { michael@0: // exclude Etc/Unknown michael@0: continue; michael@0: } michael@0: if (type == UCAL_ZONE_TYPE_CANONICAL || type == UCAL_ZONE_TYPE_CANONICAL_LOCATION) { michael@0: UnicodeString canonicalID; michael@0: ZoneMeta::getCanonicalCLDRID(id, canonicalID, ec); michael@0: if (U_FAILURE(ec)) { michael@0: break; michael@0: } michael@0: if (canonicalID != id) { michael@0: // exclude aliases michael@0: continue; michael@0: } michael@0: } michael@0: if (type == UCAL_ZONE_TYPE_CANONICAL_LOCATION) { michael@0: const UChar *region = TimeZone::getRegion(id, ec); michael@0: if (U_FAILURE(ec)) { michael@0: break; michael@0: } michael@0: if (u_strcmp(region, WORLD) == 0) { michael@0: // exclude non-location ("001") michael@0: continue; michael@0: } michael@0: } michael@0: m[numEntries++] = i; michael@0: } michael@0: if (U_SUCCESS(ec)) { michael@0: int32_t *tmp = m; michael@0: m = (int32_t *)uprv_realloc(tmp, numEntries * sizeof(int32_t)); michael@0: if (m == NULL) { michael@0: // realloc failed.. use the original one even it has unused michael@0: // area at the end michael@0: m = tmp; michael@0: } michael@0: michael@0: switch(type) { michael@0: case UCAL_ZONE_TYPE_ANY: michael@0: U_ASSERT(MAP_SYSTEM_ZONES == NULL); michael@0: MAP_SYSTEM_ZONES = m; michael@0: LEN_SYSTEM_ZONES = numEntries; michael@0: break; michael@0: case UCAL_ZONE_TYPE_CANONICAL: michael@0: U_ASSERT(MAP_CANONICAL_SYSTEM_ZONES == NULL); michael@0: MAP_CANONICAL_SYSTEM_ZONES = m; michael@0: LEN_CANONICAL_SYSTEM_ZONES = numEntries; michael@0: break; michael@0: case UCAL_ZONE_TYPE_CANONICAL_LOCATION: michael@0: U_ASSERT(MAP_CANONICAL_SYSTEM_LOCATION_ZONES == NULL); michael@0: MAP_CANONICAL_SYSTEM_LOCATION_ZONES = m; michael@0: LEN_CANONICAL_SYSTEM_LOCATION_ZONES = numEntries; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: ures_close(res); michael@0: } michael@0: michael@0: michael@0: /** michael@0: * This is the default implementation for subclasses that do not michael@0: * override this method. This implementation calls through to the michael@0: * 8-argument getOffset() method after suitable computations, and michael@0: * correctly adjusts GMT millis to local millis when necessary. michael@0: */ michael@0: void TimeZone::getOffset(UDate date, UBool local, int32_t& rawOffset, michael@0: int32_t& dstOffset, UErrorCode& ec) const { michael@0: if (U_FAILURE(ec)) { michael@0: return; michael@0: } michael@0: michael@0: rawOffset = getRawOffset(); michael@0: if (!local) { michael@0: date += rawOffset; // now in local standard millis michael@0: } michael@0: michael@0: // When local == TRUE, date might not be in local standard michael@0: // millis. getOffset taking 7 parameters used here assume michael@0: // the given time in day is local standard time. michael@0: // At STD->DST transition, there is a range of time which michael@0: // does not exist. When 'date' is in this time range michael@0: // (and local == TRUE), this method interprets the specified michael@0: // local time as DST. At DST->STD transition, there is a michael@0: // range of time which occurs twice. In this case, this michael@0: // method interprets the specified local time as STD. michael@0: // To support the behavior above, we need to call getOffset michael@0: // (with 7 args) twice when local == true and DST is michael@0: // detected in the initial call. michael@0: for (int32_t pass=0; ; ++pass) { michael@0: int32_t year, month, dom, dow; michael@0: double day = uprv_floor(date / U_MILLIS_PER_DAY); michael@0: int32_t millis = (int32_t) (date - day * U_MILLIS_PER_DAY); michael@0: michael@0: Grego::dayToFields(day, year, month, dom, dow); michael@0: michael@0: dstOffset = getOffset(GregorianCalendar::AD, year, month, dom, michael@0: (uint8_t) dow, millis, michael@0: Grego::monthLength(year, month), michael@0: ec) - rawOffset; michael@0: michael@0: // Recompute if local==TRUE, dstOffset!=0. michael@0: if (pass!=0 || !local || dstOffset == 0) { michael@0: break; michael@0: } michael@0: // adjust to local standard millis michael@0: date -= dstOffset; michael@0: } michael@0: } michael@0: michael@0: // ------------------------------------- michael@0: michael@0: // New available IDs API as of ICU 2.4. Uses StringEnumeration API. michael@0: michael@0: class TZEnumeration : public StringEnumeration { michael@0: private: michael@0: michael@0: // Map into to zones. Our results are zone[map[i]] for michael@0: // i=0..len-1, where zone[i] is the i-th Olson zone. If map==NULL michael@0: // then our results are zone[i] for i=0..len-1. Len will be zero michael@0: // if the zone data could not be loaded. michael@0: int32_t* map; michael@0: int32_t* localMap; michael@0: int32_t len; michael@0: int32_t pos; michael@0: michael@0: TZEnumeration(int32_t* mapData, int32_t mapLen, UBool adoptMapData) : pos(0) { michael@0: map = mapData; michael@0: localMap = adoptMapData ? mapData : NULL; michael@0: len = mapLen; michael@0: } michael@0: michael@0: UBool getID(int32_t i) { michael@0: UErrorCode ec = U_ZERO_ERROR; michael@0: int32_t idLen = 0; michael@0: const UChar* id = NULL; michael@0: UResourceBundle *top = ures_openDirect(0, kZONEINFO, &ec); michael@0: top = ures_getByKey(top, kNAMES, top, &ec); // dereference Zones section michael@0: id = ures_getStringByIndex(top, i, &idLen, &ec); michael@0: if(U_FAILURE(ec)) { michael@0: unistr.truncate(0); michael@0: } michael@0: else { michael@0: unistr.fastCopyFrom(UnicodeString(TRUE, id, idLen)); michael@0: } michael@0: ures_close(top); michael@0: return U_SUCCESS(ec); michael@0: } michael@0: michael@0: static int32_t* getMap(USystemTimeZoneType type, int32_t& len, UErrorCode& ec) { michael@0: len = 0; michael@0: if (U_FAILURE(ec)) { michael@0: return NULL; michael@0: } michael@0: int32_t* m = NULL; michael@0: switch (type) { michael@0: case UCAL_ZONE_TYPE_ANY: michael@0: umtx_initOnce(gSystemZonesInitOnce, &initMap, type, ec); michael@0: m = MAP_SYSTEM_ZONES; michael@0: len = LEN_SYSTEM_ZONES; michael@0: break; michael@0: case UCAL_ZONE_TYPE_CANONICAL: michael@0: umtx_initOnce(gCanonicalZonesInitOnce, &initMap, type, ec); michael@0: m = MAP_CANONICAL_SYSTEM_ZONES; michael@0: len = LEN_CANONICAL_SYSTEM_ZONES; michael@0: break; michael@0: case UCAL_ZONE_TYPE_CANONICAL_LOCATION: michael@0: umtx_initOnce(gCanonicalLocationZonesInitOnce, &initMap, type, ec); michael@0: m = MAP_CANONICAL_SYSTEM_LOCATION_ZONES; michael@0: len = LEN_CANONICAL_SYSTEM_LOCATION_ZONES; michael@0: break; michael@0: default: michael@0: ec = U_ILLEGAL_ARGUMENT_ERROR; michael@0: m = NULL; michael@0: len = 0; michael@0: break; michael@0: } michael@0: return m; michael@0: } michael@0: michael@0: public: michael@0: michael@0: #define DEFAULT_FILTERED_MAP_SIZE 8 michael@0: #define MAP_INCREMENT_SIZE 8 michael@0: michael@0: static TZEnumeration* create(USystemTimeZoneType type, const char* region, const int32_t* rawOffset, UErrorCode& ec) { michael@0: if (U_FAILURE(ec)) { michael@0: return NULL; michael@0: } michael@0: michael@0: int32_t baseLen; michael@0: int32_t *baseMap = getMap(type, baseLen, ec); michael@0: michael@0: if (U_FAILURE(ec)) { michael@0: return NULL; michael@0: } michael@0: michael@0: // If any additional conditions are available, michael@0: // create instance local map filtered by the conditions. michael@0: michael@0: int32_t *filteredMap = NULL; michael@0: int32_t numEntries = 0; michael@0: michael@0: if (region != NULL || rawOffset != NULL) { michael@0: int32_t filteredMapSize = DEFAULT_FILTERED_MAP_SIZE; michael@0: filteredMap = (int32_t *)uprv_malloc(filteredMapSize * sizeof(int32_t)); michael@0: if (filteredMap == NULL) { michael@0: ec = U_MEMORY_ALLOCATION_ERROR; michael@0: return NULL; michael@0: } michael@0: michael@0: // Walk through the base map michael@0: UResourceBundle *res = ures_openDirect(0, kZONEINFO, &ec); michael@0: res = ures_getByKey(res, kNAMES, res, &ec); // dereference Zones section michael@0: for (int32_t i = 0; i < baseLen; i++) { michael@0: int32_t zidx = baseMap[i]; michael@0: UnicodeString id = ures_getUnicodeStringByIndex(res, zidx, &ec); michael@0: if (U_FAILURE(ec)) { michael@0: break; michael@0: } michael@0: if (region != NULL) { michael@0: // Filter by region michael@0: char tzregion[4]; // max 3 letters + null term michael@0: TimeZone::getRegion(id, tzregion, sizeof(tzregion), ec); michael@0: if (U_FAILURE(ec)) { michael@0: break; michael@0: } michael@0: if (uprv_stricmp(tzregion, region) != 0) { michael@0: // region does not match michael@0: continue; michael@0: } michael@0: } michael@0: if (rawOffset != NULL) { michael@0: // Filter by raw offset michael@0: // Note: This is VERY inefficient michael@0: TimeZone *z = createSystemTimeZone(id, ec); michael@0: if (U_FAILURE(ec)) { michael@0: break; michael@0: } michael@0: int32_t tzoffset = z->getRawOffset(); michael@0: delete z; michael@0: michael@0: if (tzoffset != *rawOffset) { michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: if (filteredMapSize <= numEntries) { michael@0: filteredMapSize += MAP_INCREMENT_SIZE; michael@0: int32_t *tmp = (int32_t *)uprv_realloc(filteredMap, filteredMapSize * sizeof(int32_t)); michael@0: if (tmp == NULL) { michael@0: ec = U_MEMORY_ALLOCATION_ERROR; michael@0: break; michael@0: } else { michael@0: filteredMap = tmp; michael@0: } michael@0: } michael@0: michael@0: filteredMap[numEntries++] = zidx; michael@0: } michael@0: michael@0: if (U_FAILURE(ec)) { michael@0: uprv_free(filteredMap); michael@0: filteredMap = NULL; michael@0: } michael@0: michael@0: ures_close(res); michael@0: } michael@0: michael@0: TZEnumeration *result = NULL; michael@0: if (U_SUCCESS(ec)) { michael@0: // Finally, create a new enumeration instance michael@0: if (filteredMap == NULL) { michael@0: result = new TZEnumeration(baseMap, baseLen, FALSE); michael@0: } else { michael@0: result = new TZEnumeration(filteredMap, numEntries, TRUE); michael@0: filteredMap = NULL; michael@0: } michael@0: if (result == NULL) { michael@0: ec = U_MEMORY_ALLOCATION_ERROR; michael@0: } michael@0: } michael@0: michael@0: if (filteredMap != NULL) { michael@0: uprv_free(filteredMap); michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: TZEnumeration(const TZEnumeration &other) : StringEnumeration(), map(NULL), localMap(NULL), len(0), pos(0) { michael@0: if (other.localMap != NULL) { michael@0: localMap = (int32_t *)uprv_malloc(other.len * sizeof(int32_t)); michael@0: if (localMap != NULL) { michael@0: len = other.len; michael@0: uprv_memcpy(localMap, other.localMap, len * sizeof(int32_t)); michael@0: pos = other.pos; michael@0: map = localMap; michael@0: } else { michael@0: len = 0; michael@0: pos = 0; michael@0: map = NULL; michael@0: } michael@0: } else { michael@0: map = other.map; michael@0: localMap = NULL; michael@0: len = other.len; michael@0: pos = other.pos; michael@0: } michael@0: } michael@0: michael@0: virtual ~TZEnumeration(); michael@0: michael@0: virtual StringEnumeration *clone() const { michael@0: return new TZEnumeration(*this); michael@0: } michael@0: michael@0: virtual int32_t count(UErrorCode& status) const { michael@0: return U_FAILURE(status) ? 0 : len; michael@0: } michael@0: michael@0: virtual const UnicodeString* snext(UErrorCode& status) { michael@0: if (U_SUCCESS(status) && map != NULL && pos < len) { michael@0: getID(map[pos]); michael@0: ++pos; michael@0: return &unistr; michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: virtual void reset(UErrorCode& /*status*/) { michael@0: pos = 0; michael@0: } michael@0: michael@0: public: michael@0: static UClassID U_EXPORT2 getStaticClassID(void); michael@0: virtual UClassID getDynamicClassID(void) const; michael@0: }; michael@0: michael@0: TZEnumeration::~TZEnumeration() { michael@0: if (localMap != NULL) { michael@0: uprv_free(localMap); michael@0: } michael@0: } michael@0: michael@0: UOBJECT_DEFINE_RTTI_IMPLEMENTATION(TZEnumeration) michael@0: michael@0: StringEnumeration* U_EXPORT2 michael@0: TimeZone::createTimeZoneIDEnumeration( michael@0: USystemTimeZoneType zoneType, michael@0: const char* region, michael@0: const int32_t* rawOffset, michael@0: UErrorCode& ec) { michael@0: return TZEnumeration::create(zoneType, region, rawOffset, ec); michael@0: } michael@0: michael@0: StringEnumeration* U_EXPORT2 michael@0: TimeZone::createEnumeration() { michael@0: UErrorCode ec = U_ZERO_ERROR; michael@0: return TZEnumeration::create(UCAL_ZONE_TYPE_ANY, NULL, NULL, ec); michael@0: } michael@0: michael@0: StringEnumeration* U_EXPORT2 michael@0: TimeZone::createEnumeration(int32_t rawOffset) { michael@0: UErrorCode ec = U_ZERO_ERROR; michael@0: return TZEnumeration::create(UCAL_ZONE_TYPE_ANY, NULL, &rawOffset, ec); michael@0: } michael@0: michael@0: StringEnumeration* U_EXPORT2 michael@0: TimeZone::createEnumeration(const char* country) { michael@0: UErrorCode ec = U_ZERO_ERROR; michael@0: return TZEnumeration::create(UCAL_ZONE_TYPE_ANY, country, NULL, ec); michael@0: } michael@0: michael@0: // --------------------------------------- michael@0: michael@0: int32_t U_EXPORT2 michael@0: TimeZone::countEquivalentIDs(const UnicodeString& id) { michael@0: int32_t result = 0; michael@0: UErrorCode ec = U_ZERO_ERROR; michael@0: UResourceBundle res; michael@0: ures_initStackObject(&res); michael@0: U_DEBUG_TZ_MSG(("countEquivalentIDs..\n")); michael@0: UResourceBundle *top = openOlsonResource(id, res, ec); michael@0: if (U_SUCCESS(ec)) { michael@0: UResourceBundle r; michael@0: ures_initStackObject(&r); michael@0: ures_getByKey(&res, kLINKS, &r, &ec); michael@0: ures_getIntVector(&r, &result, &ec); michael@0: ures_close(&r); michael@0: } michael@0: ures_close(&res); michael@0: ures_close(top); michael@0: return result; michael@0: } michael@0: michael@0: // --------------------------------------- michael@0: michael@0: const UnicodeString U_EXPORT2 michael@0: TimeZone::getEquivalentID(const UnicodeString& id, int32_t index) { michael@0: U_DEBUG_TZ_MSG(("gEI(%d)\n", index)); michael@0: UnicodeString result; michael@0: UErrorCode ec = U_ZERO_ERROR; michael@0: UResourceBundle res; michael@0: ures_initStackObject(&res); michael@0: UResourceBundle *top = openOlsonResource(id, res, ec); michael@0: int32_t zone = -1; michael@0: if (U_SUCCESS(ec)) { michael@0: UResourceBundle r; michael@0: ures_initStackObject(&r); michael@0: int32_t size; michael@0: ures_getByKey(&res, kLINKS, &r, &ec); michael@0: const int32_t* v = ures_getIntVector(&r, &size, &ec); michael@0: if (U_SUCCESS(ec)) { michael@0: if (index >= 0 && index < size) { michael@0: zone = v[index]; michael@0: } michael@0: } michael@0: ures_close(&r); michael@0: } michael@0: ures_close(&res); michael@0: if (zone >= 0) { michael@0: UResourceBundle *ares = ures_getByKey(top, kNAMES, NULL, &ec); // dereference Zones section michael@0: if (U_SUCCESS(ec)) { michael@0: int32_t idLen = 0; michael@0: const UChar* id = ures_getStringByIndex(ares, zone, &idLen, &ec); michael@0: result.fastCopyFrom(UnicodeString(TRUE, id, idLen)); michael@0: U_DEBUG_TZ_MSG(("gei(%d) -> %d, len%d, %s\n", index, zone, result.length(), u_errorName(ec))); michael@0: } michael@0: ures_close(ares); michael@0: } michael@0: ures_close(top); michael@0: #if defined(U_DEBUG_TZ) michael@0: if(result.length() ==0) { michael@0: U_DEBUG_TZ_MSG(("equiv [__, #%d] -> 0 (%s)\n", index, u_errorName(ec))); michael@0: } michael@0: #endif michael@0: return result; michael@0: } michael@0: michael@0: // --------------------------------------- michael@0: michael@0: // These methods are used by ZoneMeta class only. michael@0: michael@0: const UChar* michael@0: TimeZone::findID(const UnicodeString& id) { michael@0: const UChar *result = NULL; michael@0: UErrorCode ec = U_ZERO_ERROR; michael@0: UResourceBundle *rb = ures_openDirect(NULL, kZONEINFO, &ec); michael@0: michael@0: // resolve zone index by name michael@0: UResourceBundle *names = ures_getByKey(rb, kNAMES, NULL, &ec); michael@0: int32_t idx = findInStringArray(names, id, ec); michael@0: result = ures_getStringByIndex(names, idx, NULL, &ec); michael@0: if (U_FAILURE(ec)) { michael@0: result = NULL; michael@0: } michael@0: ures_close(names); michael@0: ures_close(rb); michael@0: return result; michael@0: } michael@0: michael@0: michael@0: const UChar* michael@0: TimeZone::dereferOlsonLink(const UnicodeString& id) { michael@0: const UChar *result = NULL; michael@0: UErrorCode ec = U_ZERO_ERROR; michael@0: UResourceBundle *rb = ures_openDirect(NULL, kZONEINFO, &ec); michael@0: michael@0: // resolve zone index by name michael@0: UResourceBundle *names = ures_getByKey(rb, kNAMES, NULL, &ec); michael@0: int32_t idx = findInStringArray(names, id, ec); michael@0: result = ures_getStringByIndex(names, idx, NULL, &ec); michael@0: michael@0: // open the zone bundle by index michael@0: ures_getByKey(rb, kZONES, rb, &ec); michael@0: ures_getByIndex(rb, idx, rb, &ec); michael@0: michael@0: if (U_SUCCESS(ec)) { michael@0: if (ures_getType(rb) == URES_INT) { michael@0: // this is a link - dereference the link michael@0: int32_t deref = ures_getInt(rb, &ec); michael@0: const UChar* tmp = ures_getStringByIndex(names, deref, NULL, &ec); michael@0: if (U_SUCCESS(ec)) { michael@0: result = tmp; michael@0: } michael@0: } michael@0: } michael@0: michael@0: ures_close(names); michael@0: ures_close(rb); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: const UChar* michael@0: TimeZone::getRegion(const UnicodeString& id) { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: return getRegion(id, status); michael@0: } michael@0: michael@0: const UChar* michael@0: TimeZone::getRegion(const UnicodeString& id, UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: const UChar *result = NULL; michael@0: UResourceBundle *rb = ures_openDirect(NULL, kZONEINFO, &status); michael@0: michael@0: // resolve zone index by name michael@0: UResourceBundle *res = ures_getByKey(rb, kNAMES, NULL, &status); michael@0: int32_t idx = findInStringArray(res, id, status); michael@0: michael@0: // get region mapping michael@0: ures_getByKey(rb, kREGIONS, res, &status); michael@0: const UChar *tmp = ures_getStringByIndex(res, idx, NULL, &status); michael@0: if (U_SUCCESS(status)) { michael@0: result = tmp; michael@0: } michael@0: michael@0: ures_close(res); michael@0: ures_close(rb); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: michael@0: // --------------------------------------- michael@0: int32_t michael@0: TimeZone::getRegion(const UnicodeString& id, char *region, int32_t capacity, UErrorCode& status) michael@0: { michael@0: int32_t resultLen = 0; michael@0: *region = 0; michael@0: if (U_FAILURE(status)) { michael@0: return 0; michael@0: } michael@0: michael@0: const UChar *uregion = NULL; michael@0: // "Etc/Unknown" is not a system zone ID, michael@0: // but in the zone data michael@0: if (id.compare(UNKNOWN_ZONE_ID, UNKNOWN_ZONE_ID_LENGTH) != 0) { michael@0: uregion = getRegion(id); michael@0: } michael@0: if (uregion == NULL) { michael@0: status = U_ILLEGAL_ARGUMENT_ERROR; michael@0: return 0; michael@0: } michael@0: resultLen = u_strlen(uregion); michael@0: // A region code is represented by invariant characters michael@0: u_UCharsToChars(uregion, region, uprv_min(resultLen, capacity)); michael@0: michael@0: if (capacity < resultLen) { michael@0: status = U_BUFFER_OVERFLOW_ERROR; michael@0: return resultLen; michael@0: } michael@0: michael@0: return u_terminateChars(region, capacity, resultLen, &status); michael@0: } michael@0: michael@0: // --------------------------------------- michael@0: michael@0: michael@0: UnicodeString& michael@0: TimeZone::getDisplayName(UnicodeString& result) const michael@0: { michael@0: return getDisplayName(FALSE,LONG,Locale::getDefault(), result); michael@0: } michael@0: michael@0: UnicodeString& michael@0: TimeZone::getDisplayName(const Locale& locale, UnicodeString& result) const michael@0: { michael@0: return getDisplayName(FALSE, LONG, locale, result); michael@0: } michael@0: michael@0: UnicodeString& michael@0: TimeZone::getDisplayName(UBool daylight, EDisplayType style, UnicodeString& result) const michael@0: { michael@0: return getDisplayName(daylight,style, Locale::getDefault(), result); michael@0: } michael@0: //-------------------------------------- michael@0: int32_t michael@0: TimeZone::getDSTSavings()const { michael@0: if (useDaylightTime()) { michael@0: return 3600000; michael@0: } michael@0: return 0; michael@0: } michael@0: //--------------------------------------- michael@0: UnicodeString& michael@0: TimeZone::getDisplayName(UBool daylight, EDisplayType style, const Locale& locale, UnicodeString& result) const michael@0: { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: UDate date = Calendar::getNow(); michael@0: UTimeZoneFormatTimeType timeType; michael@0: int32_t offset; michael@0: michael@0: if (style == GENERIC_LOCATION || style == LONG_GENERIC || style == SHORT_GENERIC) { michael@0: LocalPointer tzfmt(TimeZoneFormat::createInstance(locale, status)); michael@0: if (U_FAILURE(status)) { michael@0: result.remove(); michael@0: return result; michael@0: } michael@0: // Generic format michael@0: switch (style) { michael@0: case GENERIC_LOCATION: michael@0: tzfmt->format(UTZFMT_STYLE_GENERIC_LOCATION, *this, date, result, &timeType); michael@0: break; michael@0: case LONG_GENERIC: michael@0: tzfmt->format(UTZFMT_STYLE_GENERIC_LONG, *this, date, result, &timeType); michael@0: break; michael@0: case SHORT_GENERIC: michael@0: tzfmt->format(UTZFMT_STYLE_GENERIC_SHORT, *this, date, result, &timeType); michael@0: break; michael@0: default: michael@0: U_ASSERT(FALSE); michael@0: } michael@0: // Generic format many use Localized GMT as the final fallback. michael@0: // When Localized GMT format is used, the result might not be michael@0: // appropriate for the requested daylight value. michael@0: if ((daylight && timeType == UTZFMT_TIME_TYPE_STANDARD) || (!daylight && timeType == UTZFMT_TIME_TYPE_DAYLIGHT)) { michael@0: offset = daylight ? getRawOffset() + getDSTSavings() : getRawOffset(); michael@0: if (style == SHORT_GENERIC) { michael@0: tzfmt->formatOffsetShortLocalizedGMT(offset, result, status); michael@0: } else { michael@0: tzfmt->formatOffsetLocalizedGMT(offset, result, status); michael@0: } michael@0: } michael@0: } else if (style == LONG_GMT || style == SHORT_GMT) { michael@0: LocalPointer tzfmt(TimeZoneFormat::createInstance(locale, status)); michael@0: if (U_FAILURE(status)) { michael@0: result.remove(); michael@0: return result; michael@0: } michael@0: offset = daylight && useDaylightTime() ? getRawOffset() + getDSTSavings() : getRawOffset(); michael@0: switch (style) { michael@0: case LONG_GMT: michael@0: tzfmt->formatOffsetLocalizedGMT(offset, result, status); michael@0: break; michael@0: case SHORT_GMT: michael@0: tzfmt->formatOffsetISO8601Basic(offset, FALSE, FALSE, FALSE, result, status); michael@0: break; michael@0: default: michael@0: U_ASSERT(FALSE); michael@0: } michael@0: michael@0: } else { michael@0: U_ASSERT(style == LONG || style == SHORT || style == SHORT_COMMONLY_USED); michael@0: UTimeZoneNameType nameType = UTZNM_UNKNOWN; michael@0: switch (style) { michael@0: case LONG: michael@0: nameType = daylight ? UTZNM_LONG_DAYLIGHT : UTZNM_LONG_STANDARD; michael@0: break; michael@0: case SHORT: michael@0: case SHORT_COMMONLY_USED: michael@0: nameType = daylight ? UTZNM_SHORT_DAYLIGHT : UTZNM_SHORT_STANDARD; michael@0: break; michael@0: default: michael@0: U_ASSERT(FALSE); michael@0: } michael@0: LocalPointer tznames(TimeZoneNames::createInstance(locale, status)); michael@0: if (U_FAILURE(status)) { michael@0: result.remove(); michael@0: return result; michael@0: } michael@0: UnicodeString canonicalID(ZoneMeta::getCanonicalCLDRID(*this)); michael@0: tznames->getDisplayName(canonicalID, nameType, date, result); michael@0: if (result.isEmpty()) { michael@0: // Fallback to localized GMT michael@0: LocalPointer tzfmt(TimeZoneFormat::createInstance(locale, status)); michael@0: offset = daylight && useDaylightTime() ? getRawOffset() + getDSTSavings() : getRawOffset(); michael@0: if (style == LONG) { michael@0: tzfmt->formatOffsetLocalizedGMT(offset, result, status); michael@0: } else { michael@0: tzfmt->formatOffsetShortLocalizedGMT(offset, result, status); michael@0: } michael@0: } michael@0: } michael@0: if (U_FAILURE(status)) { michael@0: result.remove(); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: /** michael@0: * Parse a custom time zone identifier and return a corresponding zone. michael@0: * @param id a string of the form GMT[+-]hh:mm, GMT[+-]hhmm, or michael@0: * GMT[+-]hh. michael@0: * @return a newly created SimpleTimeZone with the given offset and michael@0: * no Daylight Savings Time, or null if the id cannot be parsed. michael@0: */ michael@0: TimeZone* michael@0: TimeZone::createCustomTimeZone(const UnicodeString& id) michael@0: { michael@0: int32_t sign, hour, min, sec; michael@0: if (parseCustomID(id, sign, hour, min, sec)) { michael@0: UnicodeString customID; michael@0: formatCustomID(hour, min, sec, (sign < 0), customID); michael@0: int32_t offset = sign * ((hour * 60 + min) * 60 + sec) * 1000; michael@0: return new SimpleTimeZone(offset, customID); michael@0: } michael@0: return NULL; michael@0: } michael@0: michael@0: UnicodeString& michael@0: TimeZone::getCustomID(const UnicodeString& id, UnicodeString& normalized, UErrorCode& status) { michael@0: normalized.remove(); michael@0: if (U_FAILURE(status)) { michael@0: return normalized; michael@0: } michael@0: int32_t sign, hour, min, sec; michael@0: if (parseCustomID(id, sign, hour, min, sec)) { michael@0: formatCustomID(hour, min, sec, (sign < 0), normalized); michael@0: } michael@0: return normalized; michael@0: } michael@0: michael@0: UBool michael@0: TimeZone::parseCustomID(const UnicodeString& id, int32_t& sign, michael@0: int32_t& hour, int32_t& min, int32_t& sec) { michael@0: static const int32_t kParseFailed = -99999; michael@0: michael@0: NumberFormat* numberFormat = 0; michael@0: UnicodeString idUppercase = id; michael@0: idUppercase.toUpper(""); michael@0: michael@0: if (id.length() > GMT_ID_LENGTH && michael@0: idUppercase.startsWith(GMT_ID, GMT_ID_LENGTH)) michael@0: { michael@0: ParsePosition pos(GMT_ID_LENGTH); michael@0: sign = 1; michael@0: hour = 0; michael@0: min = 0; michael@0: sec = 0; michael@0: michael@0: if (id[pos.getIndex()] == MINUS /*'-'*/) { michael@0: sign = -1; michael@0: } else if (id[pos.getIndex()] != PLUS /*'+'*/) { michael@0: return FALSE; michael@0: } michael@0: pos.setIndex(pos.getIndex() + 1); michael@0: michael@0: UErrorCode success = U_ZERO_ERROR; michael@0: numberFormat = NumberFormat::createInstance(success); michael@0: if(U_FAILURE(success)){ michael@0: return FALSE; michael@0: } michael@0: numberFormat->setParseIntegerOnly(TRUE); michael@0: //numberFormat->setLenient(TRUE); // TODO: May need to set this, depends on latest timezone parsing michael@0: michael@0: // Look for either hh:mm, hhmm, or hh michael@0: int32_t start = pos.getIndex(); michael@0: Formattable n(kParseFailed); michael@0: numberFormat->parse(id, n, pos); michael@0: if (pos.getIndex() == start) { michael@0: delete numberFormat; michael@0: return FALSE; michael@0: } michael@0: hour = n.getLong(); michael@0: michael@0: if (pos.getIndex() < id.length()) { michael@0: if (pos.getIndex() - start > 2 michael@0: || id[pos.getIndex()] != COLON) { michael@0: delete numberFormat; michael@0: return FALSE; michael@0: } michael@0: // hh:mm michael@0: pos.setIndex(pos.getIndex() + 1); michael@0: int32_t oldPos = pos.getIndex(); michael@0: n.setLong(kParseFailed); michael@0: numberFormat->parse(id, n, pos); michael@0: if ((pos.getIndex() - oldPos) != 2) { michael@0: // must be 2 digits michael@0: delete numberFormat; michael@0: return FALSE; michael@0: } michael@0: min = n.getLong(); michael@0: if (pos.getIndex() < id.length()) { michael@0: if (id[pos.getIndex()] != COLON) { michael@0: delete numberFormat; michael@0: return FALSE; michael@0: } michael@0: // [:ss] michael@0: pos.setIndex(pos.getIndex() + 1); michael@0: oldPos = pos.getIndex(); michael@0: n.setLong(kParseFailed); michael@0: numberFormat->parse(id, n, pos); michael@0: if (pos.getIndex() != id.length() michael@0: || (pos.getIndex() - oldPos) != 2) { michael@0: delete numberFormat; michael@0: return FALSE; michael@0: } michael@0: sec = n.getLong(); michael@0: } michael@0: } else { michael@0: // Supported formats are below - michael@0: // michael@0: // HHmmss michael@0: // Hmmss michael@0: // HHmm michael@0: // Hmm michael@0: // HH michael@0: // H michael@0: michael@0: int32_t length = pos.getIndex() - start; michael@0: if (length <= 0 || 6 < length) { michael@0: // invalid length michael@0: delete numberFormat; michael@0: return FALSE; michael@0: } michael@0: switch (length) { michael@0: case 1: michael@0: case 2: michael@0: // already set to hour michael@0: break; michael@0: case 3: michael@0: case 4: michael@0: min = hour % 100; michael@0: hour /= 100; michael@0: break; michael@0: case 5: michael@0: case 6: michael@0: sec = hour % 100; michael@0: min = (hour/100) % 100; michael@0: hour /= 10000; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: delete numberFormat; michael@0: michael@0: if (hour > kMAX_CUSTOM_HOUR || min > kMAX_CUSTOM_MIN || sec > kMAX_CUSTOM_SEC) { michael@0: return FALSE; michael@0: } michael@0: return TRUE; michael@0: } michael@0: return FALSE; michael@0: } michael@0: michael@0: UnicodeString& michael@0: TimeZone::formatCustomID(int32_t hour, int32_t min, int32_t sec, michael@0: UBool negative, UnicodeString& id) { michael@0: // Create time zone ID - GMT[+|-]hhmm[ss] michael@0: id.setTo(GMT_ID, GMT_ID_LENGTH); michael@0: if (hour | min | sec) { michael@0: if (negative) { michael@0: id += (UChar)MINUS; michael@0: } else { michael@0: id += (UChar)PLUS; michael@0: } michael@0: michael@0: if (hour < 10) { michael@0: id += (UChar)ZERO_DIGIT; michael@0: } else { michael@0: id += (UChar)(ZERO_DIGIT + hour/10); michael@0: } michael@0: id += (UChar)(ZERO_DIGIT + hour%10); michael@0: id += (UChar)COLON; michael@0: if (min < 10) { michael@0: id += (UChar)ZERO_DIGIT; michael@0: } else { michael@0: id += (UChar)(ZERO_DIGIT + min/10); michael@0: } michael@0: id += (UChar)(ZERO_DIGIT + min%10); michael@0: michael@0: if (sec) { michael@0: id += (UChar)COLON; michael@0: if (sec < 10) { michael@0: id += (UChar)ZERO_DIGIT; michael@0: } else { michael@0: id += (UChar)(ZERO_DIGIT + sec/10); michael@0: } michael@0: id += (UChar)(ZERO_DIGIT + sec%10); michael@0: } michael@0: } michael@0: return id; michael@0: } michael@0: michael@0: michael@0: UBool michael@0: TimeZone::hasSameRules(const TimeZone& other) const michael@0: { michael@0: return (getRawOffset() == other.getRawOffset() && michael@0: useDaylightTime() == other.useDaylightTime()); michael@0: } michael@0: michael@0: static void U_CALLCONV initTZDataVersion(UErrorCode &status) { michael@0: ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONE, timeZone_cleanup); michael@0: int32_t len = 0; michael@0: UResourceBundle *bundle = ures_openDirect(NULL, kZONEINFO, &status); michael@0: const UChar *tzver = ures_getStringByKey(bundle, kTZVERSION, &len, &status); michael@0: michael@0: if (U_SUCCESS(status)) { michael@0: if (len >= (int32_t)sizeof(TZDATA_VERSION)) { michael@0: // Ensure that there is always space for a trailing nul in TZDATA_VERSION michael@0: len = sizeof(TZDATA_VERSION) - 1; michael@0: } michael@0: u_UCharsToChars(tzver, TZDATA_VERSION, len); michael@0: } michael@0: ures_close(bundle); michael@0: michael@0: } michael@0: michael@0: const char* michael@0: TimeZone::getTZDataVersion(UErrorCode& status) michael@0: { michael@0: umtx_initOnce(gTZDataVersionInitOnce, &initTZDataVersion, status); michael@0: return (const char*)TZDATA_VERSION; michael@0: } michael@0: michael@0: UnicodeString& michael@0: TimeZone::getCanonicalID(const UnicodeString& id, UnicodeString& canonicalID, UErrorCode& status) michael@0: { michael@0: UBool isSystemID = FALSE; michael@0: return getCanonicalID(id, canonicalID, isSystemID, status); michael@0: } michael@0: michael@0: UnicodeString& michael@0: TimeZone::getCanonicalID(const UnicodeString& id, UnicodeString& canonicalID, UBool& isSystemID, michael@0: UErrorCode& status) michael@0: { michael@0: canonicalID.remove(); michael@0: isSystemID = FALSE; michael@0: if (U_FAILURE(status)) { michael@0: return canonicalID; michael@0: } michael@0: if (id.compare(UNKNOWN_ZONE_ID, UNKNOWN_ZONE_ID_LENGTH) == 0) { michael@0: // special case - Etc/Unknown is a canonical ID, but not system ID michael@0: canonicalID.fastCopyFrom(id); michael@0: isSystemID = FALSE; michael@0: } else { michael@0: ZoneMeta::getCanonicalCLDRID(id, canonicalID, status); michael@0: if (U_SUCCESS(status)) { michael@0: isSystemID = TRUE; michael@0: } else { michael@0: // Not a system ID michael@0: status = U_ZERO_ERROR; michael@0: getCustomID(id, canonicalID, status); michael@0: } michael@0: } michael@0: return canonicalID; michael@0: } michael@0: michael@0: #ifndef U_HIDE_DRAFT_API michael@0: UnicodeString& michael@0: TimeZone::getWindowsID(const UnicodeString& id, UnicodeString& winid, UErrorCode& status) { michael@0: winid.remove(); michael@0: if (U_FAILURE(status)) { michael@0: return winid; michael@0: } michael@0: michael@0: // canonicalize the input ID michael@0: UnicodeString canonicalID; michael@0: UBool isSystemID = FALSE; michael@0: michael@0: getCanonicalID(id, canonicalID, isSystemID, status); michael@0: if (U_FAILURE(status) || !isSystemID) { michael@0: // mapping data is only applicable to tz database IDs michael@0: return winid; michael@0: } michael@0: michael@0: UResourceBundle *mapTimezones = ures_openDirect(NULL, "windowsZones", &status); michael@0: ures_getByKey(mapTimezones, "mapTimezones", mapTimezones, &status); michael@0: michael@0: if (U_FAILURE(status)) { michael@0: return winid; michael@0: } michael@0: michael@0: UResourceBundle *winzone = NULL; michael@0: UBool found = FALSE; michael@0: while (ures_hasNext(mapTimezones) && !found) { michael@0: winzone = ures_getNextResource(mapTimezones, winzone, &status); michael@0: if (U_FAILURE(status)) { michael@0: break; michael@0: } michael@0: if (ures_getType(winzone) != URES_TABLE) { michael@0: continue; michael@0: } michael@0: UResourceBundle *regionalData = NULL; michael@0: while (ures_hasNext(winzone) && !found) { michael@0: regionalData = ures_getNextResource(winzone, regionalData, &status); michael@0: if (U_FAILURE(status)) { michael@0: break; michael@0: } michael@0: if (ures_getType(regionalData) != URES_STRING) { michael@0: continue; michael@0: } michael@0: int32_t len; michael@0: const UChar *tzids = ures_getString(regionalData, &len, &status); michael@0: if (U_FAILURE(status)) { michael@0: break; michael@0: } michael@0: michael@0: const UChar *start = tzids; michael@0: UBool hasNext = TRUE; michael@0: while (hasNext) { michael@0: const UChar *end = u_strchr(start, (UChar)0x20); michael@0: if (end == NULL) { michael@0: end = tzids + len; michael@0: hasNext = FALSE; michael@0: } michael@0: if (canonicalID.compare(start, end - start) == 0) { michael@0: winid = UnicodeString(ures_getKey(winzone), -1 , US_INV); michael@0: found = TRUE; michael@0: break; michael@0: } michael@0: start = end + 1; michael@0: } michael@0: } michael@0: ures_close(regionalData); michael@0: } michael@0: ures_close(winzone); michael@0: ures_close(mapTimezones); michael@0: michael@0: return winid; michael@0: } michael@0: michael@0: #define MAX_WINDOWS_ID_SIZE 128 michael@0: michael@0: UnicodeString& michael@0: TimeZone::getIDForWindowsID(const UnicodeString& winid, const char* region, UnicodeString& id, UErrorCode& status) { michael@0: id.remove(); michael@0: if (U_FAILURE(status)) { michael@0: return id; michael@0: } michael@0: michael@0: UResourceBundle *zones = ures_openDirect(NULL, "windowsZones", &status); michael@0: ures_getByKey(zones, "mapTimezones", zones, &status); michael@0: if (U_FAILURE(status)) { michael@0: ures_close(zones); michael@0: return id; michael@0: } michael@0: michael@0: UErrorCode tmperr = U_ZERO_ERROR; michael@0: char winidKey[MAX_WINDOWS_ID_SIZE]; michael@0: int32_t winKeyLen = winid.extract(0, winid.length(), winidKey, sizeof(winidKey) - 1, US_INV); michael@0: michael@0: if (winKeyLen == 0 || winKeyLen >= (int32_t)sizeof(winidKey)) { michael@0: ures_close(zones); michael@0: return id; michael@0: } michael@0: winidKey[winKeyLen] = 0; michael@0: michael@0: ures_getByKey(zones, winidKey, zones, &tmperr); // use tmperr, because windows mapping might not michael@0: // be avaiable by design michael@0: if (U_FAILURE(tmperr)) { michael@0: ures_close(zones); michael@0: return id; michael@0: } michael@0: michael@0: const UChar *tzid = NULL; michael@0: int32_t len = 0; michael@0: UBool gotID = FALSE; michael@0: if (region) { michael@0: const UChar *tzids = ures_getStringByKey(zones, region, &len, &tmperr); // use tmperr, because michael@0: // regional mapping is optional michael@0: if (U_SUCCESS(tmperr)) { michael@0: // first ID delimited by space is the defasult one michael@0: const UChar *end = u_strchr(tzids, (UChar)0x20); michael@0: if (end == NULL) { michael@0: id.setTo(tzids, -1); michael@0: } else { michael@0: id.setTo(tzids, end - tzids); michael@0: } michael@0: gotID = TRUE; michael@0: } michael@0: } michael@0: michael@0: if (!gotID) { michael@0: tzid = ures_getStringByKey(zones, "001", &len, &status); // using status, because "001" must be michael@0: // available at this point michael@0: if (U_SUCCESS(status)) { michael@0: id.setTo(tzid, len); michael@0: } michael@0: } michael@0: michael@0: ures_close(zones); michael@0: return id; michael@0: } michael@0: #endif /* U_HIDE_DRAFT_API */ michael@0: michael@0: michael@0: U_NAMESPACE_END michael@0: michael@0: #endif /* #if !UCONFIG_NO_FORMATTING */ michael@0: michael@0: //eof