Background
I have an Android application where I want to format an integer value as a currency string depending on the ISO code and current locale. I'm using ICU library 1.8.1 for that.
For example, if I have a value 75 and the ISO code is "USD", I want to see "$75" on US locale, but "USD75" on French locale.
Problem
The problem is that I always see "USD75" even when I explicitly set the locale to US. I thought the problem is in my NumberFormat currency instance, but then I tried to simply get the currency symbol and noticed that it's incorrect. For some reason getSymbol() method always returns currency code.
public void test() {
Locale.setDefault(Locale.US);
String theISOCode = "USD"
Currency currency = Currency.getInstance(theISOCode);
currency.getCurrencyCode(); // "USD". This works as expected
currency.getSymbol(); // "USD". This looks weird.. Shouldn't it be "$"?
currency.getSymbol(Locale.US); // "USD". Same here, I expect it to be "$"
currency.getSymbol(ULocale.US); // still "USD"
}
I also checked this link: Java: Currency symbol based on ISO 4217 currency cod. It seems my app works a bit differently. Not sure if it's a bug in the library.
Question
It makes sense to me that currency.getSymbol() returns "USD" if your locale is Locale.FRANCE. But why does it return "USD" when my locale is Locale.US? Would be great if I could find a solution without switching the library.
The issue turned out to be a bug in the library. After updating the version the issue got resolved. Thanks #Omid for looking into this.
Related
I'm trying to get the number format according to current locale but I have a problem with the currency symbol.
This is my method:
import java.util.Locale;
import java.text.NumberFormat;
public void i18nCurrency(Locale currentLocale) {
Double price = 9876543.21;
NumberFormat currencyFormatter =
NumberFormat.getCurrencyInstance(currentLocale);
System.out.println(currencyFormatter.format(price));
}
It prints: ¤ 9 876 543,21 for uk and ¤9.876.543,21 for german. The number format is correct, but I need to get the currency symbol as well. Why I can't get the symbol?
The symbol you're getting is a universal currency placeholder. It is displayed when currency is unknown.
You probably wonder why it is unknown. Well, from your description you simply called the method passing something like Locale.GERMAN. If you did, there is no way of knowing what currency to use, because:
Euro is a currency of Germany and Austria
Swiss Frank (SFr.) is a currency of Switzerland
Each of these countries has German as at least one of their official languages. In order to resolve the problem, you always need to pass a country, i.e. call the method with Locale.GERMANY as a parameter.
Now, the harder part. It is all fairly easy when you are working with desktop application. All you have to do is to detect current OS locale like this:
Locale currentLocale = Locale.getDefault(LocaleCategory.FORMAT);
However, this method won't work with web applications. I suspect this is the case. Well, the Locale that web browsers give you might be not suitable for formatting currencies, as they may lack information about the country.
The recommended way to solve this issue is to create user profile and ask users to select the Locale (separately for UI translations and formatting purposes).
I still have to point out one important thing, because I don't want you to run into problems. When you have some monetary value in your application (usually it should be an instance of BigDecimal class, as double is not suitable for this purpose), it represents some value in a specific currency. Be it Euro, British Pound, or a Dollar, but the value is specific. It doesn't really make sense to format this value for specific country currency, as you should first change the amount (I believe you understand why).
What you probably need instead, is overriding the currency symbol or currency code to match your currency. The format and the symbol placement should obviously stay intact.
Please consider this example:
Currency dollar = Currency.getInstance("USD");
NumberFormat fmt = NumberFormat.getCurrencyInstance(Locale.GERMANY); //this gets € as currency symbol
BigDecimal monetaryAmount = BigDecimal.valueOf(12.34d);
String originalEuros = fmt.format(monetaryAmount);
System.out.println(originalEuros);
fmt.setCurrency(dollar); // change the currency symbol to $
String modifiedDollars = fmt.format(monetaryAmount);
System.out.println(modifiedDollars);
This prints:
12,34 €
12,34 USD
Wait, why? The answer to your question lies in this subtle code snippet:
System.out.println(currency.getSymbol(Locale.GERMANY));
System.out.println(currency.getSymbol(Locale.US));
The result:
USD
$
What gets printed depends on a Locale. It is probably better this way, I cannot tell...
I believe, unless you are creating Internet currency exchange application, you should stick to my example.
I'm trying to add bitcoin as a currency to display on my site. I've got exchange rates and everything, but I keep getting an IllegalArgumentException whenever I use java.util.Currency.getInstance("BTC"). This makes sense since it's not included in the list of ISO 4217 currency codes, and also not in Java 7. I've seen a couple of options, but nothing that really solves my issue.
According to the Java platform docs, you can override a specific locale's currency by creating a file $JAVA_HOME/lib/currency.properties. This is a problem since bitcoin is not tied to a specific locale, nor should it be used in place of any country's currency.
Another similar situation was presented in this StackOverflow post, where China had a second currency code to be used, so the solution was to build your own currency.data file that added a second currency for the China locale. This is better, but there is still the issue of tying a currency to a locale.
Has anyone run into this problem or found a workaround? I know bitcoin is relatively new, but it'd be cool to be able to display prices in bitcoin format.
You cannot use BTC as the currency code for bitcoins under ISO 4217. BT is reserved for Bhutan. However, ISO 3166-1 reserves several country codes for user definition. Additionally, the wiki for ISO 4217 lists XBT as a currency code for bitcoins (unofficially, of course).
Locale.Builder b = new Locale.Builder();
b.setRegion("XB");
Locale xb = b.build();
Currency bitcoin = Currency.getInstance(xb);
Your currency.properties file will look like:
XB=XBT,000,3
Unfortunately, you cannot have 8 for the minor unit because the parsing for java.util.Currency only handles a minor unit of 0-3:
Pattern propertiesPattern = Pattern.compile("([A-Z]{3})\\s*,\\s*(\\d{3})\\s*,\\s*([0-3])");
How to handle bitcoin money is explained well on the documentation. Take a look at this https://en.bitcoin.it/wiki/Proper_Money_Handling_(JSON-RPC)
Hope it helps
I had a perception that Locale is just about adding comma at proper positions at least in case of numbers. But I see a different output for what I have tried.
I tried the following,
public static void main(String[] args) {
DecimalFormat df = null;
df = (DecimalFormat) DecimalFormat.getInstance(Locale.CHINESE);
System.out.println("Locale.CHINESE "+df.format(12345.45));
df = (DecimalFormat) DecimalFormat.getInstance(Locale.GERMAN);
System.out.println("Locale.GERMAN "+df.format(12345.45));
}
Output:
Locale.CHINESE 12,345.45
Locale.GERMAN 12.345,45
If you carefully look at the comma's, you'll see a major difference.
Now, the javadoc for java.util.Locale says
... An operation that requires a Locale to perform its task is called locale-sensitive and uses the Locale to
tailor information for the user. For example, displaying a number is a locale-sensitive operation--the number
should be formatted according to
the customs/conventions of the user's native country, region, or culture ...
I see a comma being interpreted as decimal point in another Locale, which is really a curious thing, as the value is being changed.
So, help me understand this. What exactly is Locale? Won't the drastic change in output cause major issue in code/data?
I had a perception that Locale is just about adding comma at proper positions at least in case of numbers.
No, it affects the symbols used as well, as you've seen.
So, help me understand this. What exactly is Locale? Won't the drastic change in output cause major issue in code/data?
Only if you don't use them correctly :) Machine-to-machine communication should usually not be localized; typically if you really need to use text, it's best to use US as a reasonably invariant locale.
See DecimalFormatSymbols for more details of what is locale-specific.
I see nothing wrong with the above. The German way of representing 12345.45 is 12.345,45
and the Chinese way of representing the same number is 12,345.45 .
So, help me understand this. What exactly is Locale? Won't the drastic
change in output cause major issue in code/data?
No it won't you just need to keep track of the locale of the input and how you want it formatted.
I having some problems getting the default currency symbol of the system.
I am getting the currency symbol this way:
Currency currency = Currency.getInstance(Locale.getDefault());
Log.v("TAG",currency.getSymbol());
When the system language is in English (United States) the right symbol shows up ($).
But when i choose the language Portuguese (Portugal) it returns this symbol ¤.
What can be causing this?
This seems to be a known issue (http://code.google.com/p/android/issues/detail?id=38622.
I came to a possible solution this way:
Since the problem is in the Symbol and not the Currency code, i solved this problem creating a staticMap where the key is the CurrencyCode and the value is the Symbol.
public static final Map<String, String> MYCURRENCIES = new HashMap<String, String>(){
{
put("EUR","€");
put("USD","$");
(..)
}
};
In order to get all (or almost) the currencies codes available in the locales information you can do something like this:
for (Locale ll: Locale.getAvailableLocales()){
try {
Currency a = Currency.getInstance(ll);
Log.v("MyCurrency",a.getCurrencyCode()+"#"+a.getSymbol());
}catch (Exception e){
// when the locale is not supported
}
}
After you created you Map with the CurrencyCode and Symbol you just have to something like this:
Currency currency = Currency.getInstance(Locale.getDefault());
String curSymbol = MYCURRENCIES.get(currency.getCurrencyCode());
Some thoughts;
Could it be that you're getting the right symbol for (Euro) but your font/log doesn't have it and it only looks like that symbol?
Locale is a pretty frisky class, it's constructors have no error checking. You can easily see locales that aren't supported or don't really exist in the standards (such as "de_US" for "German as spoken in the US").
Note that locale data is not necessarily available for any of the locales pre-defined as constants in this class except for en_US, which is the only locale Java guarantees is always available, and it differs in different Android releases, and also can be limited by the operator or custom builds. pt_PT was added in 2.3.
Regarding the option you presented, if you check out Unicode's standards as they have been implemented in API8 and up, Portugal exists only as a territory (and not the combination).
It may be better if you therefore create a partial locale instance from the country code alone, and then get the currency symbol for Portugal for instance. You will not be able to comply with the predefined statics of Locale's class as it pretty narrowly supports different locales (It's a short list), but it should work as this locale has the data you are looking for in the system.
I would also try and see if currency.toString() returns the EUR (the ISO 4217 code of the currency, usually a three letter acronym).
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance();
String cur = currencyFormatter.format(yourValue);
This Formats your int/double to a currency based on the Device Language
String currency = cur.replaceAll("[0-9.,]","");
And this is how to get just the currency Symbol, by replacing all numbers, dots and commas
I'm doing some javascript work inside a ColdFusion shopping cart, and I need to be able to format some numbers in js which will mimic LScurrencyFormat() in CF.
Currently we are taking the first (left,1) character of a formatted string but that doesn't work for currencies like Yen or Euro which come after the number, not to mention any multiple character currency symbols.
What I need to find, based on the current CF locale, is
currency symbol
decimal delimiter (, or .)
leading or trailing (before or after the number)
From there i can run my own js formatting to make the formatted numbers come out as expected on the page.In php we can use localeconv() to get these values... how can I find them in CF?
I am not aware of any built in functions. However, you can obtain the first two items from java. As far as the third, the closest suggestion I have seen is to parse the localized number pattern and detect the position of the currency sign ie \u00A4. Note: It is just a mask placeholder. It is not the same as the actual currency symbols like "$" or "£".
Edit:
As discussed in the comments, getLocale() returns some user friendly name which unfortunately does not quite line up with java's. The easiest way to get the java locale object for the current request is using getPageContext().getResponse().getLocale().
<cfscript>
// Get the current locale as a java object
javaLocale = getPageContext().getResponse().getLocale();
// get numeric settings for that locale
currency = createObject("java", "java.text.DecimalFormat").getCurrencyInstance(javaLocale);
symbols = currency.getDecimalFormatSymbols();
// 164 => decimal code point for currency sign
currencyPattern = currency.toLocalizedPattern();
result.hasTrailingCurrencySymbol = currencyPattern.indexOf(javacast("int", 164)) > 0;
result.currencySymbol = symbols.getCurrencySymbol();
result.decimalSeparator= symbols.getDecimalSeparator();
WriteDump(result);
</cfscript>
getLocale() returns the old cf5 style locale "names" but only for those locales supported by cf5. if you dump out the supported locales (Server.Coldfusion.SupportedLocales) you'll see the goofy old cf5 style locale names as well as the core java locale IDs (ie both "Chinese(China)" and "zh_CN"). if your locale wasn't one of the cf5 supported locales you should see the core java locale ID (ie th_TH for thai, thailand). see
http://cfbugs.adobe.com/cfbugreport/flexbugui/cfbugtracker/main.html#bugId=82474
as a small tweak to leigh's answer, you should also be concerned with the currency/locale's fraction digits. for instance in normal practice, you can't have part of a yen (ie 1.1 isn't quite kosher). you can get that info from the Currency class's getDefaultFractionDigits() method:
result.fractionDigits=currency.getDefaultFractionDigits();