From 26063a3454ed7e68f0e24b0a283d13d747c4abcb Mon Sep 17 00:00:00 2001 From: Tom Zhang Date: Tue, 29 Jul 2014 18:42:07 +0000 Subject: [PATCH] ICU-10320 API for getting/setting number format override in date formatting X-SVN-Rev: 36097 --- icu4c/source/i18n/smpdtfmt.cpp | 92 +++++++++++++++++++++ icu4c/source/i18n/udat.cpp | 24 ++++++ icu4c/source/i18n/unicode/smpdtfmt.h | 35 ++++++++ icu4c/source/i18n/unicode/udat.h | 37 ++++++++- icu4c/source/test/cintltst/cdattst.c | 102 ++++++++++++++++++++++++ icu4c/source/test/cintltst/cdattst.h | 8 +- icu4c/source/test/intltest/dtfmttst.cpp | 74 +++++++++++++++++ icu4c/source/test/intltest/dtfmttst.h | 3 + 8 files changed, 373 insertions(+), 2 deletions(-) diff --git a/icu4c/source/i18n/smpdtfmt.cpp b/icu4c/source/i18n/smpdtfmt.cpp index 6ec91e6952d..8085dc12e36 100644 --- a/icu4c/source/i18n/smpdtfmt.cpp +++ b/icu4c/source/i18n/smpdtfmt.cpp @@ -1648,6 +1648,98 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, //---------------------------------------------------------------------- +void SimpleDateFormat::adoptNumberFormat(NumberFormat *formatToAdopt) { + formatToAdopt->setParseIntegerOnly(TRUE); + fNumberFormat = formatToAdopt; + + if (fNumberFormatters) { + for (int32_t i = 0; i < UDAT_FIELD_COUNT; i++) { + if (fNumberFormatters[i] == formatToAdopt) { + fNumberFormatters[i] = NULL; + } + } + uprv_free(fNumberFormatters); + fNumberFormatters = NULL; + } + + while (fOverrideList) { + NSOverride *cur = fOverrideList; + fOverrideList = cur->next; + if (cur->nf != formatToAdopt) { // only delete those not duplicate + delete cur->nf; + uprv_free(cur); + } + } +} + +void SimpleDateFormat::adoptNumberFormat(const UnicodeString& fields, NumberFormat *formatToAdopt, UErrorCode &status){ + // if it has not been initialized yet, initialize + if (fNumberFormatters == NULL) { + fNumberFormatters = (NumberFormat**)uprv_malloc(UDAT_FIELD_COUNT * sizeof(NumberFormat*)); + if (fNumberFormatters) { + for (int32_t i = 0; i < UDAT_FIELD_COUNT; i++) { + fNumberFormatters[i] = fNumberFormat; + } + } else { + status = U_MEMORY_ALLOCATION_ERROR; + return; + } + } + + // See if the numbering format is in the override list, if not, then add it. + NSOverride *cur = fOverrideList; + UBool found = FALSE; + while (cur && !found) { + if ( cur->nf == formatToAdopt ) { + found = TRUE; + } + cur = cur->next; + } + + if (!found) { + cur = (NSOverride *)uprv_malloc(sizeof(NSOverride)); + if (cur) { + // no matter what the locale's default number format looked like, we want + // to modify it so that it doesn't use thousands separators, doesn't always + // show the decimal point, and recognizes integers only when parsing + formatToAdopt->setGroupingUsed(FALSE); + DecimalFormat* decfmt = dynamic_cast(formatToAdopt); + if (decfmt != NULL) { + decfmt->setDecimalSeparatorAlwaysShown(FALSE); + } + formatToAdopt->setParseIntegerOnly(TRUE); + formatToAdopt->setMinimumFractionDigits(0); // To prevent "Jan 1.00, 1997.00" + + cur->nf = formatToAdopt; + cur->hash = -1; // set duplicate here (before we set it with NumberSystem Hash, here we cannot get nor use it) + cur->next = fOverrideList; + fOverrideList = cur; + } else { + status = U_MEMORY_ALLOCATION_ERROR; + return; + } + } + + for (int i=0; isetCalendar(*((Calendar*)calendarToSet)); } +U_DRAFT const UNumberFormat* U_EXPORT2 +udat_getNumberFormatForField(const UDateFormat* fmt, UChar field) +{ + UErrorCode status = U_ZERO_ERROR; + verifyIsSimpleDateFormat(fmt, &status); + if (U_FAILURE(status)) return (const UNumberFormat*) ((DateFormat*)fmt)->getNumberFormat(); + return (const UNumberFormat*) ((SimpleDateFormat*)fmt)->getNumberFormatForField(field); +} + U_CAPI const UNumberFormat* U_EXPORT2 udat_getNumberFormat(const UDateFormat* fmt) { return (const UNumberFormat*) ((DateFormat*)fmt)->getNumberFormat(); } +U_DRAFT void U_EXPORT2 +udat_adoptNumberFormatForFields( UDateFormat* fmt, + const UChar* fields, + UNumberFormat* numberFormatToSet, + UErrorCode* status) +{ + verifyIsSimpleDateFormat(fmt, status); + if (U_FAILURE(*status)) return; + + if (fields!=NULL) { + UnicodeString overrideFields(fields); + ((SimpleDateFormat*)fmt)->adoptNumberFormat(overrideFields, (NumberFormat*)numberFormatToSet, *status); + } +} + U_CAPI void U_EXPORT2 udat_setNumberFormat(UDateFormat* fmt, const UNumberFormat* numberFormatToSet) diff --git a/icu4c/source/i18n/unicode/smpdtfmt.h b/icu4c/source/i18n/unicode/smpdtfmt.h index efdb6471f04..55c4b5a8d88 100644 --- a/icu4c/source/i18n/unicode/smpdtfmt.h +++ b/icu4c/source/i18n/unicode/smpdtfmt.h @@ -1121,6 +1121,41 @@ public: * @draft ICU 53 */ virtual void setContext(UDisplayContext value, UErrorCode& status); + +#ifndef U_HIDE_DRAFT_API + /** + * Overrides base class method and + * This method clears per field NumberFormat instances + * previously set by {@see adoptNumberFormat(const UnicodeString&, NumberFormat*, UErrorCode)} + * @param adoptNF the NumbeferFormat used + * @draft ICU 54 + */ + void adoptNumberFormat(NumberFormat *formatToAdopt); + + /** + * Allow the user to set the NumberFormat for several fields + * It can be a single field like: "y"(year) or "M"(month) + * It can be several field combined together: "yM"(year and month) + * Note: + * 1 symbol field is enough for multiple symbol field (so "y" will override "yy", "yyy") + * If the field is not numeric, then override has no effect (like "MMM" will use abbreviation, not numerical field) + * Per field NumberFormat can also be cleared in {@see DateFormat::setNumberFormat(const NumberFormat& newNumberFormat)} + * + * @param fields the fields to override(like y) + * @param adoptNF the NumbeferFormat used + * @param status Receives a status code, which will be U_ZERO_ERROR + * if the operation succeeds. + * @draft ICU 54 + */ + void adoptNumberFormat(const UnicodeString& fields, NumberFormat *formatToAdopt, UErrorCode &status); + + /** + * Get the numbering system to be used for a particular field. + * @param field The UDateFormatField to get + * @draft ICU 54 + */ + const NumberFormat * getNumberFormatForField(UChar field) const; +#endif /* U_HIDE_DRAFT_API */ #ifndef U_HIDE_INTERNAL_API /** diff --git a/icu4c/source/i18n/unicode/udat.h b/icu4c/source/i18n/unicode/udat.h index bf4aba10eec..e0f098807f2 100644 --- a/icu4c/source/i18n/unicode/udat.h +++ b/icu4c/source/i18n/unicode/udat.h @@ -1084,14 +1084,49 @@ udat_setCalendar( UDateFormat* fmt, U_STABLE const UNumberFormat* U_EXPORT2 udat_getNumberFormat(const UDateFormat* fmt); +/** +* Get the UNumberFormat for specific field associated with an UDateFormat. +* For example: 'y' for year and 'M' for month +* @param fmt The formatter to query. +* @param field the field to query +* @return A pointer to the UNumberFormat used by fmt to format field numbers. +* @see udat_setNumberFormatForField +* @draft ICU 54 +*/ +U_DRAFT const UNumberFormat* U_EXPORT2 +udat_getNumberFormatForField(const UDateFormat* fmt, UChar field); + +/** +* Set the UNumberFormat for specific field associated with an UDateFormat. +* It can be a single field like: "y"(year) or "M"(month) +* It can be several field combined together: "yM"(year and month) +* Note: +* 1 symbol field is enough for multiple symbol field (so "y" will override "yy", "yyy") +* If the field is not numeric, then override has no effect (like "MMM" will use abbreviation, not numerical field) +* +* @param fields the fields to set +* @param fmt The formatter to set. +* @param numberFormatToSet A pointer to the UNumberFormat to be used by fmt to format numbers. +* @param status error code passed around (memory allocation or invalid fields) +* @see udat_getNumberFormatForField +* @draft ICU 54 +*/ +U_DRAFT void U_EXPORT2 +udat_adoptNumberFormatForFields( UDateFormat* fmt, + const UChar* fields, + UNumberFormat* numberFormatToSet, + UErrorCode* status); + /** * Set the UNumberFormat associated with an UDateFormat. * A UDateFormat uses a UNumberFormat to format numbers within a date, * for example the day number. -* Note: udat_setNumberFormat will clone the UNumberFormat* +* This method also clears per field NumberFormat instances previously +* set by {@see udat_setNumberFormatForField} * @param fmt The formatter to set. * @param numberFormatToSet A pointer to the UNumberFormat to be used by fmt to format numbers. * @see udat_getNumberFormat +* @see udat_setNumberFormatForField * @stable ICU 2.0 */ U_STABLE void U_EXPORT2 diff --git a/icu4c/source/test/cintltst/cdattst.c b/icu4c/source/test/cintltst/cdattst.c index 37e38804fdb..a4deee23502 100644 --- a/icu4c/source/test/cintltst/cdattst.c +++ b/icu4c/source/test/cintltst/cdattst.c @@ -55,6 +55,7 @@ void addDateForTest(TestNode** root) TESTCASE(TestRelativeCrash); TESTCASE(TestContext); TESTCASE(TestCalendarDateParse); + TESTCASE(TestOverrideNumberForamt); } /* Testing the DateFormat API */ static void TestDateFormat() @@ -1542,4 +1543,105 @@ static void TestContext(void) { } } + +// overrideNumberFormat[i][0] is to tell which field to set, +// overrideNumberFormat[i][1] is the expected result +static const char * overrideNumberFormat[][2] = { + {"", "\\u521D\\u4E03 \\u521D\\u4E8C"}, + {"d", "07 \\u521D\\u4E8C"}, + {"do", "07 \\u521D\\u4E8C"}, + {"Md", "\\u521D\\u4E03 \\u521D\\u4E8C"}, + {"MdMMd", "\\u521D\\u4E03 \\u521D\\u4E8C"}, + {"mixed", "\\u521D\\u4E03 \\u521D\\u4E8C"} +}; + +static void TestOverrideNumberForamt(void) { + UErrorCode status = U_ZERO_ERROR; + UChar pattern[50]; + UChar* expected; + UChar* fields; + char bbuf1[kBbufMax]; + char bbuf2[kBbufMax]; + const char* localeString = "zh@numbers=hanidays"; + UDateFormat* fmt; + UNumberFormat* overrideFmt; + const UNumberFormat* getter_result; + UDate july022008 = 1215000000000.0; + int32_t i; + + expected=(UChar*)malloc(sizeof(UChar) * 10); + fields=(UChar*)malloc(sizeof(UChar) * 10); + u_uastrcpy(fields, "d"); + u_uastrcpy(pattern,"MM d"); + + fmt=udat_open(UDAT_PATTERN, UDAT_PATTERN,"en_US",NULL,0,pattern, u_strlen(pattern), &status); + assertSuccess("udat_open()", &status); + + overrideFmt = unum_open(UNUM_DEFAULT, NULL, 0, localeString, NULL, &status); + assertSuccess("unum_open()", &status); + + // loop 50 times to check getter/setter + for (i = 0; i < 50; i++){ + udat_adoptNumberFormatForFields(fmt, fields, overrideFmt, &status); + assertSuccess("udat_setNumberFormatForField()", &status); + + getter_result = udat_getNumberFormatForField(fmt, 'd'); + if (getter_result != overrideFmt) + log_err("FAIL: udat_getNumberFormatForField does not work\n"); + } + udat_setNumberFormat(fmt, overrideFmt); // test the same override NF will not crash + udat_close(fmt); + + for (i=0; igetBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, status)); } +void DateFormatTest::TestNumberFormatOverride() { + UErrorCode status = U_ZERO_ERROR; + UnicodeString fields = (UnicodeString) "M"; + + LocalPointer fmt; + fmt.adoptInstead(new SimpleDateFormat((UnicodeString)"MM d", status)); + assertSuccess("SimpleDateFormat with pattern MM d", status); + + NumberFormat* check_nf = NumberFormat::createInstance(Locale("en_US"), status); + assertSuccess("NumberFormat en_US", status); + + // loop 100 times to test setter/getter + for(int i=0; i<100; i++){ + fmt->adoptNumberFormat(fields, check_nf, status); + assertSuccess("adoptNumberFormat check_nf", status); + + const NumberFormat* get_nf = fmt->getNumberFormatForField('M'); + if (get_nf != check_nf) errln("FAIL: getter and setter do not work"); + } + fmt->adoptNumberFormat(check_nf); // make sure using the same NF will not crash + + const char * DATA [][2] = { + { "", "\\u521D\\u516D \\u5341\\u4E94"}, + { "M", "\\u521D\\u516D 15"}, + { "Mo", "\\u521D\\u516D 15"}, + { "Md", "\\u521D\\u516D \\u5341\\u4E94"}, + { "MdMMd", "\\u521D\\u516D \\u5341\\u4E94"}, + { "mixed", "\\u521D\\u516D \\u5341\\u4E94"} + }; + + UDate test_date = date(97, 6 - 1, 15); + + for(int i=0; i < sizeof(DATA)/sizeof(DATA[0]); i++){ + fields = DATA[i][0]; + + LocalPointer fmt; + fmt.adoptInstead(new SimpleDateFormat((UnicodeString)"MM d", status)); + assertSuccess("SimpleDateFormat with pattern MM d", status); + NumberFormat* overrideNF = NumberFormat::createInstance(Locale::createFromName("zh@numbers=hanidays"),status); + assertSuccess("NumberFormat zh@numbers=hanidays", status); + + if (fields == (UnicodeString) "") { // use the one w/o fields + fmt->adoptNumberFormat(overrideNF); + } else if (fields == (UnicodeString) "mixed") { // set 1 field at first but then full override, both(M & d) should be override + NumberFormat* singleOverrideNF = NumberFormat::createInstance(Locale::createFromName("en@numbers=hebr"),status); + assertSuccess("NumberFormat en@numbers=hebr", status); + + fields = (UnicodeString) "M"; + fmt->adoptNumberFormat(fields, singleOverrideNF, status); + assertSuccess("adoptNumberFormat singleOverrideNF", status); + + fmt->adoptNumberFormat(overrideNF); + } else if (fields == (UnicodeString) "Mo"){ // o is invlid field + fmt->adoptNumberFormat(fields, overrideNF, status); + if(status == U_INVALID_FORMAT_ERROR) { + status = U_ZERO_ERROR; + continue; + } + } else { + fmt->adoptNumberFormat(fields, overrideNF, status); + assertSuccess("adoptNumberFormat overrideNF", status); + } + + UnicodeString result; + FieldPosition pos(0); + fmt->format(test_date,result, pos); + + UnicodeString expected = ((UnicodeString)DATA[i][1]).unescape();; + + if (result != expected) + errln("FAIL: Expected " + expected + " get: " + result); + } +} #endif /* #if !UCONFIG_NO_FORMATTING */ //eof diff --git a/icu4c/source/test/intltest/dtfmttst.h b/icu4c/source/test/intltest/dtfmttst.h index bd0f61b452b..a9dd1a66dac 100644 --- a/icu4c/source/test/intltest/dtfmttst.h +++ b/icu4c/source/test/intltest/dtfmttst.h @@ -235,6 +235,9 @@ public: void TestParseLeniencyAPIs(); + // test override NumberFormat + void TestNumberFormatOverride(); + private: UBool showParse(DateFormat &format, const UnicodeString &formattedString);