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 #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 #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 */
timeZoneNames_cleanup()59 static UBool U_CALLCONV timeZoneNames_cleanup()
60 {
61 if (gTimeZoneNamesCache != nullptr) {
62 uhash_close(gTimeZoneNamesCache);
63 gTimeZoneNamesCache = nullptr;
64 }
65 gTimeZoneNamesCacheInitialized = false;
66 return true;
67 }
68
69 /**
70 * Deleter for TimeZoneNamesCacheEntry
71 */
72 static void U_CALLCONV
deleteTimeZoneNamesCacheEntry(void * obj)73 deleteTimeZoneNamesCacheEntry(void *obj) {
74 icu::TimeZoneNamesCacheEntry *entry = (icu::TimeZoneNamesCacheEntry*)obj;
75 delete (icu::TimeZoneNamesImpl*) entry->names;
76 uprv_free(entry);
77 }
78 U_CDECL_END
79
80 /**
81 * Function used for removing unreferrenced cache entries exceeding
82 * the expiration time. This function must be called with in the mutex
83 * block.
84 */
sweepCache()85 static void sweepCache() {
86 int32_t pos = UHASH_FIRST;
87 const UHashElement* elem;
88 double now = (double)uprv_getUTCtime();
89
90 while ((elem = uhash_nextElement(gTimeZoneNamesCache, &pos)) != nullptr) {
91 TimeZoneNamesCacheEntry *entry = (TimeZoneNamesCacheEntry *)elem->value.pointer;
92 if (entry->refCount <= 0 && (now - entry->lastAccess) > CACHE_EXPIRATION) {
93 // delete this entry
94 uhash_removeElement(gTimeZoneNamesCache, elem);
95 }
96 }
97 }
98
99 // ---------------------------------------------------
100 // TimeZoneNamesDelegate
101 // ---------------------------------------------------
102 class TimeZoneNamesDelegate : public TimeZoneNames {
103 public:
104 TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status);
105 virtual ~TimeZoneNamesDelegate();
106
107 virtual bool operator==(const TimeZoneNames& other) const override;
operator !=(const TimeZoneNames & other) const108 virtual bool operator!=(const TimeZoneNames& other) const {return !operator==(other);}
109 virtual TimeZoneNamesDelegate* clone() const override;
110
111 StringEnumeration* getAvailableMetaZoneIDs(UErrorCode& status) const override;
112 StringEnumeration* getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const override;
113 UnicodeString& getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const override;
114 UnicodeString& getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const override;
115
116 UnicodeString& getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const override;
117 UnicodeString& getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const override;
118
119 UnicodeString& getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const override;
120
121 void loadAllDisplayNames(UErrorCode& status) override;
122 void getDisplayNames(const UnicodeString& tzID, const UTimeZoneNameType types[], int32_t numTypes, UDate date, UnicodeString dest[], UErrorCode& status) const override;
123
124 MatchInfoCollection* find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const override;
125 private:
126 TimeZoneNamesDelegate();
127 TimeZoneNamesCacheEntry* fTZnamesCacheEntry;
128 };
129
TimeZoneNamesDelegate()130 TimeZoneNamesDelegate::TimeZoneNamesDelegate()
131 : fTZnamesCacheEntry(nullptr) {
132 }
133
TimeZoneNamesDelegate(const Locale & locale,UErrorCode & status)134 TimeZoneNamesDelegate::TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status) {
135 Mutex lock(&gTimeZoneNamesLock);
136 if (!gTimeZoneNamesCacheInitialized) {
137 // Create empty hashtable if it is not already initialized.
138 gTimeZoneNamesCache = uhash_open(uhash_hashChars, uhash_compareChars, nullptr, &status);
139 if (U_SUCCESS(status)) {
140 uhash_setKeyDeleter(gTimeZoneNamesCache, uprv_free);
141 uhash_setValueDeleter(gTimeZoneNamesCache, deleteTimeZoneNamesCacheEntry);
142 gTimeZoneNamesCacheInitialized = true;
143 ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONENAMES, timeZoneNames_cleanup);
144 }
145 }
146
147 if (U_FAILURE(status)) {
148 return;
149 }
150
151 // Check the cache, if not available, create new one and cache
152 TimeZoneNamesCacheEntry *cacheEntry = nullptr;
153
154 const char *key = locale.getName();
155 cacheEntry = (TimeZoneNamesCacheEntry *)uhash_get(gTimeZoneNamesCache, key);
156 if (cacheEntry == nullptr) {
157 TimeZoneNames *tznames = nullptr;
158 char *newKey = nullptr;
159
160 tznames = new TimeZoneNamesImpl(locale, status);
161 if (tznames == nullptr) {
162 status = U_MEMORY_ALLOCATION_ERROR;
163 }
164 if (U_SUCCESS(status)) {
165 newKey = (char *)uprv_malloc(uprv_strlen(key) + 1);
166 if (newKey == nullptr) {
167 status = U_MEMORY_ALLOCATION_ERROR;
168 } else {
169 uprv_strcpy(newKey, key);
170 }
171 }
172 if (U_SUCCESS(status)) {
173 cacheEntry = (TimeZoneNamesCacheEntry *)uprv_malloc(sizeof(TimeZoneNamesCacheEntry));
174 if (cacheEntry == nullptr) {
175 status = U_MEMORY_ALLOCATION_ERROR;
176 } else {
177 cacheEntry->names = tznames;
178 cacheEntry->refCount = 1;
179 cacheEntry->lastAccess = (double)uprv_getUTCtime();
180
181 uhash_put(gTimeZoneNamesCache, newKey, cacheEntry, &status);
182 }
183 }
184 if (U_FAILURE(status)) {
185 delete tznames;
186 if (newKey != nullptr) {
187 uprv_free(newKey);
188 }
189 if (cacheEntry != nullptr) {
190 uprv_free(cacheEntry);
191 }
192 cacheEntry = nullptr;
193 }
194 } else {
195 // Update the reference count
196 cacheEntry->refCount++;
197 cacheEntry->lastAccess = (double)uprv_getUTCtime();
198 }
199 gAccessCount++;
200 if (gAccessCount >= SWEEP_INTERVAL) {
201 // sweep
202 sweepCache();
203 gAccessCount = 0;
204 }
205 fTZnamesCacheEntry = cacheEntry;
206 }
207
~TimeZoneNamesDelegate()208 TimeZoneNamesDelegate::~TimeZoneNamesDelegate() {
209 umtx_lock(&gTimeZoneNamesLock);
210 {
211 if (fTZnamesCacheEntry) {
212 U_ASSERT(fTZnamesCacheEntry->refCount > 0);
213 // Just decrement the reference count
214 fTZnamesCacheEntry->refCount--;
215 }
216 }
217 umtx_unlock(&gTimeZoneNamesLock);
218 }
219
220 bool
operator ==(const TimeZoneNames & other) const221 TimeZoneNamesDelegate::operator==(const TimeZoneNames& other) const {
222 if (this == &other) {
223 return true;
224 }
225 // Just compare if the other object also use the same
226 // cache entry
227 const TimeZoneNamesDelegate* rhs = dynamic_cast<const TimeZoneNamesDelegate*>(&other);
228 if (rhs) {
229 return fTZnamesCacheEntry == rhs->fTZnamesCacheEntry;
230 }
231 return false;
232 }
233
234 TimeZoneNamesDelegate*
clone() const235 TimeZoneNamesDelegate::clone() const {
236 TimeZoneNamesDelegate* other = new TimeZoneNamesDelegate();
237 if (other != nullptr) {
238 umtx_lock(&gTimeZoneNamesLock);
239 {
240 // Just increment the reference count
241 fTZnamesCacheEntry->refCount++;
242 other->fTZnamesCacheEntry = fTZnamesCacheEntry;
243 }
244 umtx_unlock(&gTimeZoneNamesLock);
245 }
246 return other;
247 }
248
249 StringEnumeration*
getAvailableMetaZoneIDs(UErrorCode & status) const250 TimeZoneNamesDelegate::getAvailableMetaZoneIDs(UErrorCode& status) const {
251 return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(status);
252 }
253
254 StringEnumeration*
getAvailableMetaZoneIDs(const UnicodeString & tzID,UErrorCode & status) const255 TimeZoneNamesDelegate::getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const {
256 return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(tzID, status);
257 }
258
259 UnicodeString&
getMetaZoneID(const UnicodeString & tzID,UDate date,UnicodeString & mzID) const260 TimeZoneNamesDelegate::getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const {
261 return fTZnamesCacheEntry->names->getMetaZoneID(tzID, date, mzID);
262 }
263
264 UnicodeString&
getReferenceZoneID(const UnicodeString & mzID,const char * region,UnicodeString & tzID) const265 TimeZoneNamesDelegate::getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const {
266 return fTZnamesCacheEntry->names->getReferenceZoneID(mzID, region, tzID);
267 }
268
269 UnicodeString&
getMetaZoneDisplayName(const UnicodeString & mzID,UTimeZoneNameType type,UnicodeString & name) const270 TimeZoneNamesDelegate::getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const {
271 return fTZnamesCacheEntry->names->getMetaZoneDisplayName(mzID, type, name);
272 }
273
274 UnicodeString&
getTimeZoneDisplayName(const UnicodeString & tzID,UTimeZoneNameType type,UnicodeString & name) const275 TimeZoneNamesDelegate::getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const {
276 return fTZnamesCacheEntry->names->getTimeZoneDisplayName(tzID, type, name);
277 }
278
279 UnicodeString&
getExemplarLocationName(const UnicodeString & tzID,UnicodeString & name) const280 TimeZoneNamesDelegate::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const {
281 return fTZnamesCacheEntry->names->getExemplarLocationName(tzID, name);
282 }
283
284 void
loadAllDisplayNames(UErrorCode & status)285 TimeZoneNamesDelegate::loadAllDisplayNames(UErrorCode& status) {
286 fTZnamesCacheEntry->names->loadAllDisplayNames(status);
287 }
288
289 void
getDisplayNames(const UnicodeString & tzID,const UTimeZoneNameType types[],int32_t numTypes,UDate date,UnicodeString dest[],UErrorCode & status) const290 TimeZoneNamesDelegate::getDisplayNames(const UnicodeString& tzID, const UTimeZoneNameType types[], int32_t numTypes, UDate date, UnicodeString dest[], UErrorCode& status) const {
291 fTZnamesCacheEntry->names->getDisplayNames(tzID, types, numTypes, date, dest, status);
292 }
293
294 TimeZoneNames::MatchInfoCollection*
find(const UnicodeString & text,int32_t start,uint32_t types,UErrorCode & status) const295 TimeZoneNamesDelegate::find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const {
296 return fTZnamesCacheEntry->names->find(text, start, types, status);
297 }
298
299 // ---------------------------------------------------
300 // TimeZoneNames base class
301 // ---------------------------------------------------
~TimeZoneNames()302 TimeZoneNames::~TimeZoneNames() {
303 }
304
305 TimeZoneNames*
createInstance(const Locale & locale,UErrorCode & status)306 TimeZoneNames::createInstance(const Locale& locale, UErrorCode& status) {
307 TimeZoneNames *instance = nullptr;
308 if (U_SUCCESS(status)) {
309 instance = new TimeZoneNamesDelegate(locale, status);
310 if (instance == nullptr && U_SUCCESS(status)) {
311 status = U_MEMORY_ALLOCATION_ERROR;
312 }
313 }
314 return instance;
315 }
316
317 TimeZoneNames*
createTZDBInstance(const Locale & locale,UErrorCode & status)318 TimeZoneNames::createTZDBInstance(const Locale& locale, UErrorCode& status) {
319 TimeZoneNames *instance = nullptr;
320 if (U_SUCCESS(status)) {
321 instance = new TZDBTimeZoneNames(locale);
322 if (instance == nullptr && U_SUCCESS(status)) {
323 status = U_MEMORY_ALLOCATION_ERROR;
324 }
325 }
326 return instance;
327 }
328
329 UnicodeString&
getExemplarLocationName(const UnicodeString & tzID,UnicodeString & name) const330 TimeZoneNames::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const {
331 return TimeZoneNamesImpl::getDefaultExemplarLocationName(tzID, name);
332 }
333
334 UnicodeString&
getDisplayName(const UnicodeString & tzID,UTimeZoneNameType type,UDate date,UnicodeString & name) const335 TimeZoneNames::getDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UDate date, UnicodeString& name) const {
336 getTimeZoneDisplayName(tzID, type, name);
337 if (name.isEmpty()) {
338 char16_t mzIDBuf[32];
339 UnicodeString mzID(mzIDBuf, 0, UPRV_LENGTHOF(mzIDBuf));
340 getMetaZoneID(tzID, date, mzID);
341 getMetaZoneDisplayName(mzID, type, name);
342 }
343 return name;
344 }
345
346 // Empty default implementation, to be overridden in tznames_impl.cpp.
347 void
loadAllDisplayNames(UErrorCode &)348 TimeZoneNames::loadAllDisplayNames(UErrorCode& /*status*/) {
349 }
350
351 // A default, lightweight implementation of getDisplayNames.
352 // Overridden in tznames_impl.cpp.
353 void
getDisplayNames(const UnicodeString & tzID,const UTimeZoneNameType types[],int32_t numTypes,UDate date,UnicodeString dest[],UErrorCode & status) const354 TimeZoneNames::getDisplayNames(const UnicodeString& tzID, const UTimeZoneNameType types[], int32_t numTypes, UDate date, UnicodeString dest[], UErrorCode& status) const {
355 if (U_FAILURE(status)) { return; }
356 if (tzID.isEmpty()) { return; }
357 UnicodeString mzID;
358 for (int i = 0; i < numTypes; i++) {
359 getTimeZoneDisplayName(tzID, types[i], dest[i]);
360 if (dest[i].isEmpty()) {
361 if (mzID.isEmpty()) {
362 getMetaZoneID(tzID, date, mzID);
363 }
364 getMetaZoneDisplayName(mzID, types[i], dest[i]);
365 }
366 }
367 }
368
369
370 struct MatchInfo : UMemory {
371 UTimeZoneNameType nameType;
372 UnicodeString id;
373 int32_t matchLength;
374 UBool isTZID;
375
MatchInfoMatchInfo376 MatchInfo(UTimeZoneNameType nameType, int32_t matchLength, const UnicodeString* tzID, const UnicodeString* mzID) {
377 this->nameType = nameType;
378 this->matchLength = matchLength;
379 if (tzID != nullptr) {
380 this->id.setTo(*tzID);
381 this->isTZID = true;
382 } else {
383 this->id.setTo(*mzID);
384 this->isTZID = false;
385 }
386 }
387 };
388
389 U_CDECL_BEGIN
390 static void U_CALLCONV
deleteMatchInfo(void * obj)391 deleteMatchInfo(void *obj) {
392 delete static_cast<MatchInfo *>(obj);
393 }
394 U_CDECL_END
395
396 // ---------------------------------------------------
397 // MatchInfoCollection class
398 // ---------------------------------------------------
MatchInfoCollection()399 TimeZoneNames::MatchInfoCollection::MatchInfoCollection()
400 : fMatches(nullptr) {
401 }
402
~MatchInfoCollection()403 TimeZoneNames::MatchInfoCollection::~MatchInfoCollection() {
404 delete fMatches;
405 }
406
407 void
addZone(UTimeZoneNameType nameType,int32_t matchLength,const UnicodeString & tzID,UErrorCode & status)408 TimeZoneNames::MatchInfoCollection::addZone(UTimeZoneNameType nameType, int32_t matchLength,
409 const UnicodeString& tzID, UErrorCode& status) {
410 if (U_FAILURE(status)) {
411 return;
412 }
413 LocalPointer <MatchInfo> matchInfo(new MatchInfo(nameType, matchLength, &tzID, nullptr), status);
414 UVector *matchesVec = matches(status);
415 if (U_FAILURE(status)) {
416 return;
417 }
418 matchesVec->adoptElement(matchInfo.orphan(), status);
419 }
420
421 void
addMetaZone(UTimeZoneNameType nameType,int32_t matchLength,const UnicodeString & mzID,UErrorCode & status)422 TimeZoneNames::MatchInfoCollection::addMetaZone(UTimeZoneNameType nameType, int32_t matchLength,
423 const UnicodeString& mzID, UErrorCode& status) {
424 if (U_FAILURE(status)) {
425 return;
426 }
427 LocalPointer<MatchInfo> matchInfo(new MatchInfo(nameType, matchLength, nullptr, &mzID), status);
428 UVector *matchesVec = matches(status);
429 if (U_FAILURE(status)) {
430 return;
431 }
432 matchesVec->adoptElement(matchInfo.orphan(), status);
433 }
434
435 int32_t
size() const436 TimeZoneNames::MatchInfoCollection::size() const {
437 if (fMatches == nullptr) {
438 return 0;
439 }
440 return fMatches->size();
441 }
442
443 UTimeZoneNameType
getNameTypeAt(int32_t idx) const444 TimeZoneNames::MatchInfoCollection::getNameTypeAt(int32_t idx) const {
445 const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx);
446 if (match) {
447 return match->nameType;
448 }
449 return UTZNM_UNKNOWN;
450 }
451
452 int32_t
getMatchLengthAt(int32_t idx) const453 TimeZoneNames::MatchInfoCollection::getMatchLengthAt(int32_t idx) const {
454 const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx);
455 if (match) {
456 return match->matchLength;
457 }
458 return 0;
459 }
460
461 UBool
getTimeZoneIDAt(int32_t idx,UnicodeString & tzID) const462 TimeZoneNames::MatchInfoCollection::getTimeZoneIDAt(int32_t idx, UnicodeString& tzID) const {
463 tzID.remove();
464 const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx);
465 if (match && match->isTZID) {
466 tzID.setTo(match->id);
467 return true;
468 }
469 return false;
470 }
471
472 UBool
getMetaZoneIDAt(int32_t idx,UnicodeString & mzID) const473 TimeZoneNames::MatchInfoCollection::getMetaZoneIDAt(int32_t idx, UnicodeString& mzID) const {
474 mzID.remove();
475 const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx);
476 if (match && !match->isTZID) {
477 mzID.setTo(match->id);
478 return true;
479 }
480 return false;
481 }
482
483 UVector*
matches(UErrorCode & status)484 TimeZoneNames::MatchInfoCollection::matches(UErrorCode& status) {
485 if (U_FAILURE(status)) {
486 return nullptr;
487 }
488 if (fMatches != nullptr) {
489 return fMatches;
490 }
491 fMatches = new UVector(deleteMatchInfo, nullptr, status);
492 if (fMatches == nullptr) {
493 status = U_MEMORY_ALLOCATION_ERROR;
494 } else if (U_FAILURE(status)) {
495 delete fMatches;
496 fMatches = nullptr;
497 }
498 return fMatches;
499 }
500
501
502 U_NAMESPACE_END
503 #endif
504