Skip to content

Commit

Permalink
ICU-21173 Add support for more currency variants. ICU4C equivalent of…
Browse files Browse the repository at this point in the history
See #1184
  • Loading branch information
jowilco authored and sffc committed Jul 3, 2020
1 parent 3fca290 commit 6fe86f3
Show file tree
Hide file tree
Showing 12 changed files with 180 additions and 21 deletions.
21 changes: 18 additions & 3 deletions icu4c/source/common/ucurr.cpp
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down
24 changes: 23 additions & 1 deletion icu4c/source/common/unicode/ucurr.h
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions icu4c/source/i18n/number_currencysymbols.cpp
Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions icu4c/source/i18n/number_currencysymbols.h
Expand Up @@ -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;
Expand Down
18 changes: 12 additions & 6 deletions icu4c/source/i18n/number_patternmodifier.cpp
Expand Up @@ -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) {
switch (fUnitWidth) {
case UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW:
return fCurrencySymbols.getNarrowCurrencySymbol(localStatus);
case UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT:
return fCurrencySymbols.getCurrencySymbol(localStatus);
case UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE:
return fCurrencySymbols.getIntlCurrencySymbol(localStatus);
} else if (fUnitWidth == UNumberUnitWidth::UNUM_UNIT_WIDTH_HIDDEN) {
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();
} else if (fUnitWidth == UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW) {
return fCurrencySymbols.getNarrowCurrencySymbol(localStatus);
} else {
default:
return fCurrencySymbols.getCurrencySymbol(localStatus);
}
}
Expand Down
14 changes: 14 additions & 0 deletions icu4c/source/i18n/number_skeletons.cpp
Expand Up @@ -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);
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions icu4c/source/i18n/number_skeletons.h
Expand Up @@ -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,
Expand Down
24 changes: 24 additions & 0 deletions icu4c/source/i18n/unicode/unumberformatter.h
Expand Up @@ -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.
*
* <p>
* 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).
*
* <p>
* 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
Expand Down
2 changes: 2 additions & 0 deletions icu4c/source/test/intltest/numbertest.h
Expand Up @@ -95,6 +95,8 @@ class NumberFormatterApiTest : public IntlTestWithFieldPosition {
CurrencyUnit ESP;
CurrencyUnit PTE;
CurrencyUnit RON;
CurrencyUnit TWD;
CurrencyUnit TRY;
CurrencyUnit CNY;

MeasureUnit METER;
Expand Down
38 changes: 38 additions & 0 deletions icu4c/source/test/intltest/numbertest_api.cpp
Expand Up @@ -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),
Expand Down Expand Up @@ -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",
Expand Down
42 changes: 32 additions & 10 deletions icu4c/source/test/intltest/numfmtst.cpp
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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,
Expand All @@ -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);
}
}

Expand Down
2 changes: 1 addition & 1 deletion icu4c/source/test/intltest/numfmtst.h
Expand Up @@ -153,7 +153,7 @@ class NumberFormatTest: public CalendarTimeZoneTest {

void TestCurrencyNames(void);

void Test20484_NarrowSymbolFallback(void);
void TestCurrencyVariants(void);

void TestCurrencyAmount(void);

Expand Down

0 comments on commit 6fe86f3

Please sign in to comment.