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