#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"
// 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};
}
int32_t choice = (int32_t) nameStyle;
- if (choice < 0 || choice > 1) {
+ if (choice < 0 || choice > 2) {
*ec = U_ILLEGAL_ARGUMENT_ERROR;
return 0;
}
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.
* 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
buildReference.setPatternInfo(&patternInfo);
info.mod = buildReference.createImmutable(status);
if (U_FAILURE(status)) { return; }
- info.numDigits = patternInfo.positive.integerTotal;
info.patternString = patternString;
}
}
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.
const CompactModInfo &info = precomputedMods[i];
if (u_strcmp(patternString, info.patternString) == 0) {
info.mod->applyToMicros(micros, quantity);
- numDigits = info.numDigits;
break;
}
}
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();
}
struct CompactModInfo {
const ImmutablePatternModifier *mod;
const UChar* patternString;
- int32_t numDigits;
};
class CompactHandler : public MicroPropsGenerator, public UMemory {
}
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;
}
#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;
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;
}
}
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);
/// 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;
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()) {
#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;
}
}
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 */
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;
}
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) {
*/
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;
// value and is treated internally as immutable.
NumberStringBuilder fPrefix;
NumberStringBuilder fSuffix;
+ bool fOverwrite;
bool fStrong;
};
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;
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);
}
}
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 {
} 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);
return AffixUtils::containsType(UnicodeStringCharSequence(pattern), type, status);
}
+bool ParsedPatternInfo::hasBody() const {
+ return positive.integerTotal > 0;
+}
+
/////////////////////////////////////////////////////
/// BEGIN RECURSIVE DESCENT PARSER IMPLEMENTATION ///
/////////////////////////////////////////////////////
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
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;
};
/**
* </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
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>:
class FractionRounder;
class CurrencyRounder;
class IncrementRounder;
-class Grouper;
class IntegerWidth;
namespace impl {
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.
*
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:
*/
Derived rounding(const Rounder &rounder) const;
-#ifndef U_HIDE_INTERNAL_API
-
/**
* Specifies the grouping strategy to use when formatting numbers.
*
* 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.
CurrencyUnit GBP;
CurrencyUnit CZK;
CurrencyUnit CAD;
+ CurrencyUnit ESP;
+ CurrencyUnit PTE;
MeasureUnit METER;
MeasureUnit DAY;
class PatternModifierTest : public IntlTest {
public:
void testBasic();
+ void testPatternWithNoPlaceholder();
void testMutableEqualsImmutable();
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0);
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) {
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() {
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() {
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() {
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",
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",
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",
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",
assertFormatDescendingBig(
u"No Grouping",
- NumberFormatter::with().grouping(Grouper::none()),
+ NumberFormatter::with().grouping(UNUM_GROUPING_OFF),
Locale("en-IN"),
u"87650000",
u"8765000",
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() {
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);
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);
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);
}
TESTCASE_AUTO_BEGIN;
TESTCASE_AUTO(testBasic);
+ TESTCASE_AUTO(testPatternWithNoPlaceholder);
TESTCASE_AUTO(testMutableEqualsImmutable);
TESTCASE_AUTO_END;
}
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);
#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
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);
TESTCASE_AUTO(Test11649_toPatternWithMultiCurrency);
TESTCASE_AUTO(Test13327_numberingSystemBufferOverflow);
TESTCASE_AUTO(Test13391_chakmaParsing);
+ TESTCASE_AUTO(Test11035_FormatCurrencyAmount);
TESTCASE_AUTO_END;
}
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;
}
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;
}
delete mFormat;
}
-
+
NumberFormat *cFormat = NumberFormat::createInstance(en_US, UNUM_CURRENCY, status);
if (cFormat == NULL || U_FAILURE(status)) {
// 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]);
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",
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",
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());
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..
*/
}
fmt.getLocale(ULOC_ACTUAL_LOCALE, status).getBaseName(),
text);
u_austrcpy(theInfo+uprv_strlen(theInfo), currency);
-
+
char theOperation[100];
uprv_strcpy(theOperation, theInfo);
uprv_strcat(theOperation, ", check currency:");
assertEquals(theOperation, currency, currencyAmount->getISOCurrency());
}
-
+
void NumberFormatTest::TestJB3832(){
const char* localeID = "pt_PT@currency=PTE";
NumberFormat *fmt = (NumberFormat *) origFmt->clone();
delete origFmt;
-
+
if (item->isRBNF) {
expect3(*fmt,item->value,CharsToUnicodeString(item->expectedResult));
} else {
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)) {
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,
//
// 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", \
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().
}
}
-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() {
{
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);
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);
}
}
#else
infoln("NOTE: UCONFIG_FORMAT_FASTPATHS not set, test skipped.");
-#endif
+#endif
// get some additional case
{
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 {
numberFormat->setMinimumSignificantDigits(3);
numberFormat->setMaximumSignificantDigits(5);
numberFormat->setGroupingUsed(false);
-
+
UnicodeString result;
UnicodeString expectedResult;
for (unsigned int i = 0; i < UPRV_LENGTHOF(input); ++i) {
numberFormat->setSignificantDigitsUsed(TRUE);
numberFormat->setMaximumSignificantDigits(3);
-
+
UnicodeString result;
numberFormat->format(0.0, result);
if (result != "0") {
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__);
}
if (numberFormat->areSignificantDigitsUsed() == FALSE) {
errln("File %s, Line %d: areSignificantDigitsUsed() was FALSE, expected TRUE.\n", __FILE__, __LINE__);
}
-
+
}
void NumberFormatTest::TestParseNegativeWithFaLocale() {
{ "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 },
// 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.");
}
errcheckln(status, "DecimalFormat constructor failed - %s", u_errorName(status));
return;
}
-
+
DecimalFormat::ERoundingMode roundingModes[] = {
DecimalFormat::kRoundCeiling,
DecimalFormat::kRoundDown,
"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.
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;
}
DigitList d;
d.set(-0.0);
assertFalse("", d.isPositive());
- d.round(3);
+ d.round(3);
assertFalse("", d.isPositive());
}
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));
static UChar USD[] = {0x55, 0x53, 0x44, 0x0};
fmt.setCurrency(USD);
UnicodeString appendTo;
-
+
assertEquals("", "US dollars 12.34", fmt.format(12.34, appendTo));
UnicodeString topattern;
return;
}
fmt2.setCurrency(USD);
-
+
appendTo.remove();
assertEquals("", "US dollars 12.34", fmt2.format(12.34, appendTo));
}
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 */
void Test13391_chakmaParsing();
void checkExceptionIssue11735();
+ void Test11035_FormatCurrencyAmount();
private:
UBool testFormattableAsUFormattable(const char *file, int line, Formattable &f);
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 {
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();
}
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;
}
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
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.
--- /dev/null
+// © 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
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;
public MeasureUnit unit;
public MeasureUnit perUnit;
public Rounder rounder;
- public Grouper grouper;
+ public Object grouping;
public Padder padder;
public IntegerWidth integerWidth;
public Object symbols;
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)
unit,
perUnit,
rounder,
- grouper,
+ grouping,
padder,
integerWidth,
symbols,
&& 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)
// 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;
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);
}
}
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
/**
* 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.
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();
public boolean containsSymbolType(int type) {
return AffixUtils.containsType(pattern, type);
}
+
+ @Override
+ public boolean hasBody() {
+ return positive.integerTotal > 0;
+ }
}
public static class ParsedSubpatternInfo {
|| AffixUtils.containsType(negPrefix, type) || AffixUtils.containsType(negSuffix, type);
}
+ @Override
+ public boolean hasBody() {
+ return true;
+ }
+
@Override
public String toString() {
return super.toString()
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;
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;
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));
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;
// 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;
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;
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;
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(
}
if (buildReference != null) {
// Safe code path
- precomputedMods = new HashMap<String, CompactModInfo>();
+ precomputedMods = new HashMap<String, ImmutablePatternModifier>();
precomputeAllModifiers(buildReference);
} else {
// Unsafe code path
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());
}
}
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();
+++ /dev/null
-// © 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
}
/**
- * 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>
// 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;
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;
}
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);
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) {
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;
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;
* 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);
}
/**
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:
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;
// GROUPING STRATEGY //
///////////////////////
- macros.grouper = Grouper.defaults().withProperties(properties);
+ macros.grouping = Grouper.forProperties(properties);
/////////////
// PADDING //
/**
* 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
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;
* 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 =
* 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
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:
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
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();
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)
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
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$$$");
}
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);
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;
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() {
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
"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
"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
assertFormatDescendingBig(
"Western Grouping",
"grouping=defaults",
- NumberFormatter.with().grouping(Grouper.defaults()),
+ NumberFormatter.with().grouping(GroupingStrategy.AUTO),
ULocale.ENGLISH,
"87,650,000",
"8,765,000",
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",
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",
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",
assertFormatDescendingBig(
"No Grouping",
"grouping=none",
- NumberFormatter.with().grouping(Grouper.none()),
+ NumberFormatter.with().grouping(GroupingStrategy.OFF),
new ULocale("en-IN"),
"87650000",
"8765000",
"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
// 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