diff options
author | Jean-Baptiste Queru <jbq@google.com> | 2009-07-17 17:56:25 -0700 |
---|---|---|
committer | Jean-Baptiste Queru <jbq@google.com> | 2009-07-17 17:56:25 -0700 |
commit | 30b33a2316a7fe6a2c0f690e319b3a0a1b8f4044 (patch) | |
tree | ebe09fbb02206e99163aa7a26cc1e03d3040b724 /i18n | |
parent | 77d00534d7d1b0988659bde4ca7356e7e04758b9 (diff) | |
parent | b13da9df870a61b11249bf741347908dbea0edd8 (diff) | |
download | icu4c-30b33a2316a7fe6a2c0f690e319b3a0a1b8f4044.tar.gz |
import cl @68900 - merge 3.8.1
Diffstat (limited to 'i18n')
-rw-r--r-- | i18n/Android.mk | 4 | ||||
-rw-r--r-- | i18n/Makefile.in | 2 | ||||
-rw-r--r-- | i18n/basictz.cpp | 9 | ||||
-rw-r--r-- | i18n/calendar.cpp | 32 | ||||
-rw-r--r-- | i18n/datefmt.cpp | 26 | ||||
-rw-r--r-- | i18n/dtfmtsym.cpp | 1253 | ||||
-rw-r--r-- | i18n/i18n.vcproj | 133 | ||||
-rw-r--r-- | i18n/nfsubs.cpp | 6 | ||||
-rw-r--r-- | i18n/olsontz.cpp | 173 | ||||
-rw-r--r-- | i18n/olsontz.h | 10 | ||||
-rw-r--r-- | i18n/rbtz.cpp | 134 | ||||
-rw-r--r-- | i18n/simpletz.cpp | 53 | ||||
-rw-r--r-- | i18n/smpdtfmt.cpp | 1096 | ||||
-rw-r--r-- | i18n/timezone.cpp | 216 | ||||
-rw-r--r-- | i18n/ucln_in.h | 2 | ||||
-rw-r--r-- | i18n/ucol.cpp | 2 | ||||
-rw-r--r-- | i18n/ucol_bld.cpp | 23 | ||||
-rw-r--r-- | i18n/ucol_elm.cpp | 464 | ||||
-rw-r--r-- | i18n/ucol_elm.h | 36 | ||||
-rw-r--r-- | i18n/ucol_tok.cpp | 2 | ||||
-rw-r--r-- | i18n/ucol_tok.h | 3 | ||||
-rw-r--r-- | i18n/unicode/basictz.h | 29 | ||||
-rw-r--r-- | i18n/unicode/dtfmtsym.h | 205 | ||||
-rw-r--r-- | i18n/unicode/rbtz.h | 17 | ||||
-rw-r--r-- | i18n/unicode/simpletz.h | 7 | ||||
-rw-r--r-- | i18n/unicode/smpdtfmt.h | 54 | ||||
-rw-r--r-- | i18n/unicode/timezone.h | 9 | ||||
-rw-r--r-- | i18n/usearch.cpp | 97 | ||||
-rw-r--r-- | i18n/usrchimp.h | 3 | ||||
-rw-r--r-- | i18n/zonemeta.cpp | 873 | ||||
-rw-r--r-- | i18n/zonemeta.h | 84 | ||||
-rw-r--r-- | i18n/zstrfmt.cpp | 1604 | ||||
-rw-r--r-- | i18n/zstrfmt.h | 442 |
33 files changed, 5167 insertions, 1936 deletions
diff --git a/i18n/Android.mk b/i18n/Android.mk index 2e5fa05c..d48adec7 100644 --- a/i18n/Android.mk +++ b/i18n/Android.mk @@ -44,7 +44,7 @@ LOCAL_SRC_FILES += \ umsg.cpp unesctrn.cpp uni2name.cpp \ unum.cpp uregexc.cpp uregex.cpp \ usearch.cpp utrans.cpp windtfmt.cpp \ - winnmfmt.cpp + winnmfmt.cpp zonemeta.cpp zstrfmt.cpp LOCAL_C_INCLUDES = \ $(LOCAL_PATH) \ @@ -62,4 +62,4 @@ LOCAL_LDLIBS += -lpthread -lm LOCAL_MODULE := libicui18n -include $(BUILD_SHARED_LIBRARY)
\ No newline at end of file +include $(BUILD_SHARED_LIBRARY) diff --git a/i18n/Makefile.in b/i18n/Makefile.in index e2b9791b..a74fbbe0 100644 --- a/i18n/Makefile.in +++ b/i18n/Makefile.in @@ -78,7 +78,7 @@ name2uni.o uni2name.o nortrans.o quant.o transreg.o \ regexcmp.o rematch.o repattrn.o regexst.o udatpg.o uregex.o uregexc.o \ ulocdata.o measfmt.o currfmt.o curramt.o currunit.o measure.o utmscale.o \ csdetect.o csmatch.o csr2022.o csrecog.o csrmbcs.o csrsbcs.o csrucode.o csrutf8.o inputext.o \ -windtfmt.o winnmfmt.o basictz.o dtrule.o rbtz.o tzrule.o tztrans.o vtzone.o +windtfmt.o winnmfmt.o basictz.o dtrule.o rbtz.o tzrule.o tztrans.o vtzone.o zonemeta.o zstrfmt.o ## Header files to install HEADERS = $(srcdir)/unicode/*.h diff --git a/i18n/basictz.cpp b/i18n/basictz.cpp index 12ac310d..546adb69 100644 --- a/i18n/basictz.cpp +++ b/i18n/basictz.cpp @@ -512,6 +512,15 @@ error: transitionRules = NULL; } +void +BasicTimeZone::getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt, + int32_t& rawOffset, int32_t& dstOffset, UErrorCode& status) /*const*/ { + if (U_FAILURE(status)) { + return; + } + status = U_UNSUPPORTED_ERROR; +} + U_NAMESPACE_END #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/i18n/calendar.cpp b/i18n/calendar.cpp index 6a932f68..03bedde0 100644 --- a/i18n/calendar.cpp +++ b/i18n/calendar.cpp @@ -1093,7 +1093,7 @@ void Calendar::computeFields(UErrorCode &ec) double localMillis = internalGetTime(); int32_t rawOffset, dstOffset; getTimeZone().getOffset(localMillis, FALSE, rawOffset, dstOffset, ec); - localMillis += rawOffset; + localMillis += (rawOffset + dstOffset); // Mark fields as set. Do this before calling handleComputeFields(). uint32_t mask = //fInternalSetMask; @@ -1133,33 +1133,8 @@ void Calendar::computeFields(UErrorCode &ec) //__FILE__, __LINE__, fFields[UCAL_JULIAN_DAY], localMillis); #endif - // In some cases we will have to call this method again below to - // adjust for DST pushing us into the next Julian day. computeGregorianAndDOWFields(fFields[UCAL_JULIAN_DAY], ec); - int32_t millisInDay = (int32_t) (localMillis - (days * kOneDay)); - if (millisInDay < 0) millisInDay += (int32_t)kOneDay; - - // Adjust our millisInDay for DST. dstOffset will be zero if DST - // is not in effect at this time of year, or if our zone does not - // use DST. - millisInDay += dstOffset; - - // If DST has pushed us into the next day, we must call - // computeGregorianAndDOWFields() again. This happens in DST between - // 12:00 am and 1:00 am every day. The first call to - // computeGregorianAndDOWFields() will give the wrong day, since the - // Standard time is in the previous day. - if (millisInDay >= (int32_t)kOneDay) { - millisInDay -= (int32_t)kOneDay; // ASSUME dstOffset < 24:00 - - // We don't worry about overflow of JULIAN_DAY because the - // allowable range of JULIAN_DAY has slop at the ends (that is, - // the max is less that 0x7FFFFFFF and the min is greater than - // -0x80000000). - computeGregorianAndDOWFields(++fFields[UCAL_JULIAN_DAY], ec); - } - // Call framework method to have subclass compute its fields. // These must include, at a minimum, MONTH, DAY_OF_MONTH, // EXTENDED_YEAR, YEAR, DAY_OF_YEAR. This method will call internalSet(), @@ -1173,6 +1148,7 @@ void Calendar::computeFields(UErrorCode &ec) // Compute time-related fields. These are indepent of the date and // of the subclass algorithm. They depend only on the local zone // wall milliseconds in day. + int32_t millisInDay = (int32_t) (localMillis - (days * kOneDay)); fFields[UCAL_MILLISECONDS_IN_DAY] = millisInDay; fFields[UCAL_MILLISECOND] = millisInDay % 1000; millisInDay /= 1000; @@ -2346,11 +2322,11 @@ void Calendar::computeTime(UErrorCode& status) { // 1. The transition into DST. Here, a designated time of 2:00 am - 2:59 am // can be in standard or in DST depending. However, 2:00 am is an invalid // representation (the representation jumps from 1:59:59 am Std to 3:00:00 am DST). - // We assume standard time. + // We assume standard time, that is, 2:30 am is interpreted as 3:30 am DST. // 2. The transition out of DST. Here, a designated time of 1:00 am - 1:59 am // can be in standard or DST. Both are valid representations (the rep // jumps from 1:59:59 DST to 1:00:00 Std). - // Again, we assume standard time. + // Again, we assume standard time, that is, 1:30 am is interpreted as 1:30 am Std. // We use the TimeZone object, unless the user has explicitly set the ZONE_OFFSET // or DST_OFFSET fields; then we use those fields. if (fStamp[UCAL_ZONE_OFFSET] >= ((int32_t)kMinimumUserStamp) || diff --git a/i18n/datefmt.cpp b/i18n/datefmt.cpp index daeb2e3d..f49ca94c 100644 --- a/i18n/datefmt.cpp +++ b/i18n/datefmt.cpp @@ -170,24 +170,32 @@ UDate DateFormat::parse(const UnicodeString& text, ParsePosition& pos) const { + UDate d = 0; // Error return UDate is 0 (the epoch) if (fCalendar != NULL) { int32_t start = pos.getIndex(); + + // Parse may update TimeZone used by the calendar. + TimeZone *tzsav = (TimeZone*)fCalendar->getTimeZone().clone(); + fCalendar->clear(); parse(text, *fCalendar, pos); if (pos.getIndex() != start) { UErrorCode ec = U_ZERO_ERROR; - UDate d = fCalendar->getTime(ec); - if (U_SUCCESS(ec)) { - return d; // Successful function exit + d = fCalendar->getTime(ec); + if (U_FAILURE(ec)) { + // We arrive here if fCalendar is non-lenient and there + // is an out-of-range field. We don't know which field + // was illegal so we set the error index to the start. + pos.setIndex(start); + pos.setErrorIndex(start); + d = 0; } - // We arrive here if fCalendar is non-lenient and there - // is an out-of-range field. We don't know which field - // was illegal so we set the error index to the start. - pos.setIndex(start); - pos.setErrorIndex(start); } + + // Restore TimeZone + fCalendar->adoptTimeZone(tzsav); } - return 0; // Error return UDate is 0 (the epoch) + return d; } //---------------------------------------------------------------------- diff --git a/i18n/dtfmtsym.cpp b/i18n/dtfmtsym.cpp index f350d3a8..86144937 100644 --- a/i18n/dtfmtsym.cpp +++ b/i18n/dtfmtsym.cpp @@ -34,7 +34,8 @@ #include "locbased.h" #include "gregoimp.h" #include "hash.h" -#include "uresimp.h" +#include "uresimp.h" +#include "zstrfmt.h" // ***************************************************************************** // class DateFormatSymbols @@ -130,6 +131,17 @@ static const UChar gLastResortZoneStrings[7][4] = {0x0047, 0x004D, 0x0054, 0x0000} /* "GMT" */ }; +static const UChar gLastResortGmtFormat[] = + {0x0047, 0x004D, 0x0054, 0x007B, 0x0030, 0x007D, 0x0000}; /* GMT{0} */ + +static const UChar gLastResortGmtHourFormats[4][10] = +{ + {0x002D, 0x0048, 0x0048, 0x003A, 0x006D, 0x006D, 0x003A, 0x0073, 0x0073, 0x0000}, /* -HH:mm:ss */ + {0x002D, 0x0048, 0x0048, 0x003A, 0x006D, 0x006D, 0x0000, 0x0000, 0x0000, 0x0000}, /* -HH:mm */ + {0x002B, 0x0048, 0x0048, 0x003A, 0x006D, 0x006D, 0x003A, 0x0073, 0x0073, 0x0000}, /* +HH:mm:ss */ + {0x002B, 0x0048, 0x0048, 0x003A, 0x006D, 0x006D, 0x0000, 0x0000, 0x0000, 0x0000} /* +HH:mm */ +}; + /* Sizes for the last resort string arrays */ typedef enum LastResortSize { kMonthNum = 13, @@ -148,7 +160,10 @@ typedef enum LastResortSize { kEraLen = 3, kZoneNum = 5, - kZoneLen = 4 + kZoneLen = 4, + + kGmtHourNum = 4, + kGmtHourLen = 10 } LastResortSize; U_NAMESPACE_BEGIN @@ -171,39 +186,15 @@ static const char gNamesNarrowTag[]="narrow"; static const char gNamesStandaloneTag[]="stand-alone"; 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 - * associated with a locale. - */ static const char gZoneStringsTag[]="zoneStrings"; +static const char gGmtFormatTag[]="gmtFormat"; +static const char gHourFormatTag[]="hourFormat"; + static const char gLocalPatternCharsTag[]="localPatternChars"; static UMTX LOCK; -/* - * Keep this variable in synch with max length of display strings - */ -#define ZID_KEY_MAX 128 -#define UTZ_MAX_DISPLAY_STRINGS_LENGTH 7 -#define UTZ_SHORT_GENERIC "sg" -#define UTZ_SHORT_STANDARD "ss" -#define UTZ_SHORT_DAYLIGHT "sd" -#define UTZ_LONG_GENERIC "lg" -#define UTZ_LONG_STANDARD "ls" -#define UTZ_LONG_DAYLIGHT "ld" -#define UTZ_EXEMPLAR_CITY "ec" -#define UTZ_USES_METAZONE "um" -#define UTZ_COMMONLY_USED "cu" - /** * Jitterbug 2974: MSVC has a bug whereby new X[0] behaves badly. * Work around this. @@ -212,12 +203,6 @@ static inline UnicodeString* newUnicodeStringArray(size_t count) { return new UnicodeString[count ? count : 1]; } -U_CDECL_BEGIN -static void deleteUnicodeStringArray(void* obj) { - delete[] (UnicodeString*)obj; -} -U_CDECL_END - //------------------------------------------------------ DateFormatSymbols::DateFormatSymbols(const Locale& locale, @@ -327,26 +312,25 @@ DateFormatSymbols::copyData(const DateFormatSymbols& other) { assignArray(fShortQuarters, fShortQuartersCount, other.fShortQuarters, other.fShortQuartersCount); assignArray(fStandaloneQuarters, fStandaloneQuartersCount, other.fStandaloneQuarters, other.fStandaloneQuartersCount); assignArray(fStandaloneShortQuarters, fStandaloneShortQuartersCount, other.fStandaloneShortQuarters, other.fStandaloneShortQuartersCount); - // the zoneStrings data is initialized on demand - //fZoneStringsRowCount = other.fZoneStringsRowCount; - //fZoneStringsColCount = other.fZoneStringsColCount; - //createZoneStrings((const UnicodeString**)other.fZoneStrings); - // initialize on demand - fZoneStringsHash = NULL; - fZoneIDEnumeration = NULL; - fZoneStrings = NULL; - fZoneStringsColCount = 0; - fZoneStringsRowCount = 0; - fResourceBundle = NULL; - fCountry = other.fCountry; - if(other.fZoneStringsHash!=NULL){ - fZoneStringsHash = createZoneStringsHash(other.fZoneStringsHash); - fZoneIDEnumeration = other.fZoneIDEnumeration->clone(); - }else{ - UErrorCode status =U_ZERO_ERROR; - fResourceBundle = ures_clone(other.fResourceBundle, &status); - // TODO: what should be done in case of error? + fGmtFormat = other.fGmtFormat; + assignArray(fGmtHourFormats, fGmtHourFormatsCount, other.fGmtHourFormats, other.fGmtHourFormatsCount); + + if (other.fZoneStrings != NULL) { + fZoneStringsColCount = other.fZoneStringsColCount; + fZoneStringsRowCount = other.fZoneStringsRowCount; + createZoneStrings((const UnicodeString**)other.fZoneStrings); + + } else { + fZoneStrings = NULL; + fZoneStringsColCount = 0; + fZoneStringsRowCount = 0; } + fZSFLocale = other.fZSFLocale; + // Other zone strings data is created on demand + fZoneStringFormat = NULL; + fLocaleZoneStrings = NULL; + fZSFCachePtr = NULL; + fZSFLocal = NULL; // fastCopyFrom() - see assignArray comments fLocalPatternChars.fastCopyFrom(other.fLocalPatternChars); @@ -389,6 +373,7 @@ void DateFormatSymbols::dispose() if (fShortQuarters) delete[] fShortQuarters; if (fStandaloneQuarters) delete[] fStandaloneQuarters; if (fStandaloneShortQuarters) delete[] fStandaloneShortQuarters; + if (fGmtHourFormats) delete[] fGmtHourFormats; disposeZoneStrings(); } @@ -396,23 +381,32 @@ void DateFormatSymbols::dispose() void DateFormatSymbols::disposeZoneStrings() { if (fZoneStrings) { - for (int32_t row=0; row<fZoneStringsRowCount; ++row) + for (int32_t row = 0; row < fZoneStringsRowCount; ++row) { delete[] fZoneStrings[row]; + } uprv_free(fZoneStrings); - } - if(fZoneStringsHash){ - delete fZoneStringsHash; - fZoneStringsHash = NULL; } - if(fZoneIDEnumeration){ - delete fZoneIDEnumeration; - fZoneIDEnumeration = NULL; + if (fLocaleZoneStrings) { + for (int32_t row = 0; row < fZoneStringsRowCount; ++row) { + delete[] fLocaleZoneStrings[row]; + } + uprv_free(fLocaleZoneStrings); } - if (fResourceBundle){ - ures_close(fResourceBundle); - fResourceBundle = NULL; + if (fZSFLocal) { + delete fZSFLocal; } + if (fZSFCachePtr) { + delete fZSFCachePtr; + } + + fZoneStrings = NULL; + fLocaleZoneStrings = NULL; + fZoneStringsRowCount = 0; + fZoneStringsColCount = 0; + fZoneStringFormat = NULL; + fZSFLocal = NULL; + fZSFCachePtr = NULL; } UBool @@ -454,7 +448,9 @@ DateFormatSymbols::operator==(const DateFormatSymbols& other) const fQuartersCount == other.fQuartersCount && fShortQuartersCount == other.fShortQuartersCount && fStandaloneQuartersCount == other.fStandaloneQuartersCount && - fStandaloneShortQuartersCount == other.fStandaloneShortQuartersCount) + fStandaloneShortQuartersCount == other.fStandaloneShortQuartersCount && + fGmtHourFormatsCount == other.fGmtHourFormatsCount && + fGmtFormat == other.fGmtFormat) { // Now compare the arrays themselves if (arrayCompare(fEras, other.fEras, fErasCount) && @@ -475,25 +471,25 @@ DateFormatSymbols::operator==(const DateFormatSymbols& other) const arrayCompare(fQuarters, other.fQuarters, fQuartersCount) && arrayCompare(fShortQuarters, other.fShortQuarters, fShortQuartersCount) && arrayCompare(fStandaloneQuarters, other.fStandaloneQuarters, fStandaloneQuartersCount) && - arrayCompare(fStandaloneShortQuarters, other.fStandaloneShortQuarters, fStandaloneShortQuartersCount)) + arrayCompare(fStandaloneShortQuarters, other.fStandaloneShortQuarters, fStandaloneShortQuartersCount) && + arrayCompare(fGmtHourFormats, other.fGmtHourFormats, fGmtHourFormatsCount)) { - - if(fZoneStringsHash == NULL || other.fZoneStringsHash == NULL){ - // fZoneStringsHash is not initialized compare the resource bundles - if(ures_equal(fResourceBundle, other.fResourceBundle)== FALSE){ - return FALSE; + // Compare the contents of fZoneStrings + if (fZoneStrings == NULL && other.fZoneStrings == NULL) { + if (fZSFLocale == other.fZSFLocale) { + return TRUE; } - }else{ - if(fZoneStringsHash->equals(*other.fZoneStringsHash) == FALSE){ - return FALSE; + } else if (fZoneStrings != NULL && other.fZoneStrings != NULL) { + if (fZoneStringsRowCount == other.fZoneStringsRowCount + && fZoneStringsColCount == other.fZoneStringsColCount) { + UBool cmpres = TRUE; + for (int32_t i = 0; (i < fZoneStringsRowCount) && cmpres; i++) { + cmpres = arrayCompare(fZoneStrings[i], other.fZoneStrings[i], fZoneStringsColCount); + } + return cmpres; } - // we always make sure that we update the enumeration when the hash is - // updated. So we can be sure that once we compare the hashes the - // enumerations are also equal } - // since fZoneStrings data member is deprecated .. and may not be initialized - // so don't compare them - return TRUE; + return FALSE; } } return FALSE; @@ -997,25 +993,73 @@ DateFormatSymbols::setAmPmStrings(const UnicodeString* amPmsArray, int32_t count } //------------------------------------------------------ +const ZoneStringFormat* +DateFormatSymbols::getZoneStringFormat(void) const { + umtx_lock(&LOCK); + if (fZoneStringFormat == NULL) { + ((DateFormatSymbols*)this)->initZoneStringFormat(); + } + umtx_unlock(&LOCK); + return fZoneStringFormat; +} + +void +DateFormatSymbols::initZoneStringFormat(void) { + if (fZoneStringFormat == NULL) { + UErrorCode status = U_ZERO_ERROR; + if (fZoneStrings) { + // Create an istance of ZoneStringFormat by the custom zone strings array + fZSFLocal = new ZoneStringFormat(fZoneStrings, fZoneStringsRowCount, + fZoneStringsColCount, status); + if (U_FAILURE(status)) { + delete fZSFLocal; + } else { + fZoneStringFormat = (const ZoneStringFormat*)fZSFLocal; + } + } else { + fZSFCachePtr = ZoneStringFormat::getZoneStringFormat(fZSFLocale, status); + if (U_FAILURE(status)) { + delete fZSFCachePtr; + } else { + fZoneStringFormat = fZSFCachePtr->get(); + } + } + } +} const UnicodeString** DateFormatSymbols::getZoneStrings(int32_t& rowCount, int32_t& columnCount) const { + const UnicodeString **result = NULL; + umtx_lock(&LOCK); - UErrorCode status = U_ZERO_ERROR; - if(fZoneStrings==NULL){ - // cast away const to get around the problem for lazy initialization - ((DateFormatSymbols*)this)->initZoneStringsArray(status); + if (fZoneStrings == NULL) { + if (fLocaleZoneStrings == NULL) { + ((DateFormatSymbols*)this)->initZoneStringsArray(); + } + result = (const UnicodeString**)fLocaleZoneStrings; + } else { + result = (const UnicodeString**)fZoneStrings; } rowCount = fZoneStringsRowCount; columnCount = fZoneStringsColCount; umtx_unlock(&LOCK); - if(U_FAILURE(status)){ - rowCount = 0; - columnCount = 0; - return NULL; + + return result; +} + +void +DateFormatSymbols::initZoneStringsArray(void) { + if (fZoneStrings == NULL && fLocaleZoneStrings == NULL) { + if (fZoneStringFormat == NULL) { + initZoneStringFormat(); + } + if (fZoneStringFormat) { + UErrorCode status = U_ZERO_ERROR; + fLocaleZoneStrings = fZoneStringFormat->createZoneStringsArray(uprv_getUTCtime() /* use current time */, + fZoneStringsRowCount, fZoneStringsColCount, status); + } } - return (const UnicodeString**)fZoneStrings; // Compiler requires cast } void @@ -1030,7 +1074,6 @@ DateFormatSymbols::setZoneStrings(const UnicodeString* const *strings, int32_t r fZoneStringsRowCount = rowCount; fZoneStringsColCount = columnCount; createZoneStrings((const UnicodeString**)strings); - initZoneStrings((const UnicodeString**)strings, rowCount,columnCount, status); } //------------------------------------------------------ @@ -1144,14 +1187,22 @@ DateFormatSymbols::initializeData(const Locale& locale, const char *type, UError fStandaloneQuartersCount = 0; fStandaloneShortQuarters = NULL; fStandaloneShortQuartersCount = 0; + fGmtHourFormats = NULL; + fGmtHourFormatsCount = 0; fZoneStringsRowCount = 0; fZoneStringsColCount = 0; fZoneStrings = NULL; - fZoneStringsHash = NULL; - fZoneIDEnumeration = NULL; - fResourceBundle = NULL; - fCountry = NULL; - + fLocaleZoneStrings = NULL; + + fZoneStringFormat = NULL; + fZSFLocal = NULL; + fZSFCachePtr = NULL; + + // We need to preserve the requested locale for + // lazy ZoneStringFormat instantiation. ZoneStringFormat + // is region sensitive, thus, bundle locale bundle's locale + // is not sufficient. + fZSFLocale = locale; if (U_FAILURE(status)) return; @@ -1161,8 +1212,12 @@ DateFormatSymbols::initializeData(const Locale& locale, const char *type, UError * these. */ CalendarData calData(locale, type, status); - fResourceBundle = ures_open(NULL, locale.getName(), &status); - fCountry = locale.getCountry(); + + /** + * Use the localeBundle for getting zone GMT formatting patterns + */ + UResourceBundle *localeBundle = ures_open(NULL, locale.getName(), &status); + UResourceBundle *zoneStringsArray = ures_getByKeyWithFallback(localeBundle, gZoneStringsTag, NULL, &status); // load the first data item UResourceBundle *erasMain = calData.getByKey(gErasTag, status); @@ -1212,6 +1267,8 @@ DateFormatSymbols::initializeData(const Locale& locale, const char *type, UError initField(&fShortQuarters, fShortQuartersCount, (const UChar *)gLastResortQuarters, kQuarterNum, kQuarterLen, status); initField(&fStandaloneQuarters, fStandaloneQuartersCount, (const UChar *)gLastResortQuarters, kQuarterNum, kQuarterLen, status); initField(&fStandaloneShortQuarters, fStandaloneShortQuartersCount, (const UChar *)gLastResortQuarters, kQuarterNum, kQuarterLen, status); + initField(&fGmtHourFormats, fGmtHourFormatsCount, (const UChar *)gLastResortGmtHourFormats, kGmtHourNum, kGmtHourLen, status); + fGmtFormat.setTo(TRUE, gLastResortGmtFormat, -1); fLocalPatternChars.setTo(TRUE, gPatternChars, PATTERN_CHARS_LEN); } goto cleanup; @@ -1274,6 +1331,41 @@ DateFormatSymbols::initializeData(const Locale& locale, const char *type, UError status = U_ZERO_ERROR; initField(&fStandaloneShortQuarters, fStandaloneShortQuartersCount, calData.getByKey2(gQuartersTag, gNamesAbbrTag, status), status); } + + // GMT format patterns + resStr = ures_getStringByKeyWithFallback(zoneStringsArray, gGmtFormatTag, &len, &status); + if (len > 0) { + fGmtFormat.setTo(TRUE, resStr, len); + } + + resStr = ures_getStringByKeyWithFallback(zoneStringsArray, gHourFormatTag, &len, &status); + if (len > 0) { + UChar *sep = u_strchr(resStr, (UChar)0x003B /* ';' */); + if (sep != NULL) { + fGmtHourFormats = newUnicodeStringArray(GMT_HOUR_COUNT); + if (fGmtHourFormats == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + } else { + fGmtHourFormatsCount = GMT_HOUR_COUNT; + fGmtHourFormats[GMT_NEGATIVE_HM].setTo(TRUE, sep + 1, -1); + fGmtHourFormats[GMT_POSITIVE_HM].setTo(FALSE, resStr, sep - resStr); + + // CLDR 1.5 does not have GMT offset pattern including second field. + // For now, append "ss" to the end. + if (fGmtHourFormats[GMT_NEGATIVE_HM].indexOf((UChar)0x003A /* ':' */) != -1) { + fGmtHourFormats[GMT_NEGATIVE_HMS] = fGmtHourFormats[GMT_NEGATIVE_HM] + ":ss"; + } else { + fGmtHourFormats[GMT_NEGATIVE_HMS] = fGmtHourFormats[GMT_NEGATIVE_HM] + "ss"; + } + if (fGmtHourFormats[GMT_POSITIVE_HM].indexOf((UChar)0x003A /* ':' */) != -1) { + fGmtHourFormats[GMT_POSITIVE_HMS] = fGmtHourFormats[GMT_POSITIVE_HM] + ":ss"; + } else { + fGmtHourFormats[GMT_POSITIVE_HMS] = fGmtHourFormats[GMT_POSITIVE_HM] + "ss"; + } + } + } + } + // ICU 3.8 or later version no longer uses localized date-time pattern characters by default (ticket#5597) /* // fastCopyFrom()/setTo() - see assignArray comments @@ -1411,55 +1503,8 @@ DateFormatSymbols::initializeData(const Locale& locale, const char *type, UError cleanup: ures_close(eras); ures_close(eraNames); -} - -/** - * Package private: used by SimpleDateFormat - * Gets the index for the given time zone ID to obtain the timezone - * strings for formatting. The time zone ID is just for programmatic - * lookup. NOT LOCALIZED!!! - * @param ID the given time zone ID. - * @return the index of the given time zone ID. Returns -1 if - * the given time zone ID can't be located in the DateFormatSymbols object. - * @see java.util.SimpleTimeZone - */ -int32_t DateFormatSymbols::getZoneIndex(const UnicodeString& ID) const -{ - int32_t result = _getZoneIndex(ID); - if (result >= 0) { - return result; - } - - // Do a search through the equivalency group for the given ID - int32_t n = TimeZone::countEquivalentIDs(ID); - if (n > 1) { - int32_t i; - for (i=0; i<n; ++i) { - UnicodeString equivID = TimeZone::getEquivalentID(ID, i); - if (equivID != ID) { - int32_t equivResult = _getZoneIndex(equivID); - if (equivResult >= 0) { - return equivResult; - } - } - } - } - - return -1; -} - -/** - * Lookup the given ID. Do NOT do an equivalency search. - */ -int32_t DateFormatSymbols::_getZoneIndex(const UnicodeString& ID) const -{ - for(int32_t index = 0; index < fZoneStringsRowCount; index++) { - if (0 == ID.caseCompare(fZoneStrings[index][0], 0)) { - return index; - } - } - - return -1; + ures_close(zoneStringsArray); + ures_close(localeBundle); } Locale @@ -1468,920 +1513,6 @@ DateFormatSymbols::getLocale(ULocDataLocaleType type, UErrorCode& status) const return locBased.getLocale(type, status); } -class TimeZoneKeysEnumeration : public StringEnumeration { -private: - UnicodeString* strings; - int32_t length; - int32_t current; - int32_t capacity; - TimeZoneKeysEnumeration(UnicodeString* oldStrs, int32_t count){ - strings = newUnicodeStringArray(count); - if(strings==NULL){ - return; - } - capacity = count; - current = 0; - for(length = 0; length<capacity; length++){ - strings[length].setTo(oldStrs[length]); - } - } -public: - static UClassID U_EXPORT2 getStaticClassID(void); - virtual UClassID getDynamicClassID(void) const; - - TimeZoneKeysEnumeration(int32_t count, UErrorCode status){ - strings = newUnicodeStringArray(count); - if(strings == NULL){ - status = U_MEMORY_ALLOCATION_ERROR; - } - length = 0; - current = 0; - capacity = count; - } - - void put(const UnicodeString& str, UErrorCode& status){ - if(length < capacity){ - strings[length++].setTo(str); - }else{ - status = U_INDEX_OUTOFBOUNDS_ERROR; - } - } - virtual ~TimeZoneKeysEnumeration() { - delete[] strings; - } - - virtual StringEnumeration * clone() const - { - return new TimeZoneKeysEnumeration(strings, length); - } - - virtual int32_t count(UErrorCode &/*status*/) const { - return length; - } - virtual const UChar* unext(int32_t *resultLength, UErrorCode& /*status*/){ - if(current < length){ - const UChar* ret = strings[current].getBuffer(); - *resultLength = strings[current].length(); - current++; - return ret; - } - return NULL; - } - - virtual const UnicodeString* snext(UErrorCode& status) { - if(U_FAILURE(status)){ - return NULL; - } - if(current < length){ - return &strings[current++]; - } - return NULL; - } - /* this method is for thread safe iteration */ - const UnicodeString* snext(int32_t& pos, UErrorCode& status)const { - if(U_FAILURE(status)){ - return NULL; - } - if(pos < length){ - return &strings[pos++]; - } - return NULL; - } - - virtual void reset(UErrorCode& /*status*/) { - current = 0; - - } -private: - UBool equals(const StringEnumeration& other) const{ - if (other.getDynamicClassID() != TimeZoneKeysEnumeration::getStaticClassID()) { - return FALSE; - } - TimeZoneKeysEnumeration& enum2 = (TimeZoneKeysEnumeration&)(other); - UErrorCode status = U_ZERO_ERROR; - - int32_t count1 = count(status); - int32_t count2 = other.count(status); - if(count1 != count2){ - return FALSE; - } - int32_t pos1 = 0; - int32_t pos2 = 0; - const UnicodeString* str1 = NULL; - const UnicodeString* str2 = NULL; - - while((str1 = snext(pos1, status))!=NULL){ - str2 = enum2.snext(pos2, status); - if(U_FAILURE(status)){ - return FALSE; - } - if(*str1 != *str2){ - // bail out at the first failure - return FALSE; - } - - } - // if we reached here that means that the enumerations are equal - return TRUE; - } -public: - virtual UBool operator==(const StringEnumeration& that)const{ - return ((this == &that) || - (getDynamicClassID() == that.getDynamicClassID() && - StringEnumeration::operator==(that) && - equals(that))); - } -}; - -UOBJECT_DEFINE_RTTI_IMPLEMENTATION(TimeZoneKeysEnumeration) - -void -DateFormatSymbols::initZoneStringsArray(UErrorCode& status){ - if(fZoneStringsHash == NULL){ - initZoneStrings(status); - } - if(U_FAILURE(status)){ - return; - } - fZoneStringsRowCount = fZoneIDEnumeration->count(status); - fZoneStringsColCount = 8; - fZoneStrings = (UnicodeString **)uprv_malloc(fZoneStringsRowCount * sizeof(UnicodeString *)); - /* if we can't get a chunk of heap then the system is going down. Pin the blame on system*/ - if (fZoneStrings == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - return; - } - const UnicodeString *zid = NULL; - TimeZoneKeysEnumeration *keys = (TimeZoneKeysEnumeration*) fZoneIDEnumeration; - int32_t pos = 0; - int32_t i = 0; - while((zid=keys->snext(pos,status))!=NULL){ - *(fZoneStrings+i) = newUnicodeStringArray(fZoneStringsColCount); - /* test for NULL */ - if ((*(fZoneStrings+i)) == 0) { - status = U_MEMORY_ALLOCATION_ERROR; - return; - } - UnicodeString* strings = (UnicodeString*)fZoneStringsHash->get(*zid); - fZoneStrings[i][0].setTo(*zid); - fZoneStrings[i][1].setTo(strings[TIMEZONE_LONG_STANDARD]); - fZoneStrings[i][2].setTo(strings[TIMEZONE_SHORT_STANDARD]); - fZoneStrings[i][3].setTo(strings[TIMEZONE_LONG_DAYLIGHT]); - fZoneStrings[i][4].setTo(strings[TIMEZONE_SHORT_DAYLIGHT]); - fZoneStrings[i][5].setTo(strings[TIMEZONE_EXEMPLAR_CITY]); - fZoneStrings[i][6].setTo(strings[TIMEZONE_LONG_GENERIC]); - fZoneStrings[i][7].setTo(strings[TIMEZONE_SHORT_GENERIC]); - i++; - } -} - -U_CDECL_BEGIN -static UBool U_CALLCONV -compareTZHashValues(const UHashTok val1, const UHashTok val2){ - - const UnicodeString* array1 = (UnicodeString*) val1.pointer; - const UnicodeString* array2 = (UnicodeString*) val2.pointer; - if(array1==array2){ - return TRUE; - } - if(array1==NULL || array2==NULL){ - return FALSE; - } - for(int32_t j=0; j< UTZ_MAX_DISPLAY_STRINGS_LENGTH; j++){ - if(array1[j] != array2[j]){ - return FALSE; - } - } - return TRUE; -} -U_CDECL_END - -void -DateFormatSymbols::initZoneStrings(UErrorCode &status){ - if(U_FAILURE(status)){ - return; - } - - if(fZoneStringsHash != NULL){ - return; - } - int32_t i; - - fZoneStringsHash = new Hashtable(uhash_compareUnicodeString, compareTZHashValues, status); - if(fZoneStringsHash==NULL){ - status = U_MEMORY_ALLOCATION_ERROR; - return; - } - fZoneStringsHash->setValueDeleter(deleteUnicodeStringArray); - - if(fResourceBundle != NULL){ - UnicodeString solidus = UNICODE_STRING_SIMPLE("/"); - UnicodeString colon = UNICODE_STRING_SIMPLE(":"); - UResourceBundle *zoneArray, *zoneItem; - for(const UResourceBundle* rb = fResourceBundle; rb!=NULL; rb=ures_getParentBundle(rb)){ - zoneArray = ures_getByKey(rb, gZoneStringsTag, NULL, &status); - if(U_FAILURE(status)){ - break; - } - while(ures_hasNext(zoneArray)){ - UErrorCode tempStatus = U_ZERO_ERROR; - zoneItem = ures_getNextResource(zoneArray, NULL, &status); - UnicodeString key(ures_getKey(zoneItem), -1, US_INV); - if (key.indexOf(colon) == -1) { - ures_close(zoneItem); - continue; - } - UnicodeString* strArray = newUnicodeStringArray(UTZ_MAX_DISPLAY_STRINGS_LENGTH); - key.findAndReplace(colon, solidus); - int32_t len = 0; - //fetch the strings with fine grained fallback - const UChar* str = ures_getStringByKeyWithFallback(zoneItem,UTZ_SHORT_STANDARD, &len, &tempStatus); - if(U_SUCCESS(tempStatus)){ - strArray[TIMEZONE_SHORT_STANDARD].setTo(TRUE, str, len); - }else{ - tempStatus = U_ZERO_ERROR; - } - str = ures_getStringByKeyWithFallback(zoneItem,UTZ_SHORT_GENERIC, &len, &tempStatus); - if(U_SUCCESS(tempStatus)){ - strArray[TIMEZONE_SHORT_GENERIC].setTo(TRUE, str, len); - }else{ - tempStatus = U_ZERO_ERROR; - } - str = ures_getStringByKeyWithFallback(zoneItem,UTZ_SHORT_DAYLIGHT, &len, &tempStatus); - if(U_SUCCESS(tempStatus)){ - strArray[TIMEZONE_SHORT_DAYLIGHT].setTo(TRUE, str, len); - }else{ - tempStatus = U_ZERO_ERROR; - } - str = ures_getStringByKeyWithFallback(zoneItem,UTZ_LONG_STANDARD, &len, &tempStatus); - if(U_SUCCESS(tempStatus)){ - strArray[TIMEZONE_LONG_STANDARD].setTo(TRUE, str, len); - }else{ - tempStatus = U_ZERO_ERROR; - } - str = ures_getStringByKeyWithFallback(zoneItem,UTZ_LONG_GENERIC, &len, &tempStatus); - if(U_SUCCESS(tempStatus)){ - strArray[TIMEZONE_LONG_GENERIC].setTo(TRUE, str, len); - }else{ - tempStatus = U_ZERO_ERROR; - } - str = ures_getStringByKeyWithFallback(zoneItem,UTZ_LONG_DAYLIGHT, &len, &tempStatus); - if(U_SUCCESS(tempStatus)){ - strArray[TIMEZONE_LONG_DAYLIGHT].setTo(TRUE, str, len); - }else{ - tempStatus = U_ZERO_ERROR; - } - str = ures_getStringByKeyWithFallback(zoneItem,UTZ_EXEMPLAR_CITY, &len, &tempStatus); - if(U_SUCCESS(tempStatus)){ - strArray[TIMEZONE_EXEMPLAR_CITY].setTo(TRUE, str, len); - }else{ - tempStatus = U_ZERO_ERROR; - } - // store the strings in hash - fZoneStringsHash->put(key, strArray, status); - ures_close(zoneItem); - } - - ures_close(zoneArray); - } - - // Need to make sure that all zoneStrings in root are covered as well, otherwise metazone lookups won't - // work properly - UResourceBundle* root_res = ures_open(NULL, "", &status); - zoneArray = ures_getByKey(root_res, gZoneStringsTag, NULL, &status); - if (U_SUCCESS(status)) { - while(ures_hasNext(zoneArray)){ - UErrorCode tempStatus = U_ZERO_ERROR; - zoneItem = ures_getNextResource(zoneArray, NULL, &status); - UnicodeString key(ures_getKey(zoneItem), -1, US_INV); - if ( key.indexOf(colon) == -1 ) { - ures_close(zoneItem); - continue; - } - key.findAndReplace(colon, solidus); - - // Don't step on anything that is already there - UnicodeString* existingArray = (UnicodeString*)fZoneStringsHash->get(key); - if(existingArray != NULL){ - ures_close(zoneItem); - continue; - } - UnicodeString* strArray = newUnicodeStringArray(UTZ_MAX_DISPLAY_STRINGS_LENGTH); - int32_t len = 0; - - const UChar *str = ures_getStringByKeyWithFallback(zoneItem,UTZ_EXEMPLAR_CITY, &len, &tempStatus); - if(U_SUCCESS(tempStatus)){ - strArray[TIMEZONE_EXEMPLAR_CITY].setTo(TRUE, str, len); - }else{ - tempStatus = U_ZERO_ERROR; - } - // store the strings in hash - fZoneStringsHash->put(key, strArray, status); - ures_close(zoneItem); - } - ures_close(zoneArray); - ures_close(root_res); - } - - int32_t length = fZoneStringsHash->count(); - TimeZoneKeysEnumeration* keysEnum = new TimeZoneKeysEnumeration(length, status); - fZoneIDEnumeration = keysEnum; - if(fZoneIDEnumeration==NULL){ - delete fZoneStringsHash; - fZoneStringsHash = NULL; - status = U_MEMORY_ALLOCATION_ERROR; - return; - } - int32_t pos=-1; - const UnicodeString* key; - const UHashElement* elem = NULL; - while((elem = fZoneStringsHash->nextElement(pos))!= NULL){ - const UHashTok keyTok = elem->key; - key = (const UnicodeString*)keyTok.pointer; - keysEnum->put(*key, status); - } - }else{ - //last resort strings - UnicodeString* array = newUnicodeStringArray(UTZ_MAX_DISPLAY_STRINGS_LENGTH); - if(array==NULL){ - delete fZoneStringsHash; - status = U_MEMORY_ALLOCATION_ERROR; - return; - } - int32_t length = ARRAY_LENGTH(gLastResortZoneStrings); - UnicodeString key(gLastResortZoneStrings[0]); - TimeZoneKeysEnumeration* keysEnum = new TimeZoneKeysEnumeration(length, status); - fZoneIDEnumeration = keysEnum; - if(fZoneIDEnumeration==NULL){ - delete fZoneStringsHash; - delete[] array; - fZoneStringsHash = NULL; - status = U_MEMORY_ALLOCATION_ERROR; - return; - } - keysEnum->put(key, status); - int32_t j=1; - for(i=0; i< length; ){ - array[i++].setTo(gLastResortZoneStrings[j++]); - } - fZoneStringsHash->put(key, array, status); - } -} -void -DateFormatSymbols::initZoneStrings(const UnicodeString** strings, int32_t rowCount, int32_t columnCount, UErrorCode& status){ - if(strings==NULL || rowCount<0 || columnCount<0){ - status = U_ILLEGAL_ARGUMENT_ERROR; - return; - } - TimeZoneKeysEnumeration* keysEnum = new TimeZoneKeysEnumeration(rowCount, status); - fZoneIDEnumeration = keysEnum; - if(U_FAILURE(status)){ - return; - } - if(fZoneIDEnumeration==NULL){ - status = U_MEMORY_ALLOCATION_ERROR; - return; - } - fZoneStringsHash = new Hashtable(uhash_compareUnicodeString, compareTZHashValues, status); - if(U_FAILURE(status)){ - return; - } - if(fZoneStringsHash==NULL){ - status = U_MEMORY_ALLOCATION_ERROR; - return; - } - fZoneStringsHash->setValueDeleter(deleteUnicodeStringArray); - for (int32_t row=0; row<rowCount; ++row){ - // the first string in the array is the key. - UnicodeString key = strings[row][0]; - keysEnum->put(key, status); - UnicodeString* array = newUnicodeStringArray(UTZ_MAX_DISPLAY_STRINGS_LENGTH); - if(array==NULL){ - status = U_MEMORY_ALLOCATION_ERROR; - return; - } - for (int32_t col=1; col<columnCount; ++col) { - // fastCopyFrom() - see assignArray comments - switch (col){ - case 1: - array[TIMEZONE_LONG_STANDARD].setTo(strings[row][col]); - break; - case 2: - array[TIMEZONE_SHORT_STANDARD].setTo(strings[row][col]); - break; - case 3: - array[TIMEZONE_LONG_DAYLIGHT].setTo(strings[row][col]); - break; - case 4: - array[TIMEZONE_LONG_DAYLIGHT].setTo(strings[row][col]); - break; - case 5: - array[TIMEZONE_EXEMPLAR_CITY].setTo(strings[row][col]); - break; - case 6: - array[TIMEZONE_LONG_GENERIC].setTo(strings[row][col]); - break; - case 7: - array[TIMEZONE_SHORT_GENERIC].setTo(strings[row][col]); - break; - default: - status = U_ILLEGAL_ARGUMENT_ERROR; - } - // populate the hash table - fZoneStringsHash->put(strings[row][0], array, status); - } - } - -} - -UnicodeString& -DateFormatSymbols::getZoneString(const UnicodeString &zid, const TimeZoneTranslationType type, - UnicodeString &result, UErrorCode &status){ - - if(fZoneStringsHash == NULL){ - //lazy initialization - initZoneStrings(status); - } - if(U_FAILURE(status)){ - return result; - } - - UnicodeString* stringsArray = (UnicodeString*)fZoneStringsHash->get(zid); - if(stringsArray != NULL){ - result.setTo(stringsArray[type],0); - } - return result; -} - -UnicodeString -DateFormatSymbols::getMetazoneString(const UnicodeString &zid, const TimeZoneTranslationType type, Calendar &cal, - UnicodeString &result, UErrorCode &status) -{ - UErrorCode tempStatus = U_ZERO_ERROR; - int32_t len; - UnicodeString mzid(UNICODE_STRING_SIMPLE("meta/")); - - // Get the appropriate metazone mapping from the resource bundles - - char usesMetazoneKey[ZID_KEY_MAX]; - char zidkey[ZID_KEY_MAX]; - - uprv_strcpy(usesMetazoneKey,gZoneStringsTag); - uprv_strcat(usesMetazoneKey,"/"); - - 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] = ':'; - } - } - - uprv_strcat(usesMetazoneKey,zidkey); - uprv_strcat(usesMetazoneKey,"/"); - uprv_strcat(usesMetazoneKey,UTZ_USES_METAZONE); - - UResourceBundle *um = ures_getByKeyWithFallback(fResourceBundle, usesMetazoneKey, NULL, &tempStatus); - if (U_FAILURE(tempStatus)) { - return result; - } - - UnicodeString* stringsArray = (UnicodeString*)fZoneStringsHash->get(zid); - - if(stringsArray != NULL){ - SimpleDateFormat df(UNICODE_STRING_SIMPLE("yyyy-MM-dd HH:mm"), Locale(""),tempStatus); - TimeZone *tz = TimeZone::createTimeZone(zid); - df.setTimeZone(*tz); - delete tz; - UnicodeString theTime; - df.format(cal.getTime(tempStatus),theTime); - - while (ures_hasNext(um)) { - UResourceBundle *mz = ures_getNextResource(um,NULL,&status); - const UChar *mz_name = ures_getStringByIndex(mz,0,&len,&status); - const UChar *mz_from = ures_getStringByIndex(mz,1,&len,&status); - const UChar *mz_to = ures_getStringByIndex(mz,2,&len,&status); - ures_close(mz); - if(U_FAILURE(status)){ - break; - } - - if (mz_name[0] != 0 && - UnicodeString(TRUE, mz_from, -1) <= theTime && - UnicodeString(TRUE, mz_to, -1) > theTime ) - { - mzid += mz_name; - getZoneString(mzid,type,result,status); - break; - } - } - } - ures_close(um); - if ( mzid.length() > 5 ) { - return mzid; - } - 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); - } - - ures_close(thisZone); - ures_close(zoneFormatting); - ures_close(supplementalDataBundle); - ures_close(countries); - ures_close(multiZone); - - return result; -} - -UBool -DateFormatSymbols::isCommonlyUsed(const UnicodeString &zid){ - UErrorCode status=U_ZERO_ERROR; - UResourceBundle *zoneArray, *zoneItem, *cuRes; - UnicodeString solidus = UNICODE_STRING_SIMPLE("/"); - UnicodeString colon = UNICODE_STRING_SIMPLE(":"); - UnicodeString key(zid); - char keychars[ZID_KEY_MAX+1]; - - key.findAndReplace(solidus,colon); - - for(const UResourceBundle* rb = fResourceBundle; rb!=NULL; rb=ures_getParentBundle(rb)){ - zoneArray = ures_getByKey(rb, gZoneStringsTag, NULL, &status); - if(U_FAILURE(status)){ - status = U_ZERO_ERROR; - continue; - } - int32_t len = key.length(); - u_UCharsToChars(key.getBuffer(), keychars, len); - keychars[len] = 0; // NULL terminate - zoneItem = ures_getByKey(zoneArray,keychars,NULL, &status); - if(U_FAILURE(status)){ - ures_close(zoneArray); - status = U_ZERO_ERROR; - continue; - } - - cuRes = ures_getByKey(zoneItem,UTZ_COMMONLY_USED,NULL,&status); - if(U_FAILURE(status)){ - ures_close(zoneItem); - ures_close(zoneArray); - status = U_ZERO_ERROR; - continue; - } - int32_t cuValue = ures_getInt(cuRes,&status); - - ures_close(cuRes); - ures_close(zoneItem); - ures_close(zoneArray); - - if(U_FAILURE(status)){ - status = U_ZERO_ERROR; - continue; - } - - if ( cuValue == 1 ) { - return TRUE; - } - } - return FALSE; -} - -StringEnumeration* -DateFormatSymbols::createZoneStringIDs(UErrorCode &status){ - if(U_FAILURE(status)){ - return NULL; - } - if(fZoneStringsHash == NULL){ - //lazy initialization - initZoneStrings(status); - } - return fZoneIDEnumeration->clone(); -} - -/** - * Sets timezone strings. - * @draft ICU 3.6 - */ -void -DateFormatSymbols::setZoneString(const UnicodeString &zid, const TimeZoneTranslationType type, - const UnicodeString &value, UErrorCode &status){ - if(fZoneStringsHash == NULL){ - //lazy initialization - initZoneStrings(status); - } - if(U_FAILURE(status)){ - return; - } - UnicodeString* stringsArray = (UnicodeString*)fZoneStringsHash->get(zid); - if(stringsArray != NULL){ - stringsArray[type].setTo(value); - }else{ - stringsArray = newUnicodeStringArray(UTZ_MAX_DISPLAY_STRINGS_LENGTH); - if(stringsArray==NULL){ - status = U_MEMORY_ALLOCATION_ERROR; - return; - } - stringsArray[type].setTo(value); - fZoneStringsHash->put(zid, stringsArray, status); - TimeZoneKeysEnumeration* keys = (TimeZoneKeysEnumeration*) fZoneIDEnumeration; - keys->put(zid, status); - } -} - -Hashtable* -DateFormatSymbols::createZoneStringsHash(const Hashtable* otherHash){ - UErrorCode status = U_ZERO_ERROR; - Hashtable* hash = new Hashtable(uhash_compareUnicodeString, compareTZHashValues, status); - if(hash==NULL){ - return NULL; - } - if(U_FAILURE(status)){ - return NULL; - } - hash->setValueDeleter(deleteUnicodeStringArray); - int32_t pos = -1; - const UHashElement* elem = NULL; - // walk through the hash table and create a deep clone - while((elem = otherHash->nextElement(pos))!= NULL){ - const UHashTok otherKeyTok = elem->key; - const UHashTok otherValueTok = elem->value; - UnicodeString* otherKey = (UnicodeString*)otherKeyTok.pointer; - UnicodeString* otherArray = (UnicodeString*)otherValueTok.pointer; - UnicodeString* array = newUnicodeStringArray(UTZ_MAX_DISPLAY_STRINGS_LENGTH); - if(array==NULL){ - return NULL; - } - UnicodeString key(*otherKey); - for(int32_t i=0; i<UTZ_MAX_DISPLAY_STRINGS_LENGTH; i++){ - array[i].setTo(otherArray[i]); - } - hash->put(key, array, status); - if(U_FAILURE(status)){ - delete[] array; - return NULL; - } - } - return hash; -} - - -UnicodeString& -DateFormatSymbols::getZoneID(const UnicodeString& zid, UnicodeString& result, UErrorCode& status){ - if(fZoneStringsHash == NULL){ - initZoneStrings(status); - } - if(U_FAILURE(status)){ - return result; - } - UnicodeString* strings = (UnicodeString*)fZoneStringsHash->get(zid); - if (strings != NULL) { - return result.setTo(zid,0); - } - - // Do a search through the equivalency group for the given ID - int32_t n = TimeZone::countEquivalentIDs(zid); - if (n > 1) { - int32_t i; - for (i=0; i<n; ++i) { - UnicodeString equivID = TimeZone::getEquivalentID(zid, i); - if (equivID != zid) { - strings = (UnicodeString*)fZoneStringsHash->get(equivID); - if (strings != NULL) { - return result.setTo(equivID,0); - } - } - } - }else{ - result.setTo(zid); - } - return result; -} - -void -DateFormatSymbols::getZoneType(const UnicodeString& zid, const UnicodeString& text, int32_t start, - TimeZoneTranslationType& type, UnicodeString& value, UErrorCode& status){ - if(fZoneStringsHash == NULL){ - initZoneStrings(status); - } - if(U_FAILURE(status)){ - return; - } - type = TIMEZONE_COUNT; - UnicodeString* strings = (UnicodeString*)fZoneStringsHash->get(zid); - if(strings != NULL){ - for(int32_t j=0; j<UTZ_MAX_DISPLAY_STRINGS_LENGTH; j++){ - if(strings[j].length() >0 && text.caseCompare(start, strings[j].length(), strings[j], 0)==0){ - type = (TimeZoneTranslationType)j; - value.setTo(strings[j]); - return; - } - } - } -} -void -DateFormatSymbols::findZoneIDTypeValue( UnicodeString& zid, const UnicodeString& text, int32_t start, - TimeZoneTranslationType& type, UnicodeString& value, - UErrorCode& status){ - if(fZoneStringsHash == NULL){ - initZoneStrings(status); - } - if(U_FAILURE(status)){ - return; - } - const UnicodeString* myKey = NULL; - int32_t pos = 0; - TimeZoneKeysEnumeration *keys = (TimeZoneKeysEnumeration*)fZoneIDEnumeration; - while( (myKey=keys->snext(pos, status))!= NULL){ - UnicodeString* strings = (UnicodeString*)fZoneStringsHash->get(*myKey); - if(strings != NULL){ - for(int32_t j=0; j<UTZ_MAX_DISPLAY_STRINGS_LENGTH; j++){ - if(strings[j].length()>0 && text.caseCompare(start, strings[j].length(), strings[j], 0)==0){ - type = (TimeZoneTranslationType)j; - value.setTo(strings[j]); - if (myKey->startsWith(UNICODE_STRING_SIMPLE("meta"))) { - zid.setTo(resolveParsedMetazone(*myKey)); - } - 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(); - - while( (myKey=tzKeys->snext(status))!= NULL){ - 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); - break; - } - } - delete tzKeys; -} - -UnicodeString -DateFormatSymbols::resolveParsedMetazone( const UnicodeString& zid ) { - - UErrorCode status = U_ZERO_ERROR; - - UResourceBundle* supplementalDataBundle = ures_openDirect(NULL, kSUPPLEMENTAL, &status); - UResourceBundle* mapTz = ures_getByKey(supplementalDataBundle, gMaptimezonesTag, NULL, &status); - if(U_FAILURE(status)){ - ures_close(supplementalDataBundle); - return UNICODE_STRING_SIMPLE("Etc/GMT"); - } - - UResourceBundle* metazoneMap = ures_getByKey(mapTz, gMetazonesTag, NULL, &status); - char mzMapKey[ZID_KEY_MAX+4]; - - int32_t len = zid.length(); - len = (len >= (ZID_KEY_MAX-1) ? ZID_KEY_MAX-1 : len); - u_UCharsToChars(zid.getBuffer(), mzMapKey, len); - mzMapKey[len] = 0; // NULL terminate - - for (int i = 0; i < len; i++) { - if (mzMapKey[i] == '/') { - mzMapKey[i] = ':'; - } - } - - uprv_strcat(mzMapKey,"_"); - uprv_strcat(mzMapKey,fCountry); - - int32_t len2; - const UChar* resStr = ures_getStringByKey(metazoneMap, mzMapKey, &len2, &status); - - // If we can't find a territory-specific metazone mapping, then use the generic one - // which is the metazone name followed by _001 - - if(U_FAILURE(status)){ - status = U_ZERO_ERROR; - mzMapKey[len] = 0; - uprv_strcat(mzMapKey,"_001"); - resStr = ures_getStringByKey(metazoneMap, mzMapKey, &len2, &status); - } - - ures_close(metazoneMap); - ures_close(mapTz); - ures_close(supplementalDataBundle); - - if(U_SUCCESS(status)){ - return resStr; - } - else { - return UNICODE_STRING_SIMPLE("Etc/GMT"); - } - -} U_NAMESPACE_END #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/i18n/i18n.vcproj b/i18n/i18n.vcproj index c6f8b06f..288a45cd 100644 --- a/i18n/i18n.vcproj +++ b/i18n/i18n.vcproj @@ -529,6 +529,24 @@ <File RelativePath=".\unicode\basictz.h" > + <FileConfiguration + Name="Release|Win32" + > + <Tool + Name="VCCustomBuildTool" + CommandLine="copy "$(InputPath)" ..\..\include\unicode" + Outputs="..\..\include\unicode\$(InputFileName)" + /> + </FileConfiguration> + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCustomBuildTool" + CommandLine="copy "$(InputPath)" ..\..\include\unicode" + Outputs="..\..\include\unicode\$(InputFileName)" + /> + </FileConfiguration> </File> <File RelativePath=".\buddhcal.cpp" @@ -799,6 +817,24 @@ <File RelativePath=".\unicode\dtrule.h" > + <FileConfiguration + Name="Release|Win32" + > + <Tool + Name="VCCustomBuildTool" + CommandLine="copy "$(InputPath)" ..\..\include\unicode" + Outputs="..\..\include\unicode\$(InputFileName)" + /> + </FileConfiguration> + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCustomBuildTool" + CommandLine="copy "$(InputPath)" ..\..\include\unicode" + Outputs="..\..\include\unicode\$(InputFileName)" + /> + </FileConfiguration> </File> <File RelativePath=".\unicode\fieldpos.h" @@ -1147,6 +1183,24 @@ <File RelativePath=".\unicode\rbtz.h" > + <FileConfiguration + Name="Release|Win32" + > + <Tool + Name="VCCustomBuildTool" + CommandLine="copy "$(InputPath)" ..\..\include\unicode
" + Outputs="..\..\include\unicode\$(InputFileName)" + /> + </FileConfiguration> + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCustomBuildTool" + CommandLine="copy "$(InputPath)" ..\..\include\unicode
" + Outputs="..\..\include\unicode\$(InputFileName)" + /> + </FileConfiguration> </File> <File RelativePath=".\reldtfmt.cpp" @@ -1249,6 +1303,24 @@ <File RelativePath=".\unicode\tzrule.h" > + <FileConfiguration + Name="Release|Win32" + > + <Tool + Name="VCCustomBuildTool" + CommandLine="copy "$(InputPath)" ..\..\include\unicode" + Outputs="..\..\include\unicode\$(InputFileName)" + /> + </FileConfiguration> + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCustomBuildTool" + CommandLine="copy "$(InputPath)" ..\..\include\unicode" + Outputs="..\..\include\unicode\$(InputFileName)" + /> + </FileConfiguration> </File> <File RelativePath=".\tztrans.cpp" @@ -1257,6 +1329,24 @@ <File RelativePath=".\unicode\tztrans.h" > + <FileConfiguration + Name="Release|Win32" + > + <Tool + Name="VCCustomBuildTool" + CommandLine="copy "$(InputPath)" ..\..\include\unicode" + Outputs="..\..\include\unicode\$(InputFileName)" + /> + </FileConfiguration> + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCustomBuildTool" + CommandLine="copy "$(InputPath)" ..\..\include\unicode" + Outputs="..\..\include\unicode\$(InputFileName)" + /> + </FileConfiguration> </File> <File RelativePath=".\ucal.cpp" @@ -1456,6 +1546,15 @@ RelativePath=".\unicode\utmscale.h" > <FileConfiguration + Name="Release|Win32" + > + <Tool + Name="VCCustomBuildTool" + CommandLine="copy "$(InputPath)" ..\..\include\unicode" + Outputs="..\..\include\unicode\$(InputFileName)" + /> + </FileConfiguration> + <FileConfiguration Name="Debug|Win32" > <Tool @@ -1472,6 +1571,24 @@ <File RelativePath=".\unicode\vtzone.h" > + <FileConfiguration + Name="Release|Win32" + > + <Tool + Name="VCCustomBuildTool" + CommandLine="copy "$(InputPath)" ..\..\include\unicode" + Outputs="..\..\include\unicode\$(InputFileName)" + /> + </FileConfiguration> + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCustomBuildTool" + CommandLine="copy "$(InputPath)" ..\..\include\unicode" + Outputs="..\..\include\unicode\$(InputFileName)" + /> + </FileConfiguration> </File> <File RelativePath=".\windtfmt.cpp" @@ -1521,6 +1638,22 @@ RelativePath=".\winnmfmt.h" > </File> + <File + RelativePath=".\zonemeta.cpp" + > + </File> + <File + RelativePath=".\zonemeta.h" + > + </File> + <File + RelativePath=".\zstrfmt.cpp" + > + </File> + <File + RelativePath=".\zstrfmt.h" + > + </File> </Filter> <Filter Name="misc" diff --git a/i18n/nfsubs.cpp b/i18n/nfsubs.cpp index 353a1b24..37de55a4 100644 --- a/i18n/nfsubs.cpp +++ b/i18n/nfsubs.cpp @@ -97,7 +97,11 @@ public: } virtual double transformNumber(double number) const { - return uprv_floor(number / divisor); + if (getRuleSet()) { + return uprv_floor(number / divisor); + } else { + return number/divisor; + } } virtual double composeRuleValue(double newRuleValue, double /*oldRuleValue*/) const { diff --git a/i18n/olsontz.cpp b/i18n/olsontz.cpp index 8599d4d2..e98b9ef6 100644 --- a/i18n/olsontz.cpp +++ b/i18n/olsontz.cpp @@ -317,11 +317,11 @@ int32_t OlsonTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, millis, monthLength, ec); } - // Compute local epoch seconds from input fields - double time = Grego::fieldsToDay(year, month, dom) * SECONDS_PER_DAY + - uprv_floor(millis / (double) U_MILLIS_PER_SECOND); - - return zoneOffset(findTransition(time, TRUE)) * U_MILLIS_PER_SECOND; + // Compute local epoch millis from input fields + UDate date = (UDate)(Grego::fieldsToDay(year, month, dom) * U_MILLIS_PER_DAY + millis); + int32_t rawoff, dstoff; + getHistoricalOffset(date, TRUE, kDaylight, kStandard, rawoff, dstoff); + return rawoff + dstoff; } /** @@ -332,40 +332,30 @@ void OlsonTimeZone::getOffset(UDate date, UBool local, int32_t& rawoff, if (U_FAILURE(ec)) { return; } - // The check against finalMillis will suffice most of the time, except // for the case in which finalMillis == DBL_MAX, date == DBL_MAX, // and finalZone == 0. For this case we add "&& finalZone != 0". if (date >= finalMillis && finalZone != 0) { - int32_t year, month, dom, dow; - double millis; - double days = Math::floorDivide(date, (double)U_MILLIS_PER_DAY, millis); - - Grego::dayToFields(days, year, month, dom, dow); - - rawoff = finalZone->getRawOffset(); - - if (!local) { - // Adjust from GMT to local - date += rawoff; - double days2 = Math::floorDivide(date, (double)U_MILLIS_PER_DAY, millis); - if (days2 != days) { - Grego::dayToFields(days2, year, month, dom, dow); - } - } + finalZone->getOffset(date, local, rawoff, dstoff, ec); + } else { + getHistoricalOffset(date, local, kFormer, kLatter, rawoff, dstoff); + } +} - dstoff = finalZone->getOffset( - GregorianCalendar::AD, year, month, - dom, (uint8_t) dow, (int32_t) millis, ec) - rawoff; +void +OlsonTimeZone::getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt, + int32_t& rawoff, int32_t& dstoff, UErrorCode& ec) /*const*/ { + if (U_FAILURE(ec)) { return; } - - double secs = uprv_floor(date / U_MILLIS_PER_SECOND); - int16_t i = findTransition(secs, local); - rawoff = rawOffset(i) * U_MILLIS_PER_SECOND; - dstoff = dstOffset(i) * U_MILLIS_PER_SECOND; + if (date >= finalMillis && finalZone != 0) { + finalZone->getOffsetFromLocal(date, nonExistingTimeOpt, duplicatedTimeOpt, rawoff, dstoff, ec); + } else { + getHistoricalOffset(date, TRUE, nonExistingTimeOpt, duplicatedTimeOpt, rawoff, dstoff); + } } + /** * TimeZone API. */ @@ -394,69 +384,84 @@ void printTime(double ms) { double days = Math::floorDivide(((double)ms), (double)U_MILLIS_PER_DAY, millis); Grego::dayToFields(days, year, month, dom, dow); - U_DEBUG_TZ_MSG((" findTransition: time %.1f (%04d.%02d.%02d+%.1fh)\n", ms, + U_DEBUG_TZ_MSG((" getHistoricalOffset: time %.1f (%04d.%02d.%02d+%.1fh)\n", ms, year, month+1, dom, (millis/kOneHour))); } #endif -/** - * Find the smallest i (in 0..transitionCount-1) such that time >= - * transition(i), where transition(i) is either the GMT or the local - * transition time, as specified by `local'. - * @param time epoch seconds, either GMT or local wall - * @param local if TRUE, `time' is in local wall units, otherwise it - * is GMT - * @return an index i, where 0 <= i < transitionCount, and - * transition(i) <= time < transition(i+1), or i == 0 if - * transitionCount == 0 or time < transition(0). - */ -int16_t OlsonTimeZone::findTransition(double time, UBool local) const { - int16_t i = 0; - U_DEBUG_TZ_MSG(("findTransition(%.1f, %s)\n", time, local?"T":"F")); +void +OlsonTimeZone::getHistoricalOffset(UDate date, UBool local, + int32_t NonExistingTimeOpt, int32_t DuplicatedTimeOpt, + int32_t& rawoff, int32_t& dstoff) const { + U_DEBUG_TZ_MSG(("getHistoricalOffset(%.1f, %s, %d, %d, raw, dst)\n", + date, local?"T":"F", NonExistingTimeOpt, DuplicatedTimeOpt)); #if defined U_DEBUG_TZ - printTime(time*1000.0); + printTime(date*1000.0); #endif - if (transitionCount != 0) { + double sec = uprv_floor(date / U_MILLIS_PER_SECOND); // Linear search from the end is the fastest approach, since // most lookups will happen at/near the end. + int16_t i; for (i = transitionCount - 1; i > 0; --i) { int32_t transition = transitionTimes[i]; + if (local) { - int32_t zoneOffsetPrev = zoneOffset(typeData[i-1]); - int32_t zoneOffsetCurr = zoneOffset(typeData[i]); + int32_t offsetBefore = zoneOffset(typeData[i-1]); + UBool dstBefore = dstOffset(typeData[i-1]) != 0; + + int32_t offsetAfter = zoneOffset(typeData[i]); + UBool dstAfter = dstOffset(typeData[i]) != 0; + + UBool dstToStd = dstBefore && !dstAfter; + UBool stdToDst = !dstBefore && dstAfter; - // use the lowest offset ( == standard time ). as per tzregts.cpp which says: - - /** - * @bug 4084933 - * The expected behavior of TimeZone around the boundaries is: - * (Assume transition time of 2:00 AM) - * day of onset 1:59 AM STD = display name 1:59 AM ST - * 2:00 AM STD = display name 3:00 AM DT - * day of end 0:59 AM STD = display name 1:59 AM DT - * 1:00 AM STD = display name 1:00 AM ST - */ - if(zoneOffsetPrev<zoneOffsetCurr) { - transition += zoneOffsetPrev; + if (offsetAfter - offsetBefore >= 0) { + // Positive transition, which makes a non-existing local time range + if (((NonExistingTimeOpt & kStdDstMask) == kStandard && dstToStd) + || ((NonExistingTimeOpt & kStdDstMask) == kDaylight && stdToDst)) { + transition += offsetBefore; + } else if (((NonExistingTimeOpt & kStdDstMask) == kStandard && stdToDst) + || ((NonExistingTimeOpt & kStdDstMask) == kDaylight && dstToStd)) { + transition += offsetAfter; + } else if ((NonExistingTimeOpt & kFormerLatterMask) == kLatter) { + transition += offsetBefore; + } else { + // Interprets the time with rule before the transition, + // default for non-existing time range + transition += offsetAfter; + } } else { - transition += zoneOffsetCurr; + // Negative transition, which makes a duplicated local time range + if (((DuplicatedTimeOpt & kStdDstMask) == kStandard && dstToStd) + || ((DuplicatedTimeOpt & kStdDstMask) == kDaylight && stdToDst)) { + transition += offsetAfter; + } else if (((DuplicatedTimeOpt & kStdDstMask) == kStandard && stdToDst) + || ((DuplicatedTimeOpt & kStdDstMask) == kDaylight && dstToStd)) { + transition += offsetBefore; + } else if ((DuplicatedTimeOpt & kFormerLatterMask) == kFormer) { + transition += offsetBefore; + } else { + // Interprets the time with rule after the transition, + // default for duplicated local time range + transition += offsetAfter; + } } } - if (time >= transition) { - U_DEBUG_TZ_MSG(("Found@%d: time=%.1f, localtransition=%d (orig %d) dz %d\n", i, time, transition, transitionTimes[i], + if (sec >= transition) { + U_DEBUG_TZ_MSG(("Found@%d: time=%.1f, localtransition=%d (orig %d) dz %d\n", i, sec, transition, transitionTimes[i], zoneOffset(typeData[i-1]))); #if defined U_DEBUG_TZ - printTime(transition*1000.0); - printTime(transitionTimes[i]*1000.0); + printTime(transition*1000.0); + printTime(transitionTimes[i]*1000.0); #endif break; } else { - U_DEBUG_TZ_MSG(("miss@%d: time=%.1f, localtransition=%d (orig %d) dz %d\n", i, time, transition, transitionTimes[i], + U_DEBUG_TZ_MSG(("miss@%d: time=%.1f, localtransition=%d (orig %d) dz %d\n", i, sec, transition, transitionTimes[i], zoneOffset(typeData[i-1]))); #if defined U_DEBUG_TZ - printTime(transition*1000.0); - printTime(transitionTimes[i]*1000.0); + printTime(transition*1000.0); + printTime(transitionTimes[i]*1000.0); #endif } } @@ -465,17 +470,25 @@ int16_t OlsonTimeZone::findTransition(double time, UBool local) const { // Check invariants for GMT times; if these pass for GMT times // the local logic should be working too. - U_ASSERT(local || time < transitionTimes[0] || time >= transitionTimes[i]); - U_ASSERT(local || i == transitionCount-1 || time < transitionTimes[i+1]); - - U_DEBUG_TZ_MSG(("findTransition(%.1f, %s)= trans %d\n", time, local?"T":"F", i)); - i = typeData[i]; + U_ASSERT(local || sec < transitionTimes[0] || sec >= transitionTimes[i]); + U_ASSERT(local || i == transitionCount-1 || sec < transitionTimes[i+1]); + + U_DEBUG_TZ_MSG(("getHistoricalOffset(%.1f, %s, %d, %d, raw, dst) - trans %d\n", + date, local?"T":"F", NonExistingTimeOpt, DuplicatedTimeOpt, i)); + + // Since ICU tzdata 2007c, the first transition data is actually not a + // transition, but used for representing the initial offset. So the code + // below works even if i == 0. + int16_t index = typeData[i]; + rawoff = rawOffset(index) * U_MILLIS_PER_SECOND; + dstoff = dstOffset(index) * U_MILLIS_PER_SECOND; + } else { + // No transitions, single pair of offsets only + rawoff = rawOffset(0) * U_MILLIS_PER_SECOND; + dstoff = dstOffset(0) * U_MILLIS_PER_SECOND; } - - U_ASSERT(i>=0 && i<typeCount); - - U_DEBUG_TZ_MSG(("findTransition(%.1f, %s)=%d, offset %d\n", time, local?"T":"F", i, zoneOffset(i))); - return i; + U_DEBUG_TZ_MSG(("getHistoricalOffset(%.1f, %s, %d, %d, raw, dst) - raw=%d, dst=%d\n", + date, local?"T":"F", NonExistingTimeOpt, DuplicatedTimeOpt, rawoff, dstoff)); } /** diff --git a/i18n/olsontz.h b/i18n/olsontz.h index d5c4bce1..64f8c0d7 100644 --- a/i18n/olsontz.h +++ b/i18n/olsontz.h @@ -182,6 +182,12 @@ class OlsonTimeZone: public BasicTimeZone { int32_t& dstOffset, UErrorCode& ec) const; /** + * BasicTimeZone API. + */ + virtual void getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt, + int32_t& rawoff, int32_t& dstoff, UErrorCode& ec) /*const*/; + + /** * TimeZone API. This method has no effect since objects of this * class are quasi-immutable (the base class allows the ID to be * changed). @@ -279,7 +285,9 @@ private: void constructEmpty(); - int16_t findTransition(double time, UBool local) const; + void getHistoricalOffset(UDate date, UBool local, + int32_t NonExistingTimeOpt, int32_t DuplicatedTimeOpt, + int32_t& rawoff, int32_t& dstoff) const; int32_t zoneOffset(int16_t index) const; int32_t rawOffset(int16_t index) const; diff --git a/i18n/rbtz.cpp b/i18n/rbtz.cpp index 38943763..f810d70b 100644 --- a/i18n/rbtz.cpp +++ b/i18n/rbtz.cpp @@ -46,14 +46,6 @@ static UBool compareRules(UVector* rules1, UVector* rules2) { return TRUE; } -static UDate getTransitionTime(Transition* transition, UBool local) { - UDate time = transition->time; - if (local) { - time += transition->from->getRawOffset() + transition->from->getDSTSavings(); - } - return time; -} - UOBJECT_DEFINE_RTTI_IMPLEMENTATION(RuleBasedTimeZone) RuleBasedTimeZone::RuleBasedTimeZone(const UnicodeString& id, InitialTimeZoneRule* initialRule) @@ -183,7 +175,7 @@ RuleBasedTimeZone::complete(UErrorCode& status) { for (i = 0; i < historicCount; i++) { done[i] = FALSE; } - while (true) { + while (TRUE) { int32_t curStdOffset = curRule->getRawOffset(); int32_t curDstSavings = curRule->getDSTSavings(); UDate nextTransitionTime = MAX_MILLIS; @@ -377,7 +369,7 @@ RuleBasedTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t d } int32_t rawOffset, dstOffset; UDate time = (UDate)Grego::fieldsToDay(year, month, day) * U_MILLIS_PER_DAY + millis; - getOffset(time, true, rawOffset, dstOffset, status); + getOffsetInternal(time, TRUE, kDaylight, kStandard, rawOffset, dstOffset, status); if (U_FAILURE(status)) { return 0; } @@ -387,6 +379,24 @@ RuleBasedTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t d void RuleBasedTimeZone::getOffset(UDate date, UBool local, int32_t& rawOffset, int32_t& dstOffset, UErrorCode& status) const { + getOffsetInternal(date, local, kFormer, kLatter, rawOffset, dstOffset, status); +} + +void +RuleBasedTimeZone::getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt, + int32_t& rawOffset, int32_t& dstOffset, UErrorCode& status) /*const*/ { + getOffsetInternal(date, TRUE, nonExistingTimeOpt, duplicatedTimeOpt, rawOffset, dstOffset, status); +} + + +/* + * The internal getOffset implementation + */ +void +RuleBasedTimeZone::getOffsetInternal(UDate date, UBool local, + int32_t NonExistingTimeOpt, int32_t DuplicatedTimeOpt, + int32_t& rawOffset, int32_t& dstOffset, + UErrorCode& status) const { rawOffset = 0; dstOffset = 0; @@ -404,15 +414,17 @@ RuleBasedTimeZone::getOffset(UDate date, UBool local, int32_t& rawOffset, if (fHistoricTransitions == NULL) { rule = fInitialRule; } else { - UDate tstart = getTransitionTime((Transition*)fHistoricTransitions->elementAt(0), local); + UDate tstart = getTransitionTime((Transition*)fHistoricTransitions->elementAt(0), + local, NonExistingTimeOpt, DuplicatedTimeOpt); if (date < tstart) { rule = fInitialRule; } else { int32_t idx = fHistoricTransitions->size() - 1; - UDate tend = getTransitionTime((Transition*)fHistoricTransitions->elementAt(idx), local); + UDate tend = getTransitionTime((Transition*)fHistoricTransitions->elementAt(idx), + local, NonExistingTimeOpt, DuplicatedTimeOpt); if (date > tend) { if (fFinalRules != NULL) { - rule = findRuleInFinal(date, local); + rule = findRuleInFinal(date, local, NonExistingTimeOpt, DuplicatedTimeOpt); } else { // no final rule, use the last rule rule = ((Transition*)fHistoricTransitions->elementAt(idx))->to; @@ -420,7 +432,8 @@ RuleBasedTimeZone::getOffset(UDate date, UBool local, int32_t& rawOffset, } else { // Find a historical transition while (idx >= 0) { - if (date >= getTransitionTime((Transition*)fHistoricTransitions->elementAt(idx), local)) { + if (date >= getTransitionTime((Transition*)fHistoricTransitions->elementAt(idx), + local, NonExistingTimeOpt, DuplicatedTimeOpt)) { break; } idx--; @@ -651,7 +664,8 @@ RuleBasedTimeZone::copyRules(UVector* source) { } TimeZoneRule* -RuleBasedTimeZone::findRuleInFinal(UDate date, UBool local) const { +RuleBasedTimeZone::findRuleInFinal(UDate date, UBool local, + int32_t NonExistingTimeOpt, int32_t DuplicatedTimeOpt) const { if (fFinalRules == NULL) { return NULL; } @@ -664,12 +678,25 @@ RuleBasedTimeZone::findRuleInFinal(UDate date, UBool local) const { UDate start0, start1; UDate base; + int32_t localDelta; - base = local ? date - fr1->getRawOffset() - fr1->getDSTSavings() : date; - UBool avail0 = fr0->getPreviousStart(base, fr1->getRawOffset(), fr1->getDSTSavings(), true, start0); + base = date; + if (local) { + localDelta = getLocalDelta(fr1->getRawOffset(), fr1->getDSTSavings(), + fr0->getRawOffset(), fr0->getDSTSavings(), + NonExistingTimeOpt, DuplicatedTimeOpt); + base -= localDelta; + } + UBool avail0 = fr0->getPreviousStart(base, fr1->getRawOffset(), fr1->getDSTSavings(), TRUE, start0); - base = local ? date - fr0->getRawOffset() - fr0->getDSTSavings() : date; - UBool avail1 = fr1->getPreviousStart(base, fr0->getRawOffset(), fr0->getDSTSavings(), true, start1); + base = date; + if (local) { + localDelta = getLocalDelta(fr0->getRawOffset(), fr0->getDSTSavings(), + fr1->getRawOffset(), fr1->getDSTSavings(), + NonExistingTimeOpt, DuplicatedTimeOpt); + base -= localDelta; + } + UBool avail1 = fr1->getPreviousStart(base, fr0->getRawOffset(), fr0->getDSTSavings(), TRUE, start1); if (avail0 && (!avail1 || start0 > start1)) { return fr0; @@ -689,14 +716,14 @@ RuleBasedTimeZone::findNext(UDate base, UBool inclusive, UDate& transitionTime, UBool found = FALSE; Transition result; Transition *tzt = (Transition*)fHistoricTransitions->elementAt(0); - UDate tt = getTransitionTime(tzt, FALSE); + UDate tt = tzt->time; if (tt > base || (inclusive && tt == base)) { result = *tzt; found = TRUE; } else { int32_t idx = fHistoricTransitions->size() - 1; tzt = (Transition*)fHistoricTransitions->elementAt(idx); - tt = getTransitionTime(tzt, FALSE); + tt = tzt->time; if (inclusive && tt == base) { result = *tzt; found = TRUE; @@ -730,7 +757,7 @@ RuleBasedTimeZone::findNext(UDate base, UBool inclusive, UDate& transitionTime, Transition *prev = tzt; while (idx > 0) { tzt = (Transition*)fHistoricTransitions->elementAt(idx); - tt = getTransitionTime(tzt, FALSE); + tt = tzt->time; if (tt < base || (!inclusive && tt == base)) { break; } @@ -772,14 +799,14 @@ RuleBasedTimeZone::findPrev(UDate base, UBool inclusive, UDate& transitionTime, UBool found = FALSE; Transition result; Transition *tzt = (Transition*)fHistoricTransitions->elementAt(0); - UDate tt = getTransitionTime(tzt, FALSE); + UDate tt = tzt->time; if (inclusive && tt == base) { result = *tzt; found = TRUE; } else if (tt < base) { int32_t idx = fHistoricTransitions->size() - 1; tzt = (Transition*)fHistoricTransitions->elementAt(idx); - tt = getTransitionTime(tzt, FALSE); + tt = tzt->time; if (inclusive && tt == base) { result = *tzt; found = TRUE; @@ -813,7 +840,7 @@ RuleBasedTimeZone::findPrev(UDate base, UBool inclusive, UDate& transitionTime, idx--; while (idx >= 0) { tzt = (Transition*)fHistoricTransitions->elementAt(idx); - tt = getTransitionTime(tzt, FALSE); + tt = tzt->time; if (tt < base || (inclusive && tt == base)) { break; } @@ -839,6 +866,63 @@ RuleBasedTimeZone::findPrev(UDate base, UBool inclusive, UDate& transitionTime, return FALSE; } +UDate +RuleBasedTimeZone::getTransitionTime(Transition* transition, UBool local, + int32_t NonExistingTimeOpt, int32_t DuplicatedTimeOpt) const { + UDate time = transition->time; + if (local) { + time += getLocalDelta(transition->from->getRawOffset(), transition->from->getDSTSavings(), + transition->to->getRawOffset(), transition->to->getDSTSavings(), + NonExistingTimeOpt, DuplicatedTimeOpt); + } + return time; +} + +int32_t +RuleBasedTimeZone::getLocalDelta(int32_t rawBefore, int32_t dstBefore, int32_t rawAfter, int32_t dstAfter, + int32_t NonExistingTimeOpt, int32_t DuplicatedTimeOpt) const { + int32_t delta = 0; + + int32_t offsetBefore = rawBefore + dstBefore; + int32_t offsetAfter = rawAfter + dstAfter; + + UBool dstToStd = (dstBefore != 0) && (dstAfter == 0); + UBool stdToDst = (dstBefore == 0) && (dstAfter != 0); + + if (offsetAfter - offsetBefore >= 0) { + // Positive transition, which makes a non-existing local time range + if (((NonExistingTimeOpt & kStdDstMask) == kStandard && dstToStd) + || ((NonExistingTimeOpt & kStdDstMask) == kDaylight && stdToDst)) { + delta = offsetBefore; + } else if (((NonExistingTimeOpt & kStdDstMask) == kStandard && stdToDst) + || ((NonExistingTimeOpt & kStdDstMask) == kDaylight && dstToStd)) { + delta = offsetAfter; + } else if ((NonExistingTimeOpt & kFormerLatterMask) == kLatter) { + delta = offsetBefore; + } else { + // Interprets the time with rule before the transition, + // default for non-existing time range + delta = offsetAfter; + } + } else { + // Negative transition, which makes a duplicated local time range + if (((DuplicatedTimeOpt & kStdDstMask) == kStandard && dstToStd) + || ((DuplicatedTimeOpt & kStdDstMask) == kDaylight && stdToDst)) { + delta = offsetAfter; + } else if (((DuplicatedTimeOpt & kStdDstMask) == kStandard && stdToDst) + || ((DuplicatedTimeOpt & kStdDstMask) == kDaylight && dstToStd)) { + delta = offsetBefore; + } else if ((DuplicatedTimeOpt & kFormerLatterMask) == kFormer) { + delta = offsetBefore; + } else { + // Interprets the time with rule after the transition, + // default for duplicated local time range + delta = offsetAfter; + } + } + return delta; +} + U_NAMESPACE_END #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/i18n/simpletz.cpp b/i18n/simpletz.cpp index ca827f38..c9cdade3 100644 --- a/i18n/simpletz.cpp +++ b/i18n/simpletz.cpp @@ -502,6 +502,55 @@ SimpleTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day, return result; } +void +SimpleTimeZone::getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt, + int32_t& rawOffsetGMT, int32_t& savingsDST, UErrorCode& status) /*const*/ { + if (U_FAILURE(status)) { + return; + } + + rawOffsetGMT = getRawOffset(); + int32_t year, month, dom, dow; + double day = uprv_floor(date / U_MILLIS_PER_DAY); + int32_t millis = (int32_t) (date - day * U_MILLIS_PER_DAY); + + Grego::dayToFields(day, year, month, dom, dow); + + savingsDST = getOffset(GregorianCalendar::AD, year, month, dom, + (uint8_t) dow, millis, + Grego::monthLength(year, month), + status) - rawOffsetGMT; + if (U_FAILURE(status)) { + return; + } + + UBool recalc = FALSE; + + // Now we need some adjustment + if (savingsDST > 0) { + if ((nonExistingTimeOpt & kStdDstMask) == kStandard + || (nonExistingTimeOpt & kStdDstMask) != kDaylight && (nonExistingTimeOpt & kFormerLatterMask) != kLatter) { + date -= getDSTSavings(); + recalc = TRUE; + } + } else { + if ((duplicatedTimeOpt & kStdDstMask) == kDaylight + || (duplicatedTimeOpt & kStdDstMask) != kStandard && (duplicatedTimeOpt & kFormerLatterMask) == kFormer) { + date -= getDSTSavings(); + recalc = TRUE; + } + } + if (recalc) { + day = uprv_floor(date / U_MILLIS_PER_DAY); + millis = (int32_t) (date - day * U_MILLIS_PER_DAY); + Grego::dayToFields(day, year, month, dom, dow); + savingsDST = getOffset(GregorianCalendar::AD, year, month, dom, + (uint8_t) dow, millis, + Grego::monthLength(year, month), + status) - rawOffsetGMT; + } +} + // ------------------------------------- /** @@ -966,8 +1015,8 @@ SimpleTimeZone::getPreviousTransition(UDate base, UBool inclusive, TimeZoneTrans return FALSE; } UDate stdDate, dstDate; - UBool stdAvail = stdRule->getPreviousStart(base, dstRule->getRawOffset(), dstRule->getDSTSavings(), false, stdDate); - UBool dstAvail = dstRule->getPreviousStart(base, stdRule->getRawOffset(), stdRule->getDSTSavings(), false, dstDate); + UBool stdAvail = stdRule->getPreviousStart(base, dstRule->getRawOffset(), dstRule->getDSTSavings(), inclusive, stdDate); + UBool dstAvail = dstRule->getPreviousStart(base, stdRule->getRawOffset(), stdRule->getDSTSavings(), inclusive, dstDate); if (stdAvail && (!dstAvail || stdDate > dstDate)) { result.setTime(stdDate); result.setFrom((const TimeZoneRule&)*dstRule); diff --git a/i18n/smpdtfmt.cpp b/i18n/smpdtfmt.cpp index 17e9a520..488af9fd 100644 --- a/i18n/smpdtfmt.cpp +++ b/i18n/smpdtfmt.cpp @@ -41,10 +41,18 @@ #include "unicode/dcfmtsym.h" #include "unicode/uchar.h" #include "unicode/ustring.h" +#include "unicode/basictz.h" +#include "unicode/simpletz.h" +#include "unicode/rbtz.h" +#include "unicode/vtzone.h" +#include "olsontz.h" #include "../common/util.h" #include "gregoimp.h" #include "cstring.h" #include "uassert.h" +#include "zstrfmt.h" +#include "cmemory.h" +#include "umutex.h" #include <float.h> #if defined( U_DEBUG_CALSVC ) || defined (U_DEBUG_CAL) @@ -66,6 +74,19 @@ U_NAMESPACE_BEGIN static const UChar gGmt[] = {0x0047, 0x004D, 0x0054, 0x0000}; // "GMT" static const UChar gGmtPlus[] = {0x0047, 0x004D, 0x0054, 0x002B, 0x0000}; // "GMT+" static const UChar gGmtMinus[] = {0x0047, 0x004D, 0x0054, 0x002D, 0x0000}; // "GMT-" +static const UChar gDefGmtPat[] = {0x0047, 0x004D, 0x0054, 0x007B, 0x0030, 0x007D, 0x0000}; /* GMT{0} */ +static const UChar gDefGmtNegHmsPat[] = {0x002D, 0x0048, 0x0048, 0x003A, 0x006D, 0x006D, 0x003A, 0x0073, 0x0073, 0x0000}; /* -HH:mm:ss */ +static const UChar gDefGmtNegHmPat[] = {0x002D, 0x0048, 0x0048, 0x003A, 0x006D, 0x006D, 0x0000}; /* -HH:mm */ +static const UChar gDefGmtPosHmsPat[] = {0x002B, 0x0048, 0x0048, 0x003A, 0x006D, 0x006D, 0x003A, 0x0073, 0x0073, 0x0000}; /* +HH:mm:ss */ +static const UChar gDefGmtPosHmPat[] = {0x002B, 0x0048, 0x0048, 0x003A, 0x006D, 0x006D, 0x0000}; /* +HH:mm */ +typedef enum GmtPatSize { + kGmtLen = 3, + kGmtPatLen = 6, + kNegHmsLen = 9, + kNegHmLen = 6, + kPosHmsLen = 9, + kPosHmLen = 6 +} GmtPatSize; // This is a pattern-of-last-resort used when we can't load a usable pattern out // of a resource. @@ -83,21 +104,36 @@ 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) - +static const UChar gEtcUTC[] = {0x45, 0x74, 0x63, 0x2F, 0x55, 0x54, 0x43, 0x00}; // "Etc/UTC" static const UChar QUOTE = 0x27; // Single quote +enum { + kGMTNegativeHMS = 0, + kGMTNegativeHM, + kGMTPositiveHMS, + kGMTPositiveHM, + + kNumGMTFormatters +}; + +static UMTX LOCK; + +UOBJECT_DEFINE_RTTI_IMPLEMENTATION(SimpleDateFormat) //---------------------------------------------------------------------- SimpleDateFormat::~SimpleDateFormat() { delete fSymbols; - delete parsedTimeZone; // sanity check + if (fGMTFormatters) { + for (int32_t i = 0; i < kNumGMTFormatters; i++) { + if (fGMTFormatters[i]) { + delete fGMTFormatters[i]; + } + } + uprv_free(fGMTFormatters); + } } //---------------------------------------------------------------------- @@ -105,7 +141,7 @@ SimpleDateFormat::~SimpleDateFormat() SimpleDateFormat::SimpleDateFormat(UErrorCode& status) : fLocale(Locale::getDefault()), fSymbols(NULL), - parsedTimeZone(NULL) + fGMTFormatters(NULL) { construct(kShort, (EStyle) (kShort + kDateOffset), fLocale, status); initializeDefaultCentury(); @@ -118,7 +154,7 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, : fPattern(pattern), fLocale(Locale::getDefault()), fSymbols(NULL), - parsedTimeZone(NULL) + fGMTFormatters(NULL) { initializeSymbols(fLocale, initializeCalendar(NULL,fLocale,status), status); initialize(fLocale, status); @@ -132,7 +168,7 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, UErrorCode& status) : fPattern(pattern), fLocale(locale), - parsedTimeZone(NULL) + fGMTFormatters(NULL) { initializeSymbols(fLocale, initializeCalendar(NULL,fLocale,status), status); initialize(fLocale, status); @@ -147,7 +183,7 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, : fPattern(pattern), fLocale(Locale::getDefault()), fSymbols(symbolsToAdopt), - parsedTimeZone(NULL) + fGMTFormatters(NULL) { initializeCalendar(NULL,fLocale,status); initialize(fLocale, status); @@ -162,7 +198,7 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, : fPattern(pattern), fLocale(Locale::getDefault()), fSymbols(new DateFormatSymbols(symbols)), - parsedTimeZone(NULL) + fGMTFormatters(NULL) { initializeCalendar(NULL, fLocale, status); initialize(fLocale, status); @@ -178,7 +214,7 @@ SimpleDateFormat::SimpleDateFormat(EStyle timeStyle, UErrorCode& status) : fLocale(locale), fSymbols(NULL), - parsedTimeZone(NULL) + fGMTFormatters(NULL) { construct(timeStyle, dateStyle, fLocale, status); if(U_SUCCESS(status)) { @@ -198,7 +234,7 @@ SimpleDateFormat::SimpleDateFormat(const Locale& locale, : fPattern(gDefaultPattern), fLocale(locale), fSymbols(NULL), - parsedTimeZone(NULL) + fGMTFormatters(NULL) { if (U_FAILURE(status)) return; initializeSymbols(fLocale, initializeCalendar(NULL, fLocale, status),status); @@ -226,7 +262,7 @@ SimpleDateFormat::SimpleDateFormat(const Locale& locale, SimpleDateFormat::SimpleDateFormat(const SimpleDateFormat& other) : DateFormat(other), fSymbols(NULL), - parsedTimeZone(NULL) + fGMTFormatters(NULL) { *this = other; } @@ -243,8 +279,6 @@ SimpleDateFormat& SimpleDateFormat::operator=(const SimpleDateFormat& other) delete fSymbols; fSymbols = NULL; - delete parsedTimeZone; parsedTimeZone = NULL; - if (other.fSymbols) fSymbols = new DateFormatSymbols(*other.fSymbols); @@ -585,20 +619,336 @@ _appendSymbol(UnicodeString& dst, } //--------------------------------------------------------------------- -inline void SimpleDateFormat::appendGMT(UnicodeString &appendTo, Calendar& cal, UErrorCode& status) const{ - int32_t value = cal.get(UCAL_ZONE_OFFSET, status) + - cal.get(UCAL_DST_OFFSET, status); +void +SimpleDateFormat::appendGMT(UnicodeString &appendTo, Calendar& cal, UErrorCode& status) const{ + int32_t offset = cal.get(UCAL_ZONE_OFFSET, status) + cal.get(UCAL_DST_OFFSET, status); + if (U_FAILURE(status)) { + return; + } + if (isDefaultGMTFormat()) { + formatGMTDefault(appendTo, offset); + } else { + ((SimpleDateFormat*)this)->initGMTFormatters(status); + if (U_SUCCESS(status)) { + int32_t type; + if (offset < 0) { + offset = -offset; + type = (offset % U_MILLIS_PER_MINUTE) == 0 ? kGMTNegativeHM : kGMTNegativeHMS; + } else { + type = (offset % U_MILLIS_PER_MINUTE) == 0 ? kGMTPositiveHM : kGMTPositiveHMS; + } + Formattable param(offset, Formattable::kIsDate); + FieldPosition fpos(0); + fGMTFormatters[type]->format(¶m, 1, appendTo, fpos, status); + } + } +} + +int32_t +SimpleDateFormat::parseGMT(const UnicodeString &text, ParsePosition &pos) const { + if (!isDefaultGMTFormat()) { + int32_t start = pos.getIndex(); + + // Quick check + UBool prefixMatch = FALSE; + int32_t prefixLen = fSymbols->fGmtFormat.indexOf((UChar)0x007B /* '{' */); + if (prefixLen > 0 && text.compare(start, prefixLen, fSymbols->fGmtFormat, 0, prefixLen) == 0) { + prefixMatch = TRUE; + } + if (prefixMatch) { + // Prefix matched + UErrorCode status = U_ZERO_ERROR; + ((SimpleDateFormat*)this)->initGMTFormatters(status); + if (U_SUCCESS(status)) { + Formattable parsed; + int32_t parsedCount; + + // Try negative Hms + fGMTFormatters[kGMTNegativeHMS]->parseObject(text, parsed, pos); + if (pos.getErrorIndex() == -1 && pos.getIndex() > start) { + parsed.getArray(parsedCount); + if (parsedCount == 1 && parsed[0].getType() == Formattable::kDate) { + return (int32_t)(-1 * parsed[0].getDate()); + } + } + + // Reset ParsePosition + pos.setIndex(start); + pos.setErrorIndex(-1); + + // Try positive Hms + fGMTFormatters[kGMTPositiveHMS]->parseObject(text, parsed, pos); + if (pos.getErrorIndex() == -1 && pos.getIndex() > start) { + parsed.getArray(parsedCount); + if (parsedCount == 1 && parsed[0].getType() == Formattable::kDate) { + return (int32_t)parsed[0].getDate(); + } + } - if (value < 0) { + // Reset ParsePosition + pos.setIndex(start); + pos.setErrorIndex(-1); + + // Try negative Hm + fGMTFormatters[kGMTNegativeHM]->parseObject(text, parsed, pos); + if (pos.getErrorIndex() == -1 && pos.getIndex() > start) { + parsed.getArray(parsedCount); + if (parsedCount == 1 && parsed[0].getType() == Formattable::kDate) { + return (int32_t)(-1 * parsed[0].getDate()); + } + } + + // Reset ParsePosition + pos.setIndex(start); + pos.setErrorIndex(-1); + + // Try positive Hm + fGMTFormatters[kGMTPositiveHM]->parseObject(text, parsed, pos); + if (pos.getErrorIndex() == -1 && pos.getIndex() > start) { + parsed.getArray(parsedCount); + if (parsedCount == 1 && parsed[0].getType() == Formattable::kDate) { + return (int32_t)parsed[0].getDate(); + } + } + + // Reset ParsePosition + pos.setIndex(start); + pos.setErrorIndex(-1); + } + // fall through to the default GMT parsing method + } + } + return parseGMTDefault(text, pos); +} + +void +SimpleDateFormat::formatGMTDefault(UnicodeString &appendTo, int32_t offset) const { + if (offset < 0) { appendTo += gGmtMinus; - value = -value; // suppress the '-' sign for text display. + offset = -offset; // suppress the '-' sign for text display. }else{ appendTo += gGmtPlus; } - zeroPaddingNumber(appendTo, (int32_t)(value/U_MILLIS_PER_HOUR), 2, 2); + offset /= U_MILLIS_PER_SECOND; // now in seconds + int32_t sec = offset % 60; + offset /= 60; + int32_t min = offset % 60; + int32_t hour = offset / 60; + + + zeroPaddingNumber(appendTo, hour, 2, 2); appendTo += (UChar)0x003A /*':'*/; - zeroPaddingNumber(appendTo, (int32_t)((value%U_MILLIS_PER_HOUR)/U_MILLIS_PER_MINUTE), 2, 2); + zeroPaddingNumber(appendTo, min, 2, 2); + if (sec != 0) { + appendTo += (UChar)0x003A /*':'*/; + zeroPaddingNumber(appendTo, sec, 2, 2); + } +} + +int32_t +SimpleDateFormat::parseGMTDefault(const UnicodeString &text, ParsePosition &pos) const { + int32_t start = pos.getIndex(); + + if (start + kGmtLen + 1 >= text.length()) { + pos.setErrorIndex(start); + return 0; + } + + int32_t cur = start; + // "GMT" + if (text.compare(start, kGmtLen, gGmt) != 0) { + pos.setErrorIndex(start); + return 0; + } + cur += kGmtLen; + // Sign + UBool negative = FALSE; + if (text.charAt(cur) == (UChar)0x002D /* minus */) { + negative = TRUE; + } else if (text.charAt(cur) != (UChar)0x002B /* plus */) { + pos.setErrorIndex(cur); + return 0; + } + cur++; + + // Numbers + int32_t numLen; + pos.setIndex(cur); + + Formattable number; + parseInt(text, number, 6, pos, FALSE); + numLen = pos.getIndex() - cur; + + if (numLen <= 0) { + pos.setIndex(start); + pos.setErrorIndex(cur); + return 0; + } + + int32_t numVal = number.getLong(); + + int32_t hour = 0; + int32_t min = 0; + int32_t sec = 0; + + if (numLen <= 2) { + // H[H][:mm[:ss]] + hour = numVal; + cur += numLen; + if (cur + 2 < text.length() && text.charAt(cur) == (UChar)0x003A /* colon */) { + cur++; + pos.setIndex(cur); + parseInt(text, number, 2, pos, FALSE); + numLen = pos.getIndex() - cur; + if (numLen == 2) { + // got minute field + min = number.getLong(); + cur += numLen; + if (cur + 2 < text.length() && text.charAt(cur) == (UChar)0x003A /* colon */) { + cur++; + pos.setIndex(cur); + parseInt(text, number, 2, pos, FALSE); + numLen = pos.getIndex() - cur; + if (numLen == 2) { + // got second field + sec = number.getLong(); + } else { + // reset position + pos.setIndex(cur - 1); + pos.setErrorIndex(-1); + } + } + } else { + // reset postion + pos.setIndex(cur - 1); + pos.setErrorIndex(-1); + } + } + } else if (numLen == 3 || numLen == 4) { + // Hmm or HHmm + hour = numVal / 100; + min = numVal % 100; + } else if (numLen == 5 || numLen == 6) { + // Hmmss or HHmmss + hour = numVal / 10000; + min = (numVal % 10000) / 100; + sec = numVal % 100; + } else { + // HHmmss followed by bogus numbers + pos.setIndex(cur + 6); + + int32_t shift = numLen - 6; + while (shift > 0) { + numVal /= 10; + shift--; + } + hour = numVal / 10000; + min = (numVal % 10000) / 100; + sec = numVal % 100; + } + + int32_t offset = ((hour*60 + min)*60 + sec)*1000; + if (negative) { + offset = -offset; + } + return offset; +} + +UBool +SimpleDateFormat::isDefaultGMTFormat() const { + // GMT pattern + if (fSymbols->fGmtFormat.length() == 0) { + // No GMT pattern is set + return TRUE; + } else if (fSymbols->fGmtFormat.compare(gDefGmtPat, kGmtPatLen) != 0) { + return FALSE; + } + // Hour patterns + if (fSymbols->fGmtHourFormats == NULL || fSymbols->fGmtHourFormatsCount != DateFormatSymbols::GMT_HOUR_COUNT) { + // No Hour pattern is set + return TRUE; + } else if ((fSymbols->fGmtHourFormats[DateFormatSymbols::GMT_NEGATIVE_HMS].compare(gDefGmtNegHmsPat, kNegHmsLen) != 0) + || (fSymbols->fGmtHourFormats[DateFormatSymbols::GMT_NEGATIVE_HM].compare(gDefGmtNegHmPat, kNegHmLen) != 0) + || (fSymbols->fGmtHourFormats[DateFormatSymbols::GMT_POSITIVE_HMS].compare(gDefGmtPosHmsPat, kPosHmsLen) != 0) + || (fSymbols->fGmtHourFormats[DateFormatSymbols::GMT_POSITIVE_HM].compare(gDefGmtPosHmPat, kPosHmLen) != 0)) { + return FALSE; + } + return TRUE; +} + +void +SimpleDateFormat::formatRFC822TZ(UnicodeString &appendTo, int32_t offset) const { + UChar sign = 0x002B /* '+' */; + if (offset < 0) { + offset = -offset; + sign = 0x002D /* '-' */; + } + appendTo.append(sign); + + int32_t offsetH = offset / U_MILLIS_PER_HOUR; + offset = offset % U_MILLIS_PER_HOUR; + int32_t offsetM = offset / U_MILLIS_PER_MINUTE; + offset = offset % U_MILLIS_PER_MINUTE; + int32_t offsetS = offset / U_MILLIS_PER_SECOND; + + int32_t num = 0, denom = 0; + if (offsetS == 0) { + offset = offsetH*100 + offsetM; // HHmm + num = offset % 10000; + denom = 1000; + } else { + offset = offsetH*10000 + offsetM*100 + offsetS; // HHmmss + num = offset % 1000000; + denom = 100000; + } + while (denom >= 1) { + UChar digit = (UChar)0x0030 + (num / denom); + appendTo.append(digit); + num = num % denom; + denom /= 10; + } +} + +void +SimpleDateFormat::initGMTFormatters(UErrorCode &status) { + if (U_FAILURE(status)) { + return; + } + umtx_lock(&LOCK); + if (fGMTFormatters == NULL) { + fGMTFormatters = (MessageFormat**)uprv_malloc(kNumGMTFormatters * sizeof(MessageFormat*)); + if (fGMTFormatters) { + for (int32_t i = 0; i < kNumGMTFormatters; i++) { + const UnicodeString *hourPattern; + switch (i) { + case kGMTNegativeHMS: + hourPattern = &(fSymbols->fGmtHourFormats[DateFormatSymbols::GMT_NEGATIVE_HMS]); + break; + case kGMTNegativeHM: + hourPattern = &(fSymbols->fGmtHourFormats[DateFormatSymbols::GMT_NEGATIVE_HM]); + break; + case kGMTPositiveHMS: + hourPattern = &(fSymbols->fGmtHourFormats[DateFormatSymbols::GMT_POSITIVE_HMS]); + break; + case kGMTPositiveHM: + hourPattern = &(fSymbols->fGmtHourFormats[DateFormatSymbols::GMT_POSITIVE_HM]); + break; + } + fGMTFormatters[i] = new MessageFormat(fSymbols->fGmtFormat, status); + if (U_FAILURE(status)) { + break; + } + SimpleDateFormat *sdf = (SimpleDateFormat*)this->clone(); + sdf->adoptTimeZone(TimeZone::createTimeZone(UnicodeString(gEtcUTC))); + sdf->applyPattern(*hourPattern); + fGMTFormatters[i]->adoptFormat(0, sdf); + } + } else { + status = U_MEMORY_ALLOCATION_ERROR; + } + } + umtx_unlock(&LOCK); } //--------------------------------------------------------------------- @@ -775,145 +1125,57 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, // then the time zone shows up as "GMT+hh:mm" or "GMT-hh:mm" (where "hh:mm" is the // offset from GMT) regardless of how many z's were in the pattern symbol case UDAT_TIMEZONE_FIELD: + case UDAT_TIMEZONE_GENERIC_FIELD: case UDAT_TIMEZONE_SPECIAL_FIELD: - case UDAT_TIMEZONE_GENERIC_FIELD: { - UnicodeString str; - UnicodeString zid; - UnicodeString mzid; - UnicodeString displayString; - zid = fSymbols->getZoneID(cal.getTimeZone().getID(str), zid, status); - if(U_FAILURE(status)){ - break; - } - if (zid.length() == 0) { - appendGMT(appendTo, cal, status); - } - else { - zoneIDCanonicalize(zid); - if (patternCharIndex == UDAT_TIMEZONE_GENERIC_FIELD) { - if(count < 4){ - if (cal.getTimeZone().useDaylightTime()) { - fSymbols->getZoneString(zid, DateFormatSymbols::TIMEZONE_SHORT_GENERIC, displayString, status); - if ( !fSymbols->isCommonlyUsed(zid)) { - displayString.remove(); - } - if(displayString.length()==0) { - mzid = fSymbols->getMetazoneString(zid, DateFormatSymbols::TIMEZONE_SHORT_GENERIC, cal, displayString, status); - if ( !fSymbols->isCommonlyUsed(mzid)) { - displayString.remove(); - } - } - } - else { - fSymbols->getZoneString(zid, DateFormatSymbols::TIMEZONE_SHORT_STANDARD, displayString, status); - if ( !fSymbols->isCommonlyUsed(zid)) { - displayString.remove(); - } - if(displayString.length()==0) { - mzid = fSymbols->getMetazoneString(zid, DateFormatSymbols::TIMEZONE_SHORT_STANDARD, cal, displayString, status); - if ( !fSymbols->isCommonlyUsed(mzid)) { - displayString.remove(); - } - } - } - if(displayString.length()==0) { - fSymbols->getFallbackString(zid, displayString, status); - } - }else{ - if (cal.getTimeZone().useDaylightTime()) { - fSymbols->getZoneString(zid, DateFormatSymbols::TIMEZONE_LONG_GENERIC, displayString, status); - if(displayString.length()==0) { - mzid = fSymbols->getMetazoneString(zid, DateFormatSymbols::TIMEZONE_LONG_GENERIC, cal, displayString, status); - } + { + UnicodeString zoneString; + const ZoneStringFormat *zsf = fSymbols->getZoneStringFormat(); + if (zsf) { + if (patternCharIndex == UDAT_TIMEZONE_FIELD) { + if (count < 4) { + // "z", "zz", "zzz" + zsf->getSpecificShortString(cal, TRUE /*commonly used only*/, + zoneString, status); + } else { + // "zzzz" + zsf->getSpecificLongString(cal, zoneString, status); } - else { - fSymbols->getZoneString(zid, DateFormatSymbols::TIMEZONE_LONG_STANDARD, displayString, status); - if(displayString.length()==0) { - mzid = fSymbols->getMetazoneString(zid, DateFormatSymbols::TIMEZONE_LONG_STANDARD, cal, displayString, status); - } + } else if (patternCharIndex == UDAT_TIMEZONE_GENERIC_FIELD) { + if (count == 1) { + // "v" + zsf->getGenericShortString(cal, TRUE /*commonly used only*/, + zoneString, status); + } else if (count == 4) { + // "vvvv" + zsf->getGenericLongString(cal, zoneString, status); } - - if(displayString.length()==0) { - fSymbols->getFallbackString(zid, displayString, status); + } else { // patternCharIndex == UDAT_TIMEZONE_SPECIAL_FIELD + if (count == 1) { + // "V" + zsf->getSpecificShortString(cal, FALSE /*ignore commonly used*/, + zoneString, status); + } else if (count == 4) { + // "VVVV" + zsf->getGenericLocationString(cal, zoneString, status); } } } - else if (patternCharIndex == UDAT_TIMEZONE_SPECIAL_FIELD) { - if(count == 4){ // VVVV format - always get fallback string. - fSymbols->getFallbackString(zid, displayString, status); - } - else if (count == 1){ // V format - ignore commonlyUsed - if (cal.get(UCAL_DST_OFFSET, status) != 0) { - fSymbols->getZoneString(zid, DateFormatSymbols::TIMEZONE_SHORT_DAYLIGHT, displayString, status); - if(displayString.length()==0) { - mzid = fSymbols->getMetazoneString(zid, DateFormatSymbols::TIMEZONE_SHORT_DAYLIGHT, cal, displayString, status); - } - } - else { - fSymbols->getZoneString(zid, DateFormatSymbols::TIMEZONE_SHORT_STANDARD, displayString, status); - if(displayString.length()==0) { - mzid = fSymbols->getMetazoneString(zid, DateFormatSymbols::TIMEZONE_SHORT_STANDARD, cal, displayString, status); - } - } - } - } else { - if (cal.get(UCAL_DST_OFFSET, status) != 0) { - if(count < 4){ - fSymbols->getZoneString(zid, DateFormatSymbols::TIMEZONE_SHORT_DAYLIGHT, displayString, status); - if ( fSymbols->isCommonlyUsed(zid) == FALSE ) { - displayString.remove(); - } - if(displayString.length()==0) { - mzid = fSymbols->getMetazoneString(zid, DateFormatSymbols::TIMEZONE_SHORT_DAYLIGHT, cal, displayString, status); - if ( fSymbols->isCommonlyUsed(mzid) == FALSE ) { - displayString.remove(); - } - } - }else{ - fSymbols->getZoneString(zid, DateFormatSymbols::TIMEZONE_LONG_DAYLIGHT, displayString, status); - if(displayString.length()==0) - fSymbols->getMetazoneString(zid, DateFormatSymbols::TIMEZONE_LONG_DAYLIGHT, cal, displayString, status); - } - }else{ - if(count < 4){ - fSymbols->getZoneString(zid, DateFormatSymbols::TIMEZONE_SHORT_STANDARD, displayString, status); - if ( !fSymbols->isCommonlyUsed(zid)) { - displayString.remove(); - } - if(displayString.length()==0) { - mzid = fSymbols->getMetazoneString(zid, DateFormatSymbols::TIMEZONE_SHORT_STANDARD, cal, displayString, status); - if ( fSymbols->isCommonlyUsed(mzid) == FALSE ) { - displayString.remove(); - } - } - }else{ - fSymbols->getZoneString(zid, DateFormatSymbols::TIMEZONE_LONG_STANDARD, displayString, status); - if(displayString.length()==0) - fSymbols->getMetazoneString(zid, DateFormatSymbols::TIMEZONE_LONG_STANDARD, cal, displayString, status); - } - } - } - if(displayString.length()==0){ + if (zoneString.isEmpty()) { appendGMT(appendTo, cal, status); - }else{ - appendTo += displayString; + } else { + appendTo += zoneString; } } - } - break; - - case 23: // 'Z' - TIMEZONE_RFC - { - UChar sign = 43/*'+'*/; - value = (cal.get(UCAL_ZONE_OFFSET, status) + - cal.get(UCAL_DST_OFFSET, status)) / U_MILLIS_PER_MINUTE; - if (value < 0) { - value = -value; - sign = 45/*'-'*/; - } - value = (value / 60) * 100 + (value % 60); // minutes => KKmm - appendTo += sign; - zeroPaddingNumber(appendTo, value, 4, 4); + break; + + case UDAT_TIMEZONE_RFC_FIELD: // 'Z' - TIMEZONE_RFC + if (count < 4) { + // RFC822 format, must use ASCII digits + value = (cal.get(UCAL_ZONE_OFFSET, status) + cal.get(UCAL_DST_OFFSET, status)); + formatRFC822TZ(appendTo, value); + } else { + // long form, localized GMT pattern + appendGMT(appendTo, cal, status); } break; @@ -955,88 +1217,8 @@ 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; - } - else { - status = U_ZERO_ERROR; - } - - // 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)) { - break; - } - - 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, -1, US_INV)); - 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); -} //---------------------------------------------------------------------- - void SimpleDateFormat::zeroPaddingNumber(UnicodeString &appendTo, int32_t value, int32_t minDigits, int32_t maxDigits) const { @@ -1073,9 +1255,8 @@ SimpleDateFormat::parse(const UnicodeString& text, Calendar& cal, ParsePosition& UBool ambiguousYear[] = { FALSE }; int32_t count = 0; - // hack, clear parsedTimeZone, cast away const - delete parsedTimeZone; - ((SimpleDateFormat*)this)->parsedTimeZone = NULL; + // hack, reset tztype, cast away const + ((SimpleDateFormat*)this)->tztype = TZTYPE_UNK; // For parsing abutting numeric fields. 'abutPat' is the // offset into 'pattern' of the first of 2 or more abutting @@ -1271,39 +1452,127 @@ SimpleDateFormat::parse(const UnicodeString& text, Calendar& cal, ParsePosition& // front or the back of the default century. This only works because we adjust // the year correctly to start with in other cases -- see subParse(). UErrorCode status = U_ZERO_ERROR; - if (ambiguousYear[0] || parsedTimeZone != NULL) // If this is true then the two-digit year == the default start year + if (ambiguousYear[0] || tztype != TZTYPE_UNK) // If this is true then the two-digit year == the default start year { // We need a copy of the fields, and we need to avoid triggering a call to // complete(), which will recalculate the fields. Since we can't access // the fields[] array in Calendar, we clone the entire object. This will // stop working if Calendar.clone() is ever rewritten to call complete(). - Calendar *copy = cal.clone(); + Calendar *copy; if (ambiguousYear[0]) { + copy = cal.clone(); UDate parsedDate = copy->getTime(status); // {sfb} check internalGetDefaultCenturyStart if (fHaveDefaultCentury && (parsedDate < fDefaultCenturyStart)) { // We can't use add here because that does a complete() first. cal.set(UCAL_YEAR, fDefaultCenturyStartYear + 100); } + delete copy; } - if (parsedTimeZone != NULL) { - TimeZone *tz = parsedTimeZone; + if (tztype != TZTYPE_UNK) { + copy = cal.clone(); + const TimeZone & tz = cal.getTimeZone(); + BasicTimeZone *btz = NULL; - // the calendar represents the parse as gmt time - // we need to turn this into local time, so we add the raw offset - // then we ask the timezone to handle this local time - int32_t rawOffset = 0; - int32_t dstOffset = 0; - tz->getOffset(copy->getTime(status)+tz->getRawOffset(), TRUE, - rawOffset, dstOffset, status); - if (U_SUCCESS(status)) { - cal.set(UCAL_ZONE_OFFSET, rawOffset); - cal.set(UCAL_DST_OFFSET, dstOffset); + if (tz.getDynamicClassID() == OlsonTimeZone::getStaticClassID() + || tz.getDynamicClassID() == SimpleTimeZone::getStaticClassID() + || tz.getDynamicClassID() == RuleBasedTimeZone::getStaticClassID() + || tz.getDynamicClassID() == VTimeZone::getStaticClassID()) { + btz = (BasicTimeZone*)&tz; } - } - delete copy; + // Get local millis + copy->set(UCAL_ZONE_OFFSET, 0); + copy->set(UCAL_DST_OFFSET, 0); + UDate localMillis = copy->getTime(status); + + // Make sure parsed time zone type (Standard or Daylight) + // matches the rule used by the parsed time zone. + int32_t raw, dst; + if (btz != NULL) { + if (tztype == TZTYPE_STD) { + btz->getOffsetFromLocal(localMillis, + BasicTimeZone::kStandard, BasicTimeZone::kStandard, raw, dst, status); + } else { + btz->getOffsetFromLocal(localMillis, + BasicTimeZone::kDaylight, BasicTimeZone::kDaylight, raw, dst, status); + } + } else { + // No good way to resolve ambiguous time at transition, + // but following code work in most case. + tz.getOffset(localMillis, TRUE, raw, dst, status); + } + + // Now, compare the results with parsed type, either standard or daylight saving time + int32_t resolvedSavings = dst; + if (tztype == TZTYPE_STD) { + if (dst != 0) { + // Override DST_OFFSET = 0 in the result calendar + resolvedSavings = 0; + } + } else { // tztype == TZTYPE_DST + if (dst == 0) { + if (btz != NULL) { + UDate time = localMillis + raw; + // We use the nearest daylight saving time rule. + TimeZoneTransition beforeTrs, afterTrs; + UDate beforeT = time, afterT = time; + int32_t beforeSav = 0, afterSav = 0; + UBool beforeTrsAvail, afterTrsAvail; + + // Search for DST rule before or on the time + while (TRUE) { + beforeTrsAvail = btz->getPreviousTransition(beforeT, TRUE, beforeTrs); + if (!beforeTrsAvail) { + break; + } + beforeT = beforeTrs.getTime() - 1; + beforeSav = beforeTrs.getFrom()->getDSTSavings(); + if (beforeSav != 0) { + break; + } + } + + // Search for DST rule after the time + while (TRUE) { + afterTrsAvail = btz->getNextTransition(afterT, FALSE, afterTrs); + if (!afterTrsAvail) { + break; + } + afterT = afterTrs.getTime(); + afterSav = afterTrs.getTo()->getDSTSavings(); + if (afterSav != 0) { + break; + } + } + + if (beforeTrsAvail && afterTrsAvail) { + if (time - beforeT > afterT - time) { + resolvedSavings = afterSav; + } else { + resolvedSavings = beforeSav; + } + } else if (beforeTrsAvail && beforeSav != 0) { + resolvedSavings = beforeSav; + } else if (afterTrsAvail && afterSav != 0) { + resolvedSavings = afterSav; + } else { + resolvedSavings = btz->getDSTSavings(); + } + } else { + resolvedSavings = tz.getDSTSavings(); + } + if (resolvedSavings == 0) { + // final fallback + resolvedSavings = U_MILLIS_PER_HOUR; + } + } + } + cal.set(UCAL_ZONE_OFFSET, raw); + cal.set(UCAL_DST_OFFSET, resolvedSavings); + delete copy; + } } // If any Calendar calls failed, we pretend that we @@ -1785,125 +2054,151 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC case UDAT_TIMEZONE_FIELD: case UDAT_TIMEZONE_RFC_FIELD: case UDAT_TIMEZONE_GENERIC_FIELD: + case UDAT_TIMEZONE_SPECIAL_FIELD: { - // First try to parse generic forms such as GMT-07:00. Do this first - // in case localized DateFormatZoneData contains the string "GMT" - // for a zone; in that case, we don't want to match the first three - // characters of GMT+/-HH:MM etc. - - int32_t sign = 0; - int32_t offset; - int32_t gmtLen = u_strlen(gGmt); - - // For time zones that have no known names, look for strings - // of the form: - // GMT[+-]hours:minutes or - // GMT[+-]hhmm or - // GMT. - - if ((text.length() - start) >= gmtLen && - (text.caseCompare(start, gmtLen, gGmt, 0, gmtLen, U_FOLD_CASE_DEFAULT)) == 0) - { - cal.set(UCAL_DST_OFFSET, 0); + int32_t offset = 0; + UBool parsed = FALSE; + + // Step 1 + // Check if this is a long GMT offset string (either localized or default) + offset = parseGMT(text, pos); + if (pos.getIndex() - start > 0) { + parsed = TRUE; + } + if (!parsed) { + // Step 2 + // Check if this is an RFC822 time zone offset. + // ICU supports the standard RFC822 format [+|-]HHmm + // and its extended form [+|-]HHmmSS. + do { + int32_t sign = 0; + UChar signChar = text.charAt(start); + if (signChar == (UChar)0x002B /* '+' */) { + sign = 1; + } else if (signChar == (UChar)0x002D /* '-' */) { + sign = -1; + } else { + // Not an RFC822 offset string + break; + } - pos.setIndex(start + gmtLen); + // Parse digits + int32_t orgPos = start + 1; + pos.setIndex(orgPos); + parseInt(text, number, 6, pos, FALSE); + int32_t numLen = pos.getIndex() - orgPos; + if (numLen <= 0) { + break; + } - if( text[pos.getIndex()] == 0x002B /*'+'*/ ) - sign = 1; - else if( text[pos.getIndex()] == 0x002D /*'-'*/ ) - sign = -1; - else { - cal.set(UCAL_ZONE_OFFSET, 0 ); - return pos.getIndex(); - } + // Followings are possible format (excluding sign char) + // HHmmSS + // HmmSS + // HHmm + // Hmm + // HH + // H + int32_t val = number.getLong(); + int32_t hour = 0, min = 0, sec = 0; + switch(numLen) { + case 1: // H + case 2: // HH + hour = val; + break; + case 3: // Hmm + case 4: // HHmm + hour = val / 100; + min = val % 100; + break; + case 5: // Hmmss + case 6: // HHmmss + hour = val / 10000; + min = (val % 10000) / 100; + sec = val % 100; + break; + } + if (hour > 23 || min > 59 || sec > 59) { + // Invalid value range + break; + } + offset = (((hour * 60) + min) * 60 + sec) * 1000 * sign; + parsed = TRUE; + } while (FALSE); - // Look for hours:minutes or hhmm. - pos.setIndex(pos.getIndex() + 1); - int32_t parseStart = pos.getIndex(); - Formattable tzNumber; - fNumberFormat->parse(text, tzNumber, pos); - if( pos.getIndex() == parseStart) { - return -start; - } - if( text[pos.getIndex()] == 0x003A /*':'*/ ) { - // This is the hours:minutes case - offset = tzNumber.getLong() * 60; - pos.setIndex(pos.getIndex() + 1); - parseStart = pos.getIndex(); - fNumberFormat->parse(text, tzNumber, pos); - if( pos.getIndex() == parseStart) { - return -start; - } - offset += tzNumber.getLong(); + if (!parsed) { + // Failed to parse. Reset the position. + pos.setIndex(start); + } } - else { - // This is the hhmm case. - offset = tzNumber.getLong(); - if( offset < 24 ) - offset *= 60; - else - offset = offset % 100 + offset / 100 * 60; + + if (parsed) { + // offset was successfully parsed as either a long GMT string or RFC822 zone offset + // string. Create normalized zone ID for the offset. + + UnicodeString tzID(gGmt); + formatRFC822TZ(tzID, offset); + //TimeZone *customTZ = TimeZone::createTimeZone(tzID); + TimeZone *customTZ = new SimpleTimeZone(offset, tzID); // faster than TimeZone::createTimeZone + cal.adoptTimeZone(customTZ); + + return pos.getIndex(); } - // Fall through for final processing below of 'offset' and 'sign'. - } - else { + // Step 3 // At this point, check for named time zones by looking through // the locale data from the DateFormatZoneData strings. // Want to be able to parse both short and long forms. - // !!! side effect, might set parsedZoneString - UErrorCode status = U_ZERO_ERROR; - int32_t result = subParseZoneString(text, start, cal, status); - if (result != 0) { - return result; - } - - // As a last resort, look for numeric timezones of the form - // [+-]hhmm as specified by RFC 822. This code is actually - // a little more permissive than RFC 822. It will try to do - // its best with numbers that aren't strictly 4 digits long - DecimalFormat fmt(UNICODE_STRING_SIMPLE("+####;-####"), status); - if(U_FAILURE(status)) - return -start; - fmt.setParseIntegerOnly(TRUE); - int32_t parseStart = pos.getIndex(); - Formattable tzNumber; - fmt.parse( text, tzNumber, pos ); - if( pos.getIndex() == parseStart) { - return -start; // Wasn't actually a number. - } - offset = tzNumber.getLong(); - sign = 1; - if( offset < 0 ) { - sign = -1; - offset = -offset; - } - if( offset < 24 ) - offset = offset * 60; - else - offset = offset % 100 + offset / 100 * 60; - - // Fall through for final processing below of 'offset' and 'sign'. - } - - // Do the final processing for both of the above cases. We only - // arrive here if the form GMT+/-... or an RFC 822 form was seen. - if (sign != 0) - { - offset *= U_MILLIS_PER_MINUTE * sign; + // optimize for calendar's current time zone + const ZoneStringFormat *zsf = fSymbols->getZoneStringFormat(); + if (zsf) { + UErrorCode status = U_ZERO_ERROR; + const ZoneStringInfo *zsinfo = NULL; + int32_t matchLen; + + switch (patternCharIndex) { + case UDAT_TIMEZONE_FIELD: // 'z' + if (count < 4) { + zsinfo = zsf->findSpecificShort(text, start, matchLen, status); + } else { + zsinfo = zsf->findSpecificLong(text, start, matchLen, status); + } + break; + case UDAT_TIMEZONE_GENERIC_FIELD: // 'v' + if (count == 1) { + zsinfo = zsf->findGenericShort(text, start, matchLen, status); + } else if (count == 4) { + zsinfo = zsf->findGenericLong(text, start, matchLen, status); + } + break; + case UDAT_TIMEZONE_SPECIAL_FIELD: // 'V' + if (count == 1) { + zsinfo = zsf->findSpecificShort(text, start, matchLen, status); + } else if (count == 4) { + zsinfo = zsf->findGenericLocation(text, start, matchLen, status); + } + break; + } - if (cal.getTimeZone().useDaylightTime()) - { - cal.set(UCAL_DST_OFFSET, U_MILLIS_PER_HOUR); - offset -= U_MILLIS_PER_HOUR; + if (U_SUCCESS(status) && zsinfo != NULL) { + if (zsinfo->isStandard()) { + ((SimpleDateFormat*)this)->tztype = TZTYPE_STD; + } else if (zsinfo->isDaylight()) { + ((SimpleDateFormat*)this)->tztype = TZTYPE_DST; + } + UnicodeString tzid; + zsinfo->getID(tzid); + + UnicodeString current; + cal.getTimeZone().getID(current); + if (tzid != current) { + TimeZone *tz = TimeZone::createTimeZone(tzid); + cal.adoptTimeZone(tz); + } + return start + matchLen; + } } - cal.set(UCAL_ZONE_OFFSET, offset); - - return pos.getIndex(); - } - - // All efforts to parse a zone failed. - return -start; + // complete failure + return -start; } default: @@ -1928,61 +2223,6 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC } } -int32_t -SimpleDateFormat::subParseZoneString(const UnicodeString& text, int32_t start, Calendar& cal, UErrorCode& status) const -{ - // At this point, check for named time zones by looking through - // the locale data from the DateFormatZoneData strings. - // Want to be able to parse both short and long forms. - - // optimize for calendar's current time zone - TimeZone *tz = NULL; - UnicodeString id; - UnicodeString zid, value; - DateFormatSymbols::TimeZoneTranslationType type = DateFormatSymbols::TIMEZONE_COUNT; - fSymbols->getZoneID(getTimeZone().getID(id), zid, status); - if(zid.length() > 0){ - fSymbols->findZoneIDTypeValue(zid, text, start, type, value, status); - if(type != DateFormatSymbols::TIMEZONE_COUNT) { - tz = TimeZone::createTimeZone(zid); - } - } - - if(U_FAILURE(status)){ - return 0; - } - if (tz != NULL) { // Matched any ? - // always set zone offset, needed to get correct hour in wall time - // when checking daylight savings - cal.set(UCAL_ZONE_OFFSET, tz->getRawOffset()); - if (type==DateFormatSymbols::TIMEZONE_SHORT_STANDARD || type==DateFormatSymbols::TIMEZONE_LONG_STANDARD ) { - // standard time - cal.set(UCAL_DST_OFFSET, 0); - delete tz; tz = NULL; - } else if (type==DateFormatSymbols::TIMEZONE_SHORT_DAYLIGHT || type==DateFormatSymbols::TIMEZONE_LONG_DAYLIGHT ) { - // daylight time - // !!! todo - no getDSTSavings() in ICU's timezone - // use the correct DST SAVINGS for the zone. - // cal.set(UCAL_DST_OFFSET, tz->getDSTSavings()); - cal.set(UCAL_DST_OFFSET, U_MILLIS_PER_HOUR); - delete tz; tz = NULL; - } else { - // either standard or daylight - // need to finish getting the date, then compute dst offset as appropriate - - // !!! hack for api compatibility, can't modify subParse(...) so can't - // pass this back any other way. cast away const. - ((SimpleDateFormat*)this)->parsedTimeZone = tz; - } - - return start + value.length(); - } - - - // complete failure - return 0; -} - /** * Parse an integer using fNumberFormat. This method is semantically * const, but actually may modify fNumberFormat. @@ -1991,6 +2231,17 @@ void SimpleDateFormat::parseInt(const UnicodeString& text, Formattable& number, ParsePosition& pos, UBool allowNegative) const { + parseInt(text, number, -1, pos, allowNegative); +} + +/** + * Parse an integer using fNumberFormat up to maxDigits. + */ +void SimpleDateFormat::parseInt(const UnicodeString& text, + Formattable& number, + int32_t maxDigits, + ParsePosition& pos, + UBool allowNegative) const { UnicodeString oldPrefix; DecimalFormat* df = NULL; if (!allowNegative && @@ -1999,10 +2250,27 @@ void SimpleDateFormat::parseInt(const UnicodeString& text, df->getNegativePrefix(oldPrefix); df->setNegativePrefix(SUPPRESS_NEGATIVE_PREFIX); } + int32_t oldPos = pos.getIndex(); fNumberFormat->parse(text, number, pos); if (df != NULL) { df->setNegativePrefix(oldPrefix); } + + if (maxDigits > 0) { + // adjust the result to fit into + // the maxDigits and move the position back + int32_t nDigits = pos.getIndex() - oldPos; + if (nDigits > maxDigits) { + int32_t val = number.getLong(); + nDigits -= maxDigits; + while (nDigits > 0) { + val /= 10; + nDigits--; + } + pos.setIndex(oldPos + maxDigits); + number.setLong(val); + } + } } //---------------------------------------------------------------------- diff --git a/i18n/timezone.cpp b/i18n/timezone.cpp index fc997ed0..56656117 100644 --- a/i18n/timezone.cpp +++ b/i18n/timezone.cpp @@ -88,6 +88,7 @@ static char gStrBuf[256]; #define kDEFAULT "Default" #define kMAX_CUSTOM_HOUR 23 #define kMAX_CUSTOM_MIN 59 +#define kMAX_CUSTOM_SEC 59 #define MINUS 0x002D #define PLUS 0x002B #define ZERO_DIGIT 0x0030 @@ -100,6 +101,7 @@ static const UChar ZZZZ_STR[] = {0x7A, 0x7A, 0x7A, 0x7A, 0x00}; /* "zzzz static const int32_t GMT_ID_LENGTH = 3; static UMTX LOCK; +static UMTX TZSET_LOCK; static U_NAMESPACE_QUALIFIER TimeZone* DEFAULT_ZONE = NULL; static U_NAMESPACE_QUALIFIER TimeZone* _GMT = NULL; // cf. TimeZone::GMT @@ -129,6 +131,10 @@ static UBool U_CALLCONV timeZone_cleanup(void) umtx_destroy(&LOCK); LOCK = NULL; } + if (TZSET_LOCK) { + umtx_destroy(&TZSET_LOCK); + TZSET_LOCK = NULL; + } return TRUE; } @@ -511,12 +517,19 @@ TimeZone::initDefault() // First, try to create a system timezone, based // on the string ID in tzname[0]. { - // NOTE: Global mutex here; TimeZone mutex above - // mutexed to avoid threading issues in the platform fcns. - // Some of the locale/timezone OS functions may not be thread safe, - // so the intent is that any setting from anywhere within ICU - // happens with the ICU global mutex held. - Mutex lock; + // NOTE: Local mutex here. TimeZone mutex below + // mutexed to avoid threading issues in the platform functions. + // Some of the locale/timezone OS functions may not be thread safe, + // so the intent is that any setting from anywhere within ICU + // happens while the ICU mutex is held. + // The operating system might actually use ICU to implement timezones. + // So we may have ICU calling ICU here, like on AIX. + // In order to prevent a double lock of a non-reentrant mutex in a + // different part of ICU, we use TZSET_LOCK to allow only one instance + // of ICU to query these thread unsafe OS functions at any given time. + Mutex lock(&TZSET_LOCK); + + ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONE, timeZone_cleanup); uprv_tzset(); // Initialize tz... system data // Get the timezone ID from the host. This function should do @@ -529,6 +542,13 @@ TimeZone::initDefault() rawOffset = uprv_timezone() * -U_MILLIS_PER_SECOND; } + UBool initialized; + UMTX_CHECK(&LOCK, (DEFAULT_ZONE != NULL), initialized); + if (initialized) { + /* Hrmph? Either a race condition happened, or tzset initialized ICU. */ + return; + } + TimeZone* default_zone = NULL; /* Make sure that the string is NULL terminated to prevent BoundsChecker/Purify warnings. */ @@ -617,13 +637,13 @@ TimeZone::createDefault() { /* This is here to prevent race conditions. */ UBool needsInit; - UMTX_CHECK(&LOCK, (DEFAULT_ZONE == 0), needsInit); + UMTX_CHECK(&LOCK, (DEFAULT_ZONE == NULL), needsInit); if (needsInit) { initDefault(); } Mutex lock(&LOCK); // In case adoptDefault is called - return DEFAULT_ZONE->clone(); + return (DEFAULT_ZONE != NULL) ? DEFAULT_ZONE->clone() : NULL; } // ------------------------------------- @@ -668,36 +688,40 @@ void TimeZone::getOffset(UDate date, UBool local, int32_t& rawOffset, } rawOffset = getRawOffset(); - - // Convert to local wall millis if necessary if (!local) { date += rawOffset; // now in local standard millis } - // When local==FALSE, we might have to recompute. This loop is - // executed once, unless a recomputation is required; then it is - // executed twice. + // When local == TRUE, date might not be in local standard + // millis. getOffset taking 7 parameters used here assume + // the given time in day is local standard time. + // At STD->DST transition, there is a range of time which + // does not exist. When 'date' is in this time range + // (and local == TRUE), this method interprets the specified + // local time as DST. At DST->STD transition, there is a + // range of time which occurs twice. In this case, this + // method interprets the specified local time as STD. + // To support the behavior above, we need to call getOffset + // (with 7 args) twice when local == true and DST is + // detected in the initial call. for (int32_t pass=0; ; ++pass) { int32_t year, month, dom, dow; double day = uprv_floor(date / U_MILLIS_PER_DAY); int32_t millis = (int32_t) (date - day * U_MILLIS_PER_DAY); - + Grego::dayToFields(day, year, month, dom, dow); - + dstOffset = getOffset(GregorianCalendar::AD, year, month, dom, (uint8_t) dow, millis, Grego::monthLength(year, month), ec) - rawOffset; - // Recompute if local==FALSE, dstOffset!=0, and addition of - // the dstOffset puts us in a different day. - if (pass!=0 || local || dstOffset==0) { - break; - } - date += dstOffset; - if (uprv_floor(date / U_MILLIS_PER_DAY) == day) { + // Recompute if local==TRUE, dstOffset!=0. + if (pass!=0 || !local || dstOffset == 0) { break; } + // adjust to local standard millis + date -= dstOffset; } } @@ -1074,6 +1098,33 @@ TimeZone::getEquivalentID(const UnicodeString& id, int32_t index) { // --------------------------------------- +UnicodeString& +TimeZone::getOlsonCanonicalID(const UnicodeString &id, UnicodeString &canonical) { + UErrorCode ec = U_ZERO_ERROR; + canonical.remove(); + UResourceBundle *top = ures_openDirect(0, kZONEINFO, &ec); + UResourceBundle *res = getZoneByName(top, id, NULL, ec); + if (U_SUCCESS(ec)) { + if (ures_getSize(res) == 1) { + int32_t deref = ures_getInt(res, &ec); + UResourceBundle *nres = ures_getByKey(top, kNAMES, NULL, &ec); // dereference Names section + int32_t len; + const UChar* tmp = ures_getStringByIndex(nres, deref, &len, &ec); + if (U_SUCCESS(ec)) { + canonical.setTo(tmp, len); + } + ures_close(nres); + } else { + canonical.setTo(id); + } + } + ures_close(res); + ures_close(top); + return canonical; +} + +// --------------------------------------- + UnicodeString& TimeZone::getDisplayName(UnicodeString& result) const @@ -1173,11 +1224,12 @@ TimeZone::createCustomTimeZone(const UnicodeString& id) UBool negative = FALSE; int32_t hour = 0; int32_t min = 0; + int32_t sec = 0; if (id[pos.getIndex()] == MINUS /*'-'*/) negative = TRUE; else if (id[pos.getIndex()] != PLUS /*'+'*/) - return 0; + return NULL; pos.setIndex(pos.getIndex() + 1); UErrorCode success = U_ZERO_ERROR; @@ -1196,75 +1248,125 @@ TimeZone::createCustomTimeZone(const UnicodeString& id) numberFormat->parse(id, n, pos); if (pos.getIndex() == start) { delete numberFormat; - return 0; + return NULL; } hour = n.getLong(); - if (pos.getIndex() < id.length() && - id[pos.getIndex()] == 0x003A /*':'*/) - { + if (pos.getIndex() < id.length()) { + if (pos.getIndex() - start > 2 + || id[pos.getIndex()] != 0x003A /*':'*/) { + delete numberFormat; + return NULL; + } // hh:mm pos.setIndex(pos.getIndex() + 1); int32_t oldPos = pos.getIndex(); n.setLong(kParseFailed); numberFormat->parse(id, n, pos); - if (pos.getIndex() == oldPos) { + if ((pos.getIndex() - oldPos) != 2) { + // must be 2 digits delete numberFormat; - return 0; + return NULL; } min = n.getLong(); - } - else - { - // hhmm or hh - - // Be strict about interpreting something as hh; it must be - // an offset < 23, and it must be one or two digits. Thus - // 0010 is interpreted as 00:10, but 10 is interpreted as - // 10:00. - if (hour > kMAX_CUSTOM_HOUR || (pos.getIndex() - start) > 2) { - min = hour % 100; - hour /= 100; + if (pos.getIndex() < id.length()) { + if (id[pos.getIndex()] != 0x003A /*':'*/) { + delete numberFormat; + return NULL; + } + // [:ss] + pos.setIndex(pos.getIndex() + 1); + oldPos = pos.getIndex(); + n.setLong(kParseFailed); + numberFormat->parse(id, n, pos); + if (pos.getIndex() != id.length() + || (pos.getIndex() - oldPos) != 2) { + delete numberFormat; + return NULL; + } + sec = n.getLong(); + } + } else { + // Supported formats are below - + // + // HHmmss + // Hmmss + // HHmm + // Hmm + // HH + // H + + int32_t length = pos.getIndex() - start; + if (length <= 0 || 6 < length) { + // invalid length + delete numberFormat; + return NULL; + } + switch (length) { + case 1: + case 2: + // already set to hour + break; + case 3: + case 4: + min = hour % 100; + hour /= 100; + break; + case 5: + case 6: + sec = hour % 100; + min = (hour/100) % 100; + hour /= 10000; + break; } } delete numberFormat; - if (hour > kMAX_CUSTOM_HOUR || min > kMAX_CUSTOM_MIN) { + if (hour > kMAX_CUSTOM_HOUR || min > kMAX_CUSTOM_MIN || sec > kMAX_CUSTOM_SEC) { return 0; } - // Create time zone ID in RFC822 format - GMT[+|-]hhmm - UnicodeString tzRFC(GMT_ID); - if (hour|min) { + // Create time zone ID - GMT[+|-]hhmm[ss] + UnicodeString tzID(GMT_ID); + if (hour | min | sec) { if (negative) { - tzRFC += (UChar)MINUS; + tzID += (UChar)MINUS; } else { - tzRFC += (UChar)PLUS; + tzID += (UChar)PLUS; } if (hour < 10) { - tzRFC += (UChar)ZERO_DIGIT; + tzID += (UChar)ZERO_DIGIT; } else { - tzRFC += (UChar)(ZERO_DIGIT + hour/10); + tzID += (UChar)(ZERO_DIGIT + hour/10); } - tzRFC += (UChar)(ZERO_DIGIT + hour%10); + tzID += (UChar)(ZERO_DIGIT + hour%10); if (min < 10) { - tzRFC += (UChar)ZERO_DIGIT; + tzID += (UChar)ZERO_DIGIT; } else { - tzRFC += (UChar)(ZERO_DIGIT + min/10); + tzID += (UChar)(ZERO_DIGIT + min/10); + } + tzID += (UChar)(ZERO_DIGIT + min%10); + + if (sec) { + if (sec < 10) { + tzID += (UChar)ZERO_DIGIT; + } else { + tzID += (UChar)(ZERO_DIGIT + sec/10); + } + tzID += (UChar)(ZERO_DIGIT + sec%10); } - tzRFC += (UChar)(ZERO_DIGIT + min%10); } - int32_t offset = (hour * 60 + min) * 60 * 1000; - if(negative) { + int32_t offset = ((hour * 60 + min) * 60 + sec) * 1000; + if (negative) { offset = -offset; } - return new SimpleTimeZone(offset, tzRFC); + return new SimpleTimeZone(offset, tzID); } - return 0; + return NULL; } diff --git a/i18n/ucln_in.h b/i18n/ucln_in.h index 6feae009..66138191 100644 --- a/i18n/ucln_in.h +++ b/i18n/ucln_in.h @@ -39,6 +39,8 @@ typedef enum ECleanupI18NType { UCLN_I18N_UCOL, UCLN_I18N_UCOL_BLD, UCLN_I18N_CSDET, + UCLN_I18N_ZONEMETA, + UCLN_I18N_ZSFORMAT, UCLN_I18N_COUNT /* This must be last */ } ECleanupI18NType; diff --git a/i18n/ucol.cpp b/i18n/ucol.cpp index 96726a25..9e443481 100644 --- a/i18n/ucol.cpp +++ b/i18n/ucol.cpp @@ -6054,6 +6054,8 @@ ucol_nextSortKeyPart(const UCollator *coll, level = UCOL_PSK_QUIN; break; } + if(CE==0) + continue; if(isShiftedCE(CE, LVT, &wasShifted)) { CE >>= 16; /* get primary */ if(CE != 0) { diff --git a/i18n/ucol_bld.cpp b/i18n/ucol_bld.cpp index 6d493b36..83f33f49 100644 --- a/i18n/ucol_bld.cpp +++ b/i18n/ucol_bld.cpp @@ -27,6 +27,7 @@ #include "ucln_in.h" #include "umutex.h" #include "unicode/uniset.h" +#include "unormimp.h" static const InverseUCATableHeader* _staticInvUCA = NULL; static UDataMemory* invUCA_DATA_MEM = NULL; @@ -825,6 +826,7 @@ U_CFUNC void ucol_createElements(UColTokenParser *src, tempUCATable *t, UColTokL UColToken *tok = lh->first; UColToken *expt = NULL; uint32_t i = 0, j = 0; + const uint16_t *fcdTrieData = unorm_getFCDTrie(status); while(tok != NULL && U_SUCCESS(*status)) { /* first, check if there are any expansions */ @@ -912,10 +914,25 @@ U_CFUNC void ucol_createElements(UColTokenParser *src, tempUCATable *t, UColTokL uprv_memcpy(el.uchars, (tok->source & 0x00FFFFFF) + src->source, el.cSize*sizeof(UChar)); } if(src->UCA != NULL) { + UBool containCombinMarks = FALSE; for(i = 0; i<el.cSize; i++) { if(UCOL_ISJAMO(el.cPoints[i])) { t->image->jamoSpecial = TRUE; } + if ( !src->buildCCTabFlag ) { + // check combining class + int16_t fcd = unorm_getFCD16(fcdTrieData, el.cPoints[i]); + if ( (fcd && 0xff) == 0 ) { + // reset flag when current char is not combining mark. + containCombinMarks = FALSE; + } + else { + containCombinMarks = TRUE; + } + } + } + if ( !src->buildCCTabFlag && containCombinMarks ) { + src->buildCCTabFlag = TRUE; } } @@ -1214,10 +1231,8 @@ UCATableHeader *ucol_assembleTailoringTable(UColTokenParser *src, UErrorCode *st // Add completely ignorable elements utrie_enum(&t->UCA->mapping, NULL, _processUCACompleteIgnorables, t); - - // canonical closure - uprv_uca_canonicalClosure(t, status); - + // add tailoring characters related canonical closures + uprv_uca_canonicalClosure(t, src, status); /* still need to produce compatibility closure */ diff --git a/i18n/ucol_elm.cpp b/i18n/ucol_elm.cpp index ecb7b5f9..929a4db8 100644 --- a/i18n/ucol_elm.cpp +++ b/i18n/ucol_elm.cpp @@ -1,7 +1,8 @@ + /* ******************************************************************************* * -* Copyright (C) 2001-2006, International Business Machines +* Copyright (C) 2001-2007, International Business Machines * Corporation and others. All Rights Reserved. * ******************************************************************************* @@ -32,6 +33,7 @@ #include "unicode/ucoleitr.h" #include "unicode/normlzr.h" #include "ucol_elm.h" +#include "ucol_tok.h" #include "unormimp.h" #include "unicode/caniter.h" #include "cmemory.h" @@ -203,6 +205,7 @@ uprv_uca_initTempTable(UCATableHeader *image, UColOptionSet *opts, const UCollat } uprv_memset(t->unsafeCP, 0, UCOL_UNSAFECP_TABLE_SIZE); uprv_memset(t->contrEndCP, 0, UCOL_UNSAFECP_TABLE_SIZE); + t->cmLookup = NULL; return t; allocation_failure: @@ -391,6 +394,11 @@ uprv_uca_closeTempTable(tempUCATable *t) { uprv_free(t->unsafeCP); uprv_free(t->contrEndCP); + + if (t->cmLookup != NULL) { + uprv_free(t->cmLookup->cPoints); + uprv_free(t->cmLookup); + } uprv_free(t); } @@ -675,25 +683,94 @@ static void unsafeCPSet(uint8_t *table, UChar c) { *htByte |= (1 << (hash & 7)); } +static void +uprv_uca_createCMTable(tempUCATable *t, int32_t noOfCM, UErrorCode *status) { + t->cmLookup = (CombinClassTable *)uprv_malloc(sizeof(CombinClassTable)); + if (t->cmLookup==NULL) { + *status = U_MEMORY_ALLOCATION_ERROR; + return; + } + t->cmLookup->cPoints=(UChar *)uprv_malloc(noOfCM*sizeof(UChar)); + if (t->cmLookup->cPoints ==NULL) { + uprv_free(t->cmLookup); + t->cmLookup = NULL; + *status = U_MEMORY_ALLOCATION_ERROR; + return; + } + + t->cmLookup->size=noOfCM; + uprv_memset(t->cmLookup->index, 0, sizeof(t->cmLookup->index)); + + return; +} + +static void +uprv_uca_copyCMTable(tempUCATable *t, UChar *cm, uint16_t *index) { + int32_t count=0; -/* to the UnsafeCP hash table, add all chars with combining class != 0 */ + for (int32_t i=0; i<256; ++i) { + if (index[i]>0) { + // cPoints is ordered by combining class value. + uprv_memcpy(t->cmLookup->cPoints+count, cm+(i<<8), index[i]*sizeof(UChar)); + count += index[i]; + } + t->cmLookup->index[i]=count; + } + return; +} + +/* 1. to the UnsafeCP hash table, add all chars with combining class != 0 */ +/* 2. build combining marks table for all chars with combining class != 0 */ static void uprv_uca_unsafeCPAddCCNZ(tempUCATable *t, UErrorCode *status) { UChar c; uint16_t fcd; // Hi byte is lead combining class. // lo byte is trailing combing class. const uint16_t *fcdTrieData; - + UBool buildCMTable = (t->cmLookup==NULL); // flag for building combining class table + UChar *cm=NULL; + uint16_t index[256]; + int32_t count=0; fcdTrieData = unorm_getFCDTrie(status); if (U_FAILURE(*status)) { return; } + if (buildCMTable) { + if (cm==NULL) { + cm = (UChar *)uprv_malloc(sizeof(UChar)*UCOL_MAX_CM_TAB); + if (cm==NULL) { + *status = U_MEMORY_ALLOCATION_ERROR; + return; + } + } + uprv_memset(index, 0, sizeof(index)); + } for (c=0; c<0xffff; c++) { fcd = unorm_getFCD16(fcdTrieData, c); if (fcd >= 0x100 || // if the leading combining class(c) > 0 || - (UTF_IS_LEAD(c) && fcd != 0)) // c is a leading surrogate with some FCD data + (UTF_IS_LEAD(c) && fcd != 0)) {// c is a leading surrogate with some FCD data + if (buildCMTable) { + uint32_t cClass = fcd & 0xff; + uint32_t temp=(cClass<<8)+index[cClass]; + cm[(cClass<<8)+index[cClass]] = c; // + index[cClass]++; + count++; + } unsafeCPSet(t->unsafeCP, c); + } + } + + // copy to cm table + if (buildCMTable) { + uprv_uca_createCMTable(t, count, status); + if(U_FAILURE(*status)) { + if (cm!=NULL) { + uprv_free(cm); + } + return; + } + uprv_uca_copyCMTable(t, cm, index); } if(t->prefixLookup != NULL) { @@ -705,21 +782,25 @@ static void uprv_uca_unsafeCPAddCCNZ(tempUCATable *t, UErrorCode *status) { while((e = uhash_nextElement(t->prefixLookup, &i)) != NULL) { element = (UCAElements *)e->value.pointer; // codepoints here are in the NFD form. We need to add the - // first code point of the NFC form to unsafe, because + // first code point of the NFC form to unsafe, because // strcoll needs to backup over them. NFCbufLen = unorm_normalize(element->cPoints, element->cSize, UNORM_NFC, 0, NFCbuf, 256, status); unsafeCPSet(t->unsafeCP, NFCbuf[0]); - } + } + } + + if (cm!=NULL) { + uprv_free(cm); } } -static uint32_t uprv_uca_addPrefix(tempUCATable *t, uint32_t CE, +static uint32_t uprv_uca_addPrefix(tempUCATable *t, uint32_t CE, UCAElements *element, UErrorCode *status) { // currently the longest prefix we're supporting in Japanese is two characters // long. Although this table could quite easily mimic complete contraction stuff - // there is no good reason to make a general solution, as it would require some + // there is no good reason to make a general solution, as it would require some // error prone messing. CntTable *contractions = t->contractions; UChar32 cp; @@ -744,7 +825,7 @@ static uint32_t uprv_uca_addPrefix(tempUCATable *t, uint32_t CE, #endif for (j = 1; j<element->prefixSize; j++) { /* First add NFD prefix chars to unsafe CP hash table */ - // Unless it is a trail surrogate, which is handled algoritmically and + // Unless it is a trail surrogate, which is handled algoritmically and // shouldn't take up space in the table. if(!(UTF_IS_TRAIL(element->prefix[j]))) { unsafeCPSet(t->unsafeCP, element->prefix[j]); @@ -784,7 +865,7 @@ static uint32_t uprv_uca_addPrefix(tempUCATable *t, uint32_t CE, element->cSize = element->prefixSize; // Add the last char of the contraction to the contraction-end hash table. - // unless it is a trail surrogate, which is handled algorithmically and + // unless it is a trail surrogate, which is handled algorithmically and // shouldn't be in the table if(!(UTF_IS_TRAIL(element->cPoints[element->cSize -1]))) { ContrEndCPSet(t->contrEndCP, element->cPoints[element->cSize -1]); @@ -793,7 +874,7 @@ static uint32_t uprv_uca_addPrefix(tempUCATable *t, uint32_t CE, // First we need to check if contractions starts with a surrogate UTF_NEXT_CHAR(element->cPoints, cpsize, element->cSize, cp); - // If there are any Jamos in the contraction, we should turn on special + // If there are any Jamos in the contraction, we should turn on special // processing for Jamos if(UCOL_ISJAMO(element->prefix[0])) { t->image->jamoSpecial = TRUE; @@ -801,7 +882,7 @@ static uint32_t uprv_uca_addPrefix(tempUCATable *t, uint32_t CE, /* then we need to deal with it */ /* we could aready have something in table - or we might not */ - if(!isPrefix(CE)) { + if(!isPrefix(CE)) { /* if it wasn't contraction, we wouldn't end up here*/ int32_t firstContractionOffset = 0; firstContractionOffset = uprv_cnttab_addContraction(contractions, UPRV_CNTTAB_NEWELEMENT, 0, CE, status); @@ -1557,45 +1638,348 @@ _enumCategoryRangeClosureCategory(const void *context, UChar32 start, UChar32 li } U_CDECL_END +static void +uprv_uca_setMapCE(tempUCATable *t, UCAElements *element, UErrorCode *status) { + uint32_t expansion = 0; + int32_t j; + + ExpansionTable *expansions = t->expansions; + if(element->noOfCEs == 2 // a two CE expansion + && isContinuation(element->CEs[1]) // which is a continuation + && (element->CEs[1] & (~(0xFF << 24 | UCOL_CONTINUATION_MARKER))) == 0 // that has only primaries in continuation, + && (((element->CEs[0]>>8) & 0xFF) == UCOL_BYTE_COMMON) // a common secondary + && ((element->CEs[0] & 0xFF) == UCOL_BYTE_COMMON) // and a common tertiary + ) { + element->mapCE = UCOL_SPECIAL_FLAG | (LONG_PRIMARY_TAG<<24) // a long primary special + | ((element->CEs[0]>>8) & 0xFFFF00) // first and second byte of primary + | ((element->CEs[1]>>24) & 0xFF); // third byte of primary + } else { + expansion = (uint32_t)(UCOL_SPECIAL_FLAG | (EXPANSION_TAG<<UCOL_TAG_SHIFT) + | ((uprv_uca_addExpansion(expansions, element->CEs[0], status)+(headersize>>2))<<4) + & 0xFFFFF0); + + for(j = 1; j<(int32_t)element->noOfCEs; j++) { + uprv_uca_addExpansion(expansions, element->CEs[j], status); + } + if(element->noOfCEs <= 0xF) { + expansion |= element->noOfCEs; + } else { + uprv_uca_addExpansion(expansions, 0, status); + } + element->mapCE = expansion; + uprv_uca_setMaxExpansion(element->CEs[element->noOfCEs - 1], + (uint8_t)element->noOfCEs, + t->maxExpansions, + status); + } +} + +static void +uprv_uca_addFCD4AccentedContractions(tempUCATable *t, + UCollationElements* colEl, + UChar *data, + int32_t len, + UCAElements *el, + UErrorCode *status) { + UChar decomp[256], comp[256]; + int32_t decLen, compLen; + + decLen = unorm_normalize(data, len, UNORM_NFD, 0, decomp, 256, status); + compLen = unorm_normalize(data, len, UNORM_NFC, 0, comp, 256, status); + decomp[decLen] = comp[compLen] = 0; + + el->cPoints = decomp; + el->cSize = decLen; + el->noOfCEs = 0; + el->prefixSize = 0; + el->prefix = el->prefixChars; + + UCAElements *prefix=(UCAElements *)uhash_get(t->prefixLookup, el); + el->cPoints = comp; + el->cSize = compLen; + el->prefix = el->prefixChars; + el->prefixSize = 0; + if(prefix == NULL) { + el->noOfCEs = 0; + ucol_setText(colEl, decomp, decLen, status); + while((el->CEs[el->noOfCEs] = ucol_next(colEl, status)) != (uint32_t)UCOL_NULLORDER) { + el->noOfCEs++; + } + uprv_uca_setMapCE(t, el, status); + uprv_uca_addAnElement(t, el, status); + } +} + +static void +uprv_uca_addMultiCMContractions(tempUCATable *t, + UCollationElements* colEl, + tempTailorContext *c, + UCAElements *el, + UErrorCode *status) { + CombinClassTable *cmLookup = t->cmLookup; + UChar newDecomp[256]; + int32_t maxComp, newDecLen; + const uint16_t *fcdTrieData = unorm_getFCDTrie(status); + int16_t curClass = (unorm_getFCD16(fcdTrieData, c->tailoringCM) & 0xff); + CompData *precomp = c->precomp; + int32_t compLen = c->compLen; + UChar *comp = c->comp; + maxComp = c->precompLen; + + for (int32_t j=0; j < maxComp; j++) { + int32_t count=0; + int32_t newClass=0; + do { + if ( count == 0 ) { // Decompose the saved precomposed char. + UChar temp[2]; + temp[0]=precomp[j].cp; + temp[1]=0; + newDecLen = unorm_normalize(temp, 1, UNORM_NFD, 0, + newDecomp, sizeof(newDecomp)/sizeof(UChar), status); + newDecomp[newDecLen++] = cmLookup->cPoints[c->cmPos]; + } + else { // swap 2 combining marks when they are equal. + uprv_memcpy(newDecomp, c->decomp, sizeof(UChar)*(c->decompLen)); + newDecLen = c->decompLen; + newDecomp[newDecLen++] = precomp[j].cClass; + } + newDecomp[newDecLen] = 0; + compLen = unorm_normalize(newDecomp, newDecLen, UNORM_NFC, 0, + comp, 256, status); + if (compLen==1) { + comp[compLen++] = newDecomp[newDecLen++] = c->tailoringCM; + comp[compLen] = newDecomp[newDecLen] = 0; + el->cPoints = newDecomp; + el->cSize = newDecLen; + + UCAElements *prefix=(UCAElements *)uhash_get(t->prefixLookup, el); + el->cPoints = c->comp; + el->cSize = compLen; + el->prefix = el->prefixChars; + el->prefixSize = 0; + if(prefix == NULL) { + el->noOfCEs = 0; + ucol_setText(colEl, newDecomp, newDecLen, status); + while((el->CEs[el->noOfCEs] = ucol_next(colEl, status)) != (uint32_t)UCOL_NULLORDER) { + el->noOfCEs++; + } + uprv_uca_setMapCE(t, el, status); + uprv_uca_finalizeAddition(t, el, status); + + // Save the current precomposed char and its class to find any + // other combining mark combinations. + precomp[c->precompLen].cp=comp[0]; + precomp[c->precompLen].cClass = curClass; + c->precompLen++; + } + } + } while (++count<2 && (precomp[j].cClass == curClass)); + } + +} + +static void +uprv_uca_addTailCanonicalClosures(tempUCATable *t, + UCollationElements* colEl, + UChar baseCh, + UChar cMark, + UCAElements *el, + UErrorCode *status) { + CombinClassTable *cmLookup = t->cmLookup; + const uint16_t *fcdTrieData = unorm_getFCDTrie(status); + int16_t maxIndex = (unorm_getFCD16(fcdTrieData, cMark) & 0xff ); + UCAElements element; + uint16_t *index; + UChar decomp[256]; + UChar comp[256]; + CompData precomp[256]; // precomposed array + int32_t precompLen = 0; // count for precomp + int32_t i, len, decompLen, curClass, replacedPos; + tempTailorContext c; + + if ( cmLookup == NULL ) { + return; + } + index = cmLookup->index; + int32_t cClass=(unorm_getFCD16(fcdTrieData, cMark) & 0xff); + maxIndex = (int32_t)index[(unorm_getFCD16(fcdTrieData, cMark) & 0xff)-1]; + c.comp = comp; + c.decomp = decomp; + c.precomp = precomp; + c.tailoringCM = cMark; + + if (cClass>0) { + maxIndex = (int32_t)index[cClass-1]; + } + else { + maxIndex=0; + } + decomp[0]=baseCh; + for ( i=0; i<maxIndex ; i++ ) { + decomp[1] = cmLookup->cPoints[i]; + decomp[2]=0; + decompLen=2; + len = unorm_normalize(decomp, decompLen, UNORM_NFC, 0, comp, 256, status); + if (len==1) { + // Save the current precomposed char and its class to find any + // other combining mark combinations. + precomp[precompLen].cp=comp[0]; + curClass = precomp[precompLen].cClass = + index[unorm_getFCD16(fcdTrieData, decomp[1]) & 0xff]; + precompLen++; + replacedPos=0; + for (decompLen=0; decompLen< (int32_t)el->cSize; decompLen++) { + decomp[decompLen] = el->cPoints[decompLen]; + if (decomp[decompLen]==cMark) { + replacedPos = decompLen; // record the position for later use + } + } + if ( replacedPos != 0 ) { + decomp[replacedPos]=cmLookup->cPoints[i]; + } + decomp[decompLen] = 0; + len = unorm_normalize(decomp, decompLen, UNORM_NFC, 0, comp, 256, status); + comp[len++] = decomp[decompLen++] = cMark; + comp[len] = decomp[decompLen] = 0; + element.cPoints = decomp; + element.cSize = decompLen; + element.noOfCEs = 0; + element.prefix = el->prefixChars; + element.prefixSize = 0; + + UCAElements *prefix=(UCAElements *)uhash_get(t->prefixLookup, &element); + element.cPoints = comp; + element.cSize = len; + element.prefix = el->prefixChars; + element.prefixSize = 0; + if(prefix == NULL) { + element.noOfCEs = 0; + ucol_setText(colEl, decomp, decompLen, status); + while((element.CEs[element.noOfCEs] = ucol_next(colEl, status)) != (uint32_t)UCOL_NULLORDER) { + element.noOfCEs++; + } + uprv_uca_setMapCE(t, &element, status); + uprv_uca_finalizeAddition(t, &element, status); + } + + // This is a fix for tailoring contractions with accented + // character at the end of contraction string. + if ((len>2) && + (unorm_getFCD16(fcdTrieData, comp[len-2]) & 0xff00)==0) { + uprv_uca_addFCD4AccentedContractions(t, colEl, comp, len, &element, status); + } + + if (precompLen >1) { + c.compLen = len; + c.decompLen = decompLen; + c.precompLen = precompLen; + c.cmPos = i; + uprv_uca_addMultiCMContractions(t, colEl, &c, &element, status); + precompLen = c.precompLen; + } + } + } +} + U_CAPI int32_t U_EXPORT2 -uprv_uca_canonicalClosure(tempUCATable *t, UErrorCode *status) +uprv_uca_canonicalClosure(tempUCATable *t, + UColTokenParser *src, + UErrorCode *status) { enumStruct context; context.noOfClosures = 0; - if(U_SUCCESS(*status)) { - UCollator *tempColl = NULL; - tempUCATable *tempTable = uprv_uca_cloneTempTable(t, status); - - UCATableHeader *tempData = uprv_uca_assembleTable(tempTable, status); - tempColl = ucol_initCollator(tempData, 0, t->UCA, status); - uprv_uca_closeTempTable(tempTable); - - if(U_SUCCESS(*status)) { - tempColl->rb = NULL; - tempColl->elements = NULL; - tempColl->validLocale = NULL; - tempColl->requestedLocale = NULL; - tempColl->hasRealData = TRUE; - tempColl->freeImageOnClose = TRUE; - } else if(tempData != 0) { - uprv_free(tempData); - } + UCAElements el; + UColToken *tok; + UColToken *expt = NULL; + uint32_t i = 0, j = 0; + UChar baseChar, firstCM; + const uint16_t *fcdTrieData = unorm_getFCDTrie(status); + + if(!U_SUCCESS(*status)) { + return 0; + } - /* produce canonical closure */ - UCollationElements* colEl = ucol_openElements(tempColl, NULL, 0, status); + UCollator *tempColl = NULL; + tempUCATable *tempTable = uprv_uca_cloneTempTable(t, status); - context.t = t; - context.tempColl = tempColl; - context.colEl = colEl; - context.status = status; - u_enumCharTypes(_enumCategoryRangeClosureCategory, &context); + UCATableHeader *tempData = uprv_uca_assembleTable(tempTable, status); + tempColl = ucol_initCollator(tempData, 0, t->UCA, status); + if ( tempTable->cmLookup != NULL ) { + t->cmLookup = tempTable->cmLookup; // copy over to t + tempTable->cmLookup = NULL; + } + uprv_uca_closeTempTable(tempTable); + if(U_SUCCESS(*status)) { + tempColl->rb = NULL; + tempColl->elements = NULL; + tempColl->validLocale = NULL; + tempColl->requestedLocale = NULL; + tempColl->hasRealData = TRUE; + tempColl->freeImageOnClose = TRUE; + } else if(tempData != 0) { + uprv_free(tempData); + } + + /* produce canonical closure */ + UCollationElements* colEl = ucol_openElements(tempColl, NULL, 0, status); + + context.t = t; + context.tempColl = tempColl; + context.colEl = colEl; + context.status = status; + u_enumCharTypes(_enumCategoryRangeClosureCategory, &context); + + if ( (src==NULL) || !src->buildCCTabFlag ) { ucol_closeElements(colEl); ucol_close(tempColl); + return context.noOfClosures; // no extra contraction needed to add + } + + for (i=0; i < src->resultLen; i++) { + baseChar = firstCM= (UChar)0; + tok = src->lh[i].first; + while (tok != NULL && U_SUCCESS(*status)) { + el.prefix = el.prefixChars; + el.cPoints = el.uchars; + if(tok->prefix != 0) { + el.prefixSize = tok->prefix>>24; + uprv_memcpy(el.prefix, src->source + (tok->prefix & 0x00FFFFFF), el.prefixSize*sizeof(UChar)); + + el.cSize = (tok->source >> 24)-(tok->prefix>>24); + uprv_memcpy(el.uchars, (tok->source & 0x00FFFFFF)+(tok->prefix>>24) + src->source, el.cSize*sizeof(UChar)); + } else { + el.prefixSize = 0; + *el.prefix = 0; + + el.cSize = (tok->source >> 24); + uprv_memcpy(el.uchars, (tok->source & 0x00FFFFFF) + src->source, el.cSize*sizeof(UChar)); + } + if(src->UCA != NULL) { + for(j = 0; j<el.cSize; j++) { + int16_t fcd = unorm_getFCD16(fcdTrieData, el.cPoints[j]); + if ( (fcd & 0xff) == 0 ) { + baseChar = el.cPoints[j]; // last base character + firstCM=0; // reset combining mark value + } + else { + if ( (baseChar!=0) && (firstCM==0) ) { + firstCM = el.cPoints[j]; // first combining mark + } + } + } + } + if ( (baseChar!= (UChar)0) && (firstCM != (UChar)0) ) { + // find all the canonical rules + uprv_uca_addTailCanonicalClosures(t, colEl, baseChar, firstCM, &el, status); + } + tok = tok->next; + } } + ucol_closeElements(colEl); + ucol_close(tempColl); + return context.noOfClosures; } #endif /* #if !UCONFIG_NO_COLLATION */ - - diff --git a/i18n/ucol_elm.h b/i18n/ucol_elm.h index 9de80da8..fbe966ea 100644 --- a/i18n/ucol_elm.h +++ b/i18n/ucol_elm.h @@ -22,6 +22,7 @@ #define UCOL_UCAELEMS_H #include "unicode/utypes.h" +#include "ucol_tok.h" #if !UCONFIG_NO_COLLATION @@ -43,6 +44,13 @@ a problem. Normally, less than 32K are tailored. */ #define UCOL_ELM_TRIE_CAPACITY 0x40000 +/* This is the maxmun capacity for temparay combining class + * table. The table will be compacted after scanning all the + * Unicode codepoints. +*/ +#define UCOL_MAX_CM_TAB 0x10000 + + typedef struct { uint32_t *CEs; int32_t position; @@ -85,6 +93,12 @@ typedef struct { } MaxExpansionTable; typedef struct { + uint16_t index[256]; /* index of cPoints by combining class 0-255. */ + UChar *cPoints; /* code point array of all combining marks */ + uint32_t size; /* total number of combining marks */ +} CombinClassTable; + +typedef struct { /*CompactEIntArray *mapping; */ UNewTrie *mapping; ExpansionTable *expansions; @@ -97,17 +111,33 @@ typedef struct { uint8_t *contrEndCP; const UCollator *UCA; UHashtable *prefixLookup; + CombinClassTable *cmLookup; /* combining class lookup for tailoring. */ } tempUCATable; +typedef struct { + UChar cp; + uint16_t cClass; // combining class +}CompData; + +typedef struct { + CompData *precomp; + int32_t precompLen; + UChar *decomp; + int32_t decompLen; + UChar *comp; + int32_t compLen; + uint16_t curClass; + uint16_t tailoringCM; + int32_t cmPos; +}tempTailorContext; + U_CAPI tempUCATable * U_EXPORT2 uprv_uca_initTempTable(UCATableHeader *image, UColOptionSet *opts, const UCollator *UCA, UColCETags initTag, UColCETags supplementaryInitTag, UErrorCode *status); U_CAPI tempUCATable * U_EXPORT2 uprv_uca_cloneTempTable(tempUCATable *t, UErrorCode *status); U_CAPI void U_EXPORT2 uprv_uca_closeTempTable(tempUCATable *t); U_CAPI uint32_t U_EXPORT2 uprv_uca_addAnElement(tempUCATable *t, UCAElements *element, UErrorCode *status); U_CAPI UCATableHeader * U_EXPORT2 uprv_uca_assembleTable(tempUCATable *t, UErrorCode *status); U_CAPI int32_t U_EXPORT2 -uprv_uca_canonicalClosure(tempUCATable *t, UErrorCode *status); - - +uprv_uca_canonicalClosure(tempUCATable *t, UColTokenParser *src, UErrorCode *status); #define paddedsize(something) ((something)+((((something)%4)!=0)?(4-(something)%4):0)) #define headersize (paddedsize(sizeof(UCATableHeader))+paddedsize(sizeof(UColOptionSet))) diff --git a/i18n/ucol_tok.cpp b/i18n/ucol_tok.cpp index d7d993a1..77e2c860 100644 --- a/i18n/ucol_tok.cpp +++ b/i18n/ucol_tok.cpp @@ -1817,7 +1817,7 @@ void ucol_tok_initTokenList(UColTokenParser *src, const UChar *rules, const uint src->parsedToken.prefixOffset = 0; src->parsedToken.flags = 0; src->parsedToken.strength = UCOL_TOK_UNSET; - + src->buildCCTabFlag = FALSE; if(U_FAILURE(*status)) { return; diff --git a/i18n/ucol_tok.h b/i18n/ucol_tok.h index efd0a021..fc252e94 100644 --- a/i18n/ucol_tok.h +++ b/i18n/ucol_tok.h @@ -1,7 +1,7 @@ /* ******************************************************************************* * -* Copyright (C) 2001-2006, International Business Machines +* Copyright (C) 2001-2007, International Business Machines * Corporation and others. All Rights Reserved. * ******************************************************************************* @@ -122,6 +122,7 @@ typedef struct { UColToken *varTop; USet *copySet; USet *removeSet; + UBool buildCCTabFlag; /* Tailoring rule requirs building combining class table. */ } UColTokenParser; typedef struct { diff --git a/i18n/unicode/basictz.h b/i18n/unicode/basictz.h index 61e05b61..a49effd5 100644 --- a/i18n/unicode/basictz.h +++ b/i18n/unicode/basictz.h @@ -140,7 +140,36 @@ public: virtual void getSimpleRulesNear(UDate date, InitialTimeZoneRule*& initial, AnnualTimeZoneRule*& std, AnnualTimeZoneRule*& dst, UErrorCode& status) /*const*/; + + /** + * The time type option bit flags used by getOffsetFromLocal + * @internal + */ + enum { + kStandard = 0x01, + kDaylight = 0x03, + kFormer = 0x04, + kLatter = 0x0C + }; + + /** + * Get time zone offsets from local wall time. + * @internal + */ + virtual void getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt, + int32_t& rawOffset, int32_t& dstOffset, UErrorCode& status) /*const*/; + protected: + + /** + * The time type option bit masks used by getOffsetFromLocal + * @internal + */ + enum { + kStdDstMask = kDaylight, + kFormerLatterMask = kLatter + }; + /** * Default constructor. * @draft ICU 3.8 diff --git a/i18n/unicode/dtfmtsym.h b/i18n/unicode/dtfmtsym.h index 5377723f..5bc73fef 100644 --- a/i18n/unicode/dtfmtsym.h +++ b/i18n/unicode/dtfmtsym.h @@ -37,6 +37,8 @@ U_NAMESPACE_BEGIN /* forward declaration */ class SimpleDateFormat; class Hashtable; +class ZoneStringFormat; +class SafeZoneStringFormatPtr; /** * DateFormatSymbols is a public class for encapsulating localizable date-time @@ -445,100 +447,6 @@ public: */ static UClassID U_EXPORT2 getStaticClassID(); - /** - * The translation type of the translated zone strings - * @internal ICU 3.6 - */ - enum TimeZoneTranslationType { - TIMEZONE_SHORT_GENERIC, - TIMEZONE_SHORT_STANDARD, - TIMEZONE_SHORT_DAYLIGHT, - TIMEZONE_LONG_GENERIC, - TIMEZONE_LONG_STANDARD, - TIMEZONE_LONG_DAYLIGHT, - TIMEZONE_EXEMPLAR_CITY, - TIMEZONE_COUNT - }; - - /** - * Creates an enumeration of time zone IDs. The object is owned by the caller and should delete it after use. - * The time zone IDs are just for programmatic lookup. NOT LOCALIZED!!! - * @param status Input/output parameter, set to success or - * failure code upon return. - * @return A new StringEnumeration object - * @internal ICU 3.6 - */ - virtual StringEnumeration* createZoneStringIDs(UErrorCode &status); - - /** - * Gets timezone string give the key and translation type - * @param ID The ID of zone strings, e.g: "America/Los_Angeles". - * The time zone ID is for programmatic lookup. - * @param type The translation type requested - * @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& getZoneString(const UnicodeString &ID, const TimeZoneTranslationType type, UnicodeString &result, UErrorCode &status); - - /** - * Gets metazone string given the key and translation type and calendar - * @param ID The ID of zone strings, e.g: "America/Los_Angeles". - * The time zone ID is for programmatic lookup. - * @param type The translation type requested - * @param cal The calendar - * @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 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". - * The time zone ID is for programmatic lookup. - * @param type The translation type to set the value for - * @param value The string with which current translation needs to be replaced - * @param status Input/output parameter, set to success or - * @internal ICU 3.6 - */ - - /** - * Determines if the Commonly Used flag is set for this zone - * @param zid The ID of zone strings, e.g: "America/Los_Angeles". - * The time zone ID is for programmatic lookup. - * @return A boolean value indicating if the zone is commonlyUsed or not. - * @internal ICU 3.8 - */ - UBool isCommonlyUsed(const UnicodeString &zid); - - /** - * Sets timezone string for the given the ID and translation type - * @param ID The ID of zone strings, e.g: "America/Los_Angeles". - * The time zone ID is for programmatic lookup. - * @param type The translation type to set the value for - * @param value The string with which current translation needs to be replaced - * @param status Input/output parameter, set to success or - * @internal ICU 3.6 - */ - void setZoneString(const UnicodeString &ID, const TimeZoneTranslationType type, const UnicodeString &value, UErrorCode &status); - private: friend class SimpleDateFormat; @@ -661,13 +569,34 @@ private: /** * The format data of all the timezones in this locale. */ - UnicodeString** fZoneStrings; + UnicodeString **fZoneStrings; // Zone string array set by setZoneStrings + UnicodeString **fLocaleZoneStrings; // Zone string array created by the locale int32_t fZoneStringsRowCount; int32_t fZoneStringsColCount; - StringEnumeration* fZoneIDEnumeration; - Hashtable* fZoneStringsHash; - UResourceBundle* fResourceBundle; - const char* fCountry; + + const ZoneStringFormat *fZoneStringFormat; + ZoneStringFormat *fZSFLocal; // Local ZoneStringFormat instance + SafeZoneStringFormatPtr *fZSFCachePtr; // Cached ZoneStringFormat + Locale fZSFLocale; // Locale used for getting ZoneStringFormat + + /** + * Pattern string used for localized time zone GMT format. For example, "GMT{0}" + */ + UnicodeString fGmtFormat; + + /** + * Pattern strings used for formatting zone offset in a localized time zone GMT string. + */ + UnicodeString *fGmtHourFormats; + int32_t fGmtHourFormatsCount; + + enum GMTHourType { + GMT_NEGATIVE_HMS = 0, + GMT_NEGATIVE_HM, + GMT_POSITIVE_HMS, + GMT_POSITIVE_HM, + GMT_HOUR_COUNT + }; /** * Localized date-time pattern characters. For example: use 'u' as 'y'. @@ -729,21 +658,6 @@ private: void createZoneStrings(const UnicodeString *const * otherStrings); /** - * Package private: used by SimpleDateFormat - * Gets the index for the given time zone ID to obtain the timezone - * strings for formatting. The time zone ID is just for programmatic - * lookup. NOT LOCALIZED!!! - * @param ID the given time zone ID. - * @return the index of the given time zone ID. Returns -1 if - * the given time zone ID can't be located in the DateFormatSymbols object. - * @see java.util.SimpleTimeZone - */ - int32_t getZoneIndex(const UnicodeString& ID) const; - - // Internal method; see source for documentation - int32_t _getZoneIndex(const UnicodeString& id) const; - - /** * Delete all the storage owned by this object. */ void dispose(void); @@ -754,67 +668,26 @@ private: */ void copyData(const DateFormatSymbols& other); - /** - * Delete just the zone strings. - */ - void disposeZoneStrings(void); /** - * Initializes the zoneStrings hash and keys StringEnumeration after reading the zoneStrings resource - */ - void initZoneStrings(UErrorCode &status); - /** - * initialzes the zoneStrings has and keys enumeration after reading the strings[][]. Required for backwards - * compatibility of setZoneStrings method - */ - void initZoneStrings(const UnicodeString** strings, int32_t rowCount, int32_t collumnCount, UErrorCode& status); - /** - * initialization of the fZoneStrings data member - */ - void initZoneStringsArray(UErrorCode& status); - /** - * Creates a deep clone of the Hashtable + * Returns a ZoneStringFormat, used only by SimpleDateFormat for now. */ - Hashtable* createZoneStringsHash(const Hashtable* otherHash); - + const ZoneStringFormat* getZoneStringFormat(void) const; + /** - * Fetches the key from the hashtable for a given ID. - * e.g: for a given ID such as PST returns "Americal/Los_Angeles" - * Used by SimpleDateFormat class. - * @param ID The id of the time zone for which the key needs to be fetched - * @param result Output parameter to recieve the key. - * @return the input UnicodeString object for chaining + * Create a ZoneStringFormat by locale if not yet availble */ - UnicodeString& getZoneID(const UnicodeString& zid, UnicodeString& result, UErrorCode& status); - + void initZoneStringFormat(void); + /** - * Fetches the zone type and zone string from the hashtable for a given key. - * e.g: for key: "Americal/Los_Angeles", text: "2004/1/1 PT 1:00" and start:9 - * returns TIMEZONE_SHORT_GENERIC and "PT". - * Used by SimpleDateFormat class. - * @param ID the name of the timezone - * @param text the string containing the time zone translation - * @param start The position in string where time zone string starts - * @param type output parameter to recieve the type of time zone string - * @param value output parameter to recieve the the acutal time zone string + * Create zone strings array by locale if not yet available */ - void getZoneType(const UnicodeString& zid, const UnicodeString& text, int32_t start, TimeZoneTranslationType& type, UnicodeString& value, UErrorCode& status); - + void initZoneStringsArray(void); + /** - * Fetches the zone type and zone string from the hashtable by cycling through all elements in the hashtable. - * e.g: text: "2004/1/1 PT 1:00" and start:9 - * returns "Americal/Los_Angeles", TIMEZONE_SHORT_GENERIC and "PT". Used by SimpleDateFormat class. - * Used by SimpleDateFormat class. - * @param ID output parameter to recieve the key name of the time zone - * @param text the string containing the time zone translation - * @param start The position in string where time zone string starts - * @param type output parameter to recieve the type of time zone string - * @param value output parameter to recieve the the acutal time zone string - * @param status output parameter to recive the error information + * Delete just the zone strings. */ - void findZoneIDTypeValue(UnicodeString& zid, const UnicodeString& text, int32_t start, TimeZoneTranslationType& type, UnicodeString& value, UErrorCode& status); - - UnicodeString resolveParsedMetazone(const UnicodeString& zid); + void disposeZoneStrings(void); }; U_NAMESPACE_END diff --git a/i18n/unicode/rbtz.h b/i18n/unicode/rbtz.h index 791f9851..d7f342d5 100644 --- a/i18n/unicode/rbtz.h +++ b/i18n/unicode/rbtz.h @@ -23,6 +23,7 @@ U_NAMESPACE_BEGIN // forward declaration class UVector; +struct Transition; class U_I18N_API RuleBasedTimeZone : public BasicTimeZone { public: @@ -289,13 +290,27 @@ public: virtual void getTimeZoneRules(const InitialTimeZoneRule*& initial, const TimeZoneRule* trsrules[], int32_t& trscount, UErrorCode& status) /*const*/; + /** + * Get time zone offsets from local wall time. + * @internal + */ + virtual void getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt, + int32_t& rawOffset, int32_t& dstOffset, UErrorCode& status) /*const*/; + private: void deleteRules(void); void deleteTransitions(void); UVector* copyRules(UVector* source); - TimeZoneRule* findRuleInFinal(UDate date, UBool local) const; + TimeZoneRule* findRuleInFinal(UDate date, UBool local, + int32_t NonExistingTimeOpt, int32_t DuplicatedTimeOpt) const; UBool findNext(UDate base, UBool inclusive, UDate& time, TimeZoneRule*& from, TimeZoneRule*& to) const; UBool findPrev(UDate base, UBool inclusive, UDate& time, TimeZoneRule*& from, TimeZoneRule*& to) const; + int32_t getLocalDelta(int32_t rawBefore, int32_t dstBefore, int32_t rawAfter, int32_t dstAfter, + int32_t NonExistingTimeOpt, int32_t DuplicatedTimeOpt) const; + UDate getTransitionTime(Transition* transition, UBool local, + int32_t NonExistingTimeOpt, int32_t DuplicatedTimeOpt) const; + void getOffsetInternal(UDate date, UBool local, int32_t NonExistingTimeOpt, int32_t DuplicatedTimeOpt, + int32_t& rawOffset, int32_t& dstOffset, UErrorCode& ec) const; InitialTimeZoneRule *fInitialRule; UVector *fHistoricRules; diff --git a/i18n/unicode/simpletz.h b/i18n/unicode/simpletz.h index c5411326..08fdd208 100644 --- a/i18n/unicode/simpletz.h +++ b/i18n/unicode/simpletz.h @@ -617,6 +617,13 @@ public: int32_t& dstOffset, UErrorCode& ec) const; /** + * Get time zone offsets from local wall time. + * @internal + */ + virtual void getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt, + int32_t& rawOffset, int32_t& dstOffset, UErrorCode& status) /*const*/; + + /** * Returns the TimeZone's raw GMT offset (i.e., the number of milliseconds to add * to GMT to get local time, before taking daylight savings time into account). * diff --git a/i18n/unicode/smpdtfmt.h b/i18n/unicode/smpdtfmt.h index b0bbc41f..2fb7c8eb 100644 --- a/i18n/unicode/smpdtfmt.h +++ b/i18n/unicode/smpdtfmt.h @@ -38,6 +38,7 @@ U_NAMESPACE_BEGIN class DateFormatSymbols; class DateFormat; +class MessageFormat; /** * @@ -650,13 +651,6 @@ private: 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" * having a number of digits between "minDigits" and @@ -767,6 +761,12 @@ private: ParsePosition& pos, UBool allowNegative) const; + void parseInt(const UnicodeString& text, + Formattable& number, + int32_t maxDigits, + ParsePosition& pos, + UBool allowNegative) const; + /** * Translate a pattern, mapping each character in the from string to the * corresponding character in the to string. Return an error if the original @@ -793,25 +793,22 @@ private: * if the operation succeeds. */ void parseAmbiguousDatesAsAfter(UDate startDate, UErrorCode& status); - - /** - * Given text, a start in the text, and a row index, return the column index that - * of the zone name that matches (case insensitive) at start, or 0 if none matches. - * - int32_t matchZoneString(const UnicodeString& text, int32_t start, int32_t zi) const; - */ - + /** - * Given text, a start in the text, and a calendar, return the next offset in the text - * after matching the zone string. If we fail to match, return 0. Update the calendar - * as appropriate. + * Private methods for formatting/parsing GMT string */ - int32_t subParseZoneString(const UnicodeString& text, int32_t start, Calendar& cal, UErrorCode& status) const; - + void appendGMT(UnicodeString &appendTo, Calendar& cal, UErrorCode& status) const; + void formatGMTDefault(UnicodeString &appendTo, int32_t offset) const; + int32_t parseGMT(const UnicodeString &text, ParsePosition &pos) const; + int32_t parseGMTDefault(const UnicodeString &text, ParsePosition &pos) const; + UBool isDefaultGMTFormat() const; + + void formatRFC822TZ(UnicodeString &appendTo, int32_t offset) const; + /** - * append the gmt string + * Initialize MessageFormat instances used for GMT formatting/parsing */ - inline void appendGMT(UnicodeString &appendTo, Calendar& cal, UErrorCode& status) const; + void initGMTFormatters(UErrorCode &status); /** * Used to map pattern characters to Calendar field identifiers. @@ -854,7 +851,18 @@ private: */ /*transient*/ int32_t fDefaultCenturyStartYear; - /*transient*/ TimeZone* parsedTimeZone; // here to avoid api change + enum ParsedTZType { + TZTYPE_UNK, + TZTYPE_STD, + TZTYPE_DST + }; + + ParsedTZType tztype; // here to avoid api change + + /* + * MessageFormat instances used for localized GMT format + */ + MessageFormat **fGMTFormatters; UBool fHaveDefaultCentury; }; diff --git a/i18n/unicode/timezone.h b/i18n/unicode/timezone.h index aed21e05..e771cb90 100644 --- a/i18n/unicode/timezone.h +++ b/i18n/unicode/timezone.h @@ -670,6 +670,15 @@ protected: static UResourceBundle* loadRule(const UResourceBundle* top, const UnicodeString& ruleid, UResourceBundle* oldbundle, UErrorCode&status); private: + friend class ZoneMeta; + + /** + * Get a canonical Olson zone ID for the given ID. If the given ID is not valid, + * this method returns empty string as the result. If the given ID is a link, then the + * referenced ID (canonical ID) is returned. + */ + static UnicodeString& getOlsonCanonicalID(const UnicodeString &id, UnicodeString &canonical); + static TimeZone* createCustomTimeZone(const UnicodeString&); // Creates a time zone based on the string. /** diff --git a/i18n/usearch.cpp b/i18n/usearch.cpp index f974d9ac..df44aa8b 100644 --- a/i18n/usearch.cpp +++ b/i18n/usearch.cpp @@ -1,6 +1,6 @@ /* ********************************************************************** -* Copyright (C) 2001-2006 IBM and others. All rights reserved. +* Copyright (C) 2001-2007 IBM and others. All rights reserved. ********************************************************************** * Date Name Description * 07/02/2001 synwee Creation. @@ -320,13 +320,19 @@ inline int16_t initializePattern(UStringSearch *strsrch, UErrorCode *status) const UChar *patterntext = pattern->text; int32_t length = pattern->textLength; int32_t index = 0; - - pattern->hasPrefixAccents = getFCD(patterntext, &index, length) >> - SECOND_LAST_BYTE_SHIFT_; - index = length; - UTF_BACK_1(patterntext, 0, index); - pattern->hasSuffixAccents = getFCD(patterntext, &index, length) & - LAST_BYTE_MASK_; + + // Since the strength is primary, accents are ignored in the pattern. + if (strsrch->strength == UCOL_PRIMARY) { + pattern->hasPrefixAccents = 0; + pattern->hasSuffixAccents = 0; + } else { + pattern->hasPrefixAccents = getFCD(patterntext, &index, length) >> + SECOND_LAST_BYTE_SHIFT_; + index = length; + UTF_BACK_1(patterntext, 0, index); + pattern->hasSuffixAccents = getFCD(patterntext, &index, length) & + LAST_BYTE_MASK_; + } // since intializePattern is an internal method status is a success. return initializePatternCETable(strsrch, status); } @@ -426,6 +432,35 @@ inline void initialize(UStringSearch *strsrch, UErrorCode *status) } /** +* Check to make sure that the match length is at the end of the character by +* using the breakiterator. +* @param strsrch string search data +* @param start target text start offset +* @param end target text end offset +*/ +static +void checkBreakBoundary(const UStringSearch *strsrch, int32_t *start, + int32_t *end) +{ +#if !UCONFIG_NO_BREAK_ITERATION + UBreakIterator *breakiterator = strsrch->search->internalBreakIter; + if (breakiterator) { + int32_t matchend = *end; + int32_t matchstart = *start; + + if (!ubrk_isBoundary(breakiterator, matchend)) + *end = ubrk_following(breakiterator, matchend); + + /* Check the start of the matched text to make sure it doesn't have any accents + * before it. This code may not be necessary and so it is commented out */ + /*if (!ubrk_isBoundary(breakiterator, matchstart) && !ubrk_isBoundary(breakiterator, matchstart-1)) { + *start = ubrk_preceding(breakiterator, matchstart); + }*/ + } +#endif +} + +/** * Determine whether the target text in UStringSearch bounded by the offset * start and end is one or more whole units of text as * determined by the breakiterator in UStringSearch. @@ -439,6 +474,7 @@ UBool isBreakUnit(const UStringSearch *strsrch, int32_t start, { #if !UCONFIG_NO_BREAK_ITERATION UBreakIterator *breakiterator = strsrch->search->breakIter; + //TODO: Add here. if (breakiterator) { int32_t startindex = ubrk_first(breakiterator); int32_t endindex = ubrk_last(breakiterator); @@ -705,7 +741,7 @@ UBool checkExtraMatchAccents(const UStringSearch *strsrch, int32_t start, uint32_t firstce = strsrch->pattern.CE[0]; UBool ignorable = TRUE; uint32_t ce = UCOL_IGNORABLE; - while (U_SUCCESS(*status) && ce != firstce) { + while (U_SUCCESS(*status) && ce != firstce && ce != UCOL_NULLORDER) { offset = ucol_getOffset(coleiter); if (ce != firstce && ce != UCOL_IGNORABLE) { ignorable = FALSE; @@ -858,11 +894,14 @@ UBool hasAccentsAfterMatch(const UStringSearch *strsrch, int32_t start, } count ++; } - int32_t ce = getCE(strsrch, ucol_next(coleiter, &status)); + int32_t ce = ucol_next(coleiter, &status); if (U_FAILURE(status)) { return TRUE; } if (ce != UCOL_NULLORDER && ce != UCOL_IGNORABLE) { + ce = getCE(strsrch, ce); + } + if (ce != UCOL_NULLORDER && ce != UCOL_IGNORABLE) { if (ucol_getOffset(coleiter) <= end) { return TRUE; } @@ -1120,6 +1159,11 @@ inline UBool checkNextExactMatch(UStringSearch *strsrch, *textoffset = getNextUStringSearchBaseOffset(strsrch, *textoffset); return FALSE; } + + //Add breakiterator boundary check for primary strength search. + if (!strsrch->search->breakIter && strsrch->strength == UCOL_PRIMARY) { + checkBreakBoundary(strsrch, &start, textoffset); + } // totally match, we will get rid of the ending ignorables. strsrch->search->matchedIndex = start; @@ -1963,6 +2007,12 @@ inline UBool checkPreviousExactMatch(UStringSearch *strsrch, *textoffset); return FALSE; } + + //Add breakiterator boundary check for primary strength search. + if (!strsrch->search->breakIter && strsrch->strength == UCOL_PRIMARY) { + checkBreakBoundary(strsrch, textoffset, &end); + } + strsrch->search->matchedIndex = *textoffset; strsrch->search->matchedLength = end - *textoffset; return TRUE; @@ -2552,8 +2602,9 @@ U_CAPI UStringSearch * U_EXPORT2 usearch_openFromCollator( result->search->breakIter = breakiter; #if !UCONFIG_NO_BREAK_ITERATION + result->search->internalBreakIter = ubrk_open(UBRK_CHARACTER, ucol_getLocale(result->collator, ULOC_VALID_LOCALE, status), text, textlength, status); if (breakiter) { - ubrk_setText(breakiter, text, textlength, status); + ubrk_setText(breakiter, text, textlength, status); } #endif @@ -2598,6 +2649,9 @@ U_CAPI void U_EXPORT2 usearch_close(UStringSearch *strsrch) if (strsrch->ownCollator && strsrch->collator) { ucol_close((UCollator *)strsrch->collator); } + if (strsrch->search->internalBreakIter) { + ubrk_close(strsrch->search->internalBreakIter); + } uprv_free(strsrch->search); uprv_free(strsrch); } @@ -2736,7 +2790,7 @@ U_CAPI void U_EXPORT2 usearch_setBreakIterator(UStringSearch *strsrch, UErrorCode *status) { if (U_SUCCESS(*status) && strsrch) { - strsrch->search->breakIter = breakiter; + strsrch->search->breakIter = breakiter; if (breakiter) { ubrk_setText(breakiter, strsrch->search->text, strsrch->search->textLength, status); @@ -2780,6 +2834,7 @@ U_CAPI void U_EXPORT2 usearch_setText( UStringSearch *strsrch, ubrk_setText(strsrch->search->breakIter, text, textlength, status); } + ubrk_setText(strsrch->search->internalBreakIter, text, textlength, status); #endif } } @@ -2812,6 +2867,11 @@ U_CAPI void U_EXPORT2 usearch_setCollator( UStringSearch *strsrch, strsrch->collator = collator; strsrch->strength = ucol_getStrength(collator); strsrch->ceMask = getMask(strsrch->strength); +#if !UCONFIG_NO_BREAK_ITERATION + ubrk_close(strsrch->search->internalBreakIter); + strsrch->search->internalBreakIter = ubrk_open(UBRK_CHARACTER, ucol_getLocale(collator, ULOC_VALID_LOCALE, status), + strsrch->search->text, strsrch->search->textLength, status); +#endif // if status is a failure, ucol_getAttribute returns UCOL_DEFAULT strsrch->toShift = ucol_getAttribute(collator, UCOL_ALTERNATE_HANDLING, status) == @@ -3229,9 +3289,10 @@ UBool usearch_handleNextExact(UStringSearch *strsrch, UErrorCode *status) } } - targetce = lastce; + //targetce = lastce; while (found && patternceindex > 0) { + lastce = targetce; targetce = ucol_previous(coleiter, status); if (U_FAILURE(*status) || targetce == UCOL_NULLORDER) { found = FALSE; @@ -3245,6 +3306,8 @@ UBool usearch_handleNextExact(UStringSearch *strsrch, UErrorCode *status) patternceindex --; found = found && targetce == patternce[patternceindex]; } + + targetce = lastce; if (!found) { if (U_FAILURE(*status)) { @@ -3411,7 +3474,7 @@ UBool usearch_handlePreviousExact(UStringSearch *strsrch, UErrorCode *status) if (firstce == UCOL_NULLORDER || firstce == UCOL_IGNORABLE) { firstce = targetce; } - if (targetce == UCOL_IGNORABLE) { + if (targetce == UCOL_IGNORABLE && strsrch->strength != UCOL_PRIMARY) { continue; } if (targetce == patternce[0]) { @@ -3425,9 +3488,10 @@ UBool usearch_handlePreviousExact(UStringSearch *strsrch, UErrorCode *status) } } - targetce = firstce; + //targetce = firstce; while (found && (patternceindex < patterncelength)) { + firstce = targetce; targetce = ucol_next(coleiter, status); if (U_FAILURE(*status) || targetce == UCOL_NULLORDER) { found = FALSE; @@ -3441,11 +3505,14 @@ UBool usearch_handlePreviousExact(UStringSearch *strsrch, UErrorCode *status) found = found && targetce == patternce[patternceindex]; patternceindex ++; } + + targetce = firstce; if (!found) { if (U_FAILURE(*status)) { break; } + textoffset = reverseShift(strsrch, textoffset, targetce, patternceindex); patternceindex = 0; diff --git a/i18n/usrchimp.h b/i18n/usrchimp.h index 94968a71..fff52b21 100644 --- a/i18n/usrchimp.h +++ b/i18n/usrchimp.h @@ -1,6 +1,6 @@ /* ********************************************************************** -* Copyright (C) 2001-2004 IBM and others. All rights reserved. +* Copyright (C) 2001-2007 IBM and others. All rights reserved. ********************************************************************** * Date Name Description * 08/13/2001 synwee Creation. @@ -26,6 +26,7 @@ struct USearch { int32_t textLength; // exact length UBool isOverlap; UBool isCanonicalMatch; + UBreakIterator *internalBreakIter; //internal character breakiterator UBreakIterator *breakIter; // value USEARCH_DONE is the default value // if we are not at the start of the text or the end of the text, diff --git a/i18n/zonemeta.cpp b/i18n/zonemeta.cpp new file mode 100644 index 00000000..0e67c687 --- /dev/null +++ b/i18n/zonemeta.cpp @@ -0,0 +1,873 @@ +/* +******************************************************************************* +* Copyright (C) 2007, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +#include "zonemeta.h" + +#include "unicode/timezone.h" +#include "unicode/ustring.h" +#include "unicode/putil.h" + +#include "umutex.h" +#include "uvector.h" +#include "cmemory.h" +#include "gregoimp.h" +#include "cstring.h" +#include "ucln_in.h" + +static UBool gZoneMetaInitialized = FALSE; + +// Metazone mapping tables +static UMTX gZoneMetaLock = NULL; +static U_NAMESPACE_QUALIFIER Hashtable *gCanonicalMap = NULL; +static U_NAMESPACE_QUALIFIER Hashtable *gOlsonToMeta = NULL; +static U_NAMESPACE_QUALIFIER Hashtable *gMetaToOlson = NULL; + +U_CDECL_BEGIN +/** + * Cleanup callback func + */ +static UBool U_CALLCONV zoneMeta_cleanup(void) +{ + umtx_destroy(&gZoneMetaLock); + + if (gCanonicalMap != NULL) { + delete gCanonicalMap; + gCanonicalMap = NULL; + } + + if (gOlsonToMeta != NULL) { + delete gOlsonToMeta; + gOlsonToMeta = NULL; + } + + if (gMetaToOlson != NULL) { + delete gMetaToOlson; + gMetaToOlson = NULL; + } + + gZoneMetaInitialized = FALSE; + + return TRUE; +} + +/** + * Deleter for UVector + */ +static void U_CALLCONV +deleteUVector(void *obj) { + delete (U_NAMESPACE_QUALIFIER UVector*) obj; +} + +/** + * Deleter for CanonicalMapEntry + */ +static void U_CALLCONV +deleteCanonicalMapEntry(void *obj) { + U_NAMESPACE_QUALIFIER CanonicalMapEntry *entry = (U_NAMESPACE_QUALIFIER CanonicalMapEntry*)obj; + uprv_free(entry->id); + uprv_free(entry); +} + +/** + * Deleter for OlsonToMetaMappingEntry + */ +static void U_CALLCONV +deleteOlsonToMetaMappingEntry(void *obj) { + U_NAMESPACE_QUALIFIER OlsonToMetaMappingEntry *entry = (U_NAMESPACE_QUALIFIER OlsonToMetaMappingEntry*)obj; + uprv_free(entry); +} + +/** + * Deleter for MetaToOlsonMappingEntry + */ +static void U_CALLCONV +deleteMetaToOlsonMappingEntry(void *obj) { + U_NAMESPACE_QUALIFIER MetaToOlsonMappingEntry *entry = (U_NAMESPACE_QUALIFIER MetaToOlsonMappingEntry*)obj; + uprv_free(entry->territory); + uprv_free(entry); +} +U_CDECL_END + +U_NAMESPACE_BEGIN + +#define ZID_KEY_MAX 128 +static const char gZoneStringsTag[] = "zoneStrings"; +static const char gUseMetazoneTag[] = "um"; + +static const char gSupplementalData[] = "supplementalData"; +static const char gMapTimezonesTag[] = "mapTimezones"; +static const char gMetazonesTag[] = "metazones"; +static const char gZoneFormattingTag[] = "zoneFormatting"; +static const char gTerritoryTag[] = "territory"; +static const char gAliasesTag[] = "aliases"; +static const char gMultizoneTag[] = "multizone"; + +static const char gMetazoneInfo[] = "metazoneInfo"; +static const char gMetazoneMappings[] = "metazoneMappings"; + +#define MZID_PREFIX_LEN 5 +static const char gMetazoneIdPrefix[] = "meta:"; + +static const UChar gWorld[] = {0x30, 0x30, 0x31, 0x00}; // "001" + +#define ASCII_DIGIT(c) (((c)>=0x30 && (c)<=0x39) ? (c)-0x30 : -1) + +/* + * Convert a date string used by metazone mappings to UDate. + * The format used by CLDR metazone mapping is "yyyy-MM-dd HH:mm". + */ + static UDate parseDate (const UChar *text, UErrorCode &status) { + if (U_FAILURE(status)) { + return 0; + } + int32_t len = u_strlen(text); + if (len != 16 && len != 10) { + // It must be yyyy-MM-dd HH:mm (length 16) or yyyy-MM-dd (length 10) + status = U_INVALID_FORMAT_ERROR; + return 0; + } + + int32_t year = 0, month = 0, day = 0, hour = 0, min = 0, n; + int32_t idx; + + // "yyyy" (0 - 3) + for (idx = 0; idx <= 3 && U_SUCCESS(status); idx++) { + n = ASCII_DIGIT(text[idx]); + if (n >= 0) { + year = 10*year + n; + } else { + status = U_INVALID_FORMAT_ERROR; + } + } + // "MM" (5 - 6) + for (idx = 5; idx <= 6 && U_SUCCESS(status); idx++) { + n = ASCII_DIGIT(text[idx]); + if (n >= 0) { + month = 10*month + n; + } else { + status = U_INVALID_FORMAT_ERROR; + } + } + // "dd" (8 - 9) + for (idx = 8; idx <= 9 && U_SUCCESS(status); idx++) { + n = ASCII_DIGIT(text[idx]); + if (n >= 0) { + day = 10*day + n; + } else { + status = U_INVALID_FORMAT_ERROR; + } + } + if (len == 16) { + // "HH" (11 - 12) + for (idx = 11; idx <= 12 && U_SUCCESS(status); idx++) { + n = ASCII_DIGIT(text[idx]); + if (n >= 0) { + hour = 10*hour + n; + } else { + status = U_INVALID_FORMAT_ERROR; + } + } + // "mm" (14 - 15) + for (idx = 14; idx <= 15 && U_SUCCESS(status); idx++) { + n = ASCII_DIGIT(text[idx]); + if (n >= 0) { + min = 10*min + n; + } else { + status = U_INVALID_FORMAT_ERROR; + } + } + } + + if (U_SUCCESS(status)) { + UDate date = Grego::fieldsToDay(year, month - 1, day) * U_MILLIS_PER_DAY + + hour * U_MILLIS_PER_HOUR + min * U_MILLIS_PER_MINUTE; + return date; + } + return 0; +} + + /* + * Initialize global objects + */ +void +ZoneMeta::initialize(void) { + UBool initialized; + UMTX_CHECK(&gZoneMetaLock, gZoneMetaInitialized, initialized); + if (initialized) { + return; + } + + // Initialize hash tables + Hashtable *tmpCanonicalMap = createCanonicalMap(); + Hashtable *tmpOlsonToMeta = createOlsonToMetaMap(); + if (tmpOlsonToMeta == NULL) { + // With ICU 3.8 data + tmpOlsonToMeta = createOlsonToMetaMapOld(); + } + Hashtable *tmpMetaToOlson = createMetaToOlsonMap(); + + umtx_lock(&gZoneMetaLock); + if (gZoneMetaInitialized) { + // Another thread already created mappings + delete tmpCanonicalMap; + delete tmpOlsonToMeta; + delete tmpMetaToOlson; + } else { + gZoneMetaInitialized = TRUE; + gCanonicalMap = tmpCanonicalMap; + gOlsonToMeta = tmpOlsonToMeta; + gMetaToOlson = tmpMetaToOlson; + ucln_i18n_registerCleanup(UCLN_I18N_ZONEMETA, zoneMeta_cleanup); + } + umtx_unlock(&gZoneMetaLock); +} + +Hashtable* +ZoneMeta::createCanonicalMap(void) { + UErrorCode status = U_ZERO_ERROR; + + Hashtable *canonicalMap = NULL; + UResourceBundle *zoneFormatting = NULL; + UResourceBundle *tzitem = NULL; + UResourceBundle *aliases = NULL; + + canonicalMap = new Hashtable(uhash_compareUnicodeString, NULL, status); + if (U_FAILURE(status)) { + return NULL; + } + canonicalMap->setValueDeleter(deleteCanonicalMapEntry); + + zoneFormatting = ures_openDirect(NULL, gSupplementalData, &status); + zoneFormatting = ures_getByKey(zoneFormatting, gZoneFormattingTag, zoneFormatting, &status); + if (U_FAILURE(status)) { + goto error_cleanup; + } + + while (ures_hasNext(zoneFormatting)) { + tzitem = ures_getNextResource(zoneFormatting, tzitem, &status); + if (U_FAILURE(status)) { + status = U_ZERO_ERROR; + continue; + } + if (ures_getType(tzitem) != URES_TABLE) { + continue; + } + + int32_t territoryLen; + const UChar *territory = ures_getStringByKey(tzitem, gTerritoryTag, &territoryLen, &status); + if (U_FAILURE(status)) { + territory = NULL; + status = U_ZERO_ERROR; + } + + int32_t tzidLen = 0; + char tzid[ZID_KEY_MAX]; + const char *tzkey = ures_getKey(tzitem); + uprv_strcpy(tzid, tzkey); + // Replace ':' with '/' + char *p = tzid; + while (*p) { + if (*p == ':') { + *p = '/'; + } + p++; + tzidLen++; + } + + // Create canonical map entry + CanonicalMapEntry *entry = (CanonicalMapEntry*)uprv_malloc(sizeof(CanonicalMapEntry)); + if (entry == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + goto error_cleanup; + } + entry->id = (UChar*)uprv_malloc((tzidLen + 1) * sizeof(UChar)); + if (entry->id == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + uprv_free(entry); + goto error_cleanup; + } + u_charsToUChars(tzid, entry->id, tzidLen + 1); + + if (territory == NULL || u_strcmp(territory, gWorld) == 0) { + entry->country = NULL; + } else { + entry->country = territory; + } + + // Put this entry to the table + canonicalMap->put(UnicodeString(entry->id), entry, status); + if (U_FAILURE(status)) { + deleteCanonicalMapEntry(entry); + goto error_cleanup; + } + + // Get aliases + aliases = ures_getByKey(tzitem, gAliasesTag, aliases, &status); + if (U_FAILURE(status)) { + // No aliases + status = U_ZERO_ERROR; + continue; + } + + while (ures_hasNext(aliases)) { + const UChar* alias = ures_getNextString(aliases, NULL, NULL, &status); + if (U_FAILURE(status)) { + status = U_ZERO_ERROR; + continue; + } + // Create canonical map entry for this alias + entry = (CanonicalMapEntry*)uprv_malloc(sizeof(CanonicalMapEntry)); + if (entry == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + goto error_cleanup; + } + entry->id = (UChar*)uprv_malloc((tzidLen + 1) * sizeof(UChar)); + if (entry->id == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + uprv_free(entry); + goto error_cleanup; + } + u_charsToUChars(tzid, entry->id, tzidLen + 1); + + if (territory == NULL || u_strcmp(territory, gWorld) == 0) { + entry->country = NULL; + } else { + entry->country = territory; + } + canonicalMap->put(UnicodeString(alias), entry, status); + if (U_FAILURE(status)) { + deleteCanonicalMapEntry(entry); + goto error_cleanup; + } + } + } + +normal_cleanup: + ures_close(aliases); + ures_close(tzitem); + ures_close(zoneFormatting); + return canonicalMap; + +error_cleanup: + delete canonicalMap; + canonicalMap = NULL; + + goto normal_cleanup; +} + +/* + * Creating Olson tzid to metazone mappings from resource (3.8.1 and beyond) + */ +Hashtable* +ZoneMeta::createOlsonToMetaMap(void) { + UErrorCode status = U_ZERO_ERROR; + + Hashtable *olsonToMeta = NULL; + UResourceBundle *metazoneMappings = NULL; + UResourceBundle *zoneItem = NULL; + UResourceBundle *mz = NULL; + StringEnumeration *tzids = NULL; + + olsonToMeta = new Hashtable(uhash_compareUnicodeString, NULL, status); + if (U_FAILURE(status)) { + return NULL; + } + olsonToMeta->setValueDeleter(deleteUVector); + + // Read metazone mappings from metazoneInfo bundle + metazoneMappings = ures_openDirect(NULL, gMetazoneInfo, &status); + metazoneMappings = ures_getByKey(metazoneMappings, gMetazoneMappings, metazoneMappings, &status); + if (U_FAILURE(status)) { + goto error_cleanup; + } + + // Walk through all canonical tzids + char zidkey[ZID_KEY_MAX]; + + tzids = TimeZone::createEnumeration(); + const UnicodeString *tzid; + while ((tzid = tzids->snext(status))) { + if (U_FAILURE(status)) { + goto error_cleanup; + } + // We may skip aliases, because the bundle + // contains only canonical IDs. For now, try + // all of them. + tzid->extract(0, tzid->length(), zidkey, sizeof(zidkey), US_INV); + zidkey[sizeof(zidkey)-1] = 0; // NULL terminate just in case. + + // Replace '/' with ':' + UBool foundSep = FALSE; + char *p = zidkey; + while (*p) { + if (*p == '/') { + *p = ':'; + foundSep = TRUE; + } + p++; + } + if (!foundSep) { + // A valid time zone key has at least one separator + continue; + } + + zoneItem = ures_getByKey(metazoneMappings, zidkey, zoneItem, &status); + if (U_FAILURE(status)) { + status = U_ZERO_ERROR; + continue; + } + + UVector *mzMappings = NULL; + while (ures_hasNext(zoneItem)) { + mz = ures_getNextResource(zoneItem, mz, &status); + const UChar *mz_name = ures_getStringByIndex(mz, 0, NULL, &status); + const UChar *mz_from = ures_getStringByIndex(mz, 1, NULL, &status); + const UChar *mz_to = ures_getStringByIndex(mz, 2, NULL, &status); + + if(U_FAILURE(status)){ + status = U_ZERO_ERROR; + continue; + } + // We do not want to use SimpleDateformat to parse boundary dates, + // because this code could be triggered by the initialization code + // used by SimpleDateFormat. + UDate from = parseDate(mz_from, status); + UDate to = parseDate(mz_to, status); + if (U_FAILURE(status)) { + status = U_ZERO_ERROR; + continue; + } + + OlsonToMetaMappingEntry *entry = (OlsonToMetaMappingEntry*)uprv_malloc(sizeof(OlsonToMetaMappingEntry)); + if (entry == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + break; + } + entry->mzid = mz_name; + entry->from = from; + entry->to = to; + + if (mzMappings == NULL) { + mzMappings = new UVector(deleteOlsonToMetaMappingEntry, NULL, status); + if (U_FAILURE(status)) { + delete mzMappings; + deleteOlsonToMetaMappingEntry(entry); + uprv_free(entry); + break; + } + } + + mzMappings->addElement(entry, status); + if (U_FAILURE(status)) { + break; + } + } + + if (U_FAILURE(status)) { + if (mzMappings != NULL) { + delete mzMappings; + } + goto error_cleanup; + } + if (mzMappings != NULL) { + olsonToMeta->put(*tzid, mzMappings, status); + if (U_FAILURE(status)) { + delete mzMappings; + goto error_cleanup; + } + } + } + +normal_cleanup: + if (tzids != NULL) { + delete tzids; + } + ures_close(zoneItem); + ures_close(mz); + ures_close(metazoneMappings); + return olsonToMeta; + +error_cleanup: + if (olsonToMeta != NULL) { + delete olsonToMeta; + olsonToMeta = NULL; + } + goto normal_cleanup; +} + +/* + * Creating Olson tzid to metazone mappings from ICU resource (3.8) + */ +Hashtable* +ZoneMeta::createOlsonToMetaMapOld(void) { + UErrorCode status = U_ZERO_ERROR; + + Hashtable *olsonToMeta = NULL; + UResourceBundle *zoneStringsArray = NULL; + UResourceBundle *mz = NULL; + UResourceBundle *zoneItem = NULL; + UResourceBundle *useMZ = NULL; + StringEnumeration *tzids = NULL; + + olsonToMeta = new Hashtable(uhash_compareUnicodeString, NULL, status); + if (U_FAILURE(status)) { + return NULL; + } + olsonToMeta->setValueDeleter(deleteUVector); + + // Read metazone mappings from root bundle + zoneStringsArray = ures_openDirect(NULL, "", &status); + zoneStringsArray = ures_getByKey(zoneStringsArray, gZoneStringsTag, zoneStringsArray, &status); + if (U_FAILURE(status)) { + goto error_cleanup; + } + + // Walk through all canonical tzids + char zidkey[ZID_KEY_MAX]; + + tzids = TimeZone::createEnumeration(); + const UnicodeString *tzid; + while ((tzid = tzids->snext(status))) { + if (U_FAILURE(status)) { + goto error_cleanup; + } + // We may skip aliases, because the bundle + // contains only canonical IDs. For now, try + // all of them. + tzid->extract(0, tzid->length(), zidkey, sizeof(zidkey), US_INV); + zidkey[sizeof(zidkey)-1] = 0; // NULL terminate just in case. + + // Replace '/' with ':' + UBool foundSep = FALSE; + char *p = zidkey; + while (*p) { + if (*p == '/') { + *p = ':'; + foundSep = TRUE; + } + p++; + } + if (!foundSep) { + // A valid time zone key has at least one separator + continue; + } + + zoneItem = ures_getByKey(zoneStringsArray, zidkey, zoneItem, &status); + useMZ = ures_getByKey(zoneItem, gUseMetazoneTag, useMZ, &status); + if (U_FAILURE(status)) { + status = U_ZERO_ERROR; + continue; + } + + UVector *mzMappings = NULL; + while (ures_hasNext(useMZ)) { + mz = ures_getNextResource(useMZ, mz, &status); + const UChar *mz_name = ures_getStringByIndex(mz, 0, NULL, &status); + const UChar *mz_from = ures_getStringByIndex(mz, 1, NULL, &status); + const UChar *mz_to = ures_getStringByIndex(mz, 2, NULL, &status); + + if(U_FAILURE(status)){ + status = U_ZERO_ERROR; + continue; + } + // We do not want to use SimpleDateformat to parse boundary dates, + // because this code could be triggered by the initialization code + // used by SimpleDateFormat. + UDate from = parseDate(mz_from, status); + UDate to = parseDate(mz_to, status); + if (U_FAILURE(status)) { + status = U_ZERO_ERROR; + continue; + } + + OlsonToMetaMappingEntry *entry = (OlsonToMetaMappingEntry*)uprv_malloc(sizeof(OlsonToMetaMappingEntry)); + if (entry == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + break; + } + entry->mzid = mz_name; + entry->from = from; + entry->to = to; + + if (mzMappings == NULL) { + mzMappings = new UVector(deleteOlsonToMetaMappingEntry, NULL, status); + if (U_FAILURE(status)) { + delete mzMappings; + deleteOlsonToMetaMappingEntry(entry); + uprv_free(entry); + break; + } + } + + mzMappings->addElement(entry, status); + if (U_FAILURE(status)) { + break; + } + } + + if (U_FAILURE(status)) { + if (mzMappings != NULL) { + delete mzMappings; + } + goto error_cleanup; + } + if (mzMappings != NULL) { + olsonToMeta->put(*tzid, mzMappings, status); + if (U_FAILURE(status)) { + delete mzMappings; + goto error_cleanup; + } + } + } + +normal_cleanup: + if (tzids != NULL) { + delete tzids; + } + ures_close(zoneItem); + ures_close(useMZ); + ures_close(mz); + ures_close(zoneStringsArray); + return olsonToMeta; + +error_cleanup: + if (olsonToMeta != NULL) { + delete olsonToMeta; + } + goto normal_cleanup; +} + +Hashtable* +ZoneMeta::createMetaToOlsonMap(void) { + UErrorCode status = U_ZERO_ERROR; + + Hashtable *metaToOlson = NULL; + UResourceBundle *metazones = NULL; + UResourceBundle *mz = NULL; + + metaToOlson = new Hashtable(uhash_compareUnicodeString, NULL, status); + if (U_FAILURE(status)) { + return NULL; + } + metaToOlson->setValueDeleter(deleteUVector); + + metazones = ures_openDirect(NULL, gSupplementalData, &status); + metazones = ures_getByKey(metazones, gMapTimezonesTag, metazones, &status); + metazones = ures_getByKey(metazones, gMetazonesTag, metazones, &status); + if (U_FAILURE(status)) { + goto error_cleanup; + } + + while (ures_hasNext(metazones)) { + mz = ures_getNextResource(metazones, mz, &status); + if (U_FAILURE(status)) { + status = U_ZERO_ERROR; + continue; + } + const char *mzkey = ures_getKey(mz); + if (uprv_strncmp(mzkey, gMetazoneIdPrefix, MZID_PREFIX_LEN) == 0) { + const char *mzid = mzkey + MZID_PREFIX_LEN; + const char *territory = uprv_strrchr(mzid, '_'); + int32_t mzidLen = 0; + int32_t territoryLen = 0; + if (territory) { + mzidLen = territory - mzid; + territory++; + territoryLen = uprv_strlen(territory); + } + if (mzidLen > 0 && territoryLen > 0) { + int32_t tzidLen; + const UChar *tzid = ures_getStringByIndex(mz, 0, &tzidLen, &status); + if (U_SUCCESS(status)) { + // Create MetaToOlsonMappingEntry + MetaToOlsonMappingEntry *entry = (MetaToOlsonMappingEntry*)uprv_malloc(sizeof(MetaToOlsonMappingEntry)); + if (entry == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + goto error_cleanup; + } + entry->id = tzid; + entry->territory = (UChar*)uprv_malloc((territoryLen + 1) * sizeof(UChar)); + if (entry->territory == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + uprv_free(entry); + goto error_cleanup; + } + u_charsToUChars(territory, entry->territory, territoryLen + 1); + + // Check if mapping entries for metazone is already available + UnicodeString mzidStr(mzid, mzidLen, US_INV); + UVector *tzMappings = (UVector*)metaToOlson->get(mzidStr); + if (tzMappings == NULL) { + // Create new UVector and put it into the hashtable + tzMappings = new UVector(deleteMetaToOlsonMappingEntry, NULL, status); + metaToOlson->put(mzidStr, tzMappings, status); + if (U_FAILURE(status)) { + if (tzMappings != NULL) { + delete tzMappings; + } + deleteMetaToOlsonMappingEntry(entry); + goto error_cleanup; + } + } + tzMappings->addElement(entry, status); + if (U_FAILURE(status)) { + goto error_cleanup; + } + } else { + status = U_ZERO_ERROR; + } + } + } + } + +normal_cleanup: + ures_close(mz); + ures_close(metazones); + return metaToOlson; + +error_cleanup: + if (metaToOlson != NULL) { + delete metaToOlson; + } + goto normal_cleanup; +} + +UnicodeString& +ZoneMeta::getCanonicalID(const UnicodeString &tzid, UnicodeString &canonicalID) { + const CanonicalMapEntry *entry = getCanonicalInfo(tzid); + if (entry != NULL) { + canonicalID.setTo(entry->id); + } else { + // Use the input tzid + canonicalID.setTo(tzid); + } + return canonicalID; +} + +UnicodeString& +ZoneMeta::getCanonicalCountry(const UnicodeString &tzid, UnicodeString &canonicalCountry) { + const CanonicalMapEntry *entry = getCanonicalInfo(tzid); + if (entry != NULL && entry->country != NULL) { + canonicalCountry.setTo(entry->country); + } else { + // Use the input tzid + canonicalCountry.remove(); + } + return canonicalCountry; +} + +const CanonicalMapEntry* +ZoneMeta::getCanonicalInfo(const UnicodeString &tzid) { + initialize(); + CanonicalMapEntry *entry = NULL; + UnicodeString canonicalOlsonId; + TimeZone::getOlsonCanonicalID(tzid, canonicalOlsonId); + if (!canonicalOlsonId.isEmpty()) { + if (gCanonicalMap != NULL) { + entry = (CanonicalMapEntry*)gCanonicalMap->get(tzid); + } + } + return entry; +} + +UnicodeString& +ZoneMeta::getSingleCountry(const UnicodeString &tzid, UnicodeString &country) { + UErrorCode status = U_ZERO_ERROR; + + // Get canonical country for the zone + getCanonicalCountry(tzid, country); + + if (!country.isEmpty()) { + UResourceBundle *supplementalDataBundle = ures_openDirect(NULL, gSupplementalData, &status); + UResourceBundle *zoneFormatting = ures_getByKey(supplementalDataBundle, gZoneFormattingTag, NULL, &status); + UResourceBundle *multizone = ures_getByKey(zoneFormatting, gMultizoneTag, NULL, &status); + + if (U_SUCCESS(status)) { + while (ures_hasNext(multizone)) { + int32_t len; + const UChar* multizoneCountry = ures_getNextString(multizone, &len, NULL, &status); + if (country.compare(multizoneCountry, len) == 0) { + // Included in the multizone country list + country.remove(); + break; + } + } + } + + ures_close(multizone); + ures_close(zoneFormatting); + ures_close(supplementalDataBundle); + } + + return country; +} + +UnicodeString& +ZoneMeta::getMetazoneID(const UnicodeString &tzid, UDate date, UnicodeString &result) { + UBool isSet = FALSE; + const UVector *mappings = getMetazoneMappings(tzid); + if (mappings != NULL) { + for (int32_t i = 0; i < mappings->size(); i++) { + OlsonToMetaMappingEntry *mzm = (OlsonToMetaMappingEntry*)mappings->elementAt(i); + if (mzm->from <= date && mzm->to > date) { + result.setTo(mzm->mzid, -1); + isSet = TRUE; + break; + } + } + } + if (!isSet) { + result.remove(); + } + return result; +} + +const UVector* +ZoneMeta::getMetazoneMappings(const UnicodeString &tzid) { + initialize(); + const UVector *result = NULL; + if (gOlsonToMeta != NULL) { + result = (UVector*)gOlsonToMeta->get(tzid); + } + return result; +} + +UnicodeString& +ZoneMeta::getZoneIdByMetazone(const UnicodeString &mzid, const UnicodeString ®ion, UnicodeString &result) { + initialize(); + UBool isSet = FALSE; + if (gMetaToOlson != NULL) { + UVector *mappings = (UVector*)gMetaToOlson->get(mzid); + if (mappings != NULL) { + // Find a preferred time zone for the given region. + for (int32_t i = 0; i < mappings->size(); i++) { + MetaToOlsonMappingEntry *olsonmap = (MetaToOlsonMappingEntry*)mappings->elementAt(i); + if (region.compare(olsonmap->territory, -1) == 0) { + result.setTo(olsonmap->id); + isSet = TRUE; + break; + } else if (u_strcmp(olsonmap->territory, gWorld) == 0) { + result.setTo(olsonmap->id); + isSet = TRUE; + } + } + } + } + if (!isSet) { + result.remove(); + } + return result; +} + + +U_NAMESPACE_END + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/i18n/zonemeta.h b/i18n/zonemeta.h new file mode 100644 index 00000000..d7cb1a48 --- /dev/null +++ b/i18n/zonemeta.h @@ -0,0 +1,84 @@ +/* +******************************************************************************* +* Copyright (C) 2007, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ +#ifndef ZONEMETA_H +#define ZONEMETA_H + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +#include "unicode/unistr.h" +#include "hash.h" + +U_NAMESPACE_BEGIN + +typedef struct CanonicalMapEntry { + UChar *id; + const UChar *country; // const because it's a reference to a resource bundle string. +} CanonicalMapEntry; + +typedef struct OlsonToMetaMappingEntry { + const UChar *mzid; // const because it's a reference to a resource bundle string. + UDate from; + UDate to; +} OlsonToMetaMappingEntry; + +typedef struct MetaToOlsonMappingEntry { + const UChar *id; // const because it's a reference to a resource bundle string. + UChar *territory; +} MetaToOlsonMappingEntry; + +class UVector; + +class U_I18N_API ZoneMeta { +public: + /** + * Return the canonical id for this tzid, which might be the id itself. + * If there is no canonical id for it, return the passed-in id. + */ + static UnicodeString& getCanonicalID(const UnicodeString &tzid, UnicodeString &canonicalID); + + /** + * Return the canonical country code for this tzid. If we have none, or if the time zone + * is not associated with a country, return null. + */ + static UnicodeString& getCanonicalCountry(const UnicodeString &tzid, UnicodeString &canonicalCountry); + + /** + * Return the country code if this is a 'single' time zone that can fallback to just + * the country, otherwise return empty string. (Note, one must also check the locale data + * to see that there is a localization for the country in order to implement + * tr#35 appendix J step 5.) + */ + static UnicodeString& getSingleCountry(const UnicodeString &tzid, UnicodeString &country); + + /** + * Returns a CLDR metazone ID for the given Olson tzid and time. + */ + static UnicodeString& getMetazoneID(const UnicodeString &tzid, UDate date, UnicodeString &result); + /** + * Returns an Olson ID for the ginve metazone and region + */ + static UnicodeString& getZoneIdByMetazone(const UnicodeString &mzid, const UnicodeString ®ion, UnicodeString &result); + + static const UVector* getMetazoneMappings(const UnicodeString &tzid); + +private: + static void initialize(void); + + static const CanonicalMapEntry* getCanonicalInfo(const UnicodeString &tzid); + + static Hashtable* createCanonicalMap(void); + static Hashtable* createOlsonToMetaMapOld(void); + static Hashtable* createOlsonToMetaMap(void); + static Hashtable* createMetaToOlsonMap(void); +}; + +U_NAMESPACE_END + +#endif /* #if !UCONFIG_NO_FORMATTING */ +#endif // ZONEMETA_H diff --git a/i18n/zstrfmt.cpp b/i18n/zstrfmt.cpp new file mode 100644 index 00000000..b42bbfb2 --- /dev/null +++ b/i18n/zstrfmt.cpp @@ -0,0 +1,1604 @@ +/* +******************************************************************************* +* Copyright (C) 2007, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +#include "zstrfmt.h" + +#include "unicode/ustring.h" +#include "unicode/putil.h" +#include "unicode/msgfmt.h" +#include "unicode/basictz.h" +#include "unicode/simpletz.h" +#include "unicode/rbtz.h" +#include "unicode/vtzone.h" + +#include "uvector.h" +#include "cstring.h" +#include "cmemory.h" +#include "uresimp.h" +#include "zonemeta.h" +#include "olsontz.h" +#include "umutex.h" +#include "ucln_in.h" + +/** + * global ZoneStringFormatCache stuffs + */ +static UMTX gZSFCacheLock = NULL; +static U_NAMESPACE_QUALIFIER ZSFCache *gZoneStringFormatCache = NULL; + +U_CDECL_BEGIN +/** + * ZoneStringFormatCache cleanup callback func + */ +static UBool U_CALLCONV zoneStringFormat_cleanup(void) +{ + umtx_destroy(&gZSFCacheLock); + if (gZoneStringFormatCache != NULL) { + delete gZoneStringFormatCache; + gZoneStringFormatCache = NULL; + } + gZoneStringFormatCache = NULL; + return TRUE; +} + +/** + * Deleter for ZoneStringInfo + */ +static void U_CALLCONV +deleteZoneStringInfo(void *obj) { + delete (U_NAMESPACE_QUALIFIER ZoneStringInfo*)obj; +} + +/** + * Deleter for ZoneStrings + */ +static void U_CALLCONV +deleteZoneStrings(void *obj) { + delete (U_NAMESPACE_QUALIFIER ZoneStrings*)obj; +} +U_CDECL_END + +U_NAMESPACE_BEGIN + +#define ZID_KEY_MAX 128 + +static const char gCountriesTag[] = "Countries"; +static const char gZoneStringsTag[] = "zoneStrings"; +static const char gShortGenericTag[] = "sg"; +static const char gShortStandardTag[] = "ss"; +static const char gShortDaylightTag[] = "sd"; +static const char gLongGenericTag[] = "lg"; +static const char gLongStandardTag[] = "ls"; +static const char gLongDaylightTag[] = "ld"; +static const char gExemplarCityTag[] = "ec"; +static const char gCommonlyUsedTag[] = "cu"; +static const char gFallbackFormatTag[] = "fallbackFormat"; +static const char gRegionFormatTag[] = "regionFormat"; + +#define MZID_PREFIX_LEN 5 +static const char gMetazoneIdPrefix[] = "meta:"; + +#define MAX_METAZONES_PER_ZONE 10 + +static const UChar gDefFallbackPattern[] = {0x7B, 0x31, 0x7D, 0x20, 0x28, 0x7B, 0x30, 0x7D, 0x29, 0x00}; // "{1} ({0})" +static const UChar gDefRegionPattern[] = {0x7B, 0x30, 0x7D, 0x00}; // "{0}" +static const UChar gCommonlyUsedTrue[] = {0x31, 0x00}; // "1" + +static const double kDstCheckRange = (double)184*U_MILLIS_PER_DAY; + +static int32_t +getTimeZoneTranslationTypeIndex(TimeZoneTranslationType type) { + int32_t typeIdx = 0; + switch (type) { + case LOCATION: + typeIdx = ZSIDX_LOCATION; + break; + case GENERIC_LONG: + typeIdx = ZSIDX_LONG_GENERIC; + break; + case GENERIC_SHORT: + typeIdx = ZSIDX_SHORT_GENERIC; + break; + case STANDARD_LONG: + typeIdx = ZSIDX_LONG_STANDARD; + break; + case STANDARD_SHORT: + typeIdx = ZSIDX_SHORT_STANDARD; + break; + case DAYLIGHT_LONG: + typeIdx = ZSIDX_LONG_DAYLIGHT; + break; + case DAYLIGHT_SHORT: + typeIdx = ZSIDX_SHORT_DAYLIGHT; + break; + } + return typeIdx; +} + +static int32_t +getTimeZoneTranslationType(TimeZoneTranslationTypeIndex typeIdx) { + int32_t type = 0; + switch (typeIdx) { + case ZSIDX_LOCATION: + type = LOCATION; + break; + case ZSIDX_LONG_GENERIC: + type = GENERIC_LONG; + break; + case ZSIDX_SHORT_GENERIC: + type = GENERIC_SHORT; + break; + case ZSIDX_LONG_STANDARD: + type = STANDARD_LONG; + break; + case ZSIDX_SHORT_STANDARD: + type = STANDARD_SHORT; + break; + case ZSIDX_LONG_DAYLIGHT: + type = DAYLIGHT_LONG; + break; + case ZSIDX_SHORT_DAYLIGHT: + type = DAYLIGHT_SHORT; + break; + } + return type; +} + +#define DEFAULT_CHARACTERNODE_CAPACITY 1 + +// ---------------------------------------------------------------------------- +CharacterNode::CharacterNode(UChar32 c, UObjectDeleter *valueDeleterFunc, UErrorCode &status) +: UMemory(), + fChildren(valueDeleterFunc, NULL, DEFAULT_CHARACTERNODE_CAPACITY, status), + fValues(valueDeleterFunc, NULL, DEFAULT_CHARACTERNODE_CAPACITY, status), + fValueDeleter(valueDeleterFunc), + fCharacter(c) +{ +} + +CharacterNode::~CharacterNode() { + while (!fChildren.isEmpty()) { + CharacterNode *node = (CharacterNode*)fChildren.orphanElementAt(0); + delete node; + } +} + +void +CharacterNode::addValue(void *value, UErrorCode &status) { + if (U_FAILURE(status)) { + return; + } + fValues.addElement(value, status); +} + +CharacterNode* +CharacterNode::addChildNode(UChar32 c, UErrorCode &status) { + if (U_FAILURE(status)) { + return NULL; + } + CharacterNode *result = NULL; + for (int32_t i = 0; i < fChildren.size(); i++) { + CharacterNode *node = (CharacterNode*)fChildren.elementAt(i); + if (node->getCharacter() == c) { + result = node; + break; + } + } + if (result == NULL) { + result = new CharacterNode(c, fValueDeleter, status); + fChildren.addElement(result, status); + } + + return result; +} + +CharacterNode* +CharacterNode::getChildNode(UChar32 c) const { + CharacterNode *result = NULL; + for (int32_t i = 0; i < fChildren.size(); i++) { + CharacterNode *node = (CharacterNode*)fChildren.elementAt(i); + if (node->getCharacter() == c) { + result = node; + break; + } + } + return result; +} + +// ---------------------------------------------------------------------------- +TextTrieMap::TextTrieMap(UBool ignoreCase, UObjectDeleter *valueDeleterFunc) +: UMemory(), fIgnoreCase(ignoreCase), fValueDeleter(valueDeleterFunc), fRoot(NULL) { +} + +TextTrieMap::~TextTrieMap() { + if (fRoot != NULL) { + delete fRoot; + } +} + +void +TextTrieMap::put(const UnicodeString &key, void *value, UErrorCode &status) { + if (fRoot == NULL) { + fRoot = new CharacterNode(0, fValueDeleter, status); + } + + UnicodeString keyString(key); + if (fIgnoreCase) { + keyString.foldCase(); + } + + CharacterNode *node = fRoot; + int32_t index = 0; + while (index < keyString.length()) { + UChar32 c = keyString.char32At(index); + node = node->addChildNode(c, status); + if (U_FAILURE(status)) { + return; + } + index = keyString.moveIndex32(index, 1); + } + node->addValue(value, status); +} + +void +TextTrieMap::search(const UnicodeString &text, int32_t start, + TextTrieMapSearchResultHandler *handler, UErrorCode &status) const { + if (fRoot == NULL) { + return; + } + search(fRoot, text, start, start, handler, status); +} + +void +TextTrieMap::search(CharacterNode *node, const UnicodeString &text, int32_t start, + int32_t index, TextTrieMapSearchResultHandler *handler, UErrorCode &status) const { + if (U_FAILURE(status)) { + return; + } + const UVector *values = node->getValues(); + if (values != NULL) { + if (!handler->handleMatch(index - start, values, status)) { + return; + } + if (U_FAILURE(status)) { + return; + } + } + UChar32 c = text.char32At(index); + if (fIgnoreCase) { + // size of character may grow after fold operation + UnicodeString tmp(c); + tmp.foldCase(); + int32_t tmpidx = 0; + while (tmpidx < tmp.length()) { + c = tmp.char32At(tmpidx); + node = node->getChildNode(c); + if (node == NULL) { + break; + } + tmpidx = tmp.moveIndex32(tmpidx, 1); + } + } else { + node = node->getChildNode(c); + } + if (node != NULL) { + search(node, text, start, index+1, handler, status); + } +} + +// ---------------------------------------------------------------------------- +ZoneStringInfo::ZoneStringInfo(const UnicodeString &id, const UnicodeString &str, + TimeZoneTranslationType type) +: UMemory(), fId(id), fStr(str), fType(type) { +} + +ZoneStringInfo::~ZoneStringInfo() { +} +// ---------------------------------------------------------------------------- +ZoneStringSearchResultHandler::ZoneStringSearchResultHandler(UErrorCode &status) +: UMemory(), fResults(status) +{ + clear(); +} + +ZoneStringSearchResultHandler::~ZoneStringSearchResultHandler() { + clear(); +} + +UBool +ZoneStringSearchResultHandler::handleMatch(int32_t matchLength, const UVector *values, UErrorCode &status) { + if (U_FAILURE(status)) { + return FALSE; + } + if (values != NULL) { + for (int32_t i = 0; values->size(); i++) { + ZoneStringInfo *zsinfo = (ZoneStringInfo*)values->elementAt(i); + if (zsinfo == NULL) { + break; + } + // Update the results + UBool foundType = FALSE; + for (int32_t j = 0; j < fResults.size(); j++) { + ZoneStringInfo *tmp = (ZoneStringInfo*)fResults.elementAt(j); + if (zsinfo->fType == tmp->fType) { + int32_t lenidx = getTimeZoneTranslationTypeIndex(tmp->fType); + if (matchLength > fMatchLen[lenidx]) { + // Same type, longer match + fResults.setElementAt(zsinfo, j); + fMatchLen[lenidx] = matchLength; + } + foundType = TRUE; + break; + } + } + if (!foundType) { + // not found in the current list + fResults.addElement(zsinfo, status); + fMatchLen[getTimeZoneTranslationTypeIndex(zsinfo->fType)] = matchLength; + } + } + } + return TRUE; +} + +int32_t +ZoneStringSearchResultHandler::countMatches(void) { + return fResults.size(); +} + +const ZoneStringInfo* +ZoneStringSearchResultHandler::getMatch(int32_t index, int32_t &matchLength) { + ZoneStringInfo *zsinfo = NULL; + if (index < fResults.size()) { + zsinfo = (ZoneStringInfo*)fResults.elementAt(index); + matchLength = fMatchLen[getTimeZoneTranslationTypeIndex(zsinfo->fType)]; + } + return zsinfo; +} + +void +ZoneStringSearchResultHandler::clear(void) { + fResults.removeAllElements(); + for (int32_t i = 0; i < (int32_t)(sizeof(fMatchLen)/sizeof(fMatchLen[0])); i++) { + fMatchLen[i] = 0; + } +} +// ---------------------------------------------------------------------------- +ZoneStringFormat::ZoneStringFormat(const UnicodeString* const* strings, + int32_t rowCount, int32_t columnCount, UErrorCode &status) +: UMemory(), + fLocale(""), + fTzidToStrings(uhash_compareUnicodeString, NULL, status), + fMzidToStrings(uhash_compareUnicodeString, NULL, status), + fZoneStringsTrie(TRUE, deleteZoneStringInfo) +{ + if (U_FAILURE(status)) { + return; + } + fLocale.setToBogus(); + if (strings == NULL || columnCount <= 0 || rowCount <= 0) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + + fTzidToStrings.setValueDeleter(deleteZoneStrings); + + for (int32_t row = 0; row < rowCount; row++) { + if (strings[row][0].isEmpty()) { + continue; + } + UnicodeString *names = new UnicodeString[ZSIDX_COUNT]; + for (int32_t col = 1; col < columnCount; col++) { + if (!strings[row][col].isEmpty()) { + int32_t typeIdx = -1; + switch (col) { + case 1: + typeIdx = ZSIDX_LONG_STANDARD; + break; + case 2: + typeIdx = ZSIDX_SHORT_STANDARD; + break; + case 3: + typeIdx = ZSIDX_LONG_DAYLIGHT; + break; + case 4: + typeIdx = ZSIDX_SHORT_DAYLIGHT; + break; + case 5: + typeIdx = ZSIDX_LOCATION; + break; + case 6: + typeIdx = ZSIDX_LONG_GENERIC; + break; + case 7: + typeIdx = ZSIDX_SHORT_GENERIC; + break; + } + if (typeIdx != -1) { + names[typeIdx].setTo(strings[row][col]); + + // Put the name into the trie + int32_t type = getTimeZoneTranslationType((TimeZoneTranslationTypeIndex)typeIdx); + ZoneStringInfo *zsinf = new ZoneStringInfo(strings[row][0], strings[row][col], (TimeZoneTranslationType)type); + fZoneStringsTrie.put(strings[row][col], zsinf, status); + if (U_FAILURE(status)) { + delete zsinf; + goto error_cleanup; + } + } + } + } + ZoneStrings *zstrings = new ZoneStrings(names, ZSIDX_COUNT, TRUE, NULL, 0, 0); + fTzidToStrings.put(strings[row][0], zstrings, status); + if (U_FAILURE(status)) { + delete zstrings; + goto error_cleanup; + } + } + return; + +error_cleanup: + return; +} + +ZoneStringFormat::ZoneStringFormat(const Locale &locale, UErrorCode &status) +: UMemory(), + fLocale(locale), + fTzidToStrings(uhash_compareUnicodeString, NULL, status), + fMzidToStrings(uhash_compareUnicodeString, NULL, status), + fZoneStringsTrie(TRUE, deleteZoneStringInfo) +{ + if (U_FAILURE(status)) { + return; + } + fTzidToStrings.setValueDeleter(deleteZoneStrings); + fMzidToStrings.setValueDeleter(deleteZoneStrings); + + UResourceBundle *zoneStringsArray = ures_open(NULL, locale.getName(), &status); + zoneStringsArray = ures_getByKeyWithFallback(zoneStringsArray, gZoneStringsTag, zoneStringsArray, &status); + if (U_FAILURE(status)) { + // If no locale bundles are available, zoneStrings will be null. + // We still want to go through the rest of zone strings initialization, + // because generic location format is generated from tzid for the case. + // The rest of code should work even zoneStrings is null. + status = U_ZERO_ERROR; + ures_close(zoneStringsArray); + zoneStringsArray = NULL; + } + + StringEnumeration *tzids = NULL; + MessageFormat *fallbackFmt = NULL; + MessageFormat *regionFmt = NULL; + + UResourceBundle *zoneItem = NULL; + UResourceBundle *metazoneItem = NULL; + + char zidkey[ZID_KEY_MAX]; + const UChar *zstrarray[ZSIDX_COUNT]; + const UChar *mzstrarray[ZSIDX_COUNT]; + UnicodeString mzPartialLoc[MAX_METAZONES_PER_ZONE][4]; + + UnicodeString region; + getRegion(region); + + fallbackFmt = getFallbackFormat(locale, status); + if (U_FAILURE(status)) { + goto error_cleanup; + } + regionFmt = getRegionFormat(locale, status); + if (U_FAILURE(status)) { + goto error_cleanup; + } + + tzids = TimeZone::createEnumeration(); + const char *tzid; + while ((tzid = tzids->next(NULL, status))) { + if (U_FAILURE(status)) { + goto error_cleanup; + } + // Skip non-canonical IDs + UnicodeString utzid(tzid, -1, US_INV); + UnicodeString canonicalID; + ZoneMeta::getCanonicalID(utzid, canonicalID); + if (utzid != canonicalID) { + continue; + } + + uprv_strcpy(zidkey, tzid); + + // Replace '/' with ':' + char *pCity = NULL; + char *p = zidkey; + while (*p) { + if (*p == '/') { + *p = ':'; + pCity = p + 1; + } + p++; + } + + if (zoneStringsArray != NULL) { + zoneItem = ures_getByKeyWithFallback(zoneStringsArray, zidkey, zoneItem, &status); + if (U_FAILURE(status)) { + // If failed to open the zone item, create only location string + ures_close(zoneItem); + zoneItem = NULL; + status = U_ZERO_ERROR; + } + } + zstrarray[ZSIDX_LONG_STANDARD] = getZoneStringFromBundle(zoneItem, gLongStandardTag); + zstrarray[ZSIDX_SHORT_STANDARD] = getZoneStringFromBundle(zoneItem, gShortStandardTag); + zstrarray[ZSIDX_LONG_DAYLIGHT] = getZoneStringFromBundle(zoneItem, gLongDaylightTag); + zstrarray[ZSIDX_SHORT_DAYLIGHT] = getZoneStringFromBundle(zoneItem, gShortDaylightTag); + zstrarray[ZSIDX_LONG_GENERIC] = getZoneStringFromBundle(zoneItem, gLongGenericTag); + zstrarray[ZSIDX_SHORT_GENERIC] = getZoneStringFromBundle(zoneItem, gShortGenericTag); + + // Compose location format string + UnicodeString location; + UnicodeString country; + UnicodeString city; + UnicodeString countryCode; + ZoneMeta::getCanonicalCountry(utzid, countryCode); + if (countryCode.isEmpty()) { + zstrarray[ZSIDX_LOCATION] = NULL; + } else { + const UChar* tmpCity = getZoneStringFromBundle(zoneItem, gExemplarCityTag); + if (tmpCity != NULL) { + city.setTo(TRUE, tmpCity, -1); + } else { + city.setTo(UnicodeString(pCity, -1, US_INV)); + // Replace '_' with ' ' + for (int32_t i = 0; i < city.length(); i++) { + if (city.charAt(i) == (UChar)0x5F /*'_'*/) { + city.setCharAt(i, (UChar)0x20 /*' '*/); + } + } + } + getLocalizedCountry(countryCode, locale, country); + UnicodeString singleCountry; + ZoneMeta::getSingleCountry(utzid, singleCountry); + FieldPosition fpos; + if (singleCountry.isEmpty()) { + Formattable params [] = { + Formattable(city), + Formattable(country) + }; + fallbackFmt->format(params, 2, location, fpos, status); + } else { + // If the zone is only one zone in the country, do not add city + Formattable params [] = { + Formattable(country) + }; + regionFmt->format(params, 1, location, fpos, status); + } + if (U_FAILURE(status)) { + goto error_cleanup; + } + + // Workaround for reducing UMR warning in Purify. + // Append NULL before calling getTerminatedBuffer() + int32_t locLen = location.length(); + location.append((UChar)0).truncate(locLen); + + zstrarray[ZSIDX_LOCATION] = location.getTerminatedBuffer(); + } + + UBool commonlyUsed = isCommonlyUsed(zoneItem); + + // Resolve metazones used by this zone + int32_t mzPartialLocIdx = 0; + const UVector *metazoneMappings = ZoneMeta::getMetazoneMappings(utzid); + if (metazoneMappings != NULL) { + for (int32_t i = 0; i < metazoneMappings->size(); i++) { + const OlsonToMetaMappingEntry *mzmap = (const OlsonToMetaMappingEntry*)metazoneMappings->elementAt(i); + UnicodeString mzid(mzmap->mzid); + const ZoneStrings *mzStrings = (const ZoneStrings*)fMzidToStrings.get(mzid); + if (mzStrings == NULL) { + // If the metazone strings are not yet processed, do it now. + char mzidkey[ZID_KEY_MAX]; + uprv_strcpy(mzidkey, gMetazoneIdPrefix); + u_UCharsToChars(mzmap->mzid, mzidkey + MZID_PREFIX_LEN, u_strlen(mzmap->mzid) + 1); + metazoneItem = ures_getByKeyWithFallback(zoneStringsArray, mzidkey, metazoneItem, &status); + if (U_FAILURE(status)) { + // No resources available for this metazone + // Resource bundle will be cleaned up after end of the loop. + status = U_ZERO_ERROR; + continue; + } + UBool mzCommonlyUsed = isCommonlyUsed(metazoneItem); + mzstrarray[ZSIDX_LONG_STANDARD] = getZoneStringFromBundle(metazoneItem, gLongStandardTag); + mzstrarray[ZSIDX_SHORT_STANDARD] = getZoneStringFromBundle(metazoneItem, gShortStandardTag); + mzstrarray[ZSIDX_LONG_DAYLIGHT] = getZoneStringFromBundle(metazoneItem, gLongDaylightTag); + mzstrarray[ZSIDX_SHORT_DAYLIGHT] = getZoneStringFromBundle(metazoneItem, gShortDaylightTag); + mzstrarray[ZSIDX_LONG_GENERIC] = getZoneStringFromBundle(metazoneItem, gLongGenericTag); + mzstrarray[ZSIDX_SHORT_GENERIC] = getZoneStringFromBundle(metazoneItem, gShortGenericTag); + mzstrarray[ZSIDX_LOCATION] = NULL; + + int32_t lastNonNullIdx = ZSIDX_COUNT - 1; + while (lastNonNullIdx >= 0) { + if (mzstrarray[lastNonNullIdx] != NULL) { + break; + } + lastNonNullIdx--; + } + UnicodeString *strings_mz = NULL; + ZoneStrings *tmp_mzStrings = NULL; + if (lastNonNullIdx >= 0) { + // Create UnicodeString array and put strings to the zone string trie + strings_mz = new UnicodeString[lastNonNullIdx + 1]; + + UnicodeString preferredIdForLocale; + ZoneMeta::getZoneIdByMetazone(mzid, region, preferredIdForLocale); + + for (int32_t typeidx = 0; typeidx <= lastNonNullIdx; typeidx++) { + if (mzstrarray[typeidx] != NULL) { + strings_mz[typeidx].setTo(TRUE, mzstrarray[typeidx], -1); + + // Add a metazone string to the zone string trie + int32_t type = getTimeZoneTranslationType((TimeZoneTranslationTypeIndex)typeidx); + ZoneStringInfo *zsinfo = new ZoneStringInfo(preferredIdForLocale, strings_mz[typeidx], (TimeZoneTranslationType)type); + fZoneStringsTrie.put(strings_mz[typeidx], zsinfo, status); + if (U_FAILURE(status)) { + delete zsinfo; + delete strings_mz; + goto error_cleanup; + } + } + } + tmp_mzStrings = new ZoneStrings(strings_mz, lastNonNullIdx + 1, mzCommonlyUsed, NULL, 0, 0); + } else { + // Create ZoneStrings with empty contents + tmp_mzStrings = new ZoneStrings(NULL, 0, FALSE, NULL, 0, 0); + } + + fMzidToStrings.put(mzid, tmp_mzStrings, status); + if (U_FAILURE(status)) { + delete tmp_mzStrings; + goto error_cleanup; + } + + mzStrings = tmp_mzStrings; + } + + // Compose generic partial location format + UnicodeString lg; + UnicodeString sg; + + mzStrings->getString(ZSIDX_LONG_GENERIC, lg); + mzStrings->getString(ZSIDX_SHORT_GENERIC, sg); + + if (!lg.isEmpty() || !sg.isEmpty()) { + UBool addMzPartialLocationNames = TRUE; + for (int32_t j = 0; j < mzPartialLocIdx; j++) { + if (mzPartialLoc[j][0] == mzid) { + // already processed + addMzPartialLocationNames = FALSE; + break; + } + } + if (addMzPartialLocationNames) { + UnicodeString *locationPart = NULL; + // Check if the zone is the preferred zone for the territory associated with the zone + UnicodeString preferredID; + ZoneMeta::getZoneIdByMetazone(mzid, countryCode, preferredID); + if (utzid == preferredID) { + // Use country for the location + locationPart = &country; + } else { + // Use city for the location + locationPart = &city; + } + // Reset the partial location string array + mzPartialLoc[mzPartialLocIdx][0].setTo(mzid); + mzPartialLoc[mzPartialLocIdx][1].remove(); + mzPartialLoc[mzPartialLocIdx][2].remove(); + mzPartialLoc[mzPartialLocIdx][3].remove(); + + if (locationPart != NULL) { + FieldPosition fpos; + if (!lg.isEmpty()) { + Formattable params [] = { + Formattable(*locationPart), + Formattable(lg) + }; + fallbackFmt->format(params, 2, mzPartialLoc[mzPartialLocIdx][1], fpos, status); + } + if (!sg.isEmpty()) { + Formattable params [] = { + Formattable(*locationPart), + Formattable(sg) + }; + fallbackFmt->format(params, 2, mzPartialLoc[mzPartialLocIdx][2], fpos, status); + if (mzStrings->isShortFormatCommonlyUsed()) { + mzPartialLoc[mzPartialLocIdx][3].setTo(TRUE, gCommonlyUsedTrue, -1); + } + } + if (U_FAILURE(status)) { + goto error_cleanup; + } + } + mzPartialLocIdx++; + } + } + } + } + // Collected names for a zone + + // Create UnicodeString array for localized zone strings + int32_t lastIdx = ZSIDX_COUNT - 1; + while (lastIdx >= 0) { + if (zstrarray[lastIdx] != NULL) { + break; + } + lastIdx--; + } + UnicodeString *strings = NULL; + int32_t stringsCount = lastIdx + 1; + + if (stringsCount > 0) { + strings = new UnicodeString[stringsCount]; + for (int32_t i = 0; i < stringsCount; i++) { + if (zstrarray[i] != NULL) { + strings[i].setTo(zstrarray[i], -1); + + // Add names to the trie + int32_t type = getTimeZoneTranslationType((TimeZoneTranslationTypeIndex)i); + ZoneStringInfo *zsinfo = new ZoneStringInfo(utzid, strings[i], (TimeZoneTranslationType)type); + fZoneStringsTrie.put(strings[i], zsinfo, status); + if (U_FAILURE(status)) { + delete zsinfo; + delete[] strings; + goto error_cleanup; + } + } + } + } + + // Create UnicodeString array for generic partial location strings + UnicodeString **genericPartialLocationNames = NULL; + int32_t genericPartialRowCount = mzPartialLocIdx; + int32_t genericPartialColCount = 4; + + if (genericPartialRowCount != 0) { + genericPartialLocationNames = (UnicodeString**)uprv_malloc(genericPartialRowCount * sizeof(UnicodeString*)); + if (genericPartialLocationNames == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + delete[] strings; + goto error_cleanup; + } + for (int32_t i = 0; i < genericPartialRowCount; i++) { + genericPartialLocationNames[i] = new UnicodeString[genericPartialColCount]; + for (int32_t j = 0; j < genericPartialColCount; j++) { + genericPartialLocationNames[i][j].setTo(mzPartialLoc[i][j]); + // Add names to the trie + if ((j == 1 || j == 2) &&!genericPartialLocationNames[i][j].isEmpty()) { + ZoneStringInfo *zsinfo; + TimeZoneTranslationType type = (j == 1) ? GENERIC_LONG : GENERIC_SHORT; + zsinfo = new ZoneStringInfo(utzid, genericPartialLocationNames[i][j], type); + fZoneStringsTrie.put(genericPartialLocationNames[i][j], zsinfo, status); + if (U_FAILURE(status)) { + delete[] genericPartialLocationNames[i]; + uprv_free(genericPartialLocationNames); + delete[] strings; + goto error_cleanup; + } + } + } + } + } + + // Finally, create ZoneStrings instance and put it into the tzidToStinrgs map + ZoneStrings *zstrings = new ZoneStrings(strings, stringsCount, commonlyUsed, + genericPartialLocationNames, genericPartialRowCount, genericPartialColCount); + + fTzidToStrings.put(utzid, zstrings, status); + if (U_FAILURE(status)) { + delete zstrings; + goto error_cleanup; + } + } + +error_cleanup: + if (fallbackFmt != NULL) { + delete fallbackFmt; + } + if (regionFmt != NULL) { + delete regionFmt; + } + if (tzids != NULL) { + delete tzids; + } + ures_close(zoneItem); + ures_close(metazoneItem); + ures_close(zoneStringsArray); +} + +ZoneStringFormat::~ZoneStringFormat() { +} + +SafeZoneStringFormatPtr* +ZoneStringFormat::getZoneStringFormat(const Locale& locale, UErrorCode &status) { + umtx_lock(&gZSFCacheLock); + if (gZoneStringFormatCache == NULL) { + gZoneStringFormatCache = new ZSFCache(10 /* capacity */); + ucln_i18n_registerCleanup(UCLN_I18N_ZSFORMAT, zoneStringFormat_cleanup); + } + umtx_unlock(&gZSFCacheLock); + + return gZoneStringFormatCache->get(locale, status); +} + + +UnicodeString** +ZoneStringFormat::createZoneStringsArray(UDate date, int32_t &rowCount, int32_t &colCount, UErrorCode &status) const { + if (U_FAILURE(status)) { + return NULL; + } + UnicodeString **result = NULL; + rowCount = 0; + colCount = 0; + + // Collect canonical time zone IDs + UVector canonicalIDs(uhash_deleteUnicodeString, uhash_compareUnicodeString, status); + if (U_FAILURE(status)) { + return NULL; + } + StringEnumeration *tzids = TimeZone::createEnumeration(); + const UChar *tzid; + while ((tzid = tzids->unext(NULL, status))) { + if (U_FAILURE(status)) { + delete tzids; + return NULL; + } + UnicodeString utzid(tzid); + UnicodeString canonicalID; + ZoneMeta::getCanonicalID(UnicodeString(tzid), canonicalID); + if (utzid == canonicalID) { + canonicalIDs.addElement(new UnicodeString(utzid), status); + if (U_FAILURE(status)) { + delete tzids; + return NULL; + } + } + } + delete tzids; + + // Allocate array + result = (UnicodeString**)uprv_malloc(canonicalIDs.size() * sizeof(UnicodeString*)); + if (result == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + return NULL; + } + for (int32_t i = 0; i < canonicalIDs.size(); i++) { + result[i] = new UnicodeString[8]; + UnicodeString *id = (UnicodeString*)canonicalIDs.elementAt(i); + result[i][0].setTo(*id); + getLongStandard(*id, date, result[i][1]); + getShortStandard(*id, date, FALSE, result[i][2]); + getLongDaylight(*id, date, result[i][3]); + getShortDaylight(*id, date, FALSE, result[i][4]); + getGenericLocation(*id, result[i][5]); + getLongGenericNonLocation(*id, date, result[i][6]); + getShortGenericNonLocation(*id, date, FALSE, result[i][7]); + } + + rowCount = canonicalIDs.size(); + colCount = 8; + return result; +} + +UnicodeString& +ZoneStringFormat::getSpecificLongString(const Calendar &cal, UnicodeString &result, + UErrorCode &status) const { + result.remove(); + if (U_FAILURE(status)) { + return result; + } + UnicodeString tzid; + cal.getTimeZone().getID(tzid); + UDate date = cal.getTime(status); + if (cal.get(UCAL_DST_OFFSET, status) == 0) { + return getString(tzid, ZSIDX_LONG_STANDARD, date, FALSE /*not used*/, result); + } else { + return getString(tzid, ZSIDX_LONG_DAYLIGHT, date, FALSE /*not used*/, result); + } +} + +UnicodeString& +ZoneStringFormat::getSpecificShortString(const Calendar &cal, UBool commonlyUsedOnly, + UnicodeString &result, UErrorCode &status) const { + result.remove(); + if (U_FAILURE(status)) { + return result; + } + UnicodeString tzid; + cal.getTimeZone().getID(tzid); + UDate date = cal.getTime(status); + if (cal.get(UCAL_DST_OFFSET, status) == 0) { + return getString(tzid, ZSIDX_SHORT_STANDARD, date, commonlyUsedOnly, result); + } else { + return getString(tzid, ZSIDX_SHORT_DAYLIGHT, date, commonlyUsedOnly, result); + } +} + +UnicodeString& +ZoneStringFormat::getGenericLongString(const Calendar &cal, UnicodeString &result, + UErrorCode &status) const { + return getGenericString(cal, FALSE /*long*/, FALSE /* not used */, result, status); +} + +UnicodeString& +ZoneStringFormat::getGenericShortString(const Calendar &cal, UBool commonlyUsedOnly, + UnicodeString &result, UErrorCode &status) const { + return getGenericString(cal, TRUE /*short*/, commonlyUsedOnly, result, status); +} + +UnicodeString& +ZoneStringFormat::getGenericLocationString(const Calendar &cal, UnicodeString &result, + UErrorCode &status) const { + UnicodeString tzid; + cal.getTimeZone().getID(tzid); + UDate date = cal.getTime(status); + return getString(tzid, ZSIDX_LOCATION, date, FALSE /*not used*/, result); +} + +const ZoneStringInfo* +ZoneStringFormat::findSpecificLong(const UnicodeString &text, int32_t start, + int32_t &matchLength, UErrorCode &status) const { + return find(text, start, STANDARD_LONG | DAYLIGHT_LONG, matchLength, status); +} + +const ZoneStringInfo* +ZoneStringFormat::findSpecificShort(const UnicodeString &text, int32_t start, + int32_t &matchLength, UErrorCode &status) const { + return find(text, start, STANDARD_SHORT | DAYLIGHT_SHORT, matchLength, status); +} + +const ZoneStringInfo* +ZoneStringFormat::findGenericLong(const UnicodeString &text, int32_t start, + int32_t &matchLength, UErrorCode &status) const { + return find(text, start, GENERIC_LONG | STANDARD_LONG | LOCATION, matchLength, status); +} + +const ZoneStringInfo* +ZoneStringFormat::findGenericShort(const UnicodeString &text, int32_t start, + int32_t &matchLength, UErrorCode &status) const { + return find(text, start, GENERIC_SHORT | STANDARD_SHORT | LOCATION, matchLength, status); +} + +const ZoneStringInfo* +ZoneStringFormat::findGenericLocation(const UnicodeString &text, int32_t start, + int32_t &matchLength, UErrorCode &status) const { + return find(text, start, LOCATION, matchLength, status); +} + +UnicodeString& +ZoneStringFormat::getString(const UnicodeString &tzid, TimeZoneTranslationTypeIndex typeIdx, UDate date, + UBool commonlyUsedOnly, UnicodeString& result) const { + result.remove(); + + // ICU's own array does not have entries for aliases + UnicodeString canonicalID; + ZoneMeta::getCanonicalID(tzid, canonicalID); + + if (fTzidToStrings.count() > 0) { + ZoneStrings *zstrings = (ZoneStrings*)fTzidToStrings.get(canonicalID); + if (zstrings != NULL) { + switch (typeIdx) { + case ZSIDX_LONG_STANDARD: + case ZSIDX_LONG_DAYLIGHT: + case ZSIDX_LONG_GENERIC: + case ZSIDX_LOCATION: + zstrings->getString(typeIdx, result); + break; + case ZSIDX_SHORT_STANDARD: + case ZSIDX_SHORT_DAYLIGHT: + case ZSIDX_SHORT_GENERIC: + if (!commonlyUsedOnly || zstrings->isShortFormatCommonlyUsed()) { + zstrings->getString(typeIdx, result); + } + break; + } + } + } + if (result.isEmpty() && fMzidToStrings.count() > 0 && typeIdx != ZSIDX_LOCATION) { + // Try metazone + UnicodeString mzid; + ZoneMeta::getMetazoneID(canonicalID, date, mzid); + if (!mzid.isEmpty()) { + ZoneStrings *mzstrings = (ZoneStrings*)fMzidToStrings.get(mzid); + if (mzstrings != NULL) { + switch (typeIdx) { + case ZSIDX_LONG_STANDARD: + case ZSIDX_LONG_DAYLIGHT: + case ZSIDX_LONG_GENERIC: + case ZSIDX_LOCATION: + mzstrings->getString(typeIdx, result); + break; + case ZSIDX_SHORT_STANDARD: + case ZSIDX_SHORT_DAYLIGHT: + case ZSIDX_SHORT_GENERIC: + if (!commonlyUsedOnly || mzstrings->isShortFormatCommonlyUsed()) { + mzstrings->getString(typeIdx, result); + } + break; + } + } + } + } + return result; +} + +UnicodeString& +ZoneStringFormat::getGenericString(const Calendar &cal, UBool isShort, UBool commonlyUsedOnly, + UnicodeString &result, UErrorCode &status) const { + result.remove(); + UDate time = cal.getTime(status); + if (U_FAILURE(status)) { + return result; + } + const TimeZone &tz = cal.getTimeZone(); + UnicodeString tzid; + tz.getID(tzid); + + // ICU's own array does not have entries for aliases + UnicodeString canonicalID; + ZoneMeta::getCanonicalID(tzid, canonicalID); + + ZoneStrings *zstrings; + if (fTzidToStrings.count() > 0) { + zstrings = (ZoneStrings*)fTzidToStrings.get(canonicalID); + if (zstrings != NULL) { + if (isShort) { + if (!commonlyUsedOnly || zstrings->isShortFormatCommonlyUsed()) { + zstrings->getString(ZSIDX_SHORT_GENERIC, result); + } + } else { + zstrings->getString(ZSIDX_LONG_GENERIC, result); + } + } + } + if (result.isEmpty() && fMzidToStrings.count() > 0) { + // try metazone + int32_t raw, sav; + UnicodeString mzid; + ZoneMeta::getMetazoneID(canonicalID, time, mzid); + if (!mzid.isEmpty()) { + UBool useStandard = FALSE; + sav = cal.get(UCAL_DST_OFFSET, status); + if (U_FAILURE(status)) { + return result; + } + if (sav == 0) { + useStandard = TRUE; + // Check if the zone actually uses daylight saving time around the time + TimeZone *tmptz = tz.clone(); + BasicTimeZone *btz = NULL; + if (tmptz->getDynamicClassID() == OlsonTimeZone::getStaticClassID() + || tmptz->getDynamicClassID() == SimpleTimeZone::getStaticClassID() + || tmptz->getDynamicClassID() == RuleBasedTimeZone::getStaticClassID() + || tmptz->getDynamicClassID() == VTimeZone::getStaticClassID()) { + btz = (BasicTimeZone*)tmptz; + } + + if (btz != NULL) { + TimeZoneTransition before; + UBool beforTrs = btz->getPreviousTransition(time, TRUE, before); + if (beforTrs + && (time - before.getTime() < kDstCheckRange) + && before.getFrom()->getDSTSavings() != 0) { + useStandard = FALSE; + } else { + TimeZoneTransition after; + UBool afterTrs = btz->getNextTransition(time, FALSE, after); + if (afterTrs + && (after.getTime() - time < kDstCheckRange) + && after.getTo()->getDSTSavings() != 0) { + useStandard = FALSE; + } + } + } else { + // If not BasicTimeZone... only if the instance is not an ICU's implementation. + // We may get a wrong answer in edge case, but it should practically work OK. + tmptz->getOffset(time - kDstCheckRange, FALSE, raw, sav, status); + if (sav != 0) { + useStandard = FALSE; + } else { + tmptz->getOffset(time + kDstCheckRange, FALSE, raw, sav, status); + if (sav != 0){ + useStandard = FALSE; + } + } + if (U_FAILURE(status)) { + delete tmptz; + result.remove(); + return result; + } + } + delete tmptz; + } + if (useStandard) { + getString(canonicalID, (isShort ? ZSIDX_SHORT_STANDARD : ZSIDX_LONG_STANDARD), + time, commonlyUsedOnly, result); + + // Note: + // In CLDR 1.5.1, a same localization is used for both generic and standard + // for some metazones in some locales. This is actually data bugs and should + // be resolved in later versions of CLDR. For now, we check if the standard + // name is different from its generic name below. + if (!result.isEmpty()) { + UnicodeString genericNonLocation; + getString(canonicalID, (isShort ? ZSIDX_SHORT_GENERIC : ZSIDX_LONG_GENERIC), + time, commonlyUsedOnly, genericNonLocation); + if (!genericNonLocation.isEmpty() && result == genericNonLocation) { + result.remove(); + } + } + } + if (result.isEmpty()) { + ZoneStrings *mzstrings = (ZoneStrings*)fMzidToStrings.get(mzid); + if (mzstrings != NULL) { + if (isShort) { + if (!commonlyUsedOnly || mzstrings->isShortFormatCommonlyUsed()) { + mzstrings->getString(ZSIDX_SHORT_GENERIC, result); + } + } else { + mzstrings->getString(ZSIDX_LONG_GENERIC, result); + } + } + if (!result.isEmpty()) { + // Check if the offsets at the given time matches the preferred zone's offsets + UnicodeString preferredId; + UnicodeString region; + ZoneMeta::getZoneIdByMetazone(mzid, getRegion(region), preferredId); + if (canonicalID != preferredId) { + // Check if the offsets at the given time are identical with the preferred zone + raw = cal.get(UCAL_ZONE_OFFSET, status); + if (U_FAILURE(status)) { + result.remove(); + return result; + } + TimeZone *preferredZone = TimeZone::createTimeZone(preferredId); + int32_t prfRaw, prfSav; + // Check offset in preferred time zone with wall time. + // With getOffset(time, false, preferredOffsets), + // you may get incorrect results because of time overlap at DST->STD + // transition. + preferredZone->getOffset(time + raw + sav, TRUE, prfRaw, prfSav, status); + delete preferredZone; + + if (U_FAILURE(status)) { + result.remove(); + return result; + } + if ((raw != prfRaw || sav != prfSav) && zstrings != NULL) { + // Use generic partial location string as fallback + zstrings->getGenericPartialLocationString(mzid, isShort, commonlyUsedOnly, result); + } + } + } + } + } + } + if (result.isEmpty()) { + // Use location format as the final fallback + getString(canonicalID, ZSIDX_LOCATION, time, FALSE /*not used*/, result); + } + + return result; +} + +UnicodeString& +ZoneStringFormat::getGenericPartialLocationString(const UnicodeString &tzid, UBool isShort, + UDate date, UBool commonlyUsedOnly, UnicodeString &result) const { + result.remove(); + if (fTzidToStrings.count() <= 0) { + return result; + } + + UnicodeString canonicalID; + ZoneMeta::getCanonicalID(tzid, canonicalID); + + UnicodeString mzid; + ZoneMeta::getMetazoneID(canonicalID, date, mzid); + + if (!mzid.isEmpty()) { + ZoneStrings *zstrings = (ZoneStrings*)fTzidToStrings.get(canonicalID); + if (zstrings != NULL) { + zstrings->getGenericPartialLocationString(mzid, isShort, commonlyUsedOnly, result); + } + } + return result; +} + +const ZoneStringInfo* +ZoneStringFormat::find(const UnicodeString &text, int32_t start, int32_t types, + int32_t &matchLength, UErrorCode &status) const { + matchLength = 0; + if (U_FAILURE(status)) { + return NULL; + } + if (fZoneStringsTrie.isEmpty()) { + return NULL; + } + const ZoneStringInfo *result = NULL; + const ZoneStringInfo *fallback = NULL; + int32_t fallbackMatchLen = 0; + + ZoneStringSearchResultHandler handler(status); + fZoneStringsTrie.search(text, start, (TextTrieMapSearchResultHandler*)&handler, status); + if (U_SUCCESS(status)) { + int32_t numMatches = handler.countMatches(); + for (int32_t i = 0; i < numMatches; i++) { + int32_t tmpMatchLen; + const ZoneStringInfo *tmp = handler.getMatch(i, tmpMatchLen); + if ((types & tmp->fType) != 0) { + if (result == NULL || matchLength < tmpMatchLen) { + result = tmp; + matchLength = tmpMatchLen; + } else if (matchLength == tmpMatchLen) { + // Tie breaker - there are some examples that a + // long standard name is identical with a location + // name - for example, "Uruguay Time". In this case, + // we interpret it as generic, not specific. + if (tmp->isGeneric() && !result->isGeneric()) { + result = tmp; + } + } + } else if (result == NULL) { + if (fallback == NULL || fallbackMatchLen < tmpMatchLen) { + fallback = tmp; + fallbackMatchLen = tmpMatchLen; + } else if (fallbackMatchLen == tmpMatchLen) { + if (tmp->isGeneric() && !fallback->isGeneric()) { + fallback = tmp; + } + } + } + } + if (result == NULL && fallback != NULL) { + result = fallback; + matchLength = fallbackMatchLen; + } + } + return result; +} + + +UnicodeString& +ZoneStringFormat::getRegion(UnicodeString ®ion) const { + const char* country = fLocale.getCountry(); + // TODO: Utilize addLikelySubtag in Locale to resolve default region + // when the implementation is ready. + region.setTo(UnicodeString(country, -1, US_INV)); + return region; +} + +MessageFormat* +ZoneStringFormat::getFallbackFormat(const Locale &locale, UErrorCode &status) { + if (U_FAILURE(status)) { + return NULL; + } + UnicodeString pattern(TRUE, gDefFallbackPattern, -1); + UResourceBundle *zoneStringsArray = ures_open(NULL, locale.getName(), &status); + zoneStringsArray = ures_getByKeyWithFallback(zoneStringsArray, gZoneStringsTag, zoneStringsArray, &status); + int32_t len; + const UChar *flbkfmt = ures_getStringByKeyWithFallback(zoneStringsArray, gFallbackFormatTag, &len, &status); + if (U_SUCCESS(status)) { + pattern.setTo(flbkfmt); + } else { + status = U_ZERO_ERROR; + } + ures_close(zoneStringsArray); + + MessageFormat *fallbackFmt = new MessageFormat(pattern, status); + return fallbackFmt; +} + +MessageFormat* +ZoneStringFormat::getRegionFormat(const Locale& locale, UErrorCode &status) { + if (U_FAILURE(status)) { + return NULL; + } + UnicodeString pattern(TRUE, gDefRegionPattern, -1); + UResourceBundle *zoneStringsArray = ures_open(NULL, locale.getName(), &status); + zoneStringsArray = ures_getByKeyWithFallback(zoneStringsArray, gZoneStringsTag, zoneStringsArray, &status); + int32_t len; + const UChar *regionfmt = ures_getStringByKeyWithFallback(zoneStringsArray, gRegionFormatTag, &len, &status); + if (U_SUCCESS(status)) { + pattern.setTo(regionfmt); + } else { + status = U_ZERO_ERROR; + } + ures_close(zoneStringsArray); + + MessageFormat *regionFmt = new MessageFormat(pattern, status); + return regionFmt; +} + +const UChar* +ZoneStringFormat::getZoneStringFromBundle(const UResourceBundle *zoneitem, const char *key) { + const UChar *str = NULL; + if (zoneitem != NULL) { + UErrorCode status = U_ZERO_ERROR; + int32_t len; + str = ures_getStringByKeyWithFallback(zoneitem, key, &len, &status); + if (U_FAILURE(status)) { + str = NULL; + } + } + return str; +} + +UBool +ZoneStringFormat::isCommonlyUsed(const UResourceBundle *zoneitem) { + if (zoneitem == NULL) { + return TRUE; + } + + UBool commonlyUsed = FALSE; + UErrorCode status = U_ZERO_ERROR; + UResourceBundle *cuRes = ures_getByKey(zoneitem, gCommonlyUsedTag, NULL, &status); + int32_t cuValue = ures_getInt(cuRes, &status); + if (U_SUCCESS(status)) { + if (cuValue == 1) { + commonlyUsed = TRUE; + } + } + ures_close(cuRes); + return commonlyUsed; +} + +UnicodeString& +ZoneStringFormat::getLocalizedCountry(const UnicodeString &countryCode, const Locale &locale, UnicodeString &displayCountry) { + // We do not want to use display country names only from the target language bundle + // Note: we should do this in better way. + displayCountry.remove(); + int32_t ccLen = countryCode.length(); + if (ccLen > 0 && ccLen < ULOC_COUNTRY_CAPACITY) { + UErrorCode status = U_ZERO_ERROR; + UResourceBundle *localeBundle = ures_open(NULL, locale.getName(), &status); + if (U_SUCCESS(status)) { + const char *bundleLocStr = ures_getLocale(localeBundle, &status); + if (U_SUCCESS(status) && uprv_strlen(bundleLocStr) > 0) { + Locale bundleLoc(bundleLocStr); + if (uprv_strcmp(bundleLocStr, "root") != 0 && uprv_strcmp(bundleLoc.getLanguage(), locale.getLanguage()) == 0) { + // Create a fake locale strings + char tmpLocStr[ULOC_COUNTRY_CAPACITY + 3]; + uprv_strcpy(tmpLocStr, "xx_"); + u_UCharsToChars(countryCode.getBuffer(), &tmpLocStr[3], ccLen); + tmpLocStr[3 + ccLen] = 0; + + Locale tmpLoc(tmpLocStr); + tmpLoc.getDisplayCountry(locale, displayCountry); + } + } + } + ures_close(localeBundle); + } + if (displayCountry.isEmpty()) { + // Use the country code as the fallback + displayCountry.setTo(countryCode); + } + return displayCountry; +} + +// ---------------------------------------------------------------------------- +/* + * This constructor adopts the input UnicodeString arrays. + */ +ZoneStrings::ZoneStrings(UnicodeString *strings, int32_t stringsCount, UBool commonlyUsed, + UnicodeString **genericPartialLocationStrings, int32_t genericRowCount, int32_t genericColCount) +: UMemory(), fStrings(strings), fStringsCount(stringsCount), fIsCommonlyUsed(commonlyUsed), + fGenericPartialLocationStrings(genericPartialLocationStrings), + fGenericPartialLocationRowCount(genericRowCount), fGenericPartialLocationColCount(genericColCount) { +} + +ZoneStrings::~ZoneStrings() { + if (fStrings != NULL) { + delete[] fStrings; + } + if (fGenericPartialLocationStrings != NULL) { + for (int32_t i = 0; i < fGenericPartialLocationRowCount; i++) { + delete[] fGenericPartialLocationStrings[i]; + } + uprv_free(fGenericPartialLocationStrings); + } +} + + +UnicodeString& +ZoneStrings::getString(int32_t typeIdx, UnicodeString &result) const { + if (typeIdx >= 0 && typeIdx < fStringsCount) { + result.setTo(fStrings[typeIdx]); + } else { + result.remove(); + } + return result; +} + +UnicodeString& +ZoneStrings::getGenericPartialLocationString(const UnicodeString &mzid, UBool isShort, + UBool commonlyUsedOnly, UnicodeString &result) const { + UBool isSet = FALSE; + if (fGenericPartialLocationColCount >= 2) { + for (int32_t i = 0; i < fGenericPartialLocationRowCount; i++) { + if (fGenericPartialLocationStrings[i][0] == mzid) { + if (isShort) { + if (fGenericPartialLocationColCount >= 3) { + if (!commonlyUsedOnly || + fGenericPartialLocationColCount == 3 || fGenericPartialLocationStrings[i][3].length() != 0) { + result.setTo(fGenericPartialLocationStrings[i][2]); + isSet = TRUE; + } + } + } else { + result.setTo(fGenericPartialLocationStrings[i][1]); + isSet = TRUE; + } + break; + } + } + } + if (!isSet) { + result.remove(); + } + return result; +} + +// -------------------------------------------------------------- +SafeZoneStringFormatPtr::SafeZoneStringFormatPtr(ZSFCacheEntry *cacheEntry) +: UMemory(), fCacheEntry(cacheEntry) { +} + +SafeZoneStringFormatPtr::~SafeZoneStringFormatPtr() { + fCacheEntry->delRef(); +} + +const ZoneStringFormat* +SafeZoneStringFormatPtr::get() const { + return fCacheEntry->getZoneStringFormat(); +} + +ZSFCacheEntry::ZSFCacheEntry(const Locale &locale, ZoneStringFormat *zsf, ZSFCacheEntry *next) +: UMemory(), fLocale(locale), fZoneStringFormat(zsf), + fNext(next), fRefCount(1) +{ +} + +ZSFCacheEntry::~ZSFCacheEntry () { + delete fZoneStringFormat; +} + +const ZoneStringFormat* +ZSFCacheEntry::getZoneStringFormat(void) { + return (const ZoneStringFormat*)fZoneStringFormat; +} + +void +ZSFCacheEntry::delRef(void) { + umtx_lock(&gZSFCacheLock); + --fRefCount; + umtx_unlock(&gZSFCacheLock); +} + +ZSFCache::ZSFCache(int32_t capacity) +: UMemory(), fCapacity(capacity), fFirst(NULL) { +} + +ZSFCache::~ZSFCache() { + ZSFCacheEntry *entry = fFirst; + while (entry) { + ZSFCacheEntry *next = entry->fNext; + delete entry; + entry = next; + } +} + +SafeZoneStringFormatPtr* +ZSFCache::get(const Locale &locale, UErrorCode &status) { + SafeZoneStringFormatPtr *result = NULL; + + // Search the cache entry list + ZSFCacheEntry *entry = NULL; + ZSFCacheEntry *next, *prev; + + umtx_lock(&gZSFCacheLock); + entry = fFirst; + prev = NULL; + while (entry) { + next = entry->fNext; + if (entry->fLocale == locale) { + // Add reference count + entry->fRefCount++; + + // move the entry to the top + if (entry != fFirst) { + prev->fNext = next; + entry->fNext = fFirst; + fFirst = entry; + } + break; + } + prev = entry; + entry = next; + } + umtx_unlock(&gZSFCacheLock); + + // Create a new ZoneStringFormat + if (entry == NULL) { + ZoneStringFormat *zsf = new ZoneStringFormat(locale, status); + if (U_FAILURE(status)) { + return NULL; + } + // Now add the new entry + umtx_lock(&gZSFCacheLock); + // Make sure no other threads already created the one for the same locale + entry = fFirst; + prev = NULL; + while (entry) { + next = entry->fNext; + if (entry->fLocale == locale) { + // Add reference count + entry->fRefCount++; + + // move the entry to the top + if (entry != fFirst) { + prev->fNext = next; + entry->fNext = fFirst; + fFirst = entry; + } + break; + } + prev = entry; + entry = next; + } + if (entry == NULL) { + // Add the new one to the top + next = fFirst; + entry = new ZSFCacheEntry(locale, zsf, next); + fFirst = entry; + } else { + delete zsf; + } + umtx_unlock(&gZSFCacheLock); + } + + result = new SafeZoneStringFormatPtr(entry); + + // Now, delete unused cache entries beyond the capacity + umtx_lock(&gZSFCacheLock); + entry = fFirst; + prev = NULL; + int32_t idx = 1; + while (entry) { + next = entry->fNext; + if (idx >= fCapacity && entry->fRefCount == 0) { + if (entry == fFirst) { + fFirst = next; + } else { + prev->fNext = next; + } + delete entry; + } else { + prev = entry; + } + entry = next; + idx++; + } + umtx_unlock(&gZSFCacheLock); + + return result; +} + +U_NAMESPACE_END + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/i18n/zstrfmt.h b/i18n/zstrfmt.h new file mode 100644 index 00000000..6765d6dd --- /dev/null +++ b/i18n/zstrfmt.h @@ -0,0 +1,442 @@ +/* +******************************************************************************* +* Copyright (C) 2007, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ +#ifndef ZSTRFMT_H +#define ZSTRFMT_H + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +#include "unicode/unistr.h" +#include "unicode/calendar.h" +#include "hash.h" +#include "uvector.h" + +U_NAMESPACE_BEGIN + +/* + * Character node used by TextTrieMap + */ +class CharacterNode : public UMemory { +public: + CharacterNode(UChar32 c, UObjectDeleter *fn, UErrorCode &status); + virtual ~CharacterNode(); + + inline UChar32 getCharacter(void) const; + inline const UVector* getValues(void) const; + inline const UVector* getChildNodes(void) const; + + void addValue(void *value, UErrorCode &status); + CharacterNode* addChildNode(UChar32 c, UErrorCode &status); + CharacterNode* getChildNode(UChar32 c) const; + +private: + UVector fChildren; + UVector fValues; + UObjectDeleter *fValueDeleter; + UChar32 fCharacter; +}; + +inline UChar32 CharacterNode::getCharacter(void) const { + return fCharacter; +} + +inline const UVector* CharacterNode::getValues(void) const { + return &fValues; +} + +inline const UVector* CharacterNode::getChildNodes(void) const { + return &fChildren; +} + +/* + * Search result handler callback interface used by TextTrieMap search. + */ +class TextTrieMapSearchResultHandler { +public: + virtual UBool handleMatch(int32_t matchLength, + const UVector *values, UErrorCode& status) = 0; +}; + +/** + * TextTrieMap is a trie implementation for supporting + * fast prefix match for the string key. + */ +class TextTrieMap : public UMemory { +public: + TextTrieMap(UBool ignoreCase, UObjectDeleter *valueDeleterFunc); + virtual ~TextTrieMap(); + + void put(const UnicodeString &key, void *value, UErrorCode &status); + void search(const UnicodeString &text, int32_t start, + TextTrieMapSearchResultHandler *handler, UErrorCode& status) const; + inline int32_t isEmpty() const; + +private: + UBool fIgnoreCase; + UObjectDeleter *fValueDeleter; + CharacterNode *fRoot; + + void search(CharacterNode *node, const UnicodeString &text, int32_t start, + int32_t index, TextTrieMapSearchResultHandler *handler, UErrorCode &status) const; +}; + +inline UChar32 TextTrieMap::isEmpty(void) const { + return fRoot == NULL; +} + +// Name types, these bit flag are used for zone string lookup +enum TimeZoneTranslationType { + LOCATION = 0x0001, + GENERIC_LONG = 0x0002, + GENERIC_SHORT = 0x0004, + STANDARD_LONG = 0x0008, + STANDARD_SHORT = 0x0010, + DAYLIGHT_LONG = 0x0020, + DAYLIGHT_SHORT = 0x0040 +}; + +// Name type index, these constants are used for index in the zone strings array. +enum TimeZoneTranslationTypeIndex { + ZSIDX_LOCATION = 0, + ZSIDX_LONG_STANDARD, + ZSIDX_SHORT_STANDARD, + ZSIDX_LONG_DAYLIGHT, + ZSIDX_SHORT_DAYLIGHT, + ZSIDX_LONG_GENERIC, + ZSIDX_SHORT_GENERIC, + + ZSIDX_COUNT +}; + +class MessageFormat; + +/* + * ZoneStringInfo is a class holding a localized zone string + * information. + */ +class ZoneStringInfo : public UMemory { +public: + virtual ~ZoneStringInfo(); + + inline UnicodeString& getID(UnicodeString &result) const; + inline UnicodeString& getString(UnicodeString &result) const; + inline UBool isStandard(void) const; + inline UBool isDaylight(void) const; + inline UBool isGeneric(void) const; + +private: + friend class ZoneStringFormat; + friend class ZoneStringSearchResultHandler; + + ZoneStringInfo(const UnicodeString &id, const UnicodeString &str, TimeZoneTranslationType type); + + UnicodeString fId; + UnicodeString fStr; + TimeZoneTranslationType fType; +}; + +inline UnicodeString& ZoneStringInfo::getID(UnicodeString &result) const { + return result.setTo(fId); +} + +inline UnicodeString& ZoneStringInfo::getString(UnicodeString &result) const { + return result.setTo(fStr); +} + +inline UBool ZoneStringInfo::isStandard(void) const { + return (fType == STANDARD_LONG || fType == STANDARD_SHORT); +} + +inline UBool ZoneStringInfo::isDaylight(void) const { + return (fType == DAYLIGHT_LONG || fType == DAYLIGHT_SHORT); +} + +inline UBool ZoneStringInfo::isGeneric(void) const { + return (fType == LOCATION || fType == GENERIC_LONG || fType == GENERIC_SHORT); +} + +class SafeZoneStringFormatPtr; + +class ZoneStringFormat : public UMemory { +public: + ZoneStringFormat(const UnicodeString* const* strings, int32_t rowCount, int32_t columnCount, UErrorCode &status); + ZoneStringFormat(const Locale& locale, UErrorCode &status); + virtual ~ZoneStringFormat(); + + static SafeZoneStringFormatPtr* getZoneStringFormat(const Locale& locale, UErrorCode &status); + + /* + * Create a snapshot of old zone strings array for the given date + */ + UnicodeString** createZoneStringsArray(UDate date, int32_t &rowCount, int32_t &colCount, UErrorCode &status) const; + + const UnicodeString** getZoneStrings(int32_t &rowCount, int32_t &columnCount) const; + + UnicodeString& getSpecificLongString(const Calendar &cal, + UnicodeString &result, UErrorCode &status) const; + + UnicodeString& getSpecificShortString(const Calendar &cal, + UBool commonlyUsedOnly, UnicodeString &result, UErrorCode &status) const; + + UnicodeString& getGenericLongString(const Calendar &cal, + UnicodeString &result, UErrorCode &status) const; + + UnicodeString& getGenericShortString(const Calendar &cal, + UBool commonlyUsedOnly, UnicodeString &result, UErrorCode &status) const; + + UnicodeString& getGenericLocationString(const Calendar &cal, + UnicodeString &result, UErrorCode &status) const; + + const ZoneStringInfo* findSpecificLong(const UnicodeString &text, int32_t start, + int32_t &matchLength, UErrorCode &status) const; + const ZoneStringInfo* findSpecificShort(const UnicodeString &text, int32_t start, + int32_t &matchLength, UErrorCode &status) const; + const ZoneStringInfo* findGenericLong(const UnicodeString &text, int32_t start, + int32_t &matchLength, UErrorCode &status) const; + const ZoneStringInfo* findGenericShort(const UnicodeString &text, int32_t start, + int32_t &matchLength, UErrorCode &status) const; + const ZoneStringInfo* findGenericLocation(const UnicodeString &text, int32_t start, + int32_t &matchLength, UErrorCode &status) const; + + // Following APIs are not used by SimpleDateFormat, but public for testing purpose + inline UnicodeString& getLongStandard(const UnicodeString &tzid, UDate date, + UnicodeString &result) const; + inline UnicodeString& getLongDaylight(const UnicodeString &tzid, UDate date, + UnicodeString &result) const; + inline UnicodeString& getLongGenericNonLocation(const UnicodeString &tzid, UDate date, + UnicodeString &result) const; + inline UnicodeString& getLongGenericPartialLocation(const UnicodeString &tzid, UDate date, + UnicodeString &result) const; + inline UnicodeString& getShortStandard(const UnicodeString &tzid, UDate date, UBool commonlyUsedOnly, + UnicodeString &result) const; + inline UnicodeString& getShortDaylight(const UnicodeString &tzid, UDate date, UBool commonlyUsedOnly, + UnicodeString &result) const; + inline UnicodeString& getShortGenericNonLocation(const UnicodeString &tzid, UDate date, UBool commonlyUsedOnly, + UnicodeString &result) const; + inline UnicodeString& getShortGenericPartialLocation(const UnicodeString &tzid, UDate date, UBool commonlyUsedOnly, + UnicodeString &result) const; + inline UnicodeString& getGenericLocation(const UnicodeString &tzid, UnicodeString &result) const; + +private: + Locale fLocale; + Hashtable fTzidToStrings; + Hashtable fMzidToStrings; + TextTrieMap fZoneStringsTrie; + + /* + * Private method to get a zone string except generic partial location types. + */ + UnicodeString& getString(const UnicodeString &tzid, TimeZoneTranslationTypeIndex typeIdx, UDate date, + UBool commonlyUsedOnly, UnicodeString& result) const; + + /* + * Private method to get a generic string, with fallback logic involved, + * that is, + * + * 1. If a generic non-location string is avaiable for the zone, return it. + * 2. If a generic non-location string is associated with a metazone and + * the zone never use daylight time around the given date, use the standard + * string (if available). + * + * Note: In CLDR1.5.1, the same localization is used for generic and standard. + * In this case, we do not use the standard string and do the rest. + * + * 3. If a generic non-location string is associated with a metazone and + * the offset at the given time is different from the preferred zone for the + * current locale, then return the generic partial location string (if avaiable) + * 4. If a generic non-location string is not available, use generic location + * string. + */ + UnicodeString& getGenericString(const Calendar &cal, UBool isShort, UBool commonlyUsedOnly, + UnicodeString &result, UErrorCode &status) const; + + /* + * Private method to get a generic partial location string + */ + UnicodeString& getGenericPartialLocationString(const UnicodeString &tzid, UBool isShort, + UDate date, UBool commonlyUsedOnly, UnicodeString &result) const; + + /* + * Find a prefix matching time zone for the given zone string types. + * @param text The text contains a time zone string + * @param start The start index within the text + * @param types The bit mask representing a set of requested types + * @param matchLength Receives the match length + * @param status + * @return If any zone string matched for the requested types, returns a + * ZoneStringInfo for the longest match. If no matches are found for + * the requested types, returns a ZoneStringInfo for the longest match + * for any other types. If nothing matches at all, returns null. + */ + const ZoneStringInfo* find(const UnicodeString &text, int32_t start, int32_t types, + int32_t &matchLength, UErrorCode &status) const; + + UnicodeString& getRegion(UnicodeString ®ion) const; + + static MessageFormat* getFallbackFormat(const Locale &locale, UErrorCode &status); + static MessageFormat* getRegionFormat(const Locale &locale, UErrorCode &status); + static const UChar* getZoneStringFromBundle(const UResourceBundle *zoneitem, const char *key); + static UBool isCommonlyUsed(const UResourceBundle *zoneitem); + static UnicodeString& getLocalizedCountry(const UnicodeString &countryCode, const Locale &locale, + UnicodeString &displayCountry); +}; + +inline UnicodeString& +ZoneStringFormat::getLongStandard(const UnicodeString &tzid, UDate date, + UnicodeString &result) const { + return getString(tzid, ZSIDX_LONG_STANDARD, date, FALSE /* not used */, result); +} + +inline UnicodeString& +ZoneStringFormat::getLongDaylight(const UnicodeString &tzid, UDate date, + UnicodeString &result) const { + return getString(tzid, ZSIDX_LONG_DAYLIGHT, date, FALSE /* not used */, result); +} + +inline UnicodeString& +ZoneStringFormat::getLongGenericNonLocation(const UnicodeString &tzid, UDate date, + UnicodeString &result) const { + return getString(tzid, ZSIDX_LONG_GENERIC, date, FALSE /* not used */, result); +} + +inline UnicodeString& +ZoneStringFormat::getLongGenericPartialLocation(const UnicodeString &tzid, UDate date, + UnicodeString &result) const { + return getGenericPartialLocationString(tzid, FALSE, date, FALSE /* not used */, result); +} + +inline UnicodeString& +ZoneStringFormat::getShortStandard(const UnicodeString &tzid, UDate date, UBool commonlyUsedOnly, + UnicodeString &result) const { + return getString(tzid, ZSIDX_SHORT_STANDARD, date, commonlyUsedOnly, result); +} + +inline UnicodeString& +ZoneStringFormat::getShortDaylight(const UnicodeString &tzid, UDate date, UBool commonlyUsedOnly, + UnicodeString &result) const { + return getString(tzid, ZSIDX_SHORT_DAYLIGHT, date, commonlyUsedOnly, result); +} + +inline UnicodeString& +ZoneStringFormat::getShortGenericNonLocation(const UnicodeString &tzid, UDate date, UBool commonlyUsedOnly, + UnicodeString &result) const { + return getString(tzid, ZSIDX_SHORT_GENERIC, date, commonlyUsedOnly, result); +} + +inline UnicodeString& +ZoneStringFormat::getShortGenericPartialLocation(const UnicodeString &tzid, UDate date, UBool commonlyUsedOnly, + UnicodeString &result) const { + return getGenericPartialLocationString(tzid, TRUE, date, commonlyUsedOnly, result); +} + +inline UnicodeString& +ZoneStringFormat::getGenericLocation(const UnicodeString &tzid, UnicodeString &result) const { + return getString(tzid, ZSIDX_LOCATION, 0 /*not used*/, FALSE /*not used*/, result); +} + + +/* + * ZooneStrings is a container of localized zone strings used by ZoneStringFormat + */ +class ZoneStrings : public UMemory { +public: + ZoneStrings(UnicodeString *strings, int32_t stringsCount, UBool commonlyUsed, + UnicodeString **genericPartialLocationStrings, int32_t genericRowCount, int32_t genericColCount); + virtual ~ZoneStrings(); + + UnicodeString& getString(int32_t typeIdx, UnicodeString &result) const; + inline UBool isShortFormatCommonlyUsed(void) const; + UnicodeString& getGenericPartialLocationString(const UnicodeString &mzid, UBool isShort, + UBool commonlyUsedOnly, UnicodeString &result) const; + +private: + UnicodeString *fStrings; + int32_t fStringsCount; + UBool fIsCommonlyUsed; + UnicodeString **fGenericPartialLocationStrings; + int32_t fGenericPartialLocationRowCount; + int32_t fGenericPartialLocationColCount; +}; + +inline UBool +ZoneStrings::isShortFormatCommonlyUsed(void) const { + return fIsCommonlyUsed; +} + +/* + * ZoneStringSearchResultHandler is an implementation of + * TextTrieMapSearchHandler. This class is used by ZoneStringFormat + * for collecting search results for localized zone strings. + */ +class ZoneStringSearchResultHandler : public UMemory, TextTrieMapSearchResultHandler { +public: + ZoneStringSearchResultHandler(UErrorCode &status); + virtual ~ZoneStringSearchResultHandler(); + + virtual UBool handleMatch(int32_t matchLength, const UVector *values, UErrorCode &status); + int32_t countMatches(void); + const ZoneStringInfo* getMatch(int32_t index, int32_t &matchLength); + void clear(void); + +private: + UVector fResults; + int32_t fMatchLen[ZSIDX_COUNT]; +}; + + +/* + * ZoneStringFormat cache implementation + */ +class ZSFCacheEntry : public UMemory { +public: + ~ZSFCacheEntry(); + + void delRef(void); + const ZoneStringFormat* getZoneStringFormat(void); + +private: + friend class ZSFCache; + + ZSFCacheEntry(const Locale &locale, ZoneStringFormat *zsf, ZSFCacheEntry *next); + + Locale fLocale; + ZoneStringFormat *fZoneStringFormat; + ZSFCacheEntry *fNext; + int32_t fRefCount; +}; + +class SafeZoneStringFormatPtr : public UMemory { +public: + ~SafeZoneStringFormatPtr(); + const ZoneStringFormat* get() const; + +private: + friend class ZSFCache; + + SafeZoneStringFormatPtr(ZSFCacheEntry *cacheEntry); + + ZSFCacheEntry *fCacheEntry; +}; + +class ZSFCache : public UMemory { +public: + ZSFCache(int32_t capacity); + ~ZSFCache(); + + SafeZoneStringFormatPtr* get(const Locale &locale, UErrorCode &status); + +private: + int32_t fCapacity; + ZSFCacheEntry *fFirst; +}; + +U_NAMESPACE_END + +#endif /* #if !UCONFIG_NO_FORMATTING */ + +#endif // ZSTRFMT_H |