]> granicus.if.org Git - icu/commitdiff
ICU-13568 ICU-13400 ICU-13389 ICU-13075 NumberFormatter assorted fixes: Adding custom...
authorShane Carr <shane@unicode.org>
Tue, 6 Feb 2018 03:08:17 +0000 (03:08 +0000)
committerShane Carr <shane@unicode.org>
Tue, 6 Feb 2018 03:08:17 +0000 (03:08 +0000)
X-SVN-Rev: 40838

48 files changed:
icu4c/source/common/ucurr.cpp
icu4c/source/common/unicode/ucurr.h
icu4c/source/i18n/number_compact.cpp
icu4c/source/i18n/number_compact.h
icu4c/source/i18n/number_fluent.cpp
icu4c/source/i18n/number_formatimpl.cpp
icu4c/source/i18n/number_grouping.cpp
icu4c/source/i18n/number_modifiers.cpp
icu4c/source/i18n/number_modifiers.h
icu4c/source/i18n/number_patternmodifier.cpp
icu4c/source/i18n/number_patternstring.cpp
icu4c/source/i18n/number_patternstring.h
icu4c/source/i18n/number_types.h
icu4c/source/i18n/unicode/numberformatter.h
icu4c/source/test/intltest/numbertest.h
icu4c/source/test/intltest/numbertest_api.cpp
icu4c/source/test/intltest/numbertest_modifiers.cpp
icu4c/source/test/intltest/numbertest_patternmodifier.cpp
icu4c/source/test/intltest/numfmtst.cpp
icu4c/source/test/intltest/numfmtst.h
icu4j/main/classes/core/src/com/ibm/icu/impl/CurrencyData.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/AffixPatternProvider.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/ConstantMultiFieldModifier.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/CurrencyPluralInfoAffixProvider.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/CurrencySpacingEnabledModifier.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/Grouper.java [new file with mode: 0644]
icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroProps.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringParser.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/PropertiesAffixPatternProvider.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/DecimalMatcher.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ScientificMatcher.java
icu4j/main/classes/core/src/com/ibm/icu/number/CompactNotation.java
icu4j/main/classes/core/src/com/ibm/icu/number/Grouper.java [deleted file]
icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java
icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java
icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java
icu4j/main/classes/core/src/com/ibm/icu/number/NumberPropertyMapper.java
icu4j/main/classes/core/src/com/ibm/icu/text/CurrencyDisplayNames.java
icu4j/main/classes/core/src/com/ibm/icu/util/Currency.java
icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ModifierTest.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MutablePatternModifierTest.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/CurrencyTest.java

index a772da9a2994172bee03d65342ba476f06570d15..37c3d79e4d16ea70e89cc45df07f40075dd73ae0 100644 (file)
@@ -17,6 +17,7 @@
 #include "unicode/ustring.h"
 #include "unicode/parsepos.h"
 #include "ustr_imp.h"
+#include "charstr.h"
 #include "cmemory.h"
 #include "cstring.h"
 #include "uassert.h"
 #include "uinvchar.h"
 #include "uresimp.h"
 #include "ulist.h"
+#include "uresimp.h"
 #include "ureslocs.h"
 #include "ulocimp.h"
 
+using namespace icu;
+
 //#define UCURR_DEBUG_EQUIV 1
 #ifdef UCURR_DEBUG_EQUIV
 #include "stdio.h"
@@ -104,6 +108,7 @@ static const char VAR_DELIM_STR[] = "_";
 
 // Tag for localized display names (symbols) of currencies
 static const char CURRENCIES[] = "Currencies";
+static const char CURRENCIES_NARROW[] = "Currencies%narrow";
 static const char CURRENCYPLURALS[] = "CurrencyPlurals";
 
 static const UChar EUR_STR[] = {0x0045,0x0055,0x0052,0};
@@ -698,7 +703,7 @@ ucurr_getName(const UChar* currency,
     }
 
     int32_t choice = (int32_t) nameStyle;
