Coverage Report

Created: 2026-01-22 06:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/icu/icu4c/source/i18n/tznames.cpp
Line
Count
Source
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-2015, 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 "unicode/locid.h"
15
#include "unicode/tznames.h"
16
#include "unicode/uenum.h"
17
#include "cmemory.h"
18
#include "cstring.h"
19
#include "mutex.h"
20
#include "putilimp.h"
21
#include "tznames_impl.h"
22
#include "uassert.h"
23
#include "ucln_in.h"
24
#include "uhash.h"
25
#include "umutex.h"
26
#include "uvector.h"
27
28
29
U_NAMESPACE_BEGIN
30
31
// TimeZoneNames object cache handling
32
static UMutex gTimeZoneNamesLock;
33
static UHashtable *gTimeZoneNamesCache = nullptr;
34
static UBool gTimeZoneNamesCacheInitialized = false;
35
36
// Access count - incremented every time up to SWEEP_INTERVAL,
37
// then reset to 0
38
static int32_t gAccessCount = 0;
39
40
// Interval for calling the cache sweep function - every 100 times
41
9.63k
#define SWEEP_INTERVAL 100
42
43
// Cache expiration in millisecond. When a cached entry is no
44
// longer referenced and exceeding this threshold since last
45
// access time, then the cache entry will be deleted by the sweep
46
// function. For now, 3 minutes.
47
0
#define CACHE_EXPIRATION 180000.0
48
49
typedef struct TimeZoneNamesCacheEntry {
50
    TimeZoneNames*  names;
51
    int32_t         refCount;
52
    double          lastAccess;
53
} TimeZoneNamesCacheEntry;
54
55
U_CDECL_BEGIN
56
/**
57
 * Cleanup callback func
58
 */
59
static UBool U_CALLCONV timeZoneNames_cleanup()
60
0
{
61
0
    if (gTimeZoneNamesCache != nullptr) {
62
0
        uhash_close(gTimeZoneNamesCache);
63
0
        gTimeZoneNamesCache = nullptr;
64
0
    }
65
0
    gTimeZoneNamesCacheInitialized = false;
66
0
    return true;
67
0
}
68
69
/**
70
 * Deleter for TimeZoneNamesCacheEntry
71
 */
72
static void U_CALLCONV
73
0
deleteTimeZoneNamesCacheEntry(void *obj) {
74
0
    icu::TimeZoneNamesCacheEntry *entry = (icu::TimeZoneNamesCacheEntry*)obj;
75
0
    if (entry->refCount <= 1) {
76
0
        delete (icu::TimeZoneNamesImpl*) entry->names;
77
0
        uprv_free(entry);
78
0
    } else {
79
0
        entry->refCount--;
80
0
    }
81
0
}
82
U_CDECL_END
83
84
/**
85
 * Function used for removing unreferrenced cache entries exceeding
86
 * the expiration time. This function must be called with in the mutex
87
 * block.
88
 */
89
95
static void sweepCache() {
90
95
    int32_t pos = UHASH_FIRST;
91
95
    const UHashElement* elem;
92
95
    double now = static_cast<double>(uprv_getUTCtime());
93
94
50.6k
    while ((elem = uhash_nextElement(gTimeZoneNamesCache, &pos)) != nullptr) {
95
50.6k
        TimeZoneNamesCacheEntry* entry = static_cast<TimeZoneNamesCacheEntry*>(elem->value.pointer);
96
50.6k
        if (entry->refCount <= 0 && (now - entry->lastAccess) > CACHE_EXPIRATION) {
97
            // delete this entry
98
0
            uhash_removeElement(gTimeZoneNamesCache, elem);
99
0
        }
100
50.6k
    }
101
95
}
102
103
// ---------------------------------------------------
104
// TimeZoneNamesDelegate
105
// ---------------------------------------------------
106
class TimeZoneNamesDelegate : public TimeZoneNames {
107
public:
108
    TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status);
109
    virtual ~TimeZoneNamesDelegate();
110
111
    virtual bool operator==(const TimeZoneNames& other) const override;
112
0
    virtual bool operator!=(const TimeZoneNames& other) const {return !operator==(other);}
113
    virtual TimeZoneNamesDelegate* clone() const override;
114
115
    StringEnumeration* getAvailableMetaZoneIDs(UErrorCode& status) const override;
116
    StringEnumeration* getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const override;
117
    UnicodeString& getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const override;
118
    UnicodeString& getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const override;
119
120
    UnicodeString& getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const override;
