The Chinese currency has the ISO 4217 code CNY. Since free global trading in that currency is restricted though, there's a second 'offshore' currency equivalent, called CNH. Wikipedia has a bit of summary of this all.
CNH isn't in ISO 4217, but I'd like to be able to use it in my app without having to write my own Currency class. Presumably there's some kind of list somewhere inside the JVM install. How do I go about adding additional currency codes?
EDIT: See this question for dealing with this in Java 7
Looks like support for this was added with Java 7.
For earlier versions, you could use an equivalent Currency class of your own devising, or less happily, replace the default java.util.Currency class (or java.util.CurrencyData, which contains the raw data) in your classpath (whitepaper).
Related
For example, the Chinese currency has the ISO 4217 code CNY. Since free global trading in that currency is restricted though, there's a second 'offshore' currency equivalent, called CNH. Wikipedia has a bit of summary of this all.
In Java 7, there's a method for updating the set of three letter ISO 4217 codes that the JVM ships with. However, it can't be used to add a separate currency code to an existing country code: it would replace CNY with CNH, which is no good for my purposes.
How do I add CNH (which is not in the ISO 4217 list) to the set of available currencies in Java 7, without overwriting CNY?
Put another way, how can I get multiple currency codes for a single country?
Note that this question: How do I add the new currency code to Java? was asked and answered for Java 6. But the strategy of replacing java.util.CurrencyData doesn't work because that file no longer exists.
The key here is in a change that's part of Java 7 to allow updating of the list of currencies without rebuilding rt.jar by replacing a file called currency.data. Using this approach, rather than the currency.properties override approach, allows you to add new Currency codes without affecting other ones from the same country.
What's left unsaid there is how to go about actually building a new currency.data.
This file is generated from a file called CurrencyData.properties, which can be found in the OpenJDK source code in java/util.
What I did was copy the CurrencyData.properties found in the OpenJDK source (openjdk\jdk\src\share\classes\java\util), and changed the line:
BZD084-CAD124-CDF976-CHF756-CLF990-CLP152-CNY156-COP170-CRC188-CSD891-CUP192-\
to
BZD084-CAD124-CDF976-CHF756-CLF990-CLP152-CNH156-CNY156-COP170-CRC188-CSD891-CUP192-\
Then I grabbed the GenerateCurrencyData.java file in the source distribution at openjdk\jdk\make\tools\src\build\tools\generatecurrencydata. This utility takes input from System.In in the same format as CurrencyData.properties, and turns it in to a currency.data file. I made a slight change so that it used a FileInputStream instead of System.In:
currencyData.load(System.in);
to
currencyData.load(new FileInputStream(fileName));
Run that on your edited CurrencyData.properties file and, after putting the original .data file somewhere safe, place the resulting currency.data file in to your JRE\lib directory, and you can now run code that uses Currency.getInstance("CNH").
To the #sharakan answer:
You may also need to add newly changed currency, in my case BYN
BY=BYR;2016-07-01-00-00-00;BYN
in format:
OLD_CUR;DATE_OF_CHANGE;NEW_CUR
Hope it will help someone.
I am aware of
NumberFormat nf = NumberFormat.getInstance(Locale.getDefault());
But I want all the numbers shown in my app to be formatted according to the locale, thus I don't think it will be a good way to format them one by one using the above method.
So is there some global setting/variable/configuration that I have to change in order to do that?
Locale-aware formatting requires more than just translating e.g. month names from one language to another. In Java that's handled by separate classes apart from the ones that actually hold the values, e.g. NumberFormat, DateFormat. So there's no way around using them like you already do.
What you could try is to create some wrappers or convenience methods (like formatDate(Date)) to simplify things for you. Also put format strings into Android Resources (res/values).
With UNIX locales, the breakdown of which means what is relatively well documented.
LC_COLLATE (string collation)
LC_CTYPE (character conversion)
LC_MESSAGES (messages shown in UI)
LC_MONETARY (formatting of monetary values)
LC_NUMERIC (formatting of non-monetary numeric values)
LC_TIME (formatting of date and time values)
LANG (fallback if any of the above are not set)
Java has a different categorisation which doesn't quite match the real world (as usual):
Locale.getDefault()
Locale.getDefault(Locale.Category.DISPLAY)
Locale.getDefault(Locale.Category.FORMAT)
If you read the documentation on these, Locale.getDefault(Locale.Category.DISPLAY) appears to correspond to LC_MESSAGES while Locale.getDefault(Locale.Category.FORMAT) appears to correspond to some combination of LC_MONETARY+LC_NUMERIC+LC_TIME.
There are problems, though.
If you read the JDK source, you start to find many worrying things. For instance, ResourceBundle.getBundle(String) - which is entirely about string messages - uses Locale.getDefault(), not Locale.getDefault(Locale.Category.DISPLAY).
So I guess what I want to know is:
Which of these methods is supposed to be used for which purpose?
Related, but I made a little test program to see which Java locales corresponded to which UNIX locales and got even more surprising results.
import java.util.Locale;
public class Test {
public static void main(String[] args) {
System.out.println(" Unqualified: " + Locale.getDefault());
System.out.println(" Display: " + Locale.getDefault(Locale.Category.DISPLAY));
System.out.println(" Format: " + Locale.getDefault(Locale.Category.FORMAT));
}
}
Locales according to my shell:
$ locale
LANG="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_CTYPE="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_ALL="en_US.UTF-8"
Output of the program:
$ java Test
Unqualified: en_AU
Display: en_AU
Format: en_AU
So it turns out Java doesn't even get it from the UNIX locale. It must be using some other back door to get the settings without using those.
It's hard to understand what you are asking here. Instead, you make a statement that reveals that you're not necessary a Java programmer. It's OK, it does not matter really.
Few things to clarify:
The Locale class is in JDK since Java 1.1
Things like Locale.Builder, Locale.Category and many others are here from Java 7 (JDK 1.7)
Locale-aware classes and methods like DateFormat, NumberFormat, Collator, ResourceBundle, String.toLowerCase(Locale), String.toUpperCase(Locale) and many, many more are here for quite a long time each (long before JDK 1.7)
Prior to Java 7/JDK 1.7 there was only one method of acquiring current OS Locale - call Locale.getDefault() (that is without parameters)
In other words, prior to Java 7, Java's Locale Model was as simple as one system property composed of a language, a country and an optional locale variant. That has changed with Java 7 (end was further extended with Java 8...) and now you have two system properties, one for formatting and one for displaying user interface messages.
The problem is, there is substantial amount of legacy code written in Java and this could shouldn't break when you upgrade the platform. And that is exactly why you still have parameterless Locale.getDefault() around. Moreover (you may test it yourself), Locale.getDefault() is basically interchangeable with Locale.getDefault(Locale.Category.DISPLAY).
Now, I said formatting and user interface messages. Basically, formatting is not only formatting, but things like character case conversion (LC_CTYPE), collation (LC_COLLATE) as well. Sort of anything but user interface messages. Sort of, because default character encoding (which depends on an OS, BTW) is not part of Locale. Instead you need to call Charset.defaultCharset().
And the fallback rules (built in Java, not read from OS) could be worked out with ResourceBundle.Control class. And as we know, it is rather related to UI category...
The reason why Java Locale Model is different from POSIX (not UNIX, it's more universal), is the simple fact that there are quite a few platforms out there. And these platforms doesn't necessary use POSIX... I mean not only Operating Systems, but things like web... Java is striving to be universal and versatile. As the result Java's Locale Model is convoluted, tough luck.
I have to add that nowadays, it's not only the language and the country, but there are also things like preferred script, calendar system, numbering system, specific collation settings and possibly more. It even works sometimes.
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
For example, the Chinese currency has the ISO 4217 code CNY. Since free global trading in that currency is restricted though, there's a second 'offshore' currency equivalent, called CNH. Wikipedia has a bit of summary of this all.
In Java 7, there's a method for updating the set of three letter ISO 4217 codes that the JVM ships with. However, it can't be used to add a separate currency code to an existing country code: it would replace CNY with CNH, which is no good for my purposes.
How do I add CNH (which is not in the ISO 4217 list) to the set of available currencies in Java 7, without overwriting CNY?
Put another way, how can I get multiple currency codes for a single country?
Note that this question: How do I add the new currency code to Java? was asked and answered for Java 6. But the strategy of replacing java.util.CurrencyData doesn't work because that file no longer exists.
The key here is in a change that's part of Java 7 to allow updating of the list of currencies without rebuilding rt.jar by replacing a file called currency.data. Using this approach, rather than the currency.properties override approach, allows you to add new Currency codes without affecting other ones from the same country.
What's left unsaid there is how to go about actually building a new currency.data.
This file is generated from a file called CurrencyData.properties, which can be found in the OpenJDK source code in java/util.
What I did was copy the CurrencyData.properties found in the OpenJDK source (openjdk\jdk\src\share\classes\java\util), and changed the line:
BZD084-CAD124-CDF976-CHF756-CLF990-CLP152-CNY156-COP170-CRC188-CSD891-CUP192-\
to
BZD084-CAD124-CDF976-CHF756-CLF990-CLP152-CNH156-CNY156-COP170-CRC188-CSD891-CUP192-\
Then I grabbed the GenerateCurrencyData.java file in the source distribution at openjdk\jdk\make\tools\src\build\tools\generatecurrencydata. This utility takes input from System.In in the same format as CurrencyData.properties, and turns it in to a currency.data file. I made a slight change so that it used a FileInputStream instead of System.In:
currencyData.load(System.in);
to
currencyData.load(new FileInputStream(fileName));
Run that on your edited CurrencyData.properties file and, after putting the original .data file somewhere safe, place the resulting currency.data file in to your JRE\lib directory, and you can now run code that uses Currency.getInstance("CNH").
To the #sharakan answer:
You may also need to add newly changed currency, in my case BYN
BY=BYR;2016-07-01-00-00-00;BYN
in format:
OLD_CUR;DATE_OF_CHANGE;NEW_CUR
Hope it will help someone.