diff --git a/icu4c/source/i18n/dcfmtsym.cpp b/icu4c/source/i18n/dcfmtsym.cpp index 43eea49ea52..d321a82f8a9 100644 --- a/icu4c/source/i18n/dcfmtsym.cpp +++ b/icu4c/source/i18n/dcfmtsym.cpp @@ -97,9 +97,7 @@ static const char *gNumberElementKeys[DecimalFormatSymbols::kFormatSymbolCount] // Initializes this with the decimal format symbols in the default locale. DecimalFormatSymbols::DecimalFormatSymbols(UErrorCode& status) - : UObject(), - locale() -{ + : UObject(), locale() { initialize(locale, status, TRUE); } @@ -107,16 +105,17 @@ DecimalFormatSymbols::DecimalFormatSymbols(UErrorCode& status) // Initializes this with the decimal format symbols in the desired locale. DecimalFormatSymbols::DecimalFormatSymbols(const Locale& loc, UErrorCode& status) - : UObject(), - locale(loc) -{ + : UObject(), locale(loc) { initialize(locale, status); } +DecimalFormatSymbols::DecimalFormatSymbols(const Locale& loc, const NumberingSystem& ns, UErrorCode& status) + : UObject(), locale(loc) { + initialize(locale, status, FALSE, &ns); +} + DecimalFormatSymbols::DecimalFormatSymbols() - : UObject(), - locale(Locale::getRoot()), - currPattern(NULL) { + : UObject(), locale(Locale::getRoot()), currPattern(NULL) { *validLocale = *actualLocale = 0; initialize(); } @@ -342,7 +341,8 @@ CurrencySpacingSink::~CurrencySpacingSink() {} } // namespace void -DecimalFormatSymbols::initialize(const Locale& loc, UErrorCode& status, UBool useLastResortData) +DecimalFormatSymbols::initialize(const Locale& loc, UErrorCode& status, + UBool useLastResortData, const NumberingSystem* ns) { if (U_FAILURE(status)) { return; } *validLocale = *actualLocale = 0; @@ -355,7 +355,13 @@ DecimalFormatSymbols::initialize(const Locale& loc, UErrorCode& status, UBool us // Next get the numbering system for this locale and set zero digit // and the digit string based on the numbering system for the locale // - LocalPointer ns(NumberingSystem::createInstance(loc, status)); + LocalPointer nsLocal; + if (ns == nullptr) { + // Use the numbering system according to the locale. + // Save it into a LocalPointer so it gets cleaned up. + nsLocal.adoptInstead(NumberingSystem::createInstance(loc, status)); + ns = nsLocal.getAlias(); + } const char *nsName; if (U_SUCCESS(status) && ns->getRadix() == 10 && !ns->isAlgorithmic()) { nsName = ns->getName(); diff --git a/icu4c/source/i18n/unicode/dcfmtsym.h b/icu4c/source/i18n/unicode/dcfmtsym.h index 3a502d0ec03..7ccc21522ed 100644 --- a/icu4c/source/i18n/unicode/dcfmtsym.h +++ b/icu4c/source/i18n/unicode/dcfmtsym.h @@ -34,6 +34,7 @@ #include "unicode/uobject.h" #include "unicode/locid.h" +#include "unicode/numsys.h" #include "unicode/unum.h" #include "unicode/unistr.h" @@ -184,6 +185,24 @@ public: */ DecimalFormatSymbols(const Locale& locale, UErrorCode& status); + /** + * Creates a DecimalFormatSymbols instance for the given locale with digits and symbols + * corresponding to the given NumberingSystem. + * + * This constructor behaves equivalently to the normal constructor called with a locale having a + * "numbers=xxxx" keyword specifying the numbering system by name. + * + * In this constructor, the NumberingSystem argument will be used even if the locale has its own + * "numbers=xxxx" keyword. + * + * @param locale The locale to get symbols for. + * @param ns The numbering system. + * @param status Input/output parameter, set to success or + * failure code upon return. + * @draft ICU 60 + */ + DecimalFormatSymbols(const Locale& locale, const NumberingSystem& ns, UErrorCode& status); + /** * Create a DecimalFormatSymbols object for the default locale. * This constructor will not fail. If the resource file data is @@ -346,8 +365,11 @@ private: * @param success Input/output parameter, set to success or * failure code upon return. * @param useLastResortData determine if use last resort data + * @param ns The NumberingSystem to use; otherwise, fall + * back to the locale. */ - void initialize(const Locale& locale, UErrorCode& success, UBool useLastResortData = FALSE); + void initialize(const Locale& locale, UErrorCode& success, + UBool useLastResortData = FALSE, const NumberingSystem* ns = nullptr); /** * Initialize the symbols with default values. diff --git a/icu4c/source/test/intltest/tsdcfmsy.cpp b/icu4c/source/test/intltest/tsdcfmsy.cpp index eb5bc3bf1ea..90198e070f4 100644 --- a/icu4c/source/test/intltest/tsdcfmsy.cpp +++ b/icu4c/source/test/intltest/tsdcfmsy.cpp @@ -23,6 +23,7 @@ void IntlTestDecimalFormatSymbols::runIndexedTest( int32_t index, UBool exec, co TESTCASE_AUTO_BEGIN; TESTCASE_AUTO(testSymbols); TESTCASE_AUTO(testLastResortData); + TESTCASE_AUTO(testNumberingSystem); TESTCASE_AUTO_END; } @@ -248,6 +249,49 @@ void IntlTestDecimalFormatSymbols::testLastResortData() { Verify(1234567.25, "#,##0.##", *lastResort, "1,234,567.25"); } +void IntlTestDecimalFormatSymbols::testNumberingSystem() { + IcuTestErrorCode errorCode(*this, "testNumberingSystem"); + struct testcase { + const char* locid; + const char* nsname; + const char16_t* expected1; // Expected number format string + const char16_t* expected2; // Expected pattern separator + }; + static const testcase cases[9] = { + {"en", "latn", u"1,234.56", u";"}, + {"en", "arab", u"١٬٢٣٤٫٥٦", u"؛"}, + {"en", "mathsanb", u"𝟭,𝟮𝟯𝟰.𝟱𝟲", u";"}, + {"en", "mymr", u"၁,၂၃၄.၅၆", u";"}, + {"my", "latn", u"1,234.56", u";"}, + {"my", "arab", u"١٬٢٣٤٫٥٦", u"؛"}, + {"my", "mathsanb", u"𝟭,𝟮𝟯𝟰.𝟱𝟲", u";"}, + {"my", "mymr", u"၁,၂၃၄.၅၆", u"၊"}, + {"en@numbers=thai", "mymr", u"၁,၂၃၄.၅၆", u";"}, // conflicting numbering system + }; + + for (int i=0; i<8; i++) { + testcase cas = cases[i]; + Locale loc(cas.locid); + LocalPointer ns(NumberingSystem::createInstanceByName(cas.nsname, errorCode)); + if (errorCode.logDataIfFailureAndReset("NumberingSystem failed")) { + return; + } + UnicodeString expected1(cas.expected1); + UnicodeString expected2(cas.expected2); + DecimalFormatSymbols dfs(loc, *ns, errorCode); + if (errorCode.logDataIfFailureAndReset("DecimalFormatSymbols failed")) { + return; + } + Verify(1234.56, "#,##0.##", dfs, expected1); + // The pattern separator is something that differs by numbering system in my@numbers=mymr. + UnicodeString actual2 = dfs.getSymbol(DecimalFormatSymbols::kPatternSeparatorSymbol); + if (expected2 != actual2) { + errln((UnicodeString)"ERROR: DecimalFormatSymbols returned pattern separator " + actual2 + + " but we expected " + expected2); + } + } +} + void IntlTestDecimalFormatSymbols::Verify(double value, const UnicodeString& pattern, const DecimalFormatSymbols &sym, const UnicodeString& expected){ UErrorCode status = U_ZERO_ERROR; diff --git a/icu4c/source/test/intltest/tsdcfmsy.h b/icu4c/source/test/intltest/tsdcfmsy.h index 5df1bc7c6fc..1fd1dfdfba3 100644 --- a/icu4c/source/test/intltest/tsdcfmsy.h +++ b/icu4c/source/test/intltest/tsdcfmsy.h @@ -28,6 +28,7 @@ private: */ void testSymbols(/*char *par*/); void testLastResortData(); + void testNumberingSystem(); /** helper functions**/ void Verify(double value, const UnicodeString& pattern, diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormatSymbols.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormatSymbols.java index 8daac032104..147ca20c113 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormatSymbols.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormatSymbols.java @@ -53,7 +53,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { * @stable ICU 2.0 */ public DecimalFormatSymbols() { - initialize(ULocale.getDefault(Category.FORMAT)); + this(ULocale.getDefault(Category.FORMAT)); } /** @@ -62,7 +62,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { * @stable ICU 2.0 */ public DecimalFormatSymbols(Locale locale) { - initialize(ULocale.forLocale(locale)); + this(ULocale.forLocale(locale)); } /** @@ -71,7 +71,15 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { * @stable ICU 3.2 */ public DecimalFormatSymbols(ULocale locale) { - initialize(locale); + initialize(locale, null); + } + + private DecimalFormatSymbols(Locale locale, NumberingSystem ns) { + this(ULocale.forLocale(locale), ns); + } + + private DecimalFormatSymbols(ULocale locale, NumberingSystem ns) { + initialize(locale, ns); } /** @@ -123,6 +131,46 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { return new DecimalFormatSymbols(locale); } + /** + * {@icu} Returns a DecimalFormatSymbols instance for the given locale with digits and symbols + * corresponding to the given {@link NumberingSystem}. + * + *