121
    UnicodeString& getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const override;
122
123
    UnicodeString& getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const override;
124
125
    void loadAllDisplayNames(UErrorCode& status) override;
126
    void getDisplayNames(const UnicodeString& tzID, const UTimeZoneNameType types[], int32_t numTypes, UDate date, UnicodeString dest[], UErrorCode& status) const override;
127
128
    MatchInfoCollection* find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const override;
129
private:
130
    TimeZoneNamesDelegate();
131
    TimeZoneNamesCacheEntry*    fTZnamesCacheEntry;
132
};
133
134
TimeZoneNamesDelegate::TimeZoneNamesDelegate()
135
0
: fTZnamesCacheEntry(nullptr) {
136
0
}
137
138
9.63k
TimeZoneNamesDelegate::TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status) {
139
9.63k
    Mutex lock(&gTimeZoneNamesLock);
140
9.63k
    if (!gTimeZoneNamesCacheInitialized) {
141
        // Create empty hashtable if it is not already initialized.
142
3
        gTimeZoneNamesCache = uhash_open(uhash_hashChars, uhash_compareChars, nullptr, &status);
143
3
        if (U_SUCCESS(status)) {
144
3
            uhash_setKeyDeleter(gTimeZoneNamesCache, uprv_free);
145
3
            uhash_setValueDeleter(gTimeZoneNamesCache, deleteTimeZoneNamesCacheEntry);
146
3
            gTimeZoneNamesCacheInitialized = true;
147
3
            ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONENAMES, timeZoneNames_cleanup);
148
3
        }
149
3
    }
150
151
9.63k
    if (U_FAILURE(status)) {
152
0
        return;
153
0
    }
154
155
    // Check the cache, if not available, create new one and cache
156
9.63k
    TimeZoneNamesCacheEntry *cacheEntry = nullptr;
157
158
9.63k
    const char *key = locale.getName();
159
9.63k
    cacheEntry = static_cast<TimeZoneNamesCacheEntry*>(uhash_get(gTimeZoneNamesCache, key));
160
9.63k
    if (cacheEntry == nullptr) {
161
1.47k
        TimeZoneNames *tznames = nullptr;
162
1.47k
        char *newKey = nullptr;
163
164
1.47k
        tznames = new TimeZoneNamesImpl(locale, status);
165
1.47k
        if (tznames == nullptr) {
166
0
            status = U_MEMORY_ALLOCATION_ERROR;
167
0
        }
168
1.47k
        if (U_SUCCESS(status)) {
169
1.47k
            newKey = static_cast<char*>(uprv_malloc(uprv_strlen(key) + 1));
170
1.47k
            if (newKey == nullptr) {
171
0
                status = U_MEMORY_ALLOCATION_ERROR;
172
1.47k
            } else {
173
1.47k
                uprv_strcpy(newKey, key);
174
1.47k
            }
175
1.47k
        }
176
1.47k
        if (U_SUCCESS(status)) {
177
1.47k
            cacheEntry = static_cast<TimeZoneNamesCacheEntry*>(uprv_malloc(sizeof(TimeZoneNamesCacheEntry)));
178
1.47k
            if (cacheEntry == nullptr) {
179
0
                status = U_MEMORY_ALLOCATION_ERROR;
180
1.47k
            } else {
181
1.47k
                cacheEntry->names = tznames;
182
                // The initial refCount is 2 because the entry is referenced both
183
                // by this TimeZoneDelegate and by the gTimeZoneNamesCache
184
1.47k
                cacheEntry->refCount = 2;
185
1.47k
                cacheEntry->lastAccess = static_cast<double>(uprv_getUTCtime());
186
187
1.47k
                uhash_put(gTimeZoneNamesCache, newKey, cacheEntry, &status);
188
1.47k
            }
189
1.47k
        }
190
1.47k
        if (U_FAILURE(status)) {
191
0
            delete tznames;
192
0
            if (newKey != nullptr) {
193
0
                uprv_free(newKey);
194
0
            }
195
0
            if (cacheEntry != nullptr) {
196
0
                uprv_free(cacheEntry);
197
0
            }
198
0
            cacheEntry = nullptr;
199
0
        }
200
8.16k
    } else {
201
        // Update the reference count
202
8.16k
        cacheEntry->refCount++;
203
8.16k
        cacheEntry->lastAccess = static_cast<double>(uprv_getUTCtime());
204
8.16k
    }
205
9.63k
    gAccessCount++;
