1/*
2*******************************************************************************
3* Copyright (C) 2007-2009, International Business Machines Corporation and    *
4* others. All Rights Reserved.                                                *
5*******************************************************************************
6*/
7
8#include "unicode/utypes.h"
9
10#if !UCONFIG_NO_FORMATTING
11
12#include "zstrfmt.h"
13
14#include "unicode/ustring.h"
15#include "unicode/putil.h"
16#include "unicode/msgfmt.h"
17#include "unicode/basictz.h"
18#include "unicode/simpletz.h"
19#include "unicode/rbtz.h"
20#include "unicode/vtzone.h"
21
22#include "uvector.h"
23#include "cstring.h"
24#include "cmemory.h"
25#include "uresimp.h"
26#include "zonemeta.h"
27#include "olsontz.h"
28#include "umutex.h"
29#include "ucln_in.h"
30
31/**
32 * global ZoneStringFormatCache stuffs
33 */
34static UMTX gZSFCacheLock = NULL;
35static U_NAMESPACE_QUALIFIER ZSFCache *gZoneStringFormatCache = NULL;
36
37U_CDECL_BEGIN
38/**
39 * ZoneStringFormatCache cleanup callback func
40 */
41static UBool U_CALLCONV zoneStringFormat_cleanup(void)
42{
43    umtx_destroy(&gZSFCacheLock);
44    if (gZoneStringFormatCache != NULL) {
45        delete gZoneStringFormatCache;
46        gZoneStringFormatCache = NULL;
47    }
48    gZoneStringFormatCache = NULL;
49    return TRUE;
50}
51
52/**
53 * Deleter for ZoneStringInfo
54 */
55static void U_CALLCONV
56deleteZoneStringInfo(void *obj) {
57    delete (U_NAMESPACE_QUALIFIER ZoneStringInfo*)obj;
58}
59
60/**
61 * Deleter for ZoneStrings
62 */
63static void U_CALLCONV
64deleteZoneStrings(void *obj) {
65    delete (U_NAMESPACE_QUALIFIER ZoneStrings*)obj;
66}
67U_CDECL_END
68
69U_NAMESPACE_BEGIN
70
71#define ZID_KEY_MAX 128
72
73static const char gCountriesTag[]       = "Countries";
74static const char gZoneStringsTag[]     = "zoneStrings";
75static const char gShortGenericTag[]    = "sg";
76static const char gShortStandardTag[]   = "ss";
77static const char gShortDaylightTag[]   = "sd";
78static const char gLongGenericTag[]     = "lg";
79static const char gLongStandardTag[]    = "ls";
80static const char gLongDaylightTag[]    = "ld";
81static const char gExemplarCityTag[]    = "ec";
82static const char gCommonlyUsedTag[]    = "cu";
83static const char gFallbackFormatTag[]  = "fallbackFormat";
84static const char gRegionFormatTag[]    = "regionFormat";
85
86#define MZID_PREFIX_LEN 5
87static const char gMetazoneIdPrefix[]   = "meta:";
88
89#define MAX_METAZONES_PER_ZONE 10
90
91static const UChar gDefFallbackPattern[]    = {0x7B, 0x31, 0x7D, 0x20, 0x28, 0x7B, 0x30, 0x7D, 0x29, 0x00}; // "{1} ({0})"
92static const UChar gDefRegionPattern[]      = {0x7B, 0x30, 0x7D, 0x00}; // "{0}"
93static const UChar gCommonlyUsedTrue[]      = {0x31, 0x00}; // "1"
94
95static const double kDstCheckRange      = (double)184*U_MILLIS_PER_DAY;
96
97static int32_t
98getTimeZoneTranslationTypeIndex(TimeZoneTranslationType type) {
99    int32_t typeIdx = 0;
100    switch (type) {
101        case LOCATION:
102            typeIdx = ZSIDX_LOCATION;
103            break;
104        case GENERIC_LONG:
105            typeIdx = ZSIDX_LONG_GENERIC;
106            break;
107        case GENERIC_SHORT:
108            typeIdx = ZSIDX_SHORT_GENERIC;
109            break;
110        case STANDARD_LONG:
111            typeIdx = ZSIDX_LONG_STANDARD;
112            break;
113        case STANDARD_SHORT:
114            typeIdx = ZSIDX_SHORT_STANDARD;
115            break;
116        case DAYLIGHT_LONG:
117            typeIdx = ZSIDX_LONG_DAYLIGHT;
118            break;
119        case DAYLIGHT_SHORT:
120            typeIdx = ZSIDX_SHORT_DAYLIGHT;
121            break;
122    }
123    return typeIdx;
124}
125
126static int32_t
127getTimeZoneTranslationType(TimeZoneTranslationTypeIndex typeIdx) {
128    int32_t type = 0;
129    switch (typeIdx) {
130        case ZSIDX_LOCATION:
131            type = LOCATION;
132            break;
133        case ZSIDX_LONG_GENERIC:
134            type = GENERIC_LONG;
135            break;
136        case ZSIDX_SHORT_GENERIC:
137            type = GENERIC_SHORT;
138            break;
139        case ZSIDX_LONG_STANDARD:
140            type = STANDARD_LONG;
141            break;
142        case ZSIDX_SHORT_STANDARD:
143            type = STANDARD_SHORT;
144            break;
145        case ZSIDX_LONG_DAYLIGHT:
146            type = DAYLIGHT_LONG;
147            break;
148        case ZSIDX_COUNT:
149        case ZSIDX_SHORT_DAYLIGHT:
150            type = DAYLIGHT_SHORT;
151            break;
152        default:
153            break;
154    }
155    return type;
156}
157
158#define DEFAULT_CHARACTERNODE_CAPACITY 1
159
160// ----------------------------------------------------------------------------
161void CharacterNode::clear() {
162    uprv_memset(this, 0, sizeof(*this));
163}
164
165void CharacterNode::deleteValues() {
166    if (fValues == NULL) {
167        // Do nothing.
168    } else if (!fHasValuesVector) {
169        deleteZoneStringInfo(fValues);
170    } else {
171        delete (UVector *)fValues;
172    }
173}
174
175void
176CharacterNode::addValue(void *value, UErrorCode &status) {
177    if (U_FAILURE(status)) {
178        deleteZoneStringInfo(value);
179        return;
180    }
181    if (fValues == NULL) {
182        fValues = value;
183    } else {
184        // At least one value already.
185        if (!fHasValuesVector) {
186            // There is only one value so far, and not in a vector yet.
187            // Create a vector and add the old value.
188            UVector *values = new UVector(deleteZoneStringInfo, NULL, DEFAULT_CHARACTERNODE_CAPACITY, status);
189            if (U_FAILURE(status)) {
190                deleteZoneStringInfo(value);
191                return;
192            }
193            values->addElement(fValues, status);
194            fValues = values;
195            fHasValuesVector = TRUE;
196        }
197        // Add the new value.
198        ((UVector *)fValues)->addElement(value, status);
199    }
200}
201
202//----------------------------------------------------------------------------
203// Virtual destructor to avoid warning
204TextTrieMapSearchResultHandler::~TextTrieMapSearchResultHandler(){
205}
206
207// ----------------------------------------------------------------------------
208TextTrieMap::TextTrieMap(UBool ignoreCase)
209: fIgnoreCase(ignoreCase), fNodes(NULL), fNodesCapacity(0), fNodesCount(0) {
210}
211
212TextTrieMap::~TextTrieMap() {
213    int32_t index;
214    for (index = 0; index < fNodesCount; ++index) {
215        fNodes[index].deleteValues();
216    }
217    uprv_free(fNodes);
218}
219
220void
221TextTrieMap::put(const UnicodeString &key, void *value, UErrorCode &status) {
222    if (fNodes == NULL) {
223        fNodesCapacity = 512;
224        fNodes = (CharacterNode *)uprv_malloc(fNodesCapacity * sizeof(CharacterNode));
225        fNodes[0].clear();  // Init root node.
226        fNodesCount = 1;
227    }
228
229    UnicodeString foldedKey;
230    const UChar *keyBuffer;
231    int32_t keyLength;
232    if (fIgnoreCase) {
233        // Ok to use fastCopyFrom() because we discard the copy when we return.
234        foldedKey.fastCopyFrom(key).foldCase();
235        keyBuffer = foldedKey.getBuffer();
236        keyLength = foldedKey.length();
237    } else {
238        keyBuffer = key.getBuffer();
239        keyLength = key.length();
240    }
241
242    CharacterNode *node = fNodes;
243    int32_t index;
244    for (index = 0; index < keyLength; ++index) {
245        node = addChildNode(node, keyBuffer[index], status);
246    }
247    node->addValue(value, status);
248}
249
250UBool
251TextTrieMap::growNodes() {
252    if (fNodesCapacity == 0xffff) {
253        return FALSE;  // We use 16-bit node indexes.
254    }
255    int32_t newCapacity = fNodesCapacity * 2;
256    if (newCapacity > 0xffff) {
257        newCapacity = 0xffff;
258    }
259    CharacterNode *newNodes = (CharacterNode *)uprv_malloc(newCapacity * sizeof(CharacterNode));
260    if (newNodes == NULL) {
261        return FALSE;
262    }
263    uprv_memcpy(newNodes, fNodes, fNodesCount * sizeof(CharacterNode));
264    uprv_free(fNodes);
265    fNodes = newNodes;
266    fNodesCapacity = newCapacity;
267    return TRUE;
268}
269
270CharacterNode*
271TextTrieMap::addChildNode(CharacterNode *parent, UChar c, UErrorCode &status) {
272    if (U_FAILURE(status)) {
273        return NULL;
274    }
275    // Linear search of the sorted list of children.
276    uint16_t prevIndex = 0;
277    uint16_t nodeIndex = parent->fFirstChild;
278    while (nodeIndex > 0) {
279        CharacterNode *current = fNodes + nodeIndex;
280        UChar childCharacter = current->fCharacter;
281        if (childCharacter == c) {
282            return current;
283        } else if (childCharacter > c) {
284            break;
285        }
286        prevIndex = nodeIndex;
287        nodeIndex = current->fNextSibling;
288    }
289
290    // Ensure capacity. Grow fNodes[] if needed.
291    if (fNodesCount == fNodesCapacity) {
292        int32_t parentIndex = (parent - fNodes);
293        if (!growNodes()) {
294            status = U_MEMORY_ALLOCATION_ERROR;
295            return NULL;
296        }
297        parent = fNodes + parentIndex;
298    }
299
300    // Insert a new child node with c in sorted order.
301    CharacterNode *node = fNodes + fNodesCount;
302    node->clear();
303    node->fCharacter = c;
304    node->fNextSibling = nodeIndex;
305    if (prevIndex == 0) {
306        parent->fFirstChild = (uint16_t)fNodesCount;
307    } else {
308        fNodes[prevIndex].fNextSibling = (uint16_t)fNodesCount;
309    }
310    ++fNodesCount;
311    return node;
312}
313
314CharacterNode*
315TextTrieMap::getChildNode(CharacterNode *parent, UChar c) const {
316    // Linear search of the sorted list of children.
317    uint16_t nodeIndex = parent->fFirstChild;
318    while (nodeIndex > 0) {
319        CharacterNode *current = fNodes + nodeIndex;
320        UChar childCharacter = current->fCharacter;
321        if (childCharacter == c) {
322            return current;
323        } else if (childCharacter > c) {
324            break;
325        }
326        nodeIndex = current->fNextSibling;
327    }
328    return NULL;
329}
330
331void
332TextTrieMap::search(const UnicodeString &text, int32_t start,
333                  TextTrieMapSearchResultHandler *handler, UErrorCode &status) const {
334    if (fNodes == NULL) {
335        return;
336    }
337    search(fNodes, text, start, start, handler, status);
338}
339
340void
341TextTrieMap::search(CharacterNode *node, const UnicodeString &text, int32_t start,
342                  int32_t index, TextTrieMapSearchResultHandler *handler, UErrorCode &status) const {
343    if (U_FAILURE(status)) {
344        return;
345    }
346    if (node->hasValues()) {
347        if (!handler->handleMatch(index - start, node, status)) {
348            return;
349        }
350        if (U_FAILURE(status)) {
351            return;
352        }
353    }
354    UChar32 c = text.char32At(index);
355    if (fIgnoreCase) {
356        // size of character may grow after fold operation
357        UnicodeString tmp(c);
358        tmp.foldCase();
359        int32_t tmpidx = 0;
360        while (tmpidx < tmp.length()) {
361            c = tmp.char32At(tmpidx);
362            node = getChildNode(node, c);
363            if (node == NULL) {
364                break;
365            }
366            tmpidx = tmp.moveIndex32(tmpidx, 1);
367        }
368    } else {
369        node = getChildNode(node, c);
370    }
371    if (node != NULL) {
372        search(node, text, start, index+1, handler, status);
373    }
374}
375
376// ----------------------------------------------------------------------------
377ZoneStringInfo::ZoneStringInfo(const UnicodeString &id, const UnicodeString &str,
378                               TimeZoneTranslationType type)
379: fId(id), fStr(str), fType(type) {
380}
381
382ZoneStringInfo::~ZoneStringInfo() {
383}
384// ----------------------------------------------------------------------------
385ZoneStringSearchResultHandler::ZoneStringSearchResultHandler(UErrorCode &status)
386: fResults(status)
387{
388    clear();
389}
390
391ZoneStringSearchResultHandler::~ZoneStringSearchResultHandler() {
392    clear();
393}
394
395UBool
396ZoneStringSearchResultHandler::handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status) {
397    if (U_FAILURE(status)) {
398        return FALSE;
399    }
400    if (node->hasValues()) {
401        int32_t valuesCount = node->countValues();
402        for (int32_t i = 0; i < valuesCount; i++) {
403            ZoneStringInfo *zsinfo = (ZoneStringInfo*)node->getValue(i);
404            if (zsinfo == NULL) {
405                break;
406            }
407            // Update the results
408            UBool foundType = FALSE;
409            for (int32_t j = 0; j < fResults.size(); j++) {
410                ZoneStringInfo *tmp = (ZoneStringInfo*)fResults.elementAt(j);
411                if (zsinfo->fType == tmp->fType) {
412                    int32_t lenidx = getTimeZoneTranslationTypeIndex(tmp->fType);
413                    if (matchLength > fMatchLen[lenidx]) {
414                        // Same type, longer match
415                        fResults.setElementAt(zsinfo, j);
416                        fMatchLen[lenidx] = matchLength;
417                    }
418                    foundType = TRUE;
419                    break;
420                }
421            }
422            if (!foundType) {
423                // not found in the current list
424                fResults.addElement(zsinfo, status);
425                fMatchLen[getTimeZoneTranslationTypeIndex(zsinfo->fType)] = matchLength;
426            }
427        }
428    }
429    return TRUE;
430}
431
432int32_t
433ZoneStringSearchResultHandler::countMatches(void) {
434    return fResults.size();
435}
436
437const ZoneStringInfo*
438ZoneStringSearchResultHandler::getMatch(int32_t index, int32_t &matchLength) {
439    ZoneStringInfo *zsinfo = NULL;
440    if (index < fResults.size()) {
441        zsinfo = (ZoneStringInfo*)fResults.elementAt(index);
442        matchLength = fMatchLen[getTimeZoneTranslationTypeIndex(zsinfo->fType)];
443    }
444    return zsinfo;
445}
446
447void
448ZoneStringSearchResultHandler::clear(void) {
449    fResults.removeAllElements();
450    for (int32_t i = 0; i < (int32_t)(sizeof(fMatchLen)/sizeof(fMatchLen[0])); i++) {
451        fMatchLen[i] = 0;
452    }
453}
454// ----------------------------------------------------------------------------
455ZoneStringFormat::ZoneStringFormat(const UnicodeString* const* strings,
456                                   int32_t rowCount, int32_t columnCount, UErrorCode &status)
457: fLocale(""),
458  fTzidToStrings(uhash_compareUnicodeString, NULL, status),
459  fMzidToStrings(uhash_compareUnicodeString, NULL, status),
460  fZoneStringsTrie(TRUE)
461{
462    if (U_FAILURE(status)) {
463        return;
464    }
465    fLocale.setToBogus();
466    if (strings == NULL || columnCount <= 0 || rowCount <= 0) {
467        status = U_ILLEGAL_ARGUMENT_ERROR;
468        return;
469    }
470
471    fTzidToStrings.setValueDeleter(deleteZoneStrings);
472
473    for (int32_t row = 0; row < rowCount; row++) {
474        if (strings[row][0].isEmpty()) {
475            continue;
476        }
477        UnicodeString *names = new UnicodeString[ZSIDX_COUNT];
478        for (int32_t col = 1; col < columnCount; col++) {
479            if (!strings[row][col].isEmpty()) {
480                int32_t typeIdx = -1;
481                switch (col) {
482                    case 1:
483                        typeIdx = ZSIDX_LONG_STANDARD;
484                        break;
485                    case 2:
486                        typeIdx = ZSIDX_SHORT_STANDARD;
487                        break;
488                    case 3:
489                        typeIdx = ZSIDX_LONG_DAYLIGHT;
490                        break;
491                    case 4:
492                        typeIdx = ZSIDX_SHORT_DAYLIGHT;
493                        break;
494                    case 5:
495                        typeIdx = ZSIDX_LOCATION;
496                        break;
497                    case 6:
498                        typeIdx = ZSIDX_LONG_GENERIC;
499                        break;
500                    case 7:
501                        typeIdx = ZSIDX_SHORT_GENERIC;
502                        break;
503                }
504                if (typeIdx != -1) {
505                    names[typeIdx].setTo(strings[row][col]);
506
507                    // Put the name into the trie
508                    int32_t type = getTimeZoneTranslationType((TimeZoneTranslationTypeIndex)typeIdx);
509                    ZoneStringInfo *zsinf = new ZoneStringInfo(strings[row][0], strings[row][col], (TimeZoneTranslationType)type);
510                    fZoneStringsTrie.put(strings[row][col], zsinf, status);
511                    if (U_FAILURE(status)) {
512                        delete zsinf;
513                        goto error_cleanup;
514                    }
515                }
516            }
517        }
518        ZoneStrings *zstrings = new ZoneStrings(names, ZSIDX_COUNT, TRUE, NULL, 0, 0);
519        fTzidToStrings.put(strings[row][0], zstrings, status);
520        if (U_FAILURE(status)) {
521            delete zstrings;
522            goto error_cleanup;
523        }
524    }
525    return;
526
527error_cleanup:
528    return;
529}
530
531ZoneStringFormat::ZoneStringFormat(const Locale &locale, UErrorCode &status)
532: fLocale(locale),
533  fTzidToStrings(uhash_compareUnicodeString, NULL, status),
534  fMzidToStrings(uhash_compareUnicodeString, NULL, status),
535  fZoneStringsTrie(TRUE)
536{
537    if (U_FAILURE(status)) {
538        return;
539    }
540    fTzidToStrings.setValueDeleter(deleteZoneStrings);
541    fMzidToStrings.setValueDeleter(deleteZoneStrings);
542
543    UResourceBundle *zoneStringsArray = ures_open(NULL, locale.getName(), &status);
544    zoneStringsArray = ures_getByKeyWithFallback(zoneStringsArray, gZoneStringsTag, zoneStringsArray, &status);
545    if (U_FAILURE(status)) {
546        // If no locale bundles are available, zoneStrings will be null.
547        // We still want to go through the rest of zone strings initialization,
548        // because generic location format is generated from tzid for the case.
549        // The rest of code should work even zoneStrings is null.
550        status = U_ZERO_ERROR;
551        ures_close(zoneStringsArray);
552        zoneStringsArray = NULL;
553    }
554
555    StringEnumeration *tzids = NULL;
556    MessageFormat *fallbackFmt = NULL;
557    MessageFormat *regionFmt = NULL;
558
559    UResourceBundle *zoneItem = NULL;
560    UResourceBundle *metazoneItem = NULL;
561
562    char zidkey[ZID_KEY_MAX];
563    const UChar *zstrarray[ZSIDX_COUNT];
564    const UChar *mzstrarray[ZSIDX_COUNT];
565    UnicodeString mzPartialLoc[MAX_METAZONES_PER_ZONE][4];
566
567    UnicodeString region;
568    getRegion(region);
569
570    fallbackFmt = getFallbackFormat(locale, status);
571    if (U_FAILURE(status)) {
572        goto error_cleanup;
573    }
574    regionFmt = getRegionFormat(locale, status);
575    if (U_FAILURE(status)) {
576        goto error_cleanup;
577    }
578
579    tzids = TimeZone::createEnumeration();
580    const char *tzid;
581    while ((tzid = tzids->next(NULL, status))) {
582        if (U_FAILURE(status)) {
583            goto error_cleanup;
584        }
585        // Skip non-canonical IDs
586        UnicodeString utzid(tzid, -1, US_INV);
587        UnicodeString canonicalID;
588        TimeZone::getCanonicalID(utzid, canonicalID, status);
589        if (U_FAILURE(status)) {
590            // Ignore unknown ID - we should not get here, but just in case.
591            status = U_ZERO_ERROR;
592            continue;
593        }
594        if (utzid != canonicalID) {
595            continue;
596        }
597
598        uprv_strcpy(zidkey, tzid);
599
600        // Replace '/' with ':'
601        char *pCity = NULL;
602        char *p = zidkey;
603        while (*p) {
604            if (*p == '/') {
605                *p = ':';
606                pCity = p + 1;
607            }
608            p++;
609        }
610
611        if (zoneStringsArray != NULL) {
612            zoneItem = ures_getByKeyWithFallback(zoneStringsArray, zidkey, zoneItem, &status);
613            if (U_FAILURE(status)) {
614                // If failed to open the zone item, create only location string
615                ures_close(zoneItem);
616                zoneItem = NULL;
617                status = U_ZERO_ERROR;
618            }
619        }
620        zstrarray[ZSIDX_LONG_STANDARD] = getZoneStringFromBundle(zoneItem, gLongStandardTag);
621        zstrarray[ZSIDX_SHORT_STANDARD] = getZoneStringFromBundle(zoneItem, gShortStandardTag);
622        zstrarray[ZSIDX_LONG_DAYLIGHT] = getZoneStringFromBundle(zoneItem, gLongDaylightTag);
623        zstrarray[ZSIDX_SHORT_DAYLIGHT] = getZoneStringFromBundle(zoneItem, gShortDaylightTag);
624        zstrarray[ZSIDX_LONG_GENERIC] = getZoneStringFromBundle(zoneItem, gLongGenericTag);
625        zstrarray[ZSIDX_SHORT_GENERIC] = getZoneStringFromBundle(zoneItem, gShortGenericTag);
626
627        // Compose location format string
628        UnicodeString location;
629        UnicodeString country;
630        UnicodeString city;
631        UnicodeString countryCode;
632        ZoneMeta::getCanonicalCountry(utzid, countryCode);
633        if (!countryCode.isEmpty()) {
634            const UChar* tmpCity = getZoneStringFromBundle(zoneItem, gExemplarCityTag);
635            if (tmpCity != NULL) {
636                city.setTo(TRUE, tmpCity, -1);
637            } else {
638                city.setTo(UnicodeString(pCity, -1, US_INV));
639                // Replace '_' with ' '
640                for (int32_t i = 0; i < city.length(); i++) {
641                    if (city.charAt(i) == (UChar)0x5F /*'_'*/) {
642                        city.setCharAt(i, (UChar)0x20 /*' '*/);
643                    }
644                }
645            }
646            getLocalizedCountry(countryCode, locale, country);
647            UnicodeString singleCountry;
648            ZoneMeta::getSingleCountry(utzid, singleCountry);
649            FieldPosition fpos;
650            if (singleCountry.isEmpty()) {
651                Formattable params [] = {
652                    Formattable(city),
653                    Formattable(country)
654                };
655                fallbackFmt->format(params, 2, location, fpos, status);
656            } else {
657                // If the zone is only one zone in the country, do not add city
658                Formattable params [] = {
659                    Formattable(country)
660                };
661                regionFmt->format(params, 1, location, fpos, status);
662            }
663            if (U_FAILURE(status)) {
664                goto error_cleanup;
665            }
666
667            // Workaround for reducing UMR warning in Purify.
668            // Append NULL before calling getTerminatedBuffer()
669            int32_t locLen = location.length();
670            location.append((UChar)0).truncate(locLen);
671
672            zstrarray[ZSIDX_LOCATION] = location.getTerminatedBuffer();
673        } else {
674            if (uprv_strlen(tzid) > 4 && uprv_strncmp(tzid, "Etc/", 4) == 0) {
675                // "Etc/xxx" is not associated with a specific location, so localized
676                // GMT format is always used as generic location format.
677                zstrarray[ZSIDX_LOCATION] = NULL;
678            } else {
679                // When a new time zone ID, which is actually associated with a specific
680                // location, is added in tzdata, but the current CLDR data does not have
681                // the information yet, ICU creates a generic location string based on
682                // the ID.  This implementation supports canonical time zone round trip
683                // with format pattern "VVVV".  See #6602 for the details.
684                UnicodeString loc(utzid);
685                int32_t slashIdx = loc.lastIndexOf((UChar)0x2f);
686                if (slashIdx == -1) {
687                    // A time zone ID without slash in the tz database is not
688                    // associated with a specific location.  For instances,
689                    // MET, CET, EET and WET fall into this category.
690                    // In this case, we still use GMT format as fallback.
691                    zstrarray[ZSIDX_LOCATION] = NULL;
692                } else {
693                    FieldPosition fpos;
694                    Formattable params[] = {
695                        Formattable(loc)
696                    };
697                    regionFmt->format(params, 1, location, fpos, status);
698                    if (U_FAILURE(status)) {
699                        goto error_cleanup;
700                    }
701                    // Workaround for reducing UMR warning in Purify.
702                    // Append NULL before calling getTerminatedBuffer()
703                    int32_t locLen = location.length();
704                    location.append((UChar)0).truncate(locLen);
705
706                    zstrarray[ZSIDX_LOCATION] = location.getTerminatedBuffer();
707                }
708            }
709        }
710
711        UBool commonlyUsed = isCommonlyUsed(zoneItem);
712
713        // Resolve metazones used by this zone
714        int32_t mzPartialLocIdx = 0;
715        const UVector *metazoneMappings = ZoneMeta::getMetazoneMappings(utzid);
716        if (metazoneMappings != NULL) {
717            for (int32_t i = 0; i < metazoneMappings->size(); i++) {
718                const OlsonToMetaMappingEntry *mzmap = (const OlsonToMetaMappingEntry*)metazoneMappings->elementAt(i);
719                UnicodeString mzid(mzmap->mzid);
720                const ZoneStrings *mzStrings = (const ZoneStrings*)fMzidToStrings.get(mzid);
721                if (mzStrings == NULL) {
722                    // If the metazone strings are not yet processed, do it now.
723                    char mzidkey[ZID_KEY_MAX];
724                    uprv_strcpy(mzidkey, gMetazoneIdPrefix);
725                    u_UCharsToChars(mzmap->mzid, mzidkey + MZID_PREFIX_LEN, u_strlen(mzmap->mzid) + 1);
726                    metazoneItem = ures_getByKeyWithFallback(zoneStringsArray, mzidkey, metazoneItem, &status);
727                    if (U_FAILURE(status)) {
728                        // No resources available for this metazone
729                        // Resource bundle will be cleaned up after end of the loop.
730                        status = U_ZERO_ERROR;
731                        continue;
732                    }
733                    UBool mzCommonlyUsed = isCommonlyUsed(metazoneItem);
734                    mzstrarray[ZSIDX_LONG_STANDARD] = getZoneStringFromBundle(metazoneItem, gLongStandardTag);
735                    mzstrarray[ZSIDX_SHORT_STANDARD] = getZoneStringFromBundle(metazoneItem, gShortStandardTag);
736                    mzstrarray[ZSIDX_LONG_DAYLIGHT] = getZoneStringFromBundle(metazoneItem, gLongDaylightTag);
737                    mzstrarray[ZSIDX_SHORT_DAYLIGHT] = getZoneStringFromBundle(metazoneItem, gShortDaylightTag);
738                    mzstrarray[ZSIDX_LONG_GENERIC] = getZoneStringFromBundle(metazoneItem, gLongGenericTag);
739                    mzstrarray[ZSIDX_SHORT_GENERIC] = getZoneStringFromBundle(metazoneItem, gShortGenericTag);
740                    mzstrarray[ZSIDX_LOCATION] = NULL;
741
742                    int32_t lastNonNullIdx = ZSIDX_COUNT - 1;
743                    while (lastNonNullIdx >= 0) {
744                        if (mzstrarray[lastNonNullIdx] != NULL) {
745                            break;
746                        }
747                        lastNonNullIdx--;
748                    }
749                    UnicodeString *strings_mz = NULL;
750                    ZoneStrings *tmp_mzStrings = NULL;
751                    if (lastNonNullIdx >= 0) {
752                        // Create UnicodeString array and put strings to the zone string trie
753                        strings_mz = new UnicodeString[lastNonNullIdx + 1];
754
755                        UnicodeString preferredIdForLocale;
756                        ZoneMeta::getZoneIdByMetazone(mzid, region, preferredIdForLocale);
757
758                        for (int32_t typeidx = 0; typeidx <= lastNonNullIdx; typeidx++) {
759                            if (mzstrarray[typeidx] != NULL) {
760                                strings_mz[typeidx].setTo(TRUE, mzstrarray[typeidx], -1);
761
762                                // Add a metazone string to the zone string trie
763                                int32_t type = getTimeZoneTranslationType((TimeZoneTranslationTypeIndex)typeidx);
764                                ZoneStringInfo *zsinfo = new ZoneStringInfo(preferredIdForLocale, strings_mz[typeidx], (TimeZoneTranslationType)type);
765                                fZoneStringsTrie.put(strings_mz[typeidx], zsinfo, status);
766                                if (U_FAILURE(status)) {
767                                    delete []strings_mz;
768                                    goto error_cleanup;
769                                }
770                            }
771                        }
772                        tmp_mzStrings = new ZoneStrings(strings_mz, lastNonNullIdx + 1, mzCommonlyUsed, NULL, 0, 0);
773                    } else {
774                        // Create ZoneStrings with empty contents
775                        tmp_mzStrings = new ZoneStrings(NULL, 0, FALSE, NULL, 0, 0);
776                    }
777
778                    fMzidToStrings.put(mzid, tmp_mzStrings, status);
779                    if (U_FAILURE(status)) {
780                        goto error_cleanup;
781                    }
782
783                    mzStrings = tmp_mzStrings;
784                }
785
786                // Compose generic partial location format
787                UnicodeString lg;
788                UnicodeString sg;
789
790                mzStrings->getString(ZSIDX_LONG_GENERIC, lg);
791                mzStrings->getString(ZSIDX_SHORT_GENERIC, sg);
792
793                if (!lg.isEmpty() || !sg.isEmpty()) {
794                    UBool addMzPartialLocationNames = TRUE;
795                    for (int32_t j = 0; j < mzPartialLocIdx; j++) {
796                        if (mzPartialLoc[j][0] == mzid) {
797                            // already processed
798                            addMzPartialLocationNames = FALSE;
799                            break;
800                        }
801                    }
802                    if (addMzPartialLocationNames) {
803                        UnicodeString *locationPart = NULL;
804                        // Check if the zone is the preferred zone for the territory associated with the zone
805                        UnicodeString preferredID;
806                        ZoneMeta::getZoneIdByMetazone(mzid, countryCode, preferredID);
807                        if (utzid == preferredID) {
808                            // Use country for the location
809                            locationPart = &country;
810                        } else {
811                            // Use city for the location
812                            locationPart = &city;
813                        }
814                        // Reset the partial location string array
815                        mzPartialLoc[mzPartialLocIdx][0].setTo(mzid);
816                        mzPartialLoc[mzPartialLocIdx][1].remove();
817                        mzPartialLoc[mzPartialLocIdx][2].remove();
818                        mzPartialLoc[mzPartialLocIdx][3].remove();
819
820                        if (locationPart != NULL) {
821                            FieldPosition fpos;
822                            if (!lg.isEmpty()) {
823                                Formattable params [] = {
824                                    Formattable(*locationPart),
825                                    Formattable(lg)
826                                };
827                                fallbackFmt->format(params, 2, mzPartialLoc[mzPartialLocIdx][1], fpos, status);
828                            }
829                            if (!sg.isEmpty()) {
830                                Formattable params [] = {
831                                    Formattable(*locationPart),
832                                    Formattable(sg)
833                                };
834                                fallbackFmt->format(params, 2, mzPartialLoc[mzPartialLocIdx][2], fpos, status);
835                                if (mzStrings->isShortFormatCommonlyUsed()) {
836                                    mzPartialLoc[mzPartialLocIdx][3].setTo(TRUE, gCommonlyUsedTrue, -1);
837                                }
838                            }
839                            if (U_FAILURE(status)) {
840                                goto error_cleanup;
841                            }
842                        }
843                        mzPartialLocIdx++;
844                    }
845                }
846            }
847        }
848        // Collected names for a zone
849
850        // Create UnicodeString array for localized zone strings
851        int32_t lastIdx = ZSIDX_COUNT - 1;
852        while (lastIdx >= 0) {
853            if (zstrarray[lastIdx] != NULL) {
854                break;
855            }
856            lastIdx--;
857        }
858        UnicodeString *strings = NULL;
859        int32_t stringsCount = lastIdx + 1;
860
861        if (stringsCount > 0) {
862            strings = new UnicodeString[stringsCount];
863            for (int32_t i = 0; i < stringsCount; i++) {
864                if (zstrarray[i] != NULL) {
865                    strings[i].setTo(zstrarray[i], -1);
866
867                    // Add names to the trie
868                    int32_t type = getTimeZoneTranslationType((TimeZoneTranslationTypeIndex)i);
869                    ZoneStringInfo *zsinfo = new ZoneStringInfo(utzid, strings[i], (TimeZoneTranslationType)type);
870                    fZoneStringsTrie.put(strings[i], zsinfo, status);
871                    if (U_FAILURE(status)) {
872                        delete zsinfo;
873                        delete[] strings;
874                        goto error_cleanup;
875                    }
876                }
877            }
878        }
879
880        // Create UnicodeString array for generic partial location strings
881        UnicodeString **genericPartialLocationNames = NULL;
882        int32_t genericPartialRowCount = mzPartialLocIdx;
883        int32_t genericPartialColCount = 4;
884
885        if (genericPartialRowCount != 0) {
886            genericPartialLocationNames = (UnicodeString**)uprv_malloc(genericPartialRowCount * sizeof(UnicodeString*));
887            if (genericPartialLocationNames == NULL) {
888                status = U_MEMORY_ALLOCATION_ERROR;
889                delete[] strings;
890                goto error_cleanup;
891            }
892            for (int32_t i = 0; i < genericPartialRowCount; i++) {
893                genericPartialLocationNames[i] = new UnicodeString[genericPartialColCount];
894                for (int32_t j = 0; j < genericPartialColCount; j++) {
895                    genericPartialLocationNames[i][j].setTo(mzPartialLoc[i][j]);
896                    // Add names to the trie
897                    if ((j == 1 || j == 2) &&!genericPartialLocationNames[i][j].isEmpty()) {
898                        ZoneStringInfo *zsinfo;
899                        TimeZoneTranslationType type = (j == 1) ? GENERIC_LONG : GENERIC_SHORT;
900                        zsinfo = new ZoneStringInfo(utzid, genericPartialLocationNames[i][j], type);
901                        fZoneStringsTrie.put(genericPartialLocationNames[i][j], zsinfo, status);
902                        if (U_FAILURE(status)) {
903                            delete[] genericPartialLocationNames[i];
904                            uprv_free(genericPartialLocationNames);
905                            delete[] strings;
906                            goto error_cleanup;
907                        }
908                    }
909                }
910            }
911        }
912
913        // Finally, create ZoneStrings instance and put it into the tzidToStinrgs map
914        ZoneStrings *zstrings = new ZoneStrings(strings, stringsCount, commonlyUsed,
915            genericPartialLocationNames, genericPartialRowCount, genericPartialColCount);
916
917        fTzidToStrings.put(utzid, zstrings, status);
918        if (U_FAILURE(status)) {
919            delete zstrings;
920            goto error_cleanup;
921        }
922    }
923
924error_cleanup:
925    if (fallbackFmt != NULL) {
926        delete fallbackFmt;
927    }
928    if (regionFmt != NULL) {
929        delete regionFmt;
930    }
931    if (tzids != NULL) {
932        delete tzids;
933    }
934    ures_close(zoneItem);
935    ures_close(metazoneItem);
936    ures_close(zoneStringsArray);
937}
938
939ZoneStringFormat::~ZoneStringFormat() {
940}
941
942SafeZoneStringFormatPtr*
943ZoneStringFormat::getZoneStringFormat(const Locale& locale, UErrorCode &status) {
944    umtx_lock(&gZSFCacheLock);
945    if (gZoneStringFormatCache == NULL) {
946        gZoneStringFormatCache = new ZSFCache(10 /* capacity */);
947        ucln_i18n_registerCleanup(UCLN_I18N_ZSFORMAT, zoneStringFormat_cleanup);
948    }
949    umtx_unlock(&gZSFCacheLock);
950
951    return gZoneStringFormatCache->get(locale, status);
952}
953
954
955UnicodeString**
956ZoneStringFormat::createZoneStringsArray(UDate date, int32_t &rowCount, int32_t &colCount, UErrorCode &status) const {
957    if (U_FAILURE(status)) {
958        return NULL;
959    }
960    UnicodeString **result = NULL;
961    rowCount = 0;
962    colCount = 0;
963
964    // Collect canonical time zone IDs
965    UVector canonicalIDs(uhash_deleteUnicodeString, uhash_compareUnicodeString, status);
966    if (U_FAILURE(status)) {
967        return NULL;
968    }
969    StringEnumeration *tzids = TimeZone::createEnumeration();
970    const UChar *tzid;
971    while ((tzid = tzids->unext(NULL, status))) {
972        if (U_FAILURE(status)) {
973            delete tzids;
974            return NULL;
975        }
976        UnicodeString utzid(tzid);
977        UnicodeString canonicalID;
978        TimeZone::getCanonicalID(UnicodeString(tzid), canonicalID, status);
979        if (U_FAILURE(status)) {
980            // Ignore unknown ID - we should not get here, but just in case.
981            status = U_ZERO_ERROR;
982            continue;
983        }
984        if (utzid == canonicalID) {
985            canonicalIDs.addElement(new UnicodeString(utzid), status);
986            if (U_FAILURE(status)) {
987                delete tzids;
988                return NULL;
989            }
990        }
991    }
992    delete tzids;
993
994    // Allocate array
995    result = (UnicodeString**)uprv_malloc(canonicalIDs.size() * sizeof(UnicodeString*));
996    if (result == NULL) {
997        status = U_MEMORY_ALLOCATION_ERROR;
998        return NULL;
999    }
1000    for (int32_t i = 0; i < canonicalIDs.size(); i++) {
1001        result[i] = new UnicodeString[8];
1002        UnicodeString *id = (UnicodeString*)canonicalIDs.elementAt(i);
1003        result[i][0].setTo(*id);
1004        getLongStandard(*id, date, result[i][1]);
1005        getShortStandard(*id, date, FALSE, result[i][2]);
1006        getLongDaylight(*id, date, result[i][3]);
1007        getShortDaylight(*id, date, FALSE, result[i][4]);
1008        getGenericLocation(*id, result[i][5]);
1009        getLongGenericNonLocation(*id, date, result[i][6]);
1010        getShortGenericNonLocation(*id, date, FALSE, result[i][7]);
1011    }
1012
1013    rowCount = canonicalIDs.size();
1014    colCount = 8;
1015    return result;
1016}
1017
1018UnicodeString&
1019ZoneStringFormat::getSpecificLongString(const Calendar &cal, UnicodeString &result,
1020                                        UErrorCode &status) const {
1021    result.remove();
1022    if (U_FAILURE(status)) {
1023        return result;
1024    }
1025    UnicodeString tzid;
1026    cal.getTimeZone().getID(tzid);
1027    UDate date = cal.getTime(status);
1028    if (cal.get(UCAL_DST_OFFSET, status) == 0) {
1029        return getString(tzid, ZSIDX_LONG_STANDARD, date, FALSE /*not used*/, result);
1030    } else {
1031        return getString(tzid, ZSIDX_LONG_DAYLIGHT, date, FALSE /*not used*/, result);
1032    }
1033}
1034
1035UnicodeString&
1036ZoneStringFormat::getSpecificShortString(const Calendar &cal, UBool commonlyUsedOnly,
1037                                         UnicodeString &result, UErrorCode &status) const {
1038    result.remove();
1039    if (U_FAILURE(status)) {
1040        return result;
1041    }
1042    UnicodeString tzid;
1043    cal.getTimeZone().getID(tzid);
1044    UDate date = cal.getTime(status);
1045    if (cal.get(UCAL_DST_OFFSET, status) == 0) {
1046        return getString(tzid, ZSIDX_SHORT_STANDARD, date, commonlyUsedOnly, result);
1047    } else {
1048        return getString(tzid, ZSIDX_SHORT_DAYLIGHT, date, commonlyUsedOnly, result);
1049    }
1050}
1051
1052UnicodeString&
1053ZoneStringFormat::getGenericLongString(const Calendar &cal, UnicodeString &result,
1054                                       UErrorCode &status) const {
1055    return getGenericString(cal, FALSE /*long*/, FALSE /* not used */, result, status);
1056}
1057
1058UnicodeString&
1059ZoneStringFormat::getGenericShortString(const Calendar &cal, UBool commonlyUsedOnly,
1060                                        UnicodeString &result, UErrorCode &status) const {
1061    return getGenericString(cal, TRUE /*short*/, commonlyUsedOnly, result, status);
1062}
1063
1064UnicodeString&
1065ZoneStringFormat::getGenericLocationString(const Calendar &cal, UnicodeString &result,
1066                                           UErrorCode &status) const {
1067    UnicodeString tzid;
1068    cal.getTimeZone().getID(tzid);
1069    UDate date = cal.getTime(status);
1070    return getString(tzid, ZSIDX_LOCATION, date, FALSE /*not used*/, result);
1071}
1072
1073const ZoneStringInfo*
1074ZoneStringFormat::findSpecificLong(const UnicodeString &text, int32_t start,
1075                                   int32_t &matchLength, UErrorCode &status) const {
1076    return find(text, start, STANDARD_LONG | DAYLIGHT_LONG, matchLength, status);
1077}
1078
1079const ZoneStringInfo*
1080ZoneStringFormat::findSpecificShort(const UnicodeString &text, int32_t start,
1081                                    int32_t &matchLength, UErrorCode &status) const {
1082    return find(text, start, STANDARD_SHORT | DAYLIGHT_SHORT, matchLength, status);
1083}
1084
1085const ZoneStringInfo*
1086ZoneStringFormat::findGenericLong(const UnicodeString &text, int32_t start,
1087                                  int32_t &matchLength, UErrorCode &status) const {
1088    return find(text, start, GENERIC_LONG | STANDARD_LONG | LOCATION, matchLength, status);
1089}
1090
1091const ZoneStringInfo*
1092ZoneStringFormat::findGenericShort(const UnicodeString &text, int32_t start,
1093                                   int32_t &matchLength, UErrorCode &status) const {
1094    return find(text, start, GENERIC_SHORT | STANDARD_SHORT | LOCATION, matchLength, status);
1095}
1096
1097const ZoneStringInfo*
1098ZoneStringFormat::findGenericLocation(const UnicodeString &text, int32_t start,
1099                                      int32_t &matchLength, UErrorCode &status) const {
1100    return find(text, start, LOCATION, matchLength, status);
1101}
1102
1103UnicodeString&
1104ZoneStringFormat::getString(const UnicodeString &tzid, TimeZoneTranslationTypeIndex typeIdx, UDate date,
1105                            UBool commonlyUsedOnly, UnicodeString& result) const {
1106    result.remove();
1107
1108    // ICU's own array does not have entries for aliases
1109    UnicodeString canonicalID;
1110    UErrorCode status = U_ZERO_ERROR;
1111    TimeZone::getCanonicalID(tzid, canonicalID, status);
1112    if (U_FAILURE(status)) {
1113        // Unknown ID, but users might have their own data.
1114        canonicalID.setTo(tzid);
1115    }
1116
1117    if (fTzidToStrings.count() > 0) {
1118        ZoneStrings *zstrings = (ZoneStrings*)fTzidToStrings.get(canonicalID);
1119        if (zstrings != NULL) {
1120            switch (typeIdx) {
1121                case ZSIDX_LONG_STANDARD:
1122                case ZSIDX_LONG_DAYLIGHT:
1123                case ZSIDX_LONG_GENERIC:
1124                case ZSIDX_LOCATION:
1125                    zstrings->getString(typeIdx, result);
1126                    break;
1127                case ZSIDX_SHORT_STANDARD:
1128                case ZSIDX_SHORT_DAYLIGHT:
1129                case ZSIDX_COUNT: //added to avoid warning
1130                case ZSIDX_SHORT_GENERIC:
1131                    if (!commonlyUsedOnly || zstrings->isShortFormatCommonlyUsed()) {
1132                        zstrings->getString(typeIdx, result);
1133                    }
1134                    break;
1135                default:
1136                    break;
1137            }
1138        }
1139    }
1140    if (result.isEmpty() && fMzidToStrings.count() > 0 && typeIdx != ZSIDX_LOCATION) {
1141        // Try metazone
1142        UnicodeString mzid;
1143        ZoneMeta::getMetazoneID(canonicalID, date, mzid);
1144        if (!mzid.isEmpty()) {
1145            ZoneStrings *mzstrings = (ZoneStrings*)fMzidToStrings.get(mzid);
1146            if (mzstrings != NULL) {
1147                switch (typeIdx) {
1148                    case ZSIDX_LONG_STANDARD:
1149                    case ZSIDX_LONG_DAYLIGHT:
1150                    case ZSIDX_LONG_GENERIC:
1151                    case ZSIDX_LOCATION:
1152                        mzstrings->getString(typeIdx, result);
1153                        break;
1154                    case ZSIDX_SHORT_STANDARD:
1155                    case ZSIDX_SHORT_DAYLIGHT:
1156                    case ZSIDX_COUNT: //added to avoid warning
1157                    case ZSIDX_SHORT_GENERIC:
1158                        if (!commonlyUsedOnly || mzstrings->isShortFormatCommonlyUsed()) {
1159                            mzstrings->getString(typeIdx, result);
1160                        }
1161                        break;
1162                    default:
1163                        break;
1164                }
1165            }
1166        }
1167    }
1168    return result;
1169}
1170
1171UnicodeString&
1172ZoneStringFormat::getGenericString(const Calendar &cal, UBool isShort, UBool commonlyUsedOnly,
1173                                   UnicodeString &result, UErrorCode &status) const {
1174    result.remove();
1175    UDate time = cal.getTime(status);
1176    if (U_FAILURE(status)) {
1177        return result;
1178    }
1179    const TimeZone &tz = cal.getTimeZone();
1180    UnicodeString tzid;
1181    tz.getID(tzid);
1182
1183    // ICU's own array does not have entries for aliases
1184    UnicodeString canonicalID;
1185    TimeZone::getCanonicalID(tzid, canonicalID, status);
1186    if (U_FAILURE(status)) {
1187        // Unknown ID, but users might have their own data.
1188        status = U_ZERO_ERROR;
1189        canonicalID.setTo(tzid);
1190    }
1191
1192    ZoneStrings *zstrings = NULL;
1193    if (fTzidToStrings.count() > 0) {
1194        zstrings = (ZoneStrings*)fTzidToStrings.get(canonicalID);
1195        if (zstrings != NULL) {
1196            if (isShort) {
1197                if (!commonlyUsedOnly || zstrings->isShortFormatCommonlyUsed()) {
1198                    zstrings->getString(ZSIDX_SHORT_GENERIC, result);
1199                }
1200            } else {
1201                zstrings->getString(ZSIDX_LONG_GENERIC, result);
1202            }
1203        }
1204    }
1205    if (result.isEmpty() && fMzidToStrings.count() > 0) {
1206        // try metazone
1207        int32_t raw, sav;
1208        UnicodeString mzid;
1209        ZoneMeta::getMetazoneID(canonicalID, time, mzid);
1210        if (!mzid.isEmpty()) {
1211            UBool useStandard = FALSE;
1212            sav = cal.get(UCAL_DST_OFFSET, status);
1213            if (U_FAILURE(status)) {
1214                return result;
1215            }
1216            if (sav == 0) {
1217                useStandard = TRUE;
1218                // Check if the zone actually uses daylight saving time around the time
1219                TimeZone *tmptz = tz.clone();
1220                BasicTimeZone *btz = NULL;
1221                if (tmptz->getDynamicClassID() == OlsonTimeZone::getStaticClassID()
1222                    || tmptz->getDynamicClassID() == SimpleTimeZone::getStaticClassID()
1223                    || tmptz->getDynamicClassID() == RuleBasedTimeZone::getStaticClassID()
1224                    || tmptz->getDynamicClassID() == VTimeZone::getStaticClassID()) {
1225                    btz = (BasicTimeZone*)tmptz;
1226                }
1227
1228                if (btz != NULL) {
1229                    TimeZoneTransition before;
1230                    UBool beforTrs = btz->getPreviousTransition(time, TRUE, before);
1231                    if (beforTrs
1232                            && (time - before.getTime() < kDstCheckRange)
1233                            && before.getFrom()->getDSTSavings() != 0) {
1234                        useStandard = FALSE;
1235                    } else {
1236                        TimeZoneTransition after;
1237                        UBool afterTrs = btz->getNextTransition(time, FALSE, after);
1238                        if (afterTrs
1239                                && (after.getTime() - time < kDstCheckRange)
1240                                && after.getTo()->getDSTSavings() != 0) {
1241                            useStandard = FALSE;
1242                        }
1243                    }
1244                } else {
1245                    // If not BasicTimeZone... only if the instance is not an ICU's implementation.
1246                    // We may get a wrong answer in edge case, but it should practically work OK.
1247                    tmptz->getOffset(time - kDstCheckRange, FALSE, raw, sav, status);
1248                    if (sav != 0) {
1249                        useStandard = FALSE;
1250                    } else {
1251                        tmptz->getOffset(time + kDstCheckRange, FALSE, raw, sav, status);
1252                        if (sav != 0){
1253                            useStandard = FALSE;
1254                        }
1255                    }
1256                    if (U_FAILURE(status)) {
1257                        delete tmptz;
1258                        result.remove();
1259                        return result;
1260                    }
1261                }
1262                delete tmptz;
1263            }
1264            if (useStandard) {
1265                getString(canonicalID, (isShort ? ZSIDX_SHORT_STANDARD : ZSIDX_LONG_STANDARD),
1266                    time, commonlyUsedOnly, result);
1267
1268                // Note:
1269                // In CLDR 1.5.1, a same localization is used for both generic and standard
1270                // for some metazones in some locales.  This is actually data bugs and should
1271                // be resolved in later versions of CLDR.  For now, we check if the standard
1272                // name is different from its generic name below.
1273                if (!result.isEmpty()) {
1274                    UnicodeString genericNonLocation;
1275                    getString(canonicalID, (isShort ? ZSIDX_SHORT_GENERIC : ZSIDX_LONG_GENERIC),
1276                        time, commonlyUsedOnly, genericNonLocation);
1277                    if (!genericNonLocation.isEmpty() && result == genericNonLocation) {
1278                        result.remove();
1279                    }
1280                }
1281            }
1282            if (result.isEmpty()) {
1283                ZoneStrings *mzstrings = (ZoneStrings*)fMzidToStrings.get(mzid);
1284                if (mzstrings != NULL) {
1285                    if (isShort) {
1286                        if (!commonlyUsedOnly || mzstrings->isShortFormatCommonlyUsed()) {
1287                            mzstrings->getString(ZSIDX_SHORT_GENERIC, result);
1288                        }
1289                    } else {
1290                        mzstrings->getString(ZSIDX_LONG_GENERIC, result);
1291                    }
1292                }
1293                if (!result.isEmpty()) {
1294                    // Check if the offsets at the given time matches the preferred zone's offsets
1295                    UnicodeString preferredId;
1296                    UnicodeString region;
1297                    ZoneMeta::getZoneIdByMetazone(mzid, getRegion(region), preferredId);
1298                    if (canonicalID != preferredId) {
1299                        // Check if the offsets at the given time are identical with the preferred zone
1300                        raw = cal.get(UCAL_ZONE_OFFSET, status);
1301                        if (U_FAILURE(status)) {
1302                            result.remove();
1303                            return result;
1304                        }
1305                        TimeZone *preferredZone = TimeZone::createTimeZone(preferredId);
1306                        int32_t prfRaw, prfSav;
1307                        // Check offset in preferred time zone with wall time.
1308                        // With getOffset(time, false, preferredOffsets),
1309                        // you may get incorrect results because of time overlap at DST->STD
1310                        // transition.
1311                        preferredZone->getOffset(time + raw + sav, TRUE, prfRaw, prfSav, status);
1312                        delete preferredZone;
1313
1314                        if (U_FAILURE(status)) {
1315                            result.remove();
1316                            return result;
1317                        }
1318                        if ((raw != prfRaw || sav != prfSav) && zstrings != NULL) {
1319                            // Use generic partial location string as fallback
1320                            zstrings->getGenericPartialLocationString(mzid, isShort, commonlyUsedOnly, result);
1321                        }
1322                    }
1323                }
1324            }
1325        }
1326    }
1327    if (result.isEmpty()) {
1328        // Use location format as the final fallback
1329        getString(canonicalID, ZSIDX_LOCATION, time, FALSE /*not used*/, result);
1330    }
1331
1332    return result;
1333}
1334
1335UnicodeString&
1336ZoneStringFormat::getGenericPartialLocationString(const UnicodeString &tzid, UBool isShort,
1337                                                  UDate date, UBool commonlyUsedOnly, UnicodeString &result) const {
1338    result.remove();
1339    if (fTzidToStrings.count() <= 0) {
1340        return result;
1341    }
1342
1343    UnicodeString canonicalID;
1344    UErrorCode status = U_ZERO_ERROR;
1345    TimeZone::getCanonicalID(tzid, canonicalID, status);
1346    if (U_FAILURE(status)) {
1347        // Unknown ID, so no corresponding meta data.
1348        return result;
1349    }
1350
1351    UnicodeString mzid;
1352    ZoneMeta::getMetazoneID(canonicalID, date, mzid);
1353
1354    if (!mzid.isEmpty()) {
1355        ZoneStrings *zstrings = (ZoneStrings*)fTzidToStrings.get(canonicalID);
1356        if (zstrings != NULL) {
1357            zstrings->getGenericPartialLocationString(mzid, isShort, commonlyUsedOnly, result);
1358        }
1359    }
1360    return result;
1361}
1362
1363const ZoneStringInfo*
1364ZoneStringFormat::find(const UnicodeString &text, int32_t start, int32_t types,
1365                       int32_t &matchLength, UErrorCode &status) const {
1366    matchLength = 0;
1367    if (U_FAILURE(status)) {
1368        return NULL;
1369    }
1370    if (fZoneStringsTrie.isEmpty()) {
1371        return NULL;
1372    }
1373    const ZoneStringInfo *result = NULL;
1374    const ZoneStringInfo *fallback = NULL;
1375    int32_t fallbackMatchLen = 0;
1376
1377    ZoneStringSearchResultHandler handler(status);
1378    fZoneStringsTrie.search(text, start, (TextTrieMapSearchResultHandler*)&handler, status);
1379    if (U_SUCCESS(status)) {
1380        int32_t numMatches = handler.countMatches();
1381        for (int32_t i = 0; i < numMatches; i++) {
1382            int32_t tmpMatchLen;
1383            const ZoneStringInfo *tmp = handler.getMatch(i, tmpMatchLen);
1384            if ((types & tmp->fType) != 0) {
1385                if (result == NULL || matchLength < tmpMatchLen) {
1386                    result = tmp;
1387                    matchLength = tmpMatchLen;
1388                } else if (matchLength == tmpMatchLen) {
1389                    // Tie breaker - there are some examples that a
1390                    // long standard name is identical with a location
1391                    // name - for example, "Uruguay Time".  In this case,
1392                    // we interpret it as generic, not specific.
1393                    if (tmp->isGeneric() && !result->isGeneric()) {
1394                        result = tmp;
1395                    }
1396                }
1397            } else if (result == NULL) {
1398                if (fallback == NULL || fallbackMatchLen < tmpMatchLen) {
1399                    fallback = tmp;
1400                    fallbackMatchLen = tmpMatchLen;
1401                } else if (fallbackMatchLen == tmpMatchLen) {
1402                    if (tmp->isGeneric() && !fallback->isGeneric()) {
1403                        fallback = tmp;
1404                    }
1405                }
1406            }
1407        }
1408        if (result == NULL && fallback != NULL) {
1409            result = fallback;
1410            matchLength = fallbackMatchLen;
1411        }
1412    }
1413    return result;
1414}
1415
1416
1417UnicodeString&
1418ZoneStringFormat::getRegion(UnicodeString &region) const {
1419    const char* country = fLocale.getCountry();
1420    // TODO: Utilize addLikelySubtag in Locale to resolve default region
1421    // when the implementation is ready.
1422    region.setTo(UnicodeString(country, -1, US_INV));
1423    return region;
1424}
1425
1426MessageFormat*
1427ZoneStringFormat::getFallbackFormat(const Locale &locale, UErrorCode &status) {
1428    if (U_FAILURE(status)) {
1429        return NULL;
1430    }
1431    UnicodeString pattern(TRUE, gDefFallbackPattern, -1);
1432    UResourceBundle *zoneStringsArray = ures_open(NULL, locale.getName(), &status);
1433    zoneStringsArray = ures_getByKeyWithFallback(zoneStringsArray, gZoneStringsTag, zoneStringsArray, &status);
1434    int32_t len;
1435    const UChar *flbkfmt = ures_getStringByKeyWithFallback(zoneStringsArray, gFallbackFormatTag, &len, &status);
1436    if (U_SUCCESS(status)) {
1437        pattern.setTo(flbkfmt);
1438    } else {
1439        status = U_ZERO_ERROR;
1440    }
1441    ures_close(zoneStringsArray);
1442
1443    MessageFormat *fallbackFmt = new MessageFormat(pattern, status);
1444    return fallbackFmt;
1445}
1446
1447MessageFormat*
1448ZoneStringFormat::getRegionFormat(const Locale& locale, UErrorCode &status) {
1449    if (U_FAILURE(status)) {
1450        return NULL;
1451    }
1452    UnicodeString pattern(TRUE, gDefRegionPattern, -1);
1453    UResourceBundle *zoneStringsArray = ures_open(NULL, locale.getName(), &status);
1454    zoneStringsArray = ures_getByKeyWithFallback(zoneStringsArray, gZoneStringsTag, zoneStringsArray, &status);
1455    int32_t len;
1456    const UChar *regionfmt = ures_getStringByKeyWithFallback(zoneStringsArray, gRegionFormatTag, &len, &status);
1457    if (U_SUCCESS(status)) {
1458        pattern.setTo(regionfmt);
1459    } else {
1460        status = U_ZERO_ERROR;
1461    }
1462    ures_close(zoneStringsArray);
1463
1464    MessageFormat *regionFmt = new MessageFormat(pattern, status);
1465    return regionFmt;
1466}
1467
1468const UChar*
1469ZoneStringFormat::getZoneStringFromBundle(const UResourceBundle *zoneitem, const char *key) {
1470    const UChar *str = NULL;
1471    if (zoneitem != NULL) {
1472        UErrorCode status = U_ZERO_ERROR;
1473        int32_t len;
1474        str = ures_getStringByKeyWithFallback(zoneitem, key, &len, &status);
1475        if (U_FAILURE(status)) {
1476            str = NULL;
1477        }
1478    }
1479    return str;
1480}
1481
1482UBool
1483ZoneStringFormat::isCommonlyUsed(const UResourceBundle *zoneitem) {
1484    if (zoneitem == NULL) {
1485        return TRUE;
1486    }
1487
1488    UBool commonlyUsed = FALSE;
1489    UErrorCode status = U_ZERO_ERROR;
1490    UResourceBundle *cuRes = ures_getByKey(zoneitem, gCommonlyUsedTag, NULL, &status);
1491    int32_t cuValue = ures_getInt(cuRes, &status);
1492    if (U_SUCCESS(status)) {
1493        if (cuValue == 1) {
1494            commonlyUsed = TRUE;
1495        }
1496    }
1497    ures_close(cuRes);
1498    return commonlyUsed;
1499}
1500
1501UnicodeString&
1502ZoneStringFormat::getLocalizedCountry(const UnicodeString &countryCode, const Locale &locale, UnicodeString &displayCountry) {
1503    // We do not want to use display country names only from the target language bundle
1504    // Note: we should do this in better way.
1505    displayCountry.remove();
1506    int32_t ccLen = countryCode.length();
1507    if (ccLen > 0 && ccLen < ULOC_COUNTRY_CAPACITY) {
1508        UErrorCode status = U_ZERO_ERROR;
1509        UResourceBundle *localeBundle = ures_open(NULL, locale.getName(), &status);
1510        if (U_SUCCESS(status)) {
1511            const char *bundleLocStr = ures_getLocale(localeBundle, &status);
1512            if (U_SUCCESS(status) && uprv_strlen(bundleLocStr) > 0) {
1513                Locale bundleLoc(bundleLocStr);
1514                if (uprv_strcmp(bundleLocStr, "root") != 0 && uprv_strcmp(bundleLoc.getLanguage(), locale.getLanguage()) == 0) {
1515                    // Create a fake locale strings
1516                    char tmpLocStr[ULOC_COUNTRY_CAPACITY + 3];
1517                    uprv_strcpy(tmpLocStr, "xx_");
1518                    u_UCharsToChars(countryCode.getBuffer(), &tmpLocStr[3], ccLen);
1519                    tmpLocStr[3 + ccLen] = 0;
1520
1521                    Locale tmpLoc(tmpLocStr);
1522                    tmpLoc.getDisplayCountry(locale, displayCountry);
1523                }
1524            }
1525        }
1526        ures_close(localeBundle);
1527    }
1528    if (displayCountry.isEmpty()) {
1529        // Use the country code as the fallback
1530        displayCountry.setTo(countryCode);
1531    }
1532    return displayCountry;
1533}
1534
1535// ----------------------------------------------------------------------------
1536/*
1537 * This constructor adopts the input UnicodeString arrays.
1538 */
1539ZoneStrings::ZoneStrings(UnicodeString *strings, int32_t stringsCount, UBool commonlyUsed,
1540       UnicodeString **genericPartialLocationStrings, int32_t genericRowCount, int32_t genericColCount)
1541: fStrings(strings), fStringsCount(stringsCount), fIsCommonlyUsed(commonlyUsed),
1542  fGenericPartialLocationStrings(genericPartialLocationStrings),
1543  fGenericPartialLocationRowCount(genericRowCount), fGenericPartialLocationColCount(genericColCount) {
1544}
1545
1546ZoneStrings::~ZoneStrings() {
1547    if (fStrings != NULL) {
1548        delete[] fStrings;
1549    }
1550    if (fGenericPartialLocationStrings != NULL) {
1551        for (int32_t i = 0; i < fGenericPartialLocationRowCount; i++) {
1552            delete[] fGenericPartialLocationStrings[i];
1553        }
1554        uprv_free(fGenericPartialLocationStrings);
1555    }
1556}
1557
1558
1559UnicodeString&
1560ZoneStrings::getString(int32_t typeIdx, UnicodeString &result) const {
1561    if (typeIdx >= 0 && typeIdx < fStringsCount) {
1562        result.setTo(fStrings[typeIdx]);
1563    } else {
1564        result.remove();
1565    }
1566    return result;
1567}
1568
1569UnicodeString&
1570ZoneStrings::getGenericPartialLocationString(const UnicodeString &mzid, UBool isShort,
1571                                        UBool commonlyUsedOnly, UnicodeString &result) const {
1572    UBool isSet = FALSE;
1573    if (fGenericPartialLocationColCount >= 2) {
1574        for (int32_t i = 0; i < fGenericPartialLocationRowCount; i++) {
1575            if (fGenericPartialLocationStrings[i][0] == mzid) {
1576                if (isShort) {
1577                    if (fGenericPartialLocationColCount >= 3) {
1578                        if (!commonlyUsedOnly ||
1579                            fGenericPartialLocationColCount == 3 || fGenericPartialLocationStrings[i][3].length() != 0) {
1580                            result.setTo(fGenericPartialLocationStrings[i][2]);
1581                            isSet = TRUE;
1582                        }
1583                    }
1584                } else {
1585                    result.setTo(fGenericPartialLocationStrings[i][1]);
1586                    isSet = TRUE;
1587                }
1588                break;
1589            }
1590        }
1591    }
1592    if (!isSet) {
1593        result.remove();
1594    }
1595    return result;
1596}
1597
1598// --------------------------------------------------------------
1599SafeZoneStringFormatPtr::SafeZoneStringFormatPtr(ZSFCacheEntry *cacheEntry)
1600: fCacheEntry(cacheEntry) {
1601}
1602
1603SafeZoneStringFormatPtr::~SafeZoneStringFormatPtr() {
1604    fCacheEntry->delRef();
1605}
1606
1607const ZoneStringFormat*
1608SafeZoneStringFormatPtr::get() const {
1609    return fCacheEntry->getZoneStringFormat();
1610}
1611
1612ZSFCacheEntry::ZSFCacheEntry(const Locale &locale, ZoneStringFormat *zsf, ZSFCacheEntry *next)
1613: fLocale(locale), fZoneStringFormat(zsf),
1614 fNext(next), fRefCount(1)
1615{
1616}
1617
1618ZSFCacheEntry::~ZSFCacheEntry () {
1619    delete fZoneStringFormat;
1620}
1621
1622const ZoneStringFormat*
1623ZSFCacheEntry::getZoneStringFormat(void) {
1624    return (const ZoneStringFormat*)fZoneStringFormat;
1625}
1626
1627void
1628ZSFCacheEntry::delRef(void) {
1629    umtx_lock(&gZSFCacheLock);
1630    --fRefCount;
1631    umtx_unlock(&gZSFCacheLock);
1632}
1633
1634ZSFCache::ZSFCache(int32_t capacity)
1635: fCapacity(capacity), fFirst(NULL) {
1636}
1637
1638ZSFCache::~ZSFCache() {
1639    ZSFCacheEntry *entry = fFirst;
1640    while (entry) {
1641        ZSFCacheEntry *next = entry->fNext;
1642        delete entry;
1643        entry = next;
1644    }
1645}
1646
1647SafeZoneStringFormatPtr*
1648ZSFCache::get(const Locale &locale, UErrorCode &status) {
1649    SafeZoneStringFormatPtr *result = NULL;
1650
1651    // Search the cache entry list
1652    ZSFCacheEntry *entry = NULL;
1653    ZSFCacheEntry *next, *prev;
1654
1655    umtx_lock(&gZSFCacheLock);
1656    entry = fFirst;
1657    prev = NULL;
1658    while (entry) {
1659        next = entry->fNext;
1660        if (entry->fLocale == locale) {
1661            // Add reference count
1662            entry->fRefCount++;
1663
1664            // move the entry to the top
1665            if (entry != fFirst) {
1666                prev->fNext = next;
1667                entry->fNext = fFirst;
1668                fFirst = entry;
1669            }
1670            break;
1671        }
1672        prev = entry;
1673        entry = next;
1674    }
1675    umtx_unlock(&gZSFCacheLock);
1676
1677    // Create a new ZoneStringFormat
1678    if (entry == NULL) {
1679        ZoneStringFormat *zsf = new ZoneStringFormat(locale, status);
1680        if (U_FAILURE(status)) {
1681            delete zsf;
1682            return NULL;
1683        }
1684        if (zsf == NULL) {
1685            status = U_MEMORY_ALLOCATION_ERROR;
1686            return NULL;
1687        }
1688        // Now add the new entry
1689        umtx_lock(&gZSFCacheLock);
1690        // Make sure no other threads already created the one for the same locale
1691        entry = fFirst;
1692        prev = NULL;
1693        while (entry) {
1694            next = entry->fNext;
1695            if (entry->fLocale == locale) {
1696                // Add reference count
1697                entry->fRefCount++;
1698
1699                // move the entry to the top
1700                if (entry != fFirst) {
1701                    prev->fNext = next;
1702                    entry->fNext = fFirst;
1703                    fFirst = entry;
1704                }
1705                break;
1706            }
1707            prev = entry;
1708            entry = next;
1709        }
1710        if (entry == NULL) {
1711            // Add the new one to the top
1712            next = fFirst;
1713            entry = new ZSFCacheEntry(locale, zsf, next);
1714            fFirst = entry;
1715        } else {
1716            delete zsf;
1717        }
1718        umtx_unlock(&gZSFCacheLock);
1719    }
1720
1721    result = new SafeZoneStringFormatPtr(entry);
1722
1723    // Now, delete unused cache entries beyond the capacity
1724    umtx_lock(&gZSFCacheLock);
1725    entry = fFirst;
1726    prev = NULL;
1727    int32_t idx = 1;
1728    while (entry) {
1729        next = entry->fNext;
1730        if (idx >= fCapacity && entry->fRefCount == 0) {
1731            if (entry == fFirst) {
1732                fFirst = next;
1733            } else {
1734                prev->fNext = next;
1735            }
1736            delete entry;
1737        } else {
1738            prev = entry;
1739        }
1740        entry = next;
1741        idx++;
1742    }
1743    umtx_unlock(&gZSFCacheLock);
1744
1745    return result;
1746}
1747
1748U_NAMESPACE_END
1749
1750#endif /* #if !UCONFIG_NO_FORMATTING */
1751