Commit 2a15ae12 by Carsten Brandt

refactored number formatting

parent a4e86c02
......@@ -13,7 +13,6 @@ use IntlDateFormatter;
use NumberFormatter;
use yii\helpers\HtmlPurifier;
use yii\helpers\Html;
use yii\i18n\FormatDefs;
/**
* Formatter provides a set of commonly used data formatting methods.
......@@ -25,25 +24,10 @@ use yii\i18n\FormatDefs;
* Formatter is configured as an application component in [[\yii\base\Application]] by default.
* You can access that instance via `Yii::$app->formatter`.
*
* TODO docs
* This refactored formatter version combines localized i18n with base functions. If "intl" extension is installed
* ICU standard is used internally. If "intl" want to be used or can't be loaded most functionality is simulated with php.
* A separate definiton class in 'yii\i18n\FormatDefs.php' has an array with localized format defintions.
* As a constraint month and day names are in english only.
*
* The communication with formatter class is per standard with php format patterns. They are converted internally to
* icu format patterns. Further it supports for date, time and datetime the named patterns "short", "medium", "long" and
* "full" plus "db" (database), also if "intl" isn't loaded. The format function has an option parameter to use "icu"
* format patterns.
*
* All number fomatters of yii\i18n\ are merged with yii\base in this formatter. Formatted numbers aren't readable for
* a machine as numeric. Therefore an "unformat" function for all "format" types has been built.
*
* For currency amounts the currency code is taken from "intl" (if loaded). Otherwise it can be defined in a localizing
* array (formatterIntl). The rounding rule can be defined in config with "$roundingIncrement". For Swiss Francs formatter rounds
* automatically to 5 cents.
* The Formatter class is designed to format values according to a [[locale]]. For this feature to work
* the [PHP intl extension](http://php.net/manual/en/book.intl.php) has to be installed.
* Most of the methods however work also if the PHP intl extension is not installed by providing
* a fallback implementation. Without intl month and day names are in english only.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Enrica Ruedin <e.ruedin@guggach.com>
......@@ -62,11 +46,13 @@ class Formatter extends Component
* @var array the text to be displayed when formatting a boolean value. The first element corresponds
* to the text displayed for `false`, the second element for `true`.
* Defaults to `['No', 'Yes']`, where `Yes` and `No`
* will be translated according to [[locale]]..
* will be translated according to [[locale]].
*/
public $booleanFormat;
/**
* @var string the locale ID that is used to localize the date and number formatting.
* For number and date formatting this is only effective when the
* [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed.
* If not set, [[\yii\base\Application::language]] will be used.
*/
public $locale;
......@@ -79,27 +65,25 @@ class Formatter extends Component
*/
public $timeZone;
/**
* @var string the default format string to be used to format a date.
* @var string the default format string to be used to format a [[asDate()|date]].
* This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
*
* It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
*
* Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
* PHP [date()](http://php.net/manual/de/function.date.php)-function.
*/
public $dateFormat = 'medium';
/**
* @var string the default format string to be used to format a time.
* @var string the default format string to be used to format a [[asTime()|time]].
* This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
*
* It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
*
* Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
* PHP [date()](http://php.net/manual/de/function.date.php)-function.
*/
public $timeFormat = 'medium';
/**
* @var string the default format string to be used to format a date and time.
* @var string the default format string to be used to format a [[asDateTime()|date and time]].
* This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
*
* It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
......@@ -108,48 +92,48 @@ class Formatter extends Component
* PHP [date()](http://php.net/manual/de/function.date.php)-function.
*/
public $datetimeFormat = 'medium';
// TODO refactor number formatters
/**
* @var array the options to be set for the NumberFormatter objects (eg. grouping used). Please refer to
* [PHP manual](http://php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformatattribute)
* for the possible options. This property is used by [[createNumberFormatter]] when
* creating a new number formatter to format decimals, currencies, etc.
*/
public $numberFormatOptions = [];
/**
* @var array the text options to be set for the NumberFormatter objects (eg. Negative sign). Please refer to
* [PHP manual](http://php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformatattribute)
* for the possible options. This property is used by [[createNumberFormatter]] when
* creating a new number formatter to format decimals, currencies, etc.
*
* Default value: GOUPING_USED = 1 / MAX_FRACTION_DIGITS = 3 / GROUPING_SIZE = 3 / ROUNDING_MODE = 4
*/
public $numberTextFormartOptions = [];
/**
* @var string the character displayed as the decimal point when formatting a number.
* If not set, the decimal separator corresponding to [[locale]] will be used.
* If PHP `intl` extension is not available, the default value is '.'.
* If [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value is '.'.
*/
public $decimalSeparator;
/**
* @var string the character displayed as the thousands separator character when formatting a number.
* @var string the character displayed as the thousands separator (also called grouping separator) character when formatting a number.
* If not set, the thousand separator corresponding to [[locale]] will be used.
* If PHP `intl` extension is not available, the default value is ','.
* If [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value is ','.
*/
public $thousandSeparator;
// TODO refactor number formatters
// /**
// * @var array the options to be set for the NumberFormatter objects (eg. grouping used). Please refer to
// * [PHP manual](http://php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformatattribute)
// * for the possible options. This property is used by [[createNumberFormatter]] when
// * creating a new number formatter to format decimals, currencies, etc.
// */
// public $numberFormatOptions = [];
// /**
// * @var array the text options to be set for the NumberFormatter objects (eg. Negative sign). Please refer to
// * [PHP manual](http://php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformatattribute)
// * for the possible options. This property is used by [[createNumberFormatter]] when
// * creating a new number formatter to format decimals, currencies, etc.
// *
// * Default value: GOUPING_USED = 1 / MAX_FRACTION_DIGITS = 3 / GROUPING_SIZE = 3 / ROUNDING_MODE = 4
// */
// public $numberTextFormartOptions = [];
/**
* @var string the international currency code displayed when formatting a number.
* If not set, the currency code corresponding to [[locale]] will be used.
* If not set, the currency code corresponding to [[locale]] will be used. TODO default value?
*/
public $currencyCode;
/**
* @var float "intl" numberformat library knows a rounding increment
* This means that any value is rounded to this increment.
* Example: increment of 0.05 rounds values <= 2.024 to 2.00 / values >= 2.025 to 2.05
*/
public $roundingIncrement;
public $roundingIncrCurrency;
// /**
// * @var float "intl" numberformat library knows a rounding increment
// * This means that any value is rounded to this increment.
// * Example: increment of 0.05 rounds values <= 2.024 to 2.00 / values >= 2.025 to 2.05
// */
// public $roundingIncrement;
// public $roundingIncrCurrency;
/**
* @var array the format used to format size (bytes). Three elements may be specified: "base", "decimals" and "decimalSeparator".
* They correspond to the base at which a kilobyte is calculated (1000 or 1024 bytes per kilobyte, defaults to 1024),
......@@ -162,7 +146,7 @@ class Formatter extends Component
];
/**
* @var boolean whether the php `intl` extension is loaded.
* @var boolean whether the [PHP intl extension](http://php.net/manual/en/book.intl.php) is loaded.
*/
private $_intlLoaded = false;
......@@ -185,8 +169,11 @@ class Formatter extends Component
$this->nullDisplay = '<span class="not-set">' . Yii::t('yii', '(not set)') . '</span>';
}
$this->_intlLoaded = extension_loaded('intl');
if (!$this->_intlLoaded) {
$this->decimalSeparator = '.';
$this->thousandSeparator = ',';
}
// TODO refactor number formatters
// if (extension_loaded('intl')) {
// $this->_intlLoaded = true;
// $this->numberFormatOptions = [NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_HALFUP];
......@@ -537,8 +524,9 @@ class Formatter extends Component
// replace short, medium, long and full with real patterns in case intl is not loaded.
if (isset($this->_phpNameToPattern[$format][$type])) {
$format = $this->_phpNameToPattern[$format][$type];
} else {
$format = $this->getPhpDatePattern($format);
}
$format = $this->getPhpDatePattern($format);
$date = new DateTime(null, new \DateTimeZone($this->timeZone));
$date->setTimestamp($value);
return $date->format($format);
......@@ -572,6 +560,7 @@ class Formatter extends Component
// if ( !($date === false)) break;
// }
// TODO throw InvalidParamException on invalid value
} catch (\Exception $e) {
return null;
}
......@@ -788,6 +777,7 @@ class Formatter extends Component
*
* @param integer|string|DateTime|\DateInterval $referenceTime if specified the value is used instead of now
* @return string the formatted result
* @throws InvalidParamException if the input value can not be evaluated as a date value.
*/
public function asRelativeTime($value, $referenceTime = null)
{
......@@ -869,67 +859,30 @@ class Formatter extends Component
// number formats
// TODO refactor number formatters
/**
* Formats the value as an integer and rounds decimals with math rule
* Formats the value as an integer number by removing any digits without rounding.
* @param mixed $value the value to be formatted
* @return string the formatting result.
* @throws InvalidParamException if the input value is not numeric.
*/
public function asInteger($value, $grouping = true) {
$format = null;
public function asInteger($value) // TODO customizing
{
if ($value === null) {
return $this->nullDisplay;
}
if (is_string($value)) {
$value = (float) $value;
}
$value = round($value, 0);
if ($this->_intlLoaded){
$f = $this->createNumberFormatter(NumberFormatter::DECIMAL, $format);
if ($grouping === false){
$f->setAttribute(NumberFormatter::GROUPING_USED, false);
}
$value = $this->normalizeNumericValue($value);
if ($this->_intlLoaded) {
$f = $this->createNumberFormatter(NumberFormatter::DECIMAL);
return $f->format($value, NumberFormatter::TYPE_INT64);
} else {
$grouping = $grouping === true ? $this->thousandSeparator : '';
return number_format($value, 0, $this->decimalSeparator, $grouping);
return number_format((int) $value, 0, $this->decimalSeparator, $this->thousandSeparator);
}
}
/**
* Formats the value as a number with decimal and thousand separators.
* This method is a synomym for asDouble.
* @param mixed $value the value to be formatted
* @param integer $decimals the number of digits after the decimal point
* @return string the formatted result
* @see decimalSeparator
* @see thousandSeparator
*/
public function asNumber($value, $decimals = 0, $roundIncr = null, $grouping = true)
{
return $this->asDouble($value, $decimals, $roundIncr, $grouping);
}
/**
* Formats the value as a decimal number. This method is a synonym for asDouble
* @see method asDouble
* @param mixed $value the value to be formatted
* @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details)
* for details on how to specify a format.
* @return string the formatted result.
*/
public function asDecimal($value, $decimals = null, $roundIncr = null, $grouping = true)
{
return $this->asDouble($value, $decimals, $roundIncr, $grouping);
}
/**
* Formats the value as a double number.
* Formats the value as a decimal number.
*
* Property [[decimalSeparator]] will be used to represent the decimal point. The
* value is rounded automatically to the defined decimal digits.
*
......@@ -938,84 +891,70 @@ class Formatter extends Component
* (eg. 2.5 [6]). Until 5 fractional digits in this function is defined to 5 up with zeros.
*
* @param mixed $value the value to be formatted
* @param integer or string $decimals the number of digits after the decimal point if the value is an integer
* otherwise it's is a format pattern string (this works only with intl [icu]).
* @param float $roundIncr Amount to which smaller fractation are rounded. Ex. 0.05 -> <=2.024 to 2.00 / >=2.025 to 2.05
* works with "intl" library only.
* @param boolean $grouping Per standard numbers are grouped in thousands. False = no grouping
* @param integer|string $decimals the number of digits after the decimal point if the value is an integer
* otherwise it's is a format pattern string (this works only with [PHP intl extension](http://php.net/manual/en/book.intl.php) [icu]).
// * @param float $roundIncr Amount to which smaller fractation are rounded. Ex. 0.05 -> <=2.024 to 2.00 / >=2.025 to 2.05
// * works with [PHP intl extension](http://php.net/manual/en/book.intl.php) library only.
// * @param boolean $grouping Per standard numbers are grouped in thousands. False = no grouping
* @return string the formatting result.
* @throws InvalidParamException if the input value is not numeric.
* @see decimalSeparator
* @see thousandSeparator
*/
public function asDouble($value, $decimals = 2, $roundIncr = null, $grouping = true)
public function asDecimal($value, $decimals = 2)
{
$format = null;
if(is_numeric($decimals)){
$decimals = intval($decimals); // number of digits after decimal
} else {
$format = $decimals; // format pattern for ICU only
}
if ($value === null) {
return $this->nullDisplay;
}
if (is_string($value)){
if (is_numeric($value)){
$value = (float)$value;
} else {
throw new InvalidParamException('"' . $value . '" is not a numeric value.');
}
}
$value = $this->normalizeNumericValue($value);
// if (true === false){
if ($this->_intlLoaded){
$f = $this->createNumberFormatter(NumberFormatter::DECIMAL, $format);
if ($decimals !== null){
$f->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimals);
if ($decimals <= 5){
$f->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimals);
}
}
if ($roundIncr == null and $this->roundingIncrement != null){
$roundIncr = $this->roundingIncrement;
}
if ($roundIncr != null){
$f->setAttribute(NumberFormatter::ROUNDING_INCREMENT, $roundIncr);
}
if ($grouping === false){
$f->setAttribute(NumberFormatter::GROUPING_USED, false);
}
if ($this->_intlLoaded) {
$f = $this->createNumberFormatter(NumberFormatter::DECIMAL);
// if ($decimals !== null){
// $f->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimals);
// if ($decimals <= 5){
// $f->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimals);
// }
// }
// if ($roundIncr == null and $this->roundingIncrement != null){
// $roundIncr = $this->roundingIncrement;
// }
// if ($roundIncr != null){
// $f->setAttribute(NumberFormatter::ROUNDING_INCREMENT, $roundIncr);
// }
// if ($grouping === false){
// $f->setAttribute(NumberFormatter::GROUPING_USED, false);
// }
return $f->format($value);
} else {
if ($roundIncr !== null){
$part = explode('.', (string)$roundIncr);
if ((string)$roundIncr != '0.05'){ // exception for Swiss rounding.
$roundIncr = $decimals;
if (intval($part[0]) > 0){
if (substr($part[0], 0, 1) === '1'){
$roundIncr = (strlen($part[0]) -1) * -1 ;
} else {
throw new InvalidParamException('$roundIncr must have "1" only eg. 0.01 or 10 but not 0.02 or 20');
}
} elseif (isset($part[1]) and intval($part[1])>0) {
if (substr($part[1], -1) === '1'){
$roundIncr = strlen($part[1]);
} else {
throw new InvalidParamException('$roundIncr must have "1" only eg. 0.01 or 10 but not 0.02 or 20');
}
}
$value = round($value, $roundIncr);
} else {
$value = round($value/5,2)*5;
}
}
if ($decimals === null){
$decimals = 0;
}
$grouping = $grouping === true ? $this->thousandSeparator : '';
return number_format($value, $decimals, $this->decimalSeparator, $grouping);
// if ($roundIncr !== null){
// $part = explode('.', (string)$roundIncr);
// if ((string)$roundIncr != '0.05'){ // exception for Swiss rounding.
// $roundIncr = $decimals;
// if (intval($part[0]) > 0){
// if (substr($part[0], 0, 1) === '1'){
// $roundIncr = (strlen($part[0]) -1) * -1 ;
// } else {
// throw new InvalidParamException('$roundIncr must have "1" only eg. 0.01 or 10 but not 0.02 or 20');
// }
// } elseif (isset($part[1]) and intval($part[1])>0) {
// if (substr($part[1], -1) === '1'){
// $roundIncr = strlen($part[1]);
// } else {
// throw new InvalidParamException('$roundIncr must have "1" only eg. 0.01 or 10 but not 0.02 or 20');
// }
// }
// $value = round($value, $roundIncr);
// } else {
// $value = round($value/5,2)*5;
// }
// }
// if ($decimals === null){
// $decimals = 0;
// }
// $grouping = $grouping === true ? $this->thousandSeparator : '';
return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator);
}
}
......@@ -1023,88 +962,68 @@ class Formatter extends Component
/**
* Formats the value as a percent number with "%" sign.
* @param mixed $value the value to be formatted. It must be a factor eg. 0.75 -> 75%
* @param string $format Number of decimals (default = 2) or format pattern ICU
* Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details)
* for details on how to specify a format.
// * @param string $format Number of decimals (default = 2) or format pattern ICU
// * Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details)
// * for details on how to specify a format.
* @param int $decimals
// * @param bool $grouping
* @return string the formatted result.
* @throws InvalidParamException if the input value is not numeric.
*/
public function asPercent($value, $format = null, $decimals = 0, $grouping = true)
public function asPercent($value, $decimals = 0)
{
if(is_numeric($decimals)){
$decimals = intval($decimals); // number of digits after decimal
} else {
$format = $decimals; // format pattern for ICU only
}
if ($value === null) {
return $this->nullDisplay;
}
if (is_string($value)) {
$value = (float) $value;
}
$value = $this->normalizeNumericValue($value);
// if (true === false){
if ($this->_intlLoaded){
$f = $this->createNumberFormatter(NumberFormatter::PERCENT, $format);
if ($decimals !== null){
$f->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimals);
if ($decimals <= 5){
$f->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimals);
}
}
if ($grouping === false){
$f->setAttribute(NumberFormatter::GROUPING_USED, false);
}
if ($this->_intlLoaded) {
$f = $this->createNumberFormatter(NumberFormatter::PERCENT);
// if ($decimals !== null){
// $f->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimals);
// if ($decimals <= 5){
// $f->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimals);
// }
// }
// if ($grouping === false){
// $f->setAttribute(NumberFormatter::GROUPING_USED, false);
// }
return $f->format($value);
} else {
if ($decimals === null){
$decimals = 0;
}
$value = $value * 100;
$grouping = $grouping === true ? $this->thousandSeparator : '';
return number_format($value, $decimals, $this->decimalSeparator, $grouping) . '%';
return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator) . '%';
}
}
/**
* Formats the value as a scientific number.
* @param mixed $value the value to be formatted
* @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details)
* for details on how to specify a format.
// * @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details)
// * for details on how to specify a format.
* @param int $decimals
* @return string the formatted result.
* @throws InvalidParamException if the input value is not numeric.
*/
public function asScientific($value, $decimals = null)
public function asScientific($value, $decimals = 0)
{
$format = null;
if(is_numeric($decimals)){
$decimals = intval($decimals); // number of digits after decimal
} else {
$format = $decimals; // format pattern for ICU only
}
if ($value === null) {
return $this->nullDisplay;
}
if (is_string($value)) {
$value = (float) $value;
}
$value = $this->normalizeNumericValue($value);
// if (true === false){
if ($this->_intlLoaded){
$f = $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $format);
if ($decimals !== null){
$f->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimals);
}
$f = $this->createNumberFormatter(NumberFormatter::SCIENTIFIC);
// if ($decimals !== null){
// $f->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimals);
// }
return $f->format($value);
} else {
if ($decimals !== null){
if ($decimals !== null) {
return sprintf("%.{$decimals}E", $value);
} else {
return sprintf("%.E", $value);
}
}
}
/**
......@@ -1112,50 +1031,84 @@ class Formatter extends Component
* @param mixed $value the value to be formatted
* @param string $currency the 3-letter ISO 4217 currency code indicating the currency to use.
* @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details)
* for details on how to specify a format. TODO ignored when not intl
* for details on how to specify a format. TODO ignored when not [PHP intl extension](http://php.net/manual/en/book.intl.php)
* @param float $roundIncrement : Amount to which smaller fractation are rounded. Ex. 0.05 -> <=2.024 to 2.00 / >=2.025 to 2.05
* works with "intl" library only.
* @param null $grouping
* @throws InvalidParamException
* @return string the formatted result.
* @throws InvalidParamException if the input value is not numeric.
*/
public function asCurrency($value, $currency = null, $format = null, $roundIncrement = null, $grouping = null)
public function asCurrency($value, $currency = null) //, $format = null, $roundIncrement = null, $grouping = null)
{
if ($value === null) {
return $this->nullDisplay;
}
if (is_string($value)) {
if (is_numeric($value)) {
$value = (float)$value;
} else {
throw new InvalidParamException('"' . $value . '" is not a numeric value.');
}
}
$value = $this->normalizeNumericValue($value); // TODO maybe not a good idea to cast money values?
if ($currency === null) {
$currency = $this->currencyCode;
}
if ($roundIncrement === null and $this->roundingIncrCurrency != null){
$roundIncrement = $this->roundingIncrCurrency;
}
// if ($roundIncrement === null and $this->roundingIncrCurrency != null){
// $roundIncrement = $this->roundingIncrCurrency;
// }
// if (true == false){
if ($this->_intlLoaded) {
$formatter = $this->createNumberFormatter(NumberFormatter::CURRENCY, $format);
if ($grouping !== null){
$formatter->setAttribute(NumberFormatter::GROUPING_USED, false);
}
if ($roundIncrement !== null){
$formatter->setAttribute(NumberFormatter::ROUNDING_INCREMENT, $roundIncrement);
}
$formatter = $this->createNumberFormatter(NumberFormatter::CURRENCY);
// if ($grouping !== null){
// $formatter->setAttribute(NumberFormatter::GROUPING_USED, false);
// }
// if ($roundIncrement !== null){
// $formatter->setAttribute(NumberFormatter::ROUNDING_INCREMENT, $roundIncrement);
// }
return $formatter->formatCurrency($value, $currency);
} else {
return $currency . ' ' . $this->asDouble($value, 2, $roundIncrement, $grouping);
return $currency . ' ' . $this->asDecimal($value, 2);
}
}
/**
* Formats the value as a number spellout.
* // TODO requires intl
* @param mixed $value the value to be formatted
* @return string the formatted result.
* @throws InvalidParamException if the input value is not numeric.
* @throws NotSupportedException
*/
public function asSpellout($value)
{
if ($value === null) {
return $this->nullDisplay;
}
$value = $this->normalizeNumericValue($value);
if ($this->_intlLoaded){
$f = $this->createNumberFormatter(NumberFormatter::SPELLOUT);
return $f->format($value);
} else {
throw new NotSupportedException('Format as Spellout is only supported when PHP intl extension is installed.');
}
}
/**
* Formats the value as a ordinal value of a number.
* // TODO requires intl
* @param mixed $value the value to be formatted
* @return string the formatted result.
* @throws InvalidParamException if the input value is not numeric.
* @throws NotSupportedException
*/
public function asOrdinal($value)
{
if ($value === null) {
return $this->nullDisplay;
}
$value = $this->normalizeNumericValue($value);
if ($this->_intlLoaded){
$f = $this->createNumberFormatter(NumberFormatter::ORDINAL);
return $f->format($value);
} else {
throw new NotSupportedException('Format as Ordinal is only supported when PHP intl extension is installed.');
}
}
/**
* Formats the value in bytes as a size in human readable form.
......@@ -1163,22 +1116,21 @@ class Formatter extends Component
* @param boolean $verbose if full names should be used (e.g. bytes, kilobytes, ...).
* Defaults to false meaning that short names will be used (e.g. B, KB, ...).
* @return string the formatted result
* @throws InvalidParamException if the input value is not numeric.
* @see sizeFormat
*/
public function asSize($value, $verbose = false)
{
$position = 0;
do {
if ($value < $this->sizeFormat['base']) {
break;
}
$value = $value / $this->sizeFormat['base'];
$position++;
} while ($position < 6);
$value = round($value, $this->sizeFormat['decimals']);
$value = round($value, $this->sizeFormat['decimals']); // todo
$formattedValue = isset($this->sizeFormat['decimalSeparator']) ? str_replace('.', $this->sizeFormat['decimalSeparator'], $value) : $value;
$params = ['n' => $formattedValue];
......@@ -1198,6 +1150,17 @@ class Formatter extends Component
}
}
protected function normalizeNumericValue($value)
{
if (is_string($value) && is_numeric($value)) {
$value = (float) $value;
}
if (!is_numeric($value)) {
throw new InvalidParamException("'$value' is not a numeric value.");
}
return $value;
}
/**
* Creates a number formatter based on the given type and format.
......@@ -1208,26 +1171,34 @@ class Formatter extends Component
* [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details)
* @return NumberFormatter the created formatter instance
*/
protected function createNumberFormatter($type, $format)
protected function createNumberFormatter($style)
{
$formatter = new NumberFormatter($this->locale, $type);
if ($format !== null) {
$formatter->setPattern($format);
} else {
$formatter = new NumberFormatter($this->locale, $style);
if ($this->decimalSeparator !== null) {
$formatter->setSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL, $this->decimalSeparator);
}
if ($this->thousandSeparator !== null) {
$formatter->setSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL, $this->thousandSeparator);
}
if (!empty($this->numberFormatOptions)) {
foreach ($this->numberFormatOptions as $name => $attribute) {
$formatter->setAttribute($name, $attribute);
}
}
if (!empty($this->numberTextFormatOptions)) {
foreach ($this->numberTextFormatOptions as $name => $attribute) {
$formatter->setTextAttribute($name, $attribute);
}
}
// if ($format !== null) {
// $formatter->setPattern($format);
// } else {
// $formatter->setSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL, $this->decimalSeparator);
// $formatter->setSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL, $this->thousandSeparator);
// }
// if (!empty($this->numberFormatOptions)) {
// foreach ($this->numberFormatOptions as $name => $attribute) {
// $formatter->setAttribute($name, $attribute);
// }
// }
// if (!empty($this->numberTextFormatOptions)) {
// foreach ($this->numberTextFormatOptions as $name => $attribute) {
// $formatter->setTextAttribute($name, $attribute);
// }
// }
return $formatter;
}
......
......@@ -71,7 +71,7 @@ class FormatterTest extends TestCase
$value = time();
$this->assertSame(date('M j, Y', $value), $this->formatter->format($value, 'date'));
$this->assertSame(date('M j, Y', $value), $this->formatter->format($value, 'DATE'));
$this->assertSame(date('Y/m/d', $value), $this->formatter->format($value, ['date', 'Y/m/d']));
$this->assertSame(date('Y/m/d', $value), $this->formatter->format($value, ['date', 'php:Y/m/d']));
$this->setExpectedException('\yii\base\InvalidParamException');
$this->assertSame(date('Y-m-d', $value), $this->formatter->format($value, 'data'));
}
......@@ -195,7 +195,7 @@ class FormatterTest extends TestCase
// public function testSetLocale(){
// $value = '12300';
// $this->formatter->setLocale('de-DE');
// $this->assertSame('12.300,00', $this->formatter->asDouble($value, 2));
// $this->assertSame('12.300,00', $this->formatter->asDecimal($value, 2));
// $value = time();
// $this->assertSame(date('d.m.Y', $value), $this->formatter->asDate($value));
// $this->formatter->setLocale('en-US');
......@@ -226,8 +226,10 @@ class FormatterTest extends TestCase
$value = time();
$this->assertSame(date('M j, Y', $value), $this->formatter->asDate($value));
$this->assertSame(date('Y/m/d', $value), $this->formatter->asDate($value, 'php:Y/m/d'));
$this->assertSame(date('n/j/y', $value), $this->formatter->asDate($value, 'short'));
$this->assertSame(date('d.m.Y', $value), $this->formatter->asDate($value, 'short'));
$this->assertSame(date('F j, Y', $value), $this->formatter->asDate($value, 'long'));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asDate(null));
}
......@@ -236,6 +238,8 @@ class FormatterTest extends TestCase
$value = time();
$this->assertSame(date('g:i:s A', $value), $this->formatter->asTime($value));
$this->assertSame(date('n:i:s A', $value), $this->formatter->asTime($value, 'php:n:i:s A'));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asTime(null));
}
......@@ -244,6 +248,8 @@ class FormatterTest extends TestCase
$value = time();
$this->assertSame(date('M j, Y g:i:s A', $value), $this->formatter->asDatetime($value));
$this->assertSame(date('Y/m/d h:i:s A', $value), $this->formatter->asDatetime($value, 'php:Y/m/d h:i:s A'));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asDatetime(null));
}
......@@ -255,6 +261,7 @@ class FormatterTest extends TestCase
$this->assertSame("$value", $this->formatter->asTimestamp(date('Y-m-d H:i:s', $value)));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asTimestamp(null));
}
......@@ -382,105 +389,174 @@ class FormatterTest extends TestCase
$dateThen = new DateTime('2014-03-31');
$this->assertSame('in a month', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-03', [$interval_1_month]), $dateNow));
$this->assertSame('in 28 days', $this->formatter->asRelativeTime($dateThen, $dateNow));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asRelativeTime(null));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asRelativeTime(null, time()));
}
// number format
// public function testAsInteger()
// {
// $value = 123;
// $this->assertSame("$value", $this->formatter->asInteger($value));
// $value = 123.23;
// $this->assertSame("123", $this->formatter->asInteger($value));
// $value = 'a';
// $this->assertSame("0", $this->formatter->asInteger($value));
// $value = -123.23;
// $this->assertSame("-123", $this->formatter->asInteger($value));
// $value = "-123abc";
// $this->assertSame("-123", $this->formatter->asInteger($value));
// $this->assertSame($this->formatter->nullDisplay, $this->formatter->asInteger(null));
// }
//
// public function testAsDouble()
// {
// $value = 123.12;
// $this->assertSame("123.12", $this->formatter->asDouble($value));
// $this->assertSame("123.1", $this->formatter->asDouble($value, 1));
// $this->assertSame("123", $this->formatter->asDouble($value, 0));
// $value = 123;
// $this->assertSame("123.00", $this->formatter->asDouble($value));
// $this->formatter->decimalSeparator = ',';
// $this->formatter->thousandSeparator = '.';
// $value = 123.12;
// $this->assertSame("123,12", $this->formatter->asDouble($value));
// $this->assertSame("123,1", $this->formatter->asDouble($value, 1));
// $this->assertSame("123", $this->formatter->asDouble($value, 0));
// $value = 123123.123;
// $this->assertSame("123.123,12", $this->formatter->asDouble($value));
// $this->assertSame("123123,12", $this->formatter->asDouble($value, 2, null, false));
// $this->assertSame($this->formatter->nullDisplay, $this->formatter->asDouble(null));
// }
//
// public function testAsNumber()
// {
// $value = 123123.123;
// $this->assertSame("123,123", $this->formatter->asNumber($value));
// $this->assertSame("123,123.12", $this->formatter->asNumber($value, 2));
// $this->formatter->decimalSeparator = ',';
// $this->formatter->thousandSeparator = ' ';
// $this->assertSame("123 123", $this->formatter->asNumber($value));
// $this->assertSame("123 123,12", $this->formatter->asNumber($value, 2));
// $this->formatter->thousandSeparator = '';
// $this->assertSame("123123", $this->formatter->asNumber($value));
// $this->assertSame("123123,12", $this->formatter->asNumber($value, 2));
// $this->assertSame($this->formatter->nullDisplay, $this->formatter->asNumber(null));
// }
//
// public function testAsDecimal()
// {
// $value = '123';
// $this->assertSame($value, $this->formatter->asDecimal($value));
// $value = '123456';
// $this->assertSame("123,456", $this->formatter->asDecimal($value));
// $value = '-123456.123';
// $this->assertSame("-123,456.123", $this->formatter->asDecimal($value));
// $this->assertSame($this->formatter->nullDisplay, $this->formatter->asDecimal(null));
// }
//
// public function testAsCurrency()
// {
// $value = '123';
// $this->assertSame('$123.00', $this->formatter->asCurrency($value));
// $value = '123.456';
// $this->assertSame("$123.46", $this->formatter->asCurrency($value));
// // Starting from ICU 52.1, negative currency value will be formatted as -$123,456.12
// // see: http://source.icu-project.org/repos/icu/icu/tags/release-52-1/source/data/locales/en.txt
//// $value = '-123456.123';
//// $this->assertSame("($123,456.12)", $this->formatter->asCurrency($value));
// $this->assertSame($this->formatter->nullDisplay, $this->formatter->asCurrency(null));
// }
//
// public function testAsScientific()
// {
// $value = '123';
// $this->assertSame('1.23E2', $this->formatter->asScientific($value));
// $value = '123456';
// $this->assertSame("1.23456E5", $this->formatter->asScientific($value));
// $value = '-123456.123';
// $this->assertSame("-1.23456123E5", $this->formatter->asScientific($value));
// $this->assertSame($this->formatter->nullDisplay, $this->formatter->asScientific(null));
// }
//
// public function testAsPercent()
// {
// $value = '123';
// $this->assertSame('12,300%', $this->formatter->asPercent($value));
// $value = '0.1234';
// $this->assertSame("12%", $this->formatter->asPercent($value));
// $value = '-0.009343';
// $this->assertSame("-1%", $this->formatter->asPercent($value));
// $this->assertSame($this->formatter->nullDisplay, $this->formatter->asPercent(null));
// }
public function testIntlAsInteger()
{
$this->testAsInteger();
}
public function testAsInteger()
{
$this->assertSame("123", $this->formatter->asInteger(123));
$this->assertSame("123", $this->formatter->asInteger(123.23));
$this->assertSame("123", $this->formatter->asInteger(123.53));
$this->assertSame("0", $this->formatter->asInteger(0));
$this->assertSame("0", $this->formatter->asInteger('a'));
$this->assertSame("-123", $this->formatter->asInteger(-123.23));
$this->assertSame("-123", $this->formatter->asInteger(-123.53));
$this->assertSame("-123", $this->formatter->asInteger("-123abc"));
$this->assertSame("123,456", $this->formatter->asInteger(123456));
$this->assertSame("123,456", $this->formatter->asInteger(123456.789));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asInteger(null));
}
public function testIntlAsDecimal()
{
$this->testAsDecimal();
}
public function testAsDecimal()
{
$value = 123.12;
$this->assertSame("123.12", $this->formatter->asDecimal($value));
$this->assertSame("123.1", $this->formatter->asDecimal($value, 1));
$this->assertSame("123", $this->formatter->asDecimal($value, 0));
$value = 123;
$this->assertSame("123.00", $this->formatter->asDecimal($value));
$this->formatter->decimalSeparator = ',';
$this->formatter->thousandSeparator = '.';
$value = 123.12;
$this->assertSame("123,12", $this->formatter->asDecimal($value));
$this->assertSame("123,1", $this->formatter->asDecimal($value, 1));
$this->assertSame("123", $this->formatter->asDecimal($value, 0));
$value = 123123.123;
$this->assertSame("123.123,12", $this->formatter->asDecimal($value));
$this->assertSame("123123,12", $this->formatter->asDecimal($value, 2, null, false));
$value = 123123.123;
$this->assertSame("123,123", $this->formatter->asDecimal($value));
$this->assertSame("123,123.12", $this->formatter->asDecimal($value, 2));
$this->formatter->decimalSeparator = ',';
$this->formatter->thousandSeparator = ' ';
$this->assertSame("123 123", $this->formatter->asDecimal($value));
$this->assertSame("123 123,12", $this->formatter->asDecimal($value, 2));
$this->formatter->thousandSeparator = '';
$this->assertSame("123123", $this->formatter->asDecimal($value));
$this->assertSame("123123,12", $this->formatter->asDecimal($value, 2));
$value = '-123456.123';
$this->assertSame("-123,456.123", $this->formatter->asDecimal($value));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asDecimal(null));
}
public function testIntlAsPercent()
{
$this->testAsPercent();
}
public function testAsPercent()
{
$value = '123';
$this->assertSame('12,300%', $this->formatter->asPercent($value));
$value = '0.1234';
$this->assertSame("12%", $this->formatter->asPercent($value));
$value = '-0.009343';
$this->assertSame("-1%", $this->formatter->asPercent($value));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asPercent(null));
}
public function testIntlAsCurrency()
{
$this->testAsCurrency();
}
public function testAsCurrency()
{
$value = '123';
$this->assertSame('$123.00', $this->formatter->asCurrency($value));
$value = '123.456';
$this->assertSame("$123.46", $this->formatter->asCurrency($value));
// Starting from ICU 52.1, negative currency value will be formatted as -$123,456.12
// see: http://source.icu-project.org/repos/icu/icu/tags/release-52-1/source/data/locales/en.txt
// $value = '-123456.123';
// $this->assertSame("($123,456.12)", $this->formatter->asCurrency($value));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asCurrency(null));
}
public function testIntlAsScientific()
{
$this->testAsScientific();
}
public function testAsScientific()
{
$value = '123';
$this->assertSame('1.23E2', $this->formatter->asScientific($value));
$value = '123456';
$this->assertSame("1.23456E5", $this->formatter->asScientific($value));
$value = '-123456.123';
$this->assertSame("-1.23456123E5", $this->formatter->asScientific($value));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asScientific(null));
}
public function testIntlAsSpellout()
{
$this->assertSame('one hundred twenty-three', $this->formatter->asSpellout(123));
$this->formatter->locale = 'de_DE';
$this->assertSame('ein­hundert­drei­und­zwanzig', $this->formatter->asSpellout(123));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asSpellout(null));
}
public function testIntlAsOrdinal()
{
$this->assertSame('0th', $this->formatter->asOrdinal(0));
$this->assertSame('1st', $this->formatter->asOrdinal(1));
$this->assertSame('2nd', $this->formatter->asOrdinal(2));
$this->assertSame('3rd', $this->formatter->asOrdinal(3));
$this->assertSame('5th', $this->formatter->asOrdinal(5));
$this->formatter->locale = 'de_DE';
$this->assertSame('0.', $this->formatter->asOrdinal(0));
$this->assertSame('1.', $this->formatter->asOrdinal(1));
$this->assertSame('2.', $this->formatter->asOrdinal(2));
$this->assertSame('3.', $this->formatter->asOrdinal(3));
$this->assertSame('5.', $this->formatter->asOrdinal(5));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asOrdinal(null));
}
public function testIntlAsSize()
{
$this->testAsSize();
}
public function testAsSize()
{
// TODO
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment