diff --git a/icu4c/source/i18n/number_currencysymbols.cpp b/icu4c/source/i18n/number_currencysymbols.cpp index 8f05da78c4..51f37076e7 100644 --- a/icu4c/source/i18n/number_currencysymbols.cpp +++ b/icu4c/source/i18n/number_currencysymbols.cpp @@ -52,17 +52,23 @@ UnicodeString CurrencySymbols::getCurrencySymbol(UErrorCode& status) const { } UnicodeString CurrencySymbols::loadSymbol(UCurrNameStyle selector, UErrorCode& status) const { + const char16_t* isoCode = fCurrency.getISOCurrency(); UBool ignoredIsChoiceFormatFillIn = FALSE; int32_t symbolLen = 0; const char16_t* symbol = ucurr_getName( - fCurrency.getISOCurrency(), + isoCode, fLocaleName.data(), selector, &ignoredIsChoiceFormatFillIn, &symbolLen, &status); - // Readonly-aliasing char16_t* constructor, which points to a resource bundle: - return UnicodeString(TRUE, symbol, symbolLen); + // If given an unknown currency, ucurr_getName returns the input string, which we can't alias safely! + // Otherwise, symbol points to a resource bundle, and we can use readonly-aliasing constructor. + if (symbol == isoCode) { + return UnicodeString(isoCode, 3); + } else { + return UnicodeString(TRUE, symbol, symbolLen); + } } UnicodeString CurrencySymbols::getIntlCurrencySymbol(UErrorCode&) const { @@ -75,17 +81,23 @@ UnicodeString CurrencySymbols::getIntlCurrencySymbol(UErrorCode&) const { } UnicodeString CurrencySymbols::getPluralName(StandardPlural::Form plural, UErrorCode& status) const { + const char16_t* isoCode = fCurrency.getISOCurrency(); UBool isChoiceFormat = FALSE; int32_t symbolLen = 0; const char16_t* symbol = ucurr_getPluralName( - fCurrency.getISOCurrency(), + isoCode, fLocaleName.data(), &isChoiceFormat, StandardPlural::getKeyword(plural), &symbolLen, &status); - // Readonly-aliasing char16_t* constructor, which points to a resource bundle: - return UnicodeString(TRUE, symbol, symbolLen); + // If given an unknown currency, ucurr_getName returns the input string, which we can't alias safely! + // Otherwise, symbol points to a resource bundle, and we can use readonly-aliasing constructor. + if (symbol == isoCode) { + return UnicodeString(isoCode, 3); + } else { + return UnicodeString(TRUE, symbol, symbolLen); + } } diff --git a/icu4c/source/i18n/numparse_currency.cpp b/icu4c/source/i18n/numparse_currency.cpp index 5c14fd7429..0848375962 100644 --- a/icu4c/source/i18n/numparse_currency.cpp +++ b/icu4c/source/i18n/numparse_currency.cpp @@ -81,7 +81,7 @@ bool CombinedCurrencyMatcher::match(StringSegment& segment, ParsedNumber& result bool CombinedCurrencyMatcher::matchCurrency(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const { - int32_t overlap1 = segment.getCommonPrefixLength(fCurrency1); + int32_t overlap1 = segment.getCaseSensitivePrefixLength(fCurrency1); if (overlap1 == fCurrency1.length()) { utils::copyCurrencyCode(result.currencyCode, fCurrencyCode); segment.adjustOffset(overlap1); @@ -89,7 +89,7 @@ bool CombinedCurrencyMatcher::matchCurrency(StringSegment& segment, ParsedNumber return segment.length() == 0; } - int32_t overlap2 = segment.getCommonPrefixLength(fCurrency2); + int32_t overlap2 = segment.getCaseSensitivePrefixLength(fCurrency2); if (overlap2 == fCurrency2.length()) { utils::copyCurrencyCode(result.currencyCode, fCurrencyCode); segment.adjustOffset(overlap2); diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index bf070fa75a..cf9bd3bc8a 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -4082,24 +4082,24 @@ NumberFormatTest::TestCurrencyParsing() { // format result using CURRENCYSTYLE, // format result using ISOCURRENCYSTYLE, // format result using PLURALCURRENCYSTYLE, - {"en_US", "1", "USD", "$1.00", "USD1.00", "1.00 US dollar"}, + {"en_US", "1", "USD", "$1.00", "USD\\u00A01.00", "1.00 US dollar"}, {"pa_IN", "1", "USD", "US$\\u00A01.00", "USD\\u00A01.00", "1.00 \\u0a2f\\u0a42.\\u0a10\\u0a38. \\u0a21\\u0a3e\\u0a32\\u0a30"}, {"es_AR", "1", "USD", "US$\\u00A01,00", "USD\\u00A01,00", "1,00 d\\u00f3lar estadounidense"}, {"ar_EG", "1", "USD", "\\u0661\\u066b\\u0660\\u0660\\u00a0US$", "\\u0661\\u066b\\u0660\\u0660\\u00a0USD", "\\u0661\\u066b\\u0660\\u0660 \\u062f\\u0648\\u0644\\u0627\\u0631 \\u0623\\u0645\\u0631\\u064a\\u0643\\u064a"}, {"fa_CA", "1", "USD", "\\u200e$\\u06f1\\u066b\\u06f0\\u06f0", "\\u200eUSD\\u06f1\\u066b\\u06f0\\u06f0", "\\u06f1\\u066b\\u06f0\\u06f0 \\u062f\\u0644\\u0627\\u0631 \\u0627\\u0645\\u0631\\u06cc\\u06a9\\u0627"}, {"he_IL", "1", "USD", "\\u200f1.00\\u00a0$", "\\u200f1.00\\u00a0USD", "1.00 \\u05d3\\u05d5\\u05dc\\u05e8 \\u05d0\\u05de\\u05e8\\u05d9\\u05e7\\u05d0\\u05d9"}, {"hr_HR", "1", "USD", "1,00\\u00a0USD", "1,00\\u00a0USD", "1,00 Ameri\\u010dki dolar"}, - {"id_ID", "1", "USD", "US$1,00", "USD1,00", "1,00 Dolar Amerika Serikat"}, + {"id_ID", "1", "USD", "US$\\u00A01,00", "USD\\u00A01,00", "1,00 Dolar Amerika Serikat"}, {"it_IT", "1", "USD", "1,00\\u00a0USD", "1,00\\u00a0USD", "1,00 Dollaro Statunitense"}, - {"ko_KR", "1", "USD", "US$1.00", "USD1.00", "1.00 \\ubbf8\\uad6d \\ub2ec\\ub7ec"}, - {"ja_JP", "1", "USD", "$1.00", "USD1.00", "1.00\\u7c73\\u30c9\\u30eb"}, - {"zh_CN", "1", "CNY", "\\uFFE51.00", "CNY01.00", "1.00\\u4EBA\\u6C11\\u5E01"}, - {"zh_TW", "1", "CNY", "CN\\u00A51.00", "CNY1.00", "1.00 \\u4eba\\u6c11\\u5e63"}, - {"zh_Hant", "1", "CNY", "CN\\u00A51.00", "CNY1.00", "1.00 \\u4eba\\u6c11\\u5e63"}, - {"zh_Hant", "1", "JPY", "\\u00A51.00", "JPY1.00", "1.00 \\u65e5\\u5713"}, - {"ja_JP", "1", "JPY", "\\uFFE51.00", "JPY1.00", "1.00\\u65e5\\u672c\\u5186"}, - {"ja_JP", "1", "JPY", "\\u00A51.00", "JPY1.00", "1.00\\u65e5\\u672c\\u5186"}, - {"ru_RU", "1", "RUB", "1,00\\u00A0\\u20BD", "1,00\\u00A0RUB", "1,00 \\u0420\\u043E\\u0441\\u0441\\u0438\\u0439\\u0441\\u043A\\u0438\\u0439 \\u0440\\u0443\\u0431\\u043B\\u044C"} + {"ko_KR", "1", "USD", "US$\\u00A01.00", "USD\\u00A01.00", "1.00 \\ubbf8\\uad6d \\ub2ec\\ub7ec"}, + {"ja_JP", "1", "USD", "$1.00", "USD\\u00A01.00", "1.00\\u00A0\\u7c73\\u30c9\\u30eb"}, + {"zh_CN", "1", "CNY", "\\uFFE51.00", "CNY\\u00A001.00", "1.00\\u00A0\\u4EBA\\u6C11\\u5E01"}, + {"zh_TW", "1", "CNY", "CN\\u00A51.00", "CNY\\u00A01.00", "1.00 \\u4eba\\u6c11\\u5e63"}, + {"zh_Hant", "1", "CNY", "CN\\u00A51.00", "CNY\\u00A01.00", "1.00 \\u4eba\\u6c11\\u5e63"}, + {"zh_Hant", "1", "JPY", "\\u00A51.00", "JPY\\u00A01.00", "1.00 \\u65e5\\u5713"}, + {"ja_JP", "1", "JPY", "\\uFFE51.00", "JPY\\u00A01.00", "1.00\\u00A0\\u65e5\\u672c\\u5186"}, + {"ja_JP", "1", "JPY", "\\u00A51.00", "JPY\\u00A01.00", "1.00\\u00A0\\u65e5\\u672c\\u5186"}, + {"ru_RU", "1", "RUB", "1,00\\u00A0\\u00A0\\u20BD", "1,00\\u00A0\\u00A0RUB", "1,00 \\u0420\\u043E\\u0441\\u0441\\u0438\\u0439\\u0441\\u043A\\u0438\\u0439 \\u0440\\u0443\\u0431\\u043B\\u044C"} }; static const UNumberFormatStyle currencyStyles[] = { UNUM_CURRENCY, @@ -4158,7 +4158,9 @@ for (;;) { } */ // test parsing, and test parsing for all currency formats. - for (int j = 3; j < 6; ++j) { + // NOTE: ICU 62 requires that the currency format match the pattern in strict mode. + //for (int j = 3; j < 6; ++j) { + for (int j = 3 + kIndex; j <= 3 + kIndex; j++) { // DATA[i][3] is the currency format result using // CURRENCYSTYLE formatter. // DATA[i][4] is the currency format result using @@ -6744,6 +6746,7 @@ NumberFormatTest::TestParseCurrencyInUCurr() { UnicodeString formatted = ctou(DATA[i]); UErrorCode status = U_ZERO_ERROR; NumberFormat* numFmt = NumberFormat::createInstance(locale, UNUM_CURRENCY, status); + numFmt->setLenient(TRUE); // ICU 62 PATCH if (numFmt != NULL && U_SUCCESS(status)) { ParsePosition parsePos; LocalPointer currAmt(numFmt->parseCurrency(formatted, parsePos)); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CombinedCurrencyMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CombinedCurrencyMatcher.java index c1d14189b6..ee303092b3 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CombinedCurrencyMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CombinedCurrencyMatcher.java @@ -109,7 +109,7 @@ public class CombinedCurrencyMatcher implements NumberParseMatcher { /** Matches the currency string without concern for currency spacing. */ private boolean matchCurrency(StringSegment segment, ParsedNumber result) { - int overlap1 = segment.getCommonPrefixLength(currency1); + int overlap1 = segment.getCaseSensitivePrefixLength(currency1); if (overlap1 == currency1.length()) { result.currencyCode = isoCode; segment.adjustOffset(overlap1); @@ -117,7 +117,7 @@ public class CombinedCurrencyMatcher implements NumberParseMatcher { return segment.length() == 0; } - int overlap2 = segment.getCommonPrefixLength(currency2); + int overlap2 = segment.getCaseSensitivePrefixLength(currency2); if (overlap2 == currency2.length()) { result.currencyCode = isoCode; segment.adjustOffset(overlap2); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt b/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt index b38a420f2c..35731ff8b7 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt @@ -1023,7 +1023,7 @@ begin parse output outputCurrency breaks 53.45 fail GBP £53.45 53.45 GBP -$53.45 fail USD J +$53.45 fail USD JP 53.45 USD 53.45 USD 53.45 GBP 53.45 GBP USD 53.45 53.45 USD J @@ -1041,7 +1041,7 @@ USD(7.92) -7.92 USD CJ (8) USD -8 USD -8 USD -8 USD C 67 USD 67 USD -53.45$ fail USD +53.45$ fail USD P US Dollars 53.45 53.45 USD J 53.45 US Dollars 53.45 USD US Dollar 53.45 53.45 USD J @@ -1070,7 +1070,7 @@ begin parse output outputCurrency breaks 53.45 fail GBP £53.45 53.45 GBP -$53.45 fail USD J +$53.45 fail USD JP 53.45 USD 53.45 USD 53.45 GBP 53.45 GBP USD 53.45 53.45 USD J @@ -1084,7 +1084,7 @@ USD -7.926 -7.926 USD CJ USD-7.92 -7.92 USD CJ -8 USD -8 USD 67 USD 67 USD -53.45$ fail USD +53.45$ fail USD P US Dollars 53.45 53.45 USD J 53.45 US Dollars 53.45 USD US Dollar 53.45 53.45 USD J @@ -1104,7 +1104,7 @@ parse output outputCurrency breaks // J throws a NullPointerException on the first case 53.45 fail GBP £53.45 53.45 GBP -$53.45 fail USD J +$53.45 fail USD JP 53.45 USD 53.45 USD 53.45 GBP 53.45 GBP USD 53.45 53.45 USD J @@ -1123,7 +1123,7 @@ USD(7.92) -7.92 USD CJ -8 USD -8 USD C 67 USD 67 USD // J throws a NullPointerException on the next case -53.45$ fail USD +53.45$ fail USD P US Dollars 53.45 53.45 USD J 53.45 US Dollars 53.45 USD US Dollar 53.45 53.45 USD J @@ -1142,7 +1142,7 @@ begin parse output outputCurrency breaks 53.45 fail GBP £53.45 53.45 GBP -$53.45 fail USD J +$53.45 fail USD JP 53.45 USD 53.45 USD 53.45 GBP 53.45 GBP USD 53.45 53.45 USD J @@ -1160,7 +1160,7 @@ USD(7.92) -7.92 USD CJ (8) USD -8 USD -8 USD -8 USD C 67 USD 67 USD -53.45$ fail USD +53.45$ fail USD P US Dollars 53.45 53.45 USD J 53.45 US Dollars 53.45 USD US Dollar 53.45 53.45 USD J @@ -1179,7 +1179,7 @@ begin parse output outputCurrency breaks 53.45 fail GBP £53.45 53.45 GBP -$53.45 fail USD J +$53.45 fail USD JP 53.45 USD 53.45 USD C 53.45 GBP 53.45 GBP C USD 53.45 53.45 USD J @@ -1198,7 +1198,7 @@ USD(7.92) -7.92 USD CJP (8) USD -8 USD CJP -8 USD -8 USD C 67 USD 67 USD C -53.45$ fail USD +53.45$ fail USD P US Dollars 53.45 53.45 USD J 53.45 US Dollars 53.45 USD US Dollar 53.45 53.45 USD J @@ -1604,6 +1604,24 @@ lenient parse output breaks 0 0 fail CJK 0 +0 0 CJK +test parse lowercase currency +set locale en +set pattern ¤¤0 +set currency USD +begin +parse output outputCurrency breaks +USD123 123 USD +USD 123 123 USD J +usd123 123 USD +usd 123 123 USD J +Usd123 123 USD +Usd 123 123 USD J +US$123 123 USD +us$123 fail fail +Us$123 fail fail +123 US dollars 123 USD +123 US DOLLARS 123 USD +123 us dollars 123 USD diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatDataDrivenTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatDataDrivenTest.java index d8be411eb9..3a38ffde9c 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatDataDrivenTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatDataDrivenTest.java @@ -663,6 +663,9 @@ public class NumberFormatDataDrivenTest { if (ppos.getIndex() == 0 || actual.getCurrency().getCurrencyCode().equals("XXX")) { return "Parse failed; got " + actual + ", but expected " + tuple.output; } + if (tuple.output.equals("fail")) { + return null; + } BigDecimal expectedNumber = new BigDecimal(tuple.output); if (expectedNumber.compareTo(new BigDecimal(actual.getNumber().toString())) != 0) { return "Wrong number: Expected: " + expectedNumber + ", got: " + actual; diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java index 639744aed4..d1386fcb87 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java @@ -6017,4 +6017,14 @@ public class NumberFormatTest extends TestFmwk { df.setParseStrict(true); expect2(df, -51.42, "-US$ 51.42"); } + + @Test + public void testCaseSensitiveCustomIsoCurrency() { + DecimalFormat df = new DecimalFormat("¤¤0", DecimalFormatSymbols.getInstance(ULocale.ENGLISH)); + df.setCurrency(Currency.getInstance("ICU")); + ParsePosition ppos = new ParsePosition(0); + df.parseCurrency("icu123", ppos); + assertEquals("Should fail to parse", 0, ppos.getIndex()); + assertEquals("Should fail to parse", 0, ppos.getErrorIndex()); + } }