• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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