This method behaves equivalently to {@link #getInstance} called with a locale having a + * "numbers=xxxx" keyword specifying the numbering system by name. + * + *

In this method, the NumberingSystem argument will be used even if the locale has its own + * "numbers=xxxx" keyword. + * + * @param locale the locale. + * @param ns the numbering system. + * @return A DecimalFormatSymbols instance. + * @provisional This API might change or be removed in a future release. + * @draft ICU 60 + */ + public static DecimalFormatSymbols forNumberingSystem(Locale locale, NumberingSystem ns) { + return new DecimalFormatSymbols(locale, ns); + } + + /** + * {@icu} Returns a DecimalFormatSymbols instance for the given locale with digits and symbols + * corresponding to the given {@link NumberingSystem}. + * + *

This method behaves equivalently to {@link #getInstance} called with a locale having a + * "numbers=xxxx" keyword specifying the numbering system by name. + * + *

In this method, the NumberingSystem argument will be used even if the locale has its own + * "numbers=xxxx" keyword. + * + * @param locale the locale. + * @param ns the numbering system. + * @return A DecimalFormatSymbols instance. + * @provisional This API might change or be removed in a future release. + * @draft ICU 60 + */ + public static DecimalFormatSymbols forNumberingSystem(ULocale locale, NumberingSystem ns) { + return new DecimalFormatSymbols(locale, ns); + } + /** * Returns an array of all locales for which the getInstance methods of * this class can return localized instances. @@ -1289,10 +1337,16 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { /** * Initializes the symbols from the locale data. */ - private void initialize( ULocale locale ) { + private void initialize(ULocale locale, NumberingSystem ns) { this.requestedLocale = locale.toLocale(); this.ulocale = locale; - CacheData data = cachedLocaleData.getInstance(locale, null /* unused */); + + // TODO: The cache requires a single key, so we just save the NumberingSystem into the + // locale string. NumberingSystem is then decoded again in the loadData() method. It would + // be more efficient if we didn't have to serialize and deserialize the NumberingSystem. + ULocale keyLocale = (ns == null) ? locale : locale.setKeywordValue("numbers", ns.getName()); + CacheData data = cachedLocaleData.getInstance(keyLocale, null /* unused */); + setLocale(data.validLocale, data.validLocale); setDigitStrings(data.digits); String[] numberElements = data.numberElements; diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/IntlTestDecimalFormatSymbols.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/IntlTestDecimalFormatSymbols.java index 2180a789c6f..360f3f2e904 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/IntlTestDecimalFormatSymbols.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/IntlTestDecimalFormatSymbols.java @@ -26,6 +26,7 @@ import org.junit.Test; import com.ibm.icu.text.DecimalFormat; import com.ibm.icu.text.DecimalFormatSymbols; +import com.ibm.icu.text.NumberingSystem; import com.ibm.icu.util.Currency; import com.ibm.icu.util.ULocale; @@ -308,4 +309,40 @@ public class IntlTestDecimalFormatSymbols extends com.ibm.icu.dev.test.TestFmwk errln("ERROR: Latin digits should be set" + symbols.getDigitStrings()[0]); } } + + @Test + public void testNumberingSystem() { + Object[][] cases = { + {"en", "latn", "1,234.56", ';'}, + {"en", "arab", "١٬٢٣٤٫٥٦", '؛'}, + {"en", "mathsanb", "𝟭,𝟮𝟯𝟰.𝟱𝟲", ';'}, + {"en", "mymr", "၁,၂၃၄.၅၆", ';'}, + {"my", "latn", "1,234.56", ';'}, + {"my", "arab", "١٬٢٣٤٫٥٦", '؛'}, + {"my", "mathsanb", "𝟭,𝟮𝟯𝟰.𝟱𝟲", ';'}, + {"my", "mymr", "၁,၂၃၄.၅၆", '၊'}, + {"en@numbers=thai", "mymr", "၁,၂၃၄.၅၆", ';'}, // conflicting numbering system + }; + + for (Object[] cas : cases) { + ULocale loc = new ULocale((String) cas[0]); + NumberingSystem ns = NumberingSystem.getInstanceByName((String) cas[1]); + String expectedFormattedNumberString = (String) cas[2]; + char expectedPatternSeparator = (Character) cas[3]; + + DecimalFormatSymbols dfs = DecimalFormatSymbols.forNumberingSystem(loc, ns); + DecimalFormat df = new DecimalFormat("#,##0.##", dfs); + String actual1 = df.format(1234.56); + assertEquals("1234.56 with " + loc + " and " + ns.getName(), + expectedFormattedNumberString, actual1); + // The pattern separator is something that differs by numbering system in my@numbers=mymr. + char actual2 = dfs.getPatternSeparator(); + assertEquals("Pattern separator with " + loc + " and " + ns.getName(), + expectedPatternSeparator, actual2); + + // Coverage for JDK Locale overload + DecimalFormatSymbols dfs2 = DecimalFormatSymbols.forNumberingSystem(loc.toLocale(), ns); + assertEquals("JDK Locale and ICU Locale should produce the same object", dfs, dfs2); + } + } }