ICU-20854 Add support for more currency variants.
See #878
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/CurrencyData.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/CurrencyData.java
index df42725..49f1a50 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/CurrencyData.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/CurrencyData.java
@@ -155,6 +155,16 @@
}
@Override
+ public String getFormalSymbol(String isoCode) {
+ return fallback ? isoCode : null;
+ }
+
+ @Override
+ public String getVariantSymbol(String isoCode) {
+ return fallback ? isoCode : null;
+ }
+
+ @Override
public Map<String, String> symbolMap() {
return Collections.emptyMap();
}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java
index 2a44151..7b49713 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java
@@ -7,6 +7,7 @@
import com.ibm.icu.impl.number.AffixUtils.SymbolProvider;
import com.ibm.icu.number.NumberFormatter.SignDisplay;
import com.ibm.icu.number.NumberFormatter.UnitWidth;
+import com.ibm.icu.text.CurrencyDisplayNames;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.NumberFormat.Field;
import com.ibm.icu.text.PluralRules;
@@ -401,8 +402,23 @@
} else if (unitWidth == UnitWidth.HIDDEN) {
return "";
} else {
- int selector = unitWidth == UnitWidth.NARROW ? Currency.NARROW_SYMBOL_NAME
- : Currency.SYMBOL_NAME;
+ int selector;
+ switch (unitWidth) {
+ case SHORT:
+ selector = Currency.SYMBOL_NAME;
+ break;
+ case NARROW:
+ selector = Currency.NARROW_SYMBOL_NAME;
+ break;
+ case FORMAL:
+ selector = Currency.FORMAL_SYMBOL_NAME;
+ break;
+ case VARIANT:
+ selector = Currency.VARIANT_SYMBOL_NAME;
+ break;
+ default:
+ throw new AssertionError();
+ }
return currency.getName(symbols.getULocale(), selector, null);
}
case AffixUtils.TYPE_CURRENCY_DOUBLE:
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java
index ff0a0a0..e8a1fc4 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java
@@ -131,8 +131,10 @@
FULL_NAME,
/**
- * Use the three-digit ISO XXX code in place of the symbol for displaying currencies. The
- * behavior of this option is currently undefined for use with measure units.
+ * Use the three-digit ISO XXX code in place of the symbol for displaying currencies.
+ *
+ * <p>
+ * Behavior of this option with non-currency units is not defined at this time.
*
* <p>
* In CLDR, this option corresponds to the "¤¤" placeholder for currencies.
@@ -143,6 +145,32 @@
ISO_CODE,
/**
+ * 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 67
+ * @provisional This API might change or be removed in a future release.
+ * @see NumberFormatter
+ */
+ 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 67
+ * @provisional This API might change or be removed in a future release.
+ * @see NumberFormatter
+ */
+ VARIANT,
+
+ /**
* 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 equivalent to not specifying the unit at all.
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java
index 3897ca3..33e60f7 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java
@@ -100,6 +100,8 @@
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,
@@ -173,6 +175,8 @@
b.add("unit-width-short", StemEnum.STEM_UNIT_WIDTH_SHORT.ordinal());
b.add("unit-width-full-name", StemEnum.STEM_UNIT_WIDTH_FULL_NAME.ordinal());
b.add("unit-width-iso-code", StemEnum.STEM_UNIT_WIDTH_ISO_CODE.ordinal());
+ b.add("unit-width-formal", StemEnum.STEM_UNIT_WIDTH_FORMAL.ordinal());
+ b.add("unit-width-variant", StemEnum.STEM_UNIT_WIDTH_VARIANT.ordinal());
b.add("unit-width-hidden", StemEnum.STEM_UNIT_WIDTH_HIDDEN.ordinal());
b.add("sign-auto", StemEnum.STEM_SIGN_AUTO.ordinal());
b.add("sign-always", StemEnum.STEM_SIGN_ALWAYS.ordinal());
@@ -315,6 +319,10 @@
return UnitWidth.FULL_NAME;
case STEM_UNIT_WIDTH_ISO_CODE:
return UnitWidth.ISO_CODE;
+ case STEM_UNIT_WIDTH_FORMAL:
+ return UnitWidth.FORMAL;
+ case STEM_UNIT_WIDTH_VARIANT:
+ return UnitWidth.VARIANT;
case STEM_UNIT_WIDTH_HIDDEN:
return UnitWidth.HIDDEN;
default:
@@ -428,6 +436,12 @@
case ISO_CODE:
sb.append("unit-width-iso-code");
break;
+ case FORMAL:
+ sb.append("unit-width-formal");
+ break;
+ case VARIANT:
+ sb.append("unit-width-variant");
+ break;
case HIDDEN:
sb.append("unit-width-hidden");
break;
@@ -729,6 +743,8 @@
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:
checkNull(macros.unitWidth, segment);
macros.unitWidth = StemToObject.unitWidth(stem);
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/CurrencyDisplayNames.java b/icu4j/main/classes/core/src/com/ibm/icu/text/CurrencyDisplayNames.java
index 21297aa..63d73e0 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/text/CurrencyDisplayNames.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/text/CurrencyDisplayNames.java
@@ -110,9 +110,10 @@
public abstract ULocale getULocale();
/**
- * Returns the symbol for the currency with the provided ISO code. If
- * there is no data for the ISO code, substitutes isoCode, or returns null
- * if noSubstitute was set in the factory method.
+ * Returns the symbol for the currency with the provided ISO code.
+ * <p>
+ * If there is no data for this symbol, substitutes isoCode,
+ * or returns null if noSubstitute was set in the factory method.
*
* @param isoCode the three-letter ISO code.
* @return the symbol.
@@ -122,7 +123,12 @@
/**
* Returns the narrow symbol for the currency with the provided ISO code.
- * If there is no data for narrow symbol, substitutes the default symbol,
+ * <p>
+ * The narrow currency symbol is similar to the regular currency symbol,
+ * but it always takes the shortest form;
+ * for example, "$" instead of "US$" for USD in en-CA.
+ * <p>
+ * If there is no data for this symbol, substitutes the default symbol,
* or returns null if noSubstitute was set in the factory method.
*
* @param isoCode the three-letter ISO code.
@@ -132,6 +138,39 @@
public abstract String getNarrowSymbol(String isoCode);
/**
+ * Returns the formal symbol for the currency with the provided ISO code.
+ * <p>
+ * 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.
+ * <p>
+ * If there is no data for this symbol, substitutes the default symbol,
+ * or returns null if noSubstitute was set in the factory method.
+ *
+ * @param isoCode the three-letter ISO code.
+ * @return the formal symbol.
+ * @draft ICU 67
+ * @provisional This API might change or be removed in a future release.
+ */
+ public abstract String getFormalSymbol(String isoCode);
+
+ /**
+ * Returns the variant symbol for the currency with the provided ISO code.
+ * <p>
+ * The variant symbol for a currency is an alternative symbol that is not
+ * necessarily as widely used as the regular symbol.
+ * <p>
+ * If there is no data for variant symbol, substitutes the default symbol,
+ * or returns null if noSubstitute was set in the factory method.
+ *
+ * @param isoCode the three-letter ISO code.
+ * @return the variant symbol.
+ * @draft ICU 67
+ * @provisional This API might change or be removed in a future release.
+ */
+ public abstract String getVariantSymbol(String isoCode);
+
+ /**
* Returns the 'long name' for the currency with the provided ISO code.
* If there is no data for the ISO code, substitutes isoCode, or returns null
* if noSubstitute was set in the factory method.
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/Currency.java b/icu4j/main/classes/core/src/com/ibm/icu/util/Currency.java
index 95e3634..3e3bdf5 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/util/Currency.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/util/Currency.java
@@ -90,15 +90,39 @@
/**
* Selector for getName() indicating the narrow currency symbol.
- * The narrow currency symbol is similar to the regular currency
- * symbol, but it always takes the shortest form: for example,
- * "$" instead of "US$" for USD in en-CA.
+ * <p>
+ * The narrow currency symbol is similar to the regular currency symbol,
+ * but it always takes the shortest form;
+ * for example, "$" instead of "US$" for USD in en-CA.
*
* @stable ICU 61
*/
public static final int NARROW_SYMBOL_NAME = 3;
/**
+ * Selector for getName() indicating the formal currency symbol.
+ * <p>
+ * 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 67
+ * @provisional This API might change or be removed in a future release.
+ */
+ public static final int FORMAL_SYMBOL_NAME = 4;
+
+ /**
+ * Selector for getName() indicating the variant currency symbol.
+ * <p>
+ * The variant symbol for a currency is an alternative symbol that is not
+ * necessarily as widely used as the regular symbol.
+ *
+ * @draft ICU 67
+ * @provisional This API might change or be removed in a future release.
+ */
+ public static final int VARIANT_SYMBOL_NAME = 5;
+
+ /**
* Currency Usage used for Decimal Format
* @stable ICU 54
*/
@@ -572,6 +596,10 @@
return names.getSymbol(subType);
case NARROW_SYMBOL_NAME:
return names.getNarrowSymbol(subType);
+ case FORMAL_SYMBOL_NAME:
+ return names.getFormalSymbol(subType);
+ case VARIANT_SYMBOL_NAME:
+ return names.getVariantSymbol(subType);
case LONG_NAME:
return names.getName(subType);
default:
diff --git a/icu4j/main/classes/currdata/src/com/ibm/icu/impl/ICUCurrencyDisplayInfoProvider.java b/icu4j/main/classes/currdata/src/com/ibm/icu/impl/ICUCurrencyDisplayInfoProvider.java
index 9fb70c5..8465ff1 100644
--- a/icu4j/main/classes/currdata/src/com/ibm/icu/impl/ICUCurrencyDisplayInfoProvider.java
+++ b/icu4j/main/classes/currdata/src/com/ibm/icu/impl/ICUCurrencyDisplayInfoProvider.java
@@ -75,10 +75,10 @@
private volatile FormattingData formattingDataCache = null;
/**
- * Single-item cache for getNarrowSymbol().
+ * Single-item cache for variant symbols.
* Holds data for only one currency. If another currency is requested, the old cache item is overwritten.
*/
- private volatile NarrowSymbol narrowSymbolCache = null;
+ private volatile VariantSymbol variantSymbolCache = null;
/**
* Single-item cache for getPluralName().
@@ -116,11 +116,15 @@
FormattingData(String isoCode) { this.isoCode = isoCode; }
}
- static class NarrowSymbol {
+ static class VariantSymbol {
final String isoCode;
- String narrowSymbol = null;
+ final String variant;
+ String symbol = null;
- NarrowSymbol(String isoCode) { this.isoCode = isoCode; }
+ VariantSymbol(String isoCode, String variant) {
+ this.isoCode = isoCode;
+ this.variant = variant;
+ }
}
static class ParsingData {
@@ -167,13 +171,35 @@
@Override
public String getNarrowSymbol(String isoCode) {
- NarrowSymbol narrowSymbol = fetchNarrowSymbol(isoCode);
+ VariantSymbol variantSymbol = fetchVariantSymbol(isoCode, "narrow");
- // Fall back to ISO Code
- if (narrowSymbol.narrowSymbol == null && fallback) {
+ // Fall back to regular symbol
+ if (variantSymbol.symbol == null && fallback) {
return getSymbol(isoCode);
}
- return narrowSymbol.narrowSymbol;
+ return variantSymbol.symbol;
+ }
+
+ @Override
+ public String getFormalSymbol(String isoCode) {
+ VariantSymbol variantSymbol = fetchVariantSymbol(isoCode, "formal");
+
+ // Fall back to regular symbol
+ if (variantSymbol.symbol == null && fallback) {
+ return getSymbol(isoCode);
+ }
+ return variantSymbol.symbol;
+ }
+
+ @Override
+ public String getVariantSymbol(String isoCode) {
+ VariantSymbol variantSymbol = fetchVariantSymbol(isoCode, "variant");
+
+ // Fall back to regular symbol
+ if (variantSymbol.symbol == null && fallback) {
+ return getSymbol(isoCode);
+ }
+ return variantSymbol.symbol;
}
@Override
@@ -256,14 +282,14 @@
return result;
}
- NarrowSymbol fetchNarrowSymbol(String isoCode) {
- NarrowSymbol result = narrowSymbolCache;
- if (result == null || !result.isoCode.equals(isoCode)) {
- result = new NarrowSymbol(isoCode);
- CurrencySink sink = new CurrencySink(!fallback, CurrencySink.EntrypointTable.CURRENCY_NARROW);
- sink.narrowSymbol = result;
- rb.getAllItemsWithFallbackNoFail("Currencies%narrow/" + isoCode, sink);
- narrowSymbolCache = result;
+ VariantSymbol fetchVariantSymbol(String isoCode, String variant) {
+ VariantSymbol result = variantSymbolCache;
+ if (result == null || !result.isoCode.equals(isoCode) || !result.variant.equals(variant)) {
+ result = new VariantSymbol(isoCode, variant);
+ CurrencySink sink = new CurrencySink(!fallback, CurrencySink.EntrypointTable.CURRENCY_VARIANT);
+ sink.variantSymbol = result;
+ rb.getAllItemsWithFallbackNoFail("Currencies%" + variant + "/" + isoCode, sink);
+ variantSymbolCache = result;
}
return result;
}
@@ -331,7 +357,7 @@
ParsingData parsingData = null;
Map<String, String> unitPatterns = null;
CurrencySpacingInfo spacingInfo = null;
- NarrowSymbol narrowSymbol = null;
+ VariantSymbol variantSymbol = null;
enum EntrypointTable {
// For Parsing:
@@ -340,7 +366,7 @@
// For Formatting:
CURRENCIES,
CURRENCY_PLURALS,
- CURRENCY_NARROW,
+ CURRENCY_VARIANT,
CURRENCY_SPACING,
CURRENCY_UNIT_PATTERNS
}
@@ -371,8 +397,8 @@
case CURRENCY_PLURALS:
consumeCurrencyPluralsEntry(key, value);
break;
- case CURRENCY_NARROW:
- consumeCurrenciesNarrowEntry(key, value);
+ case CURRENCY_VARIANT:
+ consumeCurrenciesVariantEntry(key, value);
break;
case CURRENCY_SPACING:
consumeCurrencySpacingTable(key, value);
@@ -475,11 +501,11 @@
* ...
* }
*/
- void consumeCurrenciesNarrowEntry(UResource.Key key, UResource.Value value) {
- assert narrowSymbol != null;
+ void consumeCurrenciesVariantEntry(UResource.Key key, UResource.Value value) {
+ assert variantSymbol != null;
// No extra structure to traverse.
- if (narrowSymbol.narrowSymbol == null) {
- narrowSymbol.narrowSymbol = value.getString();
+ if (variantSymbol.symbol == null) {
+ variantSymbol.symbol = value.getString();
}
}
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
index 2a51b7d..3056545 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
@@ -67,6 +67,8 @@
private static final Currency ESP = Currency.getInstance("ESP");
private static final Currency PTE = Currency.getInstance("PTE");
private static final Currency RON = Currency.getInstance("RON");
+ private static final Currency TWD = Currency.getInstance("TWD");
+ private static final Currency TRY = Currency.getInstance("TRY");
private static final Currency CNY = Currency.getInstance("CNY");
@Test
@@ -803,6 +805,42 @@
"US$5.43");
assertFormatSingle(
+ "Currency Difference between Formal and Short (Formal Version)",
+ "currency/TWD unit-width-formal",
+ "currency/TWD unit-width-formal",
+ NumberFormatter.with().unit(TWD).unitWidth(UnitWidth.FORMAL),
+ ULocale.forLanguageTag("zh-TW"),
+ 5.43,
+ "NT$5.43");
+
+ assertFormatSingle(
+ "Currency Difference between Formal and Short (Short Version)",
+ "currency/TWD unit-width-short",
+ "currency/TWD unit-width-short",
+ NumberFormatter.with().unit(TWD).unitWidth(UnitWidth.SHORT),
+ ULocale.forLanguageTag("zh-TW"),
+ 5.43,
+ "$5.43");
+
+ assertFormatSingle(
+ "Currency Difference between Variant and Short (Formal Version)",
+ "currency/TRY unit-width-variant",
+ "currency/TRY unit-width-variant",
+ NumberFormatter.with().unit(TRY).unitWidth(UnitWidth.VARIANT),
+ ULocale.forLanguageTag("tr-TR"),
+ 5.43,
+ "TL\u00A05,43");
+
+ assertFormatSingle(
+ "Currency Difference between Variant and Short (Short Version)",
+ "currency/TRY unit-width-short",
+ "currency/TRY unit-width-short",
+ NumberFormatter.with().unit(TRY).unitWidth(UnitWidth.SHORT),
+ ULocale.forLanguageTag("tr-TR"),
+ 5.43,
+ "₺5,43");
+
+ assertFormatSingle(
"Currency-dependent format (Control)",
"currency/USD unit-width-short",
"currency/USD unit-width-short",
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/CurrencyTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/CurrencyTest.java
index 058dfaa..bb024e9 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/CurrencyTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/CurrencyTest.java
@@ -94,7 +94,7 @@
}
try {
- usd.getName(ULocale.US, 5, new boolean[1]);
+ usd.getName(ULocale.US, 6, new boolean[1]);
errln("expected getName with invalid type parameter to throw exception");
}
catch (Exception e) {
@@ -177,7 +177,7 @@
Locale[] locs = Currency.getAvailableLocales();
found = false;
for (int i = 0; i < locs.length; ++i) {
- if (locs[i].equals(fu_FU)) {
+ if (locs[i].equals(fu_FU.toLocale())) {
found = true;
break;
}
@@ -246,26 +246,44 @@
}
@Test
- public void test20484_NarrowSymbolFallback() {
+ public void testCurrencyVariants() {
Object[][] cases = new Object[][] {
- {"en-US", "CAD", "CA$", "$"},
- {"en-US", "CDF", "CDF", "CDF"},
- {"sw-CD", "CDF", "FC", "FC"},
- {"en-US", "GEL", "GEL", "₾"},
- {"ka-GE", "GEL", "₾", "₾"},
- {"ka", "GEL", "₾", "₾"},
+ {"en-US", "CAD", "CA$", "$", "CA$", "CA$"},
+ {"en-US", "CDF", "CDF", "CDF", "CDF", "CDF"},
+ {"sw-CD", "CDF", "FC", "FC", "FC", "FC"},
+ {"en-US", "GEL", "GEL", "₾", "GEL", "GEL"},
+ {"ka-GE", "GEL", "₾", "₾", "₾", "₾"},
+ {"ka", "GEL", "₾", "₾", "₾", "₾"},
+ {"zh-TW", "TWD", "$", "$", "NT$", "$"},
+ {"ccp", "TRY", "TRY", "₺", "TRY", "TL"}
};
for (Object[] cas : cases) {
ULocale locale = new ULocale((String) cas[0]);
String isoCode = (String) cas[1];
String expectedShort = (String) cas[2];
String expectedNarrow = (String) cas[3];
+ String expectedFormal = (String) cas[4];
+ String expectedVariant = (String) cas[5];
CurrencyDisplayNames cdn = CurrencyDisplayNames.getInstance(locale);
assertEquals("Short symbol: " + locale + ": " + isoCode,
expectedShort, cdn.getSymbol(isoCode));
assertEquals("Narrow symbol: " + locale + ": " + isoCode,
expectedNarrow, cdn.getNarrowSymbol(isoCode));
+ assertEquals("Formal symbol: " + locale + ": " + isoCode,
+ expectedFormal, cdn.getFormalSymbol(isoCode));
+ assertEquals("Variant symbol: " + locale + ": " + isoCode,
+ expectedVariant, cdn.getVariantSymbol(isoCode));
+
+ Currency currency = Currency.getInstance(isoCode);
+ assertEquals("Old API, Short symbol: " + locale + ": " + isoCode,
+ expectedShort, currency.getName(locale, Currency.SYMBOL_NAME, null));
+ assertEquals("Old API, Narrow symbol: " + locale + ": " + isoCode,
+ expectedNarrow, currency.getName(locale, Currency.NARROW_SYMBOL_NAME, null));
+ assertEquals("Old API, Formal symbol: " + locale + ": " + isoCode,
+ expectedFormal, currency.getName(locale, Currency.FORMAL_SYMBOL_NAME, null));
+ assertEquals("Old API, Variant symbol: " + locale + ": " + isoCode,
+ expectedVariant, currency.getName(locale, Currency.VARIANT_SYMBOL_NAME, null));
}
}