blob: 128d156ffe4c9892fda317bf3f879e2164f6870d [file] [log] [blame]
/*
*******************************************************************************
* Copyright (C) 2011, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
#include "unicode/utypes.h"
#if !UCONFIG_NO_FORMATTING
#include "tzfmt.h"
#include "tzgnames.h"
#include "cmemory.h"
#include "cstring.h"
#include "putilimp.h"
#include "uassert.h"
#include "ucln_in.h"
#include "uhash.h"
#include "umutex.h"
#include "zonemeta.h"
U_NAMESPACE_BEGIN
// ---------------------------------------------------
// TimeZoneFormatImpl - the TimeZoneFormat implementation
// ---------------------------------------------------
class TimeZoneFormatImpl : public TimeZoneFormat {
public:
TimeZoneFormatImpl(const Locale& locale, UErrorCode& status);
virtual ~TimeZoneFormatImpl();
const TimeZoneNames* getTimeZoneNames() const;
UnicodeString& format(UTimeZoneFormatStyle style, const TimeZone& tz, UDate date,
UnicodeString& name, UTimeZoneTimeType* timeType = NULL) const;
UnicodeString& parse(UTimeZoneFormatStyle style, const UnicodeString& text, ParsePosition& pos,
UnicodeString& tzID, UTimeZoneTimeType* timeType = NULL) const;
private:
UMTX fLock;
Locale fLocale;
char fTargetRegion[ULOC_COUNTRY_CAPACITY];
TimeZoneNames* fTimeZoneNames;
TimeZoneGenericNames* fTimeZoneGenericNames;
UnicodeString& formatGeneric(const TimeZone& tz, UTimeZoneGenericNameType genType, UDate date, UnicodeString& name) const;
UnicodeString& formatSpecific(const TimeZone& tz, UTimeZoneNameType stdType, UTimeZoneNameType dstType,
UDate date, UnicodeString& name, UTimeZoneTimeType *timeType) const;
const TimeZoneGenericNames* getTimeZoneGenericNames(UErrorCode& status) const;
};
TimeZoneFormatImpl::TimeZoneFormatImpl(const Locale& locale, UErrorCode& status)
: fLock(NULL),fLocale(locale), fTimeZoneNames(NULL), fTimeZoneGenericNames(NULL) {
const char* region = fLocale.getCountry();
int32_t regionLen = uprv_strlen(region);
if (regionLen == 0) {
char loc[ULOC_FULLNAME_CAPACITY];
uloc_addLikelySubtags(fLocale.getName(), loc, sizeof(loc), &status);
regionLen = uloc_getCountry(loc, fTargetRegion, sizeof(fTargetRegion), &status);
if (U_SUCCESS(status)) {
fTargetRegion[regionLen] = 0;
} else {
return;
}
} else if (regionLen < (int32_t)sizeof(fTargetRegion)) {
uprv_strcpy(fTargetRegion, region);
} else {
fTargetRegion[0] = 0;
}
fTimeZoneNames = TimeZoneNames::createInstance(locale, status);
// fTimeZoneGenericNames is lazily instantiated
}
TimeZoneFormatImpl::~TimeZoneFormatImpl() {
if (fTimeZoneNames != NULL) {
delete fTimeZoneNames;
}
if (fTimeZoneGenericNames != NULL) {
delete fTimeZoneGenericNames;
}
umtx_destroy(&fLock);
}
const TimeZoneNames*
TimeZoneFormatImpl::getTimeZoneNames() const {
return fTimeZoneNames;
}
const TimeZoneGenericNames*
TimeZoneFormatImpl::getTimeZoneGenericNames(UErrorCode& status) const {
if (U_FAILURE(status)) {
return NULL;
}
UBool create;
UMTX_CHECK(&gZoneMetaLock, (fTimeZoneGenericNames == NULL), create);
if (create) {
TimeZoneFormatImpl *nonConstThis = const_cast<TimeZoneFormatImpl *>(this);
umtx_lock(&nonConstThis->fLock);
{
if (fTimeZoneGenericNames == NULL) {
nonConstThis->fTimeZoneGenericNames = new TimeZoneGenericNames(fLocale, status);
if (U_SUCCESS(status) && fTimeZoneGenericNames == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
}
}
}
umtx_unlock(&nonConstThis->fLock);
}
return fTimeZoneGenericNames;
}
UnicodeString&
TimeZoneFormatImpl::format(UTimeZoneFormatStyle style, const TimeZone& tz, UDate date,
UnicodeString& name, UTimeZoneTimeType* timeType /* = NULL */) const {
if (timeType) {
*timeType = UTZFMT_TIME_TYPE_UNKNOWN;
}
switch (style) {
case UTZFMT_STYLE_LOCATION:
formatGeneric(tz, UTZGNM_LOCATION, date, name);
break;
case UTZFMT_STYLE_GENERIC_LONG:
formatGeneric(tz, UTZGNM_LONG, date, name);
break;
case UTZFMT_STYLE_GENERIC_SHORT:
formatGeneric(tz, UTZGNM_SHORT, date, name);
break;
case UTZFMT_STYLE_SPECIFIC_LONG:
formatSpecific(tz, UTZNM_LONG_STANDARD, UTZNM_LONG_DAYLIGHT, date, name, timeType);
break;
case UTZFMT_STYLE_SPECIFIC_SHORT:
formatSpecific(tz, UTZNM_SHORT_STANDARD, UTZNM_SHORT_DAYLIGHT, date, name, timeType);
break;
case UTZFMT_STYLE_SPECIFIC_SHORT_COMMONLY_USED:
formatSpecific(tz, UTZNM_SHORT_STANDARD_COMMONLY_USED, UTZNM_SHORT_DAYLIGHT_COMMONLY_USED, date, name, timeType);
break;
}
return name;
}
UnicodeString&
TimeZoneFormatImpl::parse(UTimeZoneFormatStyle style, const UnicodeString& text, ParsePosition& pos,
UnicodeString& tzID, UTimeZoneTimeType* timeType /* = NULL */) const {
if (timeType) {
*timeType = UTZFMT_TIME_TYPE_UNKNOWN;
}
tzID.setToBogus();
int32_t startIdx = pos.getIndex();
UBool isGeneric = FALSE;
uint32_t types = 0;
switch (style) {
case UTZFMT_STYLE_LOCATION:
isGeneric = TRUE;
types = UTZGNM_LOCATION;
break;
case UTZFMT_STYLE_GENERIC_LONG:
isGeneric = TRUE;
types = UTZGNM_LOCATION | UTZGNM_LONG;
break;
case UTZFMT_STYLE_GENERIC_SHORT:
isGeneric = TRUE;
types = UTZGNM_LOCATION | UTZGNM_SHORT;
break;
case UTZFMT_STYLE_SPECIFIC_LONG:
types = UTZNM_LONG_STANDARD | UTZNM_LONG_DAYLIGHT;
break;
case UTZFMT_STYLE_SPECIFIC_SHORT:
types = UTZNM_SHORT_STANDARD | UTZNM_SHORT_DAYLIGHT;
break;
case UTZFMT_STYLE_SPECIFIC_SHORT_COMMONLY_USED:
types = UTZNM_SHORT_STANDARD_COMMONLY_USED | UTZNM_SHORT_DAYLIGHT_COMMONLY_USED;
break;
}
UTimeZoneTimeType parsedTimeType = UTZFMT_TIME_TYPE_UNKNOWN;
UnicodeString parsedTzID;
UErrorCode status = U_ZERO_ERROR;
if (isGeneric) {
int32_t len = 0;
const TimeZoneGenericNames *gnames = getTimeZoneGenericNames(status);
if (U_SUCCESS(status)) {
len = gnames->findBestMatch(text, startIdx, types, parsedTzID, parsedTimeType, status);
}
if (U_FAILURE(status) || len == 0) {
pos.setErrorIndex(startIdx);
return tzID;
}
pos.setIndex(startIdx + len);
} else {
TimeZoneNameMatchInfo *matchInfo = fTimeZoneNames->find(text, startIdx, types, status);
if (U_FAILURE(status) || matchInfo == NULL) {
pos.setErrorIndex(startIdx);
return tzID;
}
int32_t bestLen = 0;
int32_t bestIdx = -1;
for (int32_t i = 0; i < matchInfo->size(); i++) {
int32_t matchLen = matchInfo->getMatchLength(i);
if (matchLen > bestLen) {
bestLen = matchLen;
bestIdx = i;
}
}
if (bestIdx >= 0) {
matchInfo->getTimeZoneID(bestIdx, parsedTzID);
if (parsedTzID.isEmpty()) {
UnicodeString mzID;
matchInfo->getMetaZoneID(bestIdx, mzID);
U_ASSERT(mzID.length() > 0);
fTimeZoneNames->getReferenceZoneID(mzID, fTargetRegion, parsedTzID);
}
UTimeZoneNameType nameType = matchInfo->getNameType(bestIdx);
switch (nameType) {
case UTZNM_LONG_STANDARD:
case UTZNM_SHORT_STANDARD:
case UTZNM_SHORT_STANDARD_COMMONLY_USED:
parsedTimeType = UTZFMT_TIME_TYPE_STANDARD;
break;
case UTZNM_LONG_DAYLIGHT:
case UTZNM_SHORT_DAYLIGHT:
case UTZNM_SHORT_DAYLIGHT_COMMONLY_USED:
parsedTimeType = UTZFMT_TIME_TYPE_DAYLIGHT;
break;
default:
parsedTimeType = UTZFMT_TIME_TYPE_UNKNOWN;
break;
}
pos.setIndex(startIdx + bestLen);
}
delete matchInfo;
}
if (timeType) {
*timeType = parsedTimeType;
}
tzID.setTo(parsedTzID);
return tzID;
}
UnicodeString&
TimeZoneFormatImpl::formatGeneric(const TimeZone& tz, UTimeZoneGenericNameType genType, UDate date, UnicodeString& name) const {
UErrorCode status = U_ZERO_ERROR;
const TimeZoneGenericNames* gnames = getTimeZoneGenericNames(status);
if (U_FAILURE(status)) {
name.setToBogus();
return name;
}
if (genType == UTZGNM_LOCATION) {
const UChar* canonicalID = ZoneMeta::getCanonicalCLDRID(tz);
if (canonicalID == NULL) {
name.setToBogus();
return name;
}
return gnames->getGenericLocationName(UnicodeString(canonicalID), name);
}
return gnames->getDisplayName(tz, genType, date, name);
}
UnicodeString&
TimeZoneFormatImpl::formatSpecific(const TimeZone& tz, UTimeZoneNameType stdType, UTimeZoneNameType dstType,
UDate date, UnicodeString& name, UTimeZoneTimeType *timeType) const {
if (fTimeZoneNames == NULL) {
name.setToBogus();
return name;
}
UErrorCode status = U_ZERO_ERROR;
UBool isDaylight = tz.inDaylightTime(date, status);
const UChar* canonicalID = ZoneMeta::getCanonicalCLDRID(tz);
if (U_FAILURE(status) || canonicalID == NULL) {
name.setToBogus();
return name;
}
if (isDaylight) {
fTimeZoneNames->getDisplayName(UnicodeString(canonicalID), dstType, date, name);
} else {
fTimeZoneNames->getDisplayName(UnicodeString(canonicalID), stdType, date, name);
}
if (timeType && !name.isEmpty()) {
*timeType = isDaylight ? UTZFMT_TIME_TYPE_DAYLIGHT : UTZFMT_TIME_TYPE_STANDARD;
}
return name;
}
// TimeZoneFormat object cache handling
static UMTX gTimeZoneFormatLock = NULL;
static UHashtable *gTimeZoneFormatCache = NULL;
static UBool gTimeZoneFormatCacheInitialized = FALSE;
// Access count - incremented every time up to SWEEP_INTERVAL,
// then reset to 0
static int32_t gAccessCount = 0;
// Interval for calling the cache sweep function - every 100 times
#define SWEEP_INTERVAL 100
// Cache expiration in millisecond. When a cached entry is no
// longer referenced and exceeding this threshold since last
// access time, then the cache entry will be deleted by the sweep
// function. For now, 3 minutes.
#define CACHE_EXPIRATION 180000.0
typedef struct TimeZoneFormatCacheEntry {
TimeZoneFormat* tzfmt;
int32_t refCount;
double lastAccess;
} TimeZoneNameFormatCacheEntry;
U_CDECL_BEGIN
/**
* Cleanup callback func
*/
static UBool U_CALLCONV timeZoneFormat_cleanup(void)
{
umtx_destroy(&gTimeZoneFormatLock);
if (gTimeZoneFormatCache != NULL) {
uhash_close(gTimeZoneFormatCache);
gTimeZoneFormatCache = NULL;
}
gTimeZoneFormatCacheInitialized = FALSE;
return TRUE;
}
/**
* Deleter for TimeZoneNamesCacheEntry
*/
static void U_CALLCONV
deleteTimeZoneFormatCacheEntry(void *obj) {
TimeZoneNameFormatCacheEntry *entry = (TimeZoneNameFormatCacheEntry *)obj;
delete (TimeZoneFormat *) entry->tzfmt;
uprv_free((void *)entry);
}
U_CDECL_END
/**
* Function used for removing unreferrenced cache entries exceeding
* the expiration time. This function must be called with in the mutex
* block.
*/
static void sweepCache() {
int32_t pos = -1;
const UHashElement* elem;
double now = (double)uprv_getUTCtime();
while ((elem = uhash_nextElement(gTimeZoneFormatCache, &pos))) {
TimeZoneFormatCacheEntry *entry = (TimeZoneFormatCacheEntry *)elem->value.pointer;
if (entry->refCount <= 0 && (now - entry->lastAccess) > CACHE_EXPIRATION) {
// delete this entry
uhash_removeElement(gTimeZoneFormatCache, elem);
}
}
}
// ---------------------------------------------------
// TimeZoneFormatDelegate
// This class wraps a TimeZoneFormatImpl singleton
// per locale and maintain the reference count.
// ---------------------------------------------------
class TimeZoneFormatDelegate : public TimeZoneFormat {
public:
TimeZoneFormatDelegate(const Locale& locale, UErrorCode& status);
virtual ~TimeZoneFormatDelegate();
const TimeZoneNames* getTimeZoneNames() const;
UnicodeString& format(UTimeZoneFormatStyle style, const TimeZone& tz, UDate date,
UnicodeString& name, UTimeZoneTimeType* timeType = NULL) const;
UnicodeString& parse(UTimeZoneFormatStyle style, const UnicodeString& text, ParsePosition& pos,
UnicodeString& tzID, UTimeZoneTimeType* timeType = NULL) const;
private:
TimeZoneFormatCacheEntry* fTZfmtCacheEntry;
};
TimeZoneFormatDelegate::TimeZoneFormatDelegate(const Locale& locale, UErrorCode& status) {
UBool initialized;
UMTX_CHECK(&gTimeZoneFormatLock, gTimeZoneFormatCacheInitialized, initialized);
if (!initialized) {
// Create empty hashtable
umtx_lock(&gTimeZoneFormatLock);
{
if (!gTimeZoneFormatCacheInitialized) {
gTimeZoneFormatCache = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status);
if (U_SUCCESS(status)) {
uhash_setKeyDeleter(gTimeZoneFormatCache, uhash_freeBlock);
uhash_setValueDeleter(gTimeZoneFormatCache, deleteTimeZoneFormatCacheEntry);
gTimeZoneFormatCacheInitialized = TRUE;
ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONEFORMAT, timeZoneFormat_cleanup);
}
}
}
umtx_unlock(&gTimeZoneFormatLock);
}
// Check the cache, if not available, create new one and cache
TimeZoneFormatCacheEntry *cacheEntry = NULL;
umtx_lock(&gTimeZoneFormatLock);
{
const char *key = locale.getName();
cacheEntry = (TimeZoneFormatCacheEntry *)uhash_get(gTimeZoneFormatCache, key);
if (cacheEntry == NULL) {
TimeZoneFormat *tzfmt = NULL;
char *newKey = NULL;
tzfmt = new TimeZoneFormatImpl(locale, status);
if (tzfmt == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
}
if (U_SUCCESS(status)) {
newKey = (char *)uprv_malloc(uprv_strlen(key) + 1);
if (newKey == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
} else {
uprv_strcpy(newKey, key);
}
}
if (U_SUCCESS(status)) {
cacheEntry = (TimeZoneFormatCacheEntry *)uprv_malloc(sizeof(TimeZoneFormatCacheEntry));
if (cacheEntry == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
} else {
cacheEntry->tzfmt = tzfmt;
cacheEntry->refCount = 1;
cacheEntry->lastAccess = (double)uprv_getUTCtime();
uhash_put(gTimeZoneFormatCache, newKey, cacheEntry, &status);
}
}
if (U_FAILURE(status)) {
if (tzfmt != NULL) {
delete tzfmt;
}
if (newKey != NULL) {
uprv_free(newKey);
}
if (cacheEntry != NULL) {
uprv_free(cacheEntry);
}
return;
}
} else {
// Update the reference count
cacheEntry->refCount++;
cacheEntry->lastAccess = (double)uprv_getUTCtime();
}
gAccessCount++;
if (gAccessCount >= SWEEP_INTERVAL) {
// sweep
sweepCache();
gAccessCount = 0;
}
}
umtx_unlock(&gTimeZoneFormatLock);
fTZfmtCacheEntry = cacheEntry;
}
TimeZoneFormatDelegate::~TimeZoneFormatDelegate() {
umtx_lock(&gTimeZoneFormatLock);
{
U_ASSERT(fTZfmtCacheEntry->refCount > 0);
// Just decrement the reference count
fTZfmtCacheEntry->refCount--;
}
umtx_unlock(&gTimeZoneFormatLock);
}
const TimeZoneNames*
TimeZoneFormatDelegate::getTimeZoneNames() const {
return fTZfmtCacheEntry->tzfmt->getTimeZoneNames();
}
UnicodeString&
TimeZoneFormatDelegate::format(UTimeZoneFormatStyle style, const TimeZone& tz, UDate date,
UnicodeString& name, UTimeZoneTimeType* timeType /* = NULL */) const {
return fTZfmtCacheEntry->tzfmt->format(style, tz, date, name, timeType);
}
UnicodeString&
TimeZoneFormatDelegate::parse(UTimeZoneFormatStyle style, const UnicodeString& text, ParsePosition& pos,
UnicodeString& tzID, UTimeZoneTimeType* timeType /* = NULL */) const {
return fTZfmtCacheEntry->tzfmt->parse(style, text, pos, tzID, timeType);
}
// ---------------------------------------------------
// TimeZoneFormat base class
// ---------------------------------------------------
TimeZoneFormat::~TimeZoneFormat() {
}
TimeZone*
TimeZoneFormat::parse(UTimeZoneFormatStyle style, const UnicodeString& text, ParsePosition& pos,
UTimeZoneTimeType* timeType /*= NULL*/) const {
UnicodeString tzID;
parse(style, text, pos, tzID, timeType);
if (pos.getErrorIndex() < 0) {
return TimeZone::createTimeZone(tzID);
}
return NULL;
}
TimeZoneFormat* U_EXPORT2
TimeZoneFormat::createInstance(const Locale& locale, UErrorCode& status) {
TimeZoneFormat* tzfmt = new TimeZoneFormatDelegate(locale, status);
if (U_SUCCESS(status) && tzfmt == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
}
return tzfmt;
}
U_NAMESPACE_END
#endif