Skip to content

Instantly share code, notes, and snippets.

@clarkphp
Last active August 12, 2022 12:01
Show Gist options
  • Save clarkphp/b2643a4c4a16cafd02bd85eba18a72dd to your computer and use it in GitHub Desktop.
Save clarkphp/b2643a4c4a16cafd02bd85eba18a72dd to your computer and use it in GitHub Desktop.
Scratchpad for making a map from money_format() to NumberFormatter->formatCurrency()
#!/usr/bin/env php
<?php
error_reporting(E_ALL & ~E_DEPRECATED & ~E_WARNING);
// money_format() format specification string elements
$fillChar = '(=.){0,1}';
$suppressGroupingChars = '(\^){0,1}';
$posNegFormatStyle = '(\+|\(){0,1}';
$suppressCurrencySymbol = '(!){0,1}';
$leftJustify = '(-){0,1}';
$flags = "{$fillChar}{$suppressGroupingChars}{$posNegFormatStyle}{$suppressCurrencySymbol}{$leftJustify}";
$fieldWidth = '(\d+){0,1}';
$leftPrecision = '(#\d+){0,1}';
$rightPrecision = '(\.\d+){0,1}';
$conversionChar = '(i|n|%)';
$formatPattern = "/%{$flags}{$fieldWidth}{$leftPrecision}{$rightPrecision}{$conversionChar}/";
$matchGroupIndexes = [
'fillChar' => 1, //
'suppressGroupingChars' => 2, //
'posNegFormatStyle' => 3, //
'suppressCurrencySymbol' => 4, //
'leftJustify' => 5,
'fieldWidth' => 6, //?
'leftPrecision' => 7,
'rightPrecision' => 8,
'conversionChar' => 9,
];
$locales = ['en_US'/*, 'it_IT', /*'de_DE'/*, 'ch_CH'*/];
//$currencies = ['USD', 'EUR'/*, 'GBP'*/];
$numbers = [1234.5678, -1234.5678];
$formatSpecs = [
'%n', // national currency symbol
'%i', // int'l currency symbol
'%=*i', // fill char *
'%=*^i', // fill char *, suppress group chars
'%=*^(i', // fill char *, suppress group chars, surround neg w/ ()
'%=*^(i', // fill char *, suppress group chars, surround neg w/ ()
'%=*^(!i', // fill char *, suppress group chars, surround neg w/ (), suppress currency symb,
'%12n', // national currency symbol, fill char space, show group chars, do not surround neg w/ (), keep currency symb, field width
'%=*13n', // nat'l; currency symbol, fill char *, show group chars, do not surround neg w/ (), keep currency symb, field width
'%=*(14#10n',
'%(14#10n',
'%=*(#10.2n',
'%=*(#10.3n',
'%=*(#10.0n',
'%=*^(!-14#6.2i', // fill char *, suppress group chars, surround neg w/ (), suppress currency symb, left justify/pad right, field width, left precision, right precision, int'l currency symbol
'%^(!-14#6.2i', // fill char ' ', suppress group chars, surround neg w/ (), suppress currency symb, left justify/pad right, field width, left precision, right precision, int'l currency symbol
'%(14#6.2n', // fill char ' ', show group chars, surround neg w/ (), show currency symb, right justify/pad left, field width, left precision, right precision, nat'l currency symbol
'%=*+14#6.2n', // fill char *, show group chars, surround neg w/ (), show currency symb, right justify/pad left, field width, left precision, right precision, nat'l currency symbol
'%(-14#8.2n',
'%=*(-14#8.2i',
];
foreach ($locales as $locale) {
foreach ($formatSpecs as $formatSpec) {
foreach ($numbers as $number) {
$matches = [];
preg_match($formatPattern, $formatSpec, $matches);
setlocale(LC_MONETARY, $locale);
switch ($matches[$matchGroupIndexes['posNegFormatStyle']]) {
case '':
case '+':
$formatter = new NumberFormatter($locale, NumberFormatter::CURRENCY);
break;
case '(':
$formatter = new NumberFormatter($locale, NumberFormatter::CURRENCY_ACCOUNTING);
break;
default: // should never occur
$formatter = new NumberFormatter($locale, NumberFormatter::CURRENCY);
var_dump($matches);
exit;
}
if (intl_is_failure(intl_get_error_code())) {
exit(intl_get_error_message() . PHP_EOL);
}
//printf(
// "Intl symbol: %s\tlen: %d\tNatl symbol: %s\tlen: %d\tcurr code: %s\tlen:%d\n",
// $formatter->getSymbol(NumberFormatter::INTL_CURRENCY_SYMBOL),
// strlen($formatter->getSymbol(NumberFormatter::INTL_CURRENCY_SYMBOL)),
// $formatter->getSymbol(NumberFormatter::CURRENCY_SYMBOL),
// strlen($formatter->getSymbol(NumberFormatter::CURRENCY_SYMBOL)),
// $formatter->getTextAttribute(NumberFormatter::CURRENCY_CODE),
// strlen($formatter->getTextAttribute(NumberFormatter::CURRENCY_CODE))
//);
switch ($matches[$matchGroupIndexes['conversionChar']]) {
case 'i':
$formatter->setSymbol(
NumberFormatter::CURRENCY_SYMBOL,
$formatter->getSymbol(NumberFormatter::INTL_CURRENCY_SYMBOL)
);
break;
case 'n':
$formatter->setSymbol(
NumberFormatter::CURRENCY_SYMBOL,
$formatter->getSymbol(NumberFormatter::CURRENCY_SYMBOL)
);
break;
default:
throw new \InvalidArgumentException('Required conversion character not supplied in money_format() pattern string');
}
$formatter->setTextAttribute(NumberFormatter::PADDING_CHARACTER, ($matches[$matchGroupIndexes['fillChar']][1]) ?? ' ');
if ($matches[$matchGroupIndexes['suppressGroupingChars']] === '^') {
$formatter->setAttribute(NumberFormatter::GROUPING_USED, 0);
}
if ($matches[$matchGroupIndexes['suppressCurrencySymbol']] === '!') {
$formatter->setSymbol(NumberFormatter::CURRENCY_SYMBOL, '');
}
if ($matches[$matchGroupIndexes['fieldWidth']] !== '') {
$formatter->setAttribute(NumberFormatter::FORMAT_WIDTH, $matches[$matchGroupIndexes['fieldWidth']]);
}
if ($matches[$matchGroupIndexes['leftPrecision']] !== '') {
$formatter->setAttribute(NumberFormatter::MAX_INTEGER_DIGITS, substr($matches[$matchGroupIndexes['leftPrecision']], 1));
}
if ($matches[$matchGroupIndexes['rightPrecision']] !== '') {
$formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, substr($matches[$matchGroupIndexes['rightPrecision']], 1));
}
if ($matches[$matchGroupIndexes['rightPrecision']] === '.0') {
$formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, substr($matches[$matchGroupIndexes['rightPrecision']], 1));
}
if (($matches[$matchGroupIndexes['leftJustify']] ?? '') === '-') {
$formatter->setAttribute(NumberFormatter::PADDING_POSITION, NumberFormatter::PAD_AFTER_PREFIX);
}
if (assert(
money_format($formatSpec, $number)
== str_replace("\xc2\xa0", ' ', $formatter->formatCurrency($number, $formatter->getTextAttribute(NumberFormatter::CURRENCY_CODE)))
)) {
print '.';
} else {
print 'F';
showDetails($locale, $formatSpec, $number, $formatter, $matches);
}
}
}
}
function showDetails(string $locale, string $formatSpec, float $number, NumberFormatter $formatter, array $matches): void
{
print "\nLocale $locale" . PHP_EOL;
print "\tformatSpec $formatSpec" . PHP_EOL;
print "\t\tnumber $number" . PHP_EOL;
if (intl_is_failure($formatter->getErrorCode())) {
print $formatter->getErrorMessage() . PHP_EOL;
}
// print_r($matches);
var_dump(
money_format($formatSpec, $number),
str_replace("\xc2\xa0", // NumberFormatter uses non-breaking space, money_format() does not
' ',
$formatter->formatCurrency($number, $formatter->getTextAttribute(NumberFormatter::CURRENCY_CODE))
)
);
// $moneyFmtResult = money_format($formatSpec, $number);
// $len = strlen($moneyFmtResult);
// for ($i = 0; $i < $len; $i++) {
// print "{$moneyFmtResult[$i]}:" . ord($moneyFmtResult[$i]) . '|';
// }
//
// $numFmtResult = $formatter->formatCurrency($number, $formatter->getTextAttribute(NumberFormatter::CURRENCY_CODE));
// $len = strlen($numFmtResult);
// for ($i = 0; $i < $len; $i++) {
// print "{$numFmtResult[$i]}:" . ord($numFmtResult[$i]) . '|';
// }
// print PHP_EOL . '|' . str_replace("\xc2\xa0", ' ', $numFmtResult) . '|' . PHP_EOL;
}
/* Output. I'm not being US-centric, merely handling the peculiarities of one locale, after discovering differences
between INTL and NATL currency symbol positioning between en_US and everything else.
Dots indicate a passing assert(), F and the other output a failed assert().
NumberFormatter uses non-breaking space between curreny symbol and the number, while money_format() uses regular space.
$ docker run -it --rm -v $PWD:/app -w /app/tests money-numberformatter:1.6 ./money-format-tests.php
................F
Locale en_US
formatSpec %=*13n
number 1234.5678
string(13) " $1,234.57"
string(13) "****$1,234.57"
F
Locale en_US
formatSpec %=*13n
number -1234.5678
string(13) " -$1,234.57"
string(13) "***-$1,234.57"
F
Locale en_US
formatSpec %=*(14#10n
number 1234.5678
string(18) " $********1,234.57"
string(14) "*****$1,234.57"
F
Locale en_US
formatSpec %=*(14#10n
number -1234.5678
string(19) "($********1,234.57)"
string(14) "***($1,234.57)"
F
Locale en_US
formatSpec %(14#10n
number 1234.5678
string(18) " $ 1,234.57"
string(14) " $1,234.57"
F
Locale en_US
formatSpec %(14#10n
number -1234.5678
string(19) "($ 1,234.57)"
string(14) " ($1,234.57)"
F
Locale en_US
formatSpec %=*(#10.2n
number 1234.5678
string(18) " $********1,234.57"
string(9) "$1,234.57"
F
Locale en_US
formatSpec %=*(#10.2n
number -1234.5678
string(19) "($********1,234.57)"
string(11) "($1,234.57)"
F
Locale en_US
formatSpec %=*(#10.3n
number 1234.5678
string(19) " $********1,234.568"
string(10) "$1,234.568"
F
Locale en_US
formatSpec %=*(#10.3n
number -1234.5678
string(20) "($********1,234.568)"
string(12) "($1,234.568)"
F
Locale en_US
formatSpec %=*(#10.0n
number 1234.5678
string(15) " $********1,235"
string(6) "$1,235"
F
Locale en_US
formatSpec %=*(#10.0n
number -1234.5678
string(16) "($********1,235)"
string(8) "($1,235)"
F
Locale en_US
formatSpec %=*^(!-14#6.2i
number 1234.5678
string(14) " **1234.57 "
string(14) "*******1234.57"
F
Locale en_US
formatSpec %=*^(!-14#6.2i
number -1234.5678
string(14) "(**1234.57) "
string(14) "(*****1234.57)"
F
Locale en_US
formatSpec %^(!-14#6.2i
number 1234.5678
string(14) " 1234.57 "
string(14) " 1234.57"
F
Locale en_US
formatSpec %^(!-14#6.2i
number -1234.5678
string(14) "( 1234.57) "
string(14) "( 1234.57)"
F
Locale en_US
formatSpec %(14#6.2n
number 1234.5678
string(14) " $ 1,234.57"
string(14) " $1,234.57"
F
Locale en_US
formatSpec %(14#6.2n
number -1234.5678
string(14) " ($ 1,234.57)"
string(14) " ($1,234.57)"
F
Locale en_US
formatSpec %=*+14#6.2n
number 1234.5678
string(14) " $**1,234.57"
string(14) "*****$1,234.57"
F
Locale en_US
formatSpec %=*+14#6.2n
number -1234.5678
string(14) " -$**1,234.57"
string(14) "****-$1,234.57"
F
Locale en_US
formatSpec %(-14#8.2n
number 1234.5678
string(15) " $ 1,234.57"
string(14) "$ 1,234.57"
F
Locale en_US
formatSpec %(-14#8.2n
number -1234.5678
string(16) "($ 1,234.57)"
string(14) "($ 1,234.57)"
F
Locale en_US
formatSpec %=*(-14#8.2i
number 1234.5678
string(18) " USD *****1,234.57"
string(14) "USD***1,234.57"
F
Locale en_US
formatSpec %=*(-14#8.2i
number -1234.5678
string(19) "(USD *****1,234.57)"
string(14) "(USD*1,234.57)"
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment