diff --git a/icu4c/source/i18n/dtfmtsym.cpp b/icu4c/source/i18n/dtfmtsym.cpp index ebefc1489c2..a0b6deebc02 100644 --- a/icu4c/source/i18n/dtfmtsym.cpp +++ b/icu4c/source/i18n/dtfmtsym.cpp @@ -392,8 +392,10 @@ DateFormatSymbols::copyData(const DateFormatSymbols& other) { fTimeSeparator.fastCopyFrom(other.fTimeSeparator); // fastCopyFrom() - see assignArray comments assignArray(fQuarters, fQuartersCount, other.fQuarters, other.fQuartersCount); assignArray(fShortQuarters, fShortQuartersCount, other.fShortQuarters, other.fShortQuartersCount); + assignArray(fNarrowQuarters, fNarrowQuartersCount, other.fNarrowQuarters, other.fNarrowQuartersCount); assignArray(fStandaloneQuarters, fStandaloneQuartersCount, other.fStandaloneQuarters, other.fStandaloneQuartersCount); assignArray(fStandaloneShortQuarters, fStandaloneShortQuartersCount, other.fStandaloneShortQuarters, other.fStandaloneShortQuartersCount); + assignArray(fStandaloneNarrowQuarters, fStandaloneNarrowQuartersCount, other.fStandaloneNarrowQuarters, other.fStandaloneNarrowQuartersCount); assignArray(fWideDayPeriods, fWideDayPeriodsCount, other.fWideDayPeriods, other.fWideDayPeriodsCount); assignArray(fNarrowDayPeriods, fNarrowDayPeriodsCount, @@ -485,8 +487,10 @@ void DateFormatSymbols::dispose() delete[] fNarrowAmPms; delete[] fQuarters; delete[] fShortQuarters; + delete[] fNarrowQuarters; delete[] fStandaloneQuarters; delete[] fStandaloneShortQuarters; + delete[] fStandaloneNarrowQuarters; delete[] fLeapMonthPatterns; delete[] fShortYearNames; delete[] fShortZodiacNames; @@ -563,8 +567,10 @@ DateFormatSymbols::operator==(const DateFormatSymbols& other) const fNarrowAmPmsCount == other.fNarrowAmPmsCount && fQuartersCount == other.fQuartersCount && fShortQuartersCount == other.fShortQuartersCount && + fNarrowQuartersCount == other.fNarrowQuartersCount && fStandaloneQuartersCount == other.fStandaloneQuartersCount && fStandaloneShortQuartersCount == other.fStandaloneShortQuartersCount && + fStandaloneNarrowQuartersCount == other.fStandaloneNarrowQuartersCount && fLeapMonthPatternsCount == other.fLeapMonthPatternsCount && fShortYearNamesCount == other.fShortYearNamesCount && fShortZodiacNamesCount == other.fShortZodiacNamesCount && @@ -599,8 +605,10 @@ DateFormatSymbols::operator==(const DateFormatSymbols& other) const fTimeSeparator == other.fTimeSeparator && arrayCompare(fQuarters, other.fQuarters, fQuartersCount) && arrayCompare(fShortQuarters, other.fShortQuarters, fShortQuartersCount) && + arrayCompare(fNarrowQuarters, other.fNarrowQuarters, fNarrowQuartersCount) && arrayCompare(fStandaloneQuarters, other.fStandaloneQuarters, fStandaloneQuartersCount) && arrayCompare(fStandaloneShortQuarters, other.fStandaloneShortQuarters, fStandaloneShortQuartersCount) && + arrayCompare(fStandaloneNarrowQuarters, other.fStandaloneNarrowQuarters, fStandaloneNarrowQuartersCount) && arrayCompare(fLeapMonthPatterns, other.fLeapMonthPatterns, fLeapMonthPatternsCount) && arrayCompare(fShortYearNames, other.fShortYearNames, fShortYearNamesCount) && arrayCompare(fShortZodiacNames, other.fShortZodiacNames, fShortZodiacNamesCount) && @@ -809,8 +817,8 @@ DateFormatSymbols::getQuarters(int32_t &count, DtContextType context, DtWidthTyp returnValue = fShortQuarters; break; case NARROW : - count = 0; - returnValue = NULL; + count = fNarrowQuartersCount; + returnValue = fNarrowQuarters; break; case DT_WIDTH_COUNT : break; @@ -828,8 +836,8 @@ DateFormatSymbols::getQuarters(int32_t &count, DtContextType context, DtWidthTyp returnValue = fStandaloneShortQuarters; break; case NARROW : - count = 0; - returnValue = NULL; + count = fStandaloneNarrowQuartersCount; + returnValue = fStandaloneNarrowQuarters; break; case DT_WIDTH_COUNT : break; @@ -1178,13 +1186,11 @@ DateFormatSymbols::setQuarters(const UnicodeString* quartersArray, int32_t count fShortQuartersCount = count; break; case NARROW : - /* if (fNarrowQuarters) delete[] fNarrowQuarters; fNarrowQuarters = newUnicodeStringArray(count); uprv_arrayCopy( quartersArray,fNarrowQuarters,count); fNarrowQuartersCount = count; - */ break; default : break; @@ -1207,13 +1213,11 @@ DateFormatSymbols::setQuarters(const UnicodeString* quartersArray, int32_t count fStandaloneShortQuartersCount = count; break; case NARROW : - /* if (fStandaloneNarrowQuarters) delete[] fStandaloneNarrowQuarters; fStandaloneNarrowQuarters = newUnicodeStringArray(count); uprv_arrayCopy( quartersArray,fStandaloneNarrowQuarters,count); fStandaloneNarrowQuartersCount = count; - */ break; default : break; @@ -2067,10 +2071,14 @@ DateFormatSymbols::initializeData(const Locale& locale, const char *type, UError fQuartersCount = 0; fShortQuarters = NULL; fShortQuartersCount = 0; + fNarrowQuarters = NULL; + fNarrowQuartersCount = 0; fStandaloneQuarters = NULL; fStandaloneQuartersCount = 0; fStandaloneShortQuarters = NULL; fStandaloneShortQuartersCount = 0; + fStandaloneNarrowQuarters = NULL; + fStandaloneNarrowQuartersCount = 0; fLeapMonthPatterns = NULL; fLeapMonthPatternsCount = 0; fShortYearNames = NULL; @@ -2374,6 +2382,16 @@ DateFormatSymbols::initializeData(const Locale& locale, const char *type, UError assignArray(fStandaloneShortQuarters, fStandaloneShortQuartersCount, fShortQuarters, fShortQuartersCount); } + // unlike the fields above, narrow format quarters fall back on narrow standalone quarters + initField(&fStandaloneNarrowQuarters, fStandaloneNarrowQuartersCount, calendarSink, + buildResourcePath(path, gQuartersTag, gNamesStandaloneTag, gNamesNarrowTag, status), status); + initField(&fNarrowQuarters, fNarrowQuartersCount, calendarSink, + buildResourcePath(path, gQuartersTag, gNamesFormatTag, gNamesNarrowTag, status), status); + if(status == U_MISSING_RESOURCE_ERROR) { + status = U_ZERO_ERROR; + assignArray(fNarrowQuarters, fNarrowQuartersCount, fStandaloneNarrowQuarters, fStandaloneNarrowQuartersCount); + } + // ICU 3.8 or later version no longer uses localized date-time pattern characters by default (ticket#5597) /* // fastCopyFrom()/setTo() - see assignArray comments @@ -2482,8 +2500,10 @@ DateFormatSymbols::initializeData(const Locale& locale, const char *type, UError initField(&fNarrowAmPms, fNarrowAmPmsCount, (const UChar *)gLastResortAmPmMarkers, kAmPmNum, kAmPmLen, status); initField(&fQuarters, fQuartersCount, (const UChar *)gLastResortQuarters, kQuarterNum, kQuarterLen, status); initField(&fShortQuarters, fShortQuartersCount, (const UChar *)gLastResortQuarters, kQuarterNum, kQuarterLen, status); + initField(&fNarrowQuarters, fNarrowQuartersCount, (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(&fStandaloneNarrowQuarters, fStandaloneNarrowQuartersCount, (const UChar *)gLastResortQuarters, kQuarterNum, kQuarterLen, status); fLocalPatternChars.setTo(TRUE, gPatternChars, PATTERN_CHARS_LEN); } } diff --git a/icu4c/source/i18n/smpdtfmt.cpp b/icu4c/source/i18n/smpdtfmt.cpp index 0e4cd36d567..5a7e2330723 100644 --- a/icu4c/source/i18n/smpdtfmt.cpp +++ b/icu4c/source/i18n/smpdtfmt.cpp @@ -1882,7 +1882,10 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, break; case UDAT_QUARTER_FIELD: - if (count >= 4) + if (count >= 5) + _appendSymbol(appendTo, value/3, fSymbols->fNarrowQuarters, + fSymbols->fNarrowQuartersCount); + else if (count == 4) _appendSymbol(appendTo, value/3, fSymbols->fQuarters, fSymbols->fQuartersCount); else if (count == 3) @@ -1893,7 +1896,10 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, break; case UDAT_STANDALONE_QUARTER_FIELD: - if (count >= 4) + if (count >= 5) + _appendSymbol(appendTo, value/3, fSymbols->fStandaloneNarrowQuarters, + fSymbols->fStandaloneNarrowQuartersCount); + else if (count == 4) _appendSymbol(appendTo, value/3, fSymbols->fStandaloneQuarters, fSymbols->fStandaloneQuartersCount); else if (count == 3) @@ -3471,7 +3477,7 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC return pos.getIndex(); } else { // count >= 3 // i.e., QQQ or QQQQ - // Want to be able to parse both short and long forms. + // Want to be able to parse short, long, and narrow forms. // Try count == 4 first: int32_t newStart = 0; @@ -3485,6 +3491,11 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC fSymbols->fShortQuarters, fSymbols->fShortQuartersCount, cal)) > 0) return newStart; } + if(getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH, status) || count == 5) { + if ((newStart = matchQuarterString(text, start, UCAL_MONTH, + fSymbols->fNarrowQuarters, fSymbols->fNarrowQuartersCount, cal)) > 0) + return newStart; + } if (!getBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, status)) return newStart; // else we allowing parsing as number, below @@ -3517,6 +3528,11 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC fSymbols->fStandaloneShortQuarters, fSymbols->fStandaloneShortQuartersCount, cal)) > 0) return newStart; } + if(getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH, status) || count == 5) { + if ((newStart = matchQuarterString(text, start, UCAL_MONTH, + fSymbols->fStandaloneNarrowQuarters, fSymbols->fStandaloneNarrowQuartersCount, cal)) > 0) + return newStart; + } if (!getBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, status)) return newStart; // else we allowing parsing as number, below diff --git a/icu4c/source/i18n/udat.cpp b/icu4c/source/i18n/udat.cpp index 63e6baea15e..d9549d04c57 100644 --- a/icu4c/source/i18n/udat.cpp +++ b/icu4c/source/i18n/udat.cpp @@ -704,6 +704,10 @@ udat_getSymbols(const UDateFormat *fmt, res = syms->getQuarters(count, DateFormatSymbols::FORMAT, DateFormatSymbols::ABBREVIATED); break; + case UDAT_NARROW_QUARTERS: + res = syms->getQuarters(count, DateFormatSymbols::FORMAT, DateFormatSymbols::NARROW); + break; + case UDAT_STANDALONE_QUARTERS: res = syms->getQuarters(count, DateFormatSymbols::STANDALONE, DateFormatSymbols::WIDE); break; @@ -712,6 +716,10 @@ udat_getSymbols(const UDateFormat *fmt, res = syms->getQuarters(count, DateFormatSymbols::STANDALONE, DateFormatSymbols::ABBREVIATED); break; + case UDAT_STANDALONE_NARROW_QUARTERS: + res = syms->getQuarters(count, DateFormatSymbols::STANDALONE, DateFormatSymbols::NARROW); + break; + case UDAT_CYCLIC_YEARS_WIDE: res = syms->getYearNames(count, DateFormatSymbols::FORMAT, DateFormatSymbols::WIDE); break; @@ -842,6 +850,10 @@ udat_countSymbols( const UDateFormat *fmt, syms->getQuarters(count, DateFormatSymbols::FORMAT, DateFormatSymbols::ABBREVIATED); break; + case UDAT_NARROW_QUARTERS: + syms->getQuarters(count, DateFormatSymbols::FORMAT, DateFormatSymbols::NARROW); + break; + case UDAT_STANDALONE_QUARTERS: syms->getQuarters(count, DateFormatSymbols::STANDALONE, DateFormatSymbols::WIDE); break; @@ -850,6 +862,10 @@ udat_countSymbols( const UDateFormat *fmt, syms->getQuarters(count, DateFormatSymbols::STANDALONE, DateFormatSymbols::ABBREVIATED); break; + case UDAT_STANDALONE_NARROW_QUARTERS: + syms->getQuarters(count, DateFormatSymbols::STANDALONE, DateFormatSymbols::NARROW); + break; + case UDAT_CYCLIC_YEARS_WIDE: syms->getYearNames(count, DateFormatSymbols::FORMAT, DateFormatSymbols::WIDE); break; @@ -1048,6 +1064,13 @@ public: setSymbol(syms->fShortQuarters, syms->fShortQuartersCount, index, value, valueLength, errorCode); } + static void + setNarrowQuarter(DateFormatSymbols *syms, int32_t index, + const UChar *value, int32_t valueLength, UErrorCode &errorCode) + { + setSymbol(syms->fNarrowQuarters, syms->fNarrowQuartersCount, index, value, valueLength, errorCode); + } + static void setStandaloneQuarter(DateFormatSymbols *syms, int32_t index, const UChar *value, int32_t valueLength, UErrorCode &errorCode) @@ -1062,6 +1085,13 @@ public: setSymbol(syms->fStandaloneShortQuarters, syms->fStandaloneShortQuartersCount, index, value, valueLength, errorCode); } + static void + setStandaloneNarrowQuarter(DateFormatSymbols *syms, int32_t index, + const UChar *value, int32_t valueLength, UErrorCode &errorCode) + { + setSymbol(syms->fStandaloneNarrowQuarters, syms->fStandaloneNarrowQuartersCount, index, value, valueLength, errorCode); + } + static void setShortYearNames(DateFormatSymbols *syms, int32_t index, const UChar *value, int32_t valueLength, UErrorCode &errorCode) @@ -1179,6 +1209,10 @@ udat_setSymbols( UDateFormat *format, DateFormatSymbolsSingleSetter::setShortQuarter(syms, index, value, valueLength, *status); break; + case UDAT_NARROW_QUARTERS: + DateFormatSymbolsSingleSetter::setNarrowQuarter(syms, index, value, valueLength, *status); + break; + case UDAT_STANDALONE_QUARTERS: DateFormatSymbolsSingleSetter::setStandaloneQuarter(syms, index, value, valueLength, *status); break; @@ -1187,6 +1221,10 @@ udat_setSymbols( UDateFormat *format, DateFormatSymbolsSingleSetter::setStandaloneShortQuarter(syms, index, value, valueLength, *status); break; + case UDAT_STANDALONE_NARROW_QUARTERS: + DateFormatSymbolsSingleSetter::setStandaloneNarrowQuarter(syms, index, value, valueLength, *status); + break; + case UDAT_CYCLIC_YEARS_ABBREVIATED: DateFormatSymbolsSingleSetter::setShortYearNames(syms, index, value, valueLength, *status); break; diff --git a/icu4c/source/i18n/unicode/dtfmtsym.h b/icu4c/source/i18n/unicode/dtfmtsym.h index 978f30b1d3d..f8fffe9718e 100644 --- a/icu4c/source/i18n/unicode/dtfmtsym.h +++ b/icu4c/source/i18n/unicode/dtfmtsym.h @@ -388,8 +388,7 @@ public: * Gets quarter strings by width and context. For example: "1st Quarter", "2nd Quarter", etc. * @param count Filled in with length of the array. * @param context The formatting context, either FORMAT or STANDALONE - * @param width The width of returned strings, either WIDE or ABBREVIATED. There - * are no NARROW quarters. + * @param width The width of returned strings, either WIDE, ABBREVIATED, or NARROW. * @return the quarter strings. (DateFormatSymbols retains ownership.) * @stable ICU 3.6 */ @@ -401,8 +400,7 @@ public: * @param quarters The new quarter strings. (not adopted; caller retains ownership) * @param count Filled in with length of the array. * @param context The formatting context, either FORMAT or STANDALONE - * @param width The width of returned strings, either WIDE or ABBREVIATED. There - * are no NARROW quarters. + * @param width The width of returned strings, either WIDE, ABBREVIATED, or NARROW. * @stable ICU 3.6 */ void setQuarters(const UnicodeString* quarters, int32_t count, DtContextType context, DtWidthType width); @@ -775,6 +773,13 @@ private: UnicodeString *fShortQuarters; int32_t fShortQuartersCount; + /** + * Narrow quarters. For example: "1", "2", etc. + * (In many, but not all, locales, this is the same as "Q", but there are locales for which this isn't true.) + */ + UnicodeString *fNarrowQuarters; + int32_t fNarrowQuartersCount; + /** * Standalone quarter strings. For example: "1st quarter", "2nd quarter", etc. */ @@ -787,6 +792,13 @@ private: UnicodeString *fStandaloneShortQuarters; int32_t fStandaloneShortQuartersCount; + /** + * Standalone narrow quarter strings. For example: "1", "2", etc. + * (In many, but not all, locales, this is the same as "q", but there are locales for which this isn't true.) + */ + UnicodeString *fStandaloneNarrowQuarters; + int32_t fStandaloneNarrowQuartersCount; + /** * All leap month patterns, for example "{0}bis". */ diff --git a/icu4c/source/i18n/unicode/udat.h b/icu4c/source/i18n/unicode/udat.h index 25e171f2d0e..0e6e6b76daa 100644 --- a/icu4c/source/i18n/unicode/udat.h +++ b/icu4c/source/i18n/unicode/udat.h @@ -1530,7 +1530,21 @@ typedef enum UDateFormatSymbolType { * udat_setSymbols not supported for UDAT_ZODIAC_NAMES_NARROW) * @stable ICU 54 */ - UDAT_ZODIAC_NAMES_NARROW + UDAT_ZODIAC_NAMES_NARROW, + +#ifndef U_HIDE_DRAFT_API + /** + * The narrow quarter names, for example 1 + * @draft ICU 70 + */ + UDAT_NARROW_QUARTERS, + + /** + * The narrow standalone quarter names, for example 1 + * @draft ICU 70 + */ + UDAT_STANDALONE_NARROW_QUARTERS +#endif // U_HIDE_DRAFT_API } UDateFormatSymbolType; struct UDateFormatSymbols; diff --git a/icu4c/source/test/cintltst/cdattst.c b/icu4c/source/test/cintltst/cdattst.c index 8c638bd0d06..bdd13647e75 100644 --- a/icu4c/source/test/cintltst/cdattst.c +++ b/icu4c/source/test/cintltst/cdattst.c @@ -44,6 +44,7 @@ static void TestParseErrorReturnValue(void); static void TestFormatForFields(void); static void TestForceGannenNumbering(void); static void TestMapDateToCalFields(void); +static void TestNarrowQuarters(void); void addDateForTest(TestNode** root); @@ -65,6 +66,7 @@ void addDateForTest(TestNode** root) TESTCASE(TestFormatForFields); TESTCASE(TestForceGannenNumbering); TESTCASE(TestMapDateToCalFields); + TESTCASE(TestNarrowQuarters); } /* Testing the DateFormat API */ static void TestDateFormat() @@ -579,7 +581,7 @@ static void TestRelativeDateFormat() /*Testing udat_getSymbols() and udat_setSymbols() and udat_countSymbols()*/ static void TestSymbols() { - UDateFormat *def, *fr, *zhChiCal; + UDateFormat *def, *fr, *zhChiCal, *esMX; UErrorCode status = U_ZERO_ERROR; UChar *value=NULL; UChar *result = NULL; @@ -618,7 +620,15 @@ static void TestSymbols() myErrorName(status) ); return; } - + /*creating a dateformat with es_MX locale */ + log_verbose("\ncreating a date format with es_MX locale\n"); + esMX = udat_open(UDAT_SHORT, UDAT_NONE, "es_MX", NULL, 0, NULL, 0, &status); + if(U_FAILURE(status)) + { + log_data_err("error in creating the dateformat using no date, short time, locale es_MX -> %s (Are you missing data?)\n", + myErrorName(status) ); + return; + } /*Testing countSymbols, getSymbols and setSymbols*/ log_verbose("\nTesting countSymbols\n"); @@ -683,6 +693,8 @@ static void TestSymbols() VerifygetSymbols(def, UDAT_QUARTERS, 3, "4th quarter"); VerifygetSymbols(fr, UDAT_SHORT_QUARTERS, 1, "T2"); VerifygetSymbols(def, UDAT_SHORT_QUARTERS, 2, "Q3"); + VerifygetSymbols(esMX, UDAT_STANDALONE_NARROW_QUARTERS, 1, "2T"); + VerifygetSymbols(def, UDAT_NARROW_QUARTERS, 2, "3"); VerifygetSymbols(zhChiCal, UDAT_CYCLIC_YEARS_ABBREVIATED, 0, "\\u7532\\u5B50"); VerifygetSymbols(zhChiCal, UDAT_CYCLIC_YEARS_NARROW, 59, "\\u7678\\u4EA5"); VerifygetSymbols(zhChiCal, UDAT_ZODIAC_NAMES_ABBREVIATED, 0, "\\u9F20"); @@ -803,8 +815,10 @@ free(pattern); VerifysetSymbols(fr, UDAT_STANDALONE_NARROW_MONTHS, 2, "M"); VerifysetSymbols(fr, UDAT_QUARTERS, 0, "1. Quart"); VerifysetSymbols(fr, UDAT_SHORT_QUARTERS, 1, "QQ2"); + VerifysetSymbols(fr, UDAT_NARROW_QUARTERS, 1, "!2"); VerifysetSymbols(fr, UDAT_STANDALONE_QUARTERS, 2, "3rd Quar."); VerifysetSymbols(fr, UDAT_STANDALONE_SHORT_QUARTERS, 3, "4QQ"); + VerifysetSymbols(fr, UDAT_STANDALONE_NARROW_QUARTERS, 3, "!4"); VerifysetSymbols(zhChiCal, UDAT_CYCLIC_YEARS_ABBREVIATED, 1, "yi-chou"); VerifysetSymbols(zhChiCal, UDAT_ZODIAC_NAMES_ABBREVIATED, 1, "Ox"); @@ -827,6 +841,7 @@ free(pattern); udat_close(fr); udat_close(def); udat_close(zhChiCal); + udat_close(esMX); if(result != NULL) { free(result); result = NULL; @@ -1943,4 +1958,63 @@ static void TestMapDateToCalFields(void){ } } +static void TestNarrowQuarters(void) { + // Test for rdar://79238094 + const UChar* testCases[] = { + u"en_US", u"QQQQ y", u"1st quarter 1970", + u"en_US", u"QQQ y", u"Q1 1970", + u"en_US", u"QQQQQ y", u"1 1970", + u"es_MX", u"QQQQ y", u"1.er trimestre 1970", + u"es_MX", u"QQQ y", u"T1 1970", + u"es_MX", u"QQQQQ y", u"1 1970", + u"en_US", u"qqqq", u"1st quarter", + u"en_US", u"qqq", u"Q1", + u"en_US", u"qqqqq", u"1", + u"es_MX", u"qqqq", u"1.er trimestre", + u"es_MX", u"qqq", u"T1", + u"es_MX", u"qqqqq", u"1T", + }; + + UErrorCode err = U_ZERO_ERROR; + UChar result[100]; + UDate parsedDate = 0; + UDate expectedFormatParsedDate = 0; + UDate expectedStandaloneParsedDate = 0; + + for (int32_t i = 0; i < UPRV_LENGTHOF(testCases); i += 3) { + const UChar* localeID = testCases[i]; + const UChar* pattern = testCases[i + 1]; + const UChar* expectedResult = testCases[i + 2]; + + err = U_ZERO_ERROR; + + UDateFormat* df = udat_open(UDAT_PATTERN, UDAT_PATTERN, austrdup(localeID), u"UTC", 0, pattern, -1, &err); + + udat_format(df, 0, result, 100, NULL, &err); + + if (assertSuccess("Formatting date failed", &err)) { + assertUEquals("Wrong formatting result", expectedResult, result); + } + + bool patternIsStandaloneQuarter = u_strchr(pattern, u'q') != NULL; + + parsedDate = udat_parse(df, expectedResult, -1, NULL, &err); + if (!patternIsStandaloneQuarter && expectedFormatParsedDate == 0) { + expectedFormatParsedDate = parsedDate; + } else if (patternIsStandaloneQuarter && expectedStandaloneParsedDate == 0) { + expectedStandaloneParsedDate = parsedDate; + } + + if (assertSuccess("Parsing date failed", &err)) { + if (patternIsStandaloneQuarter) { + assertIntEquals("Wrong parsing result", expectedStandaloneParsedDate, parsedDate); + } else { + assertIntEquals("Wrong parsing result", expectedFormatParsedDate, parsedDate); + } + } + + udat_close(df); + } +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/test/intltest/dtfmttst.cpp b/icu4c/source/test/intltest/dtfmttst.cpp index 366ef575206..e851258fe4e 100644 --- a/icu4c/source/test/intltest/dtfmttst.cpp +++ b/icu4c/source/test/intltest/dtfmttst.cpp @@ -1892,21 +1892,34 @@ void DateFormatTest::TestQuarters() const char *EN_DATA[] = { "yyyy MM dd", - "Q", "fp", "1970 01 01", "1", "1970 01 01", - "QQ", "fp", "1970 04 01", "02", "1970 04 01", - "QQQ", "fp", "1970 07 01", "Q3", "1970 07 01", - "QQQQ", "fp", "1970 10 01", "4th quarter", "1970 10 01", + "Q", "fp", "1970 01 01", "1", "1970 01 01", + "QQ", "fp", "1970 04 01", "02", "1970 04 01", + "QQQ", "fp", "1970 07 01", "Q3", "1970 07 01", + "QQQQ", "fp", "1970 10 01", "4th quarter", "1970 10 01", + "QQQQQ", "fp", "1970 10 01", "4", "1970 10 01", - "q", "fp", "1970 01 01", "1", "1970 01 01", - "qq", "fp", "1970 04 01", "02", "1970 04 01", - "qqq", "fp", "1970 07 01", "Q3", "1970 07 01", - "qqqq", "fp", "1970 10 01", "4th quarter", "1970 10 01", + "q", "fp", "1970 01 01", "1", "1970 01 01", + "qq", "fp", "1970 04 01", "02", "1970 04 01", + "qqq", "fp", "1970 07 01", "Q3", "1970 07 01", + "qqqq", "fp", "1970 10 01", "4th quarter", "1970 10 01", + "qqqqq", "fp", "1970 10 01", "4", "1970 10 01", - "Qyy", "fp", "2015 04 01", "215", "2015 04 01", - "QQyy", "fp", "2015 07 01", "0315", "2015 07 01", + "Qyy", "fp", "2015 04 01", "215", "2015 04 01", + "QQyy", "fp", "2015 07 01", "0315", "2015 07 01", + }; + const char *ES_MX_DATA[] = { + "yyyy MM dd", + + "QQQQ y", "fp", "1970 01 01", "1.er trimestre 1970", "1970 01 01", + "QQQ y", "fp", "1970 01 01", "T1 1970", "1970 01 01", + "QQQQQ y", "fp", "1970 01 01", "1 1970", "1970 01 01", + "qqqq", "fp", "1970 01 01", "1.er trimestre", "1970 01 01", + "qqq", "fp", "1970 01 01", "T1", "1970 01 01", + "qqqqq", "fp", "1970 01 01", "1T", "1970 01 01", }; expect(EN_DATA, UPRV_LENGTHOF(EN_DATA), Locale("en", "", "")); + expect(ES_MX_DATA, UPRV_LENGTHOF(ES_MX_DATA), Locale("es", "MX", "")); } /** diff --git a/icu4c/source/test/intltest/tsdtfmsy.cpp b/icu4c/source/test/intltest/tsdtfmsy.cpp index 2c1a1832e75..76440c30cd5 100644 --- a/icu4c/source/test/intltest/tsdtfmsy.cpp +++ b/icu4c/source/test/intltest/tsdtfmsy.cpp @@ -348,6 +348,13 @@ void IntlTestDateFormatSymbols::TestSymbols(/* char *par */) errln("ERROR: setQuarters(FORMAT, ABBREVIATED) failed"); } + const UnicodeString *narrowQuarters = en.getQuarters(count,DateFormatSymbols::FORMAT, DateFormatSymbols::NARROW); + fr2.setQuarters(narrowQuarters, count, DateFormatSymbols::FORMAT, DateFormatSymbols::NARROW); + if( *en.getQuarters(count,DateFormatSymbols::FORMAT, DateFormatSymbols::NARROW) != + *fr2.getQuarters(count,DateFormatSymbols::FORMAT ,DateFormatSymbols::NARROW )) { + errln("ERROR: setQuarters(FORMAT, NARROW) failed"); + } + const UnicodeString *standaloneWideQuarters = en.getQuarters(count,DateFormatSymbols::STANDALONE, DateFormatSymbols::WIDE); fr.setQuarters(standaloneWideQuarters, count, DateFormatSymbols::STANDALONE, DateFormatSymbols::WIDE); if( *en.getQuarters(count,DateFormatSymbols::STANDALONE, DateFormatSymbols::WIDE) != @@ -362,6 +369,13 @@ void IntlTestDateFormatSymbols::TestSymbols(/* char *par */) errln("ERROR: setQuarters(STANDALONE, ABBREVIATED) failed"); } + const UnicodeString *standaloneNarrowQuarters = en.getQuarters(count,DateFormatSymbols::STANDALONE, DateFormatSymbols::NARROW); + fr2.setQuarters(standaloneNarrowQuarters, count, DateFormatSymbols::STANDALONE, DateFormatSymbols::NARROW); + if( *en.getQuarters(count,DateFormatSymbols::STANDALONE, DateFormatSymbols::NARROW) != + *fr2.getQuarters(count,DateFormatSymbols::STANDALONE ,DateFormatSymbols::NARROW )) { + errln("ERROR: setQuarters(STANDALONE, NARROW) failed"); + } + const UnicodeString *ampms = en.getAmPmStrings(count); fr.setAmPmStrings(ampms, count); if( *en.getAmPmStrings(count) != *fr.getAmPmStrings(count)) { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DateFormatSymbols.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DateFormatSymbols.java index e1c9b60e485..d94e5b32b76 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DateFormatSymbols.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DateFormatSymbols.java @@ -515,6 +515,13 @@ public class DateFormatSymbols implements Serializable, Cloneable { */ String shortQuarters[] = null; + /** + * Narrow quarter names. For example: "1", "2", "3", "4". An array + * of 4 strings indexed by the month divided by 3. + * @serial + */ + String narrowQuarters[] = null; + /** * Full quarter names. For example: "1st Quarter", "2nd Quarter", "3rd Quarter", * "4th Quarter". An array of 4 strings, indexed by the month divided by 3. @@ -529,6 +536,13 @@ public class DateFormatSymbols implements Serializable, Cloneable { */ String standaloneShortQuarters[] = null; + /** + * Standalone narrow quarter names. For example: "1", "2", "3", "4". An array + * of 4 strings indexed by the month divided by 3. + * @serial + */ + String standaloneNarrowQuarters[] = null; + /** * Standalone full quarter names. For example: "1st Quarter", "2nd Quarter", "3rd Quarter", * "4th Quarter". An array of 4 strings, indexed by the month divided by 3. @@ -1042,7 +1056,7 @@ public class DateFormatSymbols implements Serializable, Cloneable { * {@icu} Returns quarter strings. For example: "1st Quarter", "2nd Quarter", etc. * @param context The quarter context, FORMAT or STANDALONE. * @param width The width or the returned quarter string, - * either WIDE or ABBREVIATED. There are no NARROW quarters. + * WIDE, NARROW, or ABBREVIATED. * @return the quarter strings. * @stable ICU 3.6 */ @@ -1059,7 +1073,7 @@ public class DateFormatSymbols implements Serializable, Cloneable { returnValue = shortQuarters; break; case NARROW : - returnValue = null; + returnValue = narrowQuarters; break; } break; @@ -1074,7 +1088,7 @@ public class DateFormatSymbols implements Serializable, Cloneable { returnValue = standaloneShortQuarters; break; case NARROW: - returnValue = null; + returnValue = standaloneNarrowQuarters; break; } break; @@ -1090,7 +1104,7 @@ public class DateFormatSymbols implements Serializable, Cloneable { * @param newQuarters the new quarter strings. * @param context The formatting context, FORMAT or STANDALONE. * @param width The width of the quarter string, - * either WIDE or ABBREVIATED. There are no NARROW quarters. + * WIDE, NARROW, or ABBREVIATED. * @stable ICU 3.8 */ public void setQuarters(String[] newQuarters, int context, int width) { @@ -1104,7 +1118,7 @@ public class DateFormatSymbols implements Serializable, Cloneable { shortQuarters = duplicate(newQuarters); break; case NARROW : - //narrowQuarters = duplicate(newQuarters); + narrowQuarters = duplicate(newQuarters); break; default : // HANDLE SHORT, etc. break; @@ -1119,7 +1133,7 @@ public class DateFormatSymbols implements Serializable, Cloneable { standaloneShortQuarters = duplicate(newQuarters); break; case NARROW : - //standaloneNarrowQuarters = duplicate(newQuarters); + standaloneNarrowQuarters = duplicate(newQuarters); break; default : // HANDLE SHORT, etc. break; @@ -1580,8 +1594,10 @@ public class DateFormatSymbols implements Serializable, Cloneable { this.ampmsNarrow = dfs.ampmsNarrow; this.timeSeparator = dfs.timeSeparator; this.shortQuarters = dfs.shortQuarters; + this.narrowQuarters = dfs.narrowQuarters; this.quarters = dfs.quarters; this.standaloneShortQuarters = dfs.standaloneShortQuarters; + this.standaloneNarrowQuarters = dfs.standaloneNarrowQuarters; this.standaloneQuarters = dfs.standaloneQuarters; this.leapMonthPatterns = dfs.leapMonthPatterns; this.shortYearNames = dfs.shortYearNames; @@ -1982,9 +1998,11 @@ public class DateFormatSymbols implements Serializable, Cloneable { quarters = arrays.get("quarters/format/wide"); shortQuarters = arrays.get("quarters/format/abbreviated"); + narrowQuarters = arrays.get("quarters/format/narrow"); standaloneQuarters = arrays.get("quarters/stand-alone/wide"); standaloneShortQuarters = arrays.get("quarters/stand-alone/abbreviated"); + standaloneNarrowQuarters = arrays.get("quarters/stand-alone/narrow"); abbreviatedDayPeriods = loadDayPeriodStrings(maps.get("dayPeriod/format/abbreviated")); wideDayPeriods = loadDayPeriodStrings(maps.get("dayPeriod/format/wide")); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/SimpleDateFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/SimpleDateFormat.java index 01d803cf01b..222e9141c08 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/SimpleDateFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/SimpleDateFormat.java @@ -1899,7 +1899,9 @@ public class SimpleDateFormat extends DateFormat { } break; case 27: // 'Q' - QUARTER - if (count >= 4) { + if (count >= 5) { + safeAppend(formatData.narrowQuarters, value/3, buf); + } else if (count == 4) { safeAppend(formatData.quarters, value/3, buf); } else if (count == 3) { safeAppend(formatData.shortQuarters, value/3, buf); @@ -1908,7 +1910,9 @@ public class SimpleDateFormat extends DateFormat { } break; case 28: // 'q' - STANDALONE QUARTER - if (count >= 4) { + if (count >= 5) { + safeAppend(formatData.standaloneNarrowQuarters, value/3, buf); + } else if (count == 4) { safeAppend(formatData.standaloneQuarters, value/3, buf); } else if (count == 3) { safeAppend(formatData.standaloneShortQuarters, value/3, buf); @@ -3614,7 +3618,7 @@ public class SimpleDateFormat extends DateFormat { return pos.getIndex(); } else { // count >= 3 // i.e., QQQ or QQQQ - // Want to be able to parse both short and long forms. + // Want to be able to parse short, long, and narrow forms. // Try count == 4 first: int newStart = 0; if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { @@ -3624,8 +3628,14 @@ public class SimpleDateFormat extends DateFormat { } // count == 4 failed, now try count == 3 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) { + if((newStart = matchQuarterString(text, start, Calendar.MONTH, formatData.shortQuarters, cal)) > 0) { + return newStart; + } + } + // count == 3 failed, now try count == 5 + if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 5) { return matchQuarterString(text, start, Calendar.MONTH, - formatData.shortQuarters, cal); + formatData.narrowQuarters, cal); } return newStart; } @@ -3640,7 +3650,7 @@ public class SimpleDateFormat extends DateFormat { return pos.getIndex(); } else { // count >= 3 // i.e., qqq or qqqq - // Want to be able to parse both short and long forms. + // Want to be able to parse short, long, and narrow forms. // Try count == 4 first: int newStart = 0; if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { @@ -3650,8 +3660,14 @@ public class SimpleDateFormat extends DateFormat { } // count == 4 failed, now try count == 3 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) { + if((newStart = matchQuarterString(text, start, Calendar.MONTH, formatData.standaloneShortQuarters, cal)) > 0) { + return newStart; + } + } + // count == 3 failed, now try count == 5 + if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 5) { return matchQuarterString(text, start, Calendar.MONTH, - formatData.standaloneShortQuarters, cal); + formatData.standaloneNarrowQuarters, cal); } return newStart; } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatTest.java index a43a1fca71e..f15b678488e 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatTest.java @@ -3154,21 +3154,35 @@ public class DateFormatTest extends TestFmwk { String EN_DATA[] = { "yyyy MM dd", - "Q", "fp", "1970 01 01", "1", "1970 01 01", - "QQ", "fp", "1970 04 01", "02", "1970 04 01", - "QQQ", "fp", "1970 07 01", "Q3", "1970 07 01", - "QQQQ", "fp", "1970 10 01", "4th quarter", "1970 10 01", + "Q", "fp", "1970 01 01", "1", "1970 01 01", + "QQ", "fp", "1970 04 01", "02", "1970 04 01", + "QQQ", "fp", "1970 07 01", "Q3", "1970 07 01", + "QQQQ", "fp", "1970 10 01", "4th quarter", "1970 10 01", + "QQQQQ", "fp", "1970 10 01", "4", "1970 10 01", - "q", "fp", "1970 01 01", "1", "1970 01 01", - "qq", "fp", "1970 04 01", "02", "1970 04 01", - "qqq", "fp", "1970 07 01", "Q3", "1970 07 01", - "qqqq", "fp", "1970 10 01", "4th quarter", "1970 10 01", + "q", "fp", "1970 01 01", "1", "1970 01 01", + "qq", "fp", "1970 04 01", "02", "1970 04 01", + "qqq", "fp", "1970 07 01", "Q3", "1970 07 01", + "qqqq", "fp", "1970 10 01", "4th quarter", "1970 10 01", + "qqqqq", "fp", "1970 10 01", "4", "1970 10 01", "Qyy", "fp", "2015 04 01", "215", "2015 04 01", "QQyy", "fp", "2015 07 01", "0315", "2015 07 01", }; + String ES_MX_DATA[] = { + "yyyy MM dd", + +// Test commented out because of ICU-21671 +// "QQQQ y", "fp", "1970 01 01", "1.er trimestre 1970", "1970 01 01", + "QQQ y", "fp", "1970 01 01", "T1 1970", "1970 01 01", + "QQQQQ y", "fp", "1970 01 01", "1 1970", "1970 01 01", + "qqqq", "fp", "1970 01 01", "1.er trimestre", "1970 01 01", + "qqq", "fp", "1970 01 01", "T1", "1970 01 01", + "qqqqq", "fp", "1970 01 01", "1T", "1970 01 01", + }; expect(EN_DATA, new Locale("en", "", "")); + expect(ES_MX_DATA, new Locale("es", "MX", "")); } /** diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/IntlTestDateFormatSymbols.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/IntlTestDateFormatSymbols.java index 00f10e0e702..9be1c759bbe 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/IntlTestDateFormatSymbols.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/IntlTestDateFormatSymbols.java @@ -451,6 +451,21 @@ public class IntlTestDateFormatSymbols extends TestFmwk } } + final String[] narrowQuarters = en.getQuarters(DateFormatSymbols.FORMAT,DateFormatSymbols.NARROW); + fr2.setQuarters(narrowQuarters,DateFormatSymbols.FORMAT,DateFormatSymbols.NARROW); + final String[] narrowQuarters1 = fr2.getQuarters(DateFormatSymbols.FORMAT,DateFormatSymbols.NARROW); + count = narrowQuarters.length; + if( count != narrowQuarters1.length) { + errln("ERROR: setQuarters(FORMAT, NARROW) failed (different size array)"); + } + else { + for(int i = 0; i < count; i++) { + if(! narrowQuarters[i].equals(narrowQuarters1[i])) { + errln("ERROR: setQuarters(FORMAT, NARROW) failed (different string values)"); + } + } + } + final String[] standaloneQuarters = en.getQuarters(DateFormatSymbols.STANDALONE,DateFormatSymbols.WIDE); fr.setQuarters(standaloneQuarters,DateFormatSymbols.STANDALONE,DateFormatSymbols.WIDE); final String[] standaloneQuarters1 = fr.getQuarters(DateFormatSymbols.STANDALONE,DateFormatSymbols.WIDE); @@ -481,6 +496,21 @@ public class IntlTestDateFormatSymbols extends TestFmwk } } + final String[] standaloneNarrowQuarters = en.getQuarters(DateFormatSymbols.STANDALONE,DateFormatSymbols.NARROW); + fr2.setQuarters(standaloneNarrowQuarters,DateFormatSymbols.STANDALONE,DateFormatSymbols.NARROW); + final String[] standaloneNarrowQuarters1 = fr2.getQuarters(DateFormatSymbols.STANDALONE,DateFormatSymbols.NARROW); + count = standaloneNarrowQuarters.length; + if( count != standaloneNarrowQuarters1.length) { + errln("ERROR: setQuarters(STANDALONE, NARROW) failed (different size array)"); + } + else { + for(int i = 0; i < count; i++) { + if(! standaloneNarrowQuarters[i].equals(standaloneNarrowQuarters1[i])) { + errln("ERROR: setQuarters(STANDALONE, NARROW) failed (different string values)"); + } + } + } + final String[] ampms = en.getAmPmStrings(); fr.setAmPmStrings(ampms); final String[] ampms1 = fr.getAmPmStrings();