skip to Main Content

I seem to be getting very inconsistent results when trying to format currency. In PHP, I’m using https://www.php.net/manual/en/class.numberformatter.php. I have the following demo code:

$number = 5125.99;
echo getInternationallyFormattedCurrency($number, 'tl-PH', 'PHP');
echo '<br/>';
echo getInternationallyFormattedCurrency($number, 'fil-PH', 'PHP');
echo '<br/>';
echo getInternationallyFormattedCurrency($number, 'en-US', 'PHP');
echo '<br/>';
echo '<br/>';
echo getInternationallyFormattedCurrency($number, 'tl_PH', 'USD');
echo '<br/>';
echo getInternationallyFormattedCurrency($number, 'fil_PH', 'USD');
echo '<br/>';
echo getInternationallyFormattedCurrency($number, 'en_US', 'USD');
echo '<br/>';

When I run this on my localhost (was PHP Version 7.3.7, but I updated to match my server’s PHP Version 7.3.12 – ICU version 64.2), I get this:

₱5,125.99
₱5,125.99
PHP 5,125.99

$5,125.99
$5,125.99
$5,125.99

However, when I run it on my server (PHP Version 7.3.12 – ICU version 4.2.1), I get this:

₱ 5125.99
₱ 5125.99
₱5,125.99

$ 5125.99
$ 5125.99
$5,125.99

Why the difference? And which one is actually correct? I’m guessing my local machine due to higher ICU version?

I need the exact same functionality from JS, too. So, on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat, I put the following equivalent code:

var number = 5125.99;

console.log(new Intl.NumberFormat('tl-PH', { style: 'currency', currency: 'PHP' }).format(number));
console.log(new Intl.NumberFormat('fil-PH', { style: 'currency', currency: 'PHP' }).format(number));
console.log(new Intl.NumberFormat('en-US', { style: 'currency', currency: 'PHP' }).format(number));
console.log(new Intl.NumberFormat('tl-PH', { style: 'currency', currency: 'USD' }).format(number));
console.log(new Intl.NumberFormat('fil-PH', { style: 'currency', currency: 'USD' }).format(number));
console.log(new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(number));

I get this:

> "PHP 5,125.99"
> "₱5,125.99"
> "PHP 5,125.99"
> "$5,125.99"
> "$5,125.99"
> "$5,125.99"

So – this is yet another result. I need a way to format currencies consistency between PHP and JS. How do I do this?

UPDATE 1:

My local machine’s ICU version is 64.2 while my server has 4.2.1. I’ll see if my hosting provider can update the ICU version to the latest. That might explain the discrepancy between what my local machine outputs versus what my server outputs.

Still not sure why JS behaves differently.

UPDATE 2:

My hosting company says that because of cPanel, I need to stick to ICU version 4.2.1. Although cPanel has some tips on how to upgrade ICU version, it is apparently not recommended to do so.

UPDATE 3:

I’m now thinking to have my JS make an Ajax call to a PHP method that will handle number formatting, that way I can be sure I’m getting the same formatting output. Feels like a slow and expensive solution, though.

3

Answers


  1. I tried this approach:

    <?php
    
    function process($locale, $value, $currency){
        $fmt = numfmt_create($locale, NumberFormatter::CURRENCY );
        echo "Locale: $locale, Currency: $currency, Result: " . $fmt->formatCurrency($value, $currency) . "n";
    }
    
    $locales = ["tl_PH","fil_PH", "en_US"];
    $currencies = ["USD", "PHP"];
    $value = 5125.99;
    
    foreach ($locales as $locale){
        foreach ($currencies as $currency){
            process($locale, $value, $currency);
        }
    }
    

    and got consistent results:

    Locale: tl_PH, Currency: USD, Result: $5,125.99
    Locale: tl_PH, Currency: PHP, Result: ₱5,125.99
    Locale: fil_PH, Currency: USD, Result: $5,125.99
    Locale: fil_PH, Currency: PHP, Result: ₱5,125.99
    Locale: en_US, Currency: USD, Result: $5,125.99
    Locale: en_US, Currency: PHP, Result: PHP 5,125.99
    

    Thus, to be 100% safe, I would suggest creating a small server side component for the formatting purposes, so that you can pass the locale, value and currency and always have the same result regardless if it’s backend or frontend that needs the formatted string.

    Also, note that tl-PH and tl_PH is not the same. Use tl_PH and fil_PH and en_US consistently.

    Login or Signup to reply.
  2. Just an idea… But to get consistent results accross two different languages, I would suggest using the exact same library code.

    What if you used https://github.com/phpv8/v8js to run Intl.NumberFormat on PHP side? There’s also an example here https://github.com/phpv8/v8js/issues/182

    I would probably go with this path to get everything consistent

    Login or Signup to reply.
  3. From the php side, since php rely on underlying C libraries, this is from where comes inconsistencies (at compilation time):

    The note about different formatting actually does not depend on the
    PHP version but on the version of the icu library that PHP is
    compiled against because this library has a database with formatting
    rules for the different locales.
    https://www.php.net/manual/en/numberformatter.formatcurrency.php#116984

    Nothing much to do from php, we must takes extra cares of that.

    Here are some notes following own tests!

    Those snippets returns formatted numbers (won’t return scientific notation), but are rounding, which can be not desired. It takes care of grouped thousands, we can choose to display or not. From my view it’s a bad idea and wanted to avoid them.

    From javascript, there is a lot of options:

    var number = 5125.9566654332455556679;
    
    console.log("n",
      number.toLocaleString('fil-PH', {maximumFractionDigits:5, style:'currency', currency:'PHP', useGrouping:true})
      ,"n",
      number.toLocaleString('fil-PH', {maximumFractionDigits:5, style:'currency', currency:'PHP', useGrouping:false})
      ,"n",
      number.toLocaleString('fullwide', {maximumFractionDigits:5, style:'currency', currency:'USD', useGrouping:true})
      ,"n",
      number.toLocaleString('fullwide', {maximumFractionDigits:5, style:'currency', currency:'USD', currencyDisplay:'symbol', useGrouping:false})
    
    )

    Sample output:

     ₱5,125.95667 
     ₱5125.95667 
     $5,125.95667 
     $5125.95667
    

    List of supported codes


    From php, to obtain something similar, I found easier to use associative arrays for the symbols. The code works then everywhere identically.

    <?php
    $number = 5125.9566654332455556679;
    
    $a = new NumberFormatter("en-US", NumberFormatter::CURRENCY);
    $a = new NumberFormatter("tl-PH", NumberFormatter::CURRENCY);
    echo $a->format($number);  
    
    echo PHP_EOL;
    
    $symbols = (object)["PHP"=>"₱", "USD"=>"$"];
    $precision = 5;
    
    echo number_format($number,$precision,'.','');
    echo PHP_EOL;
    
    echo $symbols->PHP . number_format($number,$precision);
    echo PHP_EOL;
    
    echo $symbols->USD . number_format($number,$precision,'.','');
    echo PHP_EOL;
    

    Sample output. Notice the rounding using NumberFormatter(). It’s in most cases unwanted, it’s more flexible to use just number_format().

    ₱5,125.96
    5125.95667
    ₱5,125.95667
    $5125.95667
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search