Last active
August 12, 2022 12:01
-
-
Save clarkphp/b2643a4c4a16cafd02bd85eba18a72dd to your computer and use it in GitHub Desktop.
Scratchpad for making a map from money_format() to NumberFormatter->formatCurrency()
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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