ICU-20956 Fix monetary symbol getters in DecimalFormat

See #987
This commit is contained in:
Shane Carr 2020-03-06 01:53:58 +00:00 committed by Shane F. Carr
parent d7e40df284
commit 1e24bcd721
17 changed files with 203 additions and 203 deletions

View File

@ -167,6 +167,7 @@ DecimalFormatSymbols::operator=(const DecimalFormatSymbols& rhs)
fIsCustomCurrencySymbol = rhs.fIsCustomCurrencySymbol;
fIsCustomIntlCurrencySymbol = rhs.fIsCustomIntlCurrencySymbol;
fCodePointZero = rhs.fCodePointZero;
currPattern = rhs.currPattern;
}
return *this;
}
@ -453,58 +454,16 @@ DecimalFormatSymbols::initialize(const Locale& loc, UErrorCode& status,
}
fCodePointZero = tempCodePointZero;
// Obtain currency data from the currency API. This is strictly
// for backward compatibility; we don't use DecimalFormatSymbols
// for currency data anymore.
// Get the default currency from the currency API.
UErrorCode internalStatus = U_ZERO_ERROR; // don't propagate failures out
UChar curriso[4];
UnicodeString tempStr;
int32_t currisoLength = ucurr_forLocale(locStr, curriso, UPRV_LENGTHOF(curriso), &internalStatus);
if (U_SUCCESS(internalStatus) && currisoLength == 3) {
uprv_getStaticCurrencyName(curriso, locStr, tempStr, internalStatus);
if (U_SUCCESS(internalStatus)) {
fSymbols[kIntlCurrencySymbol].setTo(curriso, currisoLength);
fSymbols[kCurrencySymbol] = tempStr;
}
setCurrency(curriso, status);
} else {
setCurrency(nullptr, status);
}
/* else use the default values. */
//load the currency data
UChar ucc[4]={0}; //Currency Codes are always 3 chars long
int32_t uccLen = 4;
const char* locName = loc.getName();
UErrorCode localStatus = U_ZERO_ERROR;
uccLen = ucurr_forLocale(locName, ucc, uccLen, &localStatus);
// TODO: Currency pattern data loading is duplicated in number_formatimpl.cpp
if(U_SUCCESS(localStatus) && uccLen > 0) {
char cc[4]={0};
u_UCharsToChars(ucc, cc, uccLen);
/* An explicit currency was requested */
LocalUResourceBundlePointer currencyResource(ures_open(U_ICUDATA_CURR, locStr, &localStatus));
LocalUResourceBundlePointer currency(
ures_getByKeyWithFallback(currencyResource.getAlias(), "Currencies", NULL, &localStatus));
ures_getByKeyWithFallback(currency.getAlias(), cc, currency.getAlias(), &localStatus);
if(U_SUCCESS(localStatus) && ures_getSize(currency.getAlias())>2) { // the length is 3 if more data is present
ures_getByIndex(currency.getAlias(), 2, currency.getAlias(), &localStatus);
int32_t currPatternLen = 0;
currPattern =
ures_getStringByIndex(currency.getAlias(), (int32_t)0, &currPatternLen, &localStatus);
UnicodeString decimalSep =
ures_getUnicodeStringByIndex(currency.getAlias(), (int32_t)1, &localStatus);
UnicodeString groupingSep =
ures_getUnicodeStringByIndex(currency.getAlias(), (int32_t)2, &localStatus);
if(U_SUCCESS(localStatus)){
fSymbols[kMonetaryGroupingSeparatorSymbol] = groupingSep;
fSymbols[kMonetarySeparatorSymbol] = decimalSep;
//pattern.setTo(TRUE, currPattern, currPatternLen);
status = localStatus;
}
}
/* else An explicit currency was requested and is unknown or locale data is malformed. */
/* ucurr_* API will get the correct value later on. */
}
// else ignore the error if no currency
// Currency Spacing.
LocalUResourceBundlePointer currencyResource(ures_open(U_ICUDATA_CURR, locStr, &status));
@ -553,9 +512,54 @@ DecimalFormatSymbols::initialize() {
fIsCustomIntlCurrencySymbol = FALSE;
fCodePointZero = 0x30;
U_ASSERT(fCodePointZero == fSymbols[kZeroDigitSymbol].char32At(0));
currPattern = nullptr;
}
void DecimalFormatSymbols::setCurrency(const UChar* currency, UErrorCode& status) {
// TODO: If this method is made public:
// - Adopt ICU4J behavior of not allowing currency to be null.
// - Also verify that the length of currency is 3.
if (!currency) {
return;
}
UnicodeString tempStr;
uprv_getStaticCurrencyName(currency, locale.getName(), tempStr, status);
if (U_SUCCESS(status)) {
fSymbols[kIntlCurrencySymbol].setTo(currency, 3);
fSymbols[kCurrencySymbol] = tempStr;
}
char cc[4]={0};
u_UCharsToChars(currency, cc, 3);
/* An explicit currency was requested */
// TODO(ICU-13297): Move this data loading logic into a centralized place
UErrorCode localStatus = U_ZERO_ERROR;
LocalUResourceBundlePointer rbTop(ures_open(U_ICUDATA_CURR, locale.getName(), &localStatus));
LocalUResourceBundlePointer rb(
ures_getByKeyWithFallback(rbTop.getAlias(), "Currencies", NULL, &localStatus));
ures_getByKeyWithFallback(rb.getAlias(), cc, rb.getAlias(), &localStatus);
if(U_SUCCESS(localStatus) && ures_getSize(rb.getAlias())>2) { // the length is 3 if more data is present
ures_getByIndex(rb.getAlias(), 2, rb.getAlias(), &localStatus);
int32_t currPatternLen = 0;
currPattern =
ures_getStringByIndex(rb.getAlias(), (int32_t)0, &currPatternLen, &localStatus);
UnicodeString decimalSep =
ures_getUnicodeStringByIndex(rb.getAlias(), (int32_t)1, &localStatus);
UnicodeString groupingSep =
ures_getUnicodeStringByIndex(rb.getAlias(), (int32_t)2, &localStatus);
if(U_SUCCESS(localStatus)){
fSymbols[kMonetaryGroupingSeparatorSymbol] = groupingSep;
fSymbols[kMonetarySeparatorSymbol] = decimalSep;
//pattern.setTo(TRUE, currPattern, currPatternLen);
}
}
/* else An explicit currency was requested and is unknown or locale data is malformed. */
/* ucurr_* API will get the correct value later on. */
}
Locale
DecimalFormatSymbols::getLocale(ULocDataLocaleType type, UErrorCode& status) const {
U_LOCALE_BASED(locBased, *this);

View File

@ -1502,8 +1502,11 @@ void DecimalFormat::setCurrency(const char16_t* theCurrency, UErrorCode& ec) {
}
NumberFormat::setCurrency(theCurrency, ec); // to set field for compatibility
fields->properties.currency = currencyUnit;
// TODO: Set values in fields->symbols, too?
touchNoError();
// In Java, the DecimalFormatSymbols is mutable. Why not in C++?
LocalPointer<DecimalFormatSymbols> newSymbols(new DecimalFormatSymbols(*fields->symbols), ec);
newSymbols->setCurrency(currencyUnit.getISOCurrency(), ec);
fields->symbols.adoptInsteadAndCheckErrorCode(newSymbols.orphan(), ec);
touch(ec);
}
void DecimalFormat::setCurrency(const char16_t* theCurrency) {

View File

@ -24,45 +24,6 @@ using namespace icu;
using namespace icu::number;
using namespace icu::number::impl;
namespace {
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: Move this into the CurrencySymbols class?
// 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;
}
} // namespace
MicroPropsGenerator::~MicroPropsGenerator() = default;
@ -179,14 +140,6 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe,
if (isCurrency) {
currency = CurrencyUnit(macros.unit, status); // Restore CurrencyUnit from MeasureUnit
}
const CurrencySymbols* currencySymbols;
if (macros.currencySymbols != nullptr) {
// Used by the DecimalFormat code path
currencySymbols = macros.currencySymbols;
} else {
fWarehouse.fCurrencySymbols = {currency, macros.locale, status};
currencySymbols = &fWarehouse.fCurrencySymbols;
}
UNumberUnitWidth unitWidth = UNUM_UNIT_WIDTH_SHORT;
if (macros.unitWidth != UNUM_UNIT_WIDTH_COUNT) {
unitWidth = macros.unitWidth;
@ -213,41 +166,26 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe,
if (macros.symbols.isDecimalFormatSymbols()) {
fMicros.symbols = macros.symbols.getDecimalFormatSymbols();
} else {
auto newSymbols = new DecimalFormatSymbols(macros.locale, *ns, status);
if (newSymbols == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
LocalPointer<DecimalFormatSymbols> newSymbols(
new DecimalFormatSymbols(macros.locale, *ns, status), status);
if (U_FAILURE(status)) {
return nullptr;
}
fMicros.symbols = newSymbols;
// Give ownership to the NumberFormatterImpl.
fSymbols.adoptInstead(fMicros.symbols);
if (isCurrency) {
newSymbols->setCurrency(currency.getISOCurrency(), status);
if (U_FAILURE(status)) {
return nullptr;
}
}
fMicros.symbols = newSymbols.getAlias();
fSymbols.adoptInstead(newSymbols.orphan());
}
// 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.
auto symbols = new DecimalFormatSymbols(*fMicros.symbols);
if (symbols == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return nullptr;
}
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 (isCurrency && fMicros.symbols->getCurrencyPattern() != nullptr) {
pattern = fMicros.symbols->getCurrencyPattern();
}
if (pattern == nullptr) {
CldrPatternStyle patternStyle;
@ -375,11 +313,12 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe,
if (patternModifier->needsPlurals()) {
patternModifier->setSymbols(
fMicros.symbols,
currencySymbols,
currency,
unitWidth,
resolvePluralRules(macros.rules, macros.locale, status));
resolvePluralRules(macros.rules, macros.locale, status),
status);
} else {
patternModifier->setSymbols(fMicros.symbols, currencySymbols, unitWidth, nullptr);
patternModifier->setSymbols(fMicros.symbols, currency, unitWidth, nullptr, status);
}
if (safe) {
fImmutablePatternModifier.adoptInstead(patternModifier->createImmutable(status));

View File

@ -13,7 +13,6 @@
#include "number_patternstring.h"
#include "unicode/errorcode.h"
#include "number_utils.h"
#include "number_currencysymbols.h"
using namespace icu;
using namespace icu::number;
@ -81,8 +80,6 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert
// NOTE: Slicing is OK.
macros.unit = currency; // NOLINT
}
warehouse.currencySymbols = {currency, locale, symbols, status};
macros.currencySymbols = &warehouse.currencySymbols;
///////////////////////
// ROUNDING STRATEGY //

View File

@ -155,7 +155,7 @@ class AutoAffixPatternProvider {
*/
struct DecimalFormatWarehouse {
AutoAffixPatternProvider affixProvider;
CurrencySymbols currencySymbols;
};

View File

@ -34,11 +34,13 @@ void MutablePatternModifier::setPatternAttributes(UNumberSignDisplay signDisplay
}
void MutablePatternModifier::setSymbols(const DecimalFormatSymbols* symbols,
const CurrencySymbols* currencySymbols,
const UNumberUnitWidth unitWidth, const PluralRules* rules) {
const CurrencyUnit& currency,
const UNumberUnitWidth unitWidth,
const PluralRules* rules,
UErrorCode& status) {
U_ASSERT((rules != nullptr) == needsPlurals());
fSymbols = symbols;
fCurrencySymbols = currencySymbols;
fCurrencySymbols = {currency, symbols->getLocale(), *symbols, status};
fUnitWidth = unitWidth;
fRules = rules;
}
@ -294,23 +296,23 @@ UnicodeString MutablePatternModifier::getSymbol(AffixPatternType type) const {
case AffixPatternType::TYPE_CURRENCY_SINGLE: {
// UnitWidth ISO and HIDDEN overrides the singular currency symbol.
if (fUnitWidth == UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE) {
return fCurrencySymbols->getIntlCurrencySymbol(localStatus);
return fCurrencySymbols.getIntlCurrencySymbol(localStatus);
} else if (fUnitWidth == UNumberUnitWidth::UNUM_UNIT_WIDTH_HIDDEN) {
return UnicodeString();
} else if (fUnitWidth == UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW) {
return fCurrencySymbols->getNarrowCurrencySymbol(localStatus);
return fCurrencySymbols.getNarrowCurrencySymbol(localStatus);
} else {
return fCurrencySymbols->getCurrencySymbol(localStatus);
return fCurrencySymbols.getCurrencySymbol(localStatus);
}
}
case AffixPatternType::TYPE_CURRENCY_DOUBLE:
return fCurrencySymbols->getIntlCurrencySymbol(localStatus);
return fCurrencySymbols.getIntlCurrencySymbol(localStatus);
case AffixPatternType::TYPE_CURRENCY_TRIPLE:
// NOTE: This is the code path only for patterns containing "¤¤¤".
// Plural currencies set via the API are formatted in LongNameHandler.
// This code path is used by DecimalFormat via CurrencyPluralInfo.
U_ASSERT(fPlural != StandardPlural::Form::COUNT);
return fCurrencySymbols->getPluralName(fPlural, localStatus);
return fCurrencySymbols.getPluralName(fPlural, localStatus);
case AffixPatternType::TYPE_CURRENCY_QUAD:
return UnicodeString(u"\uFFFD");
case AffixPatternType::TYPE_CURRENCY_QUINT:

View File

@ -124,16 +124,18 @@ class U_I18N_API MutablePatternModifier
*
* @param symbols
* The desired instance of DecimalFormatSymbols.
* @param currencySymbols
* The currency symbols to be used when substituting currency values into the affixes.
* @param currency
* The currency to be used when substituting currency values into the affixes.
* @param unitWidth
* The width used to render currencies.
* @param rules
* Required if the triple currency sign, "¤¤¤", appears in the pattern, which can be determined from the
* convenience method {@link #needsPlurals()}.
* @param status
* Set if an error occurs while loading currency data.
*/
void setSymbols(const DecimalFormatSymbols* symbols, const CurrencySymbols* currencySymbols,
UNumberUnitWidth unitWidth, const PluralRules* rules);
void setSymbols(const DecimalFormatSymbols* symbols, const CurrencyUnit& currency,
UNumberUnitWidth unitWidth, const PluralRules* rules, UErrorCode& status);
/**
* Sets attributes of the current number being processed.
@ -206,7 +208,7 @@ class U_I18N_API MutablePatternModifier
// Symbol details (initialized in setSymbols)
const DecimalFormatSymbols *fSymbols;
UNumberUnitWidth fUnitWidth;
const CurrencySymbols *fCurrencySymbols;
CurrencySymbols fCurrencySymbols;
const PluralRules *fRules;
// Number details (initialized in setNumberProperties)

View File

@ -885,10 +885,6 @@ void GeneratorHelpers::generateSkeleton(const MacroProps& macros, UnicodeString&
status = U_UNSUPPORTED_ERROR;
return;
}
if (macros.currencySymbols != nullptr) {
status = U_UNSUPPORTED_ERROR;
return;
}
// Remove the trailing space
if (sb.length() > 0) {

View File

@ -291,6 +291,17 @@ public:
*/
void setSymbol(ENumberFormatSymbol symbol, const UnicodeString &value, const UBool propogateDigits);
#ifndef U_HIDE_INTERNAL_API
/**
* Loads symbols for the specified currency into this instance.
*
* This method is internal. If you think it should be public, file a ticket.
*
* @internal
*/
void setCurrency(const UChar* currency, UErrorCode& status);
#endif // U_HIDE_INTERNAL_API
/**
* Returns the locale for which this object was constructed.
* @stable ICU 2.6
@ -374,8 +385,6 @@ private:
*/
void initialize();
void setCurrencyForSymbols();
public:
#ifndef U_HIDE_INTERNAL_API

View File

@ -1415,9 +1415,6 @@ struct U_I18N_API MacroProps : public UMemory {
/** @internal */
const PluralRules* rules = nullptr; // no ownership
/** @internal */
const CurrencySymbols* currencySymbols = nullptr; // no ownership
/** @internal */
int32_t threshold = kInternalDefaultThreshold;

View File

@ -29,11 +29,10 @@ void PatternModifierTest::testBasic() {
mod.setPatternInfo(&patternInfo, kUndefinedField);
mod.setPatternAttributes(UNUM_SIGN_AUTO, false);
DecimalFormatSymbols symbols(Locale::getEnglish(), status);
CurrencySymbols currencySymbols({u"USD", status}, "en", status);
mod.setSymbols(&symbols, {u"USD", status}, UNUM_UNIT_WIDTH_SHORT, nullptr, status);
if (!assertSuccess("Spot 2", status, true)) {
return;
}
mod.setSymbols(&symbols, &currencySymbols, UNUM_UNIT_WIDTH_SHORT, nullptr);
mod.setNumberProperties(SIGNUM_POS, StandardPlural::Form::COUNT);
assertEquals("Pattern a0b", u"a", getPrefix(mod, status));
@ -96,11 +95,10 @@ void PatternModifierTest::testPatternWithNoPlaceholder() {
mod.setPatternInfo(&patternInfo, kUndefinedField);
mod.setPatternAttributes(UNUM_SIGN_AUTO, false);
DecimalFormatSymbols symbols(Locale::getEnglish(), status);
CurrencySymbols currencySymbols({u"USD", status}, "en", status);
mod.setSymbols(&symbols, {u"USD", status}, UNUM_UNIT_WIDTH_SHORT, nullptr, status);
if (!assertSuccess("Spot 2", status, true)) {
return;
}
mod.setSymbols(&symbols, &currencySymbols, UNUM_UNIT_WIDTH_SHORT, nullptr);
mod.setNumberProperties(SIGNUM_POS, StandardPlural::Form::COUNT);
// Unsafe Code Path
@ -139,10 +137,9 @@ void PatternModifierTest::testMutableEqualsImmutable() {
mod.setPatternInfo(&patternInfo, kUndefinedField);
mod.setPatternAttributes(UNUM_SIGN_AUTO, false);
DecimalFormatSymbols symbols(Locale::getEnglish(), status);
CurrencySymbols currencySymbols({u"USD", status}, "en", status);
mod.setSymbols(&symbols, {u"USD", status}, UNUM_UNIT_WIDTH_SHORT, nullptr, status);
assertSuccess("Spot 2", status);
if (U_FAILURE(status)) { return; }
mod.setSymbols(&symbols, &currencySymbols, UNUM_UNIT_WIDTH_SHORT, nullptr);
DecimalQuantity fq;
fq.setToInt(1);

View File

@ -238,6 +238,7 @@ void NumberFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &n
TESTCASE_AUTO(Test13840_ParseLongStringCrash);
TESTCASE_AUTO(Test13850_EmptyStringCurrency);
TESTCASE_AUTO(Test20348_CurrencyPrefixOverride);
TESTCASE_AUTO(Test20956_MonetarySymbolGetters);
TESTCASE_AUTO(Test20358_GroupingInPattern);
TESTCASE_AUTO(Test13731_DefaultCurrency);
TESTCASE_AUTO(Test20499_CurrencyVisibleDigitsPlural);
@ -9568,12 +9569,12 @@ void NumberFormatTest::Test13850_EmptyStringCurrency() {
const char16_t* currencyArg;
UErrorCode expectedError;
} cases[] = {
{u"", U_ZERO_ERROR},
{u"", U_USING_FALLBACK_WARNING},
{u"U", U_ILLEGAL_ARGUMENT_ERROR},
{u"Us", U_ILLEGAL_ARGUMENT_ERROR},
{nullptr, U_ZERO_ERROR},
{nullptr, U_USING_FALLBACK_WARNING},
{u"U$D", U_INVARIANT_CONVERSION_ERROR},
{u"Xxx", U_ZERO_ERROR}
{u"Xxx", U_USING_FALLBACK_WARNING}
};
for (const auto& cas : cases) {
UnicodeString message(u"with currency arg: ");
@ -9636,6 +9637,35 @@ void NumberFormatTest::Test20348_CurrencyPrefixOverride() {
u"$100.00", fmt->format(100, result.remove(), NULL, status));
}
void NumberFormatTest::Test20956_MonetarySymbolGetters() {
IcuTestErrorCode status(*this, "Test20956_MonetarySymbolGetters");
LocalPointer<DecimalFormat> decimalFormat(static_cast<DecimalFormat*>(
NumberFormat::createCurrencyInstance("et", status)));
decimalFormat->setCurrency(u"EEK");
const DecimalFormatSymbols* decimalFormatSymbols = decimalFormat->getDecimalFormatSymbols();
assertEquals("MONETARY DECIMAL SEPARATOR",
u".",
decimalFormatSymbols->getSymbol(DecimalFormatSymbols::kMonetarySeparatorSymbol));
assertEquals("DECIMAL SEPARATOR",
u",",
decimalFormatSymbols->getSymbol(DecimalFormatSymbols::kDecimalSeparatorSymbol));
assertEquals("MONETARY GROUPING SEPARATOR",
u" ",
decimalFormatSymbols->getSymbol(DecimalFormatSymbols::kMonetaryGroupingSeparatorSymbol));
assertEquals("GROUPING SEPARATOR",
u" ",
decimalFormatSymbols->getSymbol(DecimalFormatSymbols::kGroupingSeparatorSymbol));
assertEquals("CURRENCY SYMBOL",
u"kr",
decimalFormatSymbols->getSymbol(DecimalFormatSymbols::kCurrencySymbol));
UnicodeString sb;
decimalFormat->format(12345.12, sb, status);
assertEquals("OUTPUT", u"12 345.12 kr", sb);
}
void NumberFormatTest::Test20358_GroupingInPattern() {
IcuTestErrorCode status(*this, "Test20358_GroupingInPattern");
LocalPointer<DecimalFormat> fmt(static_cast<DecimalFormat*>(

View File

@ -294,6 +294,7 @@ class NumberFormatTest: public CalendarTimeZoneTest {
void Test13840_ParseLongStringCrash();
void Test13850_EmptyStringCurrency();
void Test20348_CurrencyPrefixOverride();
void Test20956_MonetarySymbolGetters();
void Test20358_GroupingInPattern();
void Test13731_DefaultCurrency();
void Test20499_CurrencyVisibleDigitsPlural();

View File

@ -2,8 +2,6 @@
// 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.FormattedStringBuilder;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.number.CompactData.CompactType;
@ -213,21 +211,16 @@ class NumberFormatterImpl {
micros.symbols = (DecimalFormatSymbols) macros.symbols;
} else {
micros.symbols = DecimalFormatSymbols.forNumberingSystem(macros.loc, ns);
if (isCurrency) {
micros.symbols.setCurrency(currency);
}
}
// 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 (isCurrency && micros.symbols.getCurrencyPattern() != null) {
pattern = micros.symbols.getCurrencyPattern();
}
if (pattern == null) {
int patternStyle;

View File

@ -787,7 +787,15 @@ public class DecimalFormat extends NumberFormat {
*/
@Override
public StringBuffer format(CurrencyAmount currAmt, StringBuffer result, FieldPosition fieldPosition) {
FormattedNumber output = formatter.format(currAmt);
// We need to make localSymbols in order for monetary symbols to be initialized.
// Also, bypass the CurrencyAmount override of LocalizedNumberFormatter#format,
// because its caching mechanism will not provide any benefit here.
DecimalFormatSymbols localSymbols = (DecimalFormatSymbols) symbols.clone();
localSymbols.setCurrency(currAmt.getCurrency());
FormattedNumber output = formatter
.symbols(localSymbols)
.unit(currAmt.getCurrency())
.format(currAmt.getNumber());
fieldPositionHelper(output, fieldPosition, result.length());
output.appendTo(result);
return result;
@ -2043,11 +2051,8 @@ public class DecimalFormat extends NumberFormat {
@Override
public synchronized void setCurrency(Currency currency) {
properties.setCurrency(currency);
// Backwards compatibility: also set the currency in the DecimalFormatSymbols
if (currency != null) {
symbols.setCurrency(currency);
String symbol = currency.getName(symbols.getULocale(), Currency.SYMBOL_NAME, null);
symbols.setCurrencySymbol(symbol);
}
refreshFormatter();
}

View File

@ -888,9 +888,32 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
if (currency == null) {
throw new NullPointerException();
}
if (currency.equals(this.currency)) {
return;
}
CurrencyDisplayInfo displayInfo = CurrencyData.provider.getInstance(ulocale, true);
setCurrencyOrNull(currency, displayInfo);
}
private void setCurrencyOrNull(Currency currency, CurrencyDisplayInfo displayInfo) {
this.currency = currency;
if (currency == null) {
intlCurrencySymbol = "XXX";
currencySymbol = "\u00A4"; // 'OX' currency symbol
currencyPattern = null;
return;
}
intlCurrencySymbol = currency.getCurrencyCode();
currencySymbol = currency.getSymbol(requestedLocale);
currencySymbol = currency.getSymbol(ulocale);
CurrencyFormatInfo formatInfo = displayInfo.getFormatInfo(currency.getCurrencyCode());
if (formatInfo != null) {
setMonetaryDecimalSeparatorString(formatInfo.monetaryDecimalSeparator);
setMonetaryGroupingSeparatorString(formatInfo.monetaryGroupingSeparator);
currencyPattern = formatInfo.currencyPattern;
}
}
/**
@ -1004,11 +1027,12 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
}
/**
}
* Internal API for NumberFormat
* @return String currency pattern string
* @deprecated This API is for ICU internal use only
*/
String getCurrencyPattern() {
@Deprecated
public String getCurrencyPattern() {
return currencyPattern;
}
@ -1396,30 +1420,10 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
padEscape = '*';
sigDigit = '@';
CurrencyDisplayInfo displayInfo = CurrencyData.provider.getInstance(ulocale, true);
initSpacingInfo(displayInfo.getSpacingInfo());
CurrencyDisplayInfo info = CurrencyData.provider.getInstance(locale, true);
// Obtain currency data from the currency API. This is strictly
// for backward compatibility; we don't use DecimalFormatSymbols
// for currency data anymore.
currency = Currency.getInstance(locale);
if (currency != null) {
intlCurrencySymbol = currency.getCurrencyCode();
currencySymbol = currency.getName(locale, Currency.SYMBOL_NAME, null);
CurrencyFormatInfo fmtInfo = info.getFormatInfo(intlCurrencySymbol);
if (fmtInfo != null) {
currencyPattern = fmtInfo.currencyPattern;
setMonetaryDecimalSeparatorString(fmtInfo.monetaryDecimalSeparator);
setMonetaryGroupingSeparatorString(fmtInfo.monetaryGroupingSeparator);
}
} else {
intlCurrencySymbol = "XXX";
currencySymbol = "\u00A4"; // 'OX' currency symbol
}
// Get currency spacing data.
initSpacingInfo(info.getSpacingInfo());
setCurrencyOrNull(Currency.getInstance(ulocale), displayInfo);
}
private static CacheData loadData(ULocale locale) {

View File

@ -43,6 +43,7 @@ import org.junit.runners.JUnit4;
import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.dev.test.TestUtil;
import com.ibm.icu.dev.test.format.IntlTestDecimalFormatAPIC.FieldContainer;
import com.ibm.icu.impl.DontCareFieldPosition;
import com.ibm.icu.impl.ICUConfig;
import com.ibm.icu.impl.LocaleUtility;
import com.ibm.icu.impl.data.ResourceReader;
@ -6669,6 +6670,26 @@ public class NumberFormatTest extends TestFmwk {
"$100.00", fmt.format(100));
}
@Test
public void test20956_MonetarySymbolGetters() {
Locale locale = new Locale.Builder().setLocale(Locale.forLanguageTag("et")).build();
DecimalFormat decimalFormat = (DecimalFormat) NumberFormat.getCurrencyInstance(locale);
Currency currency = Currency.getInstance("EEK");
decimalFormat.setCurrency(currency);
DecimalFormatSymbols decimalFormatSymbols = decimalFormat.getDecimalFormatSymbols();
assertEquals("MONETARY DECIMAL SEPARATOR", ".", decimalFormatSymbols.getMonetaryDecimalSeparatorString());
assertEquals("DECIMAL SEPARATOR", ",", decimalFormatSymbols.getDecimalSeparatorString());
assertEquals("MONETARY GROUPING SEPARATOR", " ", decimalFormatSymbols.getMonetaryGroupingSeparatorString());
assertEquals("GROUPING SEPARATOR", " ", decimalFormatSymbols.getGroupingSeparatorString());
assertEquals("CURRENCY SYMBOL", "kr", decimalFormatSymbols.getCurrencySymbol());
StringBuffer sb = new StringBuffer();
decimalFormat.format(new BigDecimal(12345.12), sb, DontCareFieldPosition.INSTANCE);
assertEquals("OUTPUT", "12 345.12 kr", sb.toString());
}
@Test
public void test20358_GroupingInPattern() {
DecimalFormat fmt = (DecimalFormat) NumberFormat.getInstance(ULocale.ENGLISH);