206
9.63k
    if (gAccessCount >= SWEEP_INTERVAL) {
207
        // sweep
208
95
        sweepCache();
209
95
        gAccessCount = 0;
210
95
    }
211
9.63k
    fTZnamesCacheEntry = cacheEntry;
212
9.63k
}
213
214
9.34k
TimeZoneNamesDelegate::~TimeZoneNamesDelegate() {
215
9.34k
    umtx_lock(&gTimeZoneNamesLock);
216
9.34k
    {
217
9.34k
        if (fTZnamesCacheEntry) {
218
9.34k
            if (fTZnamesCacheEntry->refCount <= 1) {
219
0
                delete fTZnamesCacheEntry->names;
220
0
                uprv_free(fTZnamesCacheEntry);
221
9.34k
            } else {
222
                // Just decrement the reference count
223
9.34k
                fTZnamesCacheEntry->refCount--;
224
9.34k
            }
225
9.34k
        }
226
9.34k
    }
227
9.34k
    umtx_unlock(&gTimeZoneNamesLock);
228
9.34k
}
229
230
bool
231
0
TimeZoneNamesDelegate::operator==(const TimeZoneNames& other) const {
232
0
    if (this == &other) {
233
0
        return true;
234
0
    }
235
    // Just compare if the other object also use the same
236
    // cache entry
237
0
    const TimeZoneNamesDelegate* rhs = dynamic_cast<const TimeZoneNamesDelegate*>(&other);
238
0
    if (rhs) {
239
0
        return fTZnamesCacheEntry == rhs->fTZnamesCacheEntry;
240
0
    }
241
0
    return false;
242
0
}
243
244
TimeZoneNamesDelegate*
245
0
TimeZoneNamesDelegate::clone() const {
246
0
    TimeZoneNamesDelegate* other = new TimeZoneNamesDelegate();
247
0
    if (other != nullptr) {
248
0
        umtx_lock(&gTimeZoneNamesLock);
249
0
        {
250
            // Just increment the reference count
251
0
            fTZnamesCacheEntry->refCount++;
252
0
            other->fTZnamesCacheEntry = fTZnamesCacheEntry;
253
0
        }
254
0
        umtx_unlock(&gTimeZoneNamesLock);
255
0
    }
256
0
    return other;
257
0
}
258
259
StringEnumeration*
260
1.27k
TimeZoneNamesDelegate::getAvailableMetaZoneIDs(UErrorCode& status) const {
261
1.27k
    return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(status);
262
1.27k
}
263
264
StringEnumeration*
265
1.55k
TimeZoneNamesDelegate::getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const {
266
1.55k
    return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(tzID, status);
267
1.55k
}
268
269
UnicodeString&
270
4.25k
TimeZoneNamesDelegate::getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const {
271
4.25k
    return fTZnamesCacheEntry->names->getMetaZoneID(tzID, date, mzID);
272
4.25k
}
273
274
UnicodeString&
275
0
TimeZoneNamesDelegate::getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const {
276
0
    return fTZnamesCacheEntry->names->getReferenceZoneID(mzID, region, tzID);
277
0
}
278
279
UnicodeString&
280
3.06k
TimeZoneNamesDelegate::getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const {
281
3.06k
    return fTZnamesCacheEntry->names->getMetaZoneDisplayName(mzID, type, name);
282
3.06k
}
283
284
UnicodeString&
285
7.67k
TimeZoneNamesDelegate::getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const {
286
7.67k
    return fTZnamesCacheEntry->names->getTimeZoneDisplayName(tzID, type, name);
287
7.67k
}
288
289
UnicodeString&
290
1.31k
TimeZoneNamesDelegate::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const {
291
1.31k
    return fTZnamesCacheEntry->names->getExemplarLocationName(tzID, name);
292
1.31k
}
293
294
void
295
426
TimeZoneNamesDelegate::loadAllDisplayNames(UErrorCode& status) {
296
426
    fTZnamesCacheEntry->names->loadAllDisplayNames(status);
297
426
}
298
299
void
300
271k
TimeZoneNamesDelegate::getDisplayNames(const UnicodeString& tzID, const UTimeZoneNameType types[], int32_t numTypes, UDate date, UnicodeString dest[], UErrorCode& status) const {
301
271k
    fTZnamesCacheEntry->names->getDisplayNames(tzID, types, numTypes, date, dest, status);
302
271k
}
303
304
TimeZoneNames::MatchInfoCollection*
305
0
TimeZoneNamesDelegate::find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const {
306
0
    return fTZnamesCacheEntry->names->find(text, start, types, status);
307
0
}
308
309
// ---------------------------------------------------
310
// TimeZoneNames base class
311
// ---------------------------------------------------
312
10.6k
TimeZoneNames::~TimeZoneNames() {
313
10.6k
}
314
315
TimeZoneNames*
316
9.63k
TimeZoneNames::createInstance(const Locale& locale, UErrorCode& status) {
317
9.63k
    TimeZoneNames *instance = nullptr;
318
9.63k
    if (U_SUCCESS(status)) {
319
9.63k
        instance = new TimeZoneNamesDelegate(locale, status);
320
9.63k
        if (instance == nullptr && U_SUCCESS(status)) {
321
0
            status = U_MEMORY_ALLOCATION_ERROR;
322
0
        }
323
9.63k
    }
324
9.63k
    return instance;
325
9.63k
}
326
327
TimeZoneNames*
328
1.27k
TimeZoneNames::createTZDBInstance(const Locale& locale, UErrorCode& status) {
329
1.27k
    TimeZoneNames *instance = nullptr;
330
1.27k
    if (U_SUCCESS(status)) {
331
1.27k
        instance = new TZDBTimeZoneNames(locale);
332
1.27k
        if (instance == nullptr && U_SUCCESS(status)) {
333
0
            status = U_MEMORY_ALLOCATION_ERROR;
334
0
        }
335
1.27k
    }
336
1.27k
    return instance;
337
1.27k
}
338
339
UnicodeString&
340
1.27k
TimeZoneNames::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const {
341
1.27k
    return TimeZoneNamesImpl::getDefaultExemplarLocationName(tzID, name);
342
1.27k
}
343
344
UnicodeString&
345
6.47k
TimeZoneNames::getDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UDate date, UnicodeString& name) const {
346
6.47k
    getTimeZoneDisplayName(tzID, type, name);
347
6.47k
    if (name.isEmpty()) {
348
3.06k
        char16_t mzIDBuf[32];
349
3.06k
        UnicodeString mzID(mzIDBuf, 0, UPRV_LENGTHOF(mzIDBuf));
350
3.06k
        getMetaZoneID(tzID, date, mzID);
351
3.06k
        getMetaZoneDisplayName(mzID, type, name);
352
3.06k
    }
353
6.47k
    return name;
354
6.47k
}
355
356
// Empty default implementation, to be overridden in tznames_impl.cpp.
357
void
358
0
TimeZoneNames::loadAllDisplayNames(UErrorCode& /*status*/) {
359
0
}
360
361
// A default, lightweight implementation of getDisplayNames.
362
// Overridden in tznames_impl.cpp.
363
void
364
0
TimeZoneNames::getDisplayNames(const UnicodeString& tzID, const UTimeZoneNameType types[], int32_t numTypes, UDate date, UnicodeString dest[], UErrorCode& status) const {
365
0
    if (U_FAILURE(status)) { return; }
366
0
    if (tzID.isEmpty()) { return; }
367
0
    UnicodeString mzID;
368
0
    for (int i = 0; i < numTypes; i++) {
369
0
        getTimeZoneDisplayName(tzID, types[i], dest[i]);
370
0
        if (dest[i].isEmpty()) {
371
0
            if (mzID.isEmpty()) {
372
0
                getMetaZoneID(tzID, date, mzID);
373
0
            }
374
0
            getMetaZoneDisplayName(mzID, types[i], dest[i]);
375
0
        }
376
0
    }
377
0
}
378
379
380
struct MatchInfo : UMemory {
381
    UTimeZoneNameType nameType;
382
    UnicodeString id;
383
    int32_t matchLength;
384
    UBool isTZID;
385
386
0
    MatchInfo(UTimeZoneNameType nameType, int32_t matchLength, const UnicodeString* tzID, const UnicodeString* mzID) {
387
0
        this->nameType = nameType;
388
0
        this->matchLength = matchLength;
389
0
        if (tzID != nullptr) {
390
0
            this->id.setTo(*tzID);
391
0
            this->isTZID = true;
392
0
        } else {
393
0
            this->id.setTo(*mzID);
394
0
            this->isTZID = false;
395
0
        }
396
0
    }
397
};
398
399
U_CDECL_BEGIN
400
static void U_CALLCONV
401
0
deleteMatchInfo(void *obj) {
402
0
    delete static_cast<MatchInfo *>(obj);
403
0
}
404
U_CDECL_END
405
406
// ---------------------------------------------------
407
// MatchInfoCollection class
408
// ---------------------------------------------------
409
TimeZoneNames::MatchInfoCollection::MatchInfoCollection()
410
0
: fMatches(nullptr) {
411
0
}
412
413
0
TimeZoneNames::MatchInfoCollection::~MatchInfoCollection() {
414
0
    delete fMatches;
415
0
}
416
417
void
418
TimeZoneNames::MatchInfoCollection::addZone(UTimeZoneNameType nameType, int32_t matchLength,
419
0
            const UnicodeString& tzID, UErrorCode& status) {
420
0
    if (U_FAILURE(status)) {
421
0
        return;
422
0
    }
423
0
    LocalPointer <MatchInfo> matchInfo(new MatchInfo(nameType, matchLength, &tzID, nullptr), status);
424
0
    UVector *matchesVec = matches(status);
425
0
    if (U_FAILURE(status)) {
426
0
        return;
427
0
    }
428
0
    matchesVec->adoptElement(matchInfo.orphan(), status);
429
0
}
430
431
void
432
TimeZoneNames::MatchInfoCollection::addMetaZone(UTimeZoneNameType nameType, int32_t matchLength,
433
0
            const UnicodeString& mzID, UErrorCode& status) {
434
0
    if (U_FAILURE(status)) {
435
0
        return;
436
0
    }
437
0
    LocalPointer<MatchInfo> matchInfo(new MatchInfo(nameType, matchLength, nullptr, &mzID), status);
438
0
    UVector *matchesVec = matches(status);
439
0
    if (U_FAILURE(status)) {
440
0
        return;
441
0
    }
442
0
    matchesVec->adoptElement(matchInfo.orphan(), status);
443
0
}
444
445
int32_t
446
0
TimeZoneNames::MatchInfoCollection::size() const {
447
0
    if (fMatches == nullptr) {
448
0
        return 0;
449
0
    }
450
0
    return fMatches->size();
451
0
}
452
453
UTimeZoneNameType
454
0
TimeZoneNames::MatchInfoCollection::getNameTypeAt(int32_t idx) const {
455
0
    const MatchInfo* match = static_cast<const MatchInfo*>(fMatches->elementAt(idx));
456
0
    if (match) {
457
0
        return match->nameType;
458
0
    }
459
0
    return UTZNM_UNKNOWN;
460
0
}
461
462
int32_t
463
0
TimeZoneNames::MatchInfoCollection::getMatchLengthAt(int32_t idx) const {
464
0
    const MatchInfo* match = static_cast<const MatchInfo*>(fMatches->elementAt(idx));
465
0
    if (match) {
466
0
        return match->matchLength;
467
0
    }
468
0
    return 0;
469
0
}
470
471
UBool
472
0
TimeZoneNames::MatchInfoCollection::getTimeZoneIDAt(int32_t idx, UnicodeString& tzID) const {
473
0
    tzID.remove();
474
0
    const MatchInfo* match = static_cast<const MatchInfo*>(fMatches->elementAt(idx));
475
0
    if (match && match->isTZID) {
476
0
        tzID.setTo(match->id);
477
0
        return true;
478
0
    }
479
0
    return false;
480
0
}
481
482
UBool
483
0
TimeZoneNames::MatchInfoCollection::getMetaZoneIDAt(int32_t idx, UnicodeString& mzID) const {
484
0
    mzID.remove();
485
0
    const MatchInfo* match = static_cast<const MatchInfo*>(fMatches->elementAt(idx));
486
0
    if (match && !match->isTZID) {
487
0
        mzID.setTo(match->id);
488
0
        return true;
489
0
    }
490
0
    return false;
491
0
}
492
493
UVector*
494
0
TimeZoneNames::MatchInfoCollection::matches(UErrorCode& status) {
495
0
    if (U_FAILURE(status)) {
496
0
        return nullptr;
497
0
    }
498
0
    if (fMatches != nullptr) {
499
0
        return fMatches;
500
0
    }
501
0
    fMatches = new UVector(deleteMatchInfo, nullptr, status);
502
0
    if (fMatches == nullptr) {
503
0
        status = U_MEMORY_ALLOCATION_ERROR;
504
0
    } else if (U_FAILURE(status)) {
505
0
        delete fMatches;
506
0
        fMatches = nullptr;
507
0
    }
508
0
    return fMatches;
509
0
}
510
511
512
U_NAMESPACE_END
513
#endif