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
23U_NAMESPACE_BEGIN
24
25// ---------------------------------------------------
26// TimeZoneFormatImpl - the TimeZoneFormat implementation
27// ---------------------------------------------------
28class TimeZoneFormatImpl : public TimeZoneFormat {
29public:
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
41private:
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
56TimeZoneFormatImpl::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
81TimeZoneFormatImpl::~TimeZoneFormatImpl() {
82    if (fTimeZoneNames != NULL) {
83        delete fTimeZoneNames;
84    }
85    if (fTimeZoneGenericNames != NULL) {
86        delete fTimeZoneGenericNames;
87    }
88    umtx_destroy(&fLock);
89}
90
91const TimeZoneNames*
92TimeZoneFormatImpl::getTimeZoneNames() const {
93    return fTimeZoneNames;
94}
95
96const TimeZoneGenericNames*
97TimeZoneFormatImpl::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
121UnicodeString&
122TimeZoneFormatImpl::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
150UnicodeString&
151TimeZoneFormatImpl::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
252UnicodeString&
253TimeZoneFormatImpl::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
272UnicodeString&
273TimeZoneFormatImpl::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
303static UMTX gTimeZoneFormatLock = NULL;
304static UHashtable *gTimeZoneFormatCache = NULL;
305static UBool gTimeZoneFormatCacheInitialized = FALSE;
306
307// Access count - incremented every time up to SWEEP_INTERVAL,
308// then reset to 0
309static 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
320typedef struct TimeZoneFormatCacheEntry {
321    TimeZoneFormat* tzfmt;
322    int32_t         refCount;
323    double          lastAccess;
324} TimeZoneNameFormatCacheEntry;
325
326U_CDECL_BEGIN
327/**
328 * Cleanup callback func
329 */
330static 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 */
345static void U_CALLCONV
346deleteTimeZoneFormatCacheEntry(void *obj) {
347    TimeZoneNameFormatCacheEntry *entry = (TimeZoneNameFormatCacheEntry *)obj;
348    delete (TimeZoneFormat *) entry->tzfmt;
349    uprv_free((void *)entry);
350}
351U_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 */
358static 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// ---------------------------------------------------
377class TimeZoneFormatDelegate : public TimeZoneFormat {
378public:
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
390private:
391    TimeZoneFormatCacheEntry* fTZfmtCacheEntry;
392};
393
394TimeZoneFormatDelegate::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
477TimeZoneFormatDelegate::~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
487const TimeZoneNames*
488TimeZoneFormatDelegate::getTimeZoneNames() const {
489    return fTZfmtCacheEntry->tzfmt->getTimeZoneNames();
490}
491
492UnicodeString&
493TimeZoneFormatDelegate::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
498UnicodeString&
499TimeZoneFormatDelegate::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// ---------------------------------------------------
508TimeZoneFormat::~TimeZoneFormat() {
509}
510
511TimeZone*
512TimeZoneFormat::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
522TimeZoneFormat* U_EXPORT2
523TimeZoneFormat::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
532U_NAMESPACE_END
533
534#endif
535