NumberFormat.parse() fails for some currency strings
Asked Answered
M

6

6

I have a simple EditText, which allows the user to enter a number such as 45.60 (example for American Dollar). I then format this number using the following method:

public String format() {
    NumberFormat formatter = NumberFormat.getCurrencyInstance(Locale.getDefault());
    return formatter.format(amount.doubleValue());
}

And on my Android phone, the language is set to English (United States) - hence the Locale.getDefault() should return the US locale (and it does).

Now the edit text is correctly updated to: $45.60 (hence formatting the entered number works).

However if I attempt to parse the above String "$45.60" using the following method:

NumberFormat numberFormat = NumberFormat.getInstance(Locale.getDefault());
Number result = numberFormat.parse("$45.60");

It fails with:

java.lang.IllegalArgumentException: Failed to parse amount $45.60 using locale en_US.

If I set my phone to English/ UK, formatting this "45.60" to "£45.60" works correctly (as for US), however parsing "£45.60" fails, just as it does for the above US sample.

However, if I set my phone to German (Germany), formatting "45,60" to "45,60€" works correctly, AND parsing "45,60€" works correctly as well!

The only difference I see between those three currencies: The Euro is appended to the amount, while the Dollar and the Pound are prepended to the amount.

Does anyone have an idea, why the same code works for Euro, but not for Pound and Dollar? Am I missing something?

I also created a unit test, to reproduce the issue:

public void testCreateStringBased() throws Exception {

    // For German locale
    CurrencyAmount amount = new CurrencyAmount("25,46€", Locale.GERMANY);
    assertEquals(25.46, amount.getAsDouble());

    // For French locale
    amount = new CurrencyAmount("25,46€", Locale.FRANCE);
    assertEquals(25.46, amount.getAsDouble());

    // For US locale
    amount = new CurrencyAmount("$25.46", Locale.US);
    assertEquals(25.46, amount.getAsDouble());

    // For UK locale
    amount = new CurrencyAmount("£25.46", Locale.UK);
    assertEquals(25.46, amount.getAsDouble());
}

CurrencyAmount basically wraps the code I posted for parsing currency strings, except that it takes the given locale instead of the default locale. In the above example, the test succeeds for the GERMANY and FRANCE locale but fails for US and UK locale.

Mope answered 23/3, 2013 at 11:15 Comment(0)
M
8

Since the answers that have been suggested thus far, did not completely solve the problem, I took a painfully amateurish approach:

String value = "$24,76" 
value = value.replace(getCurrencySymbol(locale), StringUtils.EMPTY);

NumberFormat numberFormat = NumberFormat.getInstance(locale);
Number result = numberFormat.parse(value);

So now I simply strip the String value off it's currency symbol... This way I can process everything I want, such as: 45.78 or 45,78 or $45.78 or 45,78€ ....

Whatever the input, the currency symbol is simply stripped and I end up with the plain number. My unittests (see OP) now complete successfully.

If anyone comes up with something better, please let me know.

Mope answered 23/3, 2013 at 13:48 Comment(1)
I wouldn't call it 'painfully amateurish'. It's a common use case, especially when parsing EditText's input. I also considered enforcing correct (w/currency sign) format directly on EditText (using TextWatcher), but I end up using this solution as less intrusive for the user. Thanks.Hyalo
E
3

Try following:

NumberFormat numberFormat = new DecimalFormat("¤#.00", new DecimalFormatSymbols(Locale.UK));
numberFormat.parse("£123.5678");

¤ - currency sign, expects matches with currency symbol by Locale.

other pattern symbols you can see by following link http://docs.oracle.com/javase/6/docs/api/java/text/DecimalFormat.html

Elative answered 23/3, 2013 at 12:55 Comment(3)
Thanks for the suggestion, it works for Dollar and Pounds, but it does not work for Euros. I suspect, because in the case of Euros the currency sign is appended, not prepended? I am rather looking for something that works universally. The only other thing I can come up with is strip the currency symbol using regex and parse the value :/ but I'd rather avoid that...Mope
one more approach: String value = "123.5678€".replace(new DecimalFormatSymbols(Locale.GERMANY).getCurrencySymbol(), ""); BigDecimal bigDecimal = new BigDecimal(value); it is help you don't care about current currency symbolElative
Hey, thanks for that proposal, that's pretty much what I posted yesterday ;) (see my own answer below) and that's how I am doing it right now.Mope
S
1

Try NumberFormat.getCurrencyInstance().parse() instead of NumberFormat.getInstance().parse().

Shreveport answered 23/3, 2013 at 11:46 Comment(3)
Ok tried it and it doesn't really work. I need to be able to parse $45.65 as well as 45.65 with the same code. If I change my code as you suggested, parsing number-strings (without currency Symbol) no longer works. And: after those changes I can parse $45.56 but 45,56€ no longer works oO ...Mope
This is a limitation of the Java parsing routines. For your particular requirements, you should try both.Shreveport
Thanks for that clarification. However in that case, I think I am better off with my simple replacing of the currency symbol :) - because this is one thing, which works for each and every case. And I find it a bit cumbersome, if I need two different approaches to do the same thing (parse a currency string), just because some currencies have a prepended currency symbol, while others have it appended :/ . Logically it should be simple: One routine for every kind of currency parsing. Since it's locale based, that should be no issue, but sadly it isn't that simple.Mope
D
0

You must know the locale of the string you wish to parse in order to have a locale-aware parser. The GBP string parse to a numeric ONLY when the NumberFormat's locale is en_GB; there is no such thing as a "universal" parser.

For example, how does the string "12.000" parse? For en-us, the answer is twelve; for de-de, the answer is twelve-thousand.

Always use NumberFormat.getCurrencyInstance( java.util.Locale ) to parse currency amounts.

Desireedesiri answered 7/5, 2015 at 21:5 Comment(0)
C
0

I'm using below adapted from https://dzone.com/articles/currency-format-validation-and

import java.math.BigDecimal;
import org.apache.commons.validator.routines.*;

BigDecimalValidator currencyValidator = CurrencyValidator.getInstance();
BigDecimal parsedCurrency = currencyValidator.validate(currencyString);
if ( parsedCurrency == null ) {
        throw new Exception("Invalid currency format (please also ensure it is UTF-8)");
}

If you need to insure the correct Locale is being used per user look at Change locale on login

California answered 29/6, 2016 at 20:17 Comment(0)
R
0

Sorry, but any answer provided are misleading. This is what I would call a BUG in Java. An example like this explains it better. If I want to print a value in EUR using Locale.US and then I parse it again, it fails unless I specify on the DecimalFormat the currency (EUR). Using dollars, it works:

        DecimalFormat df = new DecimalFormat("¤#,##0.00", new DecimalFormatSymbols(Locale.US));
        df.setCurrency(Currency.getInstance("EUR"));
        BigDecimal value = new BigDecimal("1.23");
        String text = df.format(value);
        System.out.println(text);

        DecimalFormat df2 = new DecimalFormat("¤#,##0.00", new DecimalFormatSymbols(Locale.US));
        df2.setParseBigDecimal(true);
        BigDecimal parsed = (BigDecimal) df2.parse(text);

        BigDecimalAsserts.assertBigDecimalEquals("parsed value is the same of the original", value, parsed);
Rhizopod answered 23/3, 2020 at 22:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.