diff --git a/icu4c/source/i18n/smpdtfmt.cpp b/icu4c/source/i18n/smpdtfmt.cpp index a3a1d148764..4a210dcdd73 100644 --- a/icu4c/source/i18n/smpdtfmt.cpp +++ b/icu4c/source/i18n/smpdtfmt.cpp @@ -92,13 +92,15 @@ static const UChar QUOTE = 0x27; // Single quote SimpleDateFormat::~SimpleDateFormat() { delete fSymbols; + delete parsedTimeZone; // sanity check } //---------------------------------------------------------------------- SimpleDateFormat::SimpleDateFormat(UErrorCode& status) : fLocale(Locale::getDefault()), - fSymbols(NULL) + fSymbols(NULL), + parsedTimeZone(NULL) { construct(kShort, (EStyle) (kShort + kDateOffset), fLocale, status); initializeDefaultCentury(); @@ -110,7 +112,8 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, UErrorCode &status) : fPattern(pattern), fLocale(Locale::getDefault()), - fSymbols(NULL) + fSymbols(NULL), + parsedTimeZone(NULL) { initializeSymbols(fLocale, initializeCalendar(NULL,fLocale,status), status); initialize(fLocale, status); @@ -123,7 +126,8 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, const Locale& locale, UErrorCode& status) : fPattern(pattern), - fLocale(locale) + fLocale(locale), + parsedTimeZone(NULL) { initializeSymbols(fLocale, initializeCalendar(NULL,fLocale,status), status); initialize(fLocale, status); @@ -137,7 +141,8 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, UErrorCode& status) : fPattern(pattern), fLocale(Locale::getDefault()), - fSymbols(symbolsToAdopt) + fSymbols(symbolsToAdopt), + parsedTimeZone(NULL) { initializeCalendar(NULL,fLocale,status); initialize(fLocale, status); @@ -151,7 +156,8 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, UErrorCode& status) : fPattern(pattern), fLocale(Locale::getDefault()), - fSymbols(new DateFormatSymbols(symbols)) + fSymbols(new DateFormatSymbols(symbols)), + parsedTimeZone(NULL) { initializeCalendar(NULL, fLocale, status); initialize(fLocale, status); @@ -166,7 +172,8 @@ SimpleDateFormat::SimpleDateFormat(EStyle timeStyle, const Locale& locale, UErrorCode& status) : fLocale(locale), - fSymbols(NULL) + fSymbols(NULL), + parsedTimeZone(NULL) { construct(timeStyle, dateStyle, fLocale, status); if(U_SUCCESS(status)) { @@ -185,7 +192,8 @@ SimpleDateFormat::SimpleDateFormat(const Locale& locale, UErrorCode& status) : fPattern(gDefaultPattern), fLocale(locale), - fSymbols(NULL) + fSymbols(NULL), + parsedTimeZone(NULL) { if (U_FAILURE(status)) return; initializeSymbols(fLocale, initializeCalendar(NULL, fLocale, status),status); @@ -212,7 +220,8 @@ SimpleDateFormat::SimpleDateFormat(const Locale& locale, SimpleDateFormat::SimpleDateFormat(const SimpleDateFormat& other) : DateFormat(other), - fSymbols(NULL) + fSymbols(NULL), + parsedTimeZone(NULL) { *this = other; } @@ -226,6 +235,8 @@ SimpleDateFormat& SimpleDateFormat::operator=(const SimpleDateFormat& other) delete fSymbols; fSymbols = NULL; + delete parsedTimeZone; parsedTimeZone = NULL; + if (other.fSymbols) fSymbols = new DateFormatSymbols(*other.fSymbols); @@ -254,11 +265,11 @@ SimpleDateFormat::operator==(const Format& other) const if (DateFormat::operator==(other)) { // DateFormat::operator== guarantees following cast is safe SimpleDateFormat* that = (SimpleDateFormat*)&other; - return (fPattern == that->fPattern && + return (fPattern == that->fPattern && fSymbols != NULL && // Check for pathological object - that->fSymbols != NULL && // Check for pathological object - *fSymbols == *that->fSymbols && - fHaveDefaultCentury == that->fHaveDefaultCentury && + that->fSymbols != NULL && // Check for pathological object + *fSymbols == *that->fSymbols && + fHaveDefaultCentury == that->fHaveDefaultCentury && fDefaultCenturyStart == that->fDefaultCenturyStart); } return FALSE; @@ -673,43 +684,45 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, break; // for the "z" symbols, we have to check our time zone data first. If we have a - // localized name for the time zone, then "zzzz" is the whole name and anything - // shorter is the abbreviation (we also have to check for daylight savings time - // since the name will be different). If we don't have a localized time zone name, + // localized name for the time zone, then "zzzz" / "zzz" indicate whether + // daylight time is in effect (long/short) and "zz" / "z" do not (long/short). + // If we don't have a localized time zone name, // 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: { UnicodeString str; int32_t zoneIndex = fSymbols->getZoneIndex(cal.getTimeZone().getID(str)); if (zoneIndex == -1) { - value = cal.get(UCAL_ZONE_OFFSET, status) + - cal.get(UCAL_DST_OFFSET, status); + value = cal.get(UCAL_ZONE_OFFSET, status) + + cal.get(UCAL_DST_OFFSET, status); - if (value < 0) { - appendTo += gGmtMinus; - value = -value; // suppress the '-' sign for text display. - } - else - appendTo += gGmtPlus; - - zeroPaddingNumber(appendTo, (int32_t)(value/U_MILLIS_PER_HOUR), 2, 2); - appendTo += (UChar)0x003A /*':'*/; - zeroPaddingNumber(appendTo, (int32_t)((value%U_MILLIS_PER_HOUR)/U_MILLIS_PER_MINUTE), 2, 2); - } - else if (cal.get(UCAL_DST_OFFSET, status) != 0) { - if (count >= 4) - appendTo += fSymbols->fZoneStrings[zoneIndex][3]; - else - appendTo += fSymbols->fZoneStrings[zoneIndex][4]; + if (value < 0) { + appendTo += gGmtMinus; + value = -value; // suppress the '-' sign for text display. + } + else + appendTo += gGmtPlus; + + zeroPaddingNumber(appendTo, (int32_t)(value/U_MILLIS_PER_HOUR), 2, 2); + appendTo += (UChar)0x003A /*':'*/; + zeroPaddingNumber(appendTo, (int32_t)((value%U_MILLIS_PER_HOUR)/U_MILLIS_PER_MINUTE), 2, 2); } else { - if (count >= 4) - appendTo += fSymbols->fZoneStrings[zoneIndex][1]; - else - appendTo += fSymbols->fZoneStrings[zoneIndex][2]; + int ix; + int zsrc = fSymbols->fZoneStringsColCount; + if (zsrc < 7 && count < 3) { + count += 2; // no generic time, default to full times + } + switch (count) { + case 1: ix = zsrc == 7 ? 6 : 7; break; // short generic time + case 2: ix = zsrc == 7 ? 5 : 6; break; // long generic time + case 3: ix = cal.get(UCAL_DST_OFFSET, status) != 0 ? 4 : 2; break; // short dst/std time + default: ix = cal.get(UCAL_DST_OFFSET, status) != 0 ? 3 : 1; break; // long dst/std time + } + appendTo += fSymbols->fZoneStrings[zoneIndex][ix]; } - } - break; + } + break; case 23: // 'Z' - TIMEZONE_RFC { @@ -780,6 +793,10 @@ 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; + // For parsing abutting numeric fields. 'abutPat' is the // offset into 'pattern' of the first of 2 or more abutting // numeric fields. 'abutStart' is the offset into 'text' @@ -974,20 +991,38 @@ 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]) // If this is true then the two-digit year == the default start year + if (ambiguousYear[0] || parsedTimeZone != NULL) // 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(); - 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); + if (ambiguousYear[0]) { + 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); + } } + + if (parsedTimeZone != NULL) { + TimeZone *tz = parsedTimeZone; + + // 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); + } + } + delete copy; } @@ -1309,20 +1344,20 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC // GMT[+-]hhmm or // GMT. - if ((text.length() - start) >= gmtLen && - (text.caseCompare(start, gmtLen, gGmt, 0, gmtLen, U_FOLD_CASE_DEFAULT)) == 0) - { + if ((text.length() - start) >= gmtLen && + (text.caseCompare(start, gmtLen, gGmt, 0, gmtLen, U_FOLD_CASE_DEFAULT)) == 0) + { cal.set(UCAL_DST_OFFSET, 0); pos.setIndex(start + gmtLen); if( text[pos.getIndex()] == 0x002B /*'+'*/ ) - sign = 1; + sign = 1; else if( text[pos.getIndex()] == 0x002D /*'-'*/ ) - sign = -1; + sign = -1; else { - cal.set(UCAL_ZONE_OFFSET, 0 ); - return pos.getIndex(); + cal.set(UCAL_ZONE_OFFSET, 0 ); + return pos.getIndex(); } // Look for hours:minutes or hhmm. @@ -1331,59 +1366,38 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC Formattable tzNumber; fNumberFormat->parse(text, tzNumber, pos); if( pos.getIndex() == parseStart) { - return -start; + 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(); + // 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(); } else { - // This is the hhmm case. - offset = tzNumber.getLong(); - if( offset < 24 ) - offset *= 60; - else - offset = offset % 100 + offset / 100 * 60; + // This is the hhmm case. + offset = tzNumber.getLong(); + if( offset < 24 ) + offset *= 60; + else + offset = offset % 100 + offset / 100 * 60; } // Fall through for final processing below of 'offset' and 'sign'. - } + } else { // 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. - const UnicodeString *zs; - int32_t j; - - for (i = 0; i < fSymbols->fZoneStringsRowCount; i++) - { - // Checking long and short zones [1 & 2], - // and long and short daylight [3 & 4]. - for (j = 1; j <= 4; ++j) - { - zs = &fSymbols->fZoneStrings[i][j]; - // ### TODO markus 20021014: This use of caseCompare() will fail - // if the text contains a character that case-folds into multiple - // characters. In that case, zs->length() may be too long, and it does not match. - // We need a case-insensitive version of startsWith(). - // There are similar cases of such caseCompare() uses elsewhere in ICU. - if (0 == (text.caseCompare(start, zs->length(), *zs, 0))) { - TimeZone *tz = TimeZone::createTimeZone(fSymbols->fZoneStrings[i][0]); - cal.set(UCAL_ZONE_OFFSET, tz->getRawOffset()); - // Must call set() with something -- TODO -- Fix this to - // use the correct DST SAVINGS for the zone. - delete tz; - cal.set(UCAL_DST_OFFSET, j >= 3 ? U_MILLIS_PER_HOUR : 0); - return (start + fSymbols->fZoneStrings[i][j].length()); - } - } + // !!! side effect, might set parsedZoneString + int32_t result = subParseZoneString(text, start, cal); + if (result != 0) { + return result; } // As a last resort, look for numeric timezones of the form @@ -1456,6 +1470,124 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC } } +int32_t +SimpleDateFormat::getTimeZoneIndex(const UnicodeString& id) const +{ + int32_t i = fSymbols->fZoneStringsRowCount; + while (--i >= 0 && fSymbols->fZoneStrings[i][0] != id); + return i; +} + +int32_t +SimpleDateFormat::matchZoneString(const UnicodeString& text, int32_t start, int32_t zi) const +{ + // ### TODO markus 20021014: This use of caseCompare() will fail + // if the text contains a character that case-folds into multiple + // characters. In that case, zs->length() may be too long, and it does not match. + // We need a case-insensitive version of startsWith(). + // There are similar cases of such caseCompare() uses elsewhere in ICU. + + int32_t i = fSymbols->fZoneStringsColCount; + const int32_t zscc = i; + + while (--i >= 1) { + if (i == 5 && (zscc == 6 || zscc >= 8)) { // skip city name if we have it + continue; + } + + const UnicodeString& zs = fSymbols->fZoneStrings[zi][i]; + if (zs.length() > 0 && 0 == text.caseCompare(start, zs.length(), zs, 0)) { + break; + } + } + return i; +} + +/** + * find time zone 'text' matched zoneStrings and set cal. + * includes optimizations for calendar and default time zones + */ +int32_t +SimpleDateFormat::subParseZoneString(const UnicodeString& text, int32_t start, Calendar& cal) 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. + + TimeZone *tz = NULL; + UnicodeString id; + int32_t zoneIndex = -1; + int32_t zi; + + // optimize for calendar's current time zone + zi = getTimeZoneIndex(getTimeZone().getID(id)); + if (zi != -1) { + int32_t j = matchZoneString(text, start, zi); + if (j > 0) { + tz = getTimeZone().clone(); + zoneIndex = j; + } + } + + // optimize for default time zone, assume different from caller + if (tz == NULL) { + TimeZone* defaultZone = TimeZone::createDefault(); + zi = getTimeZoneIndex(defaultZone->getID(id)); + if (zi != -1) { + int32_t j = matchZoneString(text, start, zi); + if (j > 0) { + zoneIndex = j; + tz = defaultZone; + } + } + if (tz == NULL) { + delete defaultZone; + } + } + + // still no luck, check all time zone strings + if (tz == NULL) { + for (zi = 0; zi < fSymbols->fZoneStringsRowCount; zi++) { + int32_t j = matchZoneString(text, start, zi); + if (j > 0) { + tz = TimeZone::createTimeZone(fSymbols->fZoneStrings[zi][0]); + zoneIndex = j; + break; + } + } + } + + 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 (zoneIndex < 3) { + // standard time + cal.set(UCAL_DST_OFFSET, 0); + delete tz; tz = NULL; + } else if (zoneIndex < 5) { + // 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 + fSymbols->fZoneStrings[zi][zoneIndex].length(); + } + + // complete failure + return 0; +} + /** * Parse an integer using fNumberFormat. This method is semantically * const, but actually may modify fNumberFormat. diff --git a/icu4c/source/i18n/unicode/smpdtfmt.h b/icu4c/source/i18n/unicode/smpdtfmt.h index 335c7651394..c3f68474183 100644 --- a/icu4c/source/i18n/unicode/smpdtfmt.h +++ b/icu4c/source/i18n/unicode/smpdtfmt.h @@ -78,7 +78,7 @@ class DateFormat; * a am/pm marker (Text) PM * k hour in day (1~24) (Number) 24 * K hour in am/pm (0~11) (Number) 0 - * z time zone (Text) Pacific Standard Time + * z time zone (Time) Pacific Standard Time * Z time zone (RFC 822) (Number) -0800 * g Julian day (Number) 2451334 * A milliseconds in day (Number) 69540000 @@ -91,6 +91,10 @@ class DateFormat; * (Text): 4 or more, use full form, <4, use short or abbreviated form if it * exists. (e.g., "EEEE" produces "Monday", "EEE" produces "Mon") *
+ * (Time): 4 or 3, display long/short time zone names with daylight/standard + * designation (e.g., Pacific Daylight Time, PDT), 2 or 1, display long/short + * time zone generic names (e.g., Pacific Time, PT). + *
* (Number): the minimum number of digits. Shorter numbers are zero-padded to * this amount (e.g. if "m" produces "6", "mm" produces "06"). Year is handled * specially; that is, if the count of 'y' is 2, the Year will be truncated to 2 digits. @@ -113,11 +117,11 @@ class DateFormat; * \code * Format Pattern Result * -------------- ------- - * "yyyy.MM.dd G 'at' HH:mm:ss z" ->> 1996.07.10 AD at 15:08:56 PDT + * "yyyy.MM.dd G 'at' HH:mm:ss zz" ->> 1996.07.10 AD at 15:08:56 Pacific Time * "EEE, MMM d, ''yy" ->> Wed, July 10, '96 * "h:mm a" ->> 12:08 PM * "hh 'o''clock' a, zzzz" ->> 12 o'clock PM, Pacific Daylight Time - * "K:mm a, z" ->> 0:00 PM, PST + * "K:mm a, z" ->> 0:00 PM, PT * "yyyyy.MMMMM.dd GGG hh:mm aaa" ->> 1996.July.10 AD 12:08 PM * \endcode * @@ -765,6 +769,25 @@ private: */ void parseAmbiguousDatesAsAfter(UDate startDate, UErrorCode& status); + /** + * Given a canonical time zone id, return the row index in our symbols for that id, + * or -1 if none found. + */ + int32_t getTimeZoneIndex(const UnicodeString& id) const; + + /** + * 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. + */ + int32_t subParseZoneString(const UnicodeString& text, int32_t start, Calendar& cal) const; + /** * Used to map pattern characters to Calendar field identifiers. */ @@ -806,6 +829,8 @@ private: */ /*transient*/ int32_t fDefaultCenturyStartYear; + /*transient*/ TimeZone* parsedTimeZone; // here to avoid api change + UBool fHaveDefaultCentury; }; diff --git a/icu4c/source/test/cintltst/ccaltst.c b/icu4c/source/test/cintltst/ccaltst.c index fd1e14ef253..ac929111d4e 100644 --- a/icu4c/source/test/cintltst/ccaltst.c +++ b/icu4c/source/test/cintltst/ccaltst.c @@ -274,7 +274,7 @@ static void TestCalendar() } ucal_getTimeZoneDisplayName(caldef, UCAL_SHORT_DST, "en_US", result, resultlength, &status); - u_uastrcpy(tzdname, "PDT"); + u_uastrcpy(tzdname, "PT"); if(u_strcmp(tzdname, result) != 0){ log_err("FAIL: got the wrong time zone(SHORT_DST) display name %s, wanted %s\n", austrdup(result), austrdup(tzdname)); } @@ -286,7 +286,7 @@ static void TestCalendar() } ucal_getTimeZoneDisplayName(caldef, UCAL_SHORT_STANDARD, "en_US", result, resultlength, &status); - u_uastrcpy(tzdname, "PST"); + u_uastrcpy(tzdname, "PT"); if(u_strcmp(tzdname, result) != 0){ log_err("FAIL: got the wrong time zone(SHORT_STANDARD) display name %s, wanted %s\n", austrdup(result), austrdup(tzdname)); } diff --git a/icu4c/source/test/cintltst/cdattst.c b/icu4c/source/test/cintltst/cdattst.c index f4b039e7163..0e6e79aebe9 100644 --- a/icu4c/source/test/cintltst/cdattst.c +++ b/icu4c/source/test/cintltst/cdattst.c @@ -170,7 +170,7 @@ static void TestDateFormat() log_err("FAIL: Date Format for US locale failed using udat_format()\n"); /*format using fr */ - u_unescape("10 juil. 96 16 h 05 HAP (\\u00c9UA)", temp, 30); + u_unescape("10 juil. 96 16 h 05 HP (\\u00c9UA)", temp, 30); if(result != NULL) { free(result); result = NULL; diff --git a/icu4c/source/test/cintltst/cmsgtst.c b/icu4c/source/test/cintltst/cmsgtst.c index cae8afb8b84..8457458a6b8 100644 --- a/icu4c/source/test/cintltst/cmsgtst.c +++ b/icu4c/source/test/cintltst/cmsgtst.c @@ -42,7 +42,7 @@ static const char* const txt_testResultStrings[] = { "Quotes ', {, a 1 {0}", "Quotes ', {, a 1 {0}", "You deposited 1 times an amount of $3,456.00 on 1/12/70", - "{2,time,full}, for 3,456, 1 is 5:46:40 AM PST and full date is Monday, January 12, 1970", + "{2,time,full}, for 3,456, 1 is 5:46:40 AM PT and full date is Monday, January 12, 1970", "{1,number,percent} for 1 is 345,600%" }; diff --git a/icu4c/source/test/intltest/dtfmttst.cpp b/icu4c/source/test/intltest/dtfmttst.cpp index e39cc3c5218..5d387cb9800 100644 --- a/icu4c/source/test/intltest/dtfmttst.cpp +++ b/icu4c/source/test/intltest/dtfmttst.cpp @@ -19,6 +19,7 @@ #include "cmemory.h" #include "cstring.h" #include "caltest.h" // for fieldName + // ***************************************************************************** // class DateFormatTest // ***************************************************************************** @@ -51,6 +52,8 @@ void DateFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &nam TESTCASE(21,TestInvalidPattern); TESTCASE(22,TestGeneral); TESTCASE(23,TestGreekMay); + TESTCASE(24,TestGenericTime); + TESTCASE(25,TestGenericTimeZoneOrder); default: name = ""; break; } } @@ -188,7 +191,7 @@ DateFormatTest::TestTwoDigitYearDSTParse(void) int32_t y, m, day, hr, min, sec; dateToFields(d, y, m, day, hr, min, sec); if (hr != hour) - errln((UnicodeString)"FAIL: Should parse to hour " + hour); + errln((UnicodeString)"FAIL: Should parse to hour " + hour + " but got " + hr); if (U_FAILURE(status)) errln((UnicodeString)"FAIL: " + (int32_t)status); @@ -304,13 +307,13 @@ void DateFormatTest::TestFieldPosition() { // Fields are given in order of DateFormat field number const char* EXPECTED[] = { "", "1997", "August", "13", "", "", "34", "12", "", - "Wednesday", "", "", "", "", "PM", "2", "", "PDT", "", "", "", "", "", "", + "Wednesday", "", "", "", "", "PM", "2", "", "PT", "", "", "", "", "", "", "", "1997", "ao\\u00FBt", "13", "", "14", "34", "", "", - "mercredi", "", "", "", "", "", "", "", "HAP (\\u00C9UA)", "", "", "", "", "", "", + "mercredi", "", "", "", "", "", "", "", "HP (\\u00C9UA)", "", "", "", "", "", "", "AD", "1997", "8", "13", "14", "14", "34", "12", "5", - "Wed", "225", "2", "33", "3", "PM", "2", "2", "PDT", "1997", "4", "1997", "2450674", "52452513", "-0700", + "Wed", "225", "2", "33", "3", "PM", "2", "2", "PT", "1997", "4", "1997", "2450674", "52452513", "-0700", "AD", "1997", "August", "0013", "0014", "0014", "0034", "0012", "5130", "Wednesday", "0225", "0002", "0033", "0003", "PM", "0002", "0002", "Pacific Daylight Time", "1997", "0004", "1997", "2450674", "52452513", "-0700", @@ -956,10 +959,10 @@ DateFormatTest::TestLocaleDateFormat() // Bug 495 DateFormat::FULL, Locale::getFrench()); DateFormat *dfUS = DateFormat::createDateTimeInstance(DateFormat::FULL, DateFormat::FULL, Locale::getUS()); - UnicodeString expectedFRENCH ( "lundi 15 septembre 1997 00 h 00 HAP (\\u00C9UA)" ); + UnicodeString expectedFRENCH ( "lundi 15 septembre 1997 00 h 00 HP (\\u00C9UA)" ); expectedFRENCH = expectedFRENCH.unescape(); //UnicodeString expectedUS ( "Monday, September 15, 1997 12:00:00 o'clock AM PDT" ); - UnicodeString expectedUS ( "Monday, September 15, 1997 12:00:00 AM PDT" ); + UnicodeString expectedUS ( "Monday, September 15, 1997 12:00:00 AM PT" ); logln((UnicodeString)"Date set to : " + dateToString(testDate)); UnicodeString out; dfFrench->format(testDate, out); @@ -1328,6 +1331,125 @@ void DateFormatTest::expect(const char** data, int32_t data_length, } } +void DateFormatTest::TestGenericTime() { + // any zone pattern should parse any zone + const Locale en("en"); + const char* ZDATA[] = { + "yyyy MM dd HH:mm zzz", + // round trip + "y/M/d H:mm zzzz", "F", "2004 01 01 01:00 PST", "2004/1/1 1:00 Pacific Standard Time", + "y/M/d H:mm zzz", "F", "2004 01 01 01:00 PST", "2004/1/1 1:00 PST", + "y/M/d H:mm zz", "F", "2004 01 01 01:00 PST", "2004/1/1 1:00 Pacific Time", + "y/M/d H:mm z", "F", "2004 01 01 01:00 PST", "2004/1/1 1:00 PT", + // non-generic timezone string influences dst offset even if wrong for date/time + "y/M/d H:mm zzz", "pf", "2004/1/1 1:00 PDT", "2004 01 01 01:00 PDT", "2004/1/1 0:00 PST", + "y/M/d H:mm zz", "pf", "2004/1/1 1:00 PDT", "2004 01 01 01:00 PDT", "2004/1/1 0:00 Pacific Time", + "y/M/d H:mm zzz", "pf", "2004/7/1 1:00 PST", "2004 07 01 02:00 PDT", "2004/7/1 2:00 PDT", + "y/M/d H:mm zz", "pf", "2004/7/1 1:00 PST", "2004 07 01 02:00 PDT", "2004/7/1 2:00 Pacific Time", + // generic timezone generates dst offset appropriate for local time + "y/M/d H:mm zzz", "pf", "2004/1/1 1:00 PT", "2004 01 01 01:00 PST", "2004/1/1 1:00 PST", + "y/M/d H:mm zz", "pf", "2004/1/1 1:00 PT", "2004 01 01 01:00 PST", "2004/1/1 1:00 Pacific Time", + "y/M/d H:mm zzz", "pf", "2004/7/1 1:00 PT", "2004 07 01 01:00 PDT", "2004/7/1 1:00 PDT", + "y/M/d H:mm zz", "pf", "2004/7/1 1:00 PT", "2004 07 01 01:00 PDT", "2004/7/1 1:00 Pacific Time", + // daylight savings time transition edge cases. + // time to parse does not really exist, PT interpreted as earlier time + "y/M/d H:mm zzz", "pf", "2005/4/3 2:30 PT", "2005 04 03 01:30 PST", "2005/4/3 1:30 PST", // adjust earlier + "y/M/d H:mm zzz", "pf", "2005/4/3 2:30 PST", "2005 04 03 03:30 PDT", "2005/4/3 3:30 PDT", + "y/M/d H:mm zzz", "pf", "2005/4/3 2:30 PDT", "2005 04 03 01:30 PST", "2005/4/3 1:30 PST", + "y/M/d H:mm z", "pf", "2005/4/3 2:30 PT", "2005 04 03 01:30 PST", "2005/4/3 1:30 PT", // adjust earlier + "y/M/d H:mm z", "pf", "2005/4/3 2:30 PST", "2005 04 03 03:30 PDT", "2005/4/3 3:30 PT", + "y/M/d H:mm z", "pf", "2005/4/3 2:30 PDT", "2005 04 03 01:30 PST", "2005/4/3 1:30 PT", + "y/M/d H:mm", "pf", "2005/4/3 2:30", "2005 04 03 01:30 PST", "2005/4/3 1:30", + // time to parse is ambiguous, PT interpreted as earlier time (?) + "y/M/d H:mm zzz", "pf", "2004/10/31 1:30 PT", "2004 10 31 01:30 PDT", "2004/10/31 1:30 PDT", // fail + "y/M/d H:mm zzz", "pf", "2004/10/31 1:30 PST", "2004 10 31 01:30 PST", "2004/10/31 1:30 PST", + "y/M/d H:mm zzz", "pf", "2004/10/31 1:30 PDT", "2004 10 31 01:30 PDT", "2004/10/31 1:30 PDT", + "y/M/d H:mm z", "pf", "2004/10/31 1:30 PT", "2004 10 31 01:30 PDT", "2004/10/31 1:30 PT", // fail + "y/M/d H:mm z", "pf", "2004/10/31 1:30 PST", "2004 10 31 01:30 PST", "2004/10/31 1:30 PT", + "y/M/d H:mm z", "pf", "2004/10/31 1:30 PDT", "2004 10 31 01:30 PDT", "2004/10/31 1:30 PT", + "y/M/d H:mm", "pf", "2004/10/31 1:30", "2004 10 31 01:30 PDT", "2004/10/31 1:30", // fail + }; + const int32_t ZDATA_length = sizeof(ZDATA)/ sizeof(ZDATA[0]); + expect(ZDATA, ZDATA_length, en); + + UErrorCode status = U_ZERO_ERROR; + + logln("cross format/parse tests"); + UnicodeString basepat("yy/MM/dd H:mm "); + SimpleDateFormat formats[] = { + SimpleDateFormat(basepat + "z", en, status), + SimpleDateFormat(basepat + "zz", en, status), + SimpleDateFormat(basepat + "zzz", en, status), + SimpleDateFormat(basepat + "zzzz", en, status) + }; + const int32_t formats_length = sizeof(formats)/sizeof(formats[0]); + + UnicodeString test; + SimpleDateFormat univ("yyyy MM dd HH:mm zzz", en, status); + const UnicodeString times[] = { + "2004 01 02 03:04 PST", + "2004 07 08 09:10 PDT" + }; + int32_t times_length = sizeof(times)/sizeof(times[0]); + for (int i = 0; i < times_length; ++i) { + UDate d = univ.parse(times[i], status); + logln(UnicodeString("\ntime: ") + d); + for (int j = 0; j < formats_length; ++j) { + test.remove(); + formats[j].format(d, test); + logln("\ntest: '" + test + "'"); + for (int k = 0; k < formats_length; ++k) { + UDate t = formats[k].parse(test, status); + if (U_SUCCESS(status)) { + if (d != t) { + errln((UnicodeString)"FAIL: format " + k + + " incorrectly parsed output of format " + j + + " (" + test + "), returned " + + dateToString(t) + " instead of " + dateToString(d)); + } else { + logln((UnicodeString)"OK: format " + k + " parsed ok"); + } + } else if (status == U_PARSE_ERROR) { + errln((UnicodeString)"FAIL: format " + k + + " could not parse output of format " + j + + " (" + test + ")"); + } + } + } + } +} + +void DateFormatTest::TestGenericTimeZoneOrder() { + // generic times should parse the same no matter what the placement of the time zone string + // should work for standard and daylight times + + const char* XDATA[] = { + "yyyy MM dd HH:mm zzz", + // standard time, explicit daylight/standard + "y/M/d H:mm zzz", "pf", "2004/1/1 1:00 PT", "2004 01 01 01:00 PST", "2004/1/1 1:00 PST", + "y/M/d zzz H:mm", "pf", "2004/1/1 PT 1:00", "2004 01 01 01:00 PST", "2004/1/1 PST 1:00", + "zzz y/M/d H:mm", "pf", "PT 2004/1/1 1:00", "2004 01 01 01:00 PST", "PST 2004/1/1 1:00", + + // standard time, generic + "y/M/d H:mm zz", "pf", "2004/1/1 1:00 PT", "2004 01 01 01:00 PST", "2004/1/1 1:00 Pacific Time", + "y/M/d zz H:mm", "pf", "2004/1/1 PT 1:00", "2004 01 01 01:00 PST", "2004/1/1 Pacific Time 1:00", + "zz y/M/d H:mm", "pf", "PT 2004/1/1 1:00", "2004 01 01 01:00 PST", "Pacific Time 2004/1/1 1:00", + + // dahylight time, explicit daylight/standard + "y/M/d H:mm zzz", "pf", "2004/7/1 1:00 PT", "2004 07 01 01:00 PDT", "2004/7/1 1:00 PDT", + "y/M/d zzz H:mm", "pf", "2004/7/1 PT 1:00", "2004 07 01 01:00 PDT", "2004/7/1 PDT 1:00", + "zzz y/M/d H:mm", "pf", "PT 2004/7/1 1:00", "2004 07 01 01:00 PDT", "PDT 2004/7/1 1:00", + + // daylight time, generic + "y/M/d H:mm zz", "pf", "2004/7/1 1:00 PT", "2004 07 01 01:00 PDT", "2004/7/1 1:00 Pacific Time", + "y/M/d zz H:mm", "pf", "2004/7/1 PT 1:00", "2004 07 01 01:00 PDT", "2004/7/1 Pacific Time 1:00", + "zz y/M/d H:mm", "pf", "PT 2004/7/1 1:00", "2004 07 01 01:00 PDT", "Pacific Time 2004/7/1 1:00", + }; + const int32_t XDATA_length = sizeof(XDATA)/sizeof(XDATA[0]); + Locale en("en"); + expect(XDATA, XDATA_length, en); +} + #endif /* #if !UCONFIG_NO_FORMATTING */ //eof diff --git a/icu4c/source/test/intltest/dtfmttst.h b/icu4c/source/test/intltest/dtfmttst.h index 8edb2a34169..8df6de0b434 100644 --- a/icu4c/source/test/intltest/dtfmttst.h +++ b/icu4c/source/test/intltest/dtfmttst.h @@ -157,6 +157,10 @@ public: // package void TestGreekMay(void); + void TestGenericTime(void); + + void TestGenericTimeZoneOrder(void); + private: void expectParse(const char** data, int32_t data_length, const Locale& locale); diff --git a/icu4c/source/test/intltest/tmsgfmt.cpp b/icu4c/source/test/intltest/tmsgfmt.cpp index 5742ac41e21..6478eb6158b 100644 --- a/icu4c/source/test/intltest/tmsgfmt.cpp +++ b/icu4c/source/test/intltest/tmsgfmt.cpp @@ -1138,7 +1138,7 @@ void TestMessageFormat::TestUnlimitedArgsAndSubformats() { UnicodeString expected = "On Nov 20, 2286 (aka 11/20/86, aka November 20, 2286) " - "at 9:46:40 AM (aka 9:46 AM, aka 9:46:40 AM PST) " + "at 9:46:40 AM (aka 9:46 AM, aka 9:46:40 AM PT) " "there were 1,303 werjes " "(a 8% increase over 1,202) " "despite the Glimmung's efforts " diff --git a/icu4c/source/test/intltest/tztest.cpp b/icu4c/source/test/intltest/tztest.cpp index 633da043c3f..5b2c884ef0c 100644 --- a/icu4c/source/test/intltest/tztest.cpp +++ b/icu4c/source/test/intltest/tztest.cpp @@ -887,8 +887,8 @@ TimeZoneTest::TestDisplayName() TimeZone::EDisplayType style; const char *expect; } kData[] = { - {FALSE, TimeZone::SHORT, "PST"}, - {TRUE, TimeZone::SHORT, "PDT"}, + {FALSE, TimeZone::SHORT, "PT"}, + {TRUE, TimeZone::SHORT, "PT"}, {FALSE, TimeZone::LONG, "Pacific Standard Time"}, {TRUE, TimeZone::LONG, "Pacific Daylight Time"},