forked from moneyphp/money
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add intl localized decimal parser/formatter (moneyphp#443)
* add intl localized decimal parser and formatter
- Loading branch information
1 parent
eb82ddf
commit c7e55b8
Showing
8 changed files
with
434 additions
and
5 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
<?php | ||
|
||
namespace Money\Formatter; | ||
|
||
use Money\Currencies; | ||
use Money\Money; | ||
use Money\MoneyFormatter; | ||
|
||
/** | ||
* Formats a Money object using intl extension. | ||
* | ||
* @author Frederik Bosch <[email protected]> | ||
*/ | ||
final class IntlLocalizedDecimalFormatter implements MoneyFormatter | ||
{ | ||
/** | ||
* @var \NumberFormatter | ||
*/ | ||
private $formatter; | ||
|
||
/** | ||
* @var Currencies | ||
*/ | ||
private $currencies; | ||
|
||
/** | ||
* @param \NumberFormatter $formatter | ||
* @param Currencies $currencies | ||
*/ | ||
public function __construct(\NumberFormatter $formatter, Currencies $currencies) | ||
{ | ||
$this->formatter = $formatter; | ||
$this->currencies = $currencies; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function format(Money $money) | ||
{ | ||
$valueBase = $money->getAmount(); | ||
$negative = false; | ||
|
||
if ($valueBase[0] === '-') { | ||
$negative = true; | ||
$valueBase = substr($valueBase, 1); | ||
} | ||
|
||
$subunit = $this->currencies->subunitFor($money->getCurrency()); | ||
$valueLength = strlen($valueBase); | ||
|
||
if ($valueLength > $subunit) { | ||
$formatted = substr($valueBase, 0, $valueLength - $subunit); | ||
$decimalDigits = substr($valueBase, $valueLength - $subunit); | ||
|
||
if (strlen($decimalDigits) > 0) { | ||
$formatted .= '.'.$decimalDigits; | ||
} | ||
} else { | ||
$formatted = '0.'.str_pad('', $subunit - $valueLength, '0').$valueBase; | ||
} | ||
|
||
if ($negative === true) { | ||
$formatted = '-'.$formatted; | ||
} | ||
|
||
return $this->formatter->format($formatted); | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
<?php | ||
|
||
namespace Money\Parser; | ||
|
||
use Money\Currencies; | ||
use Money\Currency; | ||
use Money\Exception\ParserException; | ||
use Money\Money; | ||
use Money\MoneyParser; | ||
use Money\Number; | ||
|
||
/** | ||
* Parses a string into a Money object using intl extension. | ||
* | ||
* @author Frederik Bosch <[email protected]> | ||
*/ | ||
final class IntlLocalizedDecimalParser implements MoneyParser | ||
{ | ||
/** | ||
* @var \NumberFormatter | ||
*/ | ||
private $formatter; | ||
|
||
/** | ||
* @var Currencies | ||
*/ | ||
private $currencies; | ||
|
||
/** | ||
* @param \NumberFormatter $formatter | ||
* @param Currencies $currencies | ||
*/ | ||
public function __construct(\NumberFormatter $formatter, Currencies $currencies) | ||
{ | ||
$this->formatter = $formatter; | ||
$this->currencies = $currencies; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function parse($money, $forceCurrency = null) | ||
{ | ||
if (!is_string($money)) { | ||
throw new ParserException('Formatted raw money should be string, e.g. $1.00'); | ||
} | ||
|
||
if (null === $forceCurrency) { | ||
throw new ParserException( | ||
'IntlLocalizedDecimalParser cannot parse currency symbols. Use forceCurrency argument' | ||
); | ||
} | ||
|
||
$decimal = $this->formatter->parse($money); | ||
|
||
if (false === $decimal) { | ||
throw new ParserException( | ||
'Cannot parse '.$money.' to Money. '.$this->formatter->getErrorMessage() | ||
); | ||
} | ||
|
||
/* | ||
* This conversion is only required whilst currency can be either a string or a | ||
* Currency object. | ||
*/ | ||
if (!$forceCurrency instanceof Currency) { | ||
@trigger_error('Passing a currency as string is deprecated since 3.1 and will be removed in 4.0. Please pass a '.Currency::class.' instance instead.', E_USER_DEPRECATED); | ||
$forceCurrency = new Currency($forceCurrency); | ||
} | ||
|
||
$decimal = (string) $decimal; | ||
$subunit = $this->currencies->subunitFor($forceCurrency); | ||
$decimalPosition = strpos($decimal, '.'); | ||
|
||
if (false !== $decimalPosition) { | ||
$decimalLength = strlen($decimal); | ||
$fractionDigits = $decimalLength - $decimalPosition - 1; | ||
$decimal = str_replace('.', '', $decimal); | ||
$decimal = Number::roundMoneyValue($decimal, $subunit, $fractionDigits); | ||
|
||
if ($fractionDigits > $subunit) { | ||
$decimal = substr($decimal, 0, $decimalPosition + $subunit); | ||
} elseif ($fractionDigits < $subunit) { | ||
$decimal .= str_pad('', $subunit - $fractionDigits, '0'); | ||
} | ||
} else { | ||
$decimal .= str_pad('', $subunit, '0'); | ||
} | ||
|
||
if ('-' === $decimal[0]) { | ||
$decimal = '-'.ltrim(substr($decimal, 1), '0'); | ||
} else { | ||
$decimal = ltrim($decimal, '0'); | ||
} | ||
|
||
if ('' === $decimal) { | ||
$decimal = '0'; | ||
} | ||
|
||
return new Money($decimal, $forceCurrency); | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
<?php | ||
|
||
namespace Tests\Money\Formatter; | ||
|
||
use Money\Currencies; | ||
use Money\Currency; | ||
use Money\Formatter\IntlLocalizedDecimalFormatter; | ||
use Money\Money; | ||
use Prophecy\Argument; | ||
|
||
final class IntlLocalizedDecimalFormatterTest extends \PHPUnit_Framework_TestCase | ||
{ | ||
/** | ||
* @dataProvider moneyExamples | ||
* @test | ||
*/ | ||
public function it_formats_money($amount, $currency, $subunit, $result, $mode, $fractionDigits) | ||
{ | ||
$money = new Money($amount, new Currency($currency)); | ||
|
||
$numberFormatter = new \NumberFormatter('en_US', $mode); | ||
|
||
$numberFormatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, $fractionDigits); | ||
|
||
$currencies = $this->prophesize(Currencies::class); | ||
|
||
$currencies->subunitFor(Argument::allOf( | ||
Argument::type(Currency::class), | ||
Argument::which('getCode', $currency) | ||
))->willReturn($subunit); | ||
|
||
$moneyFormatter = new IntlLocalizedDecimalFormatter($numberFormatter, $currencies->reveal()); | ||
$this->assertSame($result, $moneyFormatter->format($money)); | ||
} | ||
|
||
public static function moneyExamples() | ||
{ | ||
return [ | ||
[5005, 'USD', 2, '50', \NumberFormatter::DECIMAL, 0], | ||
[100, 'USD', 2, '1.00', \NumberFormatter::DECIMAL, 2], | ||
[41, 'USD', 2, '0.41', \NumberFormatter::DECIMAL, 2], | ||
[5, 'USD', 2, '0.05', \NumberFormatter::DECIMAL, 2], | ||
[5, 'USD', 2, '0.050', \NumberFormatter::DECIMAL, 3], | ||
[35, 'USD', 2, '0.350', \NumberFormatter::DECIMAL, 3], | ||
[135, 'USD', 2, '1.350', \NumberFormatter::DECIMAL, 3], | ||
[6135, 'USD', 2, '61.350', \NumberFormatter::DECIMAL, 3], | ||
[-6135, 'USD', 2, '-61.350', \NumberFormatter::DECIMAL, 3], | ||
[-6152, 'USD', 2, '-61.5', \NumberFormatter::DECIMAL, 1], | ||
[5, 'EUR', 2, '0.05', \NumberFormatter::DECIMAL, 2], | ||
[50, 'EUR', 2, '0.50', \NumberFormatter::DECIMAL, 2], | ||
[500, 'EUR', 2, '5.00', \NumberFormatter::DECIMAL, 2], | ||
[5, 'EUR', 2, '0.05', \NumberFormatter::DECIMAL, 2], | ||
[50, 'EUR', 2, '0.50', \NumberFormatter::DECIMAL, 2], | ||
[500, 'EUR', 2, '5.00', \NumberFormatter::DECIMAL, 2], | ||
[5, 'EUR', 2, '0', \NumberFormatter::DECIMAL, 0], | ||
[50, 'EUR', 2, '0', \NumberFormatter::DECIMAL, 0], | ||
[500, 'EUR', 2, '5', \NumberFormatter::DECIMAL, 0], | ||
[5055, 'USD', 2, '51', \NumberFormatter::DECIMAL, 0], | ||
]; | ||
} | ||
} |
This file contains 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
Oops, something went wrong.