mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-07 06:25:30 +00:00
parent
fd5c76edbd
commit
f39cf84d62
30 changed files with 450 additions and 76 deletions
|
@ -1077,7 +1077,7 @@ void DecimalFormat::setFormatWidth(int32_t width) {
|
|||
UnicodeString DecimalFormat::getPadCharacterString() const {
|
||||
if (fields == nullptr || fields->properties.padString.isBogus()) {
|
||||
// Readonly-alias the static string kFallbackPaddingString
|
||||
return {TRUE, kFallbackPaddingString, -1};
|
||||
return {true, kFallbackPaddingString, -1};
|
||||
} else {
|
||||
return fields->properties.padString;
|
||||
}
|
||||
|
@ -1322,6 +1322,7 @@ UnicodeString& DecimalFormat::toPattern(UnicodeString& result) const {
|
|||
!tprops.currency.isNull() ||
|
||||
!tprops.currencyPluralInfo.fPtr.isNull() ||
|
||||
!tprops.currencyUsage.isNull() ||
|
||||
tprops.currencyAsDecimal ||
|
||||
AffixUtils::hasCurrencySymbols(tprops.positivePrefixPattern, localStatus) ||
|
||||
AffixUtils::hasCurrencySymbols(tprops.positiveSuffixPattern, localStatus) ||
|
||||
AffixUtils::hasCurrencySymbols(tprops.negativePrefixPattern, localStatus) ||
|
||||
|
|
|
@ -76,7 +76,7 @@ UnicodeString CurrencySymbols::loadSymbol(UCurrNameStyle selector, UErrorCode& s
|
|||
if (symbol == isoCode) {
|
||||
return UnicodeString(isoCode, 3);
|
||||
} else {
|
||||
return UnicodeString(TRUE, symbol, symbolLen);
|
||||
return UnicodeString(true, symbol, symbolLen);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,7 +104,7 @@ UnicodeString CurrencySymbols::getPluralName(StandardPlural::Form plural, UError
|
|||
if (symbol == isoCode) {
|
||||
return UnicodeString(isoCode, 3);
|
||||
} else {
|
||||
return UnicodeString(TRUE, symbol, symbolLen);
|
||||
return UnicodeString(true, symbol, symbolLen);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ void DecimalFormatProperties::clear() {
|
|||
decimalPatternMatchRequired = false;
|
||||
decimalSeparatorAlwaysShown = false;
|
||||
exponentSignAlwaysShown = false;
|
||||
currencyAsDecimal = false;
|
||||
formatFailIfMoreThanMaxDigits = false;
|
||||
formatWidth = -1;
|
||||
groupingSize = -1;
|
||||
|
@ -88,6 +89,7 @@ DecimalFormatProperties::_equals(const DecimalFormatProperties& other, bool igno
|
|||
eq = eq && currencyUsage == other.currencyUsage;
|
||||
eq = eq && decimalSeparatorAlwaysShown == other.decimalSeparatorAlwaysShown;
|
||||
eq = eq && exponentSignAlwaysShown == other.exponentSignAlwaysShown;
|
||||
eq = eq && currencyAsDecimal == other.currencyAsDecimal;
|
||||
eq = eq && formatFailIfMoreThanMaxDigits == other.formatFailIfMoreThanMaxDigits;
|
||||
eq = eq && formatWidth == other.formatWidth;
|
||||
eq = eq && magnitudeMultiplier == other.magnitudeMultiplier;
|
||||
|
|
|
@ -105,6 +105,7 @@ struct U_I18N_API DecimalFormatProperties : public UMemory {
|
|||
bool decimalPatternMatchRequired;
|
||||
bool decimalSeparatorAlwaysShown;
|
||||
bool exponentSignAlwaysShown;
|
||||
bool currencyAsDecimal;
|
||||
bool formatFailIfMoreThanMaxDigits; // ICU4C-only
|
||||
int32_t formatWidth;
|
||||
int32_t groupingSize;
|
||||
|
|
|
@ -352,10 +352,11 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe,
|
|||
return nullptr;
|
||||
}
|
||||
fPatternModifier.adoptInstead(patternModifier);
|
||||
patternModifier->setPatternInfo(
|
||||
macros.affixProvider != nullptr ? macros.affixProvider
|
||||
: static_cast<const AffixPatternProvider*>(fPatternInfo.getAlias()),
|
||||
kUndefinedField);
|
||||
const AffixPatternProvider* affixProvider =
|
||||
macros.affixProvider != nullptr
|
||||
? macros.affixProvider
|
||||
: static_cast<const AffixPatternProvider*>(fPatternInfo.getAlias());
|
||||
patternModifier->setPatternInfo(affixProvider, kUndefinedField);
|
||||
patternModifier->setPatternAttributes(fMicros.sign, isPermille, macros.approximately);
|
||||
if (patternModifier->needsPlurals()) {
|
||||
patternModifier->setSymbols(
|
||||
|
@ -375,6 +376,11 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe,
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
// currencyAsDecimal
|
||||
if (affixProvider->currencyAsDecimal()) {
|
||||
fMicros.currencyAsDecimal = patternModifier->getCurrencySymbolForUnitWidth(status);
|
||||
}
|
||||
|
||||
// Outer modifier (CLDR units and currency long names)
|
||||
if (isCldrUnit) {
|
||||
const char *unitDisplayCase = "";
|
||||
|
@ -524,15 +530,27 @@ int32_t NumberFormatterImpl::writeNumber(const MicroProps& micros, DecimalQuanti
|
|||
|
||||
// Add the decimal point
|
||||
if (quantity.getLowerDisplayMagnitude() < 0 || micros.decimal == UNUM_DECIMAL_SEPARATOR_ALWAYS) {
|
||||
length += string.insert(
|
||||
if (!micros.currencyAsDecimal.isBogus()) {
|
||||
length += string.insert(
|
||||
length + index,
|
||||
micros.useCurrency ? micros.symbols->getSymbol(
|
||||
DecimalFormatSymbols::ENumberFormatSymbol::kMonetarySeparatorSymbol) : micros
|
||||
.symbols
|
||||
->getSymbol(
|
||||
DecimalFormatSymbols::ENumberFormatSymbol::kDecimalSeparatorSymbol),
|
||||
micros.currencyAsDecimal,
|
||||
{UFIELD_CATEGORY_NUMBER, UNUM_CURRENCY_FIELD},
|
||||
status);
|
||||
} else if (micros.useCurrency) {
|
||||
length += string.insert(
|
||||
length + index,
|
||||
micros.symbols->getSymbol(
|
||||
DecimalFormatSymbols::ENumberFormatSymbol::kMonetarySeparatorSymbol),
|
||||
{UFIELD_CATEGORY_NUMBER, UNUM_DECIMAL_SEPARATOR_FIELD},
|
||||
status);
|
||||
} else {
|
||||
length += string.insert(
|
||||
length + index,
|
||||
micros.symbols->getSymbol(
|
||||
DecimalFormatSymbols::ENumberFormatSymbol::kDecimalSeparatorSymbol),
|
||||
{UFIELD_CATEGORY_NUMBER, UNUM_DECIMAL_SEPARATOR_FIELD},
|
||||
status);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the fraction digits
|
||||
|
|
|
@ -115,12 +115,6 @@ class NumberFormatterImpl : public UMemory {
|
|||
LocalPointer<const LongNameMultiplexer> fLongNameMultiplexer;
|
||||
LocalPointer<const CompactHandler> fCompactHandler;
|
||||
|
||||
// Value objects possibly used by the number formatting pipeline:
|
||||
struct Warehouse {
|
||||
CurrencySymbols fCurrencySymbols;
|
||||
} fWarehouse;
|
||||
|
||||
|
||||
NumberFormatterImpl(const MacroProps ¯os, bool safe, UErrorCode &status);
|
||||
|
||||
MicroProps& preProcessUnsafe(DecimalQuantity &inValue, UErrorCode &status);
|
||||
|
|
|
@ -381,7 +381,10 @@ void PropertiesAffixPatternProvider::setTo(const DecimalFormatProperties& proper
|
|||
AffixUtils::hasCurrencySymbols(ppp, status) ||
|
||||
AffixUtils::hasCurrencySymbols(psp, status) ||
|
||||
AffixUtils::hasCurrencySymbols(npp, status) ||
|
||||
AffixUtils::hasCurrencySymbols(nsp, status));
|
||||
AffixUtils::hasCurrencySymbols(nsp, status) ||
|
||||
properties.currencyAsDecimal);
|
||||
|
||||
fCurrencyAsDecimal = properties.currencyAsDecimal;
|
||||
}
|
||||
|
||||
char16_t PropertiesAffixPatternProvider::charAt(int flags, int i) const {
|
||||
|
@ -446,6 +449,10 @@ bool PropertiesAffixPatternProvider::hasBody() const {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool PropertiesAffixPatternProvider::currencyAsDecimal() const {
|
||||
return fCurrencyAsDecimal;
|
||||
}
|
||||
|
||||
|
||||
void CurrencyPluralInfoAffixProvider::setTo(const CurrencyPluralInfo& cpi,
|
||||
const DecimalFormatProperties& properties,
|
||||
|
@ -506,5 +513,9 @@ bool CurrencyPluralInfoAffixProvider::hasBody() const {
|
|||
return affixesByPlural[StandardPlural::OTHER].hasBody();
|
||||
}
|
||||
|
||||
bool CurrencyPluralInfoAffixProvider::currencyAsDecimal() const {
|
||||
return affixesByPlural[StandardPlural::OTHER].currencyAsDecimal();
|
||||
}
|
||||
|
||||
|
||||
#endif /* #if !UCONFIG_NO_FORMATTING */
|
||||
|
|
|
@ -56,12 +56,15 @@ class PropertiesAffixPatternProvider : public AffixPatternProvider, public UMemo
|
|||
|
||||
bool hasBody() const U_OVERRIDE;
|
||||
|
||||
bool currencyAsDecimal() const U_OVERRIDE;
|
||||
|
||||
private:
|
||||
UnicodeString posPrefix;
|
||||
UnicodeString posSuffix;
|
||||
UnicodeString negPrefix;
|
||||
UnicodeString negSuffix;
|
||||
bool isCurrencyPattern;
|
||||
bool fCurrencyAsDecimal;
|
||||
|
||||
PropertiesAffixPatternProvider() = default; // puts instance in valid but undefined state
|
||||
|
||||
|
@ -107,6 +110,8 @@ class CurrencyPluralInfoAffixProvider : public AffixPatternProvider, public UMem
|
|||
|
||||
bool hasBody() const U_OVERRIDE;
|
||||
|
||||
bool currencyAsDecimal() const U_OVERRIDE;
|
||||
|
||||
private:
|
||||
PropertiesAffixPatternProvider affixesByPlural[StandardPlural::COUNT];
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "number_roundingutils.h"
|
||||
#include "decNumber.h"
|
||||
#include "charstr.h"
|
||||
#include "util.h"
|
||||
|
||||
U_NAMESPACE_BEGIN namespace number {
|
||||
namespace impl {
|
||||
|
@ -83,6 +84,9 @@ struct MicroProps : public MicroPropsGenerator {
|
|||
bool useCurrency;
|
||||
char nsName[9];
|
||||
|
||||
// Currency symbol to be used as the decimal separator
|
||||
UnicodeString currencyAsDecimal = ICU_Utility::makeBogusString();
|
||||
|
||||
// No ownership: must point at a string which will outlive MicroProps
|
||||
// instances, e.g. a string with static storage duration, or just a string
|
||||
// that will never be deallocated or modified.
|
||||
|
|
|
@ -300,24 +300,8 @@ UnicodeString MutablePatternModifier::getSymbol(AffixPatternType type) const {
|
|||
return fSymbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kPercentSymbol);
|
||||
case AffixPatternType::TYPE_PERMILLE:
|
||||
return fSymbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kPerMillSymbol);
|
||||
case AffixPatternType::TYPE_CURRENCY_SINGLE: {
|
||||
switch (fUnitWidth) {
|
||||
case UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW:
|
||||
return fCurrencySymbols.getNarrowCurrencySymbol(localStatus);
|
||||
case UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT:
|
||||
return fCurrencySymbols.getCurrencySymbol(localStatus);
|
||||
case UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE:
|
||||
return fCurrencySymbols.getIntlCurrencySymbol(localStatus);
|
||||
case UNumberUnitWidth::UNUM_UNIT_WIDTH_FORMAL:
|
||||
return fCurrencySymbols.getFormalCurrencySymbol(localStatus);
|
||||
case UNumberUnitWidth::UNUM_UNIT_WIDTH_VARIANT:
|
||||
return fCurrencySymbols.getVariantCurrencySymbol(localStatus);
|
||||
case UNumberUnitWidth::UNUM_UNIT_WIDTH_HIDDEN:
|
||||
return UnicodeString();
|
||||
default:
|
||||
return fCurrencySymbols.getCurrencySymbol(localStatus);
|
||||
}
|
||||
}
|
||||
case AffixPatternType::TYPE_CURRENCY_SINGLE:
|
||||
return getCurrencySymbolForUnitWidth(localStatus);
|
||||
case AffixPatternType::TYPE_CURRENCY_DOUBLE:
|
||||
return fCurrencySymbols.getIntlCurrencySymbol(localStatus);
|
||||
case AffixPatternType::TYPE_CURRENCY_TRIPLE:
|
||||
|
@ -335,6 +319,25 @@ UnicodeString MutablePatternModifier::getSymbol(AffixPatternType type) const {
|
|||
}
|
||||
}
|
||||
|
||||
UnicodeString MutablePatternModifier::getCurrencySymbolForUnitWidth(UErrorCode& status) const {
|
||||
switch (fUnitWidth) {
|
||||
case UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW:
|
||||
return fCurrencySymbols.getNarrowCurrencySymbol(status);
|
||||
case UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT:
|
||||
return fCurrencySymbols.getCurrencySymbol(status);
|
||||
case UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE:
|
||||
return fCurrencySymbols.getIntlCurrencySymbol(status);
|
||||
case UNumberUnitWidth::UNUM_UNIT_WIDTH_FORMAL:
|
||||
return fCurrencySymbols.getFormalCurrencySymbol(status);
|
||||
case UNumberUnitWidth::UNUM_UNIT_WIDTH_VARIANT:
|
||||
return fCurrencySymbols.getVariantCurrencySymbol(status);
|
||||
case UNumberUnitWidth::UNUM_UNIT_WIDTH_HIDDEN:
|
||||
return UnicodeString();
|
||||
default:
|
||||
return fCurrencySymbols.getCurrencySymbol(status);
|
||||
}
|
||||
}
|
||||
|
||||
UnicodeString MutablePatternModifier::toUnicodeString() const {
|
||||
// Never called by AffixUtils
|
||||
UPRV_UNREACHABLE_EXIT;
|
||||
|
|
|
@ -195,6 +195,11 @@ class U_I18N_API MutablePatternModifier
|
|||
*/
|
||||
UnicodeString getSymbol(AffixPatternType type) const U_OVERRIDE;
|
||||
|
||||
/**
|
||||
* Returns the currency symbol for the unit width specified in setSymbols()
|
||||
*/
|
||||
UnicodeString getCurrencySymbolForUnitWidth(UErrorCode& status) const;
|
||||
|
||||
UnicodeString toUnicodeString() const;
|
||||
|
||||
private:
|
||||
|
|
|
@ -115,6 +115,10 @@ bool ParsedPatternInfo::hasBody() const {
|
|||
return positive.integerTotal > 0;
|
||||
}
|
||||
|
||||
bool ParsedPatternInfo::currencyAsDecimal() const {
|
||||
return positive.hasCurrencyDecimal;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/// BEGIN RECURSIVE DESCENT PARSER IMPLEMENTATION ///
|
||||
/////////////////////////////////////////////////////
|
||||
|
@ -127,8 +131,20 @@ UChar32 ParsedPatternInfo::ParserState::peek() {
|
|||
}
|
||||
}
|
||||
|
||||
UChar32 ParsedPatternInfo::ParserState::peek2() {
|
||||
if (offset == pattern.length()) {
|
||||
return -1;
|
||||
}
|
||||
int32_t cp1 = pattern.char32At(offset);
|
||||
int32_t offset2 = offset + U16_LENGTH(cp1);
|
||||
if (offset2 == pattern.length()) {
|
||||
return -1;
|
||||
}
|
||||
return pattern.char32At(offset2);
|
||||
}
|
||||
|
||||
UChar32 ParsedPatternInfo::ParserState::next() {
|
||||
int codePoint = peek();
|
||||
int32_t codePoint = peek();
|
||||
offset += U16_LENGTH(codePoint);
|
||||
return codePoint;
|
||||
}
|
||||
|
@ -286,6 +302,35 @@ void ParsedPatternInfo::consumeFormat(UErrorCode& status) {
|
|||
currentSubpattern->widthExceptAffixes += 1;
|
||||
consumeFractionFormat(status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
} else if (state.peek() == u'¤') {
|
||||
// Check if currency is a decimal separator
|
||||
switch (state.peek2()) {
|
||||
case u'#':
|
||||
case u'0':
|
||||
case u'1':
|
||||
case u'2':
|
||||
case u'3':
|
||||
case u'4':
|
||||
case u'5':
|
||||
case u'6':
|
||||
case u'7':
|
||||
case u'8':
|
||||
case u'9':
|
||||
break;
|
||||
default:
|
||||
// Currency symbol followed by a non-numeric character;
|
||||
// treat as a normal affix.
|
||||
return;
|
||||
}
|
||||
// Currency symbol is followed by a numeric character;
|
||||
// treat as a decimal separator.
|
||||
currentSubpattern->hasCurrencySign = true;
|
||||
currentSubpattern->hasCurrencyDecimal = true;
|
||||
currentSubpattern->hasDecimal = true;
|
||||
currentSubpattern->widthExceptAffixes += 1;
|
||||
state.next(); // consume the symbol
|
||||
consumeFractionFormat(status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -565,6 +610,9 @@ PatternParser::patternInfoToProperties(DecimalFormatProperties& properties, Pars
|
|||
properties.decimalSeparatorAlwaysShown = false;
|
||||
}
|
||||
|
||||
// Persist the currency as decimal separator
|
||||
properties.currencyAsDecimal = positive.hasCurrencyDecimal;
|
||||
|
||||
// Scientific notation settings
|
||||
if (positive.exponentZeros > 0) {
|
||||
properties.exponentSignAlwaysShown = positive.exponentHasPlusSign;
|
||||
|
@ -750,7 +798,11 @@ UnicodeString PatternStringUtils::propertiesToPatternString(const DecimalFormatP
|
|||
}
|
||||
// Decimal separator
|
||||
if (magnitude == 0 && (alwaysShowDecimal || mN < 0)) {
|
||||
sb.append(u'.');
|
||||
if (properties.currencyAsDecimal) {
|
||||
sb.append(u'¤');
|
||||
} else {
|
||||
sb.append(u'.');
|
||||
}
|
||||
}
|
||||
if (!useGrouping) {
|
||||
continue;
|
||||
|
|
|
@ -62,6 +62,7 @@ struct U_I18N_API ParsedSubpatternInfo {
|
|||
bool hasPercentSign = false;
|
||||
bool hasPerMilleSign = false;
|
||||
bool hasCurrencySign = false;
|
||||
bool hasCurrencyDecimal = false;
|
||||
bool hasMinusSign = false;
|
||||
bool hasPlusSign = false;
|
||||
|
||||
|
@ -104,6 +105,8 @@ struct U_I18N_API ParsedPatternInfo : public AffixPatternProvider, public UMemor
|
|||
|
||||
bool hasBody() const U_OVERRIDE;
|
||||
|
||||
bool currencyAsDecimal() const U_OVERRIDE;
|
||||
|
||||
private:
|
||||
struct U_I18N_API ParserState {
|
||||
const UnicodeString& pattern; // reference to the parent
|
||||
|
@ -119,8 +122,13 @@ struct U_I18N_API ParsedPatternInfo : public AffixPatternProvider, public UMemor
|
|||
return *this;
|
||||
}
|
||||
|
||||
/** Returns the next code point, or -1 if string is too short. */
|
||||
UChar32 peek();
|
||||
|
||||
/** Returns the code point after the next code point, or -1 if string is too short. */
|
||||
UChar32 peek2();
|
||||
|
||||
/** Returns the next code point and then steps forward. */
|
||||
UChar32 next();
|
||||
|
||||
// TODO: We don't currently do anything with the message string.
|
||||
|
|
|
@ -140,6 +140,11 @@ class U_I18N_API AffixPatternProvider {
|
|||
* number instead of rendering the number.
|
||||
*/
|
||||
virtual bool hasBody() const = 0;
|
||||
|
||||
/**
|
||||
* True if the currency symbol should replace the decimal separator.
|
||||
*/
|
||||
virtual bool currencyAsDecimal() const = 0;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -254,12 +254,13 @@ class PatternModifierTest : public IntlTest {
|
|||
UnicodeString getSuffix(const MutablePatternModifier &mod, UErrorCode &status);
|
||||
};
|
||||
|
||||
class PatternStringTest : public IntlTest {
|
||||
class PatternStringTest : public IntlTestWithFieldPosition {
|
||||
public:
|
||||
void testLocalized();
|
||||
void testToPatternSimple();
|
||||
void testExceptionOnInvalid();
|
||||
void testBug13117();
|
||||
void testCurrencyDecimal();
|
||||
|
||||
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0) override;
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ void PatternStringTest::runIndexedTest(int32_t index, UBool exec, const char*& n
|
|||
TESTCASE_AUTO(testToPatternSimple);
|
||||
TESTCASE_AUTO(testExceptionOnInvalid);
|
||||
TESTCASE_AUTO(testBug13117);
|
||||
TESTCASE_AUTO(testCurrencyDecimal);
|
||||
TESTCASE_AUTO_END;
|
||||
}
|
||||
|
||||
|
@ -56,6 +57,9 @@ void PatternStringTest::testToPatternSimple() {
|
|||
{u"0E0", u"0E0"},
|
||||
{u"#00E00", u"#00E00"},
|
||||
{u"#,##0", u"#,##0"},
|
||||
{u"0¤", u"0¤"},
|
||||
{u"0¤a", u"0¤a"},
|
||||
{u"0¤00", u"0¤00"},
|
||||
{u"#;#", u"0;0"},
|
||||
// ignore a negative prefix pattern of '-' since that is the default:
|
||||
{u"#;-#", u"0"},
|
||||
|
@ -77,6 +81,7 @@ void PatternStringTest::testToPatternSimple() {
|
|||
assertSuccess(input, status);
|
||||
UnicodeString actual = PatternStringUtils::propertiesToPatternString(properties, status);
|
||||
assertEquals(input, output, actual);
|
||||
status = U_ZERO_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,4 +118,34 @@ void PatternStringTest::testBug13117() {
|
|||
assertTrue("Should not consume negative subpattern", expected == actual);
|
||||
}
|
||||
|
||||
void PatternStringTest::testCurrencyDecimal() {
|
||||
IcuTestErrorCode status(*this, "testCurrencyDecimal");
|
||||
|
||||
// Manually create a NumberFormatter from a specific pattern
|
||||
ParsedPatternInfo patternInfo;
|
||||
PatternParser::parseToPatternInfo(u"a0¤00b", patternInfo, status);
|
||||
MacroProps macros;
|
||||
macros.unit = CurrencyUnit(u"EUR", status);
|
||||
macros.affixProvider = &patternInfo;
|
||||
LocalizedNumberFormatter nf = NumberFormatter::with().macros(macros).locale("und");
|
||||
|
||||
// Test that the output is as expected
|
||||
FormattedNumber fn = nf.formatDouble(3.14, status);
|
||||
assertEquals("Should substitute currency symbol", u"a3€14b", fn.toTempString(status));
|
||||
|
||||
// Test field positions
|
||||
static const UFieldPosition expectedFieldPositions[] = {
|
||||
{UNUM_INTEGER_FIELD, 1, 2},
|
||||
{UNUM_CURRENCY_FIELD, 2, 3},
|
||||
{UNUM_FRACTION_FIELD, 3, 5}};
|
||||
checkFormattedValue(
|
||||
u"Currency as decimal basic field positions",
|
||||
fn,
|
||||
u"a3€14b",
|
||||
UFIELD_CATEGORY_NUMBER,
|
||||
expectedFieldPositions,
|
||||
UPRV_LENGTHOF(expectedFieldPositions)
|
||||
);
|
||||
}
|
||||
|
||||
#endif /* #if !UCONFIG_NO_FORMATTING */
|
||||
|
|
|
@ -251,6 +251,7 @@ void NumberFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &n
|
|||
TESTCASE_AUTO(Test20425_FractionWithIntegerIncrement);
|
||||
TESTCASE_AUTO(Test21232_ParseTimeout);
|
||||
TESTCASE_AUTO(Test10997_FormatCurrency);
|
||||
TESTCASE_AUTO(Test21556_CurrencyAsDecimal);
|
||||
TESTCASE_AUTO_END;
|
||||
}
|
||||
|
||||
|
@ -10089,9 +10090,8 @@ void NumberFormatTest::Test21232_ParseTimeout() {
|
|||
void NumberFormatTest::Test10997_FormatCurrency() {
|
||||
IcuTestErrorCode status(*this, "Test10997_FormatCurrency");
|
||||
|
||||
UErrorCode error = U_ZERO_ERROR;
|
||||
NumberFormat* fmt = NumberFormat::createCurrencyInstance(Locale::getUS(), error);
|
||||
if (U_FAILURE(error)) {
|
||||
LocalPointer<NumberFormat> fmt(NumberFormat::createCurrencyInstance(Locale::getUS(), status));
|
||||
if (status.errDataIfFailureAndReset()) {
|
||||
return;
|
||||
}
|
||||
fmt->setMinimumFractionDigits(4);
|
||||
|
@ -10108,8 +10108,40 @@ void NumberFormatTest::Test10997_FormatCurrency() {
|
|||
Formattable eurAmnt(new CurrencyAmount(123.45, u"EUR", status));
|
||||
fmt->format(eurAmnt, str2, fp, status);
|
||||
assertEquals("minFrac 4 should be respected in different currency", u"€123.4500", str2);
|
||||
}
|
||||
|
||||
delete fmt;
|
||||
void NumberFormatTest::Test21556_CurrencyAsDecimal() {
|
||||
IcuTestErrorCode status(*this, "Test21556_CurrencyAsDecimal");
|
||||
|
||||
{
|
||||
DecimalFormat df(u"a0¤00b", status);
|
||||
if (status.errDataIfFailureAndReset()) {
|
||||
return;
|
||||
}
|
||||
df.setCurrency(u"EUR", status);
|
||||
UnicodeString result;
|
||||
FieldPosition fp(UNUM_CURRENCY_FIELD);
|
||||
df.format(3.141, result, fp);
|
||||
assertEquals("Basic test: format", u"a3€14b", result);
|
||||
UnicodeString pattern;
|
||||
assertEquals("Basic test: toPattern", u"a0¤00b", df.toPattern(pattern));
|
||||
assertEquals("Basic test: field position begin", 2, fp.getBeginIndex());
|
||||
assertEquals("Basic test: field position end", 3, fp.getEndIndex());
|
||||
}
|
||||
|
||||
{
|
||||
LocalPointer<NumberFormat> nf(NumberFormat::createCurrencyInstance("en-GB", status));
|
||||
DecimalFormat* df = static_cast<DecimalFormat*>(nf.getAlias());
|
||||
df->applyPattern(u"a0¤00b", status);
|
||||
UnicodeString result;
|
||||
FieldPosition fp(UNUM_CURRENCY_FIELD);
|
||||
df->format(3.141, result, fp);
|
||||
assertEquals("Via applyPattern: format", u"a3£14b", result);
|
||||
UnicodeString pattern;
|
||||
assertEquals("Via applyPattern: toPattern", u"a0¤00b", df->toPattern(pattern));
|
||||
assertEquals("Via applyPattern: field position begin", 2, fp.getBeginIndex());
|
||||
assertEquals("Via applyPattern: field position end", 3, fp.getEndIndex());
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* #if !UCONFIG_NO_FORMATTING */
|
||||
|
|
|
@ -307,6 +307,7 @@ class NumberFormatTest: public CalendarTimeZoneTest {
|
|||
void Test20425_FractionWithIntegerIncrement();
|
||||
void Test21232_ParseTimeout();
|
||||
void Test10997_FormatCurrency();
|
||||
void Test21556_CurrencyAsDecimal();
|
||||
|
||||
private:
|
||||
UBool testFormattableAsUFormattable(const char *file, int line, Formattable &f);
|
||||
|
|
|
@ -38,4 +38,9 @@ public interface AffixPatternProvider {
|
|||
* number instead of rendering the number.
|
||||
*/
|
||||
public boolean hasBody();
|
||||
|
||||
/**
|
||||
* True if the currency symbol should replace the decimal separator.
|
||||
*/
|
||||
public boolean currencyAsDecimal();
|
||||
}
|
||||
|
|
|
@ -68,4 +68,9 @@ public class CurrencyPluralInfoAffixProvider implements AffixPatternProvider {
|
|||
public boolean hasBody() {
|
||||
return affixesByPlural[StandardPlural.OTHER.ordinal()].hasBody();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean currencyAsDecimal() {
|
||||
return affixesByPlural[StandardPlural.OTHER.ordinal()].currencyAsDecimal();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,6 +92,7 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
|||
private transient boolean decimalPatternMatchRequired;
|
||||
private transient boolean decimalSeparatorAlwaysShown;
|
||||
private transient boolean exponentSignAlwaysShown;
|
||||
private transient boolean currencyAsDecimal;
|
||||
private transient int formatWidth;
|
||||
private transient int groupingSize;
|
||||
private transient boolean groupingUsed;
|
||||
|
@ -164,6 +165,7 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
|||
decimalPatternMatchRequired = false;
|
||||
decimalSeparatorAlwaysShown = false;
|
||||
exponentSignAlwaysShown = false;
|
||||
currencyAsDecimal = false;
|
||||
formatWidth = -1;
|
||||
groupingSize = -1;
|
||||
groupingUsed = true;
|
||||
|
@ -210,6 +212,7 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
|||
decimalPatternMatchRequired = other.decimalPatternMatchRequired;
|
||||
decimalSeparatorAlwaysShown = other.decimalSeparatorAlwaysShown;
|
||||
exponentSignAlwaysShown = other.exponentSignAlwaysShown;
|
||||
currencyAsDecimal = other.currencyAsDecimal;
|
||||
formatWidth = other.formatWidth;
|
||||
groupingSize = other.groupingSize;
|
||||
groupingUsed = other.groupingUsed;
|
||||
|
@ -257,6 +260,7 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
|||
eq = eq && _equalsHelper(decimalPatternMatchRequired, other.decimalPatternMatchRequired);
|
||||
eq = eq && _equalsHelper(decimalSeparatorAlwaysShown, other.decimalSeparatorAlwaysShown);
|
||||
eq = eq && _equalsHelper(exponentSignAlwaysShown, other.exponentSignAlwaysShown);
|
||||
eq = eq && _equalsHelper(currencyAsDecimal, other.currencyAsDecimal);
|
||||
eq = eq && _equalsHelper(formatWidth, other.formatWidth);
|
||||
eq = eq && _equalsHelper(groupingSize, other.groupingSize);
|
||||
eq = eq && _equalsHelper(groupingUsed, other.groupingUsed);
|
||||
|
@ -320,6 +324,7 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
|||
hashCode ^= _hashCodeHelper(decimalPatternMatchRequired);
|
||||
hashCode ^= _hashCodeHelper(decimalSeparatorAlwaysShown);
|
||||
hashCode ^= _hashCodeHelper(exponentSignAlwaysShown);
|
||||
hashCode ^= _hashCodeHelper(currencyAsDecimal);
|
||||
hashCode ^= _hashCodeHelper(formatWidth);
|
||||
hashCode ^= _hashCodeHelper(groupingSize);
|
||||
hashCode ^= _hashCodeHelper(groupingUsed);
|
||||
|
@ -443,6 +448,10 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
|||
return exponentSignAlwaysShown;
|
||||
}
|
||||
|
||||
public boolean getCurrencyAsDecimal() {
|
||||
return currencyAsDecimal;
|
||||
}
|
||||
|
||||
public int getFormatWidth() {
|
||||
return formatWidth;
|
||||
}
|
||||
|
@ -769,6 +778,18 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the currency symbol should replace the decimal separator.
|
||||
*
|
||||
* @param currencyAsDecimal
|
||||
* Whether the currency symbol should replace the decimal separator.
|
||||
* @return The property bag, for chaining.
|
||||
*/
|
||||
public DecimalFormatProperties setCurrencyAsDecimal(boolean currencyAsDecimal) {
|
||||
this.currencyAsDecimal = currencyAsDecimal;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum width of the string output by the formatting pipeline. For example, if padding is
|
||||
* enabled and paddingWidth is set to 6, formatting the number "3.14159" with the pattern "0.00" will
|
||||
|
|
|
@ -46,6 +46,9 @@ public class MicroProps implements Cloneable, MicroPropsGenerator {
|
|||
public boolean useCurrency;
|
||||
public String gender;
|
||||
|
||||
// Currency symbol to be used as the decimal separator
|
||||
public String currencyAsDecimal;
|
||||
|
||||
// Internal fields:
|
||||
private final boolean immutable;
|
||||
|
||||
|
|
|
@ -402,31 +402,7 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
|
|||
case AffixUtils.TYPE_PERMILLE:
|
||||
return symbols.getPerMillString();
|
||||
case AffixUtils.TYPE_CURRENCY_SINGLE:
|
||||
// UnitWidth ISO, HIDDEN, or NARROW overrides the singular currency symbol.
|
||||
if (unitWidth == UnitWidth.ISO_CODE) {
|
||||
return currency.getCurrencyCode();
|
||||
} else if (unitWidth == UnitWidth.HIDDEN) {
|
||||
return "";
|
||||
} else {
|
||||
int selector;
|
||||
switch (unitWidth) {
|
||||
case SHORT:
|
||||
selector = Currency.SYMBOL_NAME;
|
||||
break;
|
||||
case NARROW:
|
||||
selector = Currency.NARROW_SYMBOL_NAME;
|
||||
break;
|
||||
case FORMAL:
|
||||
selector = Currency.FORMAL_SYMBOL_NAME;
|
||||
break;
|
||||
case VARIANT:
|
||||
selector = Currency.VARIANT_SYMBOL_NAME;
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
return currency.getName(symbols.getULocale(), selector, null);
|
||||
}
|
||||
return getCurrencySymbolForUnitWidth();
|
||||
case AffixUtils.TYPE_CURRENCY_DOUBLE:
|
||||
return currency.getCurrencyCode();
|
||||
case AffixUtils.TYPE_CURRENCY_TRIPLE:
|
||||
|
@ -444,4 +420,35 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
|
|||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currency symbol for the unit width specified in setSymbols()
|
||||
*/
|
||||
public String getCurrencySymbolForUnitWidth() {
|
||||
// UnitWidth ISO, HIDDEN, or NARROW overrides the singular currency symbol.
|
||||
if (unitWidth == UnitWidth.ISO_CODE) {
|
||||
return currency.getCurrencyCode();
|
||||
} else if (unitWidth == UnitWidth.HIDDEN) {
|
||||
return "";
|
||||
} else {
|
||||
int selector;
|
||||
switch (unitWidth) {
|
||||
case SHORT:
|
||||
selector = Currency.SYMBOL_NAME;
|
||||
break;
|
||||
case NARROW:
|
||||
selector = Currency.NARROW_SYMBOL_NAME;
|
||||
break;
|
||||
case FORMAL:
|
||||
selector = Currency.FORMAL_SYMBOL_NAME;
|
||||
break;
|
||||
case VARIANT:
|
||||
selector = Currency.VARIANT_SYMBOL_NAME;
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
return currency.getName(symbols.getULocale(), selector, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -174,6 +174,11 @@ public class PatternStringParser {
|
|||
public boolean hasBody() {
|
||||
return positive.integerTotal > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean currencyAsDecimal() {
|
||||
return positive.hasCurrencyDecimal;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ParsedSubpatternInfo {
|
||||
|
@ -195,6 +200,7 @@ public class PatternStringParser {
|
|||
public boolean hasPercentSign = false;
|
||||
public boolean hasPerMilleSign = false;
|
||||
public boolean hasCurrencySign = false;
|
||||
public boolean hasCurrencyDecimal = false;
|
||||
public boolean hasMinusSign = false;
|
||||
public boolean hasPlusSign = false;
|
||||
|
||||
|
@ -217,6 +223,7 @@ public class PatternStringParser {
|
|||
this.offset = 0;
|
||||
}
|
||||
|
||||
/** Returns the next code point, or -1 if string is too short. */
|
||||
int peek() {
|
||||
if (offset == pattern.length()) {
|
||||
return -1;
|
||||
|
@ -225,6 +232,20 @@ public class PatternStringParser {
|
|||
}
|
||||
}
|
||||
|
||||
/** Returns the code point after the next code point, or -1 if string is too short. */
|
||||
int peek2() {
|
||||
if (offset == pattern.length()) {
|
||||
return -1;
|
||||
}
|
||||
int cp1 = pattern.codePointAt(offset);
|
||||
int offset2 = offset + Character.charCount(cp1);
|
||||
if (offset2 == pattern.length()) {
|
||||
return -1;
|
||||
}
|
||||
return pattern.codePointAt(offset2);
|
||||
}
|
||||
|
||||
/** Returns the next code point and then steps forward. */
|
||||
int next() {
|
||||
int codePoint = peek();
|
||||
offset += Character.charCount(codePoint);
|
||||
|
@ -366,6 +387,34 @@ public class PatternStringParser {
|
|||
result.hasDecimal = true;
|
||||
result.widthExceptAffixes += 1;
|
||||
consumeFractionFormat(state, result);
|
||||
} else if (state.peek() == '¤') {
|
||||
// Check if currency is a decimal separator
|
||||
switch (state.peek2()) {
|
||||
case '#':
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
break;
|
||||
default:
|
||||
// Currency symbol followed by a non-numeric character;
|
||||
// treat as a normal affix.
|
||||
return;
|
||||
}
|
||||
// Currency symbol is followed by a numeric character;
|
||||
// treat as a decimal separator.
|
||||
result.hasCurrencySign = true;
|
||||
result.hasCurrencyDecimal = true;
|
||||
result.hasDecimal = true;
|
||||
result.widthExceptAffixes += 1;
|
||||
state.next(); // consume the symbol
|
||||
consumeFractionFormat(state, result);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -628,6 +677,9 @@ public class PatternStringParser {
|
|||
properties.setDecimalSeparatorAlwaysShown(false);
|
||||
}
|
||||
|
||||
// Persist the currency as decimal separator
|
||||
properties.setCurrencyAsDecimal(positive.hasCurrencyDecimal);
|
||||
|
||||
// Scientific notation settings
|
||||
if (positive.exponentZeros > 0) {
|
||||
properties.setExponentSignAlwaysShown(positive.exponentHasPlusSign);
|
||||
|
|
|
@ -91,6 +91,7 @@ public class PatternStringUtils {
|
|||
int minSig = Math.min(properties.getMinimumSignificantDigits(), dosMax);
|
||||
int maxSig = Math.min(properties.getMaximumSignificantDigits(), dosMax);
|
||||
boolean alwaysShowDecimal = properties.getDecimalSeparatorAlwaysShown();
|
||||
boolean currencyAsDecimal = properties.getCurrencyAsDecimal();
|
||||
int exponentDigits = Math.min(properties.getMinimumExponentDigits(), dosMax);
|
||||
boolean exponentShowPlusSign = properties.getExponentSignAlwaysShown();
|
||||
AffixPatternProvider affixes = PropertiesAffixPatternProvider.forProperties(properties);
|
||||
|
@ -153,7 +154,11 @@ public class PatternStringUtils {
|
|||
}
|
||||
// Decimal separator
|
||||
if (magnitude == 0 && (alwaysShowDecimal || mN < 0)) {
|
||||
sb.append('.');
|
||||
if (currencyAsDecimal) {
|
||||
sb.append('¤');
|
||||
} else {
|
||||
sb.append('.');
|
||||
}
|
||||
}
|
||||
if (!useGrouping) {
|
||||
continue;
|
||||
|
|
|
@ -8,6 +8,7 @@ public class PropertiesAffixPatternProvider implements AffixPatternProvider {
|
|||
private final String negPrefix;
|
||||
private final String negSuffix;
|
||||
private final boolean isCurrencyPattern;
|
||||
private final boolean currencyAsDecimal;
|
||||
|
||||
public static AffixPatternProvider forProperties(DecimalFormatProperties properties) {
|
||||
if (properties.getCurrencyPluralInfo() == null) {
|
||||
|
@ -84,7 +85,10 @@ public class PropertiesAffixPatternProvider implements AffixPatternProvider {
|
|||
AffixUtils.hasCurrencySymbols(ppp) ||
|
||||
AffixUtils.hasCurrencySymbols(psp) ||
|
||||
AffixUtils.hasCurrencySymbols(npp) ||
|
||||
AffixUtils.hasCurrencySymbols(nsp));
|
||||
AffixUtils.hasCurrencySymbols(nsp) ||
|
||||
properties.getCurrencyAsDecimal());
|
||||
|
||||
currencyAsDecimal = properties.getCurrencyAsDecimal();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -150,6 +154,11 @@ public class PropertiesAffixPatternProvider implements AffixPatternProvider {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean currencyAsDecimal() {
|
||||
return currencyAsDecimal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString()
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.ibm.icu.impl.FormattedStringBuilder;
|
|||
import com.ibm.icu.impl.IllegalIcuArgumentException;
|
||||
import com.ibm.icu.impl.StandardPlural;
|
||||
import com.ibm.icu.impl.number.CompactData.CompactType;
|
||||
import com.ibm.icu.impl.number.AffixPatternProvider;
|
||||
import com.ibm.icu.impl.number.ConstantAffixModifier;
|
||||
import com.ibm.icu.impl.number.DecimalQuantity;
|
||||
import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
|
||||
|
@ -361,7 +362,11 @@ class NumberFormatterImpl {
|
|||
// Middle modifier (patterns, positive/negative, currency symbols, percent)
|
||||
// The default middle modifier is weak (thus the false argument).
|
||||
MutablePatternModifier patternMod = new MutablePatternModifier(false);
|
||||
patternMod.setPatternInfo((macros.affixProvider != null) ? macros.affixProvider : patternInfo, null);
|
||||
AffixPatternProvider affixProvider =
|
||||
(macros.affixProvider != null)
|
||||
? macros.affixProvider
|
||||
: patternInfo;
|
||||
patternMod.setPatternInfo(affixProvider, null);
|
||||
boolean approximately = (macros.approximately != null) ? macros.approximately : false;
|
||||
patternMod.setPatternAttributes(micros.sign, isPermille, approximately);
|
||||
if (patternMod.needsPlurals()) {
|
||||
|
@ -378,6 +383,11 @@ class NumberFormatterImpl {
|
|||
immPatternMod = patternMod.createImmutable();
|
||||
}
|
||||
|
||||
// currencyAsDecimal
|
||||
if (affixProvider.currencyAsDecimal()) {
|
||||
micros.currencyAsDecimal = patternMod.getCurrencySymbolForUnitWidth();
|
||||
}
|
||||
|
||||
// Outer modifier (CLDR units and currency long names)
|
||||
if (isCldrUnit) {
|
||||
String unitDisplayCase = null;
|
||||
|
@ -513,10 +523,24 @@ class NumberFormatterImpl {
|
|||
// Add the decimal point
|
||||
if (quantity.getLowerDisplayMagnitude() < 0
|
||||
|| micros.decimal == DecimalSeparatorDisplay.ALWAYS) {
|
||||
length += string.insert(length + index,
|
||||
micros.useCurrency ? micros.symbols.getMonetaryDecimalSeparatorString()
|
||||
: micros.symbols.getDecimalSeparatorString(),
|
||||
if (micros.currencyAsDecimal != null) {
|
||||
// Note: This unconditionally substitutes the standard short symbol.
|
||||
// TODO: Should we support narrow or other variants?
|
||||
length += string.insert(
|
||||
length + index,
|
||||
micros.currencyAsDecimal,
|
||||
NumberFormat.Field.CURRENCY);
|
||||
} else if (micros.useCurrency) {
|
||||
length += string.insert(
|
||||
length + index,
|
||||
micros.symbols.getMonetaryDecimalSeparatorString(),
|
||||
NumberFormat.Field.DECIMAL_SEPARATOR);
|
||||
} else {
|
||||
length += string.insert(
|
||||
length + index,
|
||||
micros.symbols.getDecimalSeparatorString(),
|
||||
NumberFormat.Field.DECIMAL_SEPARATOR);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the fraction digits
|
||||
|
|
|
@ -2504,6 +2504,7 @@ public synchronized void setParseStrictMode(ParseMode parseMode) {
|
|||
boolean useCurrency = ((tprops.getCurrency() != null)
|
||||
|| tprops.getCurrencyPluralInfo() != null
|
||||
|| tprops.getCurrencyUsage() != null
|
||||
|| tprops.getCurrencyAsDecimal()
|
||||
|| AffixUtils.hasCurrencySymbols(tprops.getPositivePrefixPattern())
|
||||
|| AffixUtils.hasCurrencySymbols(tprops.getPositiveSuffixPattern())
|
||||
|| AffixUtils.hasCurrencySymbols(tprops.getNegativePrefixPattern())
|
||||
|
|
|
@ -6938,4 +6938,32 @@ public class NumberFormatTest extends TestFmwk {
|
|||
df.parse(input.toString());
|
||||
// Should not hang
|
||||
}
|
||||
|
||||
@Test
|
||||
public void Test21556_CurrencyAsDecimal() {
|
||||
{
|
||||
DecimalFormat df = new DecimalFormat("a0¤00b");
|
||||
df.setCurrency(Currency.getInstance("EUR"));
|
||||
StringBuffer result = new StringBuffer();
|
||||
FieldPosition fp = new FieldPosition(NumberFormat.Field.CURRENCY);
|
||||
df.format(3.141, result, fp);
|
||||
assertEquals("Basic test: format", "a3€14b", result.toString());
|
||||
assertEquals("Basic test: toPattern", "a0¤00b", df.toPattern());
|
||||
assertEquals("Basic test: field position begin", 2, fp.getBeginIndex());
|
||||
assertEquals("Basic test: field position end", 3, fp.getEndIndex());
|
||||
}
|
||||
|
||||
{
|
||||
NumberFormat nf = NumberFormat.getCurrencyInstance(new ULocale("en-GB"));
|
||||
DecimalFormat df = (DecimalFormat) nf;
|
||||
df.applyPattern("a0¤00b");
|
||||
StringBuffer result = new StringBuffer();
|
||||
FieldPosition fp = new FieldPosition(NumberFormat.Field.CURRENCY);
|
||||
df.format(3.141, result, fp);
|
||||
assertEquals("Via applyPattern: format", "a3£14b", result.toString());
|
||||
assertEquals("Via applyPattern: toPattern", "a0¤00b", df.toPattern());
|
||||
assertEquals("Via applyPattern: field position begin", 2, fp.getBeginIndex());
|
||||
assertEquals("Via applyPattern: field position end", 3, fp.getEndIndex());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,10 +7,17 @@ import static org.junit.Assert.fail;
|
|||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.ibm.icu.dev.test.format.FormattedValueTest;
|
||||
import com.ibm.icu.impl.number.DecimalFormatProperties;
|
||||
import com.ibm.icu.impl.number.MacroProps;
|
||||
import com.ibm.icu.impl.number.PatternStringParser;
|
||||
import com.ibm.icu.impl.number.PatternStringUtils;
|
||||
import com.ibm.icu.impl.number.PatternStringParser.ParsedPatternInfo;
|
||||
import com.ibm.icu.number.FormattedNumber;
|
||||
import com.ibm.icu.number.LocalizedNumberFormatter;
|
||||
import com.ibm.icu.number.NumberFormatter;
|
||||
import com.ibm.icu.text.DecimalFormatSymbols;
|
||||
import com.ibm.icu.util.Currency;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
/** @author sffc */
|
||||
|
@ -47,6 +54,9 @@ public class PatternStringTest {
|
|||
{ "0E0", "0E0" },
|
||||
{ "#00E00", "#00E00" },
|
||||
{ "#,##0", "#,##0" },
|
||||
{ "0¤", "0¤"},
|
||||
{ "0¤a", "0¤a"},
|
||||
{ "0¤00", "0¤00"},
|
||||
{ "#;#", "0;0" },
|
||||
{ "#;-#", "0" }, // ignore a negative prefix pattern of '-' since that is the default
|
||||
{ "pp#,000;(#)", "pp#,000;(#,000)" },
|
||||
|
@ -127,4 +137,30 @@ public class PatternStringTest {
|
|||
DecimalFormatProperties actual = PatternStringParser.parseToProperties("0;");
|
||||
assertEquals("Should not consume negative subpattern", expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCurrencyDecimal() {
|
||||
// Manually create a NumberFormatter from a specific pattern
|
||||
ParsedPatternInfo patternInfo = PatternStringParser.parseToPatternInfo("a0¤00b");
|
||||
MacroProps macros = new MacroProps();
|
||||
macros.unit = Currency.getInstance("EUR");
|
||||
macros.affixProvider = patternInfo;
|
||||
LocalizedNumberFormatter nf = NumberFormatter.with().macros(macros).locale(ULocale.ROOT);
|
||||
|
||||
// Test that the output is as expected
|
||||
FormattedNumber fn = nf.format(3.14);
|
||||
assertEquals("Should substitute currency symbol", "a3€14b", fn.toString());
|
||||
|
||||
// Test field positions
|
||||
Object[][] expectedFieldPositions = new Object[][] {
|
||||
{com.ibm.icu.text.NumberFormat.Field.INTEGER, 1, 2},
|
||||
{com.ibm.icu.text.NumberFormat.Field.CURRENCY, 2, 3},
|
||||
{com.ibm.icu.text.NumberFormat.Field.FRACTION, 3, 5}};
|
||||
FormattedValueTest.checkFormattedValue(
|
||||
"Currency as decimal basic field positions",
|
||||
fn,
|
||||
"a3€14b",
|
||||
expectedFieldPositions
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue