File: | root/firefox-clang/intl/icu/source/i18n/tzgnames.cpp |
Warning: | line 643, column 17 Called C++ object pointer is null |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
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_FORMATTING0 | |||
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_BEGINnamespace icu_77 { | |||
40 | ||||
41 | #define ZID_KEY_MAX128 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(86400000); | |||
54 | ||||
55 | ||||
56 | ||||
57 | U_CDECL_BEGINextern "C" { | |||
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 | hashPartialLocationKey(const UHashTok key) { | |||
70 | // <tzID>&<mzID>#[L|S] | |||
71 | PartialLocationKey *p = (PartialLocationKey *)key.pointer; | |||
72 | UnicodeString str(p->tzID); | |||
73 | str.append((char16_t)0x26) | |||
74 | .append(p->mzID, -1) | |||
75 | .append((char16_t)0x23) | |||
76 | .append((char16_t)(p->isLong ? 0x4C : 0x53)); | |||
77 | return str.hashCode(); | |||
78 | } | |||
79 | ||||
80 | /** | |||
81 | * Comparer for partial location name hash key | |||
82 | */ | |||
83 | static UBool U_CALLCONV | |||
84 | comparePartialLocationKey(const UHashTok key1, const UHashTok key2) { | |||
85 | PartialLocationKey *p1 = (PartialLocationKey *)key1.pointer; | |||
86 | PartialLocationKey *p2 = (PartialLocationKey *)key2.pointer; | |||
87 | ||||
88 | if (p1 == p2) { | |||
89 | return true; | |||
90 | } | |||
91 | if (p1 == nullptr || p2 == nullptr) { | |||
92 | return false; | |||
93 | } | |||
94 | // We just check identity of tzID/mzID | |||
95 | return (p1->tzID == p2->tzID && p1->mzID == p2->mzID && p1->isLong == p2->isLong); | |||
96 | } | |||
97 | ||||
98 | /** | |||
99 | * Deleter for GNameInfo | |||
100 | */ | |||
101 | static void U_CALLCONV | |||
102 | deleteGNameInfo(void *obj) { | |||
103 | uprv_freeuprv_free_77(obj); | |||
104 | } | |||
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 | : fMatches(matches) { | |||
144 | } | |||
145 | ||||
146 | TimeZoneGenericNameMatchInfo::~TimeZoneGenericNameMatchInfo() { | |||
147 | delete fMatches; | |||
148 | } | |||
149 | ||||
150 | int32_t | |||
151 | TimeZoneGenericNameMatchInfo::size() const { | |||
152 | if (fMatches == nullptr) { | |||
153 | return 0; | |||
154 | } | |||
155 | return fMatches->size(); | |||
156 | } | |||
157 | ||||
158 | UTimeZoneGenericNameType | |||
159 | TimeZoneGenericNameMatchInfo::getGenericNameType(int32_t index) const { | |||
160 | GMatchInfo* minfo = static_cast<GMatchInfo*>(fMatches->elementAt(index)); | |||
161 | if (minfo != nullptr) { | |||
162 | return static_cast<UTimeZoneGenericNameType>(minfo->gnameInfo->type); | |||
163 | } | |||
164 | return UTZGNM_UNKNOWN; | |||
165 | } | |||
166 | ||||
167 | int32_t | |||
168 | TimeZoneGenericNameMatchInfo::getMatchLength(int32_t index) const { | |||
169 | ZMatchInfo* minfo = static_cast<ZMatchInfo*>(fMatches->elementAt(index)); | |||
170 | if (minfo != nullptr) { | |||
171 | return minfo->matchLength; | |||
172 | } | |||
173 | return -1; | |||
174 | } | |||
175 | ||||
176 | UnicodeString& | |||
177 | TimeZoneGenericNameMatchInfo::getTimeZoneID(int32_t index, UnicodeString& tzID) const { | |||
178 | GMatchInfo* minfo = static_cast<GMatchInfo*>(fMatches->elementAt(index)); | |||
179 | if (minfo != nullptr && minfo->gnameInfo->tzID != nullptr) { | |||
180 | tzID.setTo(true, minfo->gnameInfo->tzID, -1); | |||
181 | } else { | |||
182 | tzID.setToBogus(); | |||
183 | } | |||
184 | return tzID; | |||
185 | } | |||
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 | : fTypes(types), fResults(nullptr), fMaxMatchLen(0) { | |||
206 | } | |||
207 | ||||
208 | GNameSearchHandler::~GNameSearchHandler() { | |||
209 | delete fResults; | |||
210 | } | |||
211 | ||||
212 | UBool | |||
213 | GNameSearchHandler::handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status) { | |||
214 | if (U_FAILURE(status)) { | |||
215 | return false; | |||
216 | } | |||
217 | if (node->hasValues()) { | |||
218 | int32_t valuesCount = node->countValues(); | |||
219 | for (int32_t i = 0; i < valuesCount; i++) { | |||
220 | GNameInfo *nameinfo = (ZNameInfo *)node->getValue(i); | |||
221 | if (nameinfo == nullptr) { | |||
222 | break; | |||
223 | } | |||
224 | if ((nameinfo->type & fTypes) != 0) { | |||
225 | // matches a requested type | |||
226 | if (fResults == nullptr) { | |||
227 | LocalPointer<UVector> lpResults(new UVector(uprv_freeuprv_free_77, nullptr, status), status); | |||
228 | if (U_FAILURE(status)) { | |||
229 | return false; | |||
230 | } | |||
231 | fResults = lpResults.orphan(); | |||
232 | } | |||
233 | GMatchInfo* gmatch = static_cast<GMatchInfo*>(uprv_mallocuprv_malloc_77(sizeof(GMatchInfo))); | |||
234 | if (gmatch == nullptr) { | |||
235 | status = U_MEMORY_ALLOCATION_ERROR; | |||
236 | return false; | |||
237 | } | |||
238 | // add the match to the vector | |||
239 | gmatch->gnameInfo = nameinfo; | |||
240 | gmatch->matchLength = matchLength; | |||
241 | gmatch->timeType = UTZFMT_TIME_TYPE_UNKNOWN; | |||
242 | fResults->adoptElement(gmatch, status); | |||
243 | if (U_FAILURE(status)) { | |||
244 | return false; | |||
245 | } | |||
246 | if (matchLength > fMaxMatchLen) { | |||
247 | fMaxMatchLen = matchLength; | |||
248 | } | |||
249 | } | |||
250 | } | |||
251 | } | |||
252 | return true; | |||
253 | } | |||
254 | ||||
255 | UVector* | |||
256 | GNameSearchHandler::getMatches(int32_t& maxMatchLen) { | |||
257 | // give the ownership to the caller | |||
258 | UVector *results = fResults; | |||
259 | maxMatchLen = fMaxMatchLen; | |||
260 | ||||
261 | // reset | |||
262 | fResults = nullptr; | |||
263 | fMaxMatchLen = 0; | |||
264 | return results; | |||
265 | } | |||
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 | : fLocale(locale), | |||
331 | fTimeZoneNames(nullptr), | |||
332 | fLocationNamesMap(nullptr), | |||
333 | fPartialLocationNamesMap(nullptr), | |||
334 | fLocaleDisplayNames(nullptr), | |||
335 | fStringPool(status), | |||
336 | fGNamesTrie(true, deleteGNameInfo), | |||
337 | fGNamesTrieFullyLoaded(false), | |||
338 | fTargetRegion() { | |||
339 | initialize(locale, status); | |||
340 | } | |||
341 | ||||
342 | TZGNCore::~TZGNCore() { | |||
343 | cleanup(); | |||
344 | } | |||
345 | ||||
346 | void | |||
347 | TZGNCore::initialize(const Locale& locale, UErrorCode& status) { | |||
348 | if (U_FAILURE(status)) { | |||
349 | return; | |||
350 | } | |||
351 | ||||
352 | // TimeZoneNames | |||
353 | fTimeZoneNames = TimeZoneNames::createInstance(locale, status); | |||
354 | if (U_FAILURE(status)) { | |||
355 | return; | |||
356 | } | |||
357 | ||||
358 | // Initialize format patterns | |||
359 | UnicodeString rpat(true, gDefRegionPattern, -1); | |||
360 | UnicodeString fpat(true, gDefFallbackPattern, -1); | |||
361 | ||||
362 | UErrorCode tmpsts = U_ZERO_ERROR; // OK with fallback warning.. | |||
363 | UResourceBundle *zoneStrings = ures_openures_open_77(U_ICUDATA_ZONE"icudt" "77" "l" "-" "zone", locale.getName(), &tmpsts); | |||
364 | zoneStrings = ures_getByKeyWithFallbackures_getByKeyWithFallback_77(zoneStrings, gZoneStrings, zoneStrings, &tmpsts); | |||
365 | ||||
366 | if (U_SUCCESS(tmpsts)) { | |||
367 | const char16_t *regionPattern = ures_getStringByKeyWithFallbackures_getStringByKeyWithFallback_77(zoneStrings, gRegionFormatTag, nullptr, &tmpsts); | |||
368 | if (U_SUCCESS(tmpsts) && u_strlenu_strlen_77(regionPattern) > 0) { | |||
369 | rpat.setTo(regionPattern, -1); | |||
370 | } | |||
371 | tmpsts = U_ZERO_ERROR; | |||
372 | const char16_t *fallbackPattern = ures_getStringByKeyWithFallbackures_getStringByKeyWithFallback_77(zoneStrings, gFallbackFormatTag, nullptr, &tmpsts); | |||
373 | if (U_SUCCESS(tmpsts) && u_strlenu_strlen_77(fallbackPattern) > 0) { | |||
374 | fpat.setTo(fallbackPattern, -1); | |||
375 | } | |||
376 | } | |||
377 | ures_closeures_close_77(zoneStrings); | |||
378 | ||||
379 | fRegionFormat.applyPatternMinMaxArguments(rpat, 1, 1, status); | |||
380 | fFallbackFormat.applyPatternMinMaxArguments(fpat, 2, 2, status); | |||
381 | if (U_FAILURE(status)) { | |||
382 | cleanup(); | |||
383 | return; | |||
384 | } | |||
385 | ||||
386 | // locale display names | |||
387 | fLocaleDisplayNames = LocaleDisplayNames::createInstance(locale); | |||
388 | ||||
389 | // hash table for names - no key/value deleters | |||
390 | fLocationNamesMap = uhash_openuhash_open_77(uhash_hashUCharsuhash_hashUChars_77, uhash_compareUCharsuhash_compareUChars_77, nullptr, &status); | |||
391 | if (U_FAILURE(status)) { | |||
392 | cleanup(); | |||
393 | return; | |||
394 | } | |||
395 | ||||
396 | fPartialLocationNamesMap = uhash_openuhash_open_77(hashPartialLocationKey, comparePartialLocationKey, nullptr, &status); | |||
397 | if (U_FAILURE(status)) { | |||
398 | cleanup(); | |||
399 | return; | |||
400 | } | |||
401 | uhash_setKeyDeleteruhash_setKeyDeleter_77(fPartialLocationNamesMap, uprv_freeuprv_free_77); | |||
402 | // no value deleter | |||
403 | ||||
404 | // target region | |||
405 | const char* region = fLocale.getCountry(); | |||
406 | int32_t regionLen = static_cast<int32_t>(uprv_strlen(region):: strlen(region)); | |||
407 | if (regionLen == 0) { | |||
408 | CharString loc = ulocimp_addLikelySubtagsulocimp_addLikelySubtags_77(fLocale.getName(), status); | |||
409 | ulocimp_getSubtagsulocimp_getSubtags_77(loc.toStringPiece(), nullptr, nullptr, &fTargetRegion, nullptr, nullptr, status); | |||
410 | if (U_FAILURE(status)) { | |||
411 | cleanup(); | |||
412 | return; | |||
413 | } | |||
414 | } else { | |||
415 | fTargetRegion.append(region, regionLen, status); | |||
416 | } | |||
417 | ||||
418 | // preload generic names for the default zone | |||
419 | TimeZone *tz = TimeZone::createDefault(); | |||
420 | const char16_t *tzID = ZoneMeta::getCanonicalCLDRID(*tz); | |||
421 | if (tzID != nullptr) { | |||
422 | loadStrings(UnicodeString(true, tzID, -1)); | |||
423 | } | |||
424 | delete tz; | |||
425 | } | |||
426 | ||||
427 | void | |||
428 | TZGNCore::cleanup() { | |||
429 | delete fLocaleDisplayNames; | |||
430 | delete fTimeZoneNames; | |||
431 | ||||
432 | uhash_closeuhash_close_77(fLocationNamesMap); | |||
433 | uhash_closeuhash_close_77(fPartialLocationNamesMap); | |||
434 | } | |||
435 | ||||
436 | ||||
437 | UnicodeString& | |||
438 | TZGNCore::getDisplayName(const TimeZone& tz, UTimeZoneGenericNameType type, UDate date, UnicodeString& name) const { | |||
439 | name.setToBogus(); | |||
440 | switch (type) { | |||
441 | case UTZGNM_LOCATION: | |||
442 | { | |||
443 | const char16_t* tzCanonicalID = ZoneMeta::getCanonicalCLDRID(tz); | |||
444 | if (tzCanonicalID != nullptr) { | |||
445 | getGenericLocationName(UnicodeString(true, tzCanonicalID, -1), name); | |||
446 | } | |||
447 | } | |||
448 | break; | |||
449 | case UTZGNM_LONG: | |||
450 | case UTZGNM_SHORT: | |||
451 | formatGenericNonLocationName(tz, type, date, name); | |||
452 | if (name.isEmpty()) { | |||
453 | const char16_t* tzCanonicalID = ZoneMeta::getCanonicalCLDRID(tz); | |||
454 | if (tzCanonicalID != nullptr) { | |||
455 | getGenericLocationName(UnicodeString(true, tzCanonicalID, -1), name); | |||
456 | } | |||
457 | } | |||
458 | break; | |||
459 | default: | |||
460 | break; | |||
461 | } | |||
462 | return name; | |||
463 | } | |||
464 | ||||
465 | UnicodeString& | |||
466 | TZGNCore::getGenericLocationName(const UnicodeString& tzCanonicalID, UnicodeString& name) const { | |||
467 | if (tzCanonicalID.isEmpty()) { | |||
468 | name.setToBogus(); | |||
469 | return name; | |||
470 | } | |||
471 | ||||
472 | const char16_t *locname = nullptr; | |||
473 | TZGNCore *nonConstThis = const_cast<TZGNCore *>(this); | |||
474 | umtx_lockumtx_lock_77(&gLock); | |||
475 | { | |||
476 | locname = nonConstThis->getGenericLocationName(tzCanonicalID); | |||
477 | } | |||
478 | umtx_unlockumtx_unlock_77(&gLock); | |||
479 | ||||
480 | if (locname == nullptr) { | |||
481 | name.setToBogus(); | |||
482 | } else { | |||
483 | name.setTo(locname, u_strlenu_strlen_77(locname)); | |||
484 | } | |||
485 | ||||
486 | return name; | |||
487 | } | |||
488 | ||||
489 | /* | |||
490 | * This method updates the cache and must be called with a lock | |||
491 | */ | |||
492 | const char16_t* | |||
493 | TZGNCore::getGenericLocationName(const UnicodeString& tzCanonicalID) { | |||
494 | U_ASSERT(!tzCanonicalID.isEmpty())(static_cast <bool> (!tzCanonicalID.isEmpty()) ? void ( 0) : __assert_fail ("!tzCanonicalID.isEmpty()", __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__)); | |||
495 | if (tzCanonicalID.length() > ZID_KEY_MAX128) { | |||
496 | return nullptr; | |||
497 | } | |||
498 | ||||
499 | UErrorCode status = U_ZERO_ERROR; | |||
500 | char16_t tzIDKey[ZID_KEY_MAX128 + 1]; | |||
501 | int32_t tzIDKeyLen = tzCanonicalID.extract(tzIDKey, ZID_KEY_MAX128 + 1, status); | |||
502 | U_ASSERT(status == U_ZERO_ERROR)(static_cast <bool> (status == U_ZERO_ERROR) ? void (0) : __assert_fail ("status == U_ZERO_ERROR", __builtin_FILE () , __builtin_LINE (), __extension__ __PRETTY_FUNCTION__)); // already checked length above | |||
503 | tzIDKey[tzIDKeyLen] = 0; | |||
504 | ||||
505 | const char16_t* locname = static_cast<const char16_t*>(uhash_getuhash_get_77(fLocationNamesMap, tzIDKey)); | |||
506 | ||||
507 | if (locname != nullptr) { | |||
508 | // gEmpty indicate the name is not available | |||
509 | if (locname == gEmpty) { | |||
510 | return nullptr; | |||
511 | } | |||
512 | return locname; | |||
513 | } | |||
514 | ||||
515 | // Construct location name | |||
516 | UnicodeString name; | |||
517 | UnicodeString usCountryCode; | |||
518 | UBool isPrimary = false; | |||
519 | ||||
520 | ZoneMeta::getCanonicalCountry(tzCanonicalID, usCountryCode, &isPrimary); | |||
521 | ||||
522 | if (!usCountryCode.isEmpty()) { | |||
523 | if (isPrimary) { | |||
524 | // If this is the primary zone in the country, use the country name. | |||
525 | char countryCode[ULOC_COUNTRY_CAPACITY4]; | |||
526 | U_ASSERT(usCountryCode.length() < ULOC_COUNTRY_CAPACITY)(static_cast <bool> (usCountryCode.length() < 4) ? void (0) : __assert_fail ("usCountryCode.length() < 4", __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__)); | |||
527 | int32_t ccLen = usCountryCode.extract(0, usCountryCode.length(), countryCode, sizeof(countryCode), US_INVicu::UnicodeString::kInvariant); | |||
528 | countryCode[ccLen] = 0; | |||
529 | ||||
530 | UnicodeString country; | |||
531 | fLocaleDisplayNames->regionDisplayName(countryCode, country); | |||
532 | fRegionFormat.format(country, name, status); | |||
533 | } 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 | UnicodeString city; | |||
541 | fTimeZoneNames->getExemplarLocationName(tzCanonicalID, city); | |||
542 | fRegionFormat.format(city, name, status); | |||
543 | } | |||
544 | if (U_FAILURE(status)) { | |||
545 | return nullptr; | |||
546 | } | |||
547 | } | |||
548 | ||||
549 | locname = name.isEmpty() ? nullptr : fStringPool.get(name, status); | |||
550 | if (U_SUCCESS(status)) { | |||
551 | // Cache the result | |||
552 | const char16_t* cacheID = ZoneMeta::findTimeZoneID(tzCanonicalID); | |||
553 | U_ASSERT(cacheID != nullptr)(static_cast <bool> (cacheID != nullptr) ? void (0) : __assert_fail ("cacheID != nullptr", __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__)); | |||
554 | if (locname == nullptr) { | |||
555 | // gEmpty to indicate - no location name available | |||
556 | uhash_putuhash_put_77(fLocationNamesMap, (void *)cacheID, (void *)gEmpty, &status); | |||
557 | } else { | |||
558 | uhash_putuhash_put_77(fLocationNamesMap, (void *)cacheID, (void *)locname, &status); | |||
559 | if (U_FAILURE(status)) { | |||
560 | locname = nullptr; | |||
561 | } else { | |||
562 | // put the name info into the trie | |||
563 | GNameInfo* nameinfo = static_cast<ZNameInfo*>(uprv_mallocuprv_malloc_77(sizeof(GNameInfo))); | |||
564 | if (nameinfo != nullptr) { | |||
565 | nameinfo->type = UTZGNM_LOCATION; | |||
566 | nameinfo->tzID = cacheID; | |||
567 | fGNamesTrie.put(locname, nameinfo, status); | |||
568 | } | |||
569 | } | |||
570 | } | |||
571 | } | |||
572 | ||||
573 | return locname; | |||
574 | } | |||
575 | ||||
576 | UnicodeString& | |||
577 | TZGNCore::formatGenericNonLocationName(const TimeZone& tz, UTimeZoneGenericNameType type, UDate date, UnicodeString& name) const { | |||
578 | U_ASSERT(type == UTZGNM_LONG || type == UTZGNM_SHORT)(static_cast <bool> (type == UTZGNM_LONG || type == UTZGNM_SHORT ) ? void (0) : __assert_fail ("type == UTZGNM_LONG || type == UTZGNM_SHORT" , __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__ )); | |||
579 | name.setToBogus(); | |||
580 | ||||
581 | const char16_t* uID = ZoneMeta::getCanonicalCLDRID(tz); | |||
582 | if (uID == nullptr) { | |||
583 | return name; | |||
584 | } | |||
585 | ||||
586 | UnicodeString tzID(true, uID, -1); | |||
587 | ||||
588 | // Try to get a name from time zone first | |||
589 | UTimeZoneNameType nameType = (type
| |||
590 | fTimeZoneNames->getTimeZoneDisplayName(tzID, nameType, name); | |||
591 | ||||
592 | if (!name.isEmpty()) { | |||
593 | return name; | |||
594 | } | |||
595 | ||||
596 | // Try meta zone | |||
597 | char16_t mzIDBuf[32]; | |||
598 | UnicodeString mzID(mzIDBuf, 0, UPRV_LENGTHOF(mzIDBuf)(int32_t)(sizeof(mzIDBuf)/sizeof((mzIDBuf)[0]))); | |||
599 | fTimeZoneNames->getMetaZoneID(tzID, date, mzID); | |||
600 | if (!mzID.isEmpty()) { | |||
601 | UErrorCode status = U_ZERO_ERROR; | |||
602 | UBool useStandard = false; | |||
603 | int32_t raw, sav; | |||
604 | char16_t tmpNameBuf[ZONE_NAME_U16_MAX128]; | |||
605 | ||||
606 | tz.getOffset(date, false, raw, sav, status); | |||
607 | if (U_FAILURE(status)) { | |||
608 | return name; | |||
609 | } | |||
610 | ||||
611 | if (sav == 0) { | |||
612 | useStandard = true; | |||
613 | ||||
614 | TimeZone *tmptz = tz.clone(); | |||
615 | // Check if the zone actually uses daylight saving time around the time | |||
616 | BasicTimeZone *btz = nullptr; | |||
617 | if (dynamic_cast<OlsonTimeZone *>(tmptz) != nullptr | |||
618 | || dynamic_cast<SimpleTimeZone *>(tmptz) != nullptr | |||
619 | || dynamic_cast<RuleBasedTimeZone *>(tmptz) != nullptr | |||
620 | || dynamic_cast<VTimeZone *>(tmptz) != nullptr) { | |||
621 | btz = (BasicTimeZone*)tmptz; | |||
622 | } | |||
623 | ||||
624 | if (btz != nullptr) { | |||
625 | TimeZoneTransition before; | |||
626 | UBool beforTrs = btz->getPreviousTransition(date, true, before); | |||
627 | if (beforTrs | |||
628 | && (date - before.getTime() < kDstCheckRange) | |||
629 | && before.getFrom()->getDSTSavings() != 0) { | |||
630 | useStandard = false; | |||
631 | } else { | |||
632 | TimeZoneTransition after; | |||
633 | UBool afterTrs = btz->getNextTransition(date, false, after); | |||
634 | if (afterTrs | |||
635 | && (after.getTime() - date < kDstCheckRange) | |||
636 | && after.getTo()->getDSTSavings() != 0) { | |||
637 | useStandard = false; | |||
638 | } | |||
639 | } | |||
640 | } 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 | tmptz->getOffset(date - kDstCheckRange, false, raw, sav, status); | |||
| ||||
644 | if (sav != 0) { | |||
645 | useStandard = false; | |||
646 | } else { | |||
647 | tmptz->getOffset(date + kDstCheckRange, false, raw, sav, status); | |||
648 | if (sav != 0){ | |||
649 | useStandard = false; | |||
650 | } | |||
651 | } | |||
652 | if (U_FAILURE(status)) { | |||
653 | delete tmptz; | |||
654 | return name; | |||
655 | } | |||
656 | } | |||
657 | delete tmptz; | |||
658 | } | |||
659 | if (useStandard) { | |||
660 | UTimeZoneNameType stdNameType = (nameType == UTZNM_LONG_GENERIC) | |||
661 | ? UTZNM_LONG_STANDARD : UTZNM_SHORT_STANDARD; | |||
662 | UnicodeString stdName(tmpNameBuf, 0, UPRV_LENGTHOF(tmpNameBuf)(int32_t)(sizeof(tmpNameBuf)/sizeof((tmpNameBuf)[0]))); | |||
663 | fTimeZoneNames->getDisplayName(tzID, stdNameType, date, stdName); | |||
664 | if (!stdName.isEmpty()) { | |||
665 | 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 | char16_t genNameBuf[ZONE_NAME_U16_MAX128]; | |||
673 | UnicodeString mzGenericName(genNameBuf, 0, UPRV_LENGTHOF(genNameBuf)(int32_t)(sizeof(genNameBuf)/sizeof((genNameBuf)[0]))); | |||
674 | fTimeZoneNames->getMetaZoneDisplayName(mzID, nameType, mzGenericName); | |||
675 | if (stdName.caseCompare(mzGenericName, 0) == 0) { | |||
676 | name.setToBogus(); | |||
677 | } | |||
678 | } | |||
679 | } | |||
680 | if (name.isEmpty()) { | |||
681 | // Get a name from meta zone | |||
682 | UnicodeString mzName(tmpNameBuf, 0, UPRV_LENGTHOF(tmpNameBuf)(int32_t)(sizeof(tmpNameBuf)/sizeof((tmpNameBuf)[0]))); | |||
683 | fTimeZoneNames->getMetaZoneDisplayName(mzID, nameType, mzName); | |||
684 | 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 | char16_t idBuf[32]; | |||
689 | UnicodeString goldenID(idBuf, 0, UPRV_LENGTHOF(idBuf)(int32_t)(sizeof(idBuf)/sizeof((idBuf)[0]))); | |||
690 | fTimeZoneNames->getReferenceZoneID(mzID, fTargetRegion.data(), goldenID); | |||
691 | if (!goldenID.isEmpty() && goldenID != tzID) { | |||
692 | TimeZone *goldenZone = TimeZone::createTimeZone(goldenID); | |||
693 | 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 | goldenZone->getOffset(date + raw + sav, true, raw1, sav1, status); | |||
700 | delete goldenZone; | |||
701 | if (U_SUCCESS(status)) { | |||
702 | if (raw != raw1 || sav != sav1) { | |||
703 | // Now we need to use a partial location format | |||
704 | getPartialLocationName(tzID, mzID, (nameType == UTZNM_LONG_GENERIC), mzName, name); | |||
705 | } else { | |||
706 | name.setTo(mzName); | |||
707 | } | |||
708 | } | |||
709 | } else { | |||
710 | name.setTo(mzName); | |||
711 | } | |||
712 | } | |||
713 | } | |||
714 | } | |||
715 | return name; | |||
716 | } | |||
717 | ||||
718 | UnicodeString& | |||
719 | TZGNCore::getPartialLocationName(const UnicodeString& tzCanonicalID, | |||
720 | const UnicodeString& mzID, UBool isLong, const UnicodeString& mzDisplayName, | |||
721 | UnicodeString& name) const { | |||
722 | name.setToBogus(); | |||
723 | if (tzCanonicalID.isEmpty() || mzID.isEmpty() || mzDisplayName.isEmpty()) { | |||
724 | return name; | |||
725 | } | |||
726 | ||||
727 | const char16_t *uplname = nullptr; | |||
728 | TZGNCore *nonConstThis = const_cast<TZGNCore *>(this); | |||
729 | umtx_lockumtx_lock_77(&gLock); | |||
730 | { | |||
731 | uplname = nonConstThis->getPartialLocationName(tzCanonicalID, mzID, isLong, mzDisplayName); | |||
732 | } | |||
733 | umtx_unlockumtx_unlock_77(&gLock); | |||
734 | ||||
735 | if (uplname == nullptr) { | |||
736 | name.setToBogus(); | |||
737 | } else { | |||
738 | name.setTo(true, uplname, -1); | |||
739 | } | |||
740 | return name; | |||
741 | } | |||
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 | const UnicodeString& mzID, UBool isLong, const UnicodeString& mzDisplayName) { | |||
749 | U_ASSERT(!tzCanonicalID.isEmpty())(static_cast <bool> (!tzCanonicalID.isEmpty()) ? void ( 0) : __assert_fail ("!tzCanonicalID.isEmpty()", __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__)); | |||
750 | U_ASSERT(!mzID.isEmpty())(static_cast <bool> (!mzID.isEmpty()) ? void (0) : __assert_fail ("!mzID.isEmpty()", __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__)); | |||
751 | U_ASSERT(!mzDisplayName.isEmpty())(static_cast <bool> (!mzDisplayName.isEmpty()) ? void ( 0) : __assert_fail ("!mzDisplayName.isEmpty()", __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__)); | |||
752 | ||||
753 | PartialLocationKey key; | |||
754 | key.tzID = ZoneMeta::findTimeZoneID(tzCanonicalID); | |||
755 | key.mzID = ZoneMeta::findMetaZoneID(mzID); | |||
756 | key.isLong = isLong; | |||
757 | U_ASSERT(key.tzID != nullptr && key.mzID != nullptr)(static_cast <bool> (key.tzID != nullptr && key .mzID != nullptr) ? void (0) : __assert_fail ("key.tzID != nullptr && key.mzID != nullptr" , __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__ )); | |||
758 | ||||
759 | const char16_t* uplname = static_cast<const char16_t*>(uhash_getuhash_get_77(fPartialLocationNamesMap, &key)); | |||
760 | if (uplname != nullptr) { | |||
761 | return uplname; | |||
762 | } | |||
763 | ||||
764 | UnicodeString location; | |||
765 | UnicodeString usCountryCode; | |||
766 | ZoneMeta::getCanonicalCountry(tzCanonicalID, usCountryCode); | |||
767 | if (!usCountryCode.isEmpty()) { | |||
768 | char countryCode[ULOC_COUNTRY_CAPACITY4]; | |||
769 | U_ASSERT(usCountryCode.length() < ULOC_COUNTRY_CAPACITY)(static_cast <bool> (usCountryCode.length() < 4) ? void (0) : __assert_fail ("usCountryCode.length() < 4", __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__)); | |||
770 | int32_t ccLen = usCountryCode.extract(0, usCountryCode.length(), countryCode, sizeof(countryCode), US_INVicu::UnicodeString::kInvariant); | |||
771 | countryCode[ccLen] = 0; | |||
772 | ||||
773 | UnicodeString regionalGolden; | |||
774 | fTimeZoneNames->getReferenceZoneID(mzID, countryCode, regionalGolden); | |||
775 | if (tzCanonicalID == regionalGolden) { | |||
776 | // Use country name | |||
777 | fLocaleDisplayNames->regionDisplayName(countryCode, location); | |||
778 | } else { | |||
779 | // Otherwise, use exemplar city name | |||
780 | fTimeZoneNames->getExemplarLocationName(tzCanonicalID, location); | |||
781 | } | |||
782 | } else { | |||
783 | fTimeZoneNames->getExemplarLocationName(tzCanonicalID, location); | |||
784 | 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 | location.setTo(tzCanonicalID); | |||
789 | } | |||
790 | } | |||
791 | ||||
792 | UErrorCode status = U_ZERO_ERROR; | |||
793 | UnicodeString name; | |||
794 | fFallbackFormat.format(location, mzDisplayName, name, status); | |||
795 | if (U_FAILURE(status)) { | |||
796 | return nullptr; | |||
797 | } | |||
798 | ||||
799 | uplname = fStringPool.get(name, status); | |||
800 | if (U_SUCCESS(status)) { | |||
801 | // Add the name to cache | |||
802 | PartialLocationKey* cacheKey = static_cast<PartialLocationKey*>(uprv_mallocuprv_malloc_77(sizeof(PartialLocationKey))); | |||
803 | if (cacheKey != nullptr) { | |||
804 | cacheKey->tzID = key.tzID; | |||
805 | cacheKey->mzID = key.mzID; | |||
806 | cacheKey->isLong = key.isLong; | |||
807 | uhash_putuhash_put_77(fPartialLocationNamesMap, (void *)cacheKey, (void *)uplname, &status); | |||
808 | if (U_FAILURE(status)) { | |||
809 | uprv_freeuprv_free_77(cacheKey); | |||
810 | } else { | |||
811 | // put the name to the local trie as well | |||
812 | GNameInfo* nameinfo = static_cast<ZNameInfo*>(uprv_mallocuprv_malloc_77(sizeof(GNameInfo))); | |||
813 | if (nameinfo != nullptr) { | |||
814 | nameinfo->type = isLong ? UTZGNM_LONG : UTZGNM_SHORT; | |||
815 | nameinfo->tzID = key.tzID; | |||
816 | fGNamesTrie.put(uplname, nameinfo, status); | |||
817 | } | |||
818 | } | |||
819 | } | |||
820 | } | |||
821 | return uplname; | |||
822 | } | |||
823 | ||||
824 | /* | |||
825 | * This method updates the cache and must be called with a lock, | |||
826 | * except initializer. | |||
827 | */ | |||
828 | void | |||
829 | TZGNCore::loadStrings(const UnicodeString& tzCanonicalID) { | |||
830 | // load the generic location name | |||
831 | getGenericLocationName(tzCanonicalID); | |||
832 | ||||
833 | // partial location names | |||
834 | UErrorCode status = U_ZERO_ERROR; | |||
835 | ||||
836 | const UnicodeString *mzID; | |||
837 | UnicodeString goldenID; | |||
838 | UnicodeString mzGenName; | |||
839 | UTimeZoneNameType genNonLocTypes[] = { | |||
840 | UTZNM_LONG_GENERIC, UTZNM_SHORT_GENERIC, | |||
841 | UTZNM_UNKNOWN /*terminator*/ | |||
842 | }; | |||
843 | ||||
844 | StringEnumeration *mzIDs = fTimeZoneNames->getAvailableMetaZoneIDs(tzCanonicalID, status); | |||
845 | while ((mzID = mzIDs->snext(status)) != nullptr) { | |||
846 | if (U_FAILURE(status)) { | |||
847 | break; | |||
848 | } | |||
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 | fTimeZoneNames->getReferenceZoneID(*mzID, fTargetRegion.data(), goldenID); | |||
853 | if (tzCanonicalID != goldenID) { | |||
854 | for (int32_t i = 0; genNonLocTypes[i] != UTZNM_UNKNOWN; i++) { | |||
855 | fTimeZoneNames->getMetaZoneDisplayName(*mzID, genNonLocTypes[i], mzGenName); | |||
856 | if (!mzGenName.isEmpty()) { | |||
857 | // getPartialLocationName formats a name and put it into the trie | |||
858 | getPartialLocationName(tzCanonicalID, *mzID, | |||
859 | (genNonLocTypes[i] == UTZNM_LONG_GENERIC), mzGenName); | |||
860 | } | |||
861 | } | |||
862 | } | |||
863 | } | |||
864 | delete mzIDs; | |||
865 | } | |||
866 | ||||
867 | int32_t | |||
868 | TZGNCore::findBestMatch(const UnicodeString& text, int32_t start, uint32_t types, | |||
869 | UnicodeString& tzID, UTimeZoneFormatTimeType& timeType, UErrorCode& status) const { | |||
870 | timeType = UTZFMT_TIME_TYPE_UNKNOWN; | |||
871 | tzID.setToBogus(); | |||
872 | ||||
873 | if (U_FAILURE(status)) { | |||
874 | return 0; | |||
875 | } | |||
876 | ||||
877 | // Find matches in the TimeZoneNames first | |||
878 | TimeZoneNames::MatchInfoCollection *tznamesMatches = findTimeZoneNames(text, start, types, status); | |||
879 | if (U_FAILURE(status)) { | |||
880 | return 0; | |||
881 | } | |||
882 | ||||
883 | int32_t bestMatchLen = 0; | |||
884 | UTimeZoneFormatTimeType bestMatchTimeType = UTZFMT_TIME_TYPE_UNKNOWN; | |||
885 | UnicodeString bestMatchTzID; | |||
886 | // UBool isLongStandard = false; // workaround - see the comments below | |||
887 | 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 | if (tznamesMatches != nullptr) { | |||
890 | UnicodeString mzID; | |||
891 | for (int32_t i = 0; i < tznamesMatches->size(); i++) { | |||
892 | int32_t len = tznamesMatches->getMatchLengthAt(i); | |||
893 | if (len > bestMatchLen) { | |||
894 | bestMatchLen = len; | |||
895 | if (!tznamesMatches->getTimeZoneIDAt(i, bestMatchTzID)) { | |||
896 | // name for a meta zone | |||
897 | if (tznamesMatches->getMetaZoneIDAt(i, mzID)) { | |||
898 | fTimeZoneNames->getReferenceZoneID(mzID, fTargetRegion.data(), bestMatchTzID); | |||
899 | } | |||
900 | } | |||
901 | UTimeZoneNameType nameType = tznamesMatches->getNameTypeAt(i); | |||
902 | if (U_FAILURE(status)) { | |||
903 | break; | |||
904 | } | |||
905 | switch (nameType) { | |||
906 | case UTZNM_LONG_STANDARD: | |||
907 | // isLongStandard = true; | |||
908 | case UTZNM_SHORT_STANDARD: // this one is never used for generic, but just in case | |||
909 | isStandard = true; // TODO: Remove this later, see the comments above. | |||
910 | bestMatchTimeType = UTZFMT_TIME_TYPE_STANDARD; | |||
911 | break; | |||
912 | case UTZNM_LONG_DAYLIGHT: | |||
913 | case UTZNM_SHORT_DAYLIGHT: // this one is never used for generic, but just in case | |||
914 | bestMatchTimeType = UTZFMT_TIME_TYPE_DAYLIGHT; | |||
915 | break; | |||
916 | default: | |||
917 | bestMatchTimeType = UTZFMT_TIME_TYPE_UNKNOWN; | |||
918 | } | |||
919 | } | |||
920 | } | |||
921 | delete tznamesMatches; | |||
922 | if (U_FAILURE(status)) { | |||
923 | return 0; | |||
924 | } | |||
925 | ||||
926 | 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 | if (!isStandard) { | |||
951 | tzID.setTo(bestMatchTzID); | |||
952 | timeType = bestMatchTimeType; | |||
953 | return bestMatchLen; | |||
954 | } | |||
955 | } | |||
956 | } | |||
957 | ||||
958 | // Find matches in the local trie | |||
959 | TimeZoneGenericNameMatchInfo *localMatches = findLocal(text, start, types, status); | |||
960 | if (U_FAILURE(status)) { | |||
961 | return 0; | |||
962 | } | |||
963 | if (localMatches != nullptr) { | |||
964 | for (int32_t i = 0; i < localMatches->size(); i++) { | |||
965 | 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 | if (len >= bestMatchLen) { | |||
973 | bestMatchLen = localMatches->getMatchLength(i); | |||
974 | bestMatchTimeType = UTZFMT_TIME_TYPE_UNKNOWN; // because generic | |||
975 | localMatches->getTimeZoneID(i, bestMatchTzID); | |||
976 | } | |||
977 | } | |||
978 | delete localMatches; | |||
979 | } | |||
980 | ||||
981 | if (bestMatchLen > 0) { | |||
982 | timeType = bestMatchTimeType; | |||
983 | tzID.setTo(bestMatchTzID); | |||
984 | } | |||
985 | return bestMatchLen; | |||
986 | } | |||
987 | ||||
988 | TimeZoneGenericNameMatchInfo* | |||
989 | TZGNCore::findLocal(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const { | |||
990 | GNameSearchHandler handler(types); | |||
991 | ||||
992 | TZGNCore *nonConstThis = const_cast<TZGNCore *>(this); | |||
993 | ||||
994 | umtx_lockumtx_lock_77(&gLock); | |||
995 | { | |||
996 | fGNamesTrie.search(text, start, (TextTrieMapSearchResultHandler *)&handler, status); | |||
997 | } | |||
998 | umtx_unlockumtx_unlock_77(&gLock); | |||
999 | ||||
1000 | if (U_FAILURE(status)) { | |||
1001 | return nullptr; | |||
1002 | } | |||
1003 | ||||
1004 | TimeZoneGenericNameMatchInfo *gmatchInfo = nullptr; | |||
1005 | ||||
1006 | int32_t maxLen = 0; | |||
1007 | UVector *results = handler.getMatches(maxLen); | |||
1008 | if (results != nullptr && ((maxLen == (text.length() - start)) || fGNamesTrieFullyLoaded)) { | |||
1009 | // perfect match | |||
1010 | gmatchInfo = new TimeZoneGenericNameMatchInfo(results); | |||
1011 | if (gmatchInfo == nullptr) { | |||
1012 | status = U_MEMORY_ALLOCATION_ERROR; | |||
1013 | delete results; | |||
1014 | return nullptr; | |||
1015 | } | |||
1016 | return gmatchInfo; | |||
1017 | } | |||
1018 | ||||
1019 | 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 | umtx_lockumtx_lock_77(&gLock); | |||
1024 | { | |||
1025 | if (!fGNamesTrieFullyLoaded) { | |||
1026 | StringEnumeration *tzIDs = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, nullptr, nullptr, status); | |||
1027 | if (U_SUCCESS(status)) { | |||
1028 | const UnicodeString *tzID; | |||
1029 | while ((tzID = tzIDs->snext(status)) != nullptr) { | |||
1030 | if (U_FAILURE(status)) { | |||
1031 | break; | |||
1032 | } | |||
1033 | nonConstThis->loadStrings(*tzID); | |||
1034 | } | |||
1035 | } | |||
1036 | delete tzIDs; | |||
1037 | ||||
1038 | if (U_SUCCESS(status)) { | |||
1039 | nonConstThis->fGNamesTrieFullyLoaded = true; | |||
1040 | } | |||
1041 | } | |||
1042 | } | |||
1043 | umtx_unlockumtx_unlock_77(&gLock); | |||
1044 | ||||
1045 | if (U_FAILURE(status)) { | |||
1046 | return nullptr; | |||
1047 | } | |||
1048 | ||||
1049 | umtx_lockumtx_lock_77(&gLock); | |||
1050 | { | |||
1051 | // now try it again | |||
1052 | fGNamesTrie.search(text, start, (TextTrieMapSearchResultHandler *)&handler, status); | |||
1053 | } | |||
1054 | umtx_unlockumtx_unlock_77(&gLock); | |||
1055 | ||||
1056 | results = handler.getMatches(maxLen); | |||
1057 | if (results != nullptr && maxLen > 0) { | |||
1058 | gmatchInfo = new TimeZoneGenericNameMatchInfo(results); | |||
1059 | if (gmatchInfo == nullptr) { | |||
1060 | status = U_MEMORY_ALLOCATION_ERROR; | |||
1061 | delete results; | |||
1062 | return nullptr; | |||
1063 | } | |||
1064 | } | |||
1065 | ||||
1066 | return gmatchInfo; | |||
1067 | } | |||
1068 | ||||
1069 | TimeZoneNames::MatchInfoCollection* | |||
1070 | 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 | uint32_t nameTypes = 0; | |||
1073 | if (types & UTZGNM_LONG) { | |||
1074 | nameTypes |= (UTZNM_LONG_GENERIC | UTZNM_LONG_STANDARD); | |||
1075 | } | |||
1076 | if (types & UTZGNM_SHORT) { | |||
1077 | nameTypes |= (UTZNM_SHORT_GENERIC | UTZNM_SHORT_STANDARD); | |||
1078 | } | |||
1079 | ||||
1080 | if (types) { | |||
1081 | // Find matches in the TimeZoneNames | |||
1082 | return fTimeZoneNames->find(text, start, nameTypes, status); | |||
1083 | } | |||
1084 | ||||
1085 | return nullptr; | |||
1086 | } | |||
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 | #define SWEEP_INTERVAL100 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 | #define CACHE_EXPIRATION180000.0 180000.0 | |||
1111 | ||||
1112 | U_CDECL_BEGINextern "C" { | |||
1113 | /** | |||
1114 | * Cleanup callback func | |||
1115 | */ | |||
1116 | static UBool U_CALLCONV tzgnCore_cleanup() | |||
1117 | { | |||
1118 | if (gTZGNCoreCache != nullptr) { | |||
1119 | uhash_closeuhash_close_77(gTZGNCoreCache); | |||
1120 | gTZGNCoreCache = nullptr; | |||
1121 | } | |||
1122 | gTZGNCoreCacheInitialized = false; | |||
1123 | return true; | |||
1124 | } | |||
1125 | ||||
1126 | /** | |||
1127 | * Deleter for TZGNCoreRef | |||
1128 | */ | |||
1129 | static void U_CALLCONV | |||
1130 | deleteTZGNCoreRef(void *obj) { | |||
1131 | icu::TZGNCoreRef *entry = (icu::TZGNCoreRef*)obj; | |||
1132 | delete (icu::TZGNCore*) entry->obj; | |||
1133 | uprv_freeuprv_free_77(entry); | |||
1134 | } | |||
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 | static void sweepCache() { | |||
1143 | int32_t pos = UHASH_FIRST(-1); | |||
1144 | const UHashElement* elem; | |||
1145 | double now = static_cast<double>(uprv_getUTCtimeuprv_getUTCtime_77()); | |||
1146 | ||||
1147 | while ((elem = uhash_nextElementuhash_nextElement_77(gTZGNCoreCache, &pos)) != nullptr) { | |||
1148 | TZGNCoreRef* entry = static_cast<TZGNCoreRef*>(elem->value.pointer); | |||
1149 | if (entry->refCount <= 0 && (now - entry->lastAccess) > CACHE_EXPIRATION180000.0) { | |||
1150 | // delete this entry | |||
1151 | uhash_removeElementuhash_removeElement_77(gTZGNCoreCache, elem); | |||
1152 | } | |||
1153 | } | |||
1154 | } | |||
1155 | ||||
1156 | TimeZoneGenericNames::TimeZoneGenericNames() | |||
1157 | : fRef(nullptr) { | |||
1158 | } | |||
1159 | ||||
1160 | TimeZoneGenericNames::~TimeZoneGenericNames() { | |||
1161 | umtx_lockumtx_lock_77(&gTZGNLock); | |||
1162 | { | |||
1163 | U_ASSERT(fRef->refCount > 0)(static_cast <bool> (fRef->refCount > 0) ? void ( 0) : __assert_fail ("fRef->refCount > 0", __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__)); | |||
1164 | // Just decrement the reference count | |||
1165 | fRef->refCount--; | |||
1166 | } | |||
1167 | umtx_unlockumtx_unlock_77(&gTZGNLock); | |||
1168 | } | |||
1169 | ||||
1170 | TimeZoneGenericNames* | |||
1171 | TimeZoneGenericNames::createInstance(const Locale& locale, UErrorCode& status) { | |||
1172 | if (U_FAILURE(status)) { | |||
1173 | return nullptr; | |||
1174 | } | |||
1175 | TimeZoneGenericNames* instance = new TimeZoneGenericNames(); | |||
1176 | if (instance == nullptr) { | |||
1177 | status = U_MEMORY_ALLOCATION_ERROR; | |||
1178 | return nullptr; | |||
1179 | } | |||
1180 | ||||
1181 | TZGNCoreRef *cacheEntry = nullptr; | |||
1182 | { | |||
1183 | Mutex lock(&gTZGNLock); | |||
1184 | ||||
1185 | if (!gTZGNCoreCacheInitialized) { | |||
1186 | // Create empty hashtable | |||
1187 | gTZGNCoreCache = uhash_openuhash_open_77(uhash_hashCharsuhash_hashChars_77, uhash_compareCharsuhash_compareChars_77, nullptr, &status); | |||
1188 | if (U_SUCCESS(status)) { | |||
1189 | uhash_setKeyDeleteruhash_setKeyDeleter_77(gTZGNCoreCache, uprv_freeuprv_free_77); | |||
1190 | uhash_setValueDeleteruhash_setValueDeleter_77(gTZGNCoreCache, deleteTZGNCoreRef); | |||
1191 | gTZGNCoreCacheInitialized = true; | |||
1192 | ucln_i18n_registerCleanupucln_i18n_registerCleanup_77(UCLN_I18N_TIMEZONEGENERICNAMES, tzgnCore_cleanup); | |||
1193 | } | |||
1194 | } | |||
1195 | if (U_FAILURE(status)) { | |||
1196 | delete instance; | |||
1197 | return nullptr; | |||
1198 | } | |||
1199 | ||||
1200 | // Check the cache, if not available, create new one and cache | |||
1201 | const char *key = locale.getName(); | |||
1202 | cacheEntry = static_cast<TZGNCoreRef*>(uhash_getuhash_get_77(gTZGNCoreCache, key)); | |||
1203 | if (cacheEntry == nullptr) { | |||
1204 | TZGNCore *tzgnCore = nullptr; | |||
1205 | char *newKey = nullptr; | |||
1206 | ||||
1207 | tzgnCore = new TZGNCore(locale, status); | |||
1208 | if (tzgnCore == nullptr) { | |||
1209 | status = U_MEMORY_ALLOCATION_ERROR; | |||
1210 | } | |||
1211 | if (U_SUCCESS(status)) { | |||
1212 | newKey = static_cast<char*>(uprv_mallocuprv_malloc_77(uprv_strlen(key):: strlen(key) + 1)); | |||
1213 | if (newKey == nullptr) { | |||
1214 | status = U_MEMORY_ALLOCATION_ERROR; | |||
1215 | } else { | |||
1216 | uprv_strcpy(newKey, key):: strcpy(newKey, key); | |||
1217 | } | |||
1218 | } | |||
1219 | if (U_SUCCESS(status)) { | |||
1220 | cacheEntry = static_cast<TZGNCoreRef*>(uprv_mallocuprv_malloc_77(sizeof(TZGNCoreRef))); | |||
1221 | if (cacheEntry == nullptr) { | |||
1222 | status = U_MEMORY_ALLOCATION_ERROR; | |||
1223 | } else { | |||
1224 | cacheEntry->obj = tzgnCore; | |||
1225 | cacheEntry->refCount = 1; | |||
1226 | cacheEntry->lastAccess = static_cast<double>(uprv_getUTCtimeuprv_getUTCtime_77()); | |||
1227 | ||||
1228 | uhash_putuhash_put_77(gTZGNCoreCache, newKey, cacheEntry, &status); | |||
1229 | } | |||
1230 | } | |||
1231 | if (U_FAILURE(status)) { | |||
1232 | delete tzgnCore; | |||
1233 | if (newKey != nullptr) { | |||
1234 | uprv_freeuprv_free_77(newKey); | |||
1235 | } | |||
1236 | if (cacheEntry != nullptr) { | |||
1237 | uprv_freeuprv_free_77(cacheEntry); | |||
1238 | } | |||
1239 | cacheEntry = nullptr; | |||
1240 | } | |||
1241 | } else { | |||
1242 | // Update the reference count | |||
1243 | cacheEntry->refCount++; | |||
1244 | cacheEntry->lastAccess = static_cast<double>(uprv_getUTCtimeuprv_getUTCtime_77()); | |||
1245 | } | |||
1246 | gAccessCount++; | |||
1247 | if (gAccessCount >= SWEEP_INTERVAL100) { | |||
1248 | // sweep | |||
1249 | sweepCache(); | |||
1250 | gAccessCount = 0; | |||
1251 | } | |||
1252 | } // End of mutex locked block | |||
1253 | ||||
1254 | if (cacheEntry == nullptr) { | |||
1255 | delete instance; | |||
1256 | return nullptr; | |||
1257 | } | |||
1258 | ||||
1259 | instance->fRef = cacheEntry; | |||
1260 | return instance; | |||
1261 | } | |||
1262 | ||||
1263 | bool | |||
1264 | TimeZoneGenericNames::operator==(const TimeZoneGenericNames& other) const { | |||
1265 | // Just compare if the other object also use the same | |||
1266 | // ref entry | |||
1267 | return fRef == other.fRef; | |||
1268 | } | |||
1269 | ||||
1270 | TimeZoneGenericNames* | |||
1271 | TimeZoneGenericNames::clone() const { | |||
1272 | TimeZoneGenericNames* other = new TimeZoneGenericNames(); | |||
1273 | if (other) { | |||
1274 | umtx_lockumtx_lock_77(&gTZGNLock); | |||
1275 | { | |||
1276 | // Just increments the reference count | |||
1277 | fRef->refCount++; | |||
1278 | other->fRef = fRef; | |||
1279 | } | |||
1280 | umtx_unlockumtx_unlock_77(&gTZGNLock); | |||
1281 | } | |||
1282 | return other; | |||
1283 | } | |||
1284 | ||||
1285 | UnicodeString& | |||
1286 | TimeZoneGenericNames::getDisplayName(const TimeZone& tz, UTimeZoneGenericNameType type, | |||
1287 | UDate date, UnicodeString& name) const { | |||
1288 | return fRef->obj->getDisplayName(tz, type, date, name); | |||
| ||||
1289 | } | |||
1290 | ||||
1291 | UnicodeString& | |||
1292 | TimeZoneGenericNames::getGenericLocationName(const UnicodeString& tzCanonicalID, UnicodeString& name) const { | |||
1293 | return fRef->obj->getGenericLocationName(tzCanonicalID, name); | |||
1294 | } | |||
1295 | ||||
1296 | int32_t | |||
1297 | TimeZoneGenericNames::findBestMatch(const UnicodeString& text, int32_t start, uint32_t types, | |||
1298 | UnicodeString& tzID, UTimeZoneFormatTimeType& timeType, UErrorCode& status) const { | |||
1299 | return fRef->obj->findBestMatch(text, start, types, tzID, timeType, status); | |||
1300 | } | |||
1301 | ||||
1302 | U_NAMESPACE_END} | |||
1303 | #endif |