Use Android ICU NumberFormat with only the JVM - java

Android has the ICU library NumberFormat which allows formatting currencies in different ways, in my specific case I need it as ISOCURRENCYSTYLE. This works great but only works on Android.
Java as its own implementation of NumberFormat with an instance builder called getCurrencyInstance, which internally uses a default currency style and doesn't allow to specify it.
Some of the differences are:
BE_FR | Android: 123,12 EUR | Java: 123,12 €
BE_NL | Android: EUR 123,12 | Java: € 123,12
BE_EN | Android: EUR 123.12 | Java: €123,12
GE_GE | Android: EUR 123.12 | Java: €123.12
FR_FR | Android: 123,12 EUR | Java: 123,12 €
Is there a way to get the same Android output but only within the JVM, without using any external library?

A solution is to replace the prefix and the suffix of the DecimalFormat with the currency code:
import java.text.*;
import java.util.Locale;
public class Test
{
public static DecimalFormat geCurrencyFormat(Locale locale)
{
DecimalFormat df = (DecimalFormat)NumberFormat.getCurrencyInstance(locale);
String code = df.getCurrency().getCurrencyCode();
if(!df.getPositivePrefix().isEmpty())
{
df.setPositivePrefix(code + " ");
df.setNegativePrefix(code + " -");
}
if(!df.getPositiveSuffix().isEmpty())
{
df.setPositiveSuffix(" " + code);
df.setNegativeSuffix(" " + code);
}
return df;
}
public static void test(Locale locale)
{
DecimalFormat df = geCurrencyFormat(locale);
System.out.println(df.format(123.12));
}
public static void main(String args[])
{
test(new Locale("fr", "BE"));
test(new Locale("nl", "BE"));
test(new Locale("en", "GB"));
}
}
Output:
123,12 EUR
EUR 123,12
GBP 123.12

Unfortunately not. You can get the three-letter ISO 4217 codes using Currency and hand-craft things like:
Locale locale = Locale.FRANCE;
float amount = 123.1f;
NumberFormat nf = NumberFormat.getInstance(locale);
Currency currency = Currency.getInstance(locale);
String formattedAmount = nf.format(amount) + " " + currency.getCurrencyCode());
However, Android is using ICU code to correctly order the value and units in the currency string. This is not in the standard Java JDKs.
I understand you don't want to use a library, so this is out of scope of the required answer. However, should you choose to use a library, take a look at ICU4J https://unicode-org.github.io/icu/userguide/icu4j/. It includes a NumberFormat with ISOCURRENCYSTYLE matching the Android behaviour.

Related

Java method not found running on oracle database

I have can not deal with execute my java method on oracle server. I load my java class on database using loadjava and create function using this query:
create or replace function getPeriodIdDay (multiplicity number, stardDate date, endDate date) return number
as language java
name 'Period.getPeriodIdDay(int, oracle.sql.Date, oracle.sql.Date) return int';
My method in class is:
public static int getPeriodIdDay(int multiplicity, DATE startDate, DATE date){
// day period calculation
int dayPeriodId = (date.dateValue().getTime() - startDate.dateValue().getTime()) / MILLIS_IN_DAY;
return dayPeriodId / multiplicity;
}
Each time when try to execute this function I have get this error:
29531. 00000 - "no method %s in class %s"
*Cause: An attempt was made to execute a non-existent method in a
Java class.
*Action: Adjust the call or create the specified method.
What am I doing wrong?
The signature of the java method in your function declaration is:
Period.getPeriodIdDay(int, oracle.sql.Date, oracle.sql.Date) return int
but the signatur of the metho in the java class itself is:
int getPeriodIdDay(int multiplicity, DATE startDate, DATE date)
If the capitalisation of DATE is no mistake when copying it here then this is a different type.
And even if it is a copy mistake: check with the imports of the java class that the same Date class is used.
the problem seems to be the DATE class, I suggest you to use String instead
You better change your method class like this:
CREATE OR REPLACE AND RESOLVE JAVA SOURCE NAMED JTESTDATE AS
import java.text.SimpleDateFormat;
import java.util.Date;
public class TestDate {
public static int getDate(int multiplicity, String startDate,
String endDate) {
SimpleDateFormat sf = new SimpleDateFormat();
sf.applyPattern("yyyymmdd");//You Have to specify format here
Date sd;
try {
sd = sf.parse(startDate);
} catch (Exception e) {
return -1;
}
Date ed;
try {
ed = sf.parse(endDate);
} catch (Exception e) {
return -2;
}
long dayPeriodId = (ed.getTime() - sd.getTime()) / 24 * 60 * 60;//this is replaced with your code
return (int) (dayPeriodId / multiplicity);
}
}

