NumberFormat.parse () не работает для некоторых валютных строк

У меня есть простой EditText, который позволяет пользователю вводить число, например 45.60 (например, для американского доллара). Затем я форматирую это число, используя следующий метод:

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

А на моем телефоне Android установлен английский (США) язык, поэтому Locale.getDefault() должен возвращать локаль США (и это так).

Теперь текст редактирования правильно обновлен до: $45.60 (следовательно, форматирование введенного числа работает).

Однако, если я попытаюсь проанализировать указанную выше String "$45.60", используя следующий метод:

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

Это не работает с:

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

Если я настроил свой телефон на английский / британский язык, форматирование этого "45.60" на "£45.60" будет работать правильно (как для США), однако синтаксический анализ "£45.60" завершится неудачно, как и в приведенном выше примере для США.

Однако, если я настроил свой телефон на немецкий язык (Германия), форматирование "45,60" на "45,60€" будет работать правильно, И синтаксический анализ "45,60€" также будет работать правильно!

Единственная разница, которую я вижу между этими тремя валютами: евро добавляется к сумме, а доллар и фунт добавляются к сумме.

Кто-нибудь знает, почему один и тот же код работает для евро, но не для фунта и доллара? Я что-то упускаю?

Я также создал модульный тест, чтобы воспроизвести проблему:

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 в основном обертывает опубликованный мной код для синтаксического анализа строк валюты, за исключением того, что он принимает заданный языковой стандарт вместо языкового стандарта по умолчанию. В приведенном выше примере тест проходит успешно для локали ГЕРМАНИЯ и ФРАНЦИЯ, но не проходит для локали США и Великобритании.


person AgentKnopf    schedule 23.03.2013    source источник


Ответы (6)


Поскольку ответы, которые были предложены до сих пор, не решили проблему полностью, я выбрал болезненно дилетантский подход:

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

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

Итак, теперь я просто убираю значение String из символа валюты ... Таким образом, я могу обрабатывать все, что захочу, например: 45,78 или 45,78, или 45,78 долларов США, или 45,78 евро ....

Независимо от ввода, символ валюты просто удаляется, и я получаю простой номер. Мои модульные тесты (см. OP) теперь успешно завершены.

Если кто-то придумает что-то получше, дайте мне знать.

person AgentKnopf    schedule 23.03.2013
comment
Я бы не назвал это «до боли дилетантским». Это частый случай использования, особенно при синтаксическом анализе ввода EditText. Я также рассматривал возможность применения правильного формата (со знаком валюты) непосредственно в EditText (с использованием TextWatcher), но в конечном итоге я использовал это решение как менее навязчивое для пользователя. Спасибо. - person pkk; 04.12.2013

Попробуйте следующее:

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

¤ - знак валюты, ожидает совпадений с символом валюты по Локали.

другие символы шаблона вы можете увидеть, перейдя по ссылке http://docs.oracle.com/javase/6/docs/api/java/text/DecimalFormat.html

person mokshino    schedule 23.03.2013
comment
Спасибо за предложение, он работает для доллара и фунтов, но не работает для евро. Я подозреваю, потому что в случае с евро знак валюты добавляется, а не в начале? Я скорее ищу что-то, что работает универсально. Единственное, что я могу придумать, - это разделить символ валюты с помощью регулярного выражения и проанализировать значение: / но я бы предпочел этого избежать ... - person AgentKnopf; 23.03.2013
comment
еще один подход: String value = 123,5678 € .replace (new DecimalFormatSymbols (Locale.GERMANY) .getCurrencySymbol (),); BigDecimal bigDecimal = новый BigDecimal (значение); это поможет вам не заботиться о текущем символе валюты - person mokshino; 24.03.2013
comment
Привет, спасибо за это предложение, это примерно то, что я вчера опубликовал;) (см. Мой собственный ответ ниже), и вот как я делаю это прямо сейчас. - person AgentKnopf; 24.03.2013

Попробуйте NumberFormat.getCurrencyInstance (). Parse () вместо NumberFormat.getInstance (). Parse ().

person Philip Sheard    schedule 23.03.2013
comment
Хорошо, попробовал, и это действительно не работает. Мне нужно иметь возможность анализировать 45,65 долларов США, а также 45,65 долларов США с тем же кодом. Если я изменю свой код, как вы предложили, синтаксический анализ числовых строк (без символа валюты) больше не будет работать. И: после этих изменений я могу разобрать 45,56 долларов, но 45,56 евро больше не работают oO ... - person AgentKnopf; 23.03.2013
comment
Это ограничение процедур синтаксического анализа Java. Для ваших конкретных требований вы должны попробовать оба. - person Philip Sheard; 23.03.2013
comment
Спасибо за это разъяснение. Однако в этом случае, я думаю, мне будет лучше с моей простой заменой символа валюты :) - потому что это одна вещь, которая работает для каждого случая. И я нахожу это немного громоздким, если мне нужны два разных подхода для выполнения одного и того же (синтаксический анализ строки валюты) только потому, что у некоторых валют есть добавленный символ валюты, а у других он добавлен: /. С логической точки зрения это должно быть просто: одна процедура для каждого вида анализа валюты. Поскольку он основан на локали, это не должно быть проблемой, но, к сожалению, это не так просто. - person AgentKnopf; 24.03.2013

Вы должны знать языковой стандарт строки, которую хотите проанализировать, чтобы иметь синтаксический анализатор, учитывающий языковой стандарт. Преобразование строки GBP в числовое ТОЛЬКО когда локаль NumberFormat - en_GB; не существует такой вещи, как "универсальный" синтаксический анализатор.

Например, как разбирается строка «12.000»? Для en-us ответ - двенадцать; для де-де ответ - двенадцать тысяч.

Всегда используйте NumberFormat.getCurrencyInstance (java.util.Locale) для анализа денежных сумм.

person Delfino    schedule 07.05.2015

Я использую ниже адаптированный из 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)");
}

Если вам нужно убедиться, что для каждого пользователя используется правильный языковой стандарт, посмотрите Изменить языковой стандарт при входе в систему

person melutovich    schedule 29.06.2016

Извините, но любой предоставленный ответ вводит в заблуждение. Это то, что я бы назвал ОШИБКОЙ в Java. Пример, подобный этому, объясняет это лучше. Если я хочу напечатать значение в EUR с помощью Locale.US, а затем снова проанализировать его, это не удастся, если я не укажу в DecimalFormat валюту (EUR). Используя доллары, это работает:

        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);
person Sixro    schedule 23.03.2020