-    if (choice < 0 || choice > 1) {
+    if (choice < 0 || choice > 2) {
         *ec = U_ILLEGAL_ARGUMENT_ERROR;
         return 0;
     }
@@ -731,15 +736,19 @@ ucurr_getName(const UChar* currency,
     
     const UChar* s = NULL;
     ec2 = U_ZERO_ERROR;
-    UResourceBundle* rb = ures_open(U_ICUDATA_CURR, loc, &ec2);
-
-    rb = ures_getByKey(rb, CURRENCIES, rb, &ec2);
-
-    // Fetch resource with multi-level resource inheritance fallback
-    rb = ures_getByKeyWithFallback(rb, buf, rb, &ec2);
-
-    s = ures_getStringByIndex(rb, choice, len, &ec2);
-    ures_close(rb);
+    LocalUResourceBundlePointer rb(ures_open(U_ICUDATA_CURR, loc, &ec2));
+
+    if (nameStyle == UCURR_NARROW_SYMBOL_NAME) {
+        CharString key;
+        key.append(CURRENCIES_NARROW, ec2);
+        key.append("/", ec2);
+        key.append(buf, ec2);
+        s = ures_getStringByKeyWithFallback(rb.getAlias(), key.data(), len, &ec2);
+    } else {
+        ures_getByKey(rb.getAlias(), CURRENCIES, rb.getAlias(), &ec2);
+        ures_getByKeyWithFallback(rb.getAlias(), buf, rb.getAlias(), &ec2);
+        s = ures_getStringByIndex(rb.getAlias(), choice, len, &ec2);
+    }
 
     // If we've succeeded we're done.  Otherwise, try to fallback.
     // If that fails (because we are already at root) then exit.
index 1abb3b22e97af41dcf4b422b67559437d34bed08..e3831bba15000cb7a9d7cedbd4178f685b66b178 100644 (file)
@@ -102,7 +102,17 @@ typedef enum UCurrNameStyle {
      * currency, such as "US Dollar" for USD.
      * @stable ICU 2.6
      */
-    UCURR_LONG_NAME
+    UCURR_LONG_NAME,
+
+    /**
+     * 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.
+     *
+     * @draft ICU 61
+     */
+    UCURR_NARROW_SYMBOL_NAME
 } UCurrNameStyle;
 
 #if !UCONFIG_NO_SERVICE
index 8ceee1378b24cb8b2ed249a725cc34f66b86996e..cc0d8fd2a20ccec5543b5976de27b4539f9ee9ae 100644 (file)
@@ -262,7 +262,6 @@ void CompactHandler::precomputeAllModifiers(MutablePatternModifier &buildReferen
         buildReference.setPatternInfo(&patternInfo);
         info.mod = buildReference.createImmutable(status);
         if (U_FAILURE(status)) { return; }
-        info.numDigits = patternInfo.positive.integerTotal;
         info.patternString = patternString;
     }
 }
@@ -286,7 +285,6 @@ void CompactHandler::processQuantity(DecimalQuantity &quantity, MicroProps &micr
 
     StandardPlural::Form plural = quantity.getStandardPlural(rules);
     const UChar *patternString = data.getPattern(magnitude, plural);
-    int numDigits = -1;
     if (patternString == nullptr) {
         // Use the default (non-compact) modifier.
         // No need to take any action.
@@ -299,7 +297,6 @@ void CompactHandler::processQuantity(DecimalQuantity &quantity, MicroProps &micr
             const CompactModInfo &info = precomputedMods[i];
             if (u_strcmp(patternString, info.patternString) == 0) {
                 info.mod->applyToMicros(micros, quantity);
-                numDigits = info.numDigits;
                 break;
             }
         }
@@ -313,12 +310,8 @@ void CompactHandler::processQuantity(DecimalQuantity &quantity, MicroProps &micr
         PatternParser::parseToPatternInfo(UnicodeString(patternString), patternInfo, status);
         static_cast<MutablePatternModifier*>(const_cast<Modifier*>(micros.modMiddle))
             ->setPatternInfo(&patternInfo);
-        numDigits = patternInfo.positive.integerTotal;
     }
 
-    // FIXME: Deal with numDigits == 0 (Awaiting a test case)
-    (void)numDigits;
-
     // We already performed rounding. Do not perform it again.
     micros.rounding = Rounder::constructPassThrough();
 }
index 2344abf535a96212b7265cc7e59e8e21137cc213..f7adf36416e92f0435facf5d6f80c30bbda6f941 100644 (file)
@@ -52,7 +52,6 @@ class CompactData : public MultiplierProducer {
 struct CompactModInfo {
     const ImmutablePatternModifier *mod;
     const UChar* patternString;
-    int32_t numDigits;
 };
 
 class CompactHandler : public MicroPropsGenerator, public UMemory {
index 5e59c4c14e7a84bffa2de9f0c639220af700c655..3be3401ef3a28d5e76ef7973fbea6ed495dd589e 100644 (file)
@@ -73,9 +73,11 @@ Derived NumberFormatterSettings<Derived>::rounding(const Rounder &rounder) const
 }
 
 template<typename Derived>
-Derived NumberFormatterSettings<Derived>::grouping(const Grouper &grouper) const {
+Derived NumberFormatterSettings<Derived>::grouping(const UGroupingStrategy &strategy) const {
     Derived copy(*this);
-    copy.fMacros.grouper = grouper;
+    // NOTE: This is slightly different than how the setting is stored in Java
+    // because we want to put it on the stack.
+    copy.fMacros.grouper = Grouper::forStrategy(strategy);
     return copy;
 }
 
index e2fc4f20beb85f4bd7ae2460702bbf1bdce45fde..bc96cb15dabf9024214a83d4a57bc4f760e83d57 100644 (file)
@@ -17,6 +17,8 @@
 #include "unicode/dcfmtsym.h"
 #include "number_scientific.h"
 #include "number_compact.h"
+#include "uresimp.h"
+#include "ureslocs.h"
 
 using namespace icu;
 using namespace icu::number;
@@ -88,6 +90,37 @@ const char16_t *getPatternForStyle(const Locale &locale, const char *nsName, Cld
     return pattern;
 }
 
+struct CurrencyFormatInfoResult {
+    bool exists;
+    const char16_t* pattern;
+    const char16_t* decimalSeparator;
+    const char16_t* groupingSeparator;
+};
+CurrencyFormatInfoResult getCurrencyFormatInfo(const Locale& locale, const char* isoCode, UErrorCode& status) {
+    // TODO: Load this data in a centralized location like ICU4J?
+    // TODO: Parts of this same data are loaded in dcfmtsym.cpp; should clean up.
+    CurrencyFormatInfoResult result = { false, nullptr, nullptr, nullptr };
+    if (U_FAILURE(status)) return result;
+    CharString key;
+    key.append("Currencies/", status);
+    key.append(isoCode, status);
+    UErrorCode localStatus = status;
+    LocalUResourceBundlePointer bundle(ures_open(U_ICUDATA_CURR, locale.getName(), &localStatus));
+    ures_getByKeyWithFallback(bundle.getAlias(), key.data(), bundle.getAlias(), &localStatus);
+    if (U_SUCCESS(localStatus) && ures_getSize(bundle.getAlias())>2) { // the length is 3 if more data is present
+        ures_getByIndex(bundle.getAlias(), 2, bundle.getAlias(), &localStatus);
+        int32_t dummy;
+        result.exists = true;
+        result.pattern = ures_getStringByIndex(bundle.getAlias(), 0, &dummy, &localStatus);
+        result.decimalSeparator = ures_getStringByIndex(bundle.getAlias(), 1, &dummy, &localStatus);
+        result.groupingSeparator = ures_getStringByIndex(bundle.getAlias(), 2, &dummy, &localStatus);
+        status = localStatus;
+    } else if (localStatus != U_MISSING_RESOURCE_ERROR) {
+        status = localStatus;
+    }
+    return result;
+}
+
 inline bool unitIsCurrency(const MeasureUnit &unit) {
     return uprv_strcmp("currency", unit.getType()) == 0;
 }
@@ -186,20 +219,51 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps &macros, bool safe,
     }
     const char *nsName = U_SUCCESS(status) ? ns->getName() : "latn";
 
-    // Load and parse the pattern string.  It is used for grouping sizes and affixes only.
-    CldrPatternStyle patternStyle;
-    if (isPercent || isPermille) {
-        patternStyle = CLDR_PATTERN_STYLE_PERCENT;
-    } else if (!isCurrency || unitWidth == UNUM_UNIT_WIDTH_FULL_NAME) {
-        patternStyle = CLDR_PATTERN_STYLE_DECIMAL;
-    } else if (isAccounting) {
-        // NOTE: Although ACCOUNTING and ACCOUNTING_ALWAYS are only supported in currencies right now,
-        // the API contract allows us to add support to other units in the future.
-        patternStyle = CLDR_PATTERN_STYLE_ACCOUNTING;
+    // Resolve the symbols. Do this here because currency may need to customize them.
+    if (macros.symbols.isDecimalFormatSymbols()) {
+        fMicros.symbols = macros.symbols.getDecimalFormatSymbols();
     } else {
-        patternStyle = CLDR_PATTERN_STYLE_CURRENCY;
+        fMicros.symbols = new DecimalFormatSymbols(macros.locale, *ns, status);
+        // Give ownership to the NumberFormatterImpl.
+        fSymbols.adoptInstead(fMicros.symbols);
+    }
+
+    // Load and parse the pattern string. It is used for grouping sizes and affixes only.
+    // If we are formatting currency, check for a currency-specific pattern.
+    const char16_t* pattern = nullptr;
+    if (isCurrency) {
+        CurrencyFormatInfoResult info = getCurrencyFormatInfo(macros.locale, currency.getSubtype(), status);
+        if (info.exists) {
+            pattern = info.pattern;
+            // It's clunky to clone an object here, but this code is not frequently executed.
+            DecimalFormatSymbols* symbols = new DecimalFormatSymbols(*fMicros.symbols);
+            fMicros.symbols = symbols;
+            fSymbols.adoptInstead(symbols);
+            symbols->setSymbol(
+                DecimalFormatSymbols::ENumberFormatSymbol::kMonetarySeparatorSymbol,
+                UnicodeString(info.decimalSeparator),
+                FALSE);
+            symbols->setSymbol(
+                DecimalFormatSymbols::ENumberFormatSymbol::kMonetaryGroupingSeparatorSymbol,
+                UnicodeString(info.groupingSeparator),
+                FALSE);
+        }
+    }
+    if (pattern == nullptr) {
+        CldrPatternStyle patternStyle;
+        if (isPercent || isPermille) {
+            patternStyle = CLDR_PATTERN_STYLE_PERCENT;
+        } else if (!isCurrency || unitWidth == UNUM_UNIT_WIDTH_FULL_NAME) {
+            patternStyle = CLDR_PATTERN_STYLE_DECIMAL;
+        } else if (isAccounting) {
+            // NOTE: Although ACCOUNTING and ACCOUNTING_ALWAYS are only supported in currencies right now,
+            // the API contract allows us to add support to other units in the future.
+            patternStyle = CLDR_PATTERN_STYLE_ACCOUNTING;
+        } else {
+            patternStyle = CLDR_PATTERN_STYLE_CURRENCY;
+        }
+        pattern = getPatternForStyle(macros.locale, nsName, patternStyle, status);
     }
-    const char16_t *pattern = getPatternForStyle(macros.locale, nsName, patternStyle, status);
     auto patternInfo = new ParsedPatternInfo();
     fPatternInfo.adoptInstead(patternInfo);
     PatternParser::parseToPatternInfo(UnicodeString(pattern), *patternInfo, status);
@@ -208,15 +272,6 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps &macros, bool safe,
     /// START POPULATING THE DEFAULT MICROPROPS AND BUILDING THE MICROPROPS GENERATOR ///
     /////////////////////////////////////////////////////////////////////////////////////
 
-    // Symbols
-    if (macros.symbols.isDecimalFormatSymbols()) {
-        fMicros.symbols = macros.symbols.getDecimalFormatSymbols();
-    } else {
-        fMicros.symbols = new DecimalFormatSymbols(macros.locale, *ns, status);
-        // Give ownership to the NumberFormatterImpl.
-        fSymbols.adoptInstead(fMicros.symbols);
-    }
-
     // Rounding strategy
     if (!macros.rounder.isBogus()) {
         fMicros.rounding = macros.rounder;
@@ -234,11 +289,11 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps &macros, bool safe,
         fMicros.grouping = macros.grouper;
     } else if (macros.notation.fType == Notation::NTN_COMPACT) {
         // Compact notation uses minGrouping by default since ICU 59
-        fMicros.grouping = Grouper::minTwoDigits();
+        fMicros.grouping = Grouper::forStrategy(UNUM_GROUPING_MIN2);
     } else {
-        fMicros.grouping = Grouper::defaults();
+        fMicros.grouping = Grouper::forStrategy(UNUM_GROUPING_AUTO);
     }
-    fMicros.grouping.setLocaleData(*fPatternInfo);
+    fMicros.grouping.setLocaleData(*fPatternInfo, macros.locale);
 
     // Padding strategy
     if (!macros.padder.isBogus()) {
index 15362825cc68ce8b6515ee161c7d4998b4b4bcc9..67fd4c943178085f4c2edc282f5dab884a9bb08c 100644 (file)
@@ -7,36 +7,70 @@
 
 #include "unicode/numberformatter.h"
 #include "number_patternstring.h"
+#include "uresimp.h"
 
 using namespace icu;
 using namespace icu::number;
 using namespace icu::number::impl;
 
-Grouper Grouper::defaults() {
-    return {-2, -2, false};
+namespace {
+
+int16_t getMinGroupingForLocale(const Locale& locale) {
+    // TODO: Cache this?
+    UErrorCode localStatus = U_ZERO_ERROR;
+    LocalUResourceBundlePointer bundle(ures_open(NULL, locale.getName(), &localStatus));
+    int32_t resultLen = 0;
+    const char16_t* result = ures_getStringByKeyWithFallback(
+        bundle.getAlias(),
+        "NumberElements/minimumGroupingDigits",
+        &resultLen,
+        &localStatus);
+    // TODO: Is it safe to assume resultLen == 1? Would locales set minGrouping >= 10?
+    if (U_FAILURE(localStatus) || resultLen != 1) {
+        return 1;
+    }
+    return result[0] - u'0';
 }
 
-Grouper Grouper::minTwoDigits() {
-    return {-2, -2, true};
 }
 
-Grouper Grouper::none() {
-    return {-1, -1, false};
+Grouper Grouper::forStrategy(UGroupingStrategy grouping) {
+    switch (grouping) {
+    case UNUM_GROUPING_OFF:
+        return {-1, -1, -2};
+    case UNUM_GROUPING_AUTO:
+        return {-2, -2, -2};
+    case UNUM_GROUPING_MIN2:
+        return {-2, -2, -3};
+    case UNUM_GROUPING_ON_ALIGNED:
+        return {-4, -4, 1};
+    case UNUM_GROUPING_WESTERN:
+        return {3, 3, 1};
+    default:
+        U_ASSERT(FALSE);
+    }
 }
 
-void Grouper::setLocaleData(const impl::ParsedPatternInfo &patternInfo) {
-    if (fGrouping1 != -2) {
+void Grouper::setLocaleData(const impl::ParsedPatternInfo &patternInfo, const Locale& locale) {
+    if (fGrouping1 != -2 && fGrouping2 != -4) {
         return;
     }
-    auto grouping1 = static_cast<int8_t> (patternInfo.positive.groupingSizes & 0xffff);
-    auto grouping2 = static_cast<int8_t> ((patternInfo.positive.groupingSizes >> 16) & 0xffff);
-    auto grouping3 = static_cast<int8_t> ((patternInfo.positive.groupingSizes >> 32) & 0xffff);
+    auto grouping1 = static_cast<int16_t> (patternInfo.positive.groupingSizes & 0xffff);
+    auto grouping2 = static_cast<int16_t> ((patternInfo.positive.groupingSizes >> 16) & 0xffff);
+    auto grouping3 = static_cast<int16_t> ((patternInfo.positive.groupingSizes >> 32) & 0xffff);
     if (grouping2 == -1) {
-        grouping1 = -1;
+        grouping1 = fGrouping1 == -4 ? (short) 3 : (short) -1;
     }
     if (grouping3 == -1) {
         grouping2 = grouping1;
     }
+    if (fMinGrouping == -2) {
+        fMinGrouping = getMinGroupingForLocale(locale);
+    } else if (fMinGrouping == -3) {
+        fMinGrouping = uprv_max(2, getMinGroupingForLocale(locale));
+    } else {
+        // leave fMinGrouping alone
+    }
     fGrouping1 = grouping1;
     fGrouping2 = grouping2;
 }
@@ -49,7 +83,7 @@ bool Grouper::groupAtPosition(int32_t position, const impl::DecimalQuantity &val
     }
     position -= fGrouping1;
     return position >= 0 && (position % fGrouping2) == 0
-           && value.getUpperDisplayMagnitude() - fGrouping1 + 1 >= (fMin2 ? 2 : 1);
+           && value.getUpperDisplayMagnitude() - fGrouping1 + 1 >= fMinGrouping;
 }
 
 #endif /* #if !UCONFIG_NO_FORMATTING */
index 016248950960624c312559aa1229b88e9444e3b9..872b97010d74b7f4fa0847b10ebbedfce0f3fb87 100644 (file)
@@ -155,9 +155,15 @@ SimpleModifier::formatAsPrefixSuffix(NumberStringBuilder &result, int32_t startI
 
 int32_t ConstantMultiFieldModifier::apply(NumberStringBuilder &output, int leftIndex, int rightIndex,
                                           UErrorCode &status) const {
-    // Insert the suffix first since inserting the prefix will change the rightIndex
-    int32_t length = output.insert(rightIndex, fSuffix, status);
-    length += output.insert(leftIndex, fPrefix, status);
+    int32_t length = output.insert(leftIndex, fPrefix, status);
+    if (fOverwrite) {
+        length += output.splice(
+            leftIndex + length,
+            rightIndex + length,
+            UnicodeString(), 0, 0,
+            UNUM_FIELD_COUNT, status);
+    }
+    length += output.insert(rightIndex + length, fSuffix, status);
     return length;
 }
 
@@ -177,10 +183,11 @@ bool ConstantMultiFieldModifier::isStrong() const {
 
 CurrencySpacingEnabledModifier::CurrencySpacingEnabledModifier(const NumberStringBuilder &prefix,
                                                                const NumberStringBuilder &suffix,
+                                                               bool overwrite,
                                                                bool strong,
                                                                const DecimalFormatSymbols &symbols,
                                                                UErrorCode &status)
-        : ConstantMultiFieldModifier(prefix, suffix, strong) {
+        : ConstantMultiFieldModifier(prefix, suffix, overwrite, strong) {
     // Check for currency spacing. Do not build the UnicodeSets unless there is
     // a currency code point at a boundary.
     if (prefix.length() > 0 && prefix.fieldAt(prefix.length() - 1) == UNUM_CURRENCY_FIELD) {
index 962d17b574a76724f94260fcea0c3e47fb1f8423..4762a6f6d37a2deb53f17de2f9d541f308c76cf3 100644 (file)
@@ -103,8 +103,15 @@ class U_I18N_API SimpleModifier : public Modifier, public UMemory {
  */
 class U_I18N_API ConstantMultiFieldModifier : public Modifier, public UMemory {
   public:
-    ConstantMultiFieldModifier(const NumberStringBuilder &prefix, const NumberStringBuilder &suffix,
-                               bool strong) : fPrefix(prefix), fSuffix(suffix), fStrong(strong) {}
+    ConstantMultiFieldModifier(
+            const NumberStringBuilder &prefix,
+            const NumberStringBuilder &suffix,
+            bool overwrite,
+            bool strong)
+      : fPrefix(prefix),
+        fSuffix(suffix),
+        fOverwrite(overwrite),
+        fStrong(strong) {}
 
     int32_t apply(NumberStringBuilder &output, int32_t leftIndex, int32_t rightIndex,
                   UErrorCode &status) const U_OVERRIDE;
@@ -120,6 +127,7 @@ class U_I18N_API ConstantMultiFieldModifier : public Modifier, public UMemory {
     // value and is treated internally as immutable.
     NumberStringBuilder fPrefix;
     NumberStringBuilder fSuffix;
+    bool fOverwrite;
     bool fStrong;
 };
 
@@ -127,8 +135,13 @@ class U_I18N_API ConstantMultiFieldModifier : public Modifier, public UMemory {
 class U_I18N_API CurrencySpacingEnabledModifier : public ConstantMultiFieldModifier {
   public:
     /** Safe code path */
-    CurrencySpacingEnabledModifier(const NumberStringBuilder &prefix, const NumberStringBuilder &suffix,
-                                   bool strong, const DecimalFormatSymbols &symbols, UErrorCode &status);
+    CurrencySpacingEnabledModifier(
+            const NumberStringBuilder &prefix,
+            const NumberStringBuilder &suffix,
+            bool overwrite,
+            bool strong,
+            const DecimalFormatSymbols &symbols,
+            UErrorCode &status);
 
     int32_t apply(NumberStringBuilder &output, int32_t leftIndex, int32_t rightIndex,
                   UErrorCode &status) const U_OVERRIDE;
index 0866285e4595e98a1732dd315a09bef1680700fd..e182104c9116e7606cccd022cb3816ec0a7847f4 100644 (file)
@@ -109,9 +109,9 @@ ConstantMultiFieldModifier *MutablePatternModifier::createConstantModifier(UErro
     insertPrefix(a, 0, status);
     insertSuffix(b, 0, status);
     if (patternInfo->hasCurrencySign()) {
-        return new CurrencySpacingEnabledModifier(a, b, fStrong, *symbols, status);
+        return new CurrencySpacingEnabledModifier(a, b, !patternInfo->hasBody(), fStrong, *symbols, status);
     } else {
-        return new ConstantMultiFieldModifier(a, b, fStrong);
+        return new ConstantMultiFieldModifier(a, b, !patternInfo->hasBody(), fStrong);
     }
 }
 
@@ -167,9 +167,23 @@ int32_t MutablePatternModifier::apply(NumberStringBuilder &output, int32_t leftI
     auto nonConstThis = const_cast<MutablePatternModifier *>(this);
     int32_t prefixLen = nonConstThis->insertPrefix(output, leftIndex, status);
     int32_t suffixLen = nonConstThis->insertSuffix(output, rightIndex + prefixLen, status);
+    // If the pattern had no decimal stem body (like #,##0.00), overwrite the value.
+    int32_t overwriteLen = 0;
+    if (!patternInfo->hasBody()) {
+        overwriteLen = output.splice(
+            leftIndex + prefixLen, rightIndex + prefixLen,
+            UnicodeString(), 0, 0, UNUM_FIELD_COUNT,
+            status);
+    }
     CurrencySpacingEnabledModifier::applyCurrencySpacing(
-            output, leftIndex, prefixLen, rightIndex + prefixLen, suffixLen, *symbols, status);
-    return prefixLen + suffixLen;
+            output,
+            leftIndex,
+            prefixLen,
+            rightIndex + overwriteLen + prefixLen,
+            suffixLen,
+            *symbols,
+            status);
+    return prefixLen + overwriteLen + suffixLen;
 }
 
 int32_t MutablePatternModifier::getPrefixLength(UErrorCode &status) const {
@@ -234,13 +248,16 @@ UnicodeString MutablePatternModifier::getSymbol(AffixPatternType type) const {
             } else if (unitWidth == UNumberUnitWidth::UNUM_UNIT_WIDTH_HIDDEN) {
                 return UnicodeString();
             } else {
+                UCurrNameStyle selector = (unitWidth == UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW)
+                        ? UCurrNameStyle::UCURR_NARROW_SYMBOL_NAME
+                        : UCurrNameStyle::UCURR_SYMBOL_NAME;
                 UErrorCode status = U_ZERO_ERROR;
                 UBool isChoiceFormat = FALSE;
                 int32_t symbolLen = 0;
                 const char16_t *symbol = ucurr_getName(
                         currencyCode,
                         symbols->getLocale().getName(),
-                        UCurrNameStyle::UCURR_SYMBOL_NAME,
+                        selector,
                         &isChoiceFormat,
                         &symbolLen,
                         &status);
index c67e3541816547b0c0f4dfe0e9898a0ff58d4d09..20178824b0e20a1d3217c83059089c7a2e93b556 100644 (file)
@@ -95,6 +95,10 @@ bool ParsedPatternInfo::containsSymbolType(AffixPatternType type, UErrorCode &st
     return AffixUtils::containsType(UnicodeStringCharSequence(pattern), type, status);
 }
 
+bool ParsedPatternInfo::hasBody() const {
+    return positive.integerTotal > 0;
+}
+
 /////////////////////////////////////////////////////
 /// BEGIN RECURSIVE DESCENT PARSER IMPLEMENTATION ///
 /////////////////////////////////////////////////////
index 6e1bb7f44ddc02a26718864628f16c3a56007e76..ec44290d66397c78d2697afff5df1360a5a9ccd8 100644 (file)
@@ -84,6 +84,8 @@ struct U_I18N_API ParsedPatternInfo : public AffixPatternProvider, public UMemor
 
     bool containsSymbolType(AffixPatternType type, UErrorCode &status) const U_OVERRIDE;
 
+    bool hasBody() const U_OVERRIDE;
+
   private:
     struct U_I18N_API ParserState {
         const UnicodeString &pattern; // reference to the parent
index a404ef9686d15b04f433f1733fb7a108796e1167..e914ef71ac085c97e4071d98d9745a21a2a92e66 100644 (file)
@@ -142,6 +142,13 @@ class U_I18N_API AffixPatternProvider {
     virtual bool negativeHasMinusSign() const = 0;
 
     virtual bool containsSymbolType(AffixPatternType, UErrorCode &) const = 0;
+
+    /**
+     * True if the pattern has a number placeholder like "0" or "#,##0.00"; false if the pattern does not
+     * have one. This is used in cases like compact notation, where the pattern replaces the entire
+     * number instead of rendering the number.
+     */
+    virtual bool hasBody() const = 0;
 };
 
 /**
index 397181536b9ea978698c420930548692cb1cb72c..4c4f542b4ddd4d2d0d1e58551af28e4e2a2ed71a 100644 (file)
  * </ul>
  *
  * <p>
- * * The narrow format for currencies is not currently supported; this is a known issue that will be fixed in a
- * future version. See #11666 for more information.
- *
- * <p>
  * This enum is similar to {@link com.ibm.icu.text.MeasureFormat.FormatWidth}.
  *
  * @draft ICU 60
@@ -165,6 +161,97 @@ typedef enum UNumberUnitWidth {
             UNUM_UNIT_WIDTH_COUNT
 } UNumberUnitWidth;
 
+/**
+ * An enum declaring the strategy for when and how to display grouping separators (i.e., the
+ * separator, often a comma or period, after every 2-3 powers of ten). The choices are several
+ * pre-built strategies for different use cases that employ locale data whenever possible. Example
+ * outputs for 1234 and 1234567 in <em>en-IN</em>:
+ *
+ * <ul>
+ * <li>OFF: 1234 and 12345
+ * <li>MIN2: 1234 and 12,34,567
+ * <li>AUTO: 1,234 and 12,34,567
+ * <li>ON_ALIGNED: 1,234 and 12,34,567
+ * <li>WESTERN: 1,234 and 1,234,567
+ * </ul>
+ *
+ * <p>
+ * The default is AUTO, which displays grouping separators unless the locale data says that grouping
+ * is not customary. To force grouping for all numbers greater than 1000 consistently across locales,
+ * use ON_ALIGNED. On the other hand, to display grouping less frequently than the default, use MIN2
+ * or OFF. See the docs of each option for details.
+ *
+ * <p>
+ * Note: This enum specifies the strategy for grouping sizes. To set which character to use as the
+ * grouping separator, use the "symbols" setter.
+ *
+ * @draft ICU 61
+ */
+typedef enum UGroupingStrategy {
+    /**
+     * Do not display grouping separators in any locale.
+     *
+     * @draft ICU 61
+     */
+    UNUM_GROUPING_OFF,
+
+    /**
+     * Display grouping using locale defaults, except do not show grouping on values smaller than
+     * 10000 (such that there is a <em>minimum of two digits</em> before the first separator).
+     *
+     * <p>
+     * Note that locales may restrict grouping separators to be displayed only on 1 million or
+     * greater (for example, ee and hu) or disable grouping altogether (for example, bg currency).
+     *
+     * <p>
+     * Locale data is used to determine whether to separate larger numbers into groups of 2
+     * (customary in South Asia) or groups of 3 (customary in Europe and the Americas).
+     *
+     * @draft ICU 61
+     */
+    UNUM_GROUPING_MIN2,
+
+    /**
+     * Display grouping using the default strategy for all locales. This is the default behavior.
+     *
+     * <p>
+     * Note that locales may restrict grouping separators to be displayed only on 1 million or
+     * greater (for example, ee and hu) or disable grouping altogether (for example, bg currency).
+     *
+     * <p>
+     * Locale data is used to determine whether to separate larger numbers into groups of 2
+     * (customary in South Asia) or groups of 3 (customary in Europe and the Americas).
+     *
+     * @draft ICU 61
+     */
+    UNUM_GROUPING_AUTO,
+
+    /**
+     * Always display the grouping separator on values of at least 1000.
+     *
+     * <p>
+     * This option ignores the locale data that restricts or disables grouping, described in MIN2 and
+     * AUTO. This option may be useful to normalize the alignment of numbers, such as in a
+     * spreadsheet.
+     *
+     * <p>
+     * Locale data is used to determine whether to separate larger numbers into groups of 2
+     * (customary in South Asia) or groups of 3 (customary in Europe and the Americas).
+     *
+     * @draft ICU 61
+     */
+    UNUM_GROUPING_ON_ALIGNED,
+
+    /**
+     * Use the Western defaults: groups of 3 and enabled for all numbers 1000 or greater. Do not use
+     * locale data for determining the grouping strategy.
+     *
+     * @draft ICU 61
+     */
+    UNUM_GROUPING_WESTERN
+
+} UGroupingStrategy;
+
 /**
  * An enum declaring how to denote positive and negative numbers. Example outputs when formatting 123 and -123 in
  * <em>en-US</em>:
@@ -303,7 +390,6 @@ class Rounder;
 class FractionRounder;
 class CurrencyRounder;
 class IncrementRounder;
-class Grouper;
 class IntegerWidth;
 
 namespace impl {
@@ -1036,53 +1122,6 @@ class U_I18N_API IncrementRounder : public Rounder {
     friend class Rounder;
 };
 
-/**
- * @internal This API is a technical preview.  It is likely to change in an upcoming release.
- */
-class U_I18N_API Grouper : public UMemory {
-  public:
-    /**
-     * @internal This API is a technical preview.  It is likely to change in an upcoming release.
-     */
-    static Grouper defaults();
-
-    /**
-     * @internal This API is a technical preview.  It is likely to change in an upcoming release.
-     */
-    static Grouper minTwoDigits();
-
-    /**
-     * @internal This API is a technical preview.  It is likely to change in an upcoming release.
-     */
-    static Grouper none();
-
-  private:
-    int8_t fGrouping1; // -3 means "bogus"; -2 means "needs locale data"; -1 means "no grouping"
-    int8_t fGrouping2;
-    bool fMin2;
-
-    Grouper(int8_t grouping1, int8_t grouping2, bool min2)
-            : fGrouping1(grouping1), fGrouping2(grouping2), fMin2(min2) {}
-
-    Grouper() : fGrouping1(-3) {};
-
-    bool isBogus() const {
-        return fGrouping1 == -3;
-    }
-
-    /** NON-CONST: mutates the current instance. */
-    void setLocaleData(const impl::ParsedPatternInfo &patternInfo);
-
-    bool groupAtPosition(int32_t position, const impl::DecimalQuantity &value) const;
-
-    // To allow MacroProps/MicroProps to initialize empty instances:
-    friend struct impl::MacroProps;
-    friend struct impl::MicroProps;
-
-    // To allow NumberFormatterImpl to access isBogus() and perform other operations:
-    friend class impl::NumberFormatterImpl;
-};
-
 /**
  * A class that defines the strategy for padding and truncating integers before the decimal separator.
  *
@@ -1252,6 +1291,58 @@ class U_I18N_API SymbolsWrapper : public UMemory {
     void doCleanup();
 };
 
+/** @internal */
+class U_I18N_API Grouper : public UMemory {
+  public:
+    /** @internal */
+    static Grouper forStrategy(UGroupingStrategy grouping);
+
+    // Future: static Grouper forProperties(DecimalFormatProperties& properties);
+
+    /** @internal */
+    Grouper(int16_t grouping1, int16_t grouping2, int16_t minGrouping)
+            : fGrouping1(grouping1), fGrouping2(grouping2), fMinGrouping(minGrouping) {}
+
+  private:
+    /**
+     * The grouping sizes, with the following special values:
+     * <ul>
+     * <li>-1 = no grouping
+     * <li>-2 = needs locale data
+     * <li>-4 = fall back to Western grouping if not in locale
+     * </ul>
+     */
+    int16_t fGrouping1;
+    int16_t fGrouping2;
+
+    /**
+     * The minimum gropuing size, with the following special values:
+     * <ul>
+     * <li>-2 = needs locale data
+     * <li>-3 = no less than 2
+     * </ul>
+     */
+    int16_t fMinGrouping;
+
+    Grouper() : fGrouping1(-3) {};
+
+    bool isBogus() const {
+        return fGrouping1 == -3;
+    }
+
+    /** NON-CONST: mutates the current instance. */
+    void setLocaleData(const impl::ParsedPatternInfo &patternInfo, const Locale& locale);
+
+    bool groupAtPosition(int32_t position, const impl::DecimalQuantity &value) const;
+
+    // To allow MacroProps/MicroProps to initialize empty instances:
+    friend struct MacroProps;
+    friend struct MicroProps;
+
+    // To allow NumberFormatterImpl to access isBogus() and perform other operations:
+    friend class NumberFormatterImpl;
+};
+
 /** @internal */
 class U_I18N_API Padder : public UMemory {
   public:
@@ -1531,8 +1622,6 @@ class U_I18N_API NumberFormatterSettings {
      */
     Derived rounding(const Rounder &rounder) const;
 
-#ifndef U_HIDE_INTERNAL_API
-
     /**
      * Specifies the grouping strategy to use when formatting numbers.
      *
@@ -1546,25 +1635,21 @@ class U_I18N_API NumberFormatterSettings {
      * The exact grouping widths will be chosen based on the locale.
      *
      * <p>
-     * Pass this method the return value of one of the factory methods on {@link Grouper}. For example:
+     * Pass this method an element from the {@link UGroupingStrategy} enum. For example:
      *
      * <pre>
-     * NumberFormatter::with().grouping(Grouper::min2())
+     * NumberFormatter::with().grouping(UNUM_GROUPING_MIN2)
      * </pre>
      *
-     * The default is to perform grouping without concern for the minimum grouping digits.
+     * The default is to perform grouping according to locale data; most locales, but not all locales,
+     * enable it by default.
      *
-     * @param grouper
+     * @param strategy
      *            The grouping strategy to use.
      * @return The fluent chain.
-     * @see Grouper
-     * @see Notation
-     * @internal
-     * @internal ICU 60: This API is technical preview.
+     * @draft ICU 61
      */
-    Derived grouping(const Grouper &grouper) const;
-
-#endif  /* U_HIDE_INTERNAL_API */
+    Derived grouping(const UGroupingStrategy &strategy) const;
 
     /**
      * Specifies the minimum and maximum number of digits to render before the decimal mark.
index 614cc96b20e511fd3c4c29926c2ab969ad1a1d69..9d4ffb7cef0d2afbc832b536f2785fad1610fe8b 100644 (file)
@@ -71,6 +71,8 @@ class NumberFormatterApiTest : public IntlTest {
     CurrencyUnit GBP;
     CurrencyUnit CZK;
     CurrencyUnit CAD;
+    CurrencyUnit ESP;
+    CurrencyUnit PTE;
 
     MeasureUnit METER;
     MeasureUnit DAY;
@@ -139,6 +141,7 @@ class ModifiersTest : public IntlTest {
 class PatternModifierTest : public IntlTest {
   public:
     void testBasic();
+    void testPatternWithNoPlaceholder();
     void testMutableEqualsImmutable();
 
     void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0);
index 1c1487372c33c4640c9d8aab75eaca38418884fc..62db705eac794598e4b211e1be6d174ee2923d5e 100644 (file)
@@ -22,6 +22,7 @@ NumberFormatterApiTest::NumberFormatterApiTest()
 NumberFormatterApiTest::NumberFormatterApiTest(UErrorCode &status)
               : USD(u"USD", status), GBP(u"GBP", status),
                 CZK(u"CZK", status), CAD(u"CAD", status),
+                ESP(u"ESP", status), PTE(u"PTE", status),
                 FRENCH_SYMBOLS(Locale::getFrench(), status),
                 SWISS_SYMBOLS(Locale("de-CH"), status),
                 MYANMAR_SYMBOLS(Locale("my"), status) {
@@ -349,6 +350,9 @@ void NumberFormatterApiTest::notationCompact() {
             Locale::getEnglish(),
             9990000,
             u"10M");
+
+    // NOTE: There is no API for compact custom data in C++
+    // and thus no "Compact Somali No Figure" test
 }
 
 void NumberFormatterApiTest::unitMeasure() {
@@ -608,6 +612,66 @@ void NumberFormatterApiTest::unitCurrency() {
             Locale::getEnglish(),
             -9876543.21,
             u"-£9,876,543.21");
+
+    // The full currency symbol is not shown in NARROW format.
+    // NOTE: This example is in the documentation.
+    assertFormatSingle(
+            u"Currency Difference between Narrow and Short (Narrow Version)",
+            NumberFormatter::with().unit(USD).unitWidth(UNUM_UNIT_WIDTH_NARROW),
+            Locale("en-CA"),
+            5.43,
+            u"$5.43");
+
+    assertFormatSingle(
+            u"Currency Difference between Narrow and Short (Short Version)",
+            NumberFormatter::with().unit(USD).unitWidth(UNUM_UNIT_WIDTH_SHORT),
+            Locale("en-CA"),
+            5.43,
+            u"US$5.43");
+
+    assertFormatSingle(
+            u"Currency-dependent format (Control)",
+            NumberFormatter::with().unit(USD).unitWidth(UNUM_UNIT_WIDTH_SHORT),
+            Locale("ca"),
+            444444.55,
+            u"444.444,55 USD");
+
+    assertFormatSingle(
+            u"Currency-dependent format (Test)",
+            NumberFormatter::with().unit(ESP).unitWidth(UNUM_UNIT_WIDTH_SHORT),
+            Locale("ca"),
+            444444.55,
+            u"₧ 444.445");
+
+    assertFormatSingle(
+            u"Currency-dependent symbols (Control)",
+            NumberFormatter::with().unit(USD).unitWidth(UNUM_UNIT_WIDTH_SHORT),
+            Locale("pt-PT"),
+            444444.55,
+            u"444 444,55 US$");
+
+    // NOTE: This is a bit of a hack on CLDR's part. They set the currency symbol to U+200B (zero-
+    // width space), and they set the decimal separator to the $ symbol.
+    assertFormatSingle(
+            u"Currency-dependent symbols (Test Short)",
+            NumberFormatter::with().unit(PTE).unitWidth(UNUM_UNIT_WIDTH_SHORT),
+            Locale("pt-PT"),
+            444444.55,
+            u"444,444$55 \u200B");
+
+    assertFormatSingle(
+            u"Currency-dependent symbols (Test Narrow)",
+            NumberFormatter::with().unit(PTE).unitWidth(UNUM_UNIT_WIDTH_NARROW),
+            Locale("pt-PT"),
+            444444.55,
+            u"444,444$55 PTE");
+
+    assertFormatSingle(
+            u"Currency-dependent symbols (Test ISO Code)",
+            NumberFormatter::with().unit(PTE).unitWidth(UNUM_UNIT_WIDTH_ISO_CODE),
+            Locale("pt-PT"),
+            444444.55,
+            u"444,444$55 PTE");
 }
 
 void NumberFormatterApiTest::unitPercent() {
@@ -826,6 +890,20 @@ void NumberFormatterApiTest::roundingFractionFigures() {
             u"0.09",
             u"0.01",
             u"0.00");
+
+    assertFormatSingle(
+            "FracSig with trailing zeros A",
+            NumberFormatter::with().rounding(Rounder::fixedFraction(2).withMinDigits(3)),
+            Locale::getEnglish(),
+            0.1,
+            u"0.10");
+
+    assertFormatSingle(
+            "FracSig with trailing zeros B",
+            NumberFormatter::with().rounding(Rounder::fixedFraction(2).withMinDigits(3)),
+            Locale::getEnglish(),
+            0.0999999,
+            u"0.10");
 }
 
 void NumberFormatterApiTest::roundingOther() {
@@ -950,7 +1028,7 @@ void NumberFormatterApiTest::roundingOther() {
 void NumberFormatterApiTest::grouping() {
     assertFormatDescendingBig(
             u"Western Grouping",
-            NumberFormatter::with().grouping(Grouper::defaults()),
+            NumberFormatter::with().grouping(UNUM_GROUPING_AUTO),
             Locale::getEnglish(),
             u"87,650,000",
             u"8,765,000",
@@ -964,7 +1042,7 @@ void NumberFormatterApiTest::grouping() {
 
     assertFormatDescendingBig(
             u"Indic Grouping",
-            NumberFormatter::with().grouping(Grouper::defaults()),
+            NumberFormatter::with().grouping(UNUM_GROUPING_AUTO),
             Locale("en-IN"),
             u"8,76,50,000",
             u"87,65,000",
@@ -978,7 +1056,7 @@ void NumberFormatterApiTest::grouping() {
 
     assertFormatDescendingBig(
             u"Western Grouping, Wide",
-            NumberFormatter::with().grouping(Grouper::minTwoDigits()),
+            NumberFormatter::with().grouping(UNUM_GROUPING_MIN2),
             Locale::getEnglish(),
             u"87,650,000",
             u"8,765,000",
@@ -992,7 +1070,7 @@ void NumberFormatterApiTest::grouping() {
 
     assertFormatDescendingBig(
             u"Indic Grouping, Wide",
-            NumberFormatter::with().grouping(Grouper::minTwoDigits()),
+            NumberFormatter::with().grouping(UNUM_GROUPING_MIN2),
             Locale("en-IN"),
             u"8,76,50,000",
             u"87,65,000",
@@ -1006,7 +1084,7 @@ void NumberFormatterApiTest::grouping() {
 
     assertFormatDescendingBig(
             u"No Grouping",
-            NumberFormatter::with().grouping(Grouper::none()),
+            NumberFormatter::with().grouping(UNUM_GROUPING_OFF),
             Locale("en-IN"),
             u"87650000",
             u"8765000",
@@ -1017,6 +1095,97 @@ void NumberFormatterApiTest::grouping() {
             u"87.65",
             u"8.765",
             u"0");
+
+    // NOTE: Hungarian is interesting because it has minimumGroupingDigits=4 in locale data
+    // If this test breaks due to data changes, find another locale that has minimumGroupingDigits.
+    assertFormatDescendingBig(
+            u"Hungarian Grouping",
+            NumberFormatter::with().grouping(UNUM_GROUPING_AUTO),
+            Locale("hu"),
+            u"87 650 000",
+            u"8 765 000",
+            u"876500",
+            u"87650",
+            u"8765",
+            u"876,5",
+            u"87,65",
+            u"8,765",
+            u"0");
+
+    assertFormatDescendingBig(
+            u"Hungarian Grouping, Min 2",
+            NumberFormatter::with().grouping(UNUM_GROUPING_MIN2),
+            Locale("hu"),
+            u"87 650 000",
+            u"8 765 000",
+            u"876500",
+            u"87650",
+            u"8765",
+            u"876,5",
+            u"87,65",
+            u"8,765",
+            u"0");
+
+    assertFormatDescendingBig(
+            u"Hungarian Grouping, Always",
+            NumberFormatter::with().grouping(UNUM_GROUPING_ON_ALIGNED),
+            Locale("hu"),
+            u"87 650 000",
+            u"8 765 000",
+            u"876 500",
+            u"87 650",
+            u"8 765",
+            u"876,5",
+            u"87,65",
+            u"8,765",
+            u"0");
+
+    // NOTE: Bulgarian is interesting because it has no grouping in the default currency format.
+    // If this test breaks due to data changes, find another locale that has no default grouping.
+    assertFormatDescendingBig(
+            u"Bulgarian Currency Grouping",
+            NumberFormatter::with().grouping(UNUM_GROUPING_AUTO).unit(USD),
+            Locale("bg"),
+            u"87650000,00 щ.д.",
+            u"8765000,00 щ.д.",
+            u"876500,00 щ.д.",
+            u"87650,00 щ.д.",
+            u"8765,00 щ.д.",
+            u"876,50 щ.д.",
+            u"87,65 щ.д.",
+            u"8,76 щ.д.",
+            u"0,00 щ.д.");
+
+    assertFormatDescendingBig(
+            u"Bulgarian Currency Grouping, Always",
+            NumberFormatter::with().grouping(UNUM_GROUPING_ON_ALIGNED).unit(USD),
+            Locale("bg"),
+            u"87 650 000,00 щ.д.",
+            u"8 765 000,00 щ.д.",
+            u"876 500,00 щ.д.",
+            u"87 650,00 щ.д.",
+            u"8 765,00 щ.д.",
+            u"876,50 щ.д.",
+            u"87,65 щ.д.",
+            u"8,76 щ.д.",
+            u"0,00 щ.д.");
+
+    // TODO: Enable this test when macro-setter is available in C++
+    // MacroProps macros;
+    // macros.grouping = Grouper(4, 1, 3);
+    // assertFormatDescendingBig(
+    //         u"Custom Grouping via Internal API",
+    //         NumberFormatter::with().macros(macros),
+    //         Locale::getEnglish(),
+    //         u"8,7,6,5,0000",
+    //         u"8,7,6,5000",
+    //         u"876500",
+    //         u"87650",
+    //         u"8765",
+    //         u"876.5",
+    //         u"87.65",
+    //         u"8.765",
+    //         u"0");
 }
 
 void NumberFormatterApiTest::padding() {
index 279df757f69405bf3c42c8d084f4751f9f5ffa67..bebb3f8b2b69116b1d3bf1ce732a0b275c3c2aa1 100644 (file)
@@ -38,13 +38,13 @@ void ModifiersTest::testConstantMultiFieldModifier() {
     UErrorCode status = U_ZERO_ERROR;
     NumberStringBuilder prefix;
     NumberStringBuilder suffix;
-    ConstantMultiFieldModifier mod1(prefix, suffix, true);
+    ConstantMultiFieldModifier mod1(prefix, suffix, false, true);
     assertModifierEquals(mod1, 0, true, u"|", u"n", status);
     assertSuccess("Spot 1", status);
 
     prefix.append(u"a📻", UNUM_PERCENT_FIELD, status);
     suffix.append(u"b", UNUM_CURRENCY_FIELD, status);
-    ConstantMultiFieldModifier mod2(prefix, suffix, true);
+    ConstantMultiFieldModifier mod2(prefix, suffix, false, true);
     assertModifierEquals(mod2, 3, true, u"a📻|b", u"%%%n$", status);
     assertSuccess("Spot 2", status);
 
@@ -105,14 +105,14 @@ void ModifiersTest::testCurrencySpacingEnabledModifier() {
 
     NumberStringBuilder prefix;
     NumberStringBuilder suffix;
-    CurrencySpacingEnabledModifier mod1(prefix, suffix, true, symbols, status);
+    CurrencySpacingEnabledModifier mod1(prefix, suffix, false, true, symbols, status);
     assertSuccess("Spot 2", status);
     assertModifierEquals(mod1, 0, true, u"|", u"n", status);
     assertSuccess("Spot 3", status);
 
     prefix.append(u"USD", UNUM_CURRENCY_FIELD, status);
     assertSuccess("Spot 4", status);
-    CurrencySpacingEnabledModifier mod2(prefix, suffix, true, symbols, status);
+    CurrencySpacingEnabledModifier mod2(prefix, suffix, false, true, symbols, status);
     assertSuccess("Spot 5", status);
     assertModifierEquals(mod2, 3, true, u"USD|", u"$$$n", status);
     assertSuccess("Spot 6", status);
@@ -138,7 +138,7 @@ void ModifiersTest::testCurrencySpacingEnabledModifier() {
     symbols.setPatternForCurrencySpacing(UNUM_CURRENCY_SURROUNDING_MATCH, true, u"[|]");
     suffix.append("XYZ", UNUM_CURRENCY_FIELD, status);
     assertSuccess("Spot 11", status);
-    CurrencySpacingEnabledModifier mod3(prefix, suffix, true, symbols, status);
+    CurrencySpacingEnabledModifier mod3(prefix, suffix, false, true, symbols, status);
     assertSuccess("Spot 12", status);
     assertModifierEquals(mod3, 3, true, u"USD|\u00A0XYZ", u"$$$nn$$$", status);
     assertSuccess("Spot 13", status);
index 2d0d9d75cdfaf9d88599aa4b699c0eab630053f5..07f840576bf66ef66cbdebc519dcf2767175fd13 100644 (file)
@@ -14,6 +14,7 @@ void PatternModifierTest::runIndexedTest(int32_t index, UBool exec, const char *
     }
     TESTCASE_AUTO_BEGIN;
         TESTCASE_AUTO(testBasic);
+        TESTCASE_AUTO(testPatternWithNoPlaceholder);
         TESTCASE_AUTO(testMutableEqualsImmutable);
     TESTCASE_AUTO_END;
 }
@@ -78,6 +79,42 @@ void PatternModifierTest::testBasic() {
     assertSuccess("Spot 5", status);
 }
 
+void PatternModifierTest::testPatternWithNoPlaceholder() {
+    UErrorCode status = U_ZERO_ERROR;
+    MutablePatternModifier mod(false);
+    ParsedPatternInfo patternInfo;
+    PatternParser::parseToPatternInfo(u"abc", patternInfo, status);
+    assertSuccess("Spot 1", status);
+    mod.setPatternInfo(&patternInfo);
+    mod.setPatternAttributes(UNUM_SIGN_AUTO, false);
+    DecimalFormatSymbols symbols(Locale::getEnglish(), status);
+    CurrencyUnit currency(u"USD", status);
+    assertSuccess("Spot 2", status);
+    mod.setSymbols(&symbols, currency, UNUM_UNIT_WIDTH_SHORT, nullptr);
+    mod.setNumberProperties(1, StandardPlural::Form::COUNT);
+
+    // Unsafe Code Path
+    NumberStringBuilder nsb;
+    nsb.append(u"x123y", UNUM_FIELD_COUNT, status);
+    assertSuccess("Spot 3", status);
+    mod.apply(nsb, 1, 4, status);
+    assertSuccess("Spot 4", status);
+    assertEquals("Unsafe Path", u"xabcy", nsb.toUnicodeString());
+
+    // Safe Code Path
+    nsb.clear();
+    nsb.append(u"x123y", UNUM_FIELD_COUNT, status);
+    assertSuccess("Spot 5", status);
+    MicroProps micros;
+    LocalPointer<ImmutablePatternModifier> imod(mod.createImmutable(status));
+    assertSuccess("Spot 6", status);
+    DecimalQuantity quantity;
+    imod->applyToMicros(micros, quantity);
+    micros.modMiddle->apply(nsb, 1, 4, status);
+    assertSuccess("Spot 7", status);
+    assertEquals("Safe Path", u"xabcy", nsb.toUnicodeString());
+}
+
 void PatternModifierTest::testMutableEqualsImmutable() {
     UErrorCode status = U_ZERO_ERROR;
     MutablePatternModifier mod(false);
index bfa714d228f0701d4a24b67791627003419ef4a8..c65859b873c7ccdaa939ee246f5ad727db571d1b 100644 (file)
@@ -42,7 +42,7 @@
 #include "unicode/msgfmt.h"
 
 #if (U_PLATFORM == U_PF_AIX) || (U_PLATFORM == U_PF_OS390)
-// These should not be macros. If they are, 
+// These should not be macros. If they are,
 // replace them with std::isnan and std::isinf
 #if defined(isnan)
 #undef isnan
@@ -581,8 +581,8 @@ void NumberFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &n
   TESTCASE_AUTO(TestFieldPositionIterator);
   TESTCASE_AUTO(TestDecimal);
   TESTCASE_AUTO(TestCurrencyFractionDigits);
-  TESTCASE_AUTO(TestExponentParse); 
-  TESTCASE_AUTO(TestExplicitParents); 
+  TESTCASE_AUTO(TestExponentParse);
+  TESTCASE_AUTO(TestExplicitParents);
   TESTCASE_AUTO(TestLenientParse);
   TESTCASE_AUTO(TestAvailableNumberingSystems);
   TESTCASE_AUTO(TestRoundingPattern);
@@ -623,6 +623,7 @@ void NumberFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &n
   TESTCASE_AUTO(Test11649_toPatternWithMultiCurrency);
   TESTCASE_AUTO(Test13327_numberingSystemBufferOverflow);
   TESTCASE_AUTO(Test13391_chakmaParsing);
+  TESTCASE_AUTO(Test11035_FormatCurrencyAmount);
   TESTCASE_AUTO_END;
 }
 
@@ -1458,19 +1459,19 @@ NumberFormatTest::TestLenientParse(void)
 
     Locale en_US("en_US");
     Locale sv_SE("sv_SE");
-    
+
     NumberFormat *mFormat = NumberFormat::createInstance(sv_SE, UNUM_DECIMAL, status);
-    
+
     if (mFormat == NULL || U_FAILURE(status)) {
         dataerrln("Unable to create NumberFormat (sv_SE, UNUM_DECIMAL) - %s", u_errorName(status));
     } else {
         mFormat->setLenient(TRUE);
         for (int32_t t = 0; t < UPRV_LENGTHOF(lenientMinusTestCases); t += 1) {
             UnicodeString testCase = ctou(lenientMinusTestCases[t]);
-            
+
             mFormat->parse(testCase, n, status);
             logln((UnicodeString)"parse(" + testCase + ") = " + n.getLong());
-            
+
             if (U_FAILURE(status) || n.getType() != Formattable::kLong || n.getLong() != -5) {
                 errln((UnicodeString)"Lenient parse failed for \"" + (UnicodeString) lenientMinusTestCases[t] + (UnicodeString) "\"");
                 status = U_ZERO_ERROR;
@@ -1478,19 +1479,19 @@ NumberFormatTest::TestLenientParse(void)
         }
         delete mFormat;
     }
-    
+
     mFormat = NumberFormat::createInstance(en_US, UNUM_DECIMAL, status);
-    
+
     if (mFormat == NULL || U_FAILURE(status)) {
         dataerrln("Unable to create NumberFormat (en_US, UNUM_DECIMAL) - %s", u_errorName(status));
     } else {
         mFormat->setLenient(TRUE);
         for (int32_t t = 0; t < UPRV_LENGTHOF(lenientMinusTestCases); t += 1) {
             UnicodeString testCase = ctou(lenientMinusTestCases[t]);
-            
+
             mFormat->parse(testCase, n, status);
             logln((UnicodeString)"parse(" + testCase + ") = " + n.getLong());
-            
+
             if (U_FAILURE(status) || n.getType() != Formattable::kLong || n.getLong() != -5) {
                 errln((UnicodeString)"Lenient parse failed for \"" + (UnicodeString) lenientMinusTestCases[t] + (UnicodeString) "\"");
                 status = U_ZERO_ERROR;
@@ -1498,7 +1499,7 @@ NumberFormatTest::TestLenientParse(void)
         }
         delete mFormat;
     }
-    
+
     NumberFormat *cFormat = NumberFormat::createInstance(en_US, UNUM_CURRENCY, status);
 
     if (cFormat == NULL || U_FAILURE(status)) {
@@ -1572,10 +1573,10 @@ NumberFormatTest::TestLenientParse(void)
    // Test cases that should fail with a strict parse and pass with a
    // lenient parse.
    NumberFormat *nFormat = NumberFormat::createInstance(en_US, status);
-    
+
    if (nFormat == NULL || U_FAILURE(status)) {
        dataerrln("Unable to create NumberFormat (en_US) - %s", u_errorName(status));
-   } else { 
+   } else {
        // first, make sure that they fail with a strict parse
        for (int32_t t = 0; t < UPRV_LENGTHOF(strictFailureTestCases); t += 1) {
               UnicodeString testCase = ctou(strictFailureTestCases[t]);
@@ -2311,30 +2312,54 @@ void NumberFormatTest::TestCurrencyNames(void) {
     const UBool possibleDataError = TRUE;
     // Warning: HARD-CODED LOCALE DATA in this test.  If it fails, CHECK
     // THE LOCALE DATA before diving into the code.
-    assertEquals("USD.getName(SYMBOL_NAME)",
+    assertEquals("USD.getName(SYMBOL_NAME, en)",
                  UnicodeString("$"),
                  UnicodeString(ucurr_getName(USD, "en",
                                              UCURR_SYMBOL_NAME,
                                              &isChoiceFormat, &len, &ec)),
                                              possibleDataError);
-    assertEquals("USD.getName(LONG_NAME)",
+    assertEquals("USD.getName(NARROW_SYMBOL_NAME, en)",
+                 UnicodeString("$"),
+                 UnicodeString(ucurr_getName(USD, "en",
+                                             UCURR_NARROW_SYMBOL_NAME,
+                                             &isChoiceFormat, &len, &ec)),
+                                             possibleDataError);
+    assertEquals("USD.getName(LONG_NAME, en)",
                  UnicodeString("US Dollar"),
                  UnicodeString(ucurr_getName(USD, "en",
                                              UCURR_LONG_NAME,
                                              &isChoiceFormat, &len, &ec)),
                                              possibleDataError);
-    assertEquals("CAD.getName(SYMBOL_NAME)",
+    assertEquals("CAD.getName(SYMBOL_NAME, en)",
                  UnicodeString("CA$"),
                  UnicodeString(ucurr_getName(CAD, "en",
                                              UCURR_SYMBOL_NAME,
                                              &isChoiceFormat, &len, &ec)),
                                              possibleDataError);
-    assertEquals("CAD.getName(SYMBOL_NAME)",
+    assertEquals("CAD.getName(NARROW_SYMBOL_NAME, en)",
+                 UnicodeString("$"),
+                 UnicodeString(ucurr_getName(CAD, "en",
+                                             UCURR_NARROW_SYMBOL_NAME,
+                                             &isChoiceFormat, &len, &ec)),
+                                             possibleDataError);
+    assertEquals("CAD.getName(SYMBOL_NAME, en_CA)",
                  UnicodeString("$"),
                  UnicodeString(ucurr_getName(CAD, "en_CA",
                                              UCURR_SYMBOL_NAME,
                                              &isChoiceFormat, &len, &ec)),
                                              possibleDataError);
+    assertEquals("USD.getName(SYMBOL_NAME, en_CA)",
+                 UnicodeString("US$"),
+                 UnicodeString(ucurr_getName(USD, "en_CA",
+                                             UCURR_SYMBOL_NAME,
+                                             &isChoiceFormat, &len, &ec)),
+                                             possibleDataError);
+    assertEquals("USD.getName(NARROW_SYMBOL_NAME, en_CA)",
+                 UnicodeString("$"),
+                 UnicodeString(ucurr_getName(USD, "en_CA",
+                                             UCURR_NARROW_SYMBOL_NAME,
+                                             &isChoiceFormat, &len, &ec)),
+                                             possibleDataError);
     assertEquals("USD.getName(SYMBOL_NAME) in en_NZ",
                  UnicodeString("US$"),
                  UnicodeString(ucurr_getName(USD, "en_NZ",
@@ -2347,6 +2372,18 @@ void NumberFormatTest::TestCurrencyNames(void) {
                                              UCURR_SYMBOL_NAME,
                                              &isChoiceFormat, &len, &ec)),
                                              possibleDataError);
+    assertEquals("USX.getName(SYMBOL_NAME)",
+                 UnicodeString("USX"),
+                 UnicodeString(ucurr_getName(USX, "en_US",
+                                             UCURR_SYMBOL_NAME,
+                                             &isChoiceFormat, &len, &ec)),
+                                             possibleDataError);
+    assertEquals("USX.getName(NARROW_SYMBOL_NAME)",
+                 UnicodeString("USX"),
+                 UnicodeString(ucurr_getName(USX, "en_US",
+                                             UCURR_NARROW_SYMBOL_NAME,
+                                             &isChoiceFormat, &len, &ec)),
+                                             possibleDataError);
     assertEquals("USX.getName(LONG_NAME)",
                  UnicodeString("USX"),
                  UnicodeString(ucurr_getName(USX, "en_US",
@@ -2497,7 +2534,7 @@ void NumberFormatTest::TestSymbolsWithBadLocale(void) {
         UnicodeString intlCurrencySymbol((UChar)0xa4);
 
         intlCurrencySymbol.append((UChar)0xa4);
-        
+
         logln("Current locale is %s", Locale::getDefault().getName());
         Locale::setDefault(locBad, status);
         logln("Current locale is %s", Locale::getDefault().getName());
@@ -3208,7 +3245,7 @@ void NumberFormatTest::TestCompatibleCurrencies() {
     expectParseCurrency(*fmtJP, JPY, 1235,  "\\u00A51,235");
     logln("%s:%d - testing parse of fullwidth yen sign in JP\n", __FILE__, __LINE__);
     expectParseCurrency(*fmtJP, JPY, 1235,  "\\uFFE51,235");
-    
+
     // more..
 */
 }
@@ -3228,7 +3265,7 @@ void NumberFormatTest::expectParseCurrency(const NumberFormat &fmt, const UChar*
             fmt.getLocale(ULOC_ACTUAL_LOCALE, status).getBaseName(),
             text);
     u_austrcpy(theInfo+uprv_strlen(theInfo), currency);
-    
+
     char theOperation[100];
 
     uprv_strcpy(theOperation, theInfo);
@@ -3239,7 +3276,7 @@ void NumberFormatTest::expectParseCurrency(const NumberFormat &fmt, const UChar*
     uprv_strcat(theOperation, ", check currency:");
     assertEquals(theOperation, currency, currencyAmount->getISOCurrency());
 }
-  
+
 
 void NumberFormatTest::TestJB3832(){
     const char* localeID = "pt_PT@currency=PTE";
@@ -3672,7 +3709,7 @@ void NumberFormatTest::TestNumberingSystems() {
         NumberFormat *fmt = (NumberFormat *) origFmt->clone();
         delete origFmt;
 
-        
+
         if (item->isRBNF) {
             expect3(*fmt,item->value,CharsToUnicodeString(item->expectedResult));
         } else {
@@ -4044,7 +4081,7 @@ for (;;) {
         UErrorCode status = U_ZERO_ERROR;
         NumberFormat* numFmt = NumberFormat::createInstance(locale, k, status);
         logln("#%d NumberFormat(%s, %s) Currency=%s\n",
-              i, localeString, currencyStyleNames[kIndex], 
+              i, localeString, currencyStyleNames[kIndex],
               currencyISOCode);
 
         if (U_FAILURE(status)) {
@@ -6804,7 +6841,7 @@ void NumberFormatTest::TestFormatAttributes() {
   DecimalFormat *decFmt = (DecimalFormat *) NumberFormat::createInstance(locale, UNUM_CURRENCY, status);
     if (failure(status, "NumberFormat::createInstance", TRUE)) return;
   double val = 12345.67;
-  
+
   {
     int32_t expected[] = {
       UNUM_CURRENCY_FIELD, 0, 1,
@@ -6889,7 +6926,7 @@ const char* attrString(int32_t attrId) {
 
 //
 //   Test formatting & parsing of big decimals.
-//      API test, not a comprehensive test. 
+//      API test, not a comprehensive test.
 //      See DecimalFormatTest/DataDrivenTests
 //
 #define ASSERT_SUCCESS(status) {if (U_FAILURE(status)) errln("file %s, line %d: status: %s", \
@@ -7022,7 +7059,7 @@ void NumberFormatTest::TestDecimal() {
             delete fmtr;
         }
     }
-    
+
 #if U_PLATFORM != U_PF_CYGWIN || defined(CYGWINMSVC)
     /*
      * This test fails on Cygwin (1.7.16) using GCC because of a rounding issue with strtod().
@@ -7074,38 +7111,38 @@ void NumberFormatTest::TestCurrencyFractionDigits() {
     }
 }
 
-void NumberFormatTest::TestExponentParse() { 
-    UErrorCode status = U_ZERO_ERROR; 
-    Formattable result; 
-    ParsePosition parsePos(0); 
-    // set the exponent symbol 
-    status = U_ZERO_ERROR; 
-    DecimalFormatSymbols *symbols = new DecimalFormatSymbols(Locale::getDefault(), status); 
-    if(U_FAILURE(status)) { 
-        dataerrln((UnicodeString)"ERROR: Could not create DecimalFormatSymbols (Default)"); 
-        return; 
-    } 
-    
-    // create format instance 
-    status = U_ZERO_ERROR; 
-    DecimalFormat fmt("#####", symbols, status); 
-    if(U_FAILURE(status)) { 
-        errln((UnicodeString)"ERROR: Could not create DecimalFormat (pattern, symbols*)"); 
-    } 
-    
-    // parse the text 
-    fmt.parse("5.06e-27", result, parsePos); 
-    if(result.getType() != Formattable::kDouble &&  
-       result.getDouble() != 5.06E-27 && 
-       parsePos.getIndex() != 8 
-       ) 
-    { 
-        errln("ERROR: parse failed - expected 5.06E-27, 8  - returned %d, %i", 
-              result.getDouble(), parsePos.getIndex()); 
-    } 
-} 
+void NumberFormatTest::TestExponentParse() {
+
+    UErrorCode status = U_ZERO_ERROR;
+    Formattable result;
+    ParsePosition parsePos(0);
+
+    // set the exponent symbol
+    status = U_ZERO_ERROR;
+    DecimalFormatSymbols *symbols = new DecimalFormatSymbols(Locale::getDefault(), status);
+    if(U_FAILURE(status)) {
+        dataerrln((UnicodeString)"ERROR: Could not create DecimalFormatSymbols (Default)");
+        return;
+    }
+
+    // create format instance
+    status = U_ZERO_ERROR;
+    DecimalFormat fmt("#####", symbols, status);
+    if(U_FAILURE(status)) {
+        errln((UnicodeString)"ERROR: Could not create DecimalFormat (pattern, symbols*)");
+    }
+
+    // parse the text
+    fmt.parse("5.06e-27", result, parsePos);
+    if(result.getType() != Formattable::kDouble &&
+       result.getDouble() != 5.06E-27 &&
+       parsePos.getIndex() != 8
+       )
+    {
+        errln("ERROR: parse failed - expected 5.06E-27, 8  - returned %d, %i",
+              result.getDouble(), parsePos.getIndex());
+    }
+}
 
 void NumberFormatTest::TestExplicitParents() {
 
@@ -7188,13 +7225,13 @@ NumberFormatTest::Test9087(void)
 {
     U_STRING_DECL(pattern,"#",1);
     U_STRING_INIT(pattern,"#",1);
-    
+
     U_STRING_DECL(infstr,"INF",3);
     U_STRING_INIT(infstr,"INF",3);
 
     U_STRING_DECL(nanstr,"NAN",3);
     U_STRING_INIT(nanstr,"NAN",3);
-    
+
     UChar outputbuf[50] = {0};
     UErrorCode status = U_ZERO_ERROR;
     UNumberFormat* fmt = unum_open(UNUM_PATTERN_DECIMAL,pattern,1,NULL,NULL,&status);
@@ -7216,7 +7253,7 @@ NumberFormatTest::Test9087(void)
 
     UFieldPosition position = { 0, 0, 0};
     unum_formatDouble(fmt,inf,outputbuf,50,&position,&status);
-    
+
     if ( u_strcmp(infstr, outputbuf)) {
         errln((UnicodeString)"FAIL: unexpected result for infinity - expected " + infstr + " got " + outputbuf);
     }
@@ -7237,7 +7274,7 @@ void NumberFormatTest::TestFormatFastpaths() {
     }
 #else
     infoln("NOTE: UCONFIG_FORMAT_FASTPATHS not set, test skipped.");
-#endif  
+#endif
 
     // get some additional case
     {
@@ -7500,9 +7537,9 @@ UBool NumberFormatTest::testFormattableAsUFormattable(const char *file, int line
       UErrorCode int64ConversionU = U_ZERO_ERROR;
       int64_t r = ufmt_getInt64(u, &int64ConversionU);
 
-      if( (l==r) 
+      if( (l==r)
           && ( uType != UFMT_INT64 ) // int64 better not overflow
-          && (U_INVALID_FORMAT_ERROR==int64ConversionU) 
+          && (U_INVALID_FORMAT_ERROR==int64ConversionU)
           && (U_INVALID_FORMAT_ERROR==int64ConversionF) ) {
         logln("%s:%d: OK: 64 bit overflow", file, line);
       } else {
@@ -7627,7 +7664,7 @@ void NumberFormatTest::TestSignificantDigits(void) {
     numberFormat->setMinimumSignificantDigits(3);
     numberFormat->setMaximumSignificantDigits(5);
     numberFormat->setGroupingUsed(false);
-    
+
     UnicodeString result;
     UnicodeString expectedResult;
     for (unsigned int i = 0; i < UPRV_LENGTHOF(input); ++i) {
@@ -7649,7 +7686,7 @@ void NumberFormatTest::TestShowZero() {
 
     numberFormat->setSignificantDigitsUsed(TRUE);
     numberFormat->setMaximumSignificantDigits(3);
-    
+
     UnicodeString result;
     numberFormat->format(0.0, result);
     if (result != "0") {
@@ -7666,7 +7703,7 @@ void NumberFormatTest::TestBug9936() {
         dataerrln("File %s, Line %d: status = %s.\n", __FILE__, __LINE__, u_errorName(status));
         return;
     }
-        
+
     if (numberFormat->areSignificantDigitsUsed() == TRUE) {
         errln("File %s, Line %d: areSignificantDigitsUsed() was TRUE, expected FALSE.\n", __FILE__, __LINE__);
     }
@@ -7690,7 +7727,7 @@ void NumberFormatTest::TestBug9936() {
     if (numberFormat->areSignificantDigitsUsed() == FALSE) {
         errln("File %s, Line %d: areSignificantDigitsUsed() was FALSE, expected TRUE.\n", __FILE__, __LINE__);
     }
+
 }
 
 void NumberFormatTest::TestParseNegativeWithFaLocale() {
@@ -7781,7 +7818,7 @@ void NumberFormatTest::TestParseSignsAndMarks() {
         { "en@numbers=arabext", FALSE,  CharsToUnicodeString("\\u200E-\\u200E\\u06F6\\u06F7"),          -67 },
         { "en@numbers=arabext", TRUE,   CharsToUnicodeString("\\u200E-\\u200E\\u06F6\\u06F7"),          -67 },
         { "en@numbers=arabext", TRUE,   CharsToUnicodeString("\\u200E-\\u200E \\u06F6\\u06F7"),         -67 },
+
         { "he",                 FALSE,  CharsToUnicodeString("12"),                                      12 },
         { "he",                 TRUE,   CharsToUnicodeString("12"),                                      12 },
         { "he",                 FALSE,  CharsToUnicodeString("-23"),                                    -23 },
@@ -7910,7 +7947,7 @@ void NumberFormatTest::Test10468ApplyPattern() {
     // explicit padding char is specified in the new pattern.
     fmt.applyPattern("AA#,##0.00ZZ", status);
 
-    // Oops this still prints 'a' even though we changed the pattern. 
+    // Oops this still prints 'a' even though we changed the pattern.
     if (fmt.getPadCharacterString() != UnicodeString(" ")) {
         errln("applyPattern did not clear padding character.");
     }
@@ -7923,7 +7960,7 @@ void NumberFormatTest::TestRoundingScientific10542() {
         errcheckln(status, "DecimalFormat constructor failed - %s", u_errorName(status));
         return;
     }
-        
+
     DecimalFormat::ERoundingMode roundingModes[] = {
             DecimalFormat::kRoundCeiling,
             DecimalFormat::kRoundDown,
@@ -7940,7 +7977,7 @@ void NumberFormatTest::TestRoundingScientific10542() {
             "Round half even",
             "Round half up",
             "Round up"};
-        
+
     {
         double values[] = {-0.003006, -0.003005, -0.003004, 0.003014, 0.003015, 0.003016};
         // The order of these expected values correspond to the order of roundingModes and the order of values.
@@ -8233,7 +8270,7 @@ void NumberFormatTest::TestCurrencyUsage() {
             assertEquals("Test Currency Usage 3", UnicodeString("CA$123.57"), original_rounding);
             fmt->setCurrencyUsage(UCURR_USAGE_CASH, &status);
         }else{
-            fmt = (DecimalFormat *) NumberFormat::createInstance(enUS_CAD, UNUM_CASH_CURRENCY, status); 
+            fmt = (DecimalFormat *) NumberFormat::createInstance(enUS_CAD, UNUM_CASH_CURRENCY, status);
             if (assertSuccess("en_US@currency=CAD/CASH", status, TRUE) == FALSE) {
                 continue;
             }
@@ -8634,7 +8671,7 @@ void NumberFormatTest::Test10727_RoundingZero() {
    DigitList d;
    d.set(-0.0);
    assertFalse("", d.isPositive());
-   d.round(3); 
+   d.round(3);
    assertFalse("", d.isPositive());
 }
 
@@ -8650,7 +8687,7 @@ void NumberFormatTest::Test11376_getAndSetPositivePrefix() {
         DecimalFormat *dfmt = (DecimalFormat *) fmt.getAlias();
         dfmt->setCurrency(USD);
         UnicodeString result;
-    
+
         // This line should be a no-op. I am setting the positive prefix
         // to be the same thing it was before.
         dfmt->setPositivePrefix(dfmt->getPositivePrefix(result));
@@ -8774,7 +8811,7 @@ void NumberFormatTest::Test11649_toPatternWithMultiCurrency() {
     static UChar USD[] = {0x55, 0x53, 0x44, 0x0};
     fmt.setCurrency(USD);
     UnicodeString appendTo;
-    
+
     assertEquals("", "US dollars 12.34", fmt.format(12.34, appendTo));
 
     UnicodeString topattern;
@@ -8784,7 +8821,7 @@ void NumberFormatTest::Test11649_toPatternWithMultiCurrency() {
         return;
     }
     fmt2.setCurrency(USD);
-    
+
     appendTo.remove();
     assertEquals("", "US dollars 12.34", fmt2.format(12.34, appendTo));
 }
@@ -8885,4 +8922,28 @@ void NumberFormatTest::checkExceptionIssue11735() {
     assertEquals("Issue11735 ppos", 0, ppos.getIndex());
 }
 
+void NumberFormatTest::Test11035_FormatCurrencyAmount() {
+    UErrorCode status;
+    double amount = 12345.67;
+    const char16_t* expected = u"12,345$67 ​";
+
+    // Test two ways to set a currency via API
+
+    Locale loc1 = Locale("pt_PT");
+    NumberFormat* fmt1 = NumberFormat::createCurrencyInstance(loc1, status);
+    fmt1->setCurrency(u"PTE", status);
+    UnicodeString actualSetCurrency;
+    fmt1->format(amount, actualSetCurrency);
+
+    Locale loc2 = Locale("pt_PT@currency=PTE");
+    NumberFormat* fmt2 = NumberFormat::createCurrencyInstance(loc2, status);
+    UnicodeString actualLocaleString;
+    fmt2->format(amount, actualLocaleString);
+
+    // TODO: The following test fill fail until DecimalFormat wraps NumberFormatter.
+    if (!logKnownIssue("13574")) {
+        assertEquals("Custom Currency Pattern, Set Currency", expected, actualSetCurrency);
+    }
+}
+
 #endif /* #if !UCONFIG_NO_FORMATTING */
index 8477fcbcdb2851f5e8c93be68063c6100223e686..05d05c86cfb52e8c4f241d1c8c77c41bf2a802ab 100644 (file)
@@ -219,6 +219,7 @@ class NumberFormatTest: public CalendarTimeZoneTest {
     void Test13391_chakmaParsing();
 
     void checkExceptionIssue11735();
+    void Test11035_FormatCurrencyAmount();
 
  private:
     UBool testFormattableAsUFormattable(const char *file, int line, Formattable &f);
index b656a8b725b4c35f6d67895bb70f8067bca8576e..df4272574fa29dab616fb6988fa9dfe618f8d8de 100644 (file)
@@ -29,7 +29,6 @@ public class CurrencyData {
         public abstract Map<String, String> getUnitPatterns();
         public abstract CurrencyFormatInfo getFormatInfo(String isoCode);
         public abstract CurrencySpacingInfo getSpacingInfo();
-        public abstract String getNarrowSymbol(String isoCode);
     }
 
     public static final class CurrencyFormatInfo {
index 01d137fceef383d638c9d8466f2208357c9c6ba4..e7519d5036fd0eef56263d258f0b9e02f8928624 100644 (file)
@@ -31,4 +31,11 @@ public interface AffixPatternProvider {
     public boolean negativeHasMinusSign();
 
     public boolean containsSymbolType(int type);
+
+    /**
+     * True if the pattern has a number placeholder like "0" or "#,##0.00"; false if the pattern does not
+     * have one. This is used in cases like compact notation, where the pattern replaces the entire
+     * number instead of rendering the number.
+     */
+    public boolean hasBody();
 }
index 557d0b80f83bbbe9fd33ac010a37108d55e3e1bd..cdd129c128f5c161884dbfef4428a03cac004bd9 100644 (file)
@@ -17,24 +17,29 @@ public class ConstantMultiFieldModifier implements Modifier {
     protected final char[] suffixChars;
     protected final Field[] prefixFields;
     protected final Field[] suffixFields;
+    private final boolean overwrite;
     private final boolean strong;
 
     public ConstantMultiFieldModifier(
             NumberStringBuilder prefix,
             NumberStringBuilder suffix,
+            boolean overwrite,
             boolean strong) {
         prefixChars = prefix.toCharArray();
         suffixChars = suffix.toCharArray();
         prefixFields = prefix.toFieldArray();
         suffixFields = suffix.toFieldArray();
+        this.overwrite = overwrite;
         this.strong = strong;
     }
 
     @Override
     public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
-        // Insert the suffix first since inserting the prefix will change the rightIndex
-        int length = output.insert(rightIndex, suffixChars, suffixFields);
-        length += output.insert(leftIndex, prefixChars, prefixFields);
+        int length = output.insert(leftIndex, prefixChars, prefixFields);
+        if (overwrite) {
+            length += output.splice(leftIndex + length, rightIndex + length, "", 0, 0, null);
+        }
+        length += output.insert(rightIndex + length, suffixChars, suffixFields);
         return length;
     }
 
index ada6acf025efdf8725dad567805d9b4891ad1cc3..2540899cda7586cf2489ef4754c55c81d2bdd4f5 100644 (file)
@@ -59,4 +59,9 @@ public class CurrencyPluralInfoAffixProvider implements AffixPatternProvider {
     public boolean containsSymbolType(int type) {
         return affixesByPlural[StandardPlural.OTHER.ordinal()].containsSymbolType(type);
     }
+
+    @Override
+    public boolean hasBody() {
+        return affixesByPlural[StandardPlural.OTHER.ordinal()].hasBody();
+    }
 }
\ No newline at end of file
index 9fb35f7546a4ad9ca7610a2282cb8fb6a65c3397..2cf875884e531b4ed202617760ecca56ad6d09c0 100644 (file)
@@ -30,9 +30,10 @@ public class CurrencySpacingEnabledModifier extends ConstantMultiFieldModifier {
     public CurrencySpacingEnabledModifier(
             NumberStringBuilder prefix,
             NumberStringBuilder suffix,
+            boolean overwrite,
             boolean strong,
             DecimalFormatSymbols symbols) {
-        super(prefix, suffix, strong);
+        super(prefix, suffix, overwrite, strong);
 
         // Check for currency spacing. Do not build the UnicodeSets unless there is
         // a currency code point at a boundary.
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Grouper.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Grouper.java
new file mode 100644 (file)
index 0000000..fee5564
--- /dev/null
@@ -0,0 +1,164 @@
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package com.ibm.icu.impl.number;
+
+import com.ibm.icu.impl.ICUData;
+import com.ibm.icu.impl.ICUResourceBundle;
+import com.ibm.icu.impl.number.PatternStringParser.ParsedPatternInfo;
+import com.ibm.icu.number.NumberFormatter.GroupingStrategy;
+import com.ibm.icu.util.ULocale;
+import com.ibm.icu.util.UResourceBundle;
+
+/**
+ * A full options object for grouping sizes.
+ */
+public class Grouper {
+
+    private static final Grouper GROUPER_NEVER = new Grouper((short) -1, (short) -1, (short) -2);
+    private static final Grouper GROUPER_MIN2 = new Grouper((short) -2, (short) -2, (short) -3);
+    private static final Grouper GROUPER_AUTO = new Grouper((short) -2, (short) -2, (short) -2);
+    private static final Grouper GROUPER_ON_ALIGNED = new Grouper((short) -4, (short) -4, (short) 1);
+
+    private static final Grouper GROUPER_WESTERN = new Grouper((short) 3, (short) 3, (short) 1);
+    private static final Grouper GROUPER_INDIC = new Grouper((short) 3, (short) 2, (short) 1);
+    private static final Grouper GROUPER_WESTERN_MIN2 = new Grouper((short) 3, (short) 3, (short) 2);
+    private static final Grouper GROUPER_INDIC_MIN2 = new Grouper((short) 3, (short) 2, (short) 2);
+
+    /**
+     * Convert from the GroupingStrategy enum to a Grouper object.
+     */
+    public static Grouper forStrategy(GroupingStrategy grouping) {
+        switch (grouping) {
+        case OFF:
+            return GROUPER_NEVER;
+        case MIN2:
+            return GROUPER_MIN2;
+        case AUTO:
+            return GROUPER_AUTO;
+        case ON_ALIGNED:
+            return GROUPER_ON_ALIGNED;
+        case WESTERN:
+            return GROUPER_WESTERN;
+        default:
+            throw new AssertionError();
+        }
+    }
+
+    /**
+     * Resolve the values in Properties to a Grouper object.
+     */
+    public static Grouper forProperties(DecimalFormatProperties properties) {
+        short grouping1 = (short) properties.getGroupingSize();
+        short grouping2 = (short) properties.getSecondaryGroupingSize();
+        short minGrouping = (short) properties.getMinimumGroupingDigits();
+        grouping1 = grouping1 > 0 ? grouping1 : grouping2 > 0 ? grouping2 : grouping1;
+        grouping2 = grouping2 > 0 ? grouping2 : grouping1;
+        return getInstance(grouping1, grouping2, minGrouping);
+    }
+
+    public static Grouper getInstance(short grouping1, short grouping2, short minGrouping) {
+        if (grouping1 == -1) {
+            return GROUPER_NEVER;
+        } else if (grouping1 == 3 && grouping2 == 3 && minGrouping == 1) {
+            return GROUPER_WESTERN;
+        } else if (grouping1 == 3 && grouping2 == 2 && minGrouping == 1) {
+            return GROUPER_INDIC;
+        } else if (grouping1 == 3 && grouping2 == 3 && minGrouping == 1) {
+            return GROUPER_WESTERN_MIN2;
+        } else if (grouping1 == 3 && grouping2 == 2 && minGrouping == 1) {
+            return GROUPER_INDIC_MIN2;
+        } else {
+            return new Grouper(grouping1, grouping2, minGrouping);
+        }
+    }
+
+    private static short getMinGroupingForLocale(ULocale locale) {
+        // TODO: Cache this?
+        ICUResourceBundle resource = (ICUResourceBundle) UResourceBundle
+                .getBundleInstance(ICUData.ICU_BASE_NAME, locale);
+        String result = resource.getStringWithFallback("NumberElements/minimumGroupingDigits");
+        return Short.valueOf(result);
+    }
+
+    /**
+     * The primary grouping size, with the following special values:
+     * <ul>
+     * <li>-1 = no grouping
+     * <li>-2 = needs locale data
+     * <li>-4 = fall back to Western grouping if not in locale
+     * </ul>
+     */
+    private final short grouping1;
+
+    /**
+     * The secondary grouping size, with the following special values:
+     * <ul>
+     * <li>-1 = no grouping
+     * <li>-2 = needs locale data
+     * <li>-4 = fall back to Western grouping if not in locale
+     * </ul>
+     */
+    private final short grouping2;
+
+    /**
+     * The minimum gropuing size, with the following special values:
+     * <ul>
+     * <li>-2 = needs locale data
+     * <li>-3 = no less than 2
+     * </ul>
+     */
+    private final short minGrouping;
+
+    private Grouper(short grouping1, short grouping2, short minGrouping) {
+        this.grouping1 = grouping1;
+        this.grouping2 = grouping2;
+        this.minGrouping = minGrouping;
+    }
+
+    public Grouper withLocaleData(ULocale locale, ParsedPatternInfo patternInfo) {
+        if (this.grouping1 != -2 && this.grouping1 != -4) {
+            return this;
+        }
+
+        short grouping1 = (short) (patternInfo.positive.groupingSizes & 0xffff);
+        short grouping2 = (short) ((patternInfo.positive.groupingSizes >>> 16) & 0xffff);
+        short grouping3 = (short) ((patternInfo.positive.groupingSizes >>> 32) & 0xffff);
+        if (grouping2 == -1) {
+            grouping1 = this.grouping1 == -4 ? (short) 3 : (short) -1;
+        }
+        if (grouping3 == -1) {
+            grouping2 = grouping1;
+        }
+
+        short minGrouping;
+        if (this.minGrouping == -2) {
+            minGrouping = getMinGroupingForLocale(locale);
+        } else if (this.minGrouping == -3) {
+            minGrouping = (short) Math.max(2, getMinGroupingForLocale(locale));
+        } else {
+            minGrouping = this.minGrouping;
+        }
+
+        return getInstance(grouping1, grouping2, minGrouping);
+    }
+
+    public boolean groupAtPosition(int position, DecimalQuantity value) {
+        assert grouping1 != -2 && grouping1 != -4;
+        if (grouping1 == -1 || grouping1 == 0) {
+            // Either -1 or 0 means "no grouping"
+            return false;
+        }
+        position -= grouping1;
+        return position >= 0
+                && (position % grouping2) == 0
+                && value.getUpperDisplayMagnitude() - grouping1 + 1 >= minGrouping;
+    }
+
+    public short getPrimary() {
+        return grouping1;
+    }
+
+    public short getSecondary() {
+        return grouping2;
+    }
+}
\ No newline at end of file
index e1f1cf7794a14238dbcd79c481c8bf7a0ed933f4..fa0f7648ca7a9a897018a5119045f7b057617d2c 100644 (file)
@@ -3,7 +3,6 @@
 package com.ibm.icu.impl.number;
 
 import com.ibm.icu.impl.Utility;
-import com.ibm.icu.number.Grouper;
 import com.ibm.icu.number.IntegerWidth;
 import com.ibm.icu.number.Notation;
 import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
@@ -19,7 +18,7 @@ public class MacroProps implements Cloneable {
     public MeasureUnit unit;
     public MeasureUnit perUnit;
     public Rounder rounder;
-    public Grouper grouper;
+    public Object grouping;
     public Padder padder;
     public IntegerWidth integerWidth;
     public Object symbols;
@@ -47,8 +46,8 @@ public class MacroProps implements Cloneable {
             perUnit = fallback.perUnit;
         if (rounder == null)
             rounder = fallback.rounder;
-        if (grouper == null)
-            grouper = fallback.grouper;
+        if (grouping == null)
+            grouping = fallback.grouping;
         if (padder == null)
             padder = fallback.padder;
         if (integerWidth == null)
@@ -77,7 +76,7 @@ public class MacroProps implements Cloneable {
                 unit,
                 perUnit,
                 rounder,
-                grouper,
+                grouping,
                 padder,
                 integerWidth,
                 symbols,
@@ -103,7 +102,7 @@ public class MacroProps implements Cloneable {
                 && Utility.equals(unit, other.unit)
                 && Utility.equals(perUnit, other.perUnit)
                 && Utility.equals(rounder, other.rounder)
-                && Utility.equals(grouper, other.grouper)
+                && Utility.equals(grouping, other.grouping)
                 && Utility.equals(padder, other.padder)
                 && Utility.equals(integerWidth, other.integerWidth)
                 && Utility.equals(symbols, other.symbols)
index 019a10168abf55110acfff47f9f87d8ea0ced8f3..d5e3ba44f36a10b95e7f5a5b8d639a5bddef517c 100644 (file)
@@ -2,7 +2,6 @@
 // License & terms of use: http://www.unicode.org/copyright.html#License
 package com.ibm.icu.impl.number;
 
-import com.ibm.icu.number.Grouper;
 import com.ibm.icu.number.IntegerWidth;
 import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
 import com.ibm.icu.number.NumberFormatter.SignDisplay;
index cc894bebfd94212953ed43bc8c36fed4f1da9554..609259ab078b16c5cefc7fd7f7788fe34bfaa5a6 100644 (file)
@@ -206,9 +206,9 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
         insertPrefix(a.clear(), 0);
         insertSuffix(b.clear(), 0);
         if (patternInfo.hasCurrencySign()) {
-            return new CurrencySpacingEnabledModifier(a, b, isStrong, symbols);
+            return new CurrencySpacingEnabledModifier(a, b, !patternInfo.hasBody(), isStrong, symbols);
         } else {
-            return new ConstantMultiFieldModifier(a, b, isStrong);
+            return new ConstantMultiFieldModifier(a, b, !patternInfo.hasBody(), isStrong);
         }
     }
 
@@ -271,13 +271,18 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
     public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
         int prefixLen = insertPrefix(output, leftIndex);
         int suffixLen = insertSuffix(output, rightIndex + prefixLen);
+        // If the pattern had no decimal stem body (like #,##0.00), overwrite the value.
+        int overwriteLen = 0;
+        if (!patternInfo.hasBody()) {
+            overwriteLen = output.splice(leftIndex + prefixLen, rightIndex + prefixLen, "", 0, 0, null);
+        }
         CurrencySpacingEnabledModifier.applyCurrencySpacing(output,
                 leftIndex,
                 prefixLen,
-                rightIndex + prefixLen,
+                rightIndex + prefixLen + overwriteLen,
                 suffixLen,
                 symbols);
-        return prefixLen + suffixLen;
+        return prefixLen + overwriteLen + suffixLen;
     }
 
     @Override
@@ -317,7 +322,7 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
 
     /**
      * Pre-processes the prefix or suffix into the currentAffix field, creating and mutating that field
-     * if necessary.  Calls down to {@link PatternStringUtils#affixPatternProviderToStringBuilder}.
+     * if necessary. Calls down to {@link PatternStringUtils#affixPatternProviderToStringBuilder}.
      *
      * @param isPrefix
      *            true to prepare the prefix; false to prepare the suffix.
@@ -355,10 +360,10 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
                 return currency.getCurrencyCode();
             } else if (unitWidth == UnitWidth.HIDDEN) {
                 return "";
-            } else if (unitWidth == UnitWidth.NARROW) {
-                return currency.getName(symbols.getULocale(), Currency.NARROW_SYMBOL_NAME, null);
             } else {
-                return currency.getName(symbols.getULocale(), Currency.SYMBOL_NAME, null);
+                int selector = unitWidth == UnitWidth.NARROW ? Currency.NARROW_SYMBOL_NAME
+                        : Currency.SYMBOL_NAME;
+                return currency.getName(symbols.getULocale(), selector, null);
             }
         case AffixUtils.TYPE_CURRENCY_DOUBLE:
             return currency.getCurrencyCode();
index 216427c5de0c51796fc5096624ade634eef37269..53dd482baab76640b2f02e67130e4e01d4a74a88 100644 (file)
@@ -169,6 +169,11 @@ public class PatternStringParser {
         public boolean containsSymbolType(int type) {
             return AffixUtils.containsType(pattern, type);
         }
+
+        @Override
+        public boolean hasBody() {
+            return positive.integerTotal > 0;
+        }
     }
 
     public static class ParsedSubpatternInfo {
index c6b57ba9201ea8e8e85f44ba75ef1646d22bce31..6d2eab65c801b068f3ba38404f2a5c154c8aea35 100644 (file)
@@ -127,6 +127,11 @@ public class PropertiesAffixPatternProvider implements AffixPatternProvider {
                 || AffixUtils.containsType(negPrefix, type) || AffixUtils.containsType(negSuffix, type);
     }
 
+    @Override
+    public boolean hasBody() {
+        return true;
+    }
+
     @Override
     public String toString() {
         return super.toString()
index 4a12af11c2069faa78e7c38b6403ffba6825463c..ae095505616bde1bdecfb1ee433ba4233f5c9561 100644 (file)
@@ -3,9 +3,9 @@
 package com.ibm.icu.impl.number.parse;
 
 import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
+import com.ibm.icu.impl.number.Grouper;
 import com.ibm.icu.impl.number.parse.UnicodeSetStaticCache.Key;
 import com.ibm.icu.lang.UCharacter;
-import com.ibm.icu.number.Grouper;
 import com.ibm.icu.text.DecimalFormatSymbols;
 import com.ibm.icu.text.UnicodeSet;
 
index 73654686dbb752aef2084bc258fec9400e8170b8..acb96411b16406dc74d192257ae0e6d6853d5306 100644 (file)
@@ -12,11 +12,12 @@ import com.ibm.icu.impl.number.AffixPatternProvider;
 import com.ibm.icu.impl.number.AffixUtils;
 import com.ibm.icu.impl.number.CustomSymbolCurrency;
 import com.ibm.icu.impl.number.DecimalFormatProperties;
+import com.ibm.icu.impl.number.Grouper;
 import com.ibm.icu.impl.number.PatternStringParser;
 import com.ibm.icu.impl.number.PatternStringParser.ParsedPatternInfo;
 import com.ibm.icu.impl.number.PropertiesAffixPatternProvider;
 import com.ibm.icu.impl.number.RoundingUtils;
-import com.ibm.icu.number.Grouper;
+import com.ibm.icu.number.NumberFormatter.GroupingStrategy;
 import com.ibm.icu.text.DecimalFormatSymbols;
 import com.ibm.icu.text.UnicodeSet;
 import com.ibm.icu.util.Currency;
@@ -93,7 +94,7 @@ public class NumberParserImpl {
         ParsedPatternInfo patternInfo = PatternStringParser.parseToPatternInfo(pattern);
         AffixMatcher.newGenerate(patternInfo, parser, factory, ignorables, parseFlags);
 
-        Grouper grouper = Grouper.defaults().withLocaleData(patternInfo);
+        Grouper grouper = Grouper.forStrategy(GroupingStrategy.AUTO).withLocaleData(locale, patternInfo);
 
         parser.addMatcher(ignorables);
         parser.addMatcher(DecimalMatcher.getInstance(symbols, grouper, parseFlags));
@@ -166,7 +167,7 @@ public class NumberParserImpl {
         AffixPatternProvider patternInfo = new PropertiesAffixPatternProvider(properties);
         Currency currency = CustomSymbolCurrency.resolve(properties.getCurrency(), locale, symbols);
         boolean isStrict = properties.getParseMode() == ParseMode.STRICT;
-        Grouper grouper = Grouper.defaults().withProperties(properties);
+        Grouper grouper = Grouper.forProperties(properties);
         int parseFlags = 0;
         // Fraction grouping is disabled by default because it has never been supported in DecimalFormat
         parseFlags |= ParsingUtils.PARSE_FLAG_FRACTION_GROUPING_DISABLED;
index c05e75fa80e4ef0aae995708792dd0aac8f814e1..a6c053af7eaa0e56e6f34a7dab8941b8936fbec9 100644 (file)
@@ -2,7 +2,7 @@
 // License & terms of use: http://www.unicode.org/copyright.html#License
 package com.ibm.icu.impl.number.parse;
 
-import com.ibm.icu.number.Grouper;
+import com.ibm.icu.impl.number.Grouper;
 import com.ibm.icu.text.DecimalFormatSymbols;
 import com.ibm.icu.text.UnicodeSet;
 
index c176ee2501879c359c3ee32032565878bb0eeda2..5219f0091dc93321753af4b323fd24cbd4bfa1ec 100644 (file)
@@ -10,6 +10,7 @@ import java.util.Set;
 import com.ibm.icu.impl.StandardPlural;
 import com.ibm.icu.impl.number.CompactData;
 import com.ibm.icu.impl.number.CompactData.CompactType;
+import com.ibm.icu.impl.number.DecimalFormatProperties;
 import com.ibm.icu.impl.number.DecimalQuantity;
 import com.ibm.icu.impl.number.MicroProps;
 import com.ibm.icu.impl.number.MicroPropsGenerator;
@@ -38,6 +39,17 @@ public class CompactNotation extends Notation {
     final CompactStyle compactStyle;
     final Map<String, Map<String, String>> compactCustomData;
 
+    /**
+     * Create a compact notation with custom data.
+     * @internal
+     * @deprecated This API is ICU internal only.
+     * @see DecimalFormatProperties#setCompactCustomData
+     */
+    @Deprecated
+    public static CompactNotation forCustomData(Map<String, Map<String, String>> compactCustomData) {
+        return new CompactNotation(compactCustomData);
+    }
+
     /* package-private */ CompactNotation(CompactStyle compactStyle) {
         compactCustomData = null;
         this.compactStyle = compactStyle;
@@ -61,14 +73,9 @@ public class CompactNotation extends Notation {
 
     private static class CompactHandler implements MicroPropsGenerator {
 
-        private static class CompactModInfo {
-            public ImmutablePatternModifier mod;
-            public int numDigits;
-        }
-
         final PluralRules rules;
         final MicroPropsGenerator parent;
-        final Map<String, CompactModInfo> precomputedMods;
+        final Map<String, ImmutablePatternModifier> precomputedMods;
         final CompactData data;
 
         private CompactHandler(
@@ -89,7 +96,7 @@ public class CompactNotation extends Notation {
             }
             if (buildReference != null) {
                 // Safe code path
-                precomputedMods = new HashMap<String, CompactModInfo>();
+                precomputedMods = new HashMap<String, ImmutablePatternModifier>();
                 precomputeAllModifiers(buildReference);
             } else {
                 // Unsafe code path
@@ -103,12 +110,9 @@ public class CompactNotation extends Notation {
             data.getUniquePatterns(allPatterns);
 
             for (String patternString : allPatterns) {
-                CompactModInfo info = new CompactModInfo();
                 ParsedPatternInfo patternInfo = PatternStringParser.parseToPatternInfo(patternString);
                 buildReference.setPatternInfo(patternInfo);
-                info.mod = buildReference.createImmutable();
-                info.numDigits = patternInfo.positive.integerTotal;
-                precomputedMods.put(patternString, info);
+                precomputedMods.put(patternString, buildReference.createImmutable());
             }
         }
 
@@ -131,28 +135,22 @@ public class CompactNotation extends Notation {
 
             StandardPlural plural = quantity.getStandardPlural(rules);
             String patternString = data.getPattern(magnitude, plural);
-            @SuppressWarnings("unused") // see #13075
-            int numDigits = -1;
             if (patternString == null) {
                 // Use the default (non-compact) modifier.
                 // No need to take any action.
             } else if (precomputedMods != null) {
                 // Safe code path.
                 // Java uses a hash set here for O(1) lookup. C++ uses a linear search.
-                CompactModInfo info = precomputedMods.get(patternString);
-                info.mod.applyToMicros(micros, quantity);
-                numDigits = info.numDigits;
+                ImmutablePatternModifier mod = precomputedMods.get(patternString);
+                mod.applyToMicros(micros, quantity);
             } else {
                 // Unsafe code path.
                 // Overwrite the PatternInfo in the existing modMiddle.
                 assert micros.modMiddle instanceof MutablePatternModifier;
                 ParsedPatternInfo patternInfo = PatternStringParser.parseToPatternInfo(patternString);
                 ((MutablePatternModifier) micros.modMiddle).setPatternInfo(patternInfo);
-                numDigits = patternInfo.positive.integerTotal;
             }
 
-            // FIXME: Deal with numDigits == 0 (Awaiting a test case)
-
             // We already performed rounding. Do not perform it again.
             micros.rounding = Rounder.constructPassThrough();
 
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/Grouper.java b/icu4j/main/classes/core/src/com/ibm/icu/number/Grouper.java
deleted file mode 100644 (file)
index 24d46f5..0000000
+++ /dev/null
@@ -1,157 +0,0 @@
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.number;
-
-import com.ibm.icu.impl.number.DecimalFormatProperties;
-import com.ibm.icu.impl.number.DecimalQuantity;
-import com.ibm.icu.impl.number.PatternStringParser.ParsedPatternInfo;
-
-/**
- * @internal
- * @deprecated This API is a technical preview. It is likely to change in an upcoming release.
- */
-@Deprecated
-public class Grouper {
-
-    // Conveniences for Java handling of bytes
-    private static final byte N2 = -2;
-    private static final byte N1 = -1;
-    private static final byte B2 = 2;
-    private static final byte B3 = 3;
-
-    private static final Grouper DEFAULTS = new Grouper(N2, N2, false);
-    private static final Grouper MIN2 = new Grouper(N2, N2, true);
-    private static final Grouper NONE = new Grouper(N1, N1, false);
-
-    private final byte grouping1; // -2 means "needs locale data"; -1 means "no grouping"
-    private final byte grouping2;
-    private final boolean min2;
-
-    private Grouper(byte grouping1, byte grouping2, boolean min2) {
-        this.grouping1 = grouping1;
-        this.grouping2 = grouping2;
-        this.min2 = min2;
-    }
-
-    /**
-     * @internal
-     * @deprecated This API is a technical preview. It is likely to change in an upcoming release.
-     */
-    @Deprecated
-    public static Grouper defaults() {
-        return DEFAULTS;
-    }
-
-    /**
-     * @internal
-     * @deprecated This API is a technical preview. It is likely to change in an upcoming release.
-     */
-    @Deprecated
-    public static Grouper minTwoDigits() {
-        return MIN2;
-    }
-
-    /**
-     * @internal
-     * @deprecated This API is a technical preview. It is likely to change in an upcoming release.
-     */
-    @Deprecated
-    public static Grouper none() {
-        return NONE;
-    }
-
-    //////////////////////////
-    // PACKAGE-PRIVATE APIS //
-    //////////////////////////
-
-    private static final Grouper GROUPING_3 = new Grouper(B3, B3, false);
-    private static final Grouper GROUPING_3_2 = new Grouper(B3, B2, false);
-    private static final Grouper GROUPING_3_MIN2 = new Grouper(B3, B3, true);
-    private static final Grouper GROUPING_3_2_MIN2 = new Grouper(B3, B2, true);
-
-    static Grouper getInstance(byte grouping1, byte grouping2, boolean min2) {
-        if (grouping1 == -1) {
-            return NONE;
-        } else if (!min2 && grouping1 == 3 && grouping2 == 3) {
-            return GROUPING_3;
-        } else if (!min2 && grouping1 == 3 && grouping2 == 2) {
-            return GROUPING_3_2;
-        } else if (min2 && grouping1 == 3 && grouping2 == 3) {
-            return GROUPING_3_MIN2;
-        } else if (min2 && grouping1 == 3 && grouping2 == 2) {
-            return GROUPING_3_2_MIN2;
-        } else {
-            return new Grouper(grouping1, grouping2, min2);
-        }
-    }
-
-    /**
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    @Deprecated
-    public Grouper withProperties(DecimalFormatProperties properties) {
-        if (grouping1 != -2) {
-            return this;
-        }
-        byte grouping1 = (byte) properties.getGroupingSize();
-        byte grouping2 = (byte) properties.getSecondaryGroupingSize();
-        int minGrouping = properties.getMinimumGroupingDigits();
-        grouping1 = grouping1 > 0 ? grouping1 : grouping2 > 0 ? grouping2 : grouping1;
-        grouping2 = grouping2 > 0 ? grouping2 : grouping1;
-        // TODO: Is it important to handle minGrouping > 2?
-        return getInstance(grouping1, grouping2, minGrouping == 2);
-    }
-
-    /**
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    @Deprecated
-    public Grouper withLocaleData(ParsedPatternInfo patternInfo) {
-        if (grouping1 != -2) {
-            return this;
-        }
-        // TODO: short or byte?
-        byte grouping1 = (byte) (patternInfo.positive.groupingSizes & 0xffff);
-        byte grouping2 = (byte) ((patternInfo.positive.groupingSizes >>> 16) & 0xffff);
-        byte grouping3 = (byte) ((patternInfo.positive.groupingSizes >>> 32) & 0xffff);
-        if (grouping2 == -1) {
-            grouping1 = -1;
-        }
-        if (grouping3 == -1) {
-            grouping2 = grouping1;
-        }
-        return getInstance(grouping1, grouping2, min2);
-    }
-
-    boolean groupAtPosition(int position, DecimalQuantity value) {
-        assert grouping1 != -2;
-        if (grouping1 == -1 || grouping1 == 0) {
-            // Either -1 or 0 means "no grouping"
-            return false;
-        }
-        position -= grouping1;
-        return position >= 0
-                && (position % grouping2) == 0
-                && value.getUpperDisplayMagnitude() - grouping1 + 1 >= (min2 ? 2 : 1);
-    }
-
-    /**
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    @Deprecated
-    public byte getPrimary() {
-        return grouping1;
-    }
-
-    /**
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    @Deprecated
-    public byte getSecondary() {
-        return grouping2;
-    }
-}
\ No newline at end of file
index 7a819b18aeeb2252ca91626cdefffc1c4a3c0fac..d66cb2afdec6d4386bf880080aa2234b623203da 100644 (file)
@@ -161,15 +161,119 @@ public final class NumberFormatter {
     }
 
     /**
-     * An enum declaring how to denote positive and negative numbers. Example outputs when formatting 123
-     * and -123 in <em>en-US</em>:
+     * An enum declaring the strategy for when and how to display grouping separators (i.e., the
+     * separator, often a comma or period, after every 2-3 powers of ten). The choices are several
+     * pre-built strategies for different use cases that employ locale data whenever possible. Example
+     * outputs for 1234 and 1234567 in <em>en-IN</em>:
      *
      * <ul>
-     * <li>AUTO: "123" and "-123"
-     * <li>ALWAYS: "+123" and "-123"
-     * <li>NEVER: "123" and "123"
-     * <li>ACCOUNTING: "$123" and "($123)"
-     * <li>ACCOUNTING_ALWAYS: "+$123" and "($123)"
+     * <li>OFF: 1234 and 12345
+     * <li>MIN2: 1234 and 12,34,567
+     * <li>AUTO: 1,234 and 12,34,567
+     * <li>ON_ALIGNED: 1,234 and 12,34,567
+     * <li>WESTERN: 1,234 and 1,234,567
+     * </ul>
+     *
+     * <p>
+     * The default is AUTO, which displays grouping separators unless the locale data says that grouping
+     * is not customary. To force grouping for all numbers greater than 1000 consistently across locales,
+     * use ON_ALIGNED. On the other hand, to display grouping less frequently than the default, use MIN2
+     * or OFF. See the docs of each option for details.
+     *
+     * <p>
+     * Note: This enum specifies the strategy for grouping sizes. To set which character to use as the
+     * grouping separator, use the "symbols" setter.
+     *
+     * @draft ICU 61
+     * @provisional This API might change or be removed in a future release.
+     * @see NumberFormatter
+     */
+    public static enum GroupingStrategy {
+        /**
+         * Do not display grouping separators in any locale.
+         *
+         * @draft ICU 61
+         * @provisional This API might change or be removed in a future release.
+         * @see NumberFormatter
+         */
+        OFF,
+
+        /**
+         * Display grouping using locale defaults, except do not show grouping on values smaller than
+         * 10000 (such that there is a <em>minimum of two digits</em> before the first separator).
+         *
+         * <p>
+         * Note that locales may restrict grouping separators to be displayed only on 1 million or
+         * greater (for example, ee and hu) or disable grouping altogether (for example, bg currency).
+         *
+         * <p>
+         * Locale data is used to determine whether to separate larger numbers into groups of 2
+         * (customary in South Asia) or groups of 3 (customary in Europe and the Americas).
+         *
+         * @draft ICU 61
+         * @provisional This API might change or be removed in a future release.
+         * @see NumberFormatter
+         */
+        MIN2,
+
+        /**
+         * Display grouping using the default strategy for all locales. This is the default behavior.
+         *
+         * <p>
+         * Note that locales may restrict grouping separators to be displayed only on 1 million or
+         * greater (for example, ee and hu) or disable grouping altogether (for example, bg currency).
+         *
+         * <p>
+         * Locale data is used to determine whether to separate larger numbers into groups of 2
+         * (customary in South Asia) or groups of 3 (customary in Europe and the Americas).
+         *
+         * @draft ICU 61
+         * @provisional This API might change or be removed in a future release.
+         * @see NumberFormatter
+         */
+        AUTO,
+
+        /**
+         * Always display the grouping separator on values of at least 1000.
+         *
+         * <p>
+         * This option ignores the locale data that restricts or disables grouping, described in MIN2 and
+         * AUTO. This option may be useful to normalize the alignment of numbers, such as in a
+         * spreadsheet.
+         *
+         * <p>
+         * Locale data is used to determine whether to separate larger numbers into groups of 2
+         * (customary in South Asia) or groups of 3 (customary in Europe and the Americas).
+         *
+         * @draft ICU 61
+         * @provisional This API might change or be removed in a future release.
+         * @see NumberFormatter
+         */
+        ON_ALIGNED,
+
+        /**
+         * Use the Western defaults: groups of 3 and enabled for all numbers 1000 or greater. Do not use
+         * locale data for determining the grouping strategy.
+         *
+         * @draft ICU 61
+         * @provisional This API might change or be removed in a future release.
+         * @see NumberFormatter
+         */
+        WESTERN
+    }
+
+    /**
+     * An enum declaring how to denote positive and negative numbers. Example outputs when formatting
+     * 123, 0, and -123 in <em>en-US</em>:
+     *
+     * <ul>
+     * <li>AUTO: "123", "0", and "-123"
+     * <li>ALWAYS: "+123", "+0", and "-123"
+     * <li>NEVER: "123", "0", and "123"
+     * <li>ACCOUNTING: "$123", "$0", and "($123)"
+     * <li>ACCOUNTING_ALWAYS: "+$123", "+$0", and "($123)"
+     * <li>EXCEPT_ZERO: "+123", "0", and "-123"
+     * <li>ACCOUNTING_EXCEPT_ZERO: "+$123", "$0", and "($123)"
      * </ul>
      *
      * <p>
index d612ab27bf3d231b5cd627090df8dc3861a053c9..c0cf83f2dea2bf65417d331e791c82b636fc811c 100644 (file)
@@ -2,9 +2,12 @@
 // License & terms of use: http://www.unicode.org/copyright.html#License
 package com.ibm.icu.number;
 
+import com.ibm.icu.impl.CurrencyData;
+import com.ibm.icu.impl.CurrencyData.CurrencyFormatInfo;
 import com.ibm.icu.impl.number.CompactData.CompactType;
 import com.ibm.icu.impl.number.ConstantAffixModifier;
 import com.ibm.icu.impl.number.DecimalQuantity;
+import com.ibm.icu.impl.number.Grouper;
 import com.ibm.icu.impl.number.LongNameHandler;
 import com.ibm.icu.impl.number.MacroProps;
 import com.ibm.icu.impl.number.MicroProps;
@@ -15,6 +18,7 @@ import com.ibm.icu.impl.number.Padder;
 import com.ibm.icu.impl.number.PatternStringParser;
 import com.ibm.icu.impl.number.PatternStringParser.ParsedPatternInfo;
 import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
+import com.ibm.icu.number.NumberFormatter.GroupingStrategy;
 import com.ibm.icu.number.NumberFormatter.SignDisplay;
 import com.ibm.icu.number.NumberFormatter.UnitWidth;
 import com.ibm.icu.text.DecimalFormatSymbols;
@@ -134,35 +138,49 @@ class NumberFormatterImpl {
         }
         String nsName = ns.getName();
 
-        // Load and parse the pattern string. It is used for grouping sizes and affixes only.
-        int patternStyle;
-        if (isPercent || isPermille) {
-            patternStyle = NumberFormat.PERCENTSTYLE;
-        } else if (!isCurrency || unitWidth == UnitWidth.FULL_NAME) {
-            patternStyle = NumberFormat.NUMBERSTYLE;
-        } else if (isAccounting) {
-            // NOTE: Although ACCOUNTING and ACCOUNTING_ALWAYS are only supported in currencies right
-            // now,
-            // the API contract allows us to add support to other units in the future.
-            patternStyle = NumberFormat.ACCOUNTINGCURRENCYSTYLE;
+        // Resolve the symbols. Do this here because currency may need to customize them.
+        if (macros.symbols instanceof DecimalFormatSymbols) {
+            micros.symbols = (DecimalFormatSymbols) macros.symbols;
         } else {
-            patternStyle = NumberFormat.CURRENCYSTYLE;
+            micros.symbols = DecimalFormatSymbols.forNumberingSystem(macros.loc, ns);
+        }
+
+        // Load and parse the pattern string. It is used for grouping sizes and affixes only.
+        // If we are formatting currency, check for a currency-specific pattern.
+        String pattern = null;
+        if (isCurrency) {
+            CurrencyFormatInfo info = CurrencyData.provider.getInstance(macros.loc, true)
+                    .getFormatInfo(currency.getCurrencyCode());
+            if (info != null) {
+                pattern = info.currencyPattern;
+                // It's clunky to clone an object here, but this code is not frequently executed.
+                micros.symbols = (DecimalFormatSymbols) micros.symbols.clone();
+                micros.symbols.setMonetaryDecimalSeparatorString(info.monetaryDecimalSeparator);
+                micros.symbols.setMonetaryGroupingSeparatorString(info.monetaryGroupingSeparator);
+            }
+        }
+        if (pattern == null) {
+            int patternStyle;
+            if (isPercent || isPermille) {
+                patternStyle = NumberFormat.PERCENTSTYLE;
+            } else if (!isCurrency || unitWidth == UnitWidth.FULL_NAME) {
+                patternStyle = NumberFormat.NUMBERSTYLE;
+            } else if (isAccounting) {
+                // NOTE: Although ACCOUNTING and ACCOUNTING_ALWAYS are only supported in currencies
+                // right now, the API contract allows us to add support to other units in the future.
+                patternStyle = NumberFormat.ACCOUNTINGCURRENCYSTYLE;
+            } else {
+                patternStyle = NumberFormat.CURRENCYSTYLE;
+            }
+            pattern = NumberFormat
+                    .getPatternForStyleAndNumberingSystem(macros.loc, nsName, patternStyle);
         }
-        String pattern = NumberFormat
-                .getPatternForStyleAndNumberingSystem(macros.loc, nsName, patternStyle);
         ParsedPatternInfo patternInfo = PatternStringParser.parseToPatternInfo(pattern);
 
         /////////////////////////////////////////////////////////////////////////////////////
         /// START POPULATING THE DEFAULT MICROPROPS AND BUILDING THE MICROPROPS GENERATOR ///
         /////////////////////////////////////////////////////////////////////////////////////
 
-        // Symbols
-        if (macros.symbols instanceof DecimalFormatSymbols) {
-            micros.symbols = (DecimalFormatSymbols) macros.symbols;
-        } else {
-            micros.symbols = DecimalFormatSymbols.forNumberingSystem(macros.loc, ns);
-        }
-
         // Multiplier (compatibility mode value).
         if (macros.multiplier != null) {
             chain = macros.multiplier.copyAndChain(chain);
@@ -181,15 +199,17 @@ class NumberFormatterImpl {
         micros.rounding = micros.rounding.withLocaleData(currency);
 
         // Grouping strategy
-        if (macros.grouper != null) {
-            micros.grouping = macros.grouper;
+        if (macros.grouping instanceof Grouper) {
+            micros.grouping = (Grouper) macros.grouping;
+        } else if (macros.grouping instanceof GroupingStrategy) {
+            micros.grouping = Grouper.forStrategy((GroupingStrategy) macros.grouping);
         } else if (macros.notation instanceof CompactNotation) {
             // Compact notation uses minGrouping by default since ICU 59
-            micros.grouping = Grouper.minTwoDigits();
+            micros.grouping = Grouper.forStrategy(GroupingStrategy.MIN2);
         } else {
-            micros.grouping = Grouper.defaults();
+            micros.grouping = Grouper.forStrategy(GroupingStrategy.AUTO);
         }
-        micros.grouping = micros.grouping.withLocaleData(patternInfo);
+        micros.grouping = micros.grouping.withLocaleData(macros.loc, patternInfo);
 
         // Padding strategy
         if (macros.padder != null) {
index df5d94bca9f30207effe07772b6d4bab232a57e2..0143f2e48be904407d61d3166428efc579f720a7 100644 (file)
@@ -5,6 +5,7 @@ package com.ibm.icu.number;
 import com.ibm.icu.impl.number.MacroProps;
 import com.ibm.icu.impl.number.Padder;
 import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
+import com.ibm.icu.number.NumberFormatter.GroupingStrategy;
 import com.ibm.icu.number.NumberFormatter.SignDisplay;
 import com.ibm.icu.number.NumberFormatter.UnitWidth;
 import com.ibm.icu.text.DecimalFormatSymbols;
@@ -31,7 +32,7 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
     static final int KEY_NOTATION = 2;
     static final int KEY_UNIT = 3;
     static final int KEY_ROUNDER = 4;
-    static final int KEY_GROUPER = 5;
+    static final int KEY_GROUPING = 5;
     static final int KEY_PADDER = 6;
     static final int KEY_INTEGER = 7;
     static final int KEY_SYMBOLS = 8;
@@ -220,25 +221,24 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
      * The exact grouping widths will be chosen based on the locale.
      *
      * <p>
-     * Pass this method the return value of one of the factory methods on {@link Grouper}. For example:
+     * Pass this method an element from the {@link GroupingStrategy} enum. For example:
      *
      * <pre>
-     * NumberFormatter.with().grouping(Grouper.min2())
+     * NumberFormatter.with().grouping(GroupingStrategy.MIN2)
      * </pre>
      *
-     * The default is to perform grouping without concern for the minimum grouping digits.
+     * The default is to perform grouping according to locale data; most locales, but not all locales,
+     * enable it by default.
      *
-     * @param grouper
+     * @param strategy
      *            The grouping strategy to use.
      * @return The fluent chain.
-     * @see Grouper
-     * @see Notation
-     * @internal
-     * @deprecated ICU 60 This API is technical preview; see #7861.
+     * @see GroupingStrategy
+     * @draft ICU 61
+     * @provisional This API might change or be removed in a future release.
      */
-    @Deprecated
-    public T grouping(Grouper grouper) {
-        return create(KEY_GROUPER, grouper);
+    public T grouping(GroupingStrategy strategy) {
+        return create(KEY_GROUPING, strategy);
     }
 
     /**
@@ -512,9 +512,9 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
                     macros.rounder = (Rounder) current.value;
                 }
                 break;
-            case KEY_GROUPER:
-                if (macros.grouper == null) {
-                    macros.grouper = (Grouper) current.value;
+            case KEY_GROUPING:
+                if (macros.grouping == null) {
+                    macros.grouping = /* (Object) */ current.value;
                 }
                 break;
             case KEY_PADDER:
index 8abcc5c84eae00000829aefc512dd582e793bf07..c7a1a664ff49c2f8d8d9379ea40de85dac60b7b7 100644 (file)
@@ -9,6 +9,7 @@ import com.ibm.icu.impl.number.AffixPatternProvider;
 import com.ibm.icu.impl.number.CurrencyPluralInfoAffixProvider;
 import com.ibm.icu.impl.number.CustomSymbolCurrency;
 import com.ibm.icu.impl.number.DecimalFormatProperties;
+import com.ibm.icu.impl.number.Grouper;
 import com.ibm.icu.impl.number.MacroProps;
 import com.ibm.icu.impl.number.MultiplierImpl;
 import com.ibm.icu.impl.number.Padder;
@@ -193,7 +194,7 @@ final class NumberPropertyMapper {
         // GROUPING STRATEGY //
         ///////////////////////
 
-        macros.grouper = Grouper.defaults().withProperties(properties);
+        macros.grouping = Grouper.forProperties(properties);
 
         /////////////
         // PADDING //
index 448957700e348f47421c417c72e19cd1b0d5eb8d..7bf1d0f22a9e2f7bf610fa6b02b5ed37ec9d3013 100644 (file)
@@ -111,17 +111,31 @@ public abstract class CurrencyDisplayNames {
 
     /**
      * 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.
+     * there is no data for the ISO code, substitutes isoCode, or returns null
+     * if noSubstitute was set in the factory method.
      *
      * @param isoCode the three-letter ISO code.
-     * @return the display name.
+     * @return the symbol.
      * @stable ICU 4.4
      */
     public abstract String getSymbol(String isoCode);
 
+    /**
+     * Returns the narrow symbol for the currency with the provided ISO code.
+     * If there is no data for narrow symbol, substitutes isoCode, or returns
+     * null if noSubstitute was set in the factory method.
+     *
+     * @param isoCode the three-letter ISO code.
+     * @return the narrow symbol.
+     * @draft ICU 61
+     * @provisional This API might change or be removed in a future release.
+     */
+    public abstract String getNarrowSymbol(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 there is no data for the ISO code, substitutes isoCode, or returns null
+     * if noSubstitute was set in the factory method.
      *
      * @param isoCode the three-letter ISO code
      * @return the display name
index 069608f659235e9b870c3e984788ad6eccf98dbb..659b9d1b5c3b4e844be869174491c70ff01bba31 100644 (file)
@@ -24,7 +24,6 @@ import java.util.MissingResourceException;
 import java.util.Set;
 
 import com.ibm.icu.impl.CacheBase;
-import com.ibm.icu.impl.CurrencyData.CurrencyDisplayInfo;
 import com.ibm.icu.impl.ICUCache;
 import com.ibm.icu.impl.ICUData;
 import com.ibm.icu.impl.ICUDebug;
@@ -92,15 +91,11 @@ 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$".
+     * "$" instead of "US$" for USD in en-CA.
      *
-     * This method assumes that the currency data provider is the ICU4J
-     * built-in data provider. If it is not, an exception is thrown.
-     *
-     * @internal
-     * @deprecated ICU 60: This API is ICU internal only.
+     * @draft ICU 61
+     * @provisional This API might change or be removed in a future release.
      */
-    @Deprecated
     public static final int NARROW_SYMBOL_NAME = 3;
 
     private static final EquivalenceRelation<String> EQUIVALENT_CURRENCY_SYMBOLS =
@@ -568,8 +563,8 @@ public class Currency extends MeasureUnit {
      * currency object in the en_US locale is "$".
      * @param locale locale in which to display currency
      * @param nameStyle selector for which kind of name to return.
-     *                  The nameStyle should be either SYMBOL_NAME or
-     *                  LONG_NAME. Otherwise, throw IllegalArgumentException.
+     *                  The nameStyle should be SYMBOL_NAME, NARROW_SYMBOL_NAME,
+     *                  or LONG_NAME. Otherwise, throw IllegalArgumentException.
      * @param isChoiceFormat fill-in; isChoiceFormat[0] is set to true
      * if the returned value is a ChoiceFormat pattern; otherwise it
      * is set to false
@@ -597,13 +592,7 @@ public class Currency extends MeasureUnit {
         case SYMBOL_NAME:
             return names.getSymbol(subType);
         case NARROW_SYMBOL_NAME:
-            // CurrencyDisplayNames is the public interface.
-            // CurrencyDisplayInfo is ICU's standard implementation.
-            if (!(names instanceof CurrencyDisplayInfo)) {
-                throw new UnsupportedOperationException(
-                        "Cannot get narrow symbol from custom currency display name provider");
-            }
-            return ((CurrencyDisplayInfo) names).getNarrowSymbol(subType);
+            return names.getNarrowSymbol(subType);
         case LONG_NAME:
             return names.getName(subType);
         default:
index 5d3deb758fccefe99d9b622c35a139e91050d350..e358953ac6e9df14238866da4cdc923100032f6b 100644 (file)
@@ -465,9 +465,8 @@ output      grouping        breaks  grouping2       minGroupingDigits
 1,2345,6789    4
 1,23,45,6789   4       K       2
 1,23,45,6789   4       K       2       2
-// Q only supports minGrouping<=2
 123,456789     6               6       3
-123456789      6       JKQ     6       4
+123456789      6       JK      6       4
 
 test multiplier setters
 set locale en_US
index 8a85f23a4d0e47992f090d0e1d5c599965ba7a05..f390d1e0404f355c9f400cbfb29067377c9d84ba 100644 (file)
@@ -5330,6 +5330,33 @@ public class NumberFormatTest extends TestFmwk {
         assertEquals("Grouping should be off", false, df.isGroupingUsed());
     }
 
+    @Test
+    public void Test11035_FormatCurrencyAmount() {
+        double amount = 12345.67;
+        String expected = "12,345$67 ​";
+        Currency cur = Currency.getInstance("PTE");
+
+        // Test three ways to set currency via API
+
+        ULocale loc1 = new ULocale("pt_PT");
+        NumberFormat fmt1 = NumberFormat.getCurrencyInstance(loc1);
+        fmt1.setCurrency(cur);
+        String actualSetCurrency = fmt1.format(amount);
+
+        ULocale loc2 = new ULocale("pt_PT@currency=PTE");
+        NumberFormat fmt2 = NumberFormat.getCurrencyInstance(loc2);
+        String actualLocaleString = fmt2.format(amount);
+
+        ULocale loc3 = new ULocale("pt_PT");
+        NumberFormat fmt3 = NumberFormat.getCurrencyInstance(loc3);
+        CurrencyAmount curAmt = new CurrencyAmount(amount, cur);
+        String actualCurrencyAmount = fmt3.format(curAmt);
+
+        assertEquals("Custom Currency Pattern, Set Currency", expected, actualSetCurrency);
+        assertEquals("Custom Currency Pattern, Locale String", expected, actualCurrencyAmount);
+        assertEquals("Custom Currency Pattern, CurrencyAmount", expected, actualLocaleString);
+    }
+
     @Test
     public void testPercentZero() {
         DecimalFormat df = (DecimalFormat) NumberFormat.getPercentInstance();
index 10d768080a4aadb23913ee021d65bf0b3a52261f..fee90dae83fe6134c772113f641cba5118be812f 100644 (file)
@@ -31,12 +31,12 @@ public class ModifierTest {
     public void testConstantMultiFieldModifier() {
         NumberStringBuilder prefix = new NumberStringBuilder();
         NumberStringBuilder suffix = new NumberStringBuilder();
-        Modifier mod1 = new ConstantMultiFieldModifier(prefix, suffix, true);
+        Modifier mod1 = new ConstantMultiFieldModifier(prefix, suffix, false, true);
         assertModifierEquals(mod1, 0, true, "|", "n");
 
         prefix.append("a📻", NumberFormat.Field.PERCENT);
         suffix.append("b", NumberFormat.Field.CURRENCY);
-        Modifier mod2 = new ConstantMultiFieldModifier(prefix, suffix, true);
+        Modifier mod2 = new ConstantMultiFieldModifier(prefix, suffix, false, true);
         assertModifierEquals(mod2, 3, true, "a📻|b", "%%%n$");
 
         // Make sure the first modifier is still the same (that it stayed constant)
@@ -91,11 +91,11 @@ public class ModifierTest {
         DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(ULocale.ENGLISH);
         NumberStringBuilder prefix = new NumberStringBuilder();
         NumberStringBuilder suffix = new NumberStringBuilder();
-        Modifier mod1 = new CurrencySpacingEnabledModifier(prefix, suffix, true, symbols);
+        Modifier mod1 = new CurrencySpacingEnabledModifier(prefix, suffix, false, true, symbols);
         assertModifierEquals(mod1, 0, true, "|", "n");
 
         prefix.append("USD", NumberFormat.Field.CURRENCY);
-        Modifier mod2 = new CurrencySpacingEnabledModifier(prefix, suffix, true, symbols);
+        Modifier mod2 = new CurrencySpacingEnabledModifier(prefix, suffix, false, true, symbols);
         assertModifierEquals(mod2, 3, true, "USD|", "$$$n");
 
         // Test the default currency spacing rules
@@ -116,7 +116,7 @@ public class ModifierTest {
                 true,
                 "[|]");
         suffix.append("XYZ", NumberFormat.Field.CURRENCY);
-        Modifier mod3 = new CurrencySpacingEnabledModifier(prefix, suffix, true, symbols);
+        Modifier mod3 = new CurrencySpacingEnabledModifier(prefix, suffix, false, true, symbols);
         assertModifierEquals(mod3, 3, true, "USD|\u00A0XYZ", "$$$nn$$$");
     }
 
index 7069073c14bc85dc7ebb027d9d11c365de753571..7f70a554155f3873cc6d03d8ae7c2ba6a0c8b8c2 100644 (file)
@@ -103,6 +103,32 @@ public class MutablePatternModifierTest {
         assertFalse(nsb1 + " vs. " + nsb3, nsb1.contentEquals(nsb3));
     }
 
+    @Test
+    public void patternWithNoPlaceholder() {
+        MutablePatternModifier mod = new MutablePatternModifier(false);
+        mod.setPatternInfo(PatternStringParser.parseToPatternInfo("abc"));
+        mod.setPatternAttributes(SignDisplay.AUTO, false);
+        mod.setSymbols(DecimalFormatSymbols.getInstance(ULocale.ENGLISH),
+                Currency.getInstance("USD"),
+                UnitWidth.SHORT,
+                null);
+        mod.setNumberProperties(1, null);
+
+        // Unsafe Code Path
+        NumberStringBuilder nsb = new NumberStringBuilder();
+        nsb.append("x123y", null);
+        mod.apply(nsb, 1, 4);
+        assertEquals("Unsafe Path", "xabcy", nsb.toString());
+
+        // Safe Code Path
+        nsb.clear();
+        nsb.append("x123y", null);
+        MicroProps micros = new MicroProps(false);
+        mod.createImmutable().applyToMicros(micros, new DecimalQuantity_DualStorageBCD());
+        micros.modMiddle.apply(nsb, 1, 4);
+        assertEquals("Safe Path", "xabcy", nsb.toString());
+    }
+
     private static String getPrefix(MutablePatternModifier mod) {
         NumberStringBuilder nsb = new NumberStringBuilder();
         mod.apply(nsb, 0, 0);
index e117875264c88e3a6945411ff461e3f8d28c93f1..0bfca024c131e51416a9ca53ad8d53ca37766773 100644 (file)
@@ -19,17 +19,20 @@ import java.util.Set;
 import org.junit.Ignore;
 import org.junit.Test;
 
+import com.ibm.icu.impl.number.Grouper;
+import com.ibm.icu.impl.number.MacroProps;
 import com.ibm.icu.impl.number.Padder;
 import com.ibm.icu.impl.number.Padder.PadPosition;
 import com.ibm.icu.impl.number.PatternStringParser;
+import com.ibm.icu.number.CompactNotation;
 import com.ibm.icu.number.FormattedNumber;
 import com.ibm.icu.number.FractionRounder;
-import com.ibm.icu.number.Grouper;
 import com.ibm.icu.number.IntegerWidth;
 import com.ibm.icu.number.LocalizedNumberFormatter;
 import com.ibm.icu.number.Notation;
 import com.ibm.icu.number.NumberFormatter;
 import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
+import com.ibm.icu.number.NumberFormatter.GroupingStrategy;
 import com.ibm.icu.number.NumberFormatter.SignDisplay;
 import com.ibm.icu.number.NumberFormatter.UnitWidth;
 import com.ibm.icu.number.Rounder;
@@ -51,6 +54,8 @@ public class NumberFormatterApiTest {
     private static final Currency GBP = Currency.getInstance("GBP");
     private static final Currency CZK = Currency.getInstance("CZK");
     private static final Currency CAD = Currency.getInstance("CAD");
+    private static final Currency ESP = Currency.getInstance("ESP");
+    private static final Currency PTE = Currency.getInstance("PTE");
 
     @Test
     public void notationSimple() {
@@ -354,6 +359,19 @@ public class NumberFormatterApiTest {
                 ULocale.ENGLISH,
                 9990000,
                 "10M");
+
+        Map<String, Map<String, String>> compactCustomData = new HashMap<String, Map<String, String>>();
+        Map<String, String> entry = new HashMap<String, String>();
+        entry.put("one", "Kun");
+        entry.put("other", "0KK");
+        compactCustomData.put("1000", entry);
+        assertFormatSingle(
+                "Compact Somali No Figure",
+                "",
+                NumberFormatter.with().notation(CompactNotation.forCustomData(compactCustomData)),
+                ULocale.ENGLISH,
+                1000,
+                "Kun");
     }
 
     @Test
@@ -645,9 +663,59 @@ public class NumberFormatterApiTest {
                 "Currency Difference between Narrow and Short (Short Version)",
                 "",
                 NumberFormatter.with().unit(USD).unitWidth(UnitWidth.SHORT),
-                ULocale.forLanguageTag("en_CA"),
+                ULocale.forLanguageTag("en-CA"),
                 5.43,
-                "US$ 5.43");
+                "US$5.43");
+
+        assertFormatSingle(
+                "Currency-dependent format (Control)",
+                "",
+                NumberFormatter.with().unit(USD).unitWidth(UnitWidth.SHORT),
+                ULocale.forLanguageTag("ca"),
+                444444.55,
+                "444.444,55 USD");
+
+        assertFormatSingle(
+                "Currency-dependent format (Test)",
+                "",
+                NumberFormatter.with().unit(ESP).unitWidth(UnitWidth.SHORT),
+                ULocale.forLanguageTag("ca"),
+                444444.55,
+                "₧ 444.445");
+
+        assertFormatSingle(
+                "Currency-dependent symbols (Control)",
+                "",
+                NumberFormatter.with().unit(USD).unitWidth(UnitWidth.SHORT),
+                ULocale.forLanguageTag("pt-PT"),
+                444444.55,
+                "444 444,55 US$");
+
+        // NOTE: This is a bit of a hack on CLDR's part. They set the currency symbol to U+200B (zero-
+        // width space), and they set the decimal separator to the $ symbol.
+        assertFormatSingle(
+                "Currency-dependent symbols (Test)",
+                "",
+                NumberFormatter.with().unit(PTE).unitWidth(UnitWidth.SHORT),
+                ULocale.forLanguageTag("pt-PT"),
+                444444.55,
+                "444,444$55 \u200B");
+
+        assertFormatSingle(
+                "Currency-dependent symbols (Test)",
+                "",
+                NumberFormatter.with().unit(PTE).unitWidth(UnitWidth.NARROW),
+                ULocale.forLanguageTag("pt-PT"),
+                444444.55,
+                "444,444$55 PTE");
+
+        assertFormatSingle(
+                "Currency-dependent symbols (Test)",
+                "",
+                NumberFormatter.with().unit(PTE).unitWidth(UnitWidth.ISO_CODE),
+                ULocale.forLanguageTag("pt-PT"),
+                444444.55,
+                "444,444$55 PTE");
     }
 
     @Test
@@ -889,6 +957,22 @@ public class NumberFormatterApiTest {
                 "0.09",
                 "0.01",
                 "0.00");
+
+        assertFormatSingle(
+                "FracSig with trailing zeros A",
+                "",
+                NumberFormatter.with().rounding(Rounder.fixedFraction(2).withMinDigits(3)),
+                ULocale.ENGLISH,
+                0.1,
+                "0.10");
+
+        assertFormatSingle(
+                "FracSig with trailing zeros B",
+                "",
+                NumberFormatter.with().rounding(Rounder.fixedFraction(2).withMinDigits(3)),
+                ULocale.ENGLISH,
+                0.0999999,
+                "0.10");
     }
 
     @Test
@@ -1020,7 +1104,7 @@ public class NumberFormatterApiTest {
         assertFormatDescendingBig(
                 "Western Grouping",
                 "grouping=defaults",
-                NumberFormatter.with().grouping(Grouper.defaults()),
+                NumberFormatter.with().grouping(GroupingStrategy.AUTO),
                 ULocale.ENGLISH,
                 "87,650,000",
                 "8,765,000",
@@ -1035,7 +1119,7 @@ public class NumberFormatterApiTest {
         assertFormatDescendingBig(
                 "Indic Grouping",
                 "grouping=defaults",
-                NumberFormatter.with().grouping(Grouper.defaults()),
+                NumberFormatter.with().grouping(GroupingStrategy.AUTO),
                 new ULocale("en-IN"),
                 "8,76,50,000",
                 "87,65,000",
@@ -1050,7 +1134,7 @@ public class NumberFormatterApiTest {
         assertFormatDescendingBig(
                 "Western Grouping, Min 2",
                 "grouping=min2",
-                NumberFormatter.with().grouping(Grouper.minTwoDigits()),
+                NumberFormatter.with().grouping(GroupingStrategy.MIN2),
                 ULocale.ENGLISH,
                 "87,650,000",
                 "8,765,000",
@@ -1065,7 +1149,7 @@ public class NumberFormatterApiTest {
         assertFormatDescendingBig(
                 "Indic Grouping, Min 2",
                 "grouping=min2",
-                NumberFormatter.with().grouping(Grouper.minTwoDigits()),
+                NumberFormatter.with().grouping(GroupingStrategy.MIN2),
                 new ULocale("en-IN"),
                 "8,76,50,000",
                 "87,65,000",
@@ -1080,7 +1164,7 @@ public class NumberFormatterApiTest {
         assertFormatDescendingBig(
                 "No Grouping",
                 "grouping=none",
-                NumberFormatter.with().grouping(Grouper.none()),
+                NumberFormatter.with().grouping(GroupingStrategy.OFF),
                 new ULocale("en-IN"),
                 "87650000",
                 "8765000",
@@ -1091,6 +1175,102 @@ public class NumberFormatterApiTest {
                 "87.65",
                 "8.765",
                 "0");
+
+        // NOTE: Hungarian is interesting because it has minimumGroupingDigits=4 in locale data
+        // If this test breaks due to data changes, find another locale that has minimumGroupingDigits.
+        assertFormatDescendingBig(
+                "Hungarian Grouping",
+                "",
+                NumberFormatter.with().grouping(GroupingStrategy.AUTO),
+                new ULocale("hu"),
+                "87 650 000",
+                "8 765 000",
+                "876500",
+                "87650",
+                "8765",
+                "876,5",
+                "87,65",
+                "8,765",
+                "0");
+
+        assertFormatDescendingBig(
+                "Hungarian Grouping, Min 2",
+                "",
+                NumberFormatter.with().grouping(GroupingStrategy.MIN2),
+                new ULocale("hu"),
+                "87 650 000",
+                "8 765 000",
+                "876500",
+                "87650",
+                "8765",
+                "876,5",
+                "87,65",
+                "8,765",
+                "0");
+
+        assertFormatDescendingBig(
+                "Hungarian Grouping, Always",
+                "",
+                NumberFormatter.with().grouping(GroupingStrategy.ON_ALIGNED),
+                new ULocale("hu"),
+                "87 650 000",
+                "8 765 000",
+                "876 500",
+                "87 650",
+                "8 765",
+                "876,5",
+                "87,65",
+                "8,765",
+                "0");
+
+        // NOTE: Bulgarian is interesting because it has no grouping in the default currency format.
+        // If this test breaks due to data changes, find another locale that has no default grouping.
+        assertFormatDescendingBig(
+                "Bulgarian Currency Grouping",
+                "",
+                NumberFormatter.with().grouping(GroupingStrategy.AUTO).unit(USD),
+                new ULocale("bg"),
+                "87650000,00 щ.д.",
+                "8765000,00 щ.д.",
+                "876500,00 щ.д.",
+                "87650,00 щ.д.",
+                "8765,00 щ.д.",
+                "876,50 щ.д.",
+                "87,65 щ.д.",
+                "8,76 щ.д.",
+                "0,00 щ.д.");
+
+        assertFormatDescendingBig(
+                "Bulgarian Currency Grouping, Always",
+                "",
+                NumberFormatter.with().grouping(GroupingStrategy.ON_ALIGNED).unit(USD),
+                new ULocale("bg"),
+                "87 650 000,00 щ.д.",
+                "8 765 000,00 щ.д.",
+                "876 500,00 щ.д.",
+                "87 650,00 щ.д.",
+                "8 765,00 щ.д.",
+                "876,50 щ.д.",
+                "87,65 щ.д.",
+                "8,76 щ.д.",
+                "0,00 щ.д.");
+
+        MacroProps macros = new MacroProps();
+        macros.grouping = Grouper.getInstance((short) 4, (short) 1, (short) 3);
+        assertFormatDescendingBig(
+                "Custom Grouping via Internal API",
+                "",
+                NumberFormatter.with().macros(macros),
+                ULocale.ENGLISH,
+                "8,7,6,5,0000",
+                "8,7,6,5000",
+                "876500",
+                "87650",
+                "8765",
+                "876.5",
+                "87.65",
+                "8.765",
+                "0");
     }
 
     @Test
index 9e4347feb7623886566155102d934658b432b17f..8a966669273aa7c00b29960821f2f44971b6c9e8 100644 (file)
@@ -190,17 +190,54 @@ public class CurrencyTest extends TestFmwk {
         // Do a basic check of getName()
         // USD { "US$", "US Dollar"            } // 04/04/1792-
         ULocale en = ULocale.ENGLISH;
+        ULocale en_CA = ULocale.forLanguageTag("en-CA");
+        ULocale en_US = ULocale.forLanguageTag("en-US");
+        ULocale en_NZ = ULocale.forLanguageTag("en-NZ");
         boolean[] isChoiceFormat = new boolean[1];
-        Currency usd = Currency.getInstance("USD");
+        Currency USD = Currency.getInstance("USD");
+        Currency CAD = Currency.getInstance("CAD");
+        Currency USX = Currency.getInstance("USX");
         // Warning: HARD-CODED LOCALE DATA in this test.  If it fails, CHECK
         // THE LOCALE DATA before diving into the code.
-        assertEquals("USD.getName(SYMBOL_NAME)",
+        assertEquals("USD.getName(SYMBOL_NAME, en)",
+                "$",
+                USD.getName(en, Currency.SYMBOL_NAME, isChoiceFormat));
+        assertEquals("USD.getName(NARROW_SYMBOL_NAME, en)",
                 "$",
-                usd.getName(en, Currency.SYMBOL_NAME, isChoiceFormat));
-        assertEquals("USD.getName(LONG_NAME)",
+                USD.getName(en, Currency.NARROW_SYMBOL_NAME, isChoiceFormat));
+        assertEquals("USD.getName(LONG_NAME, en)",
                 "US Dollar",
-                usd.getName(en, Currency.LONG_NAME, isChoiceFormat));
-        // TODO add more tests later
+                USD.getName(en, Currency.LONG_NAME, isChoiceFormat));
+        assertEquals("CAD.getName(SYMBOL_NAME, en)",
+                "CA$",
+                CAD.getName(en, Currency.SYMBOL_NAME, isChoiceFormat));
+        assertEquals("CAD.getName(NARROW_SYMBOL_NAME, en)",
+                "$",
+                CAD.getName(en, Currency.NARROW_SYMBOL_NAME, isChoiceFormat));
+        assertEquals("CAD.getName(SYMBOL_NAME, en_CA)",
+                "$",
+                CAD.getName(en_CA, Currency.SYMBOL_NAME, isChoiceFormat));
+        assertEquals("USD.getName(SYMBOL_NAME, en_CA)",
+                "US$",
+                USD.getName(en_CA, Currency.SYMBOL_NAME, isChoiceFormat));
+        assertEquals("USD.getName(NARROW_SYMBOL_NAME, en_CA)",
+                "$",
+                USD.getName(en_CA, Currency.NARROW_SYMBOL_NAME, isChoiceFormat));
+        assertEquals("USD.getName(SYMBOL_NAME) in en_NZ",
+                "US$",
+                USD.getName(en_NZ, Currency.SYMBOL_NAME, isChoiceFormat));
+        assertEquals("CAD.getName(SYMBOL_NAME)",
+                "CA$",
+                CAD.getName(en_NZ, Currency.SYMBOL_NAME, isChoiceFormat));
+        assertEquals("USX.getName(SYMBOL_NAME)",
+                "USX",
+                USX.getName(en_US, Currency.SYMBOL_NAME, isChoiceFormat));
+        assertEquals("USX.getName(NARROW_SYMBOL_NAME)",
+                "USX",
+                USX.getName(en_US, Currency.NARROW_SYMBOL_NAME, isChoiceFormat));
+        assertEquals("USX.getName(LONG_NAME)",
+                "USX",
+                USX.getName(en_US, Currency.LONG_NAME, isChoiceFormat));
     }
 
     @Test