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 df4272574f..49f1a50e37 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 @@ -154,6 +154,16 @@ public class CurrencyData { return fallback ? isoCode : null; } + @Override + public String getFormalSymbol(String isoCode) { + return fallback ? isoCode : null; + } + + @Override + public String getVariantSymbol(String isoCode) { + return fallback ? isoCode : null; + } + @Override public Map 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 2a441513ab..7b49713908 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.StandardPlural; 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 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr } 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 ff0a0a017b..e8a1fc4910 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 @@ public final class NumberFormatter { 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. + * + *

+ * Behavior of this option with non-currency units is not defined at this time. * *

* In CLDR, this option corresponds to the "¤¤" placeholder for currencies. @@ -142,6 +144,32 @@ public final class NumberFormatter { */ ISO_CODE, + /** + * 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 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). + * + *

+ * 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. 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 3897ca3ed0..33e60f7b73 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 @@ class NumberSkeletonImpl { 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 @@ class NumberSkeletonImpl { 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 @@ class NumberSkeletonImpl { 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 @@ class NumberSkeletonImpl { 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 @@ class NumberSkeletonImpl { 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 21297aaf97..63d73e089d 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 class CurrencyDisplayNames { 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. + *

+ * 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 @@ public abstract class CurrencyDisplayNames { /** * Returns the narrow symbol for the currency with the provided ISO code. - * If there is no data for narrow symbol, substitutes the default 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. + *

+ * 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. @@ -131,6 +137,39 @@ public abstract class CurrencyDisplayNames { */ public abstract String getNarrowSymbol(String isoCode); + /** + * Returns the formal symbol for the currency with the provided ISO code. + *

+ * 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. + *

+ * 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. + *

+ * The variant symbol for a currency is an alternative symbol that is not + * necessarily as widely used as the regular symbol. + *

+ * 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 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 95e363457d..3e3bdf554b 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,14 +90,38 @@ public class Currency extends MeasureUnit { /** * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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 @@ public class Currency extends MeasureUnit { 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 9fb70c58e0..8465ff10ae 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 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid 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 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid 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 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid @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 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid 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 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid ParsingData parsingData = null; Map unitPatterns = null; CurrencySpacingInfo spacingInfo = null; - NarrowSymbol narrowSymbol = null; + VariantSymbol variantSymbol = null; enum EntrypointTable { // For Parsing: @@ -340,7 +366,7 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid // For Formatting: CURRENCIES, CURRENCY_PLURALS, - CURRENCY_NARROW, + CURRENCY_VARIANT, CURRENCY_SPACING, CURRENCY_UNIT_PATTERNS } @@ -371,8 +397,8 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid 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 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid * ... * } */ - 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 2a51b7dff4..3056545315 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 @@ public class NumberFormatterApiTest { 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 @@ -802,6 +804,42 @@ public class NumberFormatterApiTest { 5.43, "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", 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 058dfaa9a7..bb024e9ea1 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 @@ public class CurrencyTest extends TestFmwk { } 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 @@ public class CurrencyTest extends TestFmwk { 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 @@ public class CurrencyTest extends TestFmwk { } @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)); } }