What does Java's SimpleDateFormat make with milli/micro-seconds?

I have simple test
#SuppressWarnings("deprecation")
#Test
public void test_NO_MILLIS() throws ParseException {
String rabbit = "22-OCT-15 06.37.35";
final String PATTERN = "dd-MMM-yy HH.mm.ss";
Date dateObject = new SimpleDateFormat(PATTERN).parse(rabbit);
Assert.assertNotNull(dateObject);
Assert.assertEquals(22, dateObject.getDate());
Assert.assertEquals(10, dateObject.getMonth() + 1);
Assert.assertEquals(2015, dateObject.getYear() + 1900);
Assert.assertEquals(6, dateObject.getHours());
Assert.assertEquals(37, dateObject.getMinutes());
Assert.assertEquals(35, dateObject.getSeconds());
}
And everything goes right. I get 22 as day in result.
But after I am adding microseconds both to pattern and to string value to be parsed
#Test
public void test_MILLIS() throws ParseException {
String rabbit = "22-OCT-15 06.37.35.586173000";
final String PATTERN = "dd-MMM-yy HH.mm.ss.SSSSSSSSS";
Date dateObject = new SimpleDateFormat(PATTERN).parse(rabbit);
Assert.assertNotNull(dateObject);
Assert.assertEquals(22, dateObject.getDate());
Assert.assertEquals(10, dateObject.getMonth() + 1);
Assert.assertEquals(2015, dateObject.getYear() + 1900);
Assert.assertEquals(6, dateObject.getHours());
Assert.assertEquals(37, dateObject.getMinutes());
Assert.assertEquals(35, dateObject.getSeconds());
}
I get an assert failure
junit.framework.AssertionFailedError: expected:<22> but was:<29>
at junit.framework.Assert.fail(Assert.java:57)
at junit.framework.Assert.failNotEquals(Assert.java:329)
at junit.framework.Assert.assertEquals(Assert.java:78)
at junit.framework.Assert.assertEquals(Assert.java:234)
at junit.framework.Assert.assertEquals(Assert.java:241)
at main.TestDateFormatTest.test_MILLIS(TestDateFormatTest.java:36)
...
Which means that day has become 29 instead of 22. What has gone wrong?
Tested
Platforms: mac osx 10.9, ubuntu, win7
jdk: 7,6
The format pattern S for milliseconds doesn't take into account mathematical placement values; it just sees 586173000 as the number of milliseconds to add to the rest of the date. That number is equivalent to about 6.784 days, so that explains why the date became 29 instead of 22.
Before parsing, cut off the milliseconds at 3 digits, e.g. "22-OCT-15 06.37.35.586", so it's interpreted as 586 milliseconds.

Locale ES_PE is not rendering number properly

