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 "tzgnames.h"
13
14#include "unicode/basictz.h"
15#include "unicode/locdspnm.h"
16#include "unicode/msgfmt.h"
17#include "unicode/rbtz.h"
18#include "unicode/simpletz.h"
19#include "unicode/vtzone.h"
20
21#include "cmemory.h"
22#include "cstring.h"
23#include "uhash.h"
24#include "uassert.h"
25#include "umutex.h"
26#include "uresimp.h"
27#include "ureslocs.h"
28#include "zonemeta.h"
29#include "tznames_impl.h"
30#include "olsontz.h"
31
32U_NAMESPACE_BEGIN
33
34#define ZID_KEY_MAX  128
35
36static const char gZoneStrings[]                = "zoneStrings";
37
38static const char gRegionFormatTag[]            = "regionFormat";
39static const char gFallbackRegionFormatTag[]    = "fallbackRegionFormat";
40static const char gFallbackFormatTag[]          = "fallbackFormat";
41
42static const UChar gEmpty[]                     = {0x00};
43
44static const UChar gDefRegionPattern[]          = {0x7B, 0x30, 0x7D, 0x00}; // "{0}"
45static const UChar gDefFallbackRegionPattern[]  = {0x7B, 0x31, 0x7D, 0x20, 0x28, 0x7B, 0x30, 0x7D, 0x29, 0x00}; // "{1} ({0})"
46static const UChar gDefFallbackPattern[]        = {0x7B, 0x31, 0x7D, 0x20, 0x28, 0x7B, 0x30, 0x7D, 0x29, 0x00}; // "{1} ({0})"
47
48static const double kDstCheckRange      = (double)184*U_MILLIS_PER_DAY;
49
50
51
52U_CDECL_BEGIN
53
54typedef struct PartialLocationKey {
55    const UChar* tzID;
56    const UChar* mzID;
57    UBool isLong;
58} PartialLocationKey;
59
60/**
61 * Hash function for partial location name hash key
62 */
63static int32_t U_CALLCONV
64hashPartialLocationKey(const UHashTok key) {
65    // <tzID>&<mzID>#[L|S]
66    PartialLocationKey *p = (PartialLocationKey *)key.pointer;
67    UnicodeString str(p->tzID);
68    str.append((UChar)0x26)
69        .append(p->mzID)
70        .append((UChar)0x23)
71        .append((UChar)(p->isLong ? 0x4C : 0x53));
72    return uhash_hashUCharsN(str.getBuffer(), str.length());
73}
74
75/**
76 * Comparer for partial location name hash key
77 */
78static UBool U_CALLCONV
79comparePartialLocationKey(const UHashTok key1, const UHashTok key2) {
80    PartialLocationKey *p1 = (PartialLocationKey *)key1.pointer;
81    PartialLocationKey *p2 = (PartialLocationKey *)key2.pointer;
82
83    if (p1 == p2) {
84        return TRUE;
85    }
86    if (p1 == NULL || p2 == NULL) {
87        return FALSE;
88    }
89    // We just check identity of tzID/mzID
90    return (p1->tzID == p2->tzID && p1->mzID == p2->mzID && p1->isLong == p2->isLong);
91}
92
93/**
94 * Deleter for GNameInfo
95 */
96static void U_CALLCONV
97deleteGNameInfo(void *obj) {
98    uprv_free(obj);
99}
100
101/**
102 * GNameInfo stores zone name information in the local trie
103 */
104typedef struct GNameInfo {
105    UTimeZoneGenericNameType    type;
106    const UChar*                tzID;
107} ZNameInfo;
108
109/**
110 * GMatchInfo stores zone name match information used by find method
111 */
112typedef struct GMatchInfo {
113    const GNameInfo*    gnameInfo;
114    int32_t             matchLength;
115    UTimeZoneTimeType   timeType;
116} ZMatchInfo;
117
118U_CDECL_END
119
120// ---------------------------------------------------
121// The class stores time zone generic name match information
122// ---------------------------------------------------
123TimeZoneGenericNameMatchInfo::TimeZoneGenericNameMatchInfo(UVector* matches)
124: fMatches(matches) {
125}
126
127TimeZoneGenericNameMatchInfo::~TimeZoneGenericNameMatchInfo() {
128    if (fMatches != NULL) {
129        delete fMatches;
130    }
131}
132
133int32_t
134TimeZoneGenericNameMatchInfo::size() const {
135    if (fMatches == NULL) {
136        return 0;
137    }
138    return fMatches->size();
139}
140
141UTimeZoneGenericNameType
142TimeZoneGenericNameMatchInfo::getGenericNameType(int32_t index) const {
143    GMatchInfo *minfo = (GMatchInfo *)fMatches->elementAt(index);
144    if (minfo != NULL) {
145        return static_cast<UTimeZoneGenericNameType>(minfo->gnameInfo->type);
146    }
147    return UTZGNM_UNKNOWN;
148}
149
150int32_t
151TimeZoneGenericNameMatchInfo::getMatchLength(int32_t index) const {
152    ZMatchInfo *minfo = (ZMatchInfo *)fMatches->elementAt(index);
153    if (minfo != NULL) {
154        return minfo->matchLength;
155    }
156    return -1;
157}
158
159UnicodeString&
160TimeZoneGenericNameMatchInfo::getTimeZoneID(int32_t index, UnicodeString& tzID) const {
161    GMatchInfo *minfo = (GMatchInfo *)fMatches->elementAt(index);
162    if (minfo != NULL && minfo->gnameInfo->tzID != NULL) {
163        tzID.setTo(TRUE, minfo->gnameInfo->tzID, -1);
164    } else {
165        tzID.setToBogus();
166    }
167    return tzID;
168}
169
170// ---------------------------------------------------
171// GNameSearchHandler
172// ---------------------------------------------------
173class GNameSearchHandler : public TextTrieMapSearchResultHandler {
174public:
175    GNameSearchHandler(uint32_t types);
176    virtual ~GNameSearchHandler();
177
178    UBool handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status);
179    UVector* getMatches(int32_t& maxMatchLen);
180
181private:
182    uint32_t fTypes;
183    UVector* fResults;
184    int32_t fMaxMatchLen;
185};
186
187GNameSearchHandler::GNameSearchHandler(uint32_t types)
188: fTypes(types), fResults(NULL), fMaxMatchLen(0) {
189}
190
191GNameSearchHandler::~GNameSearchHandler() {
192    if (fResults != NULL) {
193        delete fResults;
194    }
195}
196
197UBool
198GNameSearchHandler::handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status) {
199    if (U_FAILURE(status)) {
200        return FALSE;
201    }
202    if (node->hasValues()) {
203        int32_t valuesCount = node->countValues();
204        for (int32_t i = 0; i < valuesCount; i++) {
205            GNameInfo *nameinfo = (ZNameInfo *)node->getValue(i);
206            if (nameinfo == NULL) {
207                break;
208            }
209            if ((nameinfo->type & fTypes) != 0) {
210                // matches a requested type
211                if (fResults == NULL) {
212                    fResults = new UVector(uhash_freeBlock, NULL, status);
213                    if (fResults == NULL) {
214                        status = U_MEMORY_ALLOCATION_ERROR;
215                    }
216                }
217                if (U_SUCCESS(status)) {
218                    GMatchInfo *gmatch = (GMatchInfo *)uprv_malloc(sizeof(GMatchInfo));
219                    if (gmatch == NULL) {
220                        status = U_MEMORY_ALLOCATION_ERROR;
221                    } else {
222                        // add the match to the vector
223                        gmatch->gnameInfo = nameinfo;
224                        gmatch->matchLength = matchLength;
225                        gmatch->timeType = UTZFMT_TIME_TYPE_UNKNOWN;
226                        fResults->addElement(gmatch, status);
227                        if (U_FAILURE(status)) {
228                            uprv_free(gmatch);
229                        } else {
230                            if (matchLength > fMaxMatchLen) {
231                                fMaxMatchLen = matchLength;
232                            }
233                        }
234                    }
235                }
236            }
237        }
238    }
239    return TRUE;
240}
241
242UVector*
243GNameSearchHandler::getMatches(int32_t& maxMatchLen) {
244    // give the ownership to the caller
245    UVector *results = fResults;
246    maxMatchLen = fMaxMatchLen;
247
248    // reset
249    fResults = NULL;
250    fMaxMatchLen = 0;
251    return results;
252}
253
254// ---------------------------------------------------
255// TimeZoneGenericNames
256//
257// TimeZoneGenericNames is parallel to TimeZoneNames,
258// but handles run-time generated time zone names.
259// This is the main part of this module.
260// ---------------------------------------------------
261TimeZoneGenericNames::TimeZoneGenericNames(const Locale& locale, UErrorCode& status)
262: fLocale(locale),
263  fLock(NULL),
264  fTimeZoneNames(NULL),
265  fLocationNamesMap(NULL),
266  fPartialLocationNamesMap(NULL),
267  fRegionFormat(NULL),
268  fFallbackRegionFormat(NULL),
269  fFallbackFormat(NULL),
270  fLocaleDisplayNames(NULL),
271  fStringPool(status),
272  fGNamesTrie(TRUE, deleteGNameInfo),
273  fGNamesTrieFullyLoaded(FALSE) {
274    initialize(locale, status);
275}
276
277TimeZoneGenericNames::~TimeZoneGenericNames() {
278    cleanup();
279    umtx_destroy(&fLock);
280}
281
282void
283TimeZoneGenericNames::initialize(const Locale& locale, UErrorCode& status) {
284    if (U_FAILURE(status)) {
285        return;
286    }
287
288    // TimeZoneNames
289    fTimeZoneNames = TimeZoneNames::createInstance(locale, status);
290    if (U_FAILURE(status)) {
291        return;
292    }
293
294    // Initialize format patterns
295    UnicodeString rpat(TRUE, gDefRegionPattern, -1);
296    UnicodeString frpat(TRUE, gDefFallbackRegionPattern, -1);
297    UnicodeString fpat(TRUE, gDefFallbackPattern, -1);
298
299    UErrorCode tmpsts = U_ZERO_ERROR;   // OK with fallback warning..
300    UResourceBundle *zoneStrings = ures_open(U_ICUDATA_ZONE, locale.getName(), &tmpsts);
301    zoneStrings = ures_getByKeyWithFallback(zoneStrings, gZoneStrings, zoneStrings, &tmpsts);
302
303    if (U_SUCCESS(tmpsts)) {
304        const UChar *regionPattern = ures_getStringByKeyWithFallback(zoneStrings, gRegionFormatTag, NULL, &tmpsts);
305        if (U_SUCCESS(tmpsts) && u_strlen(regionPattern) > 0) {
306            rpat.setTo(regionPattern);
307        }
308        tmpsts = U_ZERO_ERROR;
309        const UChar *fallbackRegionPattern = ures_getStringByKeyWithFallback(zoneStrings, gFallbackRegionFormatTag, NULL, &tmpsts);
310        if (U_SUCCESS(tmpsts) && u_strlen(fallbackRegionPattern) > 0) {
311            frpat.setTo(fallbackRegionPattern);
312        }
313        tmpsts = U_ZERO_ERROR;
314        const UChar *fallbackPattern = ures_getStringByKeyWithFallback(zoneStrings, gFallbackFormatTag, NULL, &tmpsts);
315        if (U_SUCCESS(tmpsts) && u_strlen(fallbackPattern) > 0) {
316            fpat.setTo(fallbackPattern);
317        }
318    }
319    ures_close(zoneStrings);
320
321    fRegionFormat = new MessageFormat(rpat, status);
322    if (fRegionFormat == NULL) {
323        status = U_MEMORY_ALLOCATION_ERROR;
324    }
325    fFallbackRegionFormat = new MessageFormat(frpat, status);
326    if (fFallbackRegionFormat == NULL) {
327        status = U_MEMORY_ALLOCATION_ERROR;
328    }
329    fFallbackFormat = new MessageFormat(fpat, status);
330    if (fFallbackFormat == NULL) {
331        status = U_MEMORY_ALLOCATION_ERROR;
332    }
333    if (U_FAILURE(status)) {
334        cleanup();
335        return;
336    }
337
338    // locale display names
339    fLocaleDisplayNames = LocaleDisplayNames::createInstance(locale);
340
341    // hash table for names - no key/value deleters
342    fLocationNamesMap = uhash_open(uhash_hashUChars, uhash_compareUChars, NULL, &status);
343    if (U_FAILURE(status)) {
344        cleanup();
345        return;
346    }
347
348    fPartialLocationNamesMap = uhash_open(hashPartialLocationKey, comparePartialLocationKey, NULL, &status);
349    if (U_FAILURE(status)) {
350        cleanup();
351        return;
352    }
353    uhash_setKeyDeleter(fPartialLocationNamesMap, uhash_freeBlock);
354    // no value deleter
355
356    // target region
357    const char* region = fLocale.getCountry();
358    int32_t regionLen = uprv_strlen(region);
359    if (regionLen == 0) {
360        char loc[ULOC_FULLNAME_CAPACITY];
361        uloc_addLikelySubtags(fLocale.getName(), loc, sizeof(loc), &status);
362
363        regionLen = uloc_getCountry(loc, fTargetRegion, sizeof(fTargetRegion), &status);
364        if (U_SUCCESS(status)) {
365            fTargetRegion[regionLen] = 0;
366        } else {
367            cleanup();
368            return;
369        }
370    } else if (regionLen < (int32_t)sizeof(fTargetRegion)) {
371        uprv_strcpy(fTargetRegion, region);
372    } else {
373        fTargetRegion[0] = 0;
374    }
375
376    // preload generic names for the default zone
377    TimeZone *tz = TimeZone::createDefault();
378    const UChar *tzID = ZoneMeta::getCanonicalCLDRID(*tz);
379    if (tzID != NULL) {
380        loadStrings(UnicodeString(tzID));
381    }
382    delete tz;
383}
384
385void
386TimeZoneGenericNames::cleanup() {
387    if (fRegionFormat != NULL) {
388        delete fRegionFormat;
389    }
390    if (fFallbackRegionFormat != NULL) {
391        delete fFallbackRegionFormat;
392    }
393    if (fFallbackFormat != NULL) {
394        delete fFallbackFormat;
395    }
396    if (fLocaleDisplayNames != NULL) {
397        delete fLocaleDisplayNames;
398    }
399    if (fTimeZoneNames != NULL) {
400        delete fTimeZoneNames;
401    }
402
403    uhash_close(fLocationNamesMap);
404    uhash_close(fPartialLocationNamesMap);
405}
406
407UnicodeString&
408TimeZoneGenericNames::getDisplayName(const TimeZone& tz, UTimeZoneGenericNameType type, UDate date, UnicodeString& name) const {
409    name.setToBogus();
410    switch (type) {
411    case UTZGNM_LOCATION:
412        {
413            const UChar* tzCanonicalID = ZoneMeta::getCanonicalCLDRID(tz);
414            if (tzCanonicalID != NULL) {
415                getGenericLocationName(UnicodeString(tzCanonicalID), name);
416            }
417        }
418        break;
419    case UTZGNM_LONG:
420    case UTZGNM_SHORT:
421        formatGenericNonLocationName(tz, type, date, name);
422        if (name.isEmpty()) {
423            const UChar* tzCanonicalID = ZoneMeta::getCanonicalCLDRID(tz);
424            if (tzCanonicalID != NULL) {
425                getGenericLocationName(UnicodeString(tzCanonicalID), name);
426            }
427        }
428        break;
429    default:
430        break;
431    }
432    return name;
433}
434
435UnicodeString&
436TimeZoneGenericNames::getGenericLocationName(const UnicodeString& tzCanonicalID, UnicodeString& name) const {
437    if (tzCanonicalID.isEmpty()) {
438        name.setToBogus();
439        return name;
440    }
441
442    const UChar *locname = NULL;
443    TimeZoneGenericNames *nonConstThis = const_cast<TimeZoneGenericNames *>(this);
444    umtx_lock(&nonConstThis->fLock);
445    {
446        locname = nonConstThis->getGenericLocationName(tzCanonicalID);
447    }
448    umtx_unlock(&nonConstThis->fLock);
449
450    if (locname == NULL) {
451        name.setToBogus();
452    } else {
453        name.setTo(TRUE, locname, -1);
454    }
455
456    return name;
457}
458
459/*
460 * This method updates the cache and must be called with a lock
461 */
462const UChar*
463TimeZoneGenericNames::getGenericLocationName(const UnicodeString& tzCanonicalID) {
464    U_ASSERT(!tzCanonicalID.isEmpty());
465    if (tzCanonicalID.length() > ZID_KEY_MAX) {
466        return NULL;
467    }
468
469    UErrorCode status = U_ZERO_ERROR;
470    UChar tzIDKey[ZID_KEY_MAX + 1];
471    int32_t tzIDKeyLen = tzCanonicalID.extract(tzIDKey, ZID_KEY_MAX + 1, status);
472    U_ASSERT(status == U_ZERO_ERROR);   // already checked length above
473    tzIDKey[tzIDKeyLen] = 0;
474
475    const UChar *locname = (const UChar *)uhash_get(fLocationNamesMap, tzIDKey);
476
477    if (locname != NULL) {
478        // gEmpty indicate the name is not available
479        if (locname == gEmpty) {
480            return NULL;
481        }
482        return locname;
483    }
484
485    // Construct location name
486    UnicodeString name;
487    UBool isSingleCountry = FALSE;
488    UnicodeString usCountryCode;
489    ZoneMeta::getSingleCountry(tzCanonicalID, usCountryCode);
490    if (!usCountryCode.isEmpty()) {
491        isSingleCountry = TRUE;
492    } else {
493        ZoneMeta::getCanonicalCountry(tzCanonicalID, usCountryCode);
494    }
495
496    if (!usCountryCode.isEmpty()) {
497        char countryCode[ULOC_COUNTRY_CAPACITY];
498        U_ASSERT(usCountryCode.length() < ULOC_COUNTRY_CAPACITY);
499        int32_t ccLen = usCountryCode.extract(0, usCountryCode.length(), countryCode, sizeof(countryCode), US_INV);
500        countryCode[ccLen] = 0;
501
502        UnicodeString country;
503        fLocaleDisplayNames->regionDisplayName(countryCode, country);
504
505        // Format
506        FieldPosition fpos;
507        if (isSingleCountry) {
508            // If the zone is only one zone in the country, do not add city
509            Formattable param[] = {
510                Formattable(country)
511            };
512            fRegionFormat->format(param, 1, name, fpos, status);
513        } else {
514            // getExemplarLocationName should retur non-empty string
515            // if the time zone is associated with a region
516            UnicodeString city;
517            fTimeZoneNames->getExemplarLocationName(tzCanonicalID, city);
518
519            Formattable params[] = {
520                Formattable(city),
521                Formattable(country)
522            };
523            fFallbackRegionFormat->format(params, 2, name, fpos, status);
524        }
525        if (U_FAILURE(status)) {
526            return NULL;
527        }
528    }
529
530    locname = name.isEmpty() ? NULL : fStringPool.get(name, status);
531    if (U_SUCCESS(status)) {
532        // Cache the result
533        const UChar* cacheID = ZoneMeta::findTimeZoneID(tzCanonicalID);
534        U_ASSERT(cacheID != NULL);
535        if (locname == NULL) {
536            // gEmpty to indicate - no location name available
537            uhash_put(fLocationNamesMap, (void *)cacheID, (void *)gEmpty, &status);
538        } else {
539            uhash_put(fLocationNamesMap, (void *)cacheID, (void *)locname, &status);
540            if (U_FAILURE(status)) {
541                locname = NULL;
542            } else {
543                // put the name info into the trie
544                GNameInfo *nameinfo = (ZNameInfo *)uprv_malloc(sizeof(GNameInfo));
545                if (nameinfo != NULL) {
546                    nameinfo->type = UTZGNM_LOCATION;
547                    nameinfo->tzID = cacheID;
548                    fGNamesTrie.put(locname, nameinfo, status);
549                }
550            }
551        }
552    }
553
554    return locname;
555}
556
557UnicodeString&
558TimeZoneGenericNames::formatGenericNonLocationName(const TimeZone& tz, UTimeZoneGenericNameType type, UDate date, UnicodeString& name) const {
559    U_ASSERT(type == UTZGNM_LONG || type == UTZGNM_SHORT);
560    name.setToBogus();
561
562    const UChar* uID = ZoneMeta::getCanonicalCLDRID(tz);
563    if (uID == NULL) {
564        return name;
565    }
566
567    UnicodeString tzID(uID);
568
569    // Try to get a name from time zone first
570    UTimeZoneNameType nameType = (type == UTZGNM_LONG) ? UTZNM_LONG_GENERIC : UTZNM_SHORT_GENERIC;
571    fTimeZoneNames->getTimeZoneDisplayName(tzID, nameType, name);
572
573    if (!name.isEmpty()) {
574        return name;
575    }
576
577    // Try meta zone
578    UnicodeString mzID;
579    fTimeZoneNames->getMetaZoneID(tzID, date, mzID);
580    if (!mzID.isEmpty()) {
581        UErrorCode status = U_ZERO_ERROR;
582        UBool useStandard = FALSE;
583        int32_t raw, sav;
584
585        tz.getOffset(date, FALSE, raw, sav, status);
586        if (U_FAILURE(status)) {
587            return name;
588        }
589
590        if (sav == 0) {
591            useStandard = TRUE;
592
593            TimeZone *tmptz = tz.clone();
594            // Check if the zone actually uses daylight saving time around the time
595            BasicTimeZone *btz = NULL;
596            if (dynamic_cast<OlsonTimeZone *>(tmptz) != NULL
597                || dynamic_cast<SimpleTimeZone *>(tmptz) != NULL
598                || dynamic_cast<RuleBasedTimeZone *>(tmptz) != NULL
599                || dynamic_cast<VTimeZone *>(tmptz) != NULL) {
600                btz = (BasicTimeZone*)tmptz;
601            }
602
603            if (btz != NULL) {
604                TimeZoneTransition before;
605                UBool beforTrs = btz->getPreviousTransition(date, TRUE, before);
606                if (beforTrs
607                        && (date - before.getTime() < kDstCheckRange)
608                        && before.getFrom()->getDSTSavings() != 0) {
609                    useStandard = FALSE;
610                } else {
611                    TimeZoneTransition after;
612                    UBool afterTrs = btz->getNextTransition(date, FALSE, after);
613                    if (afterTrs
614                            && (after.getTime() - date < kDstCheckRange)
615                            && after.getTo()->getDSTSavings() != 0) {
616                        useStandard = FALSE;
617                    }
618                }
619            } else {
620                // If not BasicTimeZone... only if the instance is not an ICU's implementation.
621                // We may get a wrong answer in edge case, but it should practically work OK.
622                tmptz->getOffset(date - kDstCheckRange, FALSE, raw, sav, status);
623                if (sav != 0) {
624                    useStandard = FALSE;
625                } else {
626                    tmptz->getOffset(date + kDstCheckRange, FALSE, raw, sav, status);
627                    if (sav != 0){
628                        useStandard = FALSE;
629                    }
630                }
631                if (U_FAILURE(status)) {
632                    delete tmptz;
633                    return name;
634                }
635            }
636            delete tmptz;
637        }
638        if (useStandard) {
639            UTimeZoneNameType stdNameType = (nameType == UTZNM_LONG_GENERIC)
640                ? UTZNM_LONG_STANDARD : UTZNM_SHORT_STANDARD_COMMONLY_USED;
641            UnicodeString stdName;
642            fTimeZoneNames->getDisplayName(tzID, stdNameType, date, stdName);
643            if (!stdName.isEmpty()) {
644                name.setTo(stdName);
645
646                // TODO: revisit this issue later
647                // In CLDR, a same display name is used for both generic and standard
648                // for some meta zones in some locales.  This looks like a data bugs.
649                // For now, we check if the standard name is different from its generic
650                // name below.
651                UnicodeString mzGenericName;
652                fTimeZoneNames->getMetaZoneDisplayName(mzID, nameType, mzGenericName);
653                if (stdName.caseCompare(mzGenericName, 0) == 0) {
654                    name.setToBogus();
655                }
656            }
657        }
658        if (name.isEmpty()) {
659            // Get a name from meta zone
660            UnicodeString mzName;
661            fTimeZoneNames->getMetaZoneDisplayName(mzID, nameType, mzName);
662            if (!mzName.isEmpty()) {
663                // Check if we need to use a partial location format.
664                // This check is done by comparing offset with the meta zone's
665                // golden zone at the given date.
666                UnicodeString goldenID;
667                fTimeZoneNames->getReferenceZoneID(mzID, fTargetRegion, goldenID);
668                if (!goldenID.isEmpty() && goldenID != tzID) {
669                    TimeZone *goldenZone = TimeZone::createTimeZone(goldenID);
670                    int32_t raw1, sav1;
671
672                    // Check offset in the golden zone with wall time.
673                    // With getOffset(date, false, offsets1),
674                    // you may get incorrect results because of time overlap at DST->STD
675                    // transition.
676                    goldenZone->getOffset(date + raw + sav, TRUE, raw1, sav1, status);
677                    delete goldenZone;
678                    if (U_SUCCESS(status)) {
679                        if (raw != raw1 || sav != sav1) {
680                            // Now we need to use a partial location format
681                            getPartialLocationName(tzID, mzID, (nameType == UTZNM_LONG_GENERIC), mzName, name);
682                        } else {
683                            name.setTo(mzName);
684                        }
685                    }
686                } else {
687                    name.setTo(mzName);
688                }
689            }
690        }
691    }
692    return name;
693}
694
695UnicodeString&
696TimeZoneGenericNames::getPartialLocationName(const UnicodeString& tzCanonicalID,
697                        const UnicodeString& mzID, UBool isLong, const UnicodeString& mzDisplayName,
698                        UnicodeString& name) const {
699    name.setToBogus();
700    if (tzCanonicalID.isEmpty() || mzID.isEmpty() || mzDisplayName.isEmpty()) {
701        return name;
702    }
703
704    const UChar *uplname = NULL;
705    TimeZoneGenericNames *nonConstThis = const_cast<TimeZoneGenericNames *>(this);
706    umtx_lock(&nonConstThis->fLock);
707    {
708        uplname = nonConstThis->getPartialLocationName(tzCanonicalID, mzID, isLong, mzDisplayName);
709    }
710    umtx_unlock(&nonConstThis->fLock);
711
712    if (uplname == NULL) {
713        name.setToBogus();
714    } else {
715        name.setTo(TRUE, uplname, -1);
716    }
717    return name;
718}
719
720/*
721 * This method updates the cache and must be called with a lock
722 */
723const UChar*
724TimeZoneGenericNames::getPartialLocationName(const UnicodeString& tzCanonicalID,
725                        const UnicodeString& mzID, UBool isLong, const UnicodeString& mzDisplayName) {
726    U_ASSERT(!tzCanonicalID.isEmpty());
727    U_ASSERT(!mzID.isEmpty());
728    U_ASSERT(!mzDisplayName.isEmpty());
729
730    PartialLocationKey key;
731    key.tzID = ZoneMeta::findTimeZoneID(tzCanonicalID);
732    key.mzID = ZoneMeta::findMetaZoneID(mzID);
733    key.isLong = isLong;
734    U_ASSERT(key.tzID != NULL && key.mzID != NULL);
735
736    const UChar* uplname = (const UChar*)uhash_get(fPartialLocationNamesMap, (void *)&key);
737    if (uplname != NULL) {
738        return uplname;
739    }
740
741    UnicodeString location;
742    UnicodeString usCountryCode;
743    ZoneMeta::getCanonicalCountry(tzCanonicalID, usCountryCode);
744    if (!usCountryCode.isEmpty()) {
745        char countryCode[ULOC_COUNTRY_CAPACITY];
746        U_ASSERT(usCountryCode.length() < ULOC_COUNTRY_CAPACITY);
747        int32_t ccLen = usCountryCode.extract(0, usCountryCode.length(), countryCode, sizeof(countryCode), US_INV);
748        countryCode[ccLen] = 0;
749
750        UnicodeString regionalGolden;
751        fTimeZoneNames->getReferenceZoneID(mzID, countryCode, regionalGolden);
752        if (tzCanonicalID == regionalGolden) {
753            // Use country name
754            fLocaleDisplayNames->regionDisplayName(countryCode, location);
755        } else {
756            // Otherwise, use exemplar city name
757            fTimeZoneNames->getExemplarLocationName(tzCanonicalID, location);
758        }
759    } else {
760        fTimeZoneNames->getExemplarLocationName(tzCanonicalID, location);
761        if (location.isEmpty()) {
762            // This could happen when the time zone is not associated with a country,
763            // and its ID is not hierarchical, for example, CST6CDT.
764            // We use the canonical ID itself as the location for this case.
765            location.setTo(tzCanonicalID);
766        }
767    }
768
769    UErrorCode status = U_ZERO_ERROR;
770    UnicodeString name;
771
772    FieldPosition fpos;
773    Formattable param[] = {
774        Formattable(location),
775        Formattable(mzDisplayName)
776    };
777    fFallbackFormat->format(param, 2, name, fpos, status);
778    if (U_FAILURE(status)) {
779        return NULL;
780    }
781
782    uplname = fStringPool.get(name, status);
783    if (U_SUCCESS(status)) {
784        // Add the name to cache
785        PartialLocationKey* cacheKey = (PartialLocationKey *)uprv_malloc(sizeof(PartialLocationKey));
786        if (cacheKey != NULL) {
787            cacheKey->tzID = key.tzID;
788            cacheKey->mzID = key.mzID;
789            cacheKey->isLong = key.isLong;
790            uhash_put(fPartialLocationNamesMap, (void *)cacheKey, (void *)uplname, &status);
791            if (U_FAILURE(status)) {
792                uprv_free(cacheKey);
793            } else {
794                // put the name to the local trie as well
795                GNameInfo *nameinfo = (ZNameInfo *)uprv_malloc(sizeof(GNameInfo));
796                if (nameinfo != NULL) {
797                    nameinfo->type = isLong ? UTZGNM_LONG : UTZGNM_SHORT;
798                    nameinfo->tzID = key.tzID;
799                    fGNamesTrie.put(uplname, nameinfo, status);
800                }
801            }
802        }
803    }
804    return uplname;
805}
806
807/*
808 * This method updates the cache and must be called with a lock,
809 * except initializer.
810 */
811void
812TimeZoneGenericNames::loadStrings(const UnicodeString& tzCanonicalID) {
813    // load the generic location name
814    getGenericLocationName(tzCanonicalID);
815
816    // partial location names
817    UErrorCode status = U_ZERO_ERROR;
818
819    const UnicodeString *mzID;
820    UnicodeString goldenID;
821    UnicodeString mzGenName;
822    UTimeZoneNameType genNonLocTypes[] = {
823        UTZNM_LONG_GENERIC, UTZNM_SHORT_GENERIC,
824        UTZNM_UNKNOWN /*terminator*/
825    };
826
827    StringEnumeration *mzIDs = fTimeZoneNames->getAvailableMetaZoneIDs(tzCanonicalID, status);
828    while ((mzID = mzIDs->snext(status))) {
829        if (U_FAILURE(status)) {
830            break;
831        }
832        // if this time zone is not the golden zone of the meta zone,
833        // partial location name (such as "PT (Los Angeles)") might be
834        // available.
835        fTimeZoneNames->getReferenceZoneID(*mzID, fTargetRegion, goldenID);
836        if (tzCanonicalID != goldenID) {
837            for (int32_t i = 0; genNonLocTypes[i] != UTZNM_UNKNOWN; i++) {
838                fTimeZoneNames->getMetaZoneDisplayName(*mzID, genNonLocTypes[i], mzGenName);
839                if (!mzGenName.isEmpty()) {
840                    // getPartialLocationName formats a name and put it into the trie
841                    getPartialLocationName(tzCanonicalID, *mzID,
842                        (genNonLocTypes[i] == UTZNM_LONG_GENERIC), mzGenName);
843                }
844            }
845        }
846    }
847    if (mzIDs != NULL) {
848        delete mzIDs;
849    }
850}
851
852int32_t
853TimeZoneGenericNames::findBestMatch(const UnicodeString& text, int32_t start, uint32_t types,
854        UnicodeString& tzID, UTimeZoneTimeType& timeType, UErrorCode& status) const {
855    timeType = UTZFMT_TIME_TYPE_UNKNOWN;
856    tzID.setToBogus();
857
858    if (U_FAILURE(status)) {
859        return 0;
860    }
861
862    // Find matches in the TimeZoneNames first
863    TimeZoneNameMatchInfo *tznamesMatches = findTimeZoneNames(text, start, types, status);
864    if (U_FAILURE(status)) {
865        return 0;
866    }
867
868    int32_t bestMatchLen = 0;
869    UTimeZoneTimeType bestMatchTimeType = UTZFMT_TIME_TYPE_UNKNOWN;
870    UnicodeString bestMatchTzID;
871    UBool isLongStandard = FALSE;  // workaround - see the comments below
872
873    if (tznamesMatches != NULL) {
874        UnicodeString mzID;
875        for (int32_t i = 0; i < tznamesMatches->size(); i++) {
876            int32_t len = tznamesMatches->getMatchLength(i);
877            if (len > bestMatchLen) {
878                bestMatchLen = len;
879                tznamesMatches->getTimeZoneID(i, bestMatchTzID);
880                if (bestMatchTzID.isEmpty()) {
881                    // name for a meta zone
882                    tznamesMatches->getMetaZoneID(i, mzID);
883                    U_ASSERT(mzID.length() > 0);
884                    fTimeZoneNames->getReferenceZoneID(mzID, fTargetRegion, bestMatchTzID);
885                }
886                UTimeZoneNameType nameType = tznamesMatches->getNameType(i);
887                switch (nameType) {
888                case UTZNM_LONG_STANDARD:
889                    isLongStandard = TRUE;
890                case UTZNM_SHORT_STANDARD_COMMONLY_USED:
891                case UTZNM_SHORT_STANDARD: // this one is never used for generic, but just in case
892                    bestMatchTimeType = UTZFMT_TIME_TYPE_STANDARD;
893                    break;
894                case UTZNM_LONG_DAYLIGHT:
895                case UTZNM_SHORT_DAYLIGHT_COMMONLY_USED:
896                case UTZNM_SHORT_DAYLIGHT: // this one is never used for generic, but just in case
897                    bestMatchTimeType = UTZFMT_TIME_TYPE_DAYLIGHT;
898                    break;
899                default:
900                    bestMatchTimeType = UTZFMT_TIME_TYPE_UNKNOWN;
901                }
902            }
903        }
904        delete tznamesMatches;
905
906        if (bestMatchLen == (text.length() - start)) {
907            // Full match
908
909            //tzID.setTo(bestMatchTzID);
910            //timeType = bestMatchTimeType;
911            //return bestMatchLen;
912
913            // TODO Some time zone uses a same name for the long standard name
914            // and the location name. When the match is a long standard name,
915            // then we need to check if the name is same with the location name.
916            // This is probably a data error or a design bug.
917            if (!isLongStandard) {
918                tzID.setTo(bestMatchTzID);
919                timeType = bestMatchTimeType;
920                return bestMatchLen;
921            }
922        }
923    }
924
925    // Find matches in the local trie
926    TimeZoneGenericNameMatchInfo *localMatches = findLocal(text, start, types, status);
927    if (U_FAILURE(status)) {
928        return 0;
929    }
930    if (localMatches != NULL) {
931        for (int32_t i = 0; i < localMatches->size(); i++) {
932            int32_t len = localMatches->getMatchLength(i);
933
934            // TODO See the above TODO. We use len >= bestMatchLen
935            // because of the long standard/location name collision
936            // problem. If it is also a location name, carrying
937            // timeType = UTZFMT_TIME_TYPE_STANDARD will cause a
938            // problem in SimpleDateFormat
939            if (len >= bestMatchLen) {
940                bestMatchLen = localMatches->getMatchLength(i);
941                bestMatchTimeType = UTZFMT_TIME_TYPE_UNKNOWN;   // because generic
942                localMatches->getTimeZoneID(i, bestMatchTzID);
943            }
944        }
945        delete localMatches;
946    }
947
948    if (bestMatchLen > 0) {
949        timeType = bestMatchTimeType;
950        tzID.setTo(bestMatchTzID);
951    }
952    return bestMatchLen;
953}
954
955TimeZoneGenericNameMatchInfo*
956TimeZoneGenericNames::findLocal(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const {
957    GNameSearchHandler handler(types);
958
959    TimeZoneGenericNames *nonConstThis = const_cast<TimeZoneGenericNames *>(this);
960
961    umtx_lock(&nonConstThis->fLock);
962    {
963        fGNamesTrie.search(text, start, (TextTrieMapSearchResultHandler *)&handler, status);
964    }
965    umtx_unlock(&nonConstThis->fLock);
966
967    if (U_FAILURE(status)) {
968        return NULL;
969    }
970
971    TimeZoneGenericNameMatchInfo *gmatchInfo = NULL;
972
973    int32_t maxLen = 0;
974    UVector *results = handler.getMatches(maxLen);
975    if (results != NULL && ((maxLen == (text.length() - start)) || fGNamesTrieFullyLoaded)) {
976        // perfect match
977        gmatchInfo = new TimeZoneGenericNameMatchInfo(results);
978        if (gmatchInfo == NULL) {
979            status = U_MEMORY_ALLOCATION_ERROR;
980            delete results;
981            return NULL;
982        }
983        return gmatchInfo;
984    }
985
986    if (results != NULL) {
987        delete results;
988    }
989
990    // All names are not yet loaded into the local trie.
991    // Load all available names into the trie. This could be very heavy.
992    umtx_lock(&nonConstThis->fLock);
993    {
994        if (!fGNamesTrieFullyLoaded) {
995            StringEnumeration *tzIDs = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, NULL, NULL, status);
996            if (U_SUCCESS(status)) {
997                const UnicodeString *tzID;
998                while ((tzID = tzIDs->snext(status))) {
999                    if (U_FAILURE(status)) {
1000                        break;
1001                    }
1002                    nonConstThis->loadStrings(*tzID);
1003                }
1004            }
1005            if (tzIDs != NULL) {
1006                delete tzIDs;
1007            }
1008
1009            if (U_SUCCESS(status)) {
1010                nonConstThis->fGNamesTrieFullyLoaded = TRUE;
1011            }
1012        }
1013    }
1014    umtx_unlock(&nonConstThis->fLock);
1015
1016    if (U_FAILURE(status)) {
1017        return NULL;
1018    }
1019
1020    umtx_lock(&nonConstThis->fLock);
1021    {
1022        // now try it again
1023        fGNamesTrie.search(text, start, (TextTrieMapSearchResultHandler *)&handler, status);
1024    }
1025    umtx_unlock(&nonConstThis->fLock);
1026
1027    results = handler.getMatches(maxLen);
1028    if (results != NULL && maxLen > 0) {
1029        gmatchInfo = new TimeZoneGenericNameMatchInfo(results);
1030        if (gmatchInfo == NULL) {
1031            status = U_MEMORY_ALLOCATION_ERROR;
1032            delete results;
1033            return NULL;
1034        }
1035    }
1036
1037    return gmatchInfo;
1038}
1039
1040TimeZoneNameMatchInfo*
1041TimeZoneGenericNames::findTimeZoneNames(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const {
1042    TimeZoneNameMatchInfo *matchInfo = NULL;
1043
1044    // Check if the target name typs is really in the TimeZoneNames
1045    uint32_t nameTypes = 0;
1046    if (types & UTZGNM_LONG) {
1047        nameTypes |= (UTZNM_LONG_GENERIC | UTZNM_LONG_STANDARD);
1048    }
1049    if (types & UTZGNM_SHORT) {
1050        nameTypes |= (UTZNM_SHORT_GENERIC | UTZNM_SHORT_STANDARD_COMMONLY_USED);
1051    }
1052
1053    if (types) {
1054        // Find matches in the TimeZoneNames
1055        matchInfo = fTimeZoneNames->find(text, start, nameTypes, status);
1056    }
1057
1058    return matchInfo;
1059}
1060
1061U_NAMESPACE_END
1062#endif
1063