From a2d03cc0fb9b2aa98b58bc8d83ad8d6c41f6ebdf Mon Sep 17 00:00:00 2001 From: John Emmons Date: Mon, 6 Aug 2007 15:17:35 +0000 Subject: [PATCH] ICU-5708 Generic time zone support X-SVN-Rev: 22283 --- icu4c/source/i18n/dtfmtsym.cpp | 135 +++++++++++++++++++++++++- icu4c/source/i18n/smpdtfmt.cpp | 90 ++++++++++++++++- icu4c/source/i18n/unicode/dtfmtsym.h | 12 +++ icu4c/source/i18n/unicode/smpdtfmt.h | 7 ++ icu4c/source/test/intltest/tztest.cpp | 9 +- 5 files changed, 242 insertions(+), 11 deletions(-) diff --git a/icu4c/source/i18n/dtfmtsym.cpp b/icu4c/source/i18n/dtfmtsym.cpp index ff59b0547f0..f2da1e687ef 100644 --- a/icu4c/source/i18n/dtfmtsym.cpp +++ b/icu4c/source/i18n/dtfmtsym.cpp @@ -19,13 +19,13 @@ * 10/12/05 emmons Added setters for eraNames, month/day by width/context ******************************************************************************* */ - #include "unicode/utypes.h" #if !UCONFIG_NO_FORMATTING #include "unicode/ustring.h" #include "unicode/dtfmtsym.h" #include "unicode/smpdtfmt.h" +#include "unicode/msgfmt.h" #include "cpputils.h" #include "ucln_in.h" #include "umutex.h" @@ -173,6 +173,12 @@ static const char gAmPmMarkersTag[]="AmPmMarkers"; static const char gQuartersTag[]="quarters"; static const char gMaptimezonesTag[]="mapTimezones"; static const char gMetazonesTag[]="metazones"; +static const char gTerritoryTag[]="territory"; +static const char gCountriesTag[]="Countries"; +static const char gZoneFormattingTag[]="zoneFormatting"; +static const char gMultizoneTag[]="multizone"; +static const char gRegionFormatTag[]="zoneStrings/regionFormat"; +static const char gFallbackFormatTag[]="zoneStrings/fallbackFormat"; /** * These are the tags we expect to see in time zone data resource bundle files @@ -1977,6 +1983,106 @@ DateFormatSymbols::getMetazoneString(const UnicodeString &zid, const TimeZoneTra ures_close(um); return result; } + +UnicodeString& +DateFormatSymbols::getFallbackString(const UnicodeString &zid, UnicodeString &result, UErrorCode &status) +{ + UnicodeString exemplarCity; + char zidkey[ZID_KEY_MAX]; + char zoneTerritoryChars[ULOC_COUNTRY_CAPACITY]; + UnicodeString displayCountry; + UnicodeString solidus = UNICODE_STRING_SIMPLE("/"); + UnicodeString und = UNICODE_STRING_SIMPLE("_"); + UnicodeString spc = UNICODE_STRING_SIMPLE(" "); + const UChar* aZone = NULL; + UBool IsMultiZone = FALSE; + + + int32_t len = zid.length(); + len = (len >= (ZID_KEY_MAX-1) ? ZID_KEY_MAX-1 : len); + u_UCharsToChars(zid.getBuffer(), zidkey, len); + zidkey[len] = 0; // NULL terminate + + // Replace / with : for zid + len = (int32_t)uprv_strlen(zidkey); + for (int i = 0; i < len; i++) { + if (zidkey[i] == '/') { + zidkey[i] = ':'; + } + } + + result.remove(); + + UResourceBundle* supplementalDataBundle = ures_openDirect(NULL, kSUPPLEMENTAL, &status); + if (U_FAILURE(status) || fResourceBundle == NULL ) { + return result; + } + + UResourceBundle* zoneFormatting = ures_getByKey(supplementalDataBundle, gZoneFormattingTag, NULL, &status); + UResourceBundle* thisZone = ures_getByKey(zoneFormatting, zidkey, NULL, &status); + if (U_FAILURE(status)) { + ures_close(zoneFormatting); + ures_close(supplementalDataBundle); + return result; + } + + UResourceBundle* multiZone = ures_getByKey(zoneFormatting, gMultizoneTag, NULL, &status); + const UChar *zoneTerritory = ures_getStringByKey(thisZone,gTerritoryTag,&len,&status); + u_UCharsToChars(zoneTerritory, zoneTerritoryChars, u_strlen(zoneTerritory)); + zoneTerritoryChars[u_strlen(zoneTerritory)] = 0; // NULL terminate + + UResourceBundle* countries = ures_getByKey(fResourceBundle, gCountriesTag, NULL, &status); + if ( u_strlen(zoneTerritory) > 0 && countries != NULL ) { + displayCountry = ures_getStringByKeyWithFallback(countries,zoneTerritoryChars,&len,&status); + } + + if ( U_FAILURE(status) ) { + status = U_ZERO_ERROR; + displayCountry = UnicodeString(zoneTerritory); + } + + while ( ures_hasNext(multiZone) ) { + aZone = ures_getNextString(multiZone,&len,NULL,&status); + if ( u_strcmp(aZone,zoneTerritory) == 0 ) { + IsMultiZone = TRUE; + continue; + } + } + + if ( IsMultiZone ) { + getZoneString(zid, TIMEZONE_EXEMPLAR_CITY, exemplarCity, status); + if ( exemplarCity.length()==0 ) { + exemplarCity.setTo(UnicodeString(zid,zid.lastIndexOf(solidus)+1)); + exemplarCity.findAndReplace(und,spc); + } + Formattable cityCountryArray[2]; + UnicodeString pattern = UnicodeString(ures_getStringByKeyWithFallback(fResourceBundle,gFallbackFormatTag,&len,&status)); + if ( U_FAILURE(status) ) { + pattern = UNICODE_STRING_SIMPLE("{1} ({0})"); + status = U_ZERO_ERROR; + } + cityCountryArray[0].adoptString(new UnicodeString(exemplarCity)); + cityCountryArray[1].adoptString(new UnicodeString(displayCountry)); + MessageFormat::format(pattern,cityCountryArray, 2, result, status); + } else { + Formattable countryArray[1]; + UnicodeString pattern = UnicodeString(ures_getStringByKeyWithFallback(fResourceBundle,gRegionFormatTag,&len,&status)); + if ( U_FAILURE(status) ) { + pattern = UNICODE_STRING_SIMPLE("{0}"); + status = U_ZERO_ERROR; + } + countryArray[0].adoptString(new UnicodeString(displayCountry)); + MessageFormat::format(pattern,countryArray, 1, result, status); + } + + if (thisZone) ures_close(thisZone); + if (zoneFormatting) ures_close(zoneFormatting); + if (supplementalDataBundle) ures_close(supplementalDataBundle); + if (countries) ures_close(countries); + + return result; +} + StringEnumeration* DateFormatSymbols::createZoneStringIDs(UErrorCode &status){ if(U_FAILURE(status)){ @@ -2132,13 +2238,38 @@ DateFormatSymbols::findZoneIDTypeValue( UnicodeString& zid, const UnicodeString& if (myKey->startsWith(UNICODE_STRING_SIMPLE("meta"))) { zid.setTo(resolveParsedMetazone(*myKey)); } - else + else { zid.setTo(*myKey); + } return; } } } } + + // Check for generic tz fallback strings if we have gone through all zone strings and haven't found + // anything. + + UnicodeString fbString; + StringEnumeration *tzKeys = TimeZone::createEnumeration(); + + tzKeys->reset(status); + while( (myKey=tzKeys->snext(status))!= NULL){ + char outpt[1000]; + status = U_ZERO_ERROR; + this->getFallbackString(*myKey,fbString,status); + if ( U_FAILURE(status) ) { + status = U_ZERO_ERROR; + continue; + } + + if(fbString.length()>0 && text.compare(start, fbString.length(), fbString)==0){ + type = (TimeZoneTranslationType) TIMEZONE_LONG_GENERIC; + value.setTo(fbString); + zid.setTo(*myKey); + return; + } + } } UnicodeString diff --git a/icu4c/source/i18n/smpdtfmt.cpp b/icu4c/source/i18n/smpdtfmt.cpp index 6652adefd62..8a794df13e6 100644 --- a/icu4c/source/i18n/smpdtfmt.cpp +++ b/icu4c/source/i18n/smpdtfmt.cpp @@ -24,6 +24,8 @@ ******************************************************************************** */ +#define ZID_KEY_MAX 128 + #include "unicode/utypes.h" #if !UCONFIG_NO_FORMATTING @@ -81,6 +83,9 @@ static const UChar SUPPRESS_NEGATIVE_PREFIX[] = {0xAB00, 0}; * These are the tags we expect to see in normal resource bundle files associated * with a locale. */ +static const char kSUPPLEMENTAL[]="supplementalData"; +static const char gZoneFormattingTag[]="zoneFormatting"; +static const char gAliases[]="aliases"; static const char gDateTimePatternsTag[]="DateTimePatterns"; UOBJECT_DEFINE_RTTI_IMPLEMENTATION(SimpleDateFormat) @@ -780,16 +785,20 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, appendGMT(appendTo, cal, status); } else { - + zoneIDCanonicalize(zid); if (patternCharIndex == UDAT_TIMEZONE_GENERIC_FIELD) { if(count < 4){ fSymbols->getZoneString(zid, DateFormatSymbols::TIMEZONE_SHORT_GENERIC, displayString, status); if(displayString.length()==0) fSymbols->getMetazoneString(zid, DateFormatSymbols::TIMEZONE_SHORT_GENERIC, cal, displayString, status); + if(displayString.length()==0) + fSymbols->getFallbackString(zid, displayString, status); }else{ fSymbols->getZoneString(zid, DateFormatSymbols::TIMEZONE_LONG_GENERIC, displayString, status); if(displayString.length()==0) fSymbols->getMetazoneString(zid, DateFormatSymbols::TIMEZONE_LONG_GENERIC, cal, displayString, status); + if(displayString.length()==0) + fSymbols->getFallbackString(zid, displayString, status); } } else { if (cal.get(UCAL_DST_OFFSET, status) != 0) { @@ -876,7 +885,86 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, pos.setEndIndex(appendTo.length()); } } +//---------------------------------------------------------------------- +void +SimpleDateFormat::zoneIDCanonicalize(UnicodeString &zid) const +{ + UnicodeString colon = UNICODE_STRING_SIMPLE(":"); + UnicodeString solidus = UNICODE_STRING_SIMPLE("/"); + char zidkey[ZID_KEY_MAX]; + int32_t len; + + UErrorCode status = U_ZERO_ERROR; + UResourceBundle* supplementalDataBundle = ures_openDirect(NULL, kSUPPLEMENTAL, &status); + if (U_FAILURE(status)) { + return; + } + + UResourceBundle* zoneFormatting = ures_getByKey(supplementalDataBundle, gZoneFormattingTag, NULL, &status); + + // First try to lookup the zone itself, since most of the time the canonical ID and the ID itself + // will be the same. This should save us a significant amount of time. + + len = zid.length(); + len = (len >= (ZID_KEY_MAX-1) ? ZID_KEY_MAX-1 : len); + u_UCharsToChars(zid.getBuffer(), zidkey, len); + zidkey[len] = 0; // NULL terminate + + // Replace / with : for zid + len = (int32_t)uprv_strlen(zidkey); + for (int i = 0; i < len; i++) { + if (zidkey[i] == '/') { + zidkey[i] = ':'; + } + } + + UResourceBundle* tryThisZone = ures_getByKey(zoneFormatting,zidkey,NULL,&status); + if (U_SUCCESS(status)) { + ures_close(tryThisZone); + ures_close(zoneFormatting); + ures_close(supplementalDataBundle); + return; + } + + // Didn't find it, so go searching for an alias + + while ( ures_hasNext(zoneFormatting)) { + UResourceBundle *currentZone = ures_getNextResource(zoneFormatting,NULL,&status); + if (U_FAILURE(status)) { + ures_close(supplementalDataBundle); + return; + } + + const char *currentZoneString= ures_getKey(currentZone); + + UResourceBundle *zoneAliases = ures_getByKey(currentZone,gAliases,NULL, &status); + if (U_FAILURE(status)) { + status = U_ZERO_ERROR; + ures_close(currentZone); + continue; + } + while ( ures_hasNext(zoneAliases)) { + int32_t len; + const UChar* alias = ures_getNextString(zoneAliases,&len,NULL,&status); + if ( zid.compare(alias)==0 ) { + zid.setTo(UnicodeString(currentZoneString,(const char *)0)); + zid.findAndReplace(colon,solidus); + ures_close(zoneAliases); + ures_close(currentZone); + ures_close(zoneFormatting); + ures_close(supplementalDataBundle); + return; + } + } + ures_close(zoneAliases); + ures_close(currentZone); + } + ures_close(zoneFormatting); + ures_close(supplementalDataBundle); + + return; +} //---------------------------------------------------------------------- void diff --git a/icu4c/source/i18n/unicode/dtfmtsym.h b/icu4c/source/i18n/unicode/dtfmtsym.h index 6372e396736..9fa8c368124 100644 --- a/icu4c/source/i18n/unicode/dtfmtsym.h +++ b/icu4c/source/i18n/unicode/dtfmtsym.h @@ -497,6 +497,18 @@ public: */ UnicodeString& getMetazoneString(const UnicodeString &ID, const TimeZoneTranslationType type, Calendar &cal, UnicodeString &result, UErrorCode &status); + /** + * Gets fallback string given the key + * @param ID The ID of zone strings, e.g: "America/Los_Angeles". + * The time zone ID is for programmatic lookup. + * @param result Output parameter to recieve the translation string + * @param status Input/output parameter, set to success or + * failure code upon return. + * @return the input UnicodeString parameter for chaining + * @internal ICU 3.8 + */ + UnicodeString& getFallbackString(const UnicodeString &ID, UnicodeString &result, UErrorCode &status); + /** * Sets timezone string for the given the ID and translation type * @param ID The ID of zone strings, e.g: "America/Los_Angeles". diff --git a/icu4c/source/i18n/unicode/smpdtfmt.h b/icu4c/source/i18n/unicode/smpdtfmt.h index 4c2195943af..b0bbc41ff51 100644 --- a/icu4c/source/i18n/unicode/smpdtfmt.h +++ b/icu4c/source/i18n/unicode/smpdtfmt.h @@ -649,6 +649,13 @@ private: Calendar& cal, UErrorCode& status) const; // in case of illegal argument + /** + * Used to resolve Time Zone aliases + * + * @param zid Time Zone ID to Canonicalize ( resolve aliases ) + */ + void zoneIDCanonicalize( UnicodeString & ) const; + /** * Used by subFormat() to format a numeric value. * Appends to toAppendTo a string representation of "value" diff --git a/icu4c/source/test/intltest/tztest.cpp b/icu4c/source/test/intltest/tztest.cpp index 68f4b210d3e..42bf582d47b 100644 --- a/icu4c/source/test/intltest/tztest.cpp +++ b/icu4c/source/test/intltest/tztest.cpp @@ -914,8 +914,6 @@ void TimeZoneTest::TestCustomParse() } } -static const UVersionInfo ICU_39 = {3,9,0,0}; - void TimeZoneTest::TestAliasedNames() { @@ -1047,10 +1045,6 @@ TimeZoneTest::TestAliasedNames() UBool useDst[] = { FALSE, TRUE }; int32_t noLoc = uloc_countAvailable(); - if(isICUVersionAtLeast(ICU_39)) { - errln("This test needs to be fixed. This test fails in exhaustive mode because we need to implement generic timezones.\n"); - } - int32_t i, j, k, loc; UnicodeString fromName, toName; TimeZone *from = NULL, *to = NULL; @@ -1060,8 +1054,7 @@ TimeZoneTest::TestAliasedNames() if(!from->hasSameRules(*to)) { errln("different at %i\n", i); } - if(!quick && isICUVersionAtLeast(ICU_39)) { - errln("This test needs to be fixed. This test fails in exhaustive mode because we need to implement generic timezones.\n"); + if(!quick) { for(loc = 0; loc < noLoc; loc++) { const char* locale = uloc_getAvailable(loc); for(j = 0; j < (int32_t)(sizeof(styles)/sizeof(styles[0])); j++) {