Home | History | Annotate | Download | only in i18n
      1 /*
      2 *******************************************************************************
      3 * Copyright (C) 2011, International Business Machines Corporation and         *
      4 * others. All Rights Reserved.                                                *
      5 *******************************************************************************
      6 */
      7 
      8 #include "unicode/utypes.h"
      9 
     10 #if !UCONFIG_NO_FORMATTING
     11 
     12 #include "tzfmt.h"
     13 #include "tzgnames.h"
     14 #include "cmemory.h"
     15 #include "cstring.h"
     16 #include "putilimp.h"
     17 #include "uassert.h"
     18 #include "ucln_in.h"
     19 #include "uhash.h"
     20 #include "umutex.h"
     21 #include "zonemeta.h"
     22 
     23 U_NAMESPACE_BEGIN
     24 
     25 // ---------------------------------------------------
     26 // TimeZoneFormatImpl - the TimeZoneFormat implementation
     27 // ---------------------------------------------------
     28 class TimeZoneFormatImpl : public TimeZoneFormat {
     29 public:
     30     TimeZoneFormatImpl(const Locale& locale, UErrorCode& status);
     31     virtual ~TimeZoneFormatImpl();
     32 
     33     const TimeZoneNames* getTimeZoneNames() const;
     34 
     35     UnicodeString& format(UTimeZoneFormatStyle style, const TimeZone& tz, UDate date,
     36         UnicodeString& name, UTimeZoneTimeType* timeType = NULL) const;
     37 
     38     UnicodeString& parse(UTimeZoneFormatStyle style, const UnicodeString& text, ParsePosition& pos,
     39         UnicodeString& tzID, UTimeZoneTimeType* timeType = NULL) const;
     40 
     41 private:
     42     UMTX fLock;
     43     Locale fLocale;
     44     char fTargetRegion[ULOC_COUNTRY_CAPACITY];
     45     TimeZoneNames* fTimeZoneNames;
     46     TimeZoneGenericNames* fTimeZoneGenericNames;
     47 
     48     UnicodeString& formatGeneric(const TimeZone& tz, UTimeZoneGenericNameType genType, UDate date, UnicodeString& name) const;
     49 
     50     UnicodeString& formatSpecific(const TimeZone& tz, UTimeZoneNameType stdType, UTimeZoneNameType dstType,
     51         UDate date, UnicodeString& name, UTimeZoneTimeType *timeType) const;
     52 
     53     const TimeZoneGenericNames* getTimeZoneGenericNames(UErrorCode& status) const;
     54 };
     55 
     56 TimeZoneFormatImpl::TimeZoneFormatImpl(const Locale& locale, UErrorCode& status)
     57 : fLock(NULL),fLocale(locale), fTimeZoneNames(NULL), fTimeZoneGenericNames(NULL) {
     58 
     59     const char* region = fLocale.getCountry();
     60     int32_t regionLen = uprv_strlen(region);
     61     if (regionLen == 0) {
     62         char loc[ULOC_FULLNAME_CAPACITY];
     63         uloc_addLikelySubtags(fLocale.getName(), loc, sizeof(loc), &status);
     64 
     65         regionLen = uloc_getCountry(loc, fTargetRegion, sizeof(fTargetRegion), &status);
     66         if (U_SUCCESS(status)) {
     67             fTargetRegion[regionLen] = 0;
     68         } else {
     69             return;
     70         }
     71     } else if (regionLen < (int32_t)sizeof(fTargetRegion)) {
     72         uprv_strcpy(fTargetRegion, region);
     73     } else {
     74         fTargetRegion[0] = 0;
     75     }
     76 
     77     fTimeZoneNames = TimeZoneNames::createInstance(locale, status);
     78     // fTimeZoneGenericNames is lazily instantiated
     79 }
     80 
     81 TimeZoneFormatImpl::~TimeZoneFormatImpl() {
     82     if (fTimeZoneNames != NULL) {
     83         delete fTimeZoneNames;
     84     }
     85     if (fTimeZoneGenericNames != NULL) {
     86         delete fTimeZoneGenericNames;
     87     }
     88     umtx_destroy(&fLock);
     89 }
     90 
     91 const TimeZoneNames*
     92 TimeZoneFormatImpl::getTimeZoneNames() const {
     93     return fTimeZoneNames;
     94 }
     95 
     96 const TimeZoneGenericNames*
     97 TimeZoneFormatImpl::getTimeZoneGenericNames(UErrorCode& status) const {
     98     if (U_FAILURE(status)) {
     99         return NULL;
    100     }
    101 
    102     UBool create;
    103     UMTX_CHECK(&gZoneMetaLock, (fTimeZoneGenericNames == NULL), create);
    104     if (create) {
    105         TimeZoneFormatImpl *nonConstThis = const_cast<TimeZoneFormatImpl *>(this);
    106         umtx_lock(&nonConstThis->fLock);
    107         {
    108             if (fTimeZoneGenericNames == NULL) {
    109                 nonConstThis->fTimeZoneGenericNames = new TimeZoneGenericNames(fLocale, status);
    110                 if (U_SUCCESS(status) && fTimeZoneGenericNames == NULL) {
    111                     status = U_MEMORY_ALLOCATION_ERROR;
    112                 }
    113             }
    114         }
    115         umtx_unlock(&nonConstThis->fLock);
    116     }
    117 
    118     return fTimeZoneGenericNames;
    119 }
    120 
    121 UnicodeString&
    122 TimeZoneFormatImpl::format(UTimeZoneFormatStyle style, const TimeZone& tz, UDate date,
    123         UnicodeString& name, UTimeZoneTimeType* timeType /* = NULL */) const {
    124     if (timeType) {
    125         *timeType = UTZFMT_TIME_TYPE_UNKNOWN;
    126     }
    127     switch (style) {
    128     case UTZFMT_STYLE_LOCATION:
    129         formatGeneric(tz, UTZGNM_LOCATION, date, name);
    130         break;
    131     case UTZFMT_STYLE_GENERIC_LONG:
    132         formatGeneric(tz, UTZGNM_LONG, date, name);
    133         break;
    134     case UTZFMT_STYLE_GENERIC_SHORT:
    135         formatGeneric(tz, UTZGNM_SHORT, date, name);
    136         break;
    137     case UTZFMT_STYLE_SPECIFIC_LONG:
    138         formatSpecific(tz, UTZNM_LONG_STANDARD, UTZNM_LONG_DAYLIGHT, date, name, timeType);
    139         break;
    140     case UTZFMT_STYLE_SPECIFIC_SHORT:
    141         formatSpecific(tz, UTZNM_SHORT_STANDARD, UTZNM_SHORT_DAYLIGHT, date, name, timeType);
    142         break;
    143     case UTZFMT_STYLE_SPECIFIC_SHORT_COMMONLY_USED:
    144         formatSpecific(tz, UTZNM_SHORT_STANDARD_COMMONLY_USED, UTZNM_SHORT_DAYLIGHT_COMMONLY_USED, date, name, timeType);
    145         break;
    146     }
    147     return name;
    148 }
    149 
    150 UnicodeString&
    151 TimeZoneFormatImpl::parse(UTimeZoneFormatStyle style, const UnicodeString& text, ParsePosition& pos,
    152         UnicodeString& tzID, UTimeZoneTimeType* timeType /* = NULL */) const {
    153     if (timeType) {
    154         *timeType = UTZFMT_TIME_TYPE_UNKNOWN;
    155     }
    156     tzID.setToBogus();
    157 
    158     int32_t startIdx = pos.getIndex();
    159 
    160     UBool isGeneric = FALSE;
    161     uint32_t types = 0;
    162 
    163     switch (style) {
    164     case UTZFMT_STYLE_LOCATION:
    165         isGeneric = TRUE;
    166         types = UTZGNM_LOCATION;
    167         break;
    168     case UTZFMT_STYLE_GENERIC_LONG:
    169         isGeneric = TRUE;
    170         types = UTZGNM_LOCATION | UTZGNM_LONG;
    171         break;
    172     case UTZFMT_STYLE_GENERIC_SHORT:
    173         isGeneric = TRUE;
    174         types = UTZGNM_LOCATION | UTZGNM_SHORT;
    175         break;
    176     case UTZFMT_STYLE_SPECIFIC_LONG:
    177         types = UTZNM_LONG_STANDARD | UTZNM_LONG_DAYLIGHT;
    178         break;
    179     case UTZFMT_STYLE_SPECIFIC_SHORT:
    180         types = UTZNM_SHORT_STANDARD | UTZNM_SHORT_DAYLIGHT;
    181         break;
    182     case UTZFMT_STYLE_SPECIFIC_SHORT_COMMONLY_USED:
    183         types = UTZNM_SHORT_STANDARD_COMMONLY_USED | UTZNM_SHORT_DAYLIGHT_COMMONLY_USED;
    184         break;
    185     }
    186 
    187     UTimeZoneTimeType parsedTimeType = UTZFMT_TIME_TYPE_UNKNOWN;
    188     UnicodeString parsedTzID;
    189     UErrorCode status = U_ZERO_ERROR;
    190 
    191     if (isGeneric) {
    192         int32_t len = 0;
    193         const TimeZoneGenericNames *gnames = getTimeZoneGenericNames(status);
    194         if (U_SUCCESS(status)) {
    195             len = gnames->findBestMatch(text, startIdx, types, parsedTzID, parsedTimeType, status);
    196         }
    197         if (U_FAILURE(status) || len == 0) {
    198             pos.setErrorIndex(startIdx);
    199             return tzID;
    200         }
    201         pos.setIndex(startIdx + len);
    202     } else {
    203         TimeZoneNameMatchInfo *matchInfo = fTimeZoneNames->find(text, startIdx, types, status);
    204         if (U_FAILURE(status) || matchInfo == NULL) {
    205             pos.setErrorIndex(startIdx);
    206             return tzID;
    207         }
    208         int32_t bestLen = 0;
    209         int32_t bestIdx = -1;
    210         for (int32_t i = 0; i < matchInfo->size(); i++) {
    211             int32_t matchLen = matchInfo->getMatchLength(i);
    212             if (matchLen > bestLen) {
    213                 bestLen = matchLen;
    214                 bestIdx = i;
    215             }
    216         }
    217         if (bestIdx >= 0) {
    218             matchInfo->getTimeZoneID(bestIdx, parsedTzID);
    219             if (parsedTzID.isEmpty()) {
    220                 UnicodeString mzID;
    221                 matchInfo->getMetaZoneID(bestIdx, mzID);
    222                 U_ASSERT(mzID.length() > 0);
    223                 fTimeZoneNames->getReferenceZoneID(mzID, fTargetRegion, parsedTzID);
    224             }
    225             UTimeZoneNameType nameType = matchInfo->getNameType(bestIdx);
    226             switch (nameType) {
    227             case UTZNM_LONG_STANDARD:
    228             case UTZNM_SHORT_STANDARD:
    229             case UTZNM_SHORT_STANDARD_COMMONLY_USED:
    230                 parsedTimeType = UTZFMT_TIME_TYPE_STANDARD;
    231                 break;
    232             case UTZNM_LONG_DAYLIGHT:
    233             case UTZNM_SHORT_DAYLIGHT:
    234             case UTZNM_SHORT_DAYLIGHT_COMMONLY_USED:
    235                 parsedTimeType = UTZFMT_TIME_TYPE_DAYLIGHT;
    236                 break;
    237             default:
    238                 parsedTimeType = UTZFMT_TIME_TYPE_UNKNOWN;
    239                 break;
    240             }
    241             pos.setIndex(startIdx + bestLen);
    242         }
    243         delete matchInfo;
    244     }
    245     if (timeType) {
    246         *timeType = parsedTimeType;
    247     }
    248     tzID.setTo(parsedTzID);
    249     return tzID;
    250 }
    251 
    252 UnicodeString&
    253 TimeZoneFormatImpl::formatGeneric(const TimeZone& tz, UTimeZoneGenericNameType genType, UDate date, UnicodeString& name) const {
    254     UErrorCode status = U_ZERO_ERROR;
    255     const TimeZoneGenericNames* gnames = getTimeZoneGenericNames(status);
    256     if (U_FAILURE(status)) {
    257         name.setToBogus();
    258         return name;
    259     }
    260 
    261     if (genType == UTZGNM_LOCATION) {
    262         const UChar* canonicalID = ZoneMeta::getCanonicalCLDRID(tz);
    263         if (canonicalID == NULL) {
    264             name.setToBogus();
    265             return name;
    266         }
    267         return gnames->getGenericLocationName(UnicodeString(canonicalID), name);
    268     }
    269     return gnames->getDisplayName(tz, genType, date, name);
    270 }
    271 
    272 UnicodeString&
    273 TimeZoneFormatImpl::formatSpecific(const TimeZone& tz, UTimeZoneNameType stdType, UTimeZoneNameType dstType,
    274         UDate date, UnicodeString& name, UTimeZoneTimeType *timeType) const {
    275     if (fTimeZoneNames == NULL) {
    276         name.setToBogus();
    277         return name;
    278     }
    279 
    280     UErrorCode status = U_ZERO_ERROR;
    281     UBool isDaylight = tz.inDaylightTime(date, status);
    282     const UChar* canonicalID = ZoneMeta::getCanonicalCLDRID(tz);
    283 
    284     if (U_FAILURE(status) || canonicalID == NULL) {
    285         name.setToBogus();
    286         return name;
    287     }
    288 
    289     if (isDaylight) {
    290         fTimeZoneNames->getDisplayName(UnicodeString(canonicalID), dstType, date, name);
    291     } else {
    292         fTimeZoneNames->getDisplayName(UnicodeString(canonicalID), stdType, date, name);
    293     }
    294 
    295     if (timeType && !name.isEmpty()) {
    296         *timeType = isDaylight ? UTZFMT_TIME_TYPE_DAYLIGHT : UTZFMT_TIME_TYPE_STANDARD;
    297     }
    298     return name;
    299 }
    300 
    301 
    302 // TimeZoneFormat object cache handling
    303 static UMTX gTimeZoneFormatLock = NULL;
    304 static UHashtable *gTimeZoneFormatCache = NULL;
    305 static UBool gTimeZoneFormatCacheInitialized = FALSE;
    306 
    307 // Access count - incremented every time up to SWEEP_INTERVAL,
    308 // then reset to 0
    309 static int32_t gAccessCount = 0;
    310 
    311 // Interval for calling the cache sweep function - every 100 times
    312 #define SWEEP_INTERVAL 100
    313 
    314 // Cache expiration in millisecond. When a cached entry is no
    315 // longer referenced and exceeding this threshold since last
    316 // access time, then the cache entry will be deleted by the sweep
    317 // function. For now, 3 minutes.
    318 #define CACHE_EXPIRATION 180000.0
    319 
    320 typedef struct TimeZoneFormatCacheEntry {
    321     TimeZoneFormat* tzfmt;
    322     int32_t         refCount;
    323     double          lastAccess;
    324 } TimeZoneNameFormatCacheEntry;
    325 
    326 U_CDECL_BEGIN
    327 /**
    328  * Cleanup callback func
    329  */
    330 static UBool U_CALLCONV timeZoneFormat_cleanup(void)
    331 {
    332     umtx_destroy(&gTimeZoneFormatLock);
    333 
    334     if (gTimeZoneFormatCache != NULL) {
    335         uhash_close(gTimeZoneFormatCache);
    336         gTimeZoneFormatCache = NULL;
    337     }
    338     gTimeZoneFormatCacheInitialized = FALSE;
    339     return TRUE;
    340 }
    341 
    342 /**
    343  * Deleter for TimeZoneNamesCacheEntry
    344  */
    345 static void U_CALLCONV
    346 deleteTimeZoneFormatCacheEntry(void *obj) {
    347     TimeZoneNameFormatCacheEntry *entry = (TimeZoneNameFormatCacheEntry *)obj;
    348     delete (TimeZoneFormat *) entry->tzfmt;
    349     uprv_free((void *)entry);
    350 }
    351 U_CDECL_END
    352 
    353 /**
    354  * Function used for removing unreferrenced cache entries exceeding
    355  * the expiration time. This function must be called with in the mutex
    356  * block.
    357  */
    358 static void sweepCache() {
    359     int32_t pos = -1;
    360     const UHashElement* elem;
    361     double now = (double)uprv_getUTCtime();
    362 
    363     while ((elem = uhash_nextElement(gTimeZoneFormatCache, &pos))) {
    364         TimeZoneFormatCacheEntry *entry = (TimeZoneFormatCacheEntry *)elem->value.pointer;
    365         if (entry->refCount <= 0 && (now - entry->lastAccess) > CACHE_EXPIRATION) {
    366             // delete this entry
    367             uhash_removeElement(gTimeZoneFormatCache, elem);
    368         }
    369     }
    370 }
    371 
    372 // ---------------------------------------------------
    373 // TimeZoneFormatDelegate
    374 // This class wraps a TimeZoneFormatImpl singleton
    375 // per locale and maintain the reference count.
    376 // ---------------------------------------------------
    377 class TimeZoneFormatDelegate : public TimeZoneFormat {
    378 public:
    379     TimeZoneFormatDelegate(const Locale& locale, UErrorCode& status);
    380     virtual ~TimeZoneFormatDelegate();
    381 
    382     const TimeZoneNames* getTimeZoneNames() const;
    383 
    384     UnicodeString& format(UTimeZoneFormatStyle style, const TimeZone& tz, UDate date,
    385         UnicodeString& name, UTimeZoneTimeType* timeType = NULL) const;
    386 
    387     UnicodeString& parse(UTimeZoneFormatStyle style, const UnicodeString& text, ParsePosition& pos,
    388         UnicodeString& tzID, UTimeZoneTimeType* timeType = NULL) const;
    389 
    390 private:
    391     TimeZoneFormatCacheEntry* fTZfmtCacheEntry;
    392 };
    393 
    394 TimeZoneFormatDelegate::TimeZoneFormatDelegate(const Locale& locale, UErrorCode& status) {
    395     UBool initialized;
    396     UMTX_CHECK(&gTimeZoneFormatLock, gTimeZoneFormatCacheInitialized, initialized);
    397     if (!initialized) {
    398         // Create empty hashtable
    399         umtx_lock(&gTimeZoneFormatLock);
    400         {
    401             if (!gTimeZoneFormatCacheInitialized) {
    402                 gTimeZoneFormatCache = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status);
    403                 if (U_SUCCESS(status)) {
    404                     uhash_setKeyDeleter(gTimeZoneFormatCache, uhash_freeBlock);
    405                     uhash_setValueDeleter(gTimeZoneFormatCache, deleteTimeZoneFormatCacheEntry);
    406                     gTimeZoneFormatCacheInitialized = TRUE;
    407                     ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONEFORMAT, timeZoneFormat_cleanup);
    408                 }
    409             }
    410         }
    411         umtx_unlock(&gTimeZoneFormatLock);
    412     }
    413 
    414     // Check the cache, if not available, create new one and cache
    415     TimeZoneFormatCacheEntry *cacheEntry = NULL;
    416     umtx_lock(&gTimeZoneFormatLock);
    417     {
    418         const char *key = locale.getName();
    419         cacheEntry = (TimeZoneFormatCacheEntry *)uhash_get(gTimeZoneFormatCache, key);
    420         if (cacheEntry == NULL) {
    421             TimeZoneFormat *tzfmt = NULL;
    422             char *newKey = NULL;
    423 
    424             tzfmt = new TimeZoneFormatImpl(locale, status);
    425             if (tzfmt == NULL) {
    426                 status = U_MEMORY_ALLOCATION_ERROR;
    427             }
    428             if (U_SUCCESS(status)) {
    429                 newKey = (char *)uprv_malloc(uprv_strlen(key) + 1);
    430                 if (newKey == NULL) {
    431                     status = U_MEMORY_ALLOCATION_ERROR;
    432                 } else {
    433                     uprv_strcpy(newKey, key);
    434                 }
    435             }
    436             if (U_SUCCESS(status)) {
    437                 cacheEntry = (TimeZoneFormatCacheEntry *)uprv_malloc(sizeof(TimeZoneFormatCacheEntry));
    438                 if (cacheEntry == NULL) {
    439                     status = U_MEMORY_ALLOCATION_ERROR;
    440                 } else {
    441                     cacheEntry->tzfmt = tzfmt;
    442                     cacheEntry->refCount = 1;
    443                     cacheEntry->lastAccess = (double)uprv_getUTCtime();
    444 
    445                     uhash_put(gTimeZoneFormatCache, newKey, cacheEntry, &status);
    446                 }
    447             }
    448             if (U_FAILURE(status)) {
    449                 if (tzfmt != NULL) {
    450                     delete tzfmt;
    451                 }
    452                 if (newKey != NULL) {
    453                     uprv_free(newKey);
    454                 }
    455                 if (cacheEntry != NULL) {
    456                     uprv_free(cacheEntry);
    457                 }
    458                 return;
    459             }
    460         } else {
    461             // Update the reference count
    462             cacheEntry->refCount++;
    463             cacheEntry->lastAccess = (double)uprv_getUTCtime();
    464         }
    465         gAccessCount++;
    466         if (gAccessCount >= SWEEP_INTERVAL) {
    467             // sweep
    468             sweepCache();
    469             gAccessCount = 0;
    470         }
    471     }
    472     umtx_unlock(&gTimeZoneFormatLock);
    473 
    474     fTZfmtCacheEntry = cacheEntry;
    475 }
    476 
    477 TimeZoneFormatDelegate::~TimeZoneFormatDelegate() {
    478     umtx_lock(&gTimeZoneFormatLock);
    479     {
    480         U_ASSERT(fTZfmtCacheEntry->refCount > 0);
    481         // Just decrement the reference count
    482         fTZfmtCacheEntry->refCount--;
    483     }
    484     umtx_unlock(&gTimeZoneFormatLock);
    485 }
    486 
    487 const TimeZoneNames*
    488 TimeZoneFormatDelegate::getTimeZoneNames() const {
    489     return fTZfmtCacheEntry->tzfmt->getTimeZoneNames();
    490 }
    491 
    492 UnicodeString&
    493 TimeZoneFormatDelegate::format(UTimeZoneFormatStyle style, const TimeZone& tz, UDate date,
    494         UnicodeString& name, UTimeZoneTimeType* timeType /* = NULL */) const {
    495     return fTZfmtCacheEntry->tzfmt->format(style, tz, date, name, timeType);
    496 }
    497 
    498 UnicodeString&
    499 TimeZoneFormatDelegate::parse(UTimeZoneFormatStyle style, const UnicodeString& text, ParsePosition& pos,
    500         UnicodeString& tzID, UTimeZoneTimeType* timeType /* = NULL */) const {
    501     return fTZfmtCacheEntry->tzfmt->parse(style, text, pos, tzID, timeType);
    502 }
    503 
    504 
    505 // ---------------------------------------------------
    506 // TimeZoneFormat base class
    507 // ---------------------------------------------------
    508 TimeZoneFormat::~TimeZoneFormat() {
    509 }
    510 
    511 TimeZone*
    512 TimeZoneFormat::parse(UTimeZoneFormatStyle style, const UnicodeString& text, ParsePosition& pos,
    513         UTimeZoneTimeType* timeType /*= NULL*/) const {
    514     UnicodeString tzID;
    515     parse(style, text, pos, tzID, timeType);
    516     if (pos.getErrorIndex() < 0) {
    517         return TimeZone::createTimeZone(tzID);
    518     }
    519     return NULL;
    520 }
    521 
    522 TimeZoneFormat* U_EXPORT2
    523 TimeZoneFormat::createInstance(const Locale& locale, UErrorCode& status) {
    524     TimeZoneFormat* tzfmt = new TimeZoneFormatDelegate(locale, status);
    525     if (U_SUCCESS(status) && tzfmt == NULL) {
    526         status = U_MEMORY_ALLOCATION_ERROR;
    527     }
    528     return tzfmt;
    529 }
    530 
    531 
    532 U_NAMESPACE_END
    533 
    534 #endif
    535