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