diff --git a/icu4c/source/common/ucurr.cpp b/icu4c/source/common/ucurr.cpp index 5eacc4a99bc..f64bd07a71c 100644 --- a/icu4c/source/common/ucurr.cpp +++ b/icu4c/source/common/ucurr.cpp @@ -91,6 +91,8 @@ static const char VAR_DELIM = '_'; // Tag for localized display names (symbols) of currencies static const char CURRENCIES[] = "Currencies"; static const char CURRENCIES_NARROW[] = "Currencies%narrow"; +static const char CURRENCIES_FORMAL[] = "Currencies%formal"; +static const char CURRENCIES_VARIANT[] = "Currencies%variant"; static const char CURRENCYPLURALS[] = "CurrencyPlurals"; // ISO codes mapping table @@ -649,7 +651,7 @@ ucurr_getName(const UChar* currency, } int32_t choice = (int32_t) nameStyle; - if (choice < 0 || choice > 2) { + if (choice < 0 || choice > 4) { *ec = U_ILLEGAL_ARGUMENT_ERROR; return 0; } @@ -684,9 +686,22 @@ ucurr_getName(const UChar* currency, ec2 = U_ZERO_ERROR; LocalUResourceBundlePointer rb(ures_open(U_ICUDATA_CURR, loc, &ec2)); - if (nameStyle == UCURR_NARROW_SYMBOL_NAME) { + if (nameStyle == UCURR_NARROW_SYMBOL_NAME || nameStyle == UCURR_FORMAL_SYMBOL_NAME || nameStyle == UCURR_VARIANT_SYMBOL_NAME) { CharString key; - key.append(CURRENCIES_NARROW, ec2); + switch (nameStyle) { + case UCURR_NARROW_SYMBOL_NAME: + key.append(CURRENCIES_NARROW, ec2); + break; + case UCURR_FORMAL_SYMBOL_NAME: + key.append(CURRENCIES_FORMAL, ec2); + break; + case UCURR_VARIANT_SYMBOL_NAME: + key.append(CURRENCIES_VARIANT, ec2); + break; + default: + *ec = U_UNSUPPORTED_ERROR; + return 0; + } key.append("/", ec2); key.append(buf, ec2); s = ures_getStringByKeyWithFallback(rb.getAlias(), key.data(), len, &ec2); diff --git a/icu4c/source/common/unicode/ucurr.h b/icu4c/source/common/unicode/ucurr.h index f91cc0df7c6..6666c138272 100644 --- a/icu4c/source/common/unicode/ucurr.h +++ b/icu4c/source/common/unicode/ucurr.h @@ -113,7 +113,29 @@ typedef enum UCurrNameStyle { * * @stable ICU 61 */ - UCURR_NARROW_SYMBOL_NAME + UCURR_NARROW_SYMBOL_NAME, + +#ifndef U_HIDE_DRAFT_API + /** + * Selector for getName() indicating the formal currency symbol. + * The formal currency symbol is similar to the regular currency + * symbol, but it always takes the form used in formal settings + * such as banking; for example, "NT$" instead of "$" for TWD in zh-TW. + * + * @draft ICU 68 + */ + UCURR_FORMAL_SYMBOL_NAME, + + /** + * Selector for getName() indicating the variant currency symbol. + * The variant symbol for a currency is an alternative symbol + * that is not necessarily as widely used as the regular symbol. + * + * @draft ICU 68 + */ + UCURR_VARIANT_SYMBOL_NAME +#endif // U_HIDE_DRAFT_API + } UCurrNameStyle; #if !UCONFIG_NO_SERVICE diff --git a/icu4c/source/i18n/number_currencysymbols.cpp b/icu4c/source/i18n/number_currencysymbols.cpp index 4d6fb2cb1d8..9208427904c 100644 --- a/icu4c/source/i18n/number_currencysymbols.cpp +++ b/icu4c/source/i18n/number_currencysymbols.cpp @@ -44,6 +44,16 @@ UnicodeString CurrencySymbols::getNarrowCurrencySymbol(UErrorCode& status) const return loadSymbol(UCURR_NARROW_SYMBOL_NAME, status); } +UnicodeString CurrencySymbols::getFormalCurrencySymbol(UErrorCode& status) const { + // Note: currently no override is available for formal currency symbol + return loadSymbol(UCURR_FORMAL_SYMBOL_NAME, status); +} + +UnicodeString CurrencySymbols::getVariantCurrencySymbol(UErrorCode& status) const { + // Note: currently no override is available for variant currency symbol + return loadSymbol(UCURR_VARIANT_SYMBOL_NAME, status); +} + UnicodeString CurrencySymbols::getCurrencySymbol(UErrorCode& status) const { if (!fCurrencySymbol.isBogus()) { return fCurrencySymbol; diff --git a/icu4c/source/i18n/number_currencysymbols.h b/icu4c/source/i18n/number_currencysymbols.h index 9996bf96ae0..7e38fdf8287 100644 --- a/icu4c/source/i18n/number_currencysymbols.h +++ b/icu4c/source/i18n/number_currencysymbols.h @@ -31,6 +31,10 @@ class U_I18N_API CurrencySymbols : public UMemory { UnicodeString getNarrowCurrencySymbol(UErrorCode& status) const; + UnicodeString getFormalCurrencySymbol(UErrorCode& status) const; + + UnicodeString getVariantCurrencySymbol(UErrorCode& status) const; + UnicodeString getCurrencySymbol(UErrorCode& status) const; UnicodeString getIntlCurrencySymbol(UErrorCode& status) const; diff --git a/icu4c/source/i18n/number_patternmodifier.cpp b/icu4c/source/i18n/number_patternmodifier.cpp index 45602942aef..314e7cb75ee 100644 --- a/icu4c/source/i18n/number_patternmodifier.cpp +++ b/icu4c/source/i18n/number_patternmodifier.cpp @@ -294,14 +294,20 @@ UnicodeString MutablePatternModifier::getSymbol(AffixPatternType type) const { case AffixPatternType::TYPE_PERMILLE: return fSymbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kPerMillSymbol); case AffixPatternType::TYPE_CURRENCY_SINGLE: { - // UnitWidth ISO and HIDDEN overrides the singular currency symbol. - if (fUnitWidth == UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE) { - return fCurrencySymbols.getIntlCurrencySymbol(localStatus); - } else if (fUnitWidth == UNumberUnitWidth::UNUM_UNIT_WIDTH_HIDDEN) { - return UnicodeString(); - } else if (fUnitWidth == UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW) { + switch (fUnitWidth) { + case UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW: return fCurrencySymbols.getNarrowCurrencySymbol(localStatus); - } else { + case UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT: + return fCurrencySymbols.getCurrencySymbol(localStatus); + case UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE: + return fCurrencySymbols.getIntlCurrencySymbol(localStatus); + case UNumberUnitWidth::UNUM_UNIT_WIDTH_FORMAL: + return fCurrencySymbols.getFormalCurrencySymbol(localStatus); + case UNumberUnitWidth::UNUM_UNIT_WIDTH_VARIANT: + return fCurrencySymbols.getVariantCurrencySymbol(localStatus); + case UNumberUnitWidth::UNUM_UNIT_WIDTH_HIDDEN: + return UnicodeString(); + default: return fCurrencySymbols.getCurrencySymbol(localStatus); } } diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp index 4ba2647986c..66cae73be3c 100644 --- a/icu4c/source/i18n/number_skeletons.cpp +++ b/icu4c/source/i18n/number_skeletons.cpp @@ -80,6 +80,8 @@ void U_CALLCONV initNumberSkeletons(UErrorCode& status) { b.add(u"unit-width-short", STEM_UNIT_WIDTH_SHORT, status); b.add(u"unit-width-full-name", STEM_UNIT_WIDTH_FULL_NAME, status); b.add(u"unit-width-iso-code", STEM_UNIT_WIDTH_ISO_CODE, status); + b.add(u"unit-width-formal", STEM_UNIT_WIDTH_FORMAL, status); + b.add(u"unit-width-variant", STEM_UNIT_WIDTH_VARIANT, status); b.add(u"unit-width-hidden", STEM_UNIT_WIDTH_HIDDEN, status); b.add(u"sign-auto", STEM_SIGN_AUTO, status); b.add(u"sign-always", STEM_SIGN_ALWAYS, status); @@ -265,6 +267,10 @@ UNumberUnitWidth stem_to_object::unitWidth(skeleton::StemEnum stem) { return UNUM_UNIT_WIDTH_FULL_NAME; case STEM_UNIT_WIDTH_ISO_CODE: return UNUM_UNIT_WIDTH_ISO_CODE; + case STEM_UNIT_WIDTH_FORMAL: + return UNUM_UNIT_WIDTH_FORMAL; + case STEM_UNIT_WIDTH_VARIANT: + return UNUM_UNIT_WIDTH_VARIANT; case STEM_UNIT_WIDTH_HIDDEN: return UNUM_UNIT_WIDTH_HIDDEN; default: @@ -372,6 +378,12 @@ void enum_to_stem_string::unitWidth(UNumberUnitWidth value, UnicodeString& sb) { case UNUM_UNIT_WIDTH_ISO_CODE: sb.append(u"unit-width-iso-code", -1); break; + case UNUM_UNIT_WIDTH_FORMAL: + sb.append(u"unit-width-formal", -1); + break; + case UNUM_UNIT_WIDTH_VARIANT: + sb.append(u"unit-width-variant", -1); + break; case UNUM_UNIT_WIDTH_HIDDEN: sb.append(u"unit-width-hidden", -1); break; @@ -683,6 +695,8 @@ skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, Se case STEM_UNIT_WIDTH_SHORT: case STEM_UNIT_WIDTH_FULL_NAME: case STEM_UNIT_WIDTH_ISO_CODE: + case STEM_UNIT_WIDTH_FORMAL: + case STEM_UNIT_WIDTH_VARIANT: case STEM_UNIT_WIDTH_HIDDEN: CHECK_NULL(seen, unitWidth, status); macros.unitWidth = stem_to_object::unitWidth(stem); diff --git a/icu4c/source/i18n/number_skeletons.h b/icu4c/source/i18n/number_skeletons.h index d9b2c0ee0b1..dd3813e7a65 100644 --- a/icu4c/source/i18n/number_skeletons.h +++ b/icu4c/source/i18n/number_skeletons.h @@ -95,6 +95,8 @@ enum StemEnum { STEM_UNIT_WIDTH_SHORT, STEM_UNIT_WIDTH_FULL_NAME, STEM_UNIT_WIDTH_ISO_CODE, + STEM_UNIT_WIDTH_FORMAL, + STEM_UNIT_WIDTH_VARIANT, STEM_UNIT_WIDTH_HIDDEN, STEM_SIGN_AUTO, STEM_SIGN_ALWAYS, diff --git a/icu4c/source/i18n/unicode/unumberformatter.h b/icu4c/source/i18n/unicode/unumberformatter.h index af98ba00272..d91f1486f8f 100644 --- a/icu4c/source/i18n/unicode/unumberformatter.h +++ b/icu4c/source/i18n/unicode/unumberformatter.h @@ -147,6 +147,30 @@ typedef enum UNumberUnitWidth { */ UNUM_UNIT_WIDTH_ISO_CODE, +#ifndef U_HIDE_DRAFT_API + /** + * Use the formal variant of the currency symbol; for example, "NT$" for the New Taiwan + * dollar in zh-TW. + * + *
+ * Behavior of this option with non-currency units is not defined at this time. + * + * @draft ICU 68 + */ + UNUM_UNIT_WIDTH_FORMAL, + + /** + * Use the alternate variant of the currency symbol; for example, "TL" for the Turkish + * lira (TRY). + * + *
+ * Behavior of this option with non-currency units is not defined at this time. + * + * @draft ICU 68 + */ + UNUM_UNIT_WIDTH_VARIANT, +#endif // U_HIDE_DRAFT_API + /** * Format the number according to the specified unit, but do not display the unit. For currencies, apply * monetary symbols and formats as with SHORT, but omit the currency symbol. For measure units, the behavior is diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h index cb0be280a9a..668885dabe8 100644 --- a/icu4c/source/test/intltest/numbertest.h +++ b/icu4c/source/test/intltest/numbertest.h @@ -95,6 +95,8 @@ class NumberFormatterApiTest : public IntlTestWithFieldPosition { CurrencyUnit ESP; CurrencyUnit PTE; CurrencyUnit RON; + CurrencyUnit TWD; + CurrencyUnit TRY; CurrencyUnit CNY; MeasureUnit METER; diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp index c586603b6f0..ad7a2991d98 100644 --- a/icu4c/source/test/intltest/numbertest_api.cpp +++ b/icu4c/source/test/intltest/numbertest_api.cpp @@ -36,6 +36,8 @@ NumberFormatterApiTest::NumberFormatterApiTest(UErrorCode& status) ESP(u"ESP", status), PTE(u"PTE", status), RON(u"RON", status), + TWD(u"TWD", status), + TRY(u"TRY", status), CNY(u"CNY", status), FRENCH_SYMBOLS(Locale::getFrench(), status), SWISS_SYMBOLS(Locale("de-CH"), status), @@ -850,6 +852,42 @@ void NumberFormatterApiTest::unitCurrency() { 5.43, u"US$5.43"); + assertFormatSingle( + u"Currency Difference between Formal and Short (Formal Version)", + u"currency/TWD unit-width-formal", + u"currency/TWD unit-width-formal", + NumberFormatter::with().unit(TWD).unitWidth(UNUM_UNIT_WIDTH_FORMAL), + Locale("zh-TW"), + 5.43, + u"NT$5.43"); + + assertFormatSingle( + u"Currency Difference between Formal and Short (Short Version)", + u"currency/TWD unit-width-short", + u"currency/TWD unit-width-short", + NumberFormatter::with().unit(TWD).unitWidth(UNUM_UNIT_WIDTH_SHORT), + Locale("zh-TW"), + 5.43, + u"$5.43"); + + assertFormatSingle( + u"Currency Difference between Variant and Short (Formal Version)", + u"currency/TRY unit-width-variant", + u"currency/TRY unit-width-variant", + NumberFormatter::with().unit(TRY).unitWidth(UNUM_UNIT_WIDTH_VARIANT), + Locale("tr-TR"), + 5.43, + u"TL\u00A05,43"); + + assertFormatSingle( + u"Currency Difference between Variant and Short (Short Version)", + u"currency/TRY unit-width-short", + u"currency/TRY unit-width-short", + NumberFormatter::with().unit(TRY).unitWidth(UNUM_UNIT_WIDTH_SHORT), + Locale("tr-TR"), + 5.43, + u"₺5,43"); + assertFormatSingle( u"Currency-dependent format (Control)", u"currency/USD unit-width-short", diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index ed912cf3ce1..cd06f6029b2 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -133,7 +133,7 @@ void NumberFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &n TESTCASE_AUTO(TestCases); TESTCASE_AUTO(TestCurrencyNames); - TESTCASE_AUTO(Test20484_NarrowSymbolFallback); + TESTCASE_AUTO(TestCurrencyVariants); TESTCASE_AUTO(TestCurrencyAmount); TESTCASE_AUTO(TestCurrencyUnit); TESTCASE_AUTO(TestCoverage); @@ -2116,22 +2116,26 @@ void NumberFormatTest::TestCurrencyNames(void) { // TODO add more tests later } -void NumberFormatTest::Test20484_NarrowSymbolFallback(){ - IcuTestErrorCode status(*this, "Test20484_NarrowSymbolFallback"); +void NumberFormatTest::TestCurrencyVariants(){ + IcuTestErrorCode status(*this, "TestCurrencyVariants"); struct TestCase { const char* locale; const char16_t* isoCode; const char16_t* expectedShort; const char16_t* expectedNarrow; + const char16_t* expectedFormal; + const char16_t* expectedVariant; UErrorCode expectedNarrowError; } cases[] = { - {"en-US", u"CAD", u"CA$", u"$", U_USING_DEFAULT_WARNING}, // narrow: fallback to root - {"en-US", u"CDF", u"CDF", u"CDF", U_USING_FALLBACK_WARNING}, // narrow: fallback to short - {"sw-CD", u"CDF", u"FC", u"FC", U_USING_FALLBACK_WARNING}, // narrow: fallback to short - {"en-US", u"GEL", u"GEL", u"₾", U_USING_DEFAULT_WARNING}, // narrow: fallback to root - {"ka-GE", u"GEL", u"₾", u"₾", U_USING_FALLBACK_WARNING}, // narrow: fallback to ka - {"ka", u"GEL", u"₾", u"₾", U_ZERO_ERROR}, // no fallback on narrow + {"en-US", u"CAD", u"CA$", u"$", u"CA$", u"CA$", U_USING_DEFAULT_WARNING}, // narrow: fallback to root + {"en-US", u"CDF", u"CDF", u"CDF", u"CDF", u"CDF", U_USING_FALLBACK_WARNING}, // narrow: fallback to short + {"sw-CD", u"CDF", u"FC", u"FC", u"FC", u"FC", U_USING_FALLBACK_WARNING}, // narrow: fallback to short + {"en-US", u"GEL", u"GEL", u"₾", u"GEL", u"GEL", U_USING_DEFAULT_WARNING}, // narrow: fallback to root + {"ka-GE", u"GEL", u"₾", u"₾", u"₾", u"₾", U_USING_FALLBACK_WARNING}, // narrow: fallback to ka + {"ka", u"GEL", u"₾", u"₾", u"₾", u"₾", U_ZERO_ERROR}, // no fallback on narrow + {"zh-TW", u"TWD", u"$", u"$", u"NT$", u"$", U_USING_FALLBACK_WARNING}, // narrow: fallback to short + {"ccp", u"TRY", u"TRY", u"₺", u"TRY", u"TL", U_ZERO_ERROR}, // no fallback on variant }; for (const auto& cas : cases) { status.setScope(cas.isoCode); @@ -2144,6 +2148,20 @@ void NumberFormatTest::Test20484_NarrowSymbolFallback(){ &choiceFormatIgnored, &lengthIgnored, status); + const UChar* actualFormal = ucurr_getName( + cas.isoCode, + cas.locale, + UCURR_FORMAL_SYMBOL_NAME, + &choiceFormatIgnored, + &lengthIgnored, + status); + const UChar* actualVarant = ucurr_getName( + cas.isoCode, + cas.locale, + UCURR_VARIANT_SYMBOL_NAME, + &choiceFormatIgnored, + &lengthIgnored, + status); status.errIfFailureAndReset(); const UChar* actualNarrow = ucurr_getName( cas.isoCode, @@ -2155,8 +2173,12 @@ void NumberFormatTest::Test20484_NarrowSymbolFallback(){ status.expectErrorAndReset(cas.expectedNarrowError); assertEquals(UnicodeString("Short symbol: ") + cas.locale + u": " + cas.isoCode, cas.expectedShort, actualShort); - assertEquals(UnicodeString("Narrow symbol: ") + cas.locale + ": " + cas.isoCode, + assertEquals(UnicodeString("Narrow symbol: ") + cas.locale + u": " + cas.isoCode, cas.expectedNarrow, actualNarrow); + assertEquals(UnicodeString("Formal symbol: ") + cas.locale + u": " + cas.isoCode, + cas.expectedFormal, actualFormal); + assertEquals(UnicodeString("Variant symbol: ") + cas.locale + u": " + cas.isoCode, + cas.expectedVariant, actualVarant); } } diff --git a/icu4c/source/test/intltest/numfmtst.h b/icu4c/source/test/intltest/numfmtst.h index 309098c32aa..4e2d797b107 100644 --- a/icu4c/source/test/intltest/numfmtst.h +++ b/icu4c/source/test/intltest/numfmtst.h @@ -153,7 +153,7 @@ class NumberFormatTest: public CalendarTimeZoneTest { void TestCurrencyNames(void); - void Test20484_NarrowSymbolFallback(void); + void TestCurrencyVariants(void); void TestCurrencyAmount(void);