Hi Christoph,

Its worth investigating, tracking the issue with:

https://bugs.openjdk.java.net/browse/JDK-8252383

Bugs can be reported with:
   https://bugreport.java.com/bugreport/

Thanks, Roger


On 8/26/20 10:18 AM, Christoph Dreis wrote:
Hi,

A colleague of mine (filipe.silvest...@innogames.com) approached me today that 
his code wasn’t working that converted a currency String into cents.
Apparently, the code worked with Java 8 while it didn’t with 11+.

public class Main {

        public static void main(String[] args) throws IOException {
                // System.setProperty("java.locale.providers", "JRE");
                System.out.println(getPriceInCents(Locale.GERMANY, "9,99 €"));
        }

        static int getPriceInCents(Locale locale, String price) {
                try {
                        DecimalFormat format = (DecimalFormat) 
NumberFormat.getCurrencyInstance(locale);
                        Number number = format.parse(price);
                        return (int) (number.doubleValue() * 100);
                } catch (ParseException e) {
                                            // This should be thrown on JDK 9+
                        System.out.println(e);
                }
                return 0;
        }

}
        
After some digging I think this is caused by the changes done for 
JDK-8008577[1].
When I change the java.locale.providers property to "JRE" for example, it works 
again.

My investigations so far revealed that apparently the CLDR number pattern for 
the currency slightly differs.

I created breakpoints in 
sun.util.locale.provider.NumberFormatProviderImpl::getInstance() to display 
some things:

         LocaleProviderAdapter adapter = LocaleProviderAdapter.forType(type);
         String[] numberPatterns = 
adapter.getLocaleResources(override).getNumberPatterns();
         DecimalFormatSymbols symbols = 
DecimalFormatSymbols.getInstance(override);
         int entry = (choice == INTEGERSTYLE) ? NUMBERSTYLE : choice;
         DecimalFormat format = new DecimalFormat(numberPatterns[entry], 
symbols);

        // CLDR (type)
        // #,##0.00 ¤ (numberPatterns[entry])
        // [35,44,35,35,48,46,48,48,-62,-96,-62,-92] (numberPatterns[entry] in 
bytes)

        //
        // JRE type
        // #,##0.00 ¤;-#,##0.00 ¤ (numberPatterns[entry])
        // 
[35,44,35,35,48,46,48,48,32,-62,-92,59,45,35,44,35,35,48,46,48,48,32,-62,-92] 
(numberPatterns[entry] in bytes)

The JRE one includes the negative pattern, but the more interesting bit is that 
apparently the spacing differs here.
For JRE it seems to be a normal space (the 32), but for CLDR it's showing [-62, 
-96] which seems to be a non breaking space aka nbsp.

Ultimately this leads to a check failing in DecimalFormat when parsing the string 
"9,99 €" that obviously includes a normal space.

             if (gotPositive) {
                 // the regionMatches will return false because nbsp != space
                 gotPositive = text.regionMatches(position,positiveSuffix,0,
                                                  positiveSuffix.length());
             }

Which itself leads to the following in our case:

         // fail if neither or both
         if (gotPositive == gotNegative) {
             parsePosition.errorIndex = position;
             // We hit this part here which causes the parsing to fail
             return false;
         }


There are workarounds - e.g. by setting java.locale.providers as already mentioned or 
setting format.setPositiveSuffix(" €"); to fix this particular case.

Is this a bug or a feature or are we missing something?

In case this is an actual bug we would appreciate a "reported-by" mentioning in 
an eventual fix.

Thanks in advance. I do hope you can follow my thoughts in this email.

[1] https://bugs.openjdk.java.net/browse/JDK-8008577

Cheers,
Christoph



Reply via email to