Locale planet specified that the es_PE locale should use "." for decimal and "," for grouping. But when I run this unit test on the Oracle JVM it fails:
public class TestLocale extends TestCase {
public void test() {
Locale locale = new Locale("es", "PE");
DecimalFormatSymbols decimalFormatSymbols = new DecimalFormatSymbols(locale);
assertEquals('.', decimalFormatSymbols.getDecimalSeparator());
assertEquals(',', decimalFormatSymbols.getGroupingSeparator());
}
}
And it was reported by our support staff that numbers are no rendering correctly on WAS 8 either.
Any idea how to fix this at the JVM level? Do I need to juggle the locale management code in the application for this special case?
Change
Locale locale = new Locale("es", "PE");
to
Locale locale = new Locale("es_PE");
By the way, PE is from Peru (where I'm from).

java: default number-formatting

I have a program that does algorithmic calculations with some number-output. I want this output to look nice and the program still being fast. I used the DecumalFormat but this makes it so slow, but works.
Is there a way to set the default number output so I wouldnt need DecimalFormat???
Locale deLocale = new Locale("de_DE");
// should be format: ###,###.### 123.456,789 de_DE
Locale.setDefault (deLocale);
double f=-123456.123458998;
System.out.println (""+f+""); // I wourld expect -123.456,123
// but the output is -123456.123458998
any ideas?? thanks!
chris
You need to look at the Customizing Format.
You need a ###,###.### - de_DE pattern.
String pattern= "###,###.###";
DecimalFormat myFormatter = new DecimalFormat(pattern);
double f=-123456.123458998;
String output = myFormatter.format(f);
System.out.println(f+ " " + pattern + " " + output);
EDIT : Use Predefined format, in case you don't want your own pattern.

non standard locale with java.util.Calendar

We have a customer in Sweden, using the software in English. So we set the Locale(en, SV). We did hope the Calendar class would adhere to the country settings, but it uses the language and with this locale it assumes US settings.
So I am now searching for a way to let the calendar get to know the new firstDayOfWeek and minimumDayinFirstWeek settings preferred by a standard way other than setting it manually and thus hardcoded.
For clarification: The 29. August 2010 is in Sweden in CW 34 (also in Germany and Great Britain) but in the US it is reported as CW 36. The different results from the fact that the 01.01.2010 is a Friday and the 29.08.2010 a Sunday.
I cannot change the language setting itself to Swedish and use the English fallback since we do not support Swedish as language, but Sun/Oracle/.. does, so the Swing UI would have a mixture of Swedish and English texts, which not acceptable.
And just adding a properties file named "sun.util.resources.CalendarData_en_SV.properties" does not work out: it does not get read! Manually as a ResourceBundle that's possible. Somehow LocaleData.getCalendarData(Locale) does its own magic in reading the resourcfiles which i cannot find out since the source of it is not available. The method is called here: java.util.Calendar.setWeekCountData(Locale).
I also found the java.util.spi package but it does not provide access to the firstDayOfWeek and minimumDaysInFirstWeek settings.
Perhaps I can try to intercept the calls to the resourcebundles and use the default fallback to English and only let the calls to CalendarData proceed!? But that sounds hacky.
package de.drews.i18n;
import java.util.Calendar;
import java.util.Locale;
import java.util.ResourceBundle;
public class Test {
/**
* #param args
*/
public static void main(String[] args) {
// en_GB = 34
// en_US = 36
// sv_SV = 34
// en_SV = 36 --> wrong
printTest("en", "GB", 34);
printTest("en", "US", 36);
printTest("sv", "SV", 34);
printTest("en", "SV", 34);
}
private static void printTest(String language, String country, int expected) {
Locale locale = new Locale(language, country);
Calendar cal = Calendar.getInstance(locale);
cal.set(Calendar.YEAR, 2010);
cal.set(Calendar.MONTH, Calendar.AUGUST);
cal.set(Calendar.DATE, 29);
int actual = cal.get(Calendar.WEEK_OF_YEAR);
System.out.println(actual + "\t" + expected + "\t"
+ ((actual == expected) ? "Yeah!" : "-") + "\t" + language
+ "\t" + country);
}
}
How about using getInstance(TimeZone zone, Locale aLocale) providing a timezone to select calendar behaviour and locale to define language?
One ugly workaround that I can offer is to reflectively obtain the cachedLocaleData static field of the Calendar class, and put there the following:
key = new Locale(..); // your english-swedish locale
value = new int[] {firstDayOfWeek, minimalDaysInFirstWeek};
This can be done at init-time and will work for the entire application

Categories