• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1  // © 2016 and later: Unicode, Inc. and others.
2  // License & terms of use: http://www.unicode.org/copyright.html
3  /*
4  *******************************************************************************
5  * Copyright (C) 2007-2014, 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 "zonemeta.h"
15  
16  #include "unicode/timezone.h"
17  #include "unicode/ustring.h"
18  #include "unicode/putil.h"
19  #include "unicode/simpletz.h"
20  #include "unicode/strenum.h"
21  #include "umutex.h"
22  #include "uvector.h"
23  #include "cmemory.h"
24  #include "gregoimp.h"
25  #include "cstring.h"
26  #include "ucln_in.h"
27  #include "uassert.h"
28  #include "uresimp.h"
29  #include "uhash.h"
30  #include "olsontz.h"
31  #include "uinvchar.h"
32  
33  static icu::UMutex gZoneMetaLock;
34  
35  // CLDR Canonical ID mapping table
36  static UHashtable *gCanonicalIDCache = NULL;
37  static icu::UInitOnce gCanonicalIDCacheInitOnce = U_INITONCE_INITIALIZER;
38  
39  // Metazone mapping table
40  static UHashtable *gOlsonToMeta = NULL;
41  static icu::UInitOnce gOlsonToMetaInitOnce = U_INITONCE_INITIALIZER;
42  
43  // Available metazone IDs vector and table
44  static icu::UVector *gMetaZoneIDs = NULL;
45  static UHashtable *gMetaZoneIDTable = NULL;
46  static icu::UInitOnce gMetaZoneIDsInitOnce = U_INITONCE_INITIALIZER;
47  
48  // Country info vectors
49  static icu::UVector *gSingleZoneCountries = NULL;
50  static icu::UVector *gMultiZonesCountries = NULL;
51  static icu::UInitOnce gCountryInfoVectorsInitOnce = U_INITONCE_INITIALIZER;
52  
53  U_CDECL_BEGIN
54  
55  /**
56   * Cleanup callback func
57   */
zoneMeta_cleanup(void)58  static UBool U_CALLCONV zoneMeta_cleanup(void)
59  {
60      if (gCanonicalIDCache != NULL) {
61          uhash_close(gCanonicalIDCache);
62          gCanonicalIDCache = NULL;
63      }
64      gCanonicalIDCacheInitOnce.reset();
65  
66      if (gOlsonToMeta != NULL) {
67          uhash_close(gOlsonToMeta);
68          gOlsonToMeta = NULL;
69      }
70      gOlsonToMetaInitOnce.reset();
71  
72      if (gMetaZoneIDTable != NULL) {
73          uhash_close(gMetaZoneIDTable);
74          gMetaZoneIDTable = NULL;
75      }
76      // delete after closing gMetaZoneIDTable, because it holds
77      // value objects held by the hashtable
78      delete gMetaZoneIDs;
79      gMetaZoneIDs = NULL;
80      gMetaZoneIDsInitOnce.reset();
81  
82      delete gSingleZoneCountries;
83      gSingleZoneCountries = NULL;
84      delete gMultiZonesCountries;
85      gMultiZonesCountries = NULL;
86      gCountryInfoVectorsInitOnce.reset();
87  
88      return TRUE;
89  }
90  
91  /**
92   * Deleter for UChar* string
93   */
94  static void U_CALLCONV
deleteUCharString(void * obj)95  deleteUCharString(void *obj) {
96      UChar *entry = (UChar*)obj;
97      uprv_free(entry);
98  }
99  
100  /**
101   * Deleter for UVector
102   */
103  static void U_CALLCONV
deleteUVector(void * obj)104  deleteUVector(void *obj) {
105     delete (icu::UVector*) obj;
106  }
107  
108  /**
109   * Deleter for OlsonToMetaMappingEntry
110   */
111  static void U_CALLCONV
deleteOlsonToMetaMappingEntry(void * obj)112  deleteOlsonToMetaMappingEntry(void *obj) {
113      icu::OlsonToMetaMappingEntry *entry = (icu::OlsonToMetaMappingEntry*)obj;
114      uprv_free(entry);
115  }
116  
117  U_CDECL_END
118  
119  U_NAMESPACE_BEGIN
120  
121  #define ZID_KEY_MAX 128
122  
123  static const char gMetaZones[]          = "metaZones";
124  static const char gMetazoneInfo[]       = "metazoneInfo";
125  static const char gMapTimezonesTag[]    = "mapTimezones";
126  
127  static const char gKeyTypeData[]        = "keyTypeData";
128  static const char gTypeAliasTag[]       = "typeAlias";
129  static const char gTypeMapTag[]         = "typeMap";
130  static const char gTimezoneTag[]        = "timezone";
131  
132  static const char gPrimaryZonesTag[]    = "primaryZones";
133  
134  static const char gWorldTag[]           = "001";
135  
136  static const UChar gWorld[] = {0x30, 0x30, 0x31, 0x00}; // "001"
137  
138  static const UChar gDefaultFrom[] = {0x31, 0x39, 0x37, 0x30, 0x2D, 0x30, 0x31, 0x2D, 0x30, 0x31,
139                                       0x20, 0x30, 0x30, 0x3A, 0x30, 0x30, 0x00}; // "1970-01-01 00:00"
140  static const UChar gDefaultTo[]   = {0x39, 0x39, 0x39, 0x39, 0x2D, 0x31, 0x32, 0x2D, 0x33, 0x31,
141                                       0x20, 0x32, 0x33, 0x3A, 0x35, 0x39, 0x00}; // "9999-12-31 23:59"
142  
143  static const UChar gCustomTzPrefix[]    = {0x47, 0x4D, 0x54, 0};    // "GMT"
144  
145  #define ASCII_DIGIT(c) (((c)>=0x30 && (c)<=0x39) ? (c)-0x30 : -1)
146  
147  /*
148   * Convert a date string used by metazone mappings to UDate.
149   * The format used by CLDR metazone mapping is "yyyy-MM-dd HH:mm".
150   */
151  static UDate
parseDate(const UChar * text,UErrorCode & status)152  parseDate (const UChar *text, UErrorCode &status) {
153      if (U_FAILURE(status)) {
154          return 0;
155      }
156      int32_t len = u_strlen(text);
157      if (len != 16 && len != 10) {
158          // It must be yyyy-MM-dd HH:mm (length 16) or yyyy-MM-dd (length 10)
159          status = U_INVALID_FORMAT_ERROR;
160          return 0;
161      }
162  
163      int32_t year = 0, month = 0, day = 0, hour = 0, min = 0, n;
164      int32_t idx;
165  
166      // "yyyy" (0 - 3)
167      for (idx = 0; idx <= 3 && U_SUCCESS(status); idx++) {
168          n = ASCII_DIGIT((int32_t)text[idx]);
169          if (n >= 0) {
170              year = 10*year + n;
171          } else {
172              status = U_INVALID_FORMAT_ERROR;
173          }
174      }
175      // "MM" (5 - 6)
176      for (idx = 5; idx <= 6 && U_SUCCESS(status); idx++) {
177          n = ASCII_DIGIT((int32_t)text[idx]);
178          if (n >= 0) {
179              month = 10*month + n;
180          } else {
181              status = U_INVALID_FORMAT_ERROR;
182          }
183      }
184      // "dd" (8 - 9)
185      for (idx = 8; idx <= 9 && U_SUCCESS(status); idx++) {
186          n = ASCII_DIGIT((int32_t)text[idx]);
187          if (n >= 0) {
188              day = 10*day + n;
189          } else {
190              status = U_INVALID_FORMAT_ERROR;
191          }
192      }
193      if (len == 16) {
194          // "HH" (11 - 12)
195          for (idx = 11; idx <= 12 && U_SUCCESS(status); idx++) {
196              n = ASCII_DIGIT((int32_t)text[idx]);
197              if (n >= 0) {
198                  hour = 10*hour + n;
199              } else {
200                  status = U_INVALID_FORMAT_ERROR;
201              }
202          }
203          // "mm" (14 - 15)
204          for (idx = 14; idx <= 15 && U_SUCCESS(status); idx++) {
205              n = ASCII_DIGIT((int32_t)text[idx]);
206              if (n >= 0) {
207                  min = 10*min + n;
208              } else {
209                  status = U_INVALID_FORMAT_ERROR;
210              }
211          }
212      }
213  
214      if (U_SUCCESS(status)) {
215          UDate date = Grego::fieldsToDay(year, month - 1, day) * U_MILLIS_PER_DAY
216              + hour * U_MILLIS_PER_HOUR + min * U_MILLIS_PER_MINUTE;
217          return date;
218      }
219      return 0;
220  }
221  
initCanonicalIDCache(UErrorCode & status)222  static void U_CALLCONV initCanonicalIDCache(UErrorCode &status) {
223      gCanonicalIDCache = uhash_open(uhash_hashUChars, uhash_compareUChars, NULL, &status);
224      if (gCanonicalIDCache == NULL) {
225          status = U_MEMORY_ALLOCATION_ERROR;
226      }
227      if (U_FAILURE(status)) {
228          gCanonicalIDCache = NULL;
229      }
230      // No key/value deleters - keys/values are from a resource bundle
231      ucln_i18n_registerCleanup(UCLN_I18N_ZONEMETA, zoneMeta_cleanup);
232  }
233  
234  
235  const UChar* U_EXPORT2
getCanonicalCLDRID(const UnicodeString & tzid,UErrorCode & status)236  ZoneMeta::getCanonicalCLDRID(const UnicodeString &tzid, UErrorCode& status) {
237      if (U_FAILURE(status)) {
238          return NULL;
239      }
240  
241      if (tzid.isBogus() || tzid.length() > ZID_KEY_MAX) {
242          status = U_ILLEGAL_ARGUMENT_ERROR;
243          return NULL;
244      }
245  
246      // Checking the cached results
247      umtx_initOnce(gCanonicalIDCacheInitOnce, &initCanonicalIDCache, status);
248      if (U_FAILURE(status)) {
249          return NULL;
250      }
251  
252      const UChar *canonicalID = NULL;
253  
254      UErrorCode tmpStatus = U_ZERO_ERROR;
255      UChar utzid[ZID_KEY_MAX + 1];
256      tzid.extract(utzid, ZID_KEY_MAX + 1, tmpStatus);
257      U_ASSERT(tmpStatus == U_ZERO_ERROR);    // we checked the length of tzid already
258  
259      if (!uprv_isInvariantUString(utzid, -1)) {
260          // All of known tz IDs are only containing ASCII invariant characters.
261          status = U_ILLEGAL_ARGUMENT_ERROR;
262          return NULL;
263      }
264  
265      // Check if it was already cached
266      umtx_lock(&gZoneMetaLock);
267      {
268          canonicalID = (const UChar *)uhash_get(gCanonicalIDCache, utzid);
269      }
270      umtx_unlock(&gZoneMetaLock);
271  
272      if (canonicalID != NULL) {
273          return canonicalID;
274      }
275  
276      // If not, resolve CLDR canonical ID with resource data
277      UBool isInputCanonical = FALSE;
278      char id[ZID_KEY_MAX + 1];
279      tzid.extract(0, 0x7fffffff, id, UPRV_LENGTHOF(id), US_INV);
280  
281      // replace '/' with ':'
282      char *p = id;
283      while (*p++) {
284          if (*p == '/') {
285              *p = ':';
286          }
287      }
288  
289      UResourceBundle *top = ures_openDirect(NULL, gKeyTypeData, &tmpStatus);
290      UResourceBundle *rb = ures_getByKey(top, gTypeMapTag, NULL, &tmpStatus);
291      ures_getByKey(rb, gTimezoneTag, rb, &tmpStatus);
292      ures_getByKey(rb, id, rb, &tmpStatus);
293      if (U_SUCCESS(tmpStatus)) {
294          // type entry (canonical) found
295          // the input is the canonical ID. resolve to const UChar*
296          canonicalID = TimeZone::findID(tzid);
297          isInputCanonical = TRUE;
298      }
299  
300      if (canonicalID == NULL) {
301          // If a map element not found, then look for an alias
302          tmpStatus = U_ZERO_ERROR;
303          ures_getByKey(top, gTypeAliasTag, rb, &tmpStatus);
304          ures_getByKey(rb, gTimezoneTag, rb, &tmpStatus);
305          const UChar *canonical = ures_getStringByKey(rb,id,NULL,&tmpStatus);
306          if (U_SUCCESS(tmpStatus)) {
307              // canonical map found
308              canonicalID = canonical;
309          }
310  
311          if (canonicalID == NULL) {
312              // Dereference the input ID using the tz data
313              const UChar *derefer = TimeZone::dereferOlsonLink(tzid);
314              if (derefer == NULL) {
315                  status = U_ILLEGAL_ARGUMENT_ERROR;
316              } else {
317                  int32_t len = u_strlen(derefer);
318                  u_UCharsToChars(derefer,id,len);
319                  id[len] = (char) 0; // Make sure it is null terminated.
320  
321                  // replace '/' with ':'
322                  char *q = id;
323                  while (*q++) {
324                      if (*q == '/') {
325                          *q = ':';
326                      }
327                  }
328  
329                  // If a dereference turned something up then look for an alias.
330                  // rb still points to the alias table, so we don't have to go looking
331                  // for it.
332                  tmpStatus = U_ZERO_ERROR;
333                  canonical = ures_getStringByKey(rb,id,NULL,&tmpStatus);
334                  if (U_SUCCESS(tmpStatus)) {
335                      // canonical map for the dereferenced ID found
336                      canonicalID = canonical;
337                  } else {
338                      canonicalID = derefer;
339                      isInputCanonical = TRUE;
340                  }
341              }
342          }
343      }
344      ures_close(rb);
345      ures_close(top);
346  
347      if (U_SUCCESS(status)) {
348          U_ASSERT(canonicalID != NULL);  // canocanilD must be non-NULL here
349  
350          // Put the resolved canonical ID to the cache
351          umtx_lock(&gZoneMetaLock);
352          {
353              const UChar* idInCache = (const UChar *)uhash_get(gCanonicalIDCache, utzid);
354              if (idInCache == NULL) {
355                  const UChar* key = ZoneMeta::findTimeZoneID(tzid);
356                  U_ASSERT(key != NULL);
357                  if (key != NULL) {
358                      idInCache = (const UChar *)uhash_put(gCanonicalIDCache, (void *)key, (void *)canonicalID, &status);
359                      U_ASSERT(idInCache == NULL);
360                  }
361              }
362              if (U_SUCCESS(status) && isInputCanonical) {
363                  // Also put canonical ID itself into the cache if not exist
364                  const UChar *canonicalInCache = (const UChar*)uhash_get(gCanonicalIDCache, canonicalID);
365                  if (canonicalInCache == NULL) {
366                      canonicalInCache = (const UChar *)uhash_put(gCanonicalIDCache, (void *)canonicalID, (void *)canonicalID, &status);
367                      U_ASSERT(canonicalInCache == NULL);
368                  }
369              }
370          }
371          umtx_unlock(&gZoneMetaLock);
372      }
373  
374      return canonicalID;
375  }
376  
377  UnicodeString& U_EXPORT2
getCanonicalCLDRID(const UnicodeString & tzid,UnicodeString & systemID,UErrorCode & status)378  ZoneMeta::getCanonicalCLDRID(const UnicodeString &tzid, UnicodeString &systemID, UErrorCode& status) {
379      const UChar *canonicalID = getCanonicalCLDRID(tzid, status);
380      if (U_FAILURE(status) || canonicalID == NULL) {
381          systemID.setToBogus();
382          return systemID;
383      }
384      systemID.setTo(TRUE, canonicalID, -1);
385      return systemID;
386  }
387  
388  const UChar* U_EXPORT2
getCanonicalCLDRID(const TimeZone & tz)389  ZoneMeta::getCanonicalCLDRID(const TimeZone& tz) {
390      if (dynamic_cast<const OlsonTimeZone *>(&tz) != NULL) {
391          // short cut for OlsonTimeZone
392          const OlsonTimeZone *otz = (const OlsonTimeZone*)&tz;
393          return otz->getCanonicalID();
394      }
395      UErrorCode status = U_ZERO_ERROR;
396      UnicodeString tzID;
397      return getCanonicalCLDRID(tz.getID(tzID), status);
398  }
399  
countryInfoVectorsInit(UErrorCode & status)400  static void U_CALLCONV countryInfoVectorsInit(UErrorCode &status) {
401      // Create empty vectors
402      // No deleters for these UVectors, it's a reference to a resource bundle string.
403      gSingleZoneCountries = new UVector(NULL, uhash_compareUChars, status);
404      if (gSingleZoneCountries == NULL) {
405          status = U_MEMORY_ALLOCATION_ERROR;
406      }
407      gMultiZonesCountries = new UVector(NULL, uhash_compareUChars, status);
408      if (gMultiZonesCountries == NULL) {
409          status = U_MEMORY_ALLOCATION_ERROR;
410      }
411  
412      if (U_FAILURE(status)) {
413          delete gSingleZoneCountries;
414          delete gMultiZonesCountries;
415          gSingleZoneCountries = NULL;
416          gMultiZonesCountries  = NULL;
417      }
418      ucln_i18n_registerCleanup(UCLN_I18N_ZONEMETA, zoneMeta_cleanup);
419  }
420  
421  
422  UnicodeString& U_EXPORT2
getCanonicalCountry(const UnicodeString & tzid,UnicodeString & country,UBool * isPrimary)423  ZoneMeta::getCanonicalCountry(const UnicodeString &tzid, UnicodeString &country, UBool *isPrimary /* = NULL */) {
424      if (isPrimary != NULL) {
425          *isPrimary = FALSE;
426      }
427  
428      const UChar *region = TimeZone::getRegion(tzid);
429      if (region != NULL && u_strcmp(gWorld, region) != 0) {
430          country.setTo(region, -1);
431      } else {
432          country.setToBogus();
433          return country;
434      }
435  
436      if (isPrimary != NULL) {
437          char regionBuf[] = {0, 0, 0};
438  
439          // Checking the cached results
440          UErrorCode status = U_ZERO_ERROR;
441          umtx_initOnce(gCountryInfoVectorsInitOnce, &countryInfoVectorsInit, status);
442          if (U_FAILURE(status)) {
443              return country;
444          }
445  
446          // Check if it was already cached
447          UBool cached = FALSE;
448          UBool singleZone = FALSE;
449          umtx_lock(&gZoneMetaLock);
450          {
451              singleZone = cached = gSingleZoneCountries->contains((void*)region);
452              if (!cached) {
453                  cached = gMultiZonesCountries->contains((void*)region);
454              }
455          }
456          umtx_unlock(&gZoneMetaLock);
457  
458          if (!cached) {
459              // We need to go through all zones associated with the region.
460              // This is relatively heavy operation.
461  
462              U_ASSERT(u_strlen(region) == 2);
463  
464              u_UCharsToChars(region, regionBuf, 2);
465  
466              StringEnumeration *ids = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL_LOCATION, regionBuf, NULL, status);
467              int32_t idsLen = ids->count(status);
468              if (U_SUCCESS(status) && idsLen == 1) {
469                  // only the single zone is available for the region
470                  singleZone = TRUE;
471              }
472              delete ids;
473  
474              // Cache the result
475              umtx_lock(&gZoneMetaLock);
476              {
477                  UErrorCode ec = U_ZERO_ERROR;
478                  if (singleZone) {
479                      if (!gSingleZoneCountries->contains((void*)region)) {
480                          gSingleZoneCountries->addElement((void*)region, ec);
481                      }
482                  } else {
483                      if (!gMultiZonesCountries->contains((void*)region)) {
484                          gMultiZonesCountries->addElement((void*)region, ec);
485                      }
486                  }
487              }
488              umtx_unlock(&gZoneMetaLock);
489          }
490  
491          if (singleZone) {
492              *isPrimary = TRUE;
493          } else {
494              // Note: We may cache the primary zone map in future.
495  
496              // Even a country has multiple zones, one of them might be
497              // dominant and treated as a primary zone
498              int32_t idLen = 0;
499              if (regionBuf[0] == 0) {
500                  u_UCharsToChars(region, regionBuf, 2);
501              }
502  
503              UResourceBundle *rb = ures_openDirect(NULL, gMetaZones, &status);
504              ures_getByKey(rb, gPrimaryZonesTag, rb, &status);
505              const UChar *primaryZone = ures_getStringByKey(rb, regionBuf, &idLen, &status);
506              if (U_SUCCESS(status)) {
507                  if (tzid.compare(primaryZone, idLen) == 0) {
508                      *isPrimary = TRUE;
509                  } else {
510                      // The given ID might not be a canonical ID
511                      UnicodeString canonicalID;
512                      TimeZone::getCanonicalID(tzid, canonicalID, status);
513                      if (U_SUCCESS(status) && canonicalID.compare(primaryZone, idLen) == 0) {
514                          *isPrimary = TRUE;
515                      }
516                  }
517              }
518              ures_close(rb);
519          }
520      }
521  
522      return country;
523  }
524  
525  UnicodeString& U_EXPORT2
getMetazoneID(const UnicodeString & tzid,UDate date,UnicodeString & result)526  ZoneMeta::getMetazoneID(const UnicodeString &tzid, UDate date, UnicodeString &result) {
527      UBool isSet = FALSE;
528      const UVector *mappings = getMetazoneMappings(tzid);
529      if (mappings != NULL) {
530          for (int32_t i = 0; i < mappings->size(); i++) {
531              OlsonToMetaMappingEntry *mzm = (OlsonToMetaMappingEntry*)mappings->elementAt(i);
532              if (mzm->from <= date && mzm->to > date) {
533                  result.setTo(mzm->mzid, -1);
534                  isSet = TRUE;
535                  break;
536              }
537          }
538      }
539      if (!isSet) {
540          result.setToBogus();
541      }
542      return result;
543  }
544  
olsonToMetaInit(UErrorCode & status)545  static void U_CALLCONV olsonToMetaInit(UErrorCode &status) {
546      U_ASSERT(gOlsonToMeta == NULL);
547      ucln_i18n_registerCleanup(UCLN_I18N_ZONEMETA, zoneMeta_cleanup);
548      gOlsonToMeta = uhash_open(uhash_hashUChars, uhash_compareUChars, NULL, &status);
549      if (U_FAILURE(status)) {
550          gOlsonToMeta = NULL;
551      } else {
552          uhash_setKeyDeleter(gOlsonToMeta, deleteUCharString);
553          uhash_setValueDeleter(gOlsonToMeta, deleteUVector);
554      }
555  }
556  
557  
558  const UVector* U_EXPORT2
getMetazoneMappings(const UnicodeString & tzid)559  ZoneMeta::getMetazoneMappings(const UnicodeString &tzid) {
560      UErrorCode status = U_ZERO_ERROR;
561      UChar tzidUChars[ZID_KEY_MAX + 1];
562      tzid.extract(tzidUChars, ZID_KEY_MAX + 1, status);
563      if (U_FAILURE(status) || status == U_STRING_NOT_TERMINATED_WARNING) {
564          return NULL;
565      }
566  
567      umtx_initOnce(gOlsonToMetaInitOnce, &olsonToMetaInit, status);
568      if (U_FAILURE(status)) {
569          return NULL;
570      }
571  
572      // get the mapping from cache
573      const UVector *result = NULL;
574  
575      umtx_lock(&gZoneMetaLock);
576      {
577          result = (UVector*) uhash_get(gOlsonToMeta, tzidUChars);
578      }
579      umtx_unlock(&gZoneMetaLock);
580  
581      if (result != NULL) {
582          return result;
583      }
584  
585      // miss the cache - create new one
586      UVector *tmpResult = createMetazoneMappings(tzid);
587      if (tmpResult == NULL) {
588          // not available
589          return NULL;
590      }
591  
592      // put the new one into the cache
593      umtx_lock(&gZoneMetaLock);
594      {
595          // make sure it's already created
596          result = (UVector*) uhash_get(gOlsonToMeta, tzidUChars);
597          if (result == NULL) {
598              // add the one just created
599              int32_t tzidLen = tzid.length() + 1;
600              UChar *key = (UChar*)uprv_malloc(tzidLen * sizeof(UChar));
601              if (key == NULL) {
602                  // memory allocation error..  just return NULL
603                  result = NULL;
604                  delete tmpResult;
605              } else {
606                  tzid.extract(key, tzidLen, status);
607                  uhash_put(gOlsonToMeta, key, tmpResult, &status);
608                  if (U_FAILURE(status)) {
609                      // delete the mapping
610                      result = NULL;
611                      delete tmpResult;
612                  } else {
613                      result = tmpResult;
614                  }
615              }
616          } else {
617              // another thread already put the one
618              delete tmpResult;
619          }
620      }
621      umtx_unlock(&gZoneMetaLock);
622  
623      return result;
624  }
625  
626  UVector*
createMetazoneMappings(const UnicodeString & tzid)627  ZoneMeta::createMetazoneMappings(const UnicodeString &tzid) {
628      UVector *mzMappings = NULL;
629      UErrorCode status = U_ZERO_ERROR;
630  
631      UnicodeString canonicalID;
632      UResourceBundle *rb = ures_openDirect(NULL, gMetaZones, &status);
633      ures_getByKey(rb, gMetazoneInfo, rb, &status);
634      getCanonicalCLDRID(tzid, canonicalID, status);
635  
636      if (U_SUCCESS(status)) {
637          char tzKey[ZID_KEY_MAX + 1];
638          int32_t tzKeyLen = canonicalID.extract(0, canonicalID.length(), tzKey, sizeof(tzKey), US_INV);
639          tzKey[tzKeyLen] = 0;
640  
641          // tzid keys are using ':' as separators
642          char *p = tzKey;
643          while (*p) {
644              if (*p == '/') {
645                  *p = ':';
646              }
647              p++;
648          }
649  
650          ures_getByKey(rb, tzKey, rb, &status);
651  
652          if (U_SUCCESS(status)) {
653              UResourceBundle *mz = NULL;
654              while (ures_hasNext(rb)) {
655                  mz = ures_getNextResource(rb, mz, &status);
656  
657                  const UChar *mz_name = ures_getStringByIndex(mz, 0, NULL, &status);
658                  const UChar *mz_from = gDefaultFrom;
659                  const UChar *mz_to = gDefaultTo;
660  
661                  if (ures_getSize(mz) == 3) {
662                      mz_from = ures_getStringByIndex(mz, 1, NULL, &status);
663                      mz_to   = ures_getStringByIndex(mz, 2, NULL, &status);
664                  }
665  
666                  if(U_FAILURE(status)){
667                      status = U_ZERO_ERROR;
668                      continue;
669                  }
670                  // We do not want to use SimpleDateformat to parse boundary dates,
671                  // because this code could be triggered by the initialization code
672                  // used by SimpleDateFormat.
673                  UDate from = parseDate(mz_from, status);
674                  UDate to = parseDate(mz_to, status);
675                  if (U_FAILURE(status)) {
676                      status = U_ZERO_ERROR;
677                      continue;
678                  }
679  
680                  OlsonToMetaMappingEntry *entry = (OlsonToMetaMappingEntry*)uprv_malloc(sizeof(OlsonToMetaMappingEntry));
681                  if (entry == NULL) {
682                      status = U_MEMORY_ALLOCATION_ERROR;
683                      break;
684                  }
685                  entry->mzid = mz_name;
686                  entry->from = from;
687                  entry->to = to;
688  
689                  if (mzMappings == NULL) {
690                      mzMappings = new UVector(deleteOlsonToMetaMappingEntry, NULL, status);
691                      if (U_FAILURE(status)) {
692                          delete mzMappings;
693                          mzMappings = NULL;
694                          uprv_free(entry);
695                          break;
696                      }
697                  }
698  
699                  mzMappings->addElement(entry, status);
700                  if (U_FAILURE(status)) {
701                      break;
702                  }
703              }
704              ures_close(mz);
705              if (U_FAILURE(status)) {
706                  if (mzMappings != NULL) {
707                      delete mzMappings;
708                      mzMappings = NULL;
709                  }
710              }
711          }
712      }
713      ures_close(rb);
714      return mzMappings;
715  }
716  
717  UnicodeString& U_EXPORT2
getZoneIdByMetazone(const UnicodeString & mzid,const UnicodeString & region,UnicodeString & result)718  ZoneMeta::getZoneIdByMetazone(const UnicodeString &mzid, const UnicodeString &region, UnicodeString &result) {
719      UErrorCode status = U_ZERO_ERROR;
720      const UChar *tzid = NULL;
721      int32_t tzidLen = 0;
722      char keyBuf[ZID_KEY_MAX + 1];
723      int32_t keyLen = 0;
724  
725      if (mzid.isBogus() || mzid.length() > ZID_KEY_MAX) {
726          result.setToBogus();
727          return result;
728      }
729  
730      keyLen = mzid.extract(0, mzid.length(), keyBuf, ZID_KEY_MAX + 1, US_INV);
731      keyBuf[keyLen] = 0;
732  
733      UResourceBundle *rb = ures_openDirect(NULL, gMetaZones, &status);
734      ures_getByKey(rb, gMapTimezonesTag, rb, &status);
735      ures_getByKey(rb, keyBuf, rb, &status);
736  
737      if (U_SUCCESS(status)) {
738          // check region mapping
739          if (region.length() == 2 || region.length() == 3) {
740              keyLen = region.extract(0, region.length(), keyBuf, ZID_KEY_MAX + 1, US_INV);
741              keyBuf[keyLen] = 0;
742              tzid = ures_getStringByKey(rb, keyBuf, &tzidLen, &status);
743              if (status == U_MISSING_RESOURCE_ERROR) {
744                  status = U_ZERO_ERROR;
745              }
746          }
747          if (U_SUCCESS(status) && tzid == NULL) {
748              // try "001"
749              tzid = ures_getStringByKey(rb, gWorldTag, &tzidLen, &status);
750          }
751      }
752      ures_close(rb);
753  
754      if (tzid == NULL) {
755          result.setToBogus();
756      } else {
757          result.setTo(tzid, tzidLen);
758      }
759  
760      return result;
761  }
762  
initAvailableMetaZoneIDs()763  static void U_CALLCONV initAvailableMetaZoneIDs () {
764      U_ASSERT(gMetaZoneIDs == NULL);
765      U_ASSERT(gMetaZoneIDTable == NULL);
766      ucln_i18n_registerCleanup(UCLN_I18N_ZONEMETA, zoneMeta_cleanup);
767  
768      UErrorCode status = U_ZERO_ERROR;
769      gMetaZoneIDTable = uhash_open(uhash_hashUnicodeString, uhash_compareUnicodeString, NULL, &status);
770      if (U_FAILURE(status) || gMetaZoneIDTable == NULL) {
771          gMetaZoneIDTable = NULL;
772          return;
773      }
774      uhash_setKeyDeleter(gMetaZoneIDTable, uprv_deleteUObject);
775      // No valueDeleter, because the vector maintain the value objects
776      gMetaZoneIDs = new UVector(NULL, uhash_compareUChars, status);
777      if (U_FAILURE(status) || gMetaZoneIDs == NULL) {
778          gMetaZoneIDs = NULL;
779          uhash_close(gMetaZoneIDTable);
780          gMetaZoneIDTable = NULL;
781          return;
782      }
783      gMetaZoneIDs->setDeleter(uprv_free);
784  
785      UResourceBundle *rb = ures_openDirect(NULL, gMetaZones, &status);
786      UResourceBundle *bundle = ures_getByKey(rb, gMapTimezonesTag, NULL, &status);
787      StackUResourceBundle res;
788      while (U_SUCCESS(status) && ures_hasNext(bundle)) {
789          ures_getNextResource(bundle, res.getAlias(), &status);
790          if (U_FAILURE(status)) {
791              break;
792          }
793          const char *mzID = ures_getKey(res.getAlias());
794          int32_t len = static_cast<int32_t>(uprv_strlen(mzID));
795          UChar *uMzID = (UChar*)uprv_malloc(sizeof(UChar) * (len + 1));
796          if (uMzID == NULL) {
797              status = U_MEMORY_ALLOCATION_ERROR;
798              break;
799          }
800          u_charsToUChars(mzID, uMzID, len);
801          uMzID[len] = 0;
802          UnicodeString *usMzID = new UnicodeString(uMzID);
803          if (uhash_get(gMetaZoneIDTable, usMzID) == NULL) {
804              gMetaZoneIDs->addElement((void *)uMzID, status);
805              uhash_put(gMetaZoneIDTable, (void *)usMzID, (void *)uMzID, &status);
806          } else {
807              uprv_free(uMzID);
808              delete usMzID;
809          }
810      }
811      ures_close(bundle);
812      ures_close(rb);
813  
814      if (U_FAILURE(status)) {
815          uhash_close(gMetaZoneIDTable);
816          delete gMetaZoneIDs;
817          gMetaZoneIDTable = NULL;
818          gMetaZoneIDs = NULL;
819      }
820  }
821  
822  const UVector*
getAvailableMetazoneIDs()823  ZoneMeta::getAvailableMetazoneIDs() {
824      umtx_initOnce(gMetaZoneIDsInitOnce, &initAvailableMetaZoneIDs);
825      return gMetaZoneIDs;
826  }
827  
828  const UChar*
findMetaZoneID(const UnicodeString & mzid)829  ZoneMeta::findMetaZoneID(const UnicodeString& mzid) {
830      umtx_initOnce(gMetaZoneIDsInitOnce, &initAvailableMetaZoneIDs);
831      if (gMetaZoneIDTable == NULL) {
832          return NULL;
833      }
834      return (const UChar*)uhash_get(gMetaZoneIDTable, &mzid);
835  }
836  
837  const UChar*
findTimeZoneID(const UnicodeString & tzid)838  ZoneMeta::findTimeZoneID(const UnicodeString& tzid) {
839      return TimeZone::findID(tzid);
840  }
841  
842  
843  TimeZone*
createCustomTimeZone(int32_t offset)844  ZoneMeta::createCustomTimeZone(int32_t offset) {
845      UBool negative = FALSE;
846      int32_t tmp = offset;
847      if (offset < 0) {
848          negative = TRUE;
849          tmp = -offset;
850      }
851      uint8_t hour, min, sec;
852  
853      tmp /= 1000;
854      sec = static_cast<uint8_t>(tmp % 60);
855      tmp /= 60;
856      min = static_cast<uint8_t>(tmp % 60);
857      hour = static_cast<uint8_t>(tmp / 60);
858  
859      UnicodeString zid;
860      formatCustomID(hour, min, sec, negative, zid);
861      return new SimpleTimeZone(offset, zid);
862  }
863  
864  UnicodeString&
formatCustomID(uint8_t hour,uint8_t min,uint8_t sec,UBool negative,UnicodeString & id)865  ZoneMeta::formatCustomID(uint8_t hour, uint8_t min, uint8_t sec, UBool negative, UnicodeString& id) {
866      // Create normalized time zone ID - GMT[+|-]HH:mm[:ss]
867      id.setTo(gCustomTzPrefix, -1);
868      if (hour != 0 || min != 0) {
869          if (negative) {
870            id.append((UChar)0x2D);    // '-'
871          } else {
872            id.append((UChar)0x2B);    // '+'
873          }
874          // Always use US-ASCII digits
875          id.append((UChar)(0x30 + (hour%100)/10));
876          id.append((UChar)(0x30 + (hour%10)));
877          id.append((UChar)0x3A);    // ':'
878          id.append((UChar)(0x30 + (min%100)/10));
879          id.append((UChar)(0x30 + (min%10)));
880          if (sec != 0) {
881            id.append((UChar)0x3A);    // ':'
882            id.append((UChar)(0x30 + (sec%100)/10));
883            id.append((UChar)(0x30 + (sec%10)));
884          }
885      }
886      return id;
887  }
888  
889  const UChar*
getShortID(const TimeZone & tz)890  ZoneMeta::getShortID(const TimeZone& tz) {
891      const UChar* canonicalID = NULL;
892      if (dynamic_cast<const OlsonTimeZone *>(&tz) != NULL) {
893          // short cut for OlsonTimeZone
894          const OlsonTimeZone *otz = (const OlsonTimeZone*)&tz;
895          canonicalID = otz->getCanonicalID();
896      }
897      if (canonicalID == NULL) {
898          return NULL;
899      }
900      return getShortIDFromCanonical(canonicalID);
901  }
902  
903  const UChar*
getShortID(const UnicodeString & id)904  ZoneMeta::getShortID(const UnicodeString& id) {
905      UErrorCode status = U_ZERO_ERROR;
906      const UChar* canonicalID = ZoneMeta::getCanonicalCLDRID(id, status);
907      if (U_FAILURE(status) || canonicalID == NULL) {
908          return NULL;
909      }
910      return ZoneMeta::getShortIDFromCanonical(canonicalID);
911  }
912  
913  const UChar*
getShortIDFromCanonical(const UChar * canonicalID)914  ZoneMeta::getShortIDFromCanonical(const UChar* canonicalID) {
915      const UChar* shortID = NULL;
916      int32_t len = u_strlen(canonicalID);
917      char tzidKey[ZID_KEY_MAX + 1];
918  
919      u_UCharsToChars(canonicalID, tzidKey, len);
920      tzidKey[len] = (char) 0; // Make sure it is null terminated.
921  
922      // replace '/' with ':'
923      char *p = tzidKey;
924      while (*p++) {
925          if (*p == '/') {
926              *p = ':';
927          }
928      }
929  
930      UErrorCode status = U_ZERO_ERROR;
931      UResourceBundle *rb = ures_openDirect(NULL, gKeyTypeData, &status);
932      ures_getByKey(rb, gTypeMapTag, rb, &status);
933      ures_getByKey(rb, gTimezoneTag, rb, &status);
934      shortID = ures_getStringByKey(rb, tzidKey, NULL, &status);
935      ures_close(rb);
936  
937      return shortID;
938  }
939  
940  U_NAMESPACE_END
941  
942  #endif /* #if !UCONFIG_NO_FORMATTING */
943