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
TimeZoneFormatImpl(const Locale & locale,UErrorCode & status)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
~TimeZoneFormatImpl()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*
getTimeZoneNames() const92 TimeZoneFormatImpl::getTimeZoneNames() const {
93 return fTimeZoneNames;
94 }
95
96 const TimeZoneGenericNames*
getTimeZoneGenericNames(UErrorCode & status) const97 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&
format(UTimeZoneFormatStyle style,const TimeZone & tz,UDate date,UnicodeString & name,UTimeZoneTimeType * timeType) const122 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&
parse(UTimeZoneFormatStyle style,const UnicodeString & text,ParsePosition & pos,UnicodeString & tzID,UTimeZoneTimeType * timeType) const151 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&
formatGeneric(const TimeZone & tz,UTimeZoneGenericNameType genType,UDate date,UnicodeString & name) const253 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&
formatSpecific(const TimeZone & tz,UTimeZoneNameType stdType,UTimeZoneNameType dstType,UDate date,UnicodeString & name,UTimeZoneTimeType * timeType) const273 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 */
timeZoneFormat_cleanup(void)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
deleteTimeZoneFormatCacheEntry(void * obj)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 */
sweepCache()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
TimeZoneFormatDelegate(const Locale & locale,UErrorCode & status)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
~TimeZoneFormatDelegate()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*
getTimeZoneNames() const488 TimeZoneFormatDelegate::getTimeZoneNames() const {
489 return fTZfmtCacheEntry->tzfmt->getTimeZoneNames();
490 }
491
492 UnicodeString&
format(UTimeZoneFormatStyle style,const TimeZone & tz,UDate date,UnicodeString & name,UTimeZoneTimeType * timeType) const493 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&
parse(UTimeZoneFormatStyle style,const UnicodeString & text,ParsePosition & pos,UnicodeString & tzID,UTimeZoneTimeType * timeType) const499 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 // ---------------------------------------------------
~TimeZoneFormat()508 TimeZoneFormat::~TimeZoneFormat() {
509 }
510
511 TimeZone*
parse(UTimeZoneFormatStyle style,const UnicodeString & text,ParsePosition & pos,UTimeZoneTimeType * timeType) const512 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
createInstance(const Locale & locale,UErrorCode & status)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