Coverage Report

Created: 2025-06-13 06:38

/src/icu/icu4c/source/i18n/tzgnames.cpp
Line
Count
Source (jump to first uncovered line)
1
// © 2016 and later: Unicode, Inc. and others.
2
// License & terms of use: http://www.unicode.org/copyright.html
3
/*
4
*******************************************************************************
5
* Copyright (C) 2011-2016, International Business Machines Corporation and
6
* others. All Rights Reserved.
7
*******************************************************************************
8
*/
9
10
#include "unicode/utypes.h"
11
12
#if !UCONFIG_NO_FORMATTING
13
14
#include "tzgnames.h"
15
16
#include "unicode/basictz.h"
17
#include "unicode/locdspnm.h"
18
#include "unicode/rbtz.h"
19
#include "unicode/simpleformatter.h"
20
#include "unicode/simpletz.h"
21
#include "unicode/strenum.h"
22
#include "unicode/vtzone.h"
23
24
#include "charstr.h"
25
#include "cmemory.h"
26
#include "cstring.h"
27
#include "mutex.h"
28
#include "uhash.h"
29
#include "uassert.h"
30
#include "umutex.h"
31
#include "ulocimp.h"
32
#include "uresimp.h"
33
#include "ureslocs.h"
34
#include "zonemeta.h"
35
#include "tznames_impl.h"
36
#include "olsontz.h"
37
#include "ucln_in.h"
38
39
U_NAMESPACE_BEGIN
40
41
3.08k
#define ZID_KEY_MAX  128
42
43
static const char gZoneStrings[]                = "zoneStrings";
44
45
static const char gRegionFormatTag[]            = "regionFormat";
46
static const char gFallbackFormatTag[]          = "fallbackFormat";
47
48
static const char16_t gEmpty[]                     = {0x00};
49
50
static const char16_t gDefRegionPattern[]          = {0x7B, 0x30, 0x7D, 0x00}; // "{0}"
51
static const char16_t gDefFallbackPattern[]        = {0x7B, 0x31, 0x7D, 0x20, 0x28, 0x7B, 0x30, 0x7D, 0x29, 0x00}; // "{1} ({0})"
52
53
static const double kDstCheckRange = static_cast<double>(184) * U_MILLIS_PER_DAY;
54
55
56
57
U_CDECL_BEGIN
58
59
typedef struct PartialLocationKey {
60
    const char16_t* tzID;
61
    const char16_t* mzID;
62
    UBool isLong;
63
} PartialLocationKey;
64
65
/**
66
 * Hash function for partial location name hash key
67
 */
68
static int32_t U_CALLCONV
69
0
hashPartialLocationKey(const UHashTok key) {
70
    // <tzID>&<mzID>#[L|S]
71
0
    PartialLocationKey *p = (PartialLocationKey *)key.pointer;
72
0
    UnicodeString str(p->tzID);
73
0
    str.append((char16_t)0x26)
74
0
        .append(p->mzID, -1)
75
0
        .append((char16_t)0x23)
76
0
        .append((char16_t)(p->isLong ? 0x4C : 0x53));
77
0
    return str.hashCode();
78
0
}
79
80
/**
81
 * Comparer for partial location name hash key
82
 */
83
static UBool U_CALLCONV
84
0
comparePartialLocationKey(const UHashTok key1, const UHashTok key2) {
85
0
    PartialLocationKey *p1 = (PartialLocationKey *)key1.pointer;
86
0
    PartialLocationKey *p2 = (PartialLocationKey *)key2.pointer;
87
88
0
    if (p1 == p2) {
89
0
        return true;
90
0
    }
91
0
    if (p1 == nullptr || p2 == nullptr) {
92
0
        return false;
93
0
    }
94
    // We just check identity of tzID/mzID
95
0
    return (p1->tzID == p2->tzID && p1->mzID == p2->mzID && p1->isLong == p2->isLong);
96
0
}
97
98
/**
99
 * Deleter for GNameInfo
100
 */
101
static void U_CALLCONV
102
0
deleteGNameInfo(void *obj) {
103
0
    uprv_free(obj);
104
0
}
105
106
/**
107
 * GNameInfo stores zone name information in the local trie
108
 */
109
typedef struct GNameInfo {
110
    UTimeZoneGenericNameType    type;
111
    const char16_t*                tzID;
112
} ZNameInfo;
113
114
/**
115
 * GMatchInfo stores zone name match information used by find method
116
 */
117
typedef struct GMatchInfo {
118
    const GNameInfo*    gnameInfo;
119
    int32_t             matchLength;
120
    UTimeZoneFormatTimeType   timeType;
121
} ZMatchInfo;
122
123
U_CDECL_END
124
125
// ---------------------------------------------------
126
// The class stores time zone generic name match information
127
// ---------------------------------------------------
128
class TimeZoneGenericNameMatchInfo : public UMemory {
129
public:
130
    TimeZoneGenericNameMatchInfo(UVector* matches);
131
    ~TimeZoneGenericNameMatchInfo();
132
133
    int32_t size() const;
134
    UTimeZoneGenericNameType getGenericNameType(int32_t index) const;
135
    int32_t getMatchLength(int32_t index) const;
136
    UnicodeString& getTimeZoneID(int32_t index, UnicodeString& tzID) const;
137
138
private:
139
    UVector* fMatches;  // vector of MatchEntry
140
};
141
142
TimeZoneGenericNameMatchInfo::TimeZoneGenericNameMatchInfo(UVector* matches)
143
0
: fMatches(matches) {
144
0
}
145
146
0
TimeZoneGenericNameMatchInfo::~TimeZoneGenericNameMatchInfo() {
147
0
    delete fMatches;
148
0
}
149
150
int32_t
151
0
TimeZoneGenericNameMatchInfo::size() const {
152
0
    if (fMatches == nullptr) {
153
0
        return 0;
154
0
    }
155
0
    return fMatches->size();
156
0
}
157
158
UTimeZoneGenericNameType
159
0
TimeZoneGenericNameMatchInfo::getGenericNameType(int32_t index) const {
160
0
    GMatchInfo* minfo = static_cast<GMatchInfo*>(fMatches->elementAt(index));
161
0
    if (minfo != nullptr) {
162
0
        return static_cast<UTimeZoneGenericNameType>(minfo->gnameInfo->type);
163
0
    }
164
0
    return UTZGNM_UNKNOWN;
165
0
}
166
167
int32_t
168
0
TimeZoneGenericNameMatchInfo::getMatchLength(int32_t index) const {
169
0
    ZMatchInfo* minfo = static_cast<ZMatchInfo*>(fMatches->elementAt(index));
170
0
    if (minfo != nullptr) {
171
0
        return minfo->matchLength;
172
0
    }
173
0
    return -1;
174
0
}
175
176
UnicodeString&
177
0
TimeZoneGenericNameMatchInfo::getTimeZoneID(int32_t index, UnicodeString& tzID) const {
178
0
    GMatchInfo* minfo = static_cast<GMatchInfo*>(fMatches->elementAt(index));
179
0
    if (minfo != nullptr && minfo->gnameInfo->tzID != nullptr) {
180
0
        tzID.setTo(true, minfo->gnameInfo->tzID, -1);
181
0
    } else {
182
0
        tzID.setToBogus();
183
0
    }
184
0
    return tzID;
185
0
}
186
187
// ---------------------------------------------------
188
// GNameSearchHandler
189
// ---------------------------------------------------
190
class GNameSearchHandler : public TextTrieMapSearchResultHandler {
191
public:
192
    GNameSearchHandler(uint32_t types);
193
    virtual ~GNameSearchHandler();
194
195
    UBool handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status) override;
196
    UVector* getMatches(int32_t& maxMatchLen);
197
198
private:
199
    uint32_t fTypes;
200
    UVector* fResults;
201
    int32_t fMaxMatchLen;
202
};
203
204
GNameSearchHandler::GNameSearchHandler(uint32_t types)
205
0
: fTypes(types), fResults(nullptr), fMaxMatchLen(0) {
206
0
}
207
208
0
GNameSearchHandler::~GNameSearchHandler() {
209
0
    delete fResults;
210
0
}
211
212
UBool
213
0
GNameSearchHandler::handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status) {
214
0
    if (U_FAILURE(status)) {
215
0
        return false;
216
0
    }
217
0
    if (node->hasValues()) {
218
0
        int32_t valuesCount = node->countValues();
219
0
        for (int32_t i = 0; i < valuesCount; i++) {
220
0
            GNameInfo *nameinfo = (ZNameInfo *)node->getValue(i);
221
0
            if (nameinfo == nullptr) {
222
0
                break;
223
0
            }
224
0
            if ((nameinfo->type & fTypes) != 0) {
225
                // matches a requested type
226
0
                if (fResults == nullptr) {
227
0
                    LocalPointer<UVector> lpResults(new UVector(uprv_free, nullptr, status), status);
228
0
                    if (U_FAILURE(status)) {
229
0
                        return false;
230
0
                    }
231
0
                    fResults = lpResults.orphan();
232
0
                }
233
0
                GMatchInfo* gmatch = static_cast<GMatchInfo*>(uprv_malloc(sizeof(GMatchInfo)));
234
0
                if (gmatch == nullptr) {
235
0
                    status = U_MEMORY_ALLOCATION_ERROR;
236
0
                    return false;
237
0
                }
238
                // add the match to the vector
239
0
                gmatch->gnameInfo = nameinfo;
240
0
                gmatch->matchLength = matchLength;
241
0
                gmatch->timeType = UTZFMT_TIME_TYPE_UNKNOWN;
242
0
                fResults->adoptElement(gmatch, status);
243
0
                if (U_FAILURE(status)) {
244
0
                    return false;
245
0
                }
246
0
                if (matchLength > fMaxMatchLen) {
247
0
                    fMaxMatchLen = matchLength;
248
0
                }
249
0
            }
250
0
        }
251
0
    }
252
0
    return true;
253
0
}
254
255
UVector*
256
0
GNameSearchHandler::getMatches(int32_t& maxMatchLen) {
257
    // give the ownership to the caller
258
0
    UVector *results = fResults;
259
0
    maxMatchLen = fMaxMatchLen;
260
261
    // reset
262
0
    fResults = nullptr;
263
0
    fMaxMatchLen = 0;
264
0
    return results;
265
0
}
266
267
static UMutex gLock;
268
269
class TZGNCore : public UMemory {
270
public:
271
    TZGNCore(const Locale& locale, UErrorCode& status);
272
    virtual ~TZGNCore();
273
274
    UnicodeString& getDisplayName(const TimeZone& tz, UTimeZoneGenericNameType type,
275
                        UDate date, UnicodeString& name) const;
276
277
    UnicodeString& getGenericLocationName(const UnicodeString& tzCanonicalID, UnicodeString& name) const;
278
279
    int32_t findBestMatch(const UnicodeString& text, int32_t start, uint32_t types,
280
        UnicodeString& tzID, UTimeZoneFormatTimeType& timeType, UErrorCode& status) const;
281
282
private:
283
    Locale fLocale;
284
    const TimeZoneNames* fTimeZoneNames;
285
    UHashtable* fLocationNamesMap;
286
    UHashtable* fPartialLocationNamesMap;
287
288
    SimpleFormatter fRegionFormat;
289
    SimpleFormatter fFallbackFormat;
290
291
    LocaleDisplayNames* fLocaleDisplayNames;
292
    ZNStringPool fStringPool;
293
294
    TextTrieMap fGNamesTrie;
295
    UBool fGNamesTrieFullyLoaded;
296
297
    CharString fTargetRegion;
298
299
    void initialize(const Locale& locale, UErrorCode& status);
300
    void cleanup();
301
302
    void loadStrings(const UnicodeString& tzCanonicalID);
303
304
    const char16_t* getGenericLocationName(const UnicodeString& tzCanonicalID);
305
306
    UnicodeString& formatGenericNonLocationName(const TimeZone& tz, UTimeZoneGenericNameType type,
307
                        UDate date, UnicodeString& name) const;
308
309
    UnicodeString& getPartialLocationName(const UnicodeString& tzCanonicalID,
310
                        const UnicodeString& mzID, UBool isLong, const UnicodeString& mzDisplayName,
311
                        UnicodeString& name) const;
312
313
    const char16_t* getPartialLocationName(const UnicodeString& tzCanonicalID,
314
                        const UnicodeString& mzID, UBool isLong, const UnicodeString& mzDisplayName);
315
316
    TimeZoneGenericNameMatchInfo* findLocal(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const;
317
318
    TimeZoneNames::MatchInfoCollection* findTimeZoneNames(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const;
319
};
320
321
322
// ---------------------------------------------------
323
// TZGNCore - core implementation of TimeZoneGenericNames
324
//
325
// TimeZoneGenericNames is parallel to TimeZoneNames,
326
// but handles run-time generated time zone names.
327
// This is the main part of this module.
328
// ---------------------------------------------------
329
TZGNCore::TZGNCore(const Locale& locale, UErrorCode& status)
330
278
: fLocale(locale),
331
278
  fTimeZoneNames(nullptr),
332
278
  fLocationNamesMap(nullptr),
333
278
  fPartialLocationNamesMap(nullptr),
334
278
  fLocaleDisplayNames(nullptr),
335
278
  fStringPool(status),
336
278
  fGNamesTrie(true, deleteGNameInfo),
337
278
  fGNamesTrieFullyLoaded(false),
338
278
  fTargetRegion() {
339
278
    initialize(locale, status);
340
278
}
341
342
0
TZGNCore::~TZGNCore() {
343
0
    cleanup();
344
0
}
345
346
void
347
278
TZGNCore::initialize(const Locale& locale, UErrorCode& status) {
348
278
    if (U_FAILURE(status)) {
349
0
        return;
350
0
    }
351
352
    // TimeZoneNames
353
278
    fTimeZoneNames = TimeZoneNames::createInstance(locale, status);
354
278
    if (U_FAILURE(status)) {
355
0
        return;
356
0
    }
357
358
    // Initialize format patterns
359
278
    UnicodeString rpat(true, gDefRegionPattern, -1);
360
278
    UnicodeString fpat(true, gDefFallbackPattern, -1);
361
362
278
    UErrorCode tmpsts = U_ZERO_ERROR;   // OK with fallback warning..
363
278
    UResourceBundle *zoneStrings = ures_open(U_ICUDATA_ZONE, locale.getName(), &tmpsts);
364
278
    zoneStrings = ures_getByKeyWithFallback(zoneStrings, gZoneStrings, zoneStrings, &tmpsts);
365
366
278
    if (U_SUCCESS(tmpsts)) {
367
278
        const char16_t *regionPattern = ures_getStringByKeyWithFallback(zoneStrings, gRegionFormatTag, nullptr, &tmpsts);
368
278
        if (U_SUCCESS(tmpsts) && u_strlen(regionPattern) > 0) {
369
278
            rpat.setTo(regionPattern, -1);
370
278
        }
371
278
        tmpsts = U_ZERO_ERROR;
372
278
        const char16_t *fallbackPattern = ures_getStringByKeyWithFallback(zoneStrings, gFallbackFormatTag, nullptr, &tmpsts);
373
278
        if (U_SUCCESS(tmpsts) && u_strlen(fallbackPattern) > 0) {
374
278
            fpat.setTo(fallbackPattern, -1);
375
278
        }
376
278
    }
377
278
    ures_close(zoneStrings);
378
379
278
    fRegionFormat.applyPatternMinMaxArguments(rpat, 1, 1, status);
380
278
    fFallbackFormat.applyPatternMinMaxArguments(fpat, 2, 2, status);
381
278
    if (U_FAILURE(status)) {
382
0
        cleanup();
383
0
        return;
384
0
    }
385
386
    // locale display names
387
278
    fLocaleDisplayNames = LocaleDisplayNames::createInstance(locale);
388
389
    // hash table for names - no key/value deleters
390
278
    fLocationNamesMap = uhash_open(uhash_hashUChars, uhash_compareUChars, nullptr, &status);
391
278
    if (U_FAILURE(status)) {
392
0
        cleanup();
393
0
        return;
394
0
    }
395
396
278
    fPartialLocationNamesMap = uhash_open(hashPartialLocationKey, comparePartialLocationKey, nullptr, &status);
397
278
    if (U_FAILURE(status)) {
398
0
        cleanup();
399
0
        return;
400
0
    }
401
278
    uhash_setKeyDeleter(fPartialLocationNamesMap, uprv_free);
402
    // no value deleter
403
404
    // target region
405
278
    const char* region = fLocale.getCountry();
406
278
    int32_t regionLen = static_cast<int32_t>(uprv_strlen(region));
407
278
    if (regionLen == 0) {
408
86
        CharString loc = ulocimp_addLikelySubtags(fLocale.getName(), status);
409
86
        ulocimp_getSubtags(loc.toStringPiece(), nullptr, nullptr, &fTargetRegion, nullptr, nullptr, status);
410
86
        if (U_FAILURE(status)) {
411
0
            cleanup();
412
0
            return;
413
0
        }
414
192
    } else {
415
192
        fTargetRegion.append(region, regionLen, status);
416
192
    }
417
418
    // preload generic names for the default zone
419
278
    TimeZone *tz = TimeZone::createDefault();
420
278
    const char16_t *tzID = ZoneMeta::getCanonicalCLDRID(*tz);
421
278
    if (tzID != nullptr) {
422
278
        loadStrings(UnicodeString(true, tzID, -1));
423
278
    }
424
278
    delete tz;
425
278
}
426
427
void
428
0
TZGNCore::cleanup() {
429
0
    delete fLocaleDisplayNames;
430
0
    delete fTimeZoneNames;
431
432
0
    uhash_close(fLocationNamesMap);
433
0
    uhash_close(fPartialLocationNamesMap);
434
0
}
435
436
437
UnicodeString&
438
1.26k
TZGNCore::getDisplayName(const TimeZone& tz, UTimeZoneGenericNameType type, UDate date, UnicodeString& name) const {
439
1.26k
    name.setToBogus();
440
1.26k
    switch (type) {
441
0
    case UTZGNM_LOCATION:
442
0
        {
443
0
            const char16_t* tzCanonicalID = ZoneMeta::getCanonicalCLDRID(tz);
444
0
            if (tzCanonicalID != nullptr) {
445
0
                getGenericLocationName(UnicodeString(true, tzCanonicalID, -1), name);
446
0
            }
447
0
        }
448
0
        break;
449
2
    case UTZGNM_LONG:
450
1.26k
    case UTZGNM_SHORT:
451
1.26k
        formatGenericNonLocationName(tz, type, date, name);
452
1.26k
        if (name.isEmpty()) {
453
1.26k
            const char16_t* tzCanonicalID = ZoneMeta::getCanonicalCLDRID(tz);
454
1.26k
            if (tzCanonicalID != nullptr) {
455
1.26k
                getGenericLocationName(UnicodeString(true, tzCanonicalID, -1), name);
456
1.26k
            }
457
1.26k
        }
458
1.26k
        break;
459
0
    default:
460
0
        break;
461
1.26k
    }
462
1.26k
    return name;
463
1.26k
}
464
465
UnicodeString&
466
1.26k
TZGNCore::getGenericLocationName(const UnicodeString& tzCanonicalID, UnicodeString& name) const {
467
1.26k
    if (tzCanonicalID.isEmpty()) {
468
0
        name.setToBogus();
469
0
        return name;
470
0
    }
471
472
1.26k
    const char16_t *locname = nullptr;
473
1.26k
    TZGNCore *nonConstThis = const_cast<TZGNCore *>(this);
474
1.26k
    umtx_lock(&gLock);
475
1.26k
    {
476
1.26k
        locname = nonConstThis->getGenericLocationName(tzCanonicalID);
477
1.26k
    }
478
1.26k
    umtx_unlock(&gLock);
479
480
1.26k
    if (locname == nullptr) {
481
1.26k
        name.setToBogus();
482
1.26k
    } else {
483
0
        name.setTo(locname, u_strlen(locname));
484
0
    }
485
486
1.26k
    return name;
487
1.26k
}
488
489
/*
490
 * This method updates the cache and must be called with a lock
491
 */
492
const char16_t*
493
1.54k
TZGNCore::getGenericLocationName(const UnicodeString& tzCanonicalID) {
494
1.54k
    U_ASSERT(!tzCanonicalID.isEmpty());
495
1.54k
    if (tzCanonicalID.length() > ZID_KEY_MAX) {
496
0
        return nullptr;
497
0
    }
498
499
1.54k
    UErrorCode status = U_ZERO_ERROR;
500
1.54k
    char16_t tzIDKey[ZID_KEY_MAX + 1];
501
1.54k
    int32_t tzIDKeyLen = tzCanonicalID.extract(tzIDKey, ZID_KEY_MAX + 1, status);
502
1.54k
    U_ASSERT(status == U_ZERO_ERROR);   // already checked length above
503
1.54k
    tzIDKey[tzIDKeyLen] = 0;
504
505
1.54k
    const char16_t* locname = static_cast<const char16_t*>(uhash_get(fLocationNamesMap, tzIDKey));
506
507
1.54k
    if (locname != nullptr) {
508
        // gEmpty indicate the name is not available
509
1.26k
        if (locname == gEmpty) {
510
1.26k
            return nullptr;
511
1.26k
        }
512
0
        return locname;
513
1.26k
    }
514
515
    // Construct location name
516
278
    UnicodeString name;
517
278
    UnicodeString usCountryCode;
518
278
    UBool isPrimary = false;
519
520
278
    ZoneMeta::getCanonicalCountry(tzCanonicalID, usCountryCode, &isPrimary);
521
522
278
    if (!usCountryCode.isEmpty()) {
523
0
        if (isPrimary) {
524
            // If this is the primary zone in the country, use the country name.
525
0
            char countryCode[ULOC_COUNTRY_CAPACITY];
526
0
            U_ASSERT(usCountryCode.length() < ULOC_COUNTRY_CAPACITY);
527
0
            int32_t ccLen = usCountryCode.extract(0, usCountryCode.length(), countryCode, sizeof(countryCode), US_INV);
528
0
            countryCode[ccLen] = 0;
529
530
0
            UnicodeString country;
531
0
            fLocaleDisplayNames->regionDisplayName(countryCode, country);
532
0
            fRegionFormat.format(country, name, status);
533
0
        } else {
534
            // If this is not the primary zone in the country,
535
            // use the exemplar city name.
536
537
            // getExemplarLocationName should return non-empty string
538
            // if the time zone is associated with a region
539
540
0
            UnicodeString city;
541
0
            fTimeZoneNames->getExemplarLocationName(tzCanonicalID, city);
542
0
            fRegionFormat.format(city, name, status);
543
0
        }
544
0
        if (U_FAILURE(status)) {
545
0
            return nullptr;
546
0
        }
547
0
    }
548
549
278
    locname = name.isEmpty() ? nullptr : fStringPool.get(name, status);
550
278
    if (U_SUCCESS(status)) {
551
        // Cache the result
552
278
        const char16_t* cacheID = ZoneMeta::findTimeZoneID(tzCanonicalID);
553
278
        U_ASSERT(cacheID != nullptr);
554
278
        if (locname == nullptr) {
555
            // gEmpty to indicate - no location name available
556
278
            uhash_put(fLocationNamesMap, (void *)cacheID, (void *)gEmpty, &status);
557
278
        } else {
558
0
            uhash_put(fLocationNamesMap, (void *)cacheID, (void *)locname, &status);
559
0
            if (U_FAILURE(status)) {
560
0
                locname = nullptr;
561
0
            } else {
562
                // put the name info into the trie
563
0
                GNameInfo* nameinfo = static_cast<ZNameInfo*>(uprv_malloc(sizeof(GNameInfo)));
564
0
                if (nameinfo != nullptr) {
565
0
                    nameinfo->type = UTZGNM_LOCATION;
566
0
                    nameinfo->tzID = cacheID;
567
0
                    fGNamesTrie.put(locname, nameinfo, status);
568
0
                }
569
0
            }
570
0
        }
571
278
    }
572
573
278
    return locname;
574
278
}
575
576
UnicodeString&
577
1.26k
TZGNCore::formatGenericNonLocationName(const TimeZone& tz, UTimeZoneGenericNameType type, UDate date, UnicodeString& name) const {
578
1.26k
    U_ASSERT(type == UTZGNM_LONG || type == UTZGNM_SHORT);
579
1.26k
    name.setToBogus();
580
581
1.26k
    const char16_t* uID = ZoneMeta::getCanonicalCLDRID(tz);
582
1.26k
    if (uID == nullptr) {
583
0
        return name;
584
0
    }
585
586
1.26k
    UnicodeString tzID(true, uID, -1);
587
588
    // Try to get a name from time zone first
589
1.26k
    UTimeZoneNameType nameType = (type == UTZGNM_LONG) ? UTZNM_LONG_GENERIC : UTZNM_SHORT_GENERIC;
590
1.26k
    fTimeZoneNames->getTimeZoneDisplayName(tzID, nameType, name);
591
592
1.26k
    if (!name.isEmpty()) {
593
0
        return name;
594
0
    }
595
596
    // Try meta zone
597
1.26k
    char16_t mzIDBuf[32];
598
1.26k
    UnicodeString mzID(mzIDBuf, 0, UPRV_LENGTHOF(mzIDBuf));
599
1.26k
    fTimeZoneNames->getMetaZoneID(tzID, date, mzID);
600
1.26k
    if (!mzID.isEmpty()) {
601
0
        UErrorCode status = U_ZERO_ERROR;
602
0
        UBool useStandard = false;
603
0
        int32_t raw, sav;
604
0
        char16_t tmpNameBuf[ZONE_NAME_U16_MAX];
605
606
0
        tz.getOffset(date, false, raw, sav, status);
607
0
        if (U_FAILURE(status)) {
608
0
            return name;
609
0
        }
610
611
0
        if (sav == 0) {
612
0
            useStandard = true;
613
614
0
            TimeZone *tmptz = tz.clone();
615
            // Check if the zone actually uses daylight saving time around the time
616
0
            BasicTimeZone *btz = nullptr;
617
0
            if (dynamic_cast<OlsonTimeZone *>(tmptz) != nullptr
618
0
                || dynamic_cast<SimpleTimeZone *>(tmptz) != nullptr
619
0
                || dynamic_cast<RuleBasedTimeZone *>(tmptz) != nullptr
620
0
                || dynamic_cast<VTimeZone *>(tmptz) != nullptr) {
621
0
                btz = (BasicTimeZone*)tmptz;
622
0
            }
623
624
0
            if (btz != nullptr) {
625
0
                TimeZoneTransition before;
626
0
                UBool beforTrs = btz->getPreviousTransition(date, true, before);
627
0
                if (beforTrs
628
0
                        && (date - before.getTime() < kDstCheckRange)
629
0
                        && before.getFrom()->getDSTSavings() != 0) {
630
0
                    useStandard = false;
631
0
                } else {
632
0
                    TimeZoneTransition after;
633
0
                    UBool afterTrs = btz->getNextTransition(date, false, after);
634
0
                    if (afterTrs
635
0
                            && (after.getTime() - date < kDstCheckRange)
636
0
                            && after.getTo()->getDSTSavings() != 0) {
637
0
                        useStandard = false;
638
0
                    }
639
0
                }
640
0
            } else {
641
                // If not BasicTimeZone... only if the instance is not an ICU's implementation.
642
                // We may get a wrong answer in edge case, but it should practically work OK.
643
0
                tmptz->getOffset(date - kDstCheckRange, false, raw, sav, status);
644
0
                if (sav != 0) {
645
0
                    useStandard = false;
646
0
                } else {
647
0
                    tmptz->getOffset(date + kDstCheckRange, false, raw, sav, status);
648
0
                    if (sav != 0){
649
0
                        useStandard = false;
650
0
                    }
651
0
                }
652
0
                if (U_FAILURE(status)) {
653
0
                    delete tmptz;
654
0
                    return name;
655
0
                }
656
0
            }
657
0
            delete tmptz;
658
0
        }
659
0
        if (useStandard) {
660
0
            UTimeZoneNameType stdNameType = (nameType == UTZNM_LONG_GENERIC)
661
0
                ? UTZNM_LONG_STANDARD : UTZNM_SHORT_STANDARD;
662
0
            UnicodeString stdName(tmpNameBuf, 0, UPRV_LENGTHOF(tmpNameBuf));
663
0
            fTimeZoneNames->getDisplayName(tzID, stdNameType, date, stdName);
664
0
            if (!stdName.isEmpty()) {
665
0
                name.setTo(stdName);
666
667
                // TODO: revisit this issue later
668
                // In CLDR, a same display name is used for both generic and standard
669
                // for some meta zones in some locales.  This looks like a data bugs.
670
                // For now, we check if the standard name is different from its generic
671
                // name below.
672
0
                char16_t genNameBuf[ZONE_NAME_U16_MAX];
673
0
                UnicodeString mzGenericName(genNameBuf, 0, UPRV_LENGTHOF(genNameBuf));
674
0
                fTimeZoneNames->getMetaZoneDisplayName(mzID, nameType, mzGenericName);
675
0
                if (stdName.caseCompare(mzGenericName, 0) == 0) {
676
0
                    name.setToBogus();
677
0
                }
678
0
            }
679
0
        }
680
0
        if (name.isEmpty()) {
681
            // Get a name from meta zone
682
0
            UnicodeString mzName(tmpNameBuf, 0, UPRV_LENGTHOF(tmpNameBuf));
683
0
            fTimeZoneNames->getMetaZoneDisplayName(mzID, nameType, mzName);
684
0
            if (!mzName.isEmpty()) {
685
                // Check if we need to use a partial location format.
686
                // This check is done by comparing offset with the meta zone's
687
                // golden zone at the given date.
688
0
                char16_t idBuf[32];
689
0
                UnicodeString goldenID(idBuf, 0, UPRV_LENGTHOF(idBuf));
690
0
                fTimeZoneNames->getReferenceZoneID(mzID, fTargetRegion.data(), goldenID);
691
0
                if (!goldenID.isEmpty() && goldenID != tzID) {
692
0
                    TimeZone *goldenZone = TimeZone::createTimeZone(goldenID);
693
0
                    int32_t raw1, sav1;
694
695
                    // Check offset in the golden zone with wall time.
696
                    // With getOffset(date, false, offsets1),
697
                    // you may get incorrect results because of time overlap at DST->STD
698
                    // transition.
699
0
                    goldenZone->getOffset(date + raw + sav, true, raw1, sav1, status);
700
0
                    delete goldenZone;
701
0
                    if (U_SUCCESS(status)) {
702
0
                        if (raw != raw1 || sav != sav1) {
703
                            // Now we need to use a partial location format
704
0
                            getPartialLocationName(tzID, mzID, (nameType == UTZNM_LONG_GENERIC), mzName, name);
705
0
                        } else {
706
0
                            name.setTo(mzName);
707
0
                        }
708
0
                    }
709
0
                } else {
710
0
                    name.setTo(mzName);
711
0
                }
712
0
            }
713
0
        }
714
0
    }
715
1.26k
    return name;
716
1.26k
}
717
718
UnicodeString&
719
TZGNCore::getPartialLocationName(const UnicodeString& tzCanonicalID,
720
                        const UnicodeString& mzID, UBool isLong, const UnicodeString& mzDisplayName,
721
0
                        UnicodeString& name) const {
722
0
    name.setToBogus();
723
0
    if (tzCanonicalID.isEmpty() || mzID.isEmpty() || mzDisplayName.isEmpty()) {
724
0
        return name;
725
0
    }
726
727
0
    const char16_t *uplname = nullptr;
728
0
    TZGNCore *nonConstThis = const_cast<TZGNCore *>(this);
729
0
    umtx_lock(&gLock);
730
0
    {
731
0
        uplname = nonConstThis->getPartialLocationName(tzCanonicalID, mzID, isLong, mzDisplayName);
732
0
    }
733
0
    umtx_unlock(&gLock);
734
735
0
    if (uplname == nullptr) {
736
0
        name.setToBogus();
737
0
    } else {
738
0
        name.setTo(true, uplname, -1);
739
0
    }
740
0
    return name;
741
0
}
742
743
/*
744
 * This method updates the cache and must be called with a lock
745
 */
746
const char16_t*
747
TZGNCore::getPartialLocationName(const UnicodeString& tzCanonicalID,
748
0
                        const UnicodeString& mzID, UBool isLong, const UnicodeString& mzDisplayName) {
749
0
    U_ASSERT(!tzCanonicalID.isEmpty());
750
0
    U_ASSERT(!mzID.isEmpty());
751
0
    U_ASSERT(!mzDisplayName.isEmpty());
752
753
0
    PartialLocationKey key;
754
0
    key.tzID = ZoneMeta::findTimeZoneID(tzCanonicalID);
755
0
    key.mzID = ZoneMeta::findMetaZoneID(mzID);
756
0
    key.isLong = isLong;
757
0
    U_ASSERT(key.tzID != nullptr && key.mzID != nullptr);
758
759
0
    const char16_t* uplname = static_cast<const char16_t*>(uhash_get(fPartialLocationNamesMap, &key));
760
0
    if (uplname != nullptr) {
761
0
        return uplname;
762
0
    }
763
764
0
    UnicodeString location;
765
0
    UnicodeString usCountryCode;
766
0
    ZoneMeta::getCanonicalCountry(tzCanonicalID, usCountryCode);
767
0
    if (!usCountryCode.isEmpty()) {
768
0
        char countryCode[ULOC_COUNTRY_CAPACITY];
769
0
        U_ASSERT(usCountryCode.length() < ULOC_COUNTRY_CAPACITY);
770
0
        int32_t ccLen = usCountryCode.extract(0, usCountryCode.length(), countryCode, sizeof(countryCode), US_INV);
771
0
        countryCode[ccLen] = 0;
772
773
0
        UnicodeString regionalGolden;
774
0
        fTimeZoneNames->getReferenceZoneID(mzID, countryCode, regionalGolden);
775
0
        if (tzCanonicalID == regionalGolden) {
776
            // Use country name
777
0
            fLocaleDisplayNames->regionDisplayName(countryCode, location);
778
0
        } else {
779
            // Otherwise, use exemplar city name
780
0
            fTimeZoneNames->getExemplarLocationName(tzCanonicalID, location);
781
0
        }
782
0
    } else {
783
0
        fTimeZoneNames->getExemplarLocationName(tzCanonicalID, location);
784
0
        if (location.isEmpty()) {
785
            // This could happen when the time zone is not associated with a country,
786
            // and its ID is not hierarchical, for example, CST6CDT.
787
            // We use the canonical ID itself as the location for this case.
788
0
            location.setTo(tzCanonicalID);
789
0
        }
790
0
    }
791
792
0
    UErrorCode status = U_ZERO_ERROR;
793
0
    UnicodeString name;
794
0
    fFallbackFormat.format(location, mzDisplayName, name, status);
795
0
    if (U_FAILURE(status)) {
796
0
        return nullptr;
797
0
    }
798
799
0
    uplname = fStringPool.get(name, status);
800
0
    if (U_SUCCESS(status)) {
801
        // Add the name to cache
802
0
        PartialLocationKey* cacheKey = static_cast<PartialLocationKey*>(uprv_malloc(sizeof(PartialLocationKey)));
803
0
        if (cacheKey != nullptr) {
804
0
            cacheKey->tzID = key.tzID;
805
0
            cacheKey->mzID = key.mzID;
806
0
            cacheKey->isLong = key.isLong;
807
0
            uhash_put(fPartialLocationNamesMap, (void *)cacheKey, (void *)uplname, &status);
808
0
            if (U_FAILURE(status)) {
809
0
                uprv_free(cacheKey);
810
0
            } else {
811
                // put the name to the local trie as well
812
0
                GNameInfo* nameinfo = static_cast<ZNameInfo*>(uprv_malloc(sizeof(GNameInfo)));
813
0
                if (nameinfo != nullptr) {
814
0
                    nameinfo->type = isLong ? UTZGNM_LONG : UTZGNM_SHORT;
815
0
                    nameinfo->tzID = key.tzID;
816
0
                    fGNamesTrie.put(uplname, nameinfo, status);
817
0
                }
818
0
            }
819
0
        }
820
0
    }
821
0
    return uplname;
822
0
}
823
824
/*
825
 * This method updates the cache and must be called with a lock,
826
 * except initializer.
827
 */
828
void
829
278
TZGNCore::loadStrings(const UnicodeString& tzCanonicalID) {
830
    // load the generic location name
831
278
    getGenericLocationName(tzCanonicalID);
832
833
    // partial location names
834
278
    UErrorCode status = U_ZERO_ERROR;
835
836
278
    const UnicodeString *mzID;
837
278
    UnicodeString goldenID;
838
278
    UnicodeString mzGenName;
839
278
    UTimeZoneNameType genNonLocTypes[] = {
840
278
        UTZNM_LONG_GENERIC, UTZNM_SHORT_GENERIC,
841
278
        UTZNM_UNKNOWN /*terminator*/
842
278
    };
843
844
278
    StringEnumeration *mzIDs = fTimeZoneNames->getAvailableMetaZoneIDs(tzCanonicalID, status);
845
278
    while ((mzID = mzIDs->snext(status)) != nullptr) {
846
0
        if (U_FAILURE(status)) {
847
0
            break;
848
0
        }
849
        // if this time zone is not the golden zone of the meta zone,
850
        // partial location name (such as "PT (Los Angeles)") might be
851
        // available.
852
0
        fTimeZoneNames->getReferenceZoneID(*mzID, fTargetRegion.data(), goldenID);
853
0
        if (tzCanonicalID != goldenID) {
854
0
            for (int32_t i = 0; genNonLocTypes[i] != UTZNM_UNKNOWN; i++) {
855
0
                fTimeZoneNames->getMetaZoneDisplayName(*mzID, genNonLocTypes[i], mzGenName);
856
0
                if (!mzGenName.isEmpty()) {
857
                    // getPartialLocationName formats a name and put it into the trie
858
0
                    getPartialLocationName(tzCanonicalID, *mzID,
859
0
                        (genNonLocTypes[i] == UTZNM_LONG_GENERIC), mzGenName);
860
0
                }
861
0
            }
862
0
        }
863
0
    }
864
278
    delete mzIDs;
865
278
}
866
867
int32_t
868
TZGNCore::findBestMatch(const UnicodeString& text, int32_t start, uint32_t types,
869
0
        UnicodeString& tzID, UTimeZoneFormatTimeType& timeType, UErrorCode& status) const {
870
0
    timeType = UTZFMT_TIME_TYPE_UNKNOWN;
871
0
    tzID.setToBogus();
872
873
0
    if (U_FAILURE(status)) {
874
0
        return 0;
875
0
    }
876
877
    // Find matches in the TimeZoneNames first
878
0
    TimeZoneNames::MatchInfoCollection *tznamesMatches = findTimeZoneNames(text, start, types, status);
879
0
    if (U_FAILURE(status)) {
880
0
        return 0;
881
0
    }
882
883
0
    int32_t bestMatchLen = 0;
884
0
    UTimeZoneFormatTimeType bestMatchTimeType = UTZFMT_TIME_TYPE_UNKNOWN;
885
0
    UnicodeString bestMatchTzID;
886
    // UBool isLongStandard = false;   // workaround - see the comments below
887
0
    UBool isStandard = false;       // TODO: Temporary hack (on hack) for short standard name/location name conflict (found in zh_Hant), should be removed after CLDR 21m1 integration
888
889
0
    if (tznamesMatches != nullptr) {
890
0
        UnicodeString mzID;
891
0
        for (int32_t i = 0; i < tznamesMatches->size(); i++) {
892
0
            int32_t len = tznamesMatches->getMatchLengthAt(i);
893
0
            if (len > bestMatchLen) {
894
0
                bestMatchLen = len;
895
0
                if (!tznamesMatches->getTimeZoneIDAt(i, bestMatchTzID)) {
896
                    // name for a meta zone
897
0
                    if (tznamesMatches->getMetaZoneIDAt(i, mzID)) {
898
0
                        fTimeZoneNames->getReferenceZoneID(mzID, fTargetRegion.data(), bestMatchTzID);
899
0
                    }
900
0
                }
901
0
                UTimeZoneNameType nameType = tznamesMatches->getNameTypeAt(i);
902
0
                if (U_FAILURE(status)) {
903
0
                    break;
904
0
                }
905
0
                switch (nameType) {
906
0
                case UTZNM_LONG_STANDARD:
907
                    // isLongStandard = true;
908
0
                case UTZNM_SHORT_STANDARD:  // this one is never used for generic, but just in case
909
0
                    isStandard = true;      // TODO: Remove this later, see the comments above.
910
0
                    bestMatchTimeType = UTZFMT_TIME_TYPE_STANDARD;
911
0
                    break;
912
0
                case UTZNM_LONG_DAYLIGHT:
913
0
                case UTZNM_SHORT_DAYLIGHT: // this one is never used for generic, but just in case
914
0
                    bestMatchTimeType = UTZFMT_TIME_TYPE_DAYLIGHT;
915
0
                    break;
916
0
                default:
917
0
                    bestMatchTimeType = UTZFMT_TIME_TYPE_UNKNOWN;
918
0
                }
919
0
            }
920
0
        }
921
0
        delete tznamesMatches;
922
0
        if (U_FAILURE(status)) {
923
0
            return 0;
924
0
        }
925
926
0
        if (bestMatchLen == (text.length() - start)) {
927
            // Full match
928
929
            //tzID.setTo(bestMatchTzID);
930
            //timeType = bestMatchTimeType;
931
            //return bestMatchLen;
932
933
            // TODO Some time zone uses a same name for the long standard name
934
            // and the location name. When the match is a long standard name,
935
            // then we need to check if the name is same with the location name.
936
            // This is probably a data error or a design bug.
937
/*
938
            if (!isLongStandard) {
939
                tzID.setTo(bestMatchTzID);
940
                timeType = bestMatchTimeType;
941
                return bestMatchLen;
942
            }
943
*/
944
            // TODO The deprecation of commonlyUsed flag introduced the name
945
            // conflict not only for long standard names, but short standard names too.
946
            // These short names (found in zh_Hant) should be gone once we clean
947
            // up CLDR time zone display name data. Once the short name conflict
948
            // problem (with location name) is resolved, we should change the condition
949
            // below back to the original one above. -Yoshito (2011-09-14)
950
0
            if (!isStandard) {
951
0
                tzID.setTo(bestMatchTzID);
952
0
                timeType = bestMatchTimeType;
953
0
                return bestMatchLen;
954
0
            }
955
0
        }
956
0
    }
957
958
    // Find matches in the local trie
959
0
    TimeZoneGenericNameMatchInfo *localMatches = findLocal(text, start, types, status);
960
0
    if (U_FAILURE(status)) {
961
0
        return 0;
962
0
    }
963
0
    if (localMatches != nullptr) {
964
0
        for (int32_t i = 0; i < localMatches->size(); i++) {
965
0
            int32_t len = localMatches->getMatchLength(i);
966
967
            // TODO See the above TODO. We use len >= bestMatchLen
968
            // because of the long standard/location name collision
969
            // problem. If it is also a location name, carrying
970
            // timeType = UTZFMT_TIME_TYPE_STANDARD will cause a
971
            // problem in SimpleDateFormat
972
0
            if (len >= bestMatchLen) {
973
0
                bestMatchLen = localMatches->getMatchLength(i);
974
0
                bestMatchTimeType = UTZFMT_TIME_TYPE_UNKNOWN;   // because generic
975
0
                localMatches->getTimeZoneID(i, bestMatchTzID);
976
0
            }
977
0
        }
978
0
        delete localMatches;
979
0
    }
980
981
0
    if (bestMatchLen > 0) {
982
0
        timeType = bestMatchTimeType;
983
0
        tzID.setTo(bestMatchTzID);
984
0
    }
985
0
    return bestMatchLen;
986
0
}
987
988
TimeZoneGenericNameMatchInfo*
989
0
TZGNCore::findLocal(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const {
990
0
    GNameSearchHandler handler(types);
991
992
0
    TZGNCore *nonConstThis = const_cast<TZGNCore *>(this);
993
994
0
    umtx_lock(&gLock);
995
0
    {
996
0
        fGNamesTrie.search(text, start, (TextTrieMapSearchResultHandler *)&handler, status);
997
0
    }
998
0
    umtx_unlock(&gLock);
999
1000
0
    if (U_FAILURE(status)) {
1001
0
        return nullptr;
1002
0
    }
1003
1004
0
    TimeZoneGenericNameMatchInfo *gmatchInfo = nullptr;
1005
1006
0
    int32_t maxLen = 0;
1007
0
    UVector *results = handler.getMatches(maxLen);
1008
0
    if (results != nullptr && ((maxLen == (text.length() - start)) || fGNamesTrieFullyLoaded)) {
1009
        // perfect match
1010
0
        gmatchInfo = new TimeZoneGenericNameMatchInfo(results);
1011
0
        if (gmatchInfo == nullptr) {
1012
0
            status = U_MEMORY_ALLOCATION_ERROR;
1013
0
            delete results;
1014
0
            return nullptr;
1015
0
        }
1016
0
        return gmatchInfo;
1017
0
    }
1018
1019
0
    delete results;
1020
1021
    // All names are not yet loaded into the local trie.
1022
    // Load all available names into the trie. This could be very heavy.
1023
0
    umtx_lock(&gLock);
1024
0
    {
1025
0
        if (!fGNamesTrieFullyLoaded) {
1026
0
            StringEnumeration *tzIDs = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, nullptr, nullptr, status);
1027
0
            if (U_SUCCESS(status)) {
1028
0
                const UnicodeString *tzID;
1029
0
                while ((tzID = tzIDs->snext(status)) != nullptr) {
1030
0
                    if (U_FAILURE(status)) {
1031
0
                        break;
1032
0
                    }
1033
0
                    nonConstThis->loadStrings(*tzID);
1034
0
                }
1035
0
            }
1036
0
            delete tzIDs;
1037
1038
0
            if (U_SUCCESS(status)) {
1039
0
                nonConstThis->fGNamesTrieFullyLoaded = true;
1040
0
            }
1041
0
        }
1042
0
    }
1043
0
    umtx_unlock(&gLock);
1044
1045
0
    if (U_FAILURE(status)) {
1046
0
        return nullptr;
1047
0
    }
1048
1049
0
    umtx_lock(&gLock);
1050
0
    {
1051
        // now try it again
1052
0
        fGNamesTrie.search(text, start, (TextTrieMapSearchResultHandler *)&handler, status);
1053
0
    }
1054
0
    umtx_unlock(&gLock);
1055
1056
0
    results = handler.getMatches(maxLen);
1057
0
    if (results != nullptr && maxLen > 0) {
1058
0
        gmatchInfo = new TimeZoneGenericNameMatchInfo(results);
1059
0
        if (gmatchInfo == nullptr) {
1060
0
            status = U_MEMORY_ALLOCATION_ERROR;
1061
0
            delete results;
1062
0
            return nullptr;
1063
0
        }
1064
0
    }
1065
1066
0
    return gmatchInfo;
1067
0
}
1068
1069
TimeZoneNames::MatchInfoCollection*
1070
0
TZGNCore::findTimeZoneNames(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const {
1071
    // Check if the target name typs is really in the TimeZoneNames
1072
0
    uint32_t nameTypes = 0;
1073
0
    if (types & UTZGNM_LONG) {
1074
0
        nameTypes |= (UTZNM_LONG_GENERIC | UTZNM_LONG_STANDARD);
1075
0
    }
1076
0
    if (types & UTZGNM_SHORT) {
1077
0
        nameTypes |= (UTZNM_SHORT_GENERIC | UTZNM_SHORT_STANDARD);
1078
0
    }
1079
1080
0
    if (types) {
1081
        // Find matches in the TimeZoneNames
1082
0
        return fTimeZoneNames->find(text, start, nameTypes, status);
1083
0
    }
1084
1085
0
    return nullptr;
1086
0
}
1087
1088
typedef struct TZGNCoreRef {
1089
    TZGNCore*       obj;
1090
    int32_t         refCount;
1091
    double          lastAccess;
1092
} TZGNCoreRef;
1093
1094
// TZGNCore object cache handling
1095
static UMutex gTZGNLock;
1096
static UHashtable *gTZGNCoreCache = nullptr;
1097
static UBool gTZGNCoreCacheInitialized = false;
1098
1099
// Access count - incremented every time up to SWEEP_INTERVAL,
1100
// then reset to 0
1101
static int32_t gAccessCount = 0;
1102
1103
// Interval for calling the cache sweep function - every 100 times
1104
1.26k
#define SWEEP_INTERVAL 100
1105
1106
// Cache expiration in millisecond. When a cached entry is no
1107
// longer referenced and exceeding this threshold since last
1108
// access time, then the cache entry will be deleted by the sweep
1109
// function. For now, 3 minutes.
1110
2.18k
#define CACHE_EXPIRATION 180000.0
1111
1112
U_CDECL_BEGIN
1113
/**
1114
 * Cleanup callback func
1115
 */
1116
static UBool U_CALLCONV tzgnCore_cleanup()
1117
0
{
1118
0
    if (gTZGNCoreCache != nullptr) {
1119
0
        uhash_close(gTZGNCoreCache);
1120
0
        gTZGNCoreCache = nullptr;
1121
0
    }
1122
0
    gTZGNCoreCacheInitialized = false;
1123
0
    return true;
1124
0
}
1125
1126
/**
1127
 * Deleter for TZGNCoreRef
1128
 */
1129
static void U_CALLCONV
1130
0
deleteTZGNCoreRef(void *obj) {
1131
0
    icu::TZGNCoreRef *entry = (icu::TZGNCoreRef*)obj;
1132
0
    delete (icu::TZGNCore*) entry->obj;
1133
0
    uprv_free(entry);
1134
0
}
1135
U_CDECL_END
1136
1137
/**
1138
 * Function used for removing unreferrenced cache entries exceeding
1139
 * the expiration time. This function must be called with in the mutex
1140
 * block.
1141
 */
1142
12
static void sweepCache() {
1143
12
    int32_t pos = UHASH_FIRST;
1144
12
    const UHashElement* elem;
1145
12
    double now = static_cast<double>(uprv_getUTCtime());
1146
1147
2.20k
    while ((elem = uhash_nextElement(gTZGNCoreCache, &pos)) != nullptr) {
1148
2.19k
        TZGNCoreRef* entry = static_cast<TZGNCoreRef*>(elem->value.pointer);
1149
2.19k
        if (entry->refCount <= 0 && (now - entry->lastAccess) > CACHE_EXPIRATION) {
1150
            // delete this entry
1151
0
            uhash_removeElement(gTZGNCoreCache, elem);
1152
0
        }
1153
2.19k
    }
1154
12
}
1155
1156
TimeZoneGenericNames::TimeZoneGenericNames()
1157
1.26k
: fRef(nullptr) {
1158
1.26k
}
1159
1160
1.26k
TimeZoneGenericNames::~TimeZoneGenericNames() {
1161
1.26k
    umtx_lock(&gTZGNLock);
1162
1.26k
    {
1163
1.26k
        U_ASSERT(fRef->refCount > 0);
1164
        // Just decrement the reference count
1165
1.26k
        fRef->refCount--;
1166
1.26k
    }
1167
1.26k
    umtx_unlock(&gTZGNLock);
1168
1.26k
}
1169
1170
TimeZoneGenericNames*
1171
1.26k
TimeZoneGenericNames::createInstance(const Locale& locale, UErrorCode& status) {
1172
1.26k
    if (U_FAILURE(status)) {
1173
0
        return nullptr;
1174
0
    }
1175
1.26k
    LocalPointer<TimeZoneGenericNames> instance(new TimeZoneGenericNames(), status);
1176
1.26k
    if (U_FAILURE(status)) {
1177
0
        return nullptr;
1178
0
    }
1179
1180
1.26k
    TZGNCoreRef *cacheEntry = nullptr;
1181
1.26k
    {
1182
1.26k
        Mutex lock(&gTZGNLock);
1183
1184
1.26k
        if (!gTZGNCoreCacheInitialized) {
1185
            // Create empty hashtable
1186
1
            gTZGNCoreCache = uhash_open(uhash_hashChars, uhash_compareChars, nullptr, &status);
1187
1
            if (U_SUCCESS(status)) {
1188
1
                uhash_setKeyDeleter(gTZGNCoreCache, uprv_free);
1189
1
                uhash_setValueDeleter(gTZGNCoreCache, deleteTZGNCoreRef);
1190
1
                gTZGNCoreCacheInitialized = true;
1191
1
                ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONEGENERICNAMES, tzgnCore_cleanup);
1192
1
            }
1193
1
        }
1194
1.26k
        if (U_FAILURE(status)) {
1195
0
            return nullptr;
1196
0
        }
1197
1198
        // Check the cache, if not available, create new one and cache
1199
1.26k
        const char *key = locale.getName();
1200
1.26k
        cacheEntry = static_cast<TZGNCoreRef*>(uhash_get(gTZGNCoreCache, key));
1201
1.26k
        if (cacheEntry == nullptr) {
1202
278
            TZGNCore *tzgnCore = nullptr;
1203
278
            char *newKey = nullptr;
1204
1205
278
            tzgnCore = new TZGNCore(locale, status);
1206
278
            if (tzgnCore == nullptr) {
1207
0
                status = U_MEMORY_ALLOCATION_ERROR;
1208
0
            }
1209
278
            if (U_SUCCESS(status)) {
1210
278
                newKey = static_cast<char*>(uprv_malloc(uprv_strlen(key) + 1));
1211
278
                if (newKey == nullptr) {
1212
0
                    status = U_MEMORY_ALLOCATION_ERROR;
1213
278
                } else {
1214
278
                    uprv_strcpy(newKey, key);
1215
278
                }
1216
278
            }
1217
278
            if (U_SUCCESS(status)) {
1218
278
                cacheEntry = static_cast<TZGNCoreRef*>(uprv_malloc(sizeof(TZGNCoreRef)));
1219
278
                if (cacheEntry == nullptr) {
1220
0
                    status = U_MEMORY_ALLOCATION_ERROR;
1221
278
                } else {
1222
278
                    cacheEntry->obj = tzgnCore;
1223
278
                    cacheEntry->refCount = 1;
1224
278
                    cacheEntry->lastAccess = static_cast<double>(uprv_getUTCtime());
1225
1226
278
                    uhash_put(gTZGNCoreCache, newKey, cacheEntry, &status);
1227
278
                }
1228
278
            }
1229
278
            if (U_FAILURE(status)) {
1230
0
                delete tzgnCore;
1231
0
                if (newKey != nullptr) {
1232
0
                    uprv_free(newKey);
1233
0
                }
1234
0
                if (cacheEntry != nullptr) {
1235
0
                    uprv_free(cacheEntry);
1236
0
                }
1237
0
                cacheEntry = nullptr;
1238
0
            }
1239
987
        } else {
1240
            // Update the reference count
1241
987
            cacheEntry->refCount++;
1242
987
            cacheEntry->lastAccess = static_cast<double>(uprv_getUTCtime());
1243
987
        }
1244
1.26k
        gAccessCount++;
1245
1.26k
        if (gAccessCount >= SWEEP_INTERVAL) {
1246
            // sweep
1247
12
            sweepCache();
1248
12
            gAccessCount = 0;
1249
12
        }
1250
1.26k
    }  // End of mutex locked block
1251
1252
1.26k
    if (cacheEntry == nullptr) {
1253
0
        return nullptr;
1254
0
    }
1255
1256
1.26k
    instance->fRef = cacheEntry;
1257
1.26k
    return instance.orphan();
1258
1.26k
}
1259
1260
bool
1261
0
TimeZoneGenericNames::operator==(const TimeZoneGenericNames& other) const {
1262
    // Just compare if the other object also use the same
1263
    // ref entry
1264
0
    return fRef == other.fRef;
1265
0
}
1266
1267
TimeZoneGenericNames*
1268
0
TimeZoneGenericNames::clone() const {
1269
0
    TimeZoneGenericNames* other = new TimeZoneGenericNames();
1270
0
    if (other) {
1271
0
        umtx_lock(&gTZGNLock);
1272
0
        {
1273
            // Just increments the reference count
1274
0
            fRef->refCount++;
1275
0
            other->fRef = fRef;
1276
0
        }
1277
0
        umtx_unlock(&gTZGNLock);
1278
0
    }
1279
0
    return other;
1280
0
}
1281
1282
UnicodeString&
1283
TimeZoneGenericNames::getDisplayName(const TimeZone& tz, UTimeZoneGenericNameType type,
1284
1.26k
                        UDate date, UnicodeString& name) const {
1285
1.26k
    return fRef->obj->getDisplayName(tz, type, date, name);
1286
1.26k
}
1287
1288
UnicodeString&
1289
2
TimeZoneGenericNames::getGenericLocationName(const UnicodeString& tzCanonicalID, UnicodeString& name) const {
1290
2
    return fRef->obj->getGenericLocationName(tzCanonicalID, name);
1291
2
}
1292
1293
int32_t
1294
TimeZoneGenericNames::findBestMatch(const UnicodeString& text, int32_t start, uint32_t types,
1295
0
        UnicodeString& tzID, UTimeZoneFormatTimeType& timeType, UErrorCode& status) const {
1296
0
    return fRef->obj->findBestMatch(text, start, types, tzID, timeType, status);
1297
0
}
1298
1299
U_NAMESPACE_END
1300
#endif