3
* @link https://www.yiiframework.com/
4
* @copyright Copyright (c) 2008 Yii Software LLC
5
* @license https://www.yiiframework.com/license/
18
use yii\base\Component;
19
use yii\base\InvalidArgumentException;
20
use yii\base\InvalidConfigException;
21
use yii\helpers\ArrayHelper;
22
use yii\helpers\FormatConverter;
24
use yii\helpers\HtmlPurifier;
28
* Formatter provides a set of commonly used data formatting methods.
30
* The formatting methods provided by Formatter are all named in the form of `asXyz()`.
31
* The behavior of some of them may be configured via the properties of Formatter. For example,
32
* by configuring [[dateFormat]], one may control how [[asDate()]] formats the value into a date string.
34
* Formatter is configured as an application component in [[\yii\base\Application]] by default.
35
* You can access that instance via `Yii::$app->formatter`.
37
* The Formatter class is designed to format values according to a [[locale]]. For this feature to work
38
* the [PHP intl extension](https://www.php.net/manual/en/book.intl.php) has to be installed.
39
* Most of the methods however work also if the PHP intl extension is not installed by providing
40
* a fallback implementation. Without intl month and day names are in English only.
41
* Note that even if the intl extension is installed, formatting date and time values for years >=2038 or <=1901
42
* on 32bit systems will fall back to the PHP implementation because intl uses a 32bit UNIX timestamp internally.
43
* On a 64bit system the intl formatter is used in all cases if installed.
45
* > Note: The Formatter class is meant to be used for formatting values for display to users in different
46
* > languages and time zones. If you need to format a date or time in machine readable format, use the
47
* > PHP [date()](https://www.php.net/manual/en/function.date.php) function instead.
49
* @author Qiang Xue <qiang.xue@gmail.com>
50
* @author Enrica Ruedin <e.ruedin@guggach.com>
51
* @author Carsten Brandt <mail@cebe.cc>
54
class Formatter extends Component
59
const UNIT_SYSTEM_METRIC = 'metric';
63
const UNIT_SYSTEM_IMPERIAL = 'imperial';
67
const FORMAT_WIDTH_LONG = 'long';
71
const FORMAT_WIDTH_SHORT = 'short';
75
const UNIT_LENGTH = 'length';
79
const UNIT_WEIGHT = 'mass';
82
* @var string|null the text to be displayed when formatting a `null` value.
83
* Defaults to `'<span class="not-set">(not set)</span>'`, where `(not set)`
84
* will be translated according to [[locale]].
88
* @var array the text to be displayed when formatting a boolean value. The first element corresponds
89
* to the text displayed for `false`, the second element for `true`.
90
* Defaults to `['No', 'Yes']`, where `Yes` and `No`
91
* will be translated according to [[locale]].
93
public $booleanFormat;
95
* @var string|null the locale ID that is used to localize the date and number formatting.
96
* For number and date formatting this is only effective when the
97
* [PHP intl extension](https://www.php.net/manual/en/book.intl.php) is installed.
98
* If not set, [[\yii\base\Application::language]] will be used.
102
* @var string|null the language code (e.g. `en-US`, `en`) that is used to translate internal messages.
103
* If not set, [[locale]] will be used (without the `@calendar` param, if included).
109
* @var string|null the time zone to use for formatting time and date values.
111
* This can be any value that may be passed to [date_default_timezone_set()](https://www.php.net/manual/en/function.date-default-timezone-set.php)
112
* e.g. `UTC`, `Europe/Berlin` or `America/Chicago`.
113
* Refer to the [php manual](https://www.php.net/manual/en/timezones.php) for available time zones.
114
* If this property is not set, [[\yii\base\Application::timeZone]] will be used.
116
* Note that the default time zone for input data is assumed to be UTC by default if no time zone is included in the input date value.
117
* If you store your data in a different time zone in the database, you have to adjust [[defaultTimeZone]] accordingly.
121
* @var string the time zone that is assumed for input values if they do not include a time zone explicitly.
123
* The value must be a valid time zone identifier, e.g. `UTC`, `Europe/Berlin` or `America/Chicago`.
124
* Please refer to the [php manual](https://www.php.net/manual/en/timezones.php) for available time zones.
126
* It defaults to `UTC` so you only have to adjust this value if you store datetime values in another time zone in your database.
128
* Note that a UNIX timestamp is always in UTC by its definition. That means that specifying a default time zone different from
129
* UTC has no effect on date values given as UNIX timestamp.
133
public $defaultTimeZone = 'UTC';
135
* @var string the default format string to be used to format a [[asDate()|date]].
136
* This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
138
* It can also be a custom format as specified in the [ICU manual](https://unicode-org.github.io/icu/userguide/format_parse/datetime/).
139
* Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
140
* PHP [date()](https://www.php.net/manual/en/function.date.php)-function.
145
* 'MM/dd/yyyy' // date in ICU format
146
* 'php:m/d/Y' // the same date in PHP format
149
public $dateFormat = 'medium';
151
* @var string the default format string to be used to format a [[asTime()|time]].
152
* This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
154
* It can also be a custom format as specified in the [ICU manual](https://unicode-org.github.io/icu/userguide/format_parse/datetime/).
155
* Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
156
* PHP [date()](https://www.php.net/manual/en/function.date.php)-function.
161
* 'HH:mm:ss' // time in ICU format
162
* 'php:H:i:s' // the same time in PHP format
165
public $timeFormat = 'medium';
167
* @var string the default format string to be used to format a [[asDatetime()|date and time]].
168
* This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
170
* It can also be a custom format as specified in the [ICU manual](https://unicode-org.github.io/icu/userguide/format_parse/datetime/).
172
* Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
173
* PHP [date()](https://www.php.net/manual/en/function.date.php) function.
178
* 'MM/dd/yyyy HH:mm:ss' // date and time in ICU format
179
* 'php:m/d/Y H:i:s' // the same date and time in PHP format
182
public $datetimeFormat = 'medium';
184
* @var \IntlCalendar|int|null the calendar to be used for date formatting. The value of this property will be directly
185
* passed to the [constructor of the `IntlDateFormatter` class](https://www.php.net/manual/en/intldateformatter.create.php).
187
* Defaults to `null`, which means the Gregorian calendar will be used. You may also explicitly pass the constant
188
* `\IntlDateFormatter::GREGORIAN` for Gregorian calendar.
190
* To use an alternative calendar like for example the [Jalali calendar](https://en.wikipedia.org/wiki/Jalali_calendar),
191
* set this property to `\IntlDateFormatter::TRADITIONAL`.
192
* The calendar must then be specified in the [[locale]], for example for the persian calendar the configuration for the formatter would be:
196
* 'locale' => 'fa_IR@calendar=persian',
197
* 'calendar' => \IntlDateFormatter::TRADITIONAL,
201
* Available calendar names can be found in the [ICU manual](https://unicode-org.github.io/icu/userguide/datetime/calendar/).
203
* Since PHP 5.5 you may also use an instance of the [[\IntlCalendar]] class.
204
* Check the [PHP manual](https://www.php.net/manual/en/intldateformatter.create.php) for more details.
206
* If the [PHP intl extension](https://www.php.net/manual/en/book.intl.php) is not available, setting this property will have no effect.
208
* @see https://www.php.net/manual/en/intldateformatter.create.php
209
* @see https://www.php.net/manual/en/class.intldateformatter.php#intl.intldateformatter-constants.calendartypes
210
* @see https://www.php.net/manual/en/class.intlcalendar.php
215
* @var string|null the character displayed as the decimal point when formatting a number.
216
* If not set, the decimal separator corresponding to [[locale]] will be used.
217
* If [PHP intl extension](https://www.php.net/manual/en/book.intl.php) is not available, the default value is '.'.
219
public $decimalSeparator;
221
* @var string|null the character displayed as the decimal point when formatting a currency.
222
* If not set, the currency decimal separator corresponding to [[locale]] will be used.
223
* If [PHP intl extension](https://www.php.net/manual/en/book.intl.php) is not available, setting this property will have no effect.
226
public $currencyDecimalSeparator;
228
* @var string|null the character displayed as the thousands separator (also called grouping separator) character when formatting a number.
229
* If not set, the thousand separator corresponding to [[locale]] will be used.
230
* If [PHP intl extension](https://www.php.net/manual/en/book.intl.php) is not available, the default value is ','.
232
public $thousandSeparator;
234
* @var array a list of name value pairs that are passed to the
235
* intl [NumberFormatter::setAttribute()](https://www.php.net/manual/en/numberformatter.setattribute.php) method of all
236
* the number formatter objects created by [[createNumberFormatter()]].
237
* This property takes only effect if the [PHP intl extension](https://www.php.net/manual/en/book.intl.php) is installed.
239
* Please refer to the [PHP manual](https://www.php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformatattribute)
240
* for the possible options.
242
* For example to adjust the maximum and minimum value of fraction digits you can configure this property like the following:
246
* NumberFormatter::MIN_FRACTION_DIGITS => 0,
247
* NumberFormatter::MAX_FRACTION_DIGITS => 2,
251
public $numberFormatterOptions = [];
253
* @var array a list of name value pairs that are passed to the
254
* intl [NumberFormatter::setTextAttribute()](https://www.php.net/manual/en/numberformatter.settextattribute.php) method of all
255
* the number formatter objects created by [[createNumberFormatter()]].
256
* This property takes only effect if the [PHP intl extension](https://www.php.net/manual/en/book.intl.php) is installed.
258
* Please refer to the [PHP manual](https://www.php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformattextattribute)
259
* for the possible options.
261
* For example to change the minus sign for negative numbers you can configure this property like the following:
265
* NumberFormatter::NEGATIVE_PREFIX => 'MINUS',
269
public $numberFormatterTextOptions = [];
271
* @var array a list of name value pairs that are passed to the
272
* intl [NumberFormatter::setSymbol()](https://www.php.net/manual/en/numberformatter.setsymbol.php) method of all
273
* the number formatter objects created by [[createNumberFormatter()]].
274
* This property takes only effect if the [PHP intl extension](https://www.php.net/manual/en/book.intl.php) is installed.
276
* Please refer to the [PHP manual](https://www.php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformatsymbol)
277
* for the possible options.
279
* For example to choose a custom currency symbol, e.g. [U+20BD](https://unicode-table.com/en/20BD/) instead of `руб.` for Russian Ruble:
283
* NumberFormatter::CURRENCY_SYMBOL => '₽',
289
public $numberFormatterSymbols = [];
291
* @var string|null the 3-letter ISO 4217 currency code indicating the default currency to use for [[asCurrency]].
292
* If not set, the currency code corresponding to [[locale]] will be used.
293
* Note that in this case the [[locale]] has to be specified with a country code, e.g. `en-US` otherwise it
294
* is not possible to determine the default currency.
296
public $currencyCode;
298
* @var int the base at which a kilobyte is calculated (1000 or 1024 bytes per kilobyte), used by [[asSize]] and [[asShortSize]].
301
public $sizeFormatBase = 1024;
303
* @var string default system of measure units. Defaults to [[UNIT_SYSTEM_METRIC]].
305
* - [[UNIT_SYSTEM_METRIC]]
306
* - [[UNIT_SYSTEM_IMPERIAL]]
312
public $systemOfUnits = self::UNIT_SYSTEM_METRIC;
314
* @var array configuration of weight and length measurement units.
315
* This array contains the most usable measurement units, but you can change it
316
* in case you have some special requirements.
318
* For example, you can add smaller measure unit:
321
* $this->measureUnits[self::UNIT_LENGTH][self::UNIT_SYSTEM_METRIC] = [
322
* 'nanometer' => 0.000001
329
public $measureUnits = [
330
self::UNIT_LENGTH => [
331
self::UNIT_SYSTEM_IMPERIAL => [
339
self::UNIT_SYSTEM_METRIC => [
343
'kilometer' => 1000000,
346
self::UNIT_WEIGHT => [
347
self::UNIT_SYSTEM_IMPERIAL => [
349
'drachm' => 27.34375,
354
'hundredweight' => 784000,
357
self::UNIT_SYSTEM_METRIC => [
365
* @var array The base units that are used as multipliers for smallest possible unit from [[measureUnits]].
368
public $baseUnits = [
369
self::UNIT_LENGTH => [
370
self::UNIT_SYSTEM_IMPERIAL => 12, // 1 feet = 12 inches
371
self::UNIT_SYSTEM_METRIC => 1000, // 1 meter = 1000 millimeters
373
self::UNIT_WEIGHT => [
374
self::UNIT_SYSTEM_IMPERIAL => 7000, // 1 pound = 7000 grains
375
self::UNIT_SYSTEM_METRIC => 1000, // 1 kilogram = 1000 grams
380
* @var bool whether the [PHP intl extension](https://www.php.net/manual/en/book.intl.php) is loaded.
382
private $_intlLoaded = false;
384
* @var \ResourceBundle cached ResourceBundle object used to read unit translations
386
private $_resourceBundle;
388
* @var array cached unit translation patterns
390
private $_unitMessages = [];
396
public function init()
398
if ($this->timeZone === null) {
399
$this->timeZone = Yii::$app->timeZone;
401
if ($this->locale === null) {
402
$this->locale = Yii::$app->language;
404
if ($this->language === null) {
405
$this->language = strtok($this->locale, '@');
407
if ($this->booleanFormat === null) {
408
$this->booleanFormat = [Yii::t('yii', 'No', [], $this->language), Yii::t('yii', 'Yes', [], $this->language)];
410
if ($this->nullDisplay === null) {
411
$this->nullDisplay = '<span class="not-set">' . Yii::t('yii', '(not set)', [], $this->language) . '</span>';
413
$this->_intlLoaded = extension_loaded('intl');
414
if (!$this->_intlLoaded) {
415
if ($this->decimalSeparator === null) {
416
$this->decimalSeparator = '.';
418
if ($this->thousandSeparator === null) {
419
$this->thousandSeparator = ',';
425
* Formats the value based on the given format type.
426
* This method will call one of the "as" methods available in this class to do the formatting.
427
* For type "xyz", the method "asXyz" will be used. For example, if the format is "html",
428
* then [[asHtml()]] will be used. Format names are case insensitive.
429
* @param mixed $value the value to be formatted.
430
* @param string|array|Closure $format the format of the value, e.g., "html", "text" or an anonymous function
431
* returning the formatted value.
433
* To specify additional parameters of the formatting method, you may use an array.
434
* The first element of the array specifies the format name, while the rest of the elements will be used as the
435
* parameters to the formatting method. For example, a format of `['date', 'Y-m-d']` will cause the invocation
436
* of `asDate($value, 'Y-m-d')`.
438
* The anonymous function signature should be: `function($value, $formatter)`,
439
* where `$value` is the value that should be formatted and `$formatter` is an instance of the Formatter class,
440
* which can be used to call other formatting functions.
441
* The possibility to use an anonymous function is available since version 2.0.13.
442
* @return string the formatting result.
443
* @throws InvalidArgumentException if the format type is not supported by this class.
445
public function format($value, $format)
447
if ($format instanceof Closure) {
448
return $format($value, $this);
450
if (is_array($format)) {
451
if (!isset($format[0])) {
452
throw new InvalidArgumentException('The $format array must contain at least one element.');
461
$method = 'as' . $format;
462
if ($this->hasMethod($method)) {
463
return call_user_func_array([$this, $method], array_values($params));
466
throw new InvalidArgumentException("Unknown format type: $format");
472
* Formats the value as is without any formatting.
473
* This method simply returns back the parameter without any format.
474
* The only exception is a `null` value which will be formatted using [[nullDisplay]].
475
* @param mixed $value the value to be formatted.
476
* @return string the formatted result.
478
public function asRaw($value)
480
if ($value === null) {
481
return $this->nullDisplay;
488
* Formats the value as an HTML-encoded plain text.
489
* @param string|null $value the value to be formatted.
490
* @return string the formatted result.
492
public function asText($value)
494
if ($value === null) {
495
return $this->nullDisplay;
498
return Html::encode($value);
502
* Formats the value as an HTML-encoded plain text with newlines converted into breaks.
503
* @param string|null $value the value to be formatted.
504
* @return string the formatted result.
506
public function asNtext($value)
508
if ($value === null) {
509
return $this->nullDisplay;
512
return nl2br(Html::encode($value));
516
* Formats the value as HTML-encoded text paragraphs.
517
* Each text paragraph is enclosed within a `<p>` tag.
518
* One or multiple consecutive empty lines divide two paragraphs.
519
* @param string|null $value the value to be formatted.
520
* @return string the formatted result.
522
public function asParagraphs($value)
524
if ($value === null) {
525
return $this->nullDisplay;
528
return str_replace('<p></p>', '', '<p>' . preg_replace('/\R{2,}/u', "</p>\n<p>", Html::encode($value)) . '</p>');
532
* Formats the value as HTML text.
533
* The value will be purified using [[HtmlPurifier]] to avoid XSS attacks.
534
* Use [[asRaw()]] if you do not want any purification of the value.
535
* @param string|null $value the value to be formatted.
536
* @param array|null $config the configuration for the HTMLPurifier class.
537
* @return string the formatted result.
539
public function asHtml($value, $config = null)
541
if ($value === null) {
542
return $this->nullDisplay;
545
return HtmlPurifier::process($value, $config);
549
* Formats the value as a mailto link.
550
* @param string|null $value the value to be formatted.
551
* @param array $options the tag options in terms of name-value pairs. See [[Html::mailto()]].
552
* @return string the formatted result.
554
public function asEmail($value, $options = [])
556
if ($value === null) {
557
return $this->nullDisplay;
560
return Html::mailto(Html::encode($value), $value, $options);
564
* Formats the value as an image tag.
565
* @param mixed $value the value to be formatted.
566
* @param array $options the tag options in terms of name-value pairs. See [[Html::img()]].
567
* @return string the formatted result.
569
public function asImage($value, $options = [])
571
if ($value === null) {
572
return $this->nullDisplay;
575
return Html::img($value, $options);
579
* Formats the value as a hyperlink.
580
* @param mixed $value the value to be formatted.
581
* @param array $options the tag options in terms of name-value pairs. See [[Html::a()]]. Since 2.0.43 there is
582
* a special option available `scheme` - if set it won't be passed to [[Html::a()]] but it will control the URL
583
* protocol part of the link by normalizing URL and ensuring that it uses specified scheme. See [[Url::ensureScheme()]].
584
* If `scheme` is not set the original behavior is preserved which is to add "http://" prefix when "://" string is
585
* not found in the $value.
586
* @return string the formatted result.
588
public function asUrl($value, $options = [])
590
if ($value === null) {
591
return $this->nullDisplay;
594
$scheme = ArrayHelper::remove($options, 'scheme');
595
if ($scheme === null) {
596
if (strpos($url, '://') === false) {
597
$url = 'http://' . $url;
600
$url = Url::ensureScheme($url, $scheme);
603
return Html::a(Html::encode($value), $url, $options);
607
* Formats the value as a boolean.
608
* @param mixed $value the value to be formatted.
609
* @return string the formatted result.
612
public function asBoolean($value)
614
if ($value === null) {
615
return $this->nullDisplay;
618
return $value ? $this->booleanFormat[1] : $this->booleanFormat[0];
621
// date and time formats
624
* Formats the value as a date.
625
* @param int|string|DateTime|DateTimeInterface|null $value the value to be formatted. The following
626
* types of value are supported:
628
* - an integer representing a UNIX timestamp. A UNIX timestamp is always in UTC by its definition.
629
* - a string that can be [parsed to create a DateTime object](https://www.php.net/manual/en/datetime.formats.php).
630
* The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
631
* - a PHP [DateTime](https://www.php.net/manual/en/class.datetime.php) object. You may set the time zone
632
* for the DateTime object to specify the source time zone.
634
* The formatter will convert date values according to [[timeZone]] before formatting it.
635
* If no timezone conversion should be performed, you need to set [[defaultTimeZone]] and [[timeZone]] to the same value.
636
* Also no conversion will be performed on values that have no time information, e.g. `"2017-06-05"`.
638
* @param string|null $format the format used to convert the value into a date string.
639
* If null, [[dateFormat]] will be used.
641
* This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
642
* It can also be a custom format as specified in the [ICU manual](https://unicode-org.github.io/icu/userguide/format_parse/datetime/).
644
* Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
645
* PHP [date()](https://www.php.net/manual/en/function.date.php)-function.
647
* @return string the formatted result.
648
* @throws InvalidArgumentException if the input value can not be evaluated as a date value.
649
* @throws InvalidConfigException if the date format is invalid.
652
public function asDate($value, $format = null)
654
if ($format === null) {
655
$format = $this->dateFormat;
658
return $this->formatDateTimeValue($value, $format, 'date');
662
* Formats the value as a time.
663
* @param int|string|DateTime|DateTimeInterface|null $value the value to be formatted. The following
664
* types of value are supported:
666
* - an integer representing a UNIX timestamp. A UNIX timestamp is always in UTC by its definition.
667
* - a string that can be [parsed to create a DateTime object](https://www.php.net/manual/en/datetime.formats.php).
668
* The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
669
* - a PHP [DateTime](https://www.php.net/manual/en/class.datetime.php) object. You may set the time zone
670
* for the DateTime object to specify the source time zone.
672
* The formatter will convert date values according to [[timeZone]] before formatting it.
673
* If no timezone conversion should be performed, you need to set [[defaultTimeZone]] and [[timeZone]] to the same value.
675
* @param string|null $format the format used to convert the value into a date string.
676
* If null, [[timeFormat]] will be used.
678
* This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
679
* It can also be a custom format as specified in the [ICU manual](https://unicode-org.github.io/icu/userguide/format_parse/datetime/).
681
* Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
682
* PHP [date()](https://www.php.net/manual/en/function.date.php)-function.
684
* @return string the formatted result.
685
* @throws InvalidArgumentException if the input value can not be evaluated as a date value.
686
* @throws InvalidConfigException if the date format is invalid.
689
public function asTime($value, $format = null)
691
if ($format === null) {
692
$format = $this->timeFormat;
695
return $this->formatDateTimeValue($value, $format, 'time');
699
* Formats the value as a datetime.
700
* @param int|string|DateTime|DateTimeInterface|null $value the value to be formatted. The following
701
* types of value are supported:
703
* - an integer representing a UNIX timestamp. A UNIX timestamp is always in UTC by its definition.
704
* - a string that can be [parsed to create a DateTime object](https://www.php.net/manual/en/datetime.formats.php).
705
* The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
706
* - a PHP [DateTime](https://www.php.net/manual/en/class.datetime.php) object. You may set the time zone
707
* for the DateTime object to specify the source time zone.
709
* The formatter will convert date values according to [[timeZone]] before formatting it.
710
* If no timezone conversion should be performed, you need to set [[defaultTimeZone]] and [[timeZone]] to the same value.
712
* @param string|null $format the format used to convert the value into a date string.
713
* If null, [[datetimeFormat]] will be used.
715
* This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
716
* It can also be a custom format as specified in the [ICU manual](https://unicode-org.github.io/icu/userguide/format_parse/datetime/).
718
* Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
719
* PHP [date()](https://www.php.net/manual/en/function.date.php)-function.
721
* @return string the formatted result.
722
* @throws InvalidArgumentException if the input value can not be evaluated as a date value.
723
* @throws InvalidConfigException if the date format is invalid.
724
* @see datetimeFormat
726
public function asDatetime($value, $format = null)
728
if ($format === null) {
729
$format = $this->datetimeFormat;
732
return $this->formatDateTimeValue($value, $format, 'datetime');
736
* @var array map of short format names to IntlDateFormatter constant values.
738
private $_dateFormats = [
739
'short' => 3, // IntlDateFormatter::SHORT,
740
'medium' => 2, // IntlDateFormatter::MEDIUM,
741
'long' => 1, // IntlDateFormatter::LONG,
742
'full' => 0, // IntlDateFormatter::FULL,
746
* @param int|string|DateTime|DateTimeInterface|null $value the value to be formatted. The following
747
* types of value are supported:
749
* - an integer representing a UNIX timestamp
750
* - a string that can be [parsed to create a DateTime object](https://www.php.net/manual/en/datetime.formats.php).
751
* The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
752
* - a PHP [DateTime](https://www.php.net/manual/en/class.datetime.php) object
754
* @param string $format the format used to convert the value into a date string.
755
* @param string $type 'date', 'time', or 'datetime'.
756
* @throws InvalidConfigException if the date format is invalid.
757
* @return string the formatted result.
759
private function formatDateTimeValue($value, $format, $type)
761
$timeZone = $this->timeZone;
762
// avoid time zone conversion for date-only and time-only values
763
if ($type === 'date' || $type === 'time') {
764
list($timestamp, $hasTimeInfo, $hasDateInfo) = $this->normalizeDatetimeValue($value, true);
765
if (($type === 'date' && !$hasTimeInfo) || ($type === 'time' && !$hasDateInfo)) {
766
$timeZone = $this->defaultTimeZone;
769
$timestamp = $this->normalizeDatetimeValue($value);
771
if ($timestamp === null) {
772
return $this->nullDisplay;
775
// intl does not work with dates >=2038 or <=1901 on 32bit machines, fall back to PHP
776
$year = $timestamp->format('Y');
777
if ($this->_intlLoaded && !(PHP_INT_SIZE === 4 && ($year <= 1901 || $year >= 2038))) {
778
if (strncmp($format, 'php:', 4) === 0) {
779
$format = FormatConverter::convertDatePhpToIcu(substr($format, 4));
781
if (isset($this->_dateFormats[$format])) {
782
if ($type === 'date') {
783
$formatter = new IntlDateFormatter(
785
$this->_dateFormats[$format],
786
IntlDateFormatter::NONE,
790
} elseif ($type === 'time') {
791
$formatter = new IntlDateFormatter(
793
IntlDateFormatter::NONE,
794
$this->_dateFormats[$format],
799
$formatter = new IntlDateFormatter(
801
$this->_dateFormats[$format],
802
$this->_dateFormats[$format],
808
$formatter = new IntlDateFormatter(
810
IntlDateFormatter::NONE,
811
IntlDateFormatter::NONE,
818
// make IntlDateFormatter work with DateTimeImmutable
819
if ($timestamp instanceof \DateTimeImmutable) {
820
$timestamp = new DateTime($timestamp->format(DateTime::ISO8601), $timestamp->getTimezone());
823
return $formatter->format($timestamp);
826
if (strncmp($format, 'php:', 4) === 0) {
827
$format = substr($format, 4);
829
$format = FormatConverter::convertDateIcuToPhp($format, $type, $this->locale);
831
if ($timeZone != null) {
832
if ($timestamp instanceof \DateTimeImmutable) {
833
$timestamp = $timestamp->setTimezone(new DateTimeZone($timeZone));
835
$timestamp->setTimezone(new DateTimeZone($timeZone));
839
return $timestamp->format($format);
843
* Normalizes the given datetime value as a DateTime object that can be taken by various date/time formatting methods.
845
* @param int|string|DateTime|DateTimeInterface|null $value the datetime value to be normalized. The following
846
* types of value are supported:
848
* - an integer representing a UNIX timestamp
849
* - a string that can be [parsed to create a DateTime object](https://www.php.net/manual/en/datetime.formats.php).
850
* The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
851
* - a PHP [DateTime](https://www.php.net/manual/en/class.datetime.php) object
853
* @param bool $checkDateTimeInfo whether to also check if the date/time value has some time and date information attached.
854
* Defaults to `false`. If `true`, the method will then return an array with the first element being the normalized
855
* timestamp, the second a boolean indicating whether the timestamp has time information and third a boolean indicating
856
* whether the timestamp has date information.
857
* This parameter is available since version 2.0.1.
858
* @return DateTime|array the normalized datetime value
859
* Since version 2.0.1 this may also return an array if `$checkDateTimeInfo` is true.
860
* The first element of the array is the normalized timestamp and the second is a boolean indicating whether
861
* the timestamp has time information or it is just a date value.
862
* Since version 2.0.12 the array has third boolean element indicating whether the timestamp has date information
863
* or it is just a time value.
864
* @throws InvalidArgumentException if the input value can not be evaluated as a date value.
866
protected function normalizeDatetimeValue($value, $checkDateTimeInfo = false)
868
// checking for DateTime and DateTimeInterface is not redundant, DateTimeInterface is only in PHP>5.5
869
if ($value === null || $value instanceof DateTime || $value instanceof DateTimeInterface) {
870
// skip any processing
871
return $checkDateTimeInfo ? [$value, true, true] : $value;
877
if (is_numeric($value)) { // process as unix timestamp, which is always in UTC
878
$timestamp = new DateTime('@' . (int) $value, new DateTimeZone('UTC'));
879
return $checkDateTimeInfo ? [$timestamp, true, true] : $timestamp;
882
($timestamp = DateTime::createFromFormat(
885
new DateTimeZone($this->defaultTimeZone))
887
) { // try Y-m-d format (support invalid dates like 2012-13-01)
888
return $checkDateTimeInfo ? [$timestamp, false, true] : $timestamp;
891
($timestamp = DateTime::createFromFormat(
894
new DateTimeZone($this->defaultTimeZone))
896
) { // try Y-m-d H:i:s format (support invalid dates like 2012-13-01 12:63:12)
897
return $checkDateTimeInfo ? [$timestamp, true, true] : $timestamp;
899
// finally try to create a DateTime object with the value
900
if ($checkDateTimeInfo) {
901
$timestamp = new DateTime($value, new DateTimeZone($this->defaultTimeZone));
902
$info = date_parse($value);
905
!($info['hour'] === false && $info['minute'] === false && $info['second'] === false),
906
!($info['year'] === false && $info['month'] === false && $info['day'] === false && empty($info['zone'])),
910
return new DateTime($value, new DateTimeZone($this->defaultTimeZone));
911
} catch (\Exception $e) {
912
throw new InvalidArgumentException("'$value' is not a valid date time value: " . $e->getMessage()
913
. "\n" . print_r(DateTime::getLastErrors(), true), $e->getCode(), $e);
918
* Formats a date, time or datetime in a float number as UNIX timestamp (seconds since 01-01-1970).
919
* @param int|string|DateTime|DateTimeInterface|null $value the value to be formatted. The following
920
* types of value are supported:
922
* - an integer representing a UNIX timestamp
923
* - a string that can be [parsed to create a DateTime object](https://www.php.net/manual/en/datetime.formats.php).
924
* The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
925
* - a PHP [DateTime](https://www.php.net/manual/en/class.datetime.php) object
927
* @return string the formatted result.
929
public function asTimestamp($value)
931
if ($value === null) {
932
return $this->nullDisplay;
934
$timestamp = $this->normalizeDatetimeValue($value);
935
return number_format($timestamp->format('U'), 0, '.', '');
939
* Formats the value as the time interval between a date and now in human readable form.
941
* This method can be used in three different ways:
943
* 1. Using a timestamp that is relative to `now`.
944
* 2. Using a timestamp that is relative to the `$referenceTime`.
945
* 3. Using a `DateInterval` object.
947
* @param int|string|DateTime|DateTimeInterface|DateInterval|null $value the value to be formatted. The following
948
* types of value are supported:
950
* - an integer representing a UNIX timestamp
951
* - a string that can be [parsed to create a DateTime object](https://www.php.net/manual/en/datetime.formats.php).
952
* The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
953
* - a PHP [DateTime](https://www.php.net/manual/en/class.datetime.php) object
954
* - a PHP DateInterval object (a positive time interval will refer to the past, a negative one to the future)
956
* @param int|string|DateTime|DateTimeInterface|null $referenceTime if specified the value is used as a reference time instead of `now`
957
* when `$value` is not a `DateInterval` object.
958
* @return string the formatted result.
959
* @throws InvalidArgumentException if the input value can not be evaluated as a date value.
961
public function asRelativeTime($value, $referenceTime = null)
963
if ($value === null) {
964
return $this->nullDisplay;
967
if ($value instanceof DateInterval) {
970
$timestamp = $this->normalizeDatetimeValue($value);
971
$timeZone = new DateTimeZone($this->timeZone);
973
if ($referenceTime === null) {
974
$dateNow = new DateTime('now', $timeZone);
976
$dateNow = $this->normalizeDatetimeValue($referenceTime);
977
$dateNow->setTimezone($timeZone);
980
$dateThen = $timestamp->setTimezone($timeZone);
981
$interval = $dateThen->diff($dateNow);
984
if ($interval->invert) {
985
if ($interval->y >= 1) {
986
return Yii::t('yii', 'in {delta, plural, =1{a year} other{# years}}', ['delta' => $interval->y], $this->language);
988
if ($interval->m >= 1) {
989
return Yii::t('yii', 'in {delta, plural, =1{a month} other{# months}}', ['delta' => $interval->m], $this->language);
991
if ($interval->d >= 1) {
992
return Yii::t('yii', 'in {delta, plural, =1{a day} other{# days}}', ['delta' => $interval->d], $this->language);
994
if ($interval->h >= 1) {
995
return Yii::t('yii', 'in {delta, plural, =1{an hour} other{# hours}}', ['delta' => $interval->h], $this->language);
997
if ($interval->i >= 1) {
998
return Yii::t('yii', 'in {delta, plural, =1{a minute} other{# minutes}}', ['delta' => $interval->i], $this->language);
1000
if ($interval->s == 0) {
1001
return Yii::t('yii', 'just now', [], $this->language);
1004
return Yii::t('yii', 'in {delta, plural, =1{a second} other{# seconds}}', ['delta' => $interval->s], $this->language);
1007
if ($interval->y >= 1) {
1008
return Yii::t('yii', '{delta, plural, =1{a year} other{# years}} ago', ['delta' => $interval->y], $this->language);
1010
if ($interval->m >= 1) {
1011
return Yii::t('yii', '{delta, plural, =1{a month} other{# months}} ago', ['delta' => $interval->m], $this->language);
1013
if ($interval->d >= 1) {
1014
return Yii::t('yii', '{delta, plural, =1{a day} other{# days}} ago', ['delta' => $interval->d], $this->language);
1016
if ($interval->h >= 1) {
1017
return Yii::t('yii', '{delta, plural, =1{an hour} other{# hours}} ago', ['delta' => $interval->h], $this->language);
1019
if ($interval->i >= 1) {
1020
return Yii::t('yii', '{delta, plural, =1{a minute} other{# minutes}} ago', ['delta' => $interval->i], $this->language);
1022
if ($interval->s == 0) {
1023
return Yii::t('yii', 'just now', [], $this->language);
1026
return Yii::t('yii', '{delta, plural, =1{a second} other{# seconds}} ago', ['delta' => $interval->s], $this->language);
1030
* Represents the value as duration in human readable format.
1032
* @param DateInterval|string|int|null $value the value to be formatted. Acceptable formats:
1033
* - [DateInterval object](https://www.php.net/manual/ru/class.dateinterval.php)
1034
* - integer - number of seconds. For example: value `131` represents `2 minutes, 11 seconds`
1035
* - ISO8601 duration format. For example, all of these values represent `1 day, 2 hours, 30 minutes` duration:
1036
* `2015-01-01T13:00:00Z/2015-01-02T13:30:00Z` - between two datetime values
1037
* `2015-01-01T13:00:00Z/P1D2H30M` - time interval after datetime value
1038
* `P1D2H30M/2015-01-02T13:30:00Z` - time interval before datetime value
1039
* `P1D2H30M` - simply a date interval
1040
* `P-1D2H30M` - a negative date interval (`-1 day, 2 hours, 30 minutes`)
1042
* @param string $implodeString will be used to concatenate duration parts. Defaults to `, `.
1043
* @param string $negativeSign will be prefixed to the formatted duration, when it is negative. Defaults to `-`.
1044
* @return string the formatted duration.
1047
public function asDuration($value, $implodeString = ', ', $negativeSign = '-')
1049
if ($value === null) {
1050
return $this->nullDisplay;
1053
if ($value instanceof DateInterval) {
1054
$isNegative = $value->invert;
1056
} elseif (is_numeric($value)) {
1057
$isNegative = $value < 0;
1058
$zeroDateTime = (new DateTime())->setTimestamp(0);
1059
$valueDateTime = (new DateTime())->setTimestamp(abs($value));
1060
$interval = $valueDateTime->diff($zeroDateTime);
1061
} elseif (strncmp($value, 'P-', 2) === 0) {
1062
$interval = new DateInterval('P' . substr($value, 2));
1065
$interval = new DateInterval($value);
1066
$isNegative = $interval->invert;
1070
if ($interval->y > 0) {
1071
$parts[] = Yii::t('yii', '{delta, plural, =1{1 year} other{# years}}', ['delta' => $interval->y], $this->language);
1073
if ($interval->m > 0) {
1074
$parts[] = Yii::t('yii', '{delta, plural, =1{1 month} other{# months}}', ['delta' => $interval->m], $this->language);
1076
if ($interval->d > 0) {
1077
$parts[] = Yii::t('yii', '{delta, plural, =1{1 day} other{# days}}', ['delta' => $interval->d], $this->language);
1079
if ($interval->h > 0) {
1080
$parts[] = Yii::t('yii', '{delta, plural, =1{1 hour} other{# hours}}', ['delta' => $interval->h], $this->language);
1082
if ($interval->i > 0) {
1083
$parts[] = Yii::t('yii', '{delta, plural, =1{1 minute} other{# minutes}}', ['delta' => $interval->i], $this->language);
1085
if ($interval->s > 0) {
1086
$parts[] = Yii::t('yii', '{delta, plural, =1{1 second} other{# seconds}}', ['delta' => $interval->s], $this->language);
1088
if ($interval->s === 0 && empty($parts)) {
1089
$parts[] = Yii::t('yii', '{delta, plural, =1{1 second} other{# seconds}}', ['delta' => $interval->s], $this->language);
1090
$isNegative = false;
1093
return empty($parts) ? $this->nullDisplay : (($isNegative ? $negativeSign : '') . implode($implodeString, $parts));
1101
* Formats the value as an integer number by removing any decimal digits without rounding.
1103
* Since 2.0.16 numbers that are mispresented after normalization are formatted as strings using fallback function
1104
* without [PHP intl extension](https://www.php.net/manual/en/book.intl.php) support. For very big numbers it's
1105
* recommended to pass them as strings and not use scientific notation otherwise the output might be wrong.
1107
* @param mixed $value the value to be formatted.
1108
* @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1109
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1110
* @return string the formatted result.
1111
* @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1113
public function asInteger($value, $options = [], $textOptions = [])
1115
if ($value === null) {
1116
return $this->nullDisplay;
1119
$normalizedValue = $this->normalizeNumericValue($value);
1121
if ($this->isNormalizedValueMispresented($value, $normalizedValue)) {
1122
return $this->asIntegerStringFallback((string) $value);
1125
if ($this->_intlLoaded) {
1126
$f = $this->createNumberFormatter(NumberFormatter::DECIMAL, null, $options, $textOptions);
1127
$f->setAttribute(NumberFormatter::FRACTION_DIGITS, 0);
1128
if (($result = $f->format($normalizedValue, NumberFormatter::TYPE_INT64)) === false) {
1129
throw new InvalidArgumentException('Formatting integer value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1135
return number_format((int) $normalizedValue, 0, $this->decimalSeparator, $this->thousandSeparator);
1139
* Formats the value as a decimal number.
1141
* Property [[decimalSeparator]] will be used to represent the decimal point. The
1142
* value is rounded automatically to the defined decimal digits.
1144
* Since 2.0.16 numbers that are mispresented after normalization are formatted as strings using fallback function
1145
* without [PHP intl extension](https://www.php.net/manual/en/book.intl.php) support. For very big numbers it's
1146
* recommended to pass them as strings and not use scientific notation otherwise the output might be wrong.
1148
* @param mixed $value the value to be formatted.
1149
* @param int|null $decimals the number of digits after the decimal point.
1150
* If not given, the number of digits depends in the input value and is determined based on
1151
* `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
1152
* using [[$numberFormatterOptions]].
1153
* If the PHP intl extension is not available, the default value is `2`.
1154
* If you want consistent behavior between environments where intl is available and not, you should explicitly
1155
* specify a value here.
1156
* @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1157
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1158
* @return string the formatted result.
1159
* @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1160
* @see decimalSeparator
1161
* @see thousandSeparator
1163
public function asDecimal($value, $decimals = null, $options = [], $textOptions = [])
1165
if ($value === null) {
1166
return $this->nullDisplay;
1169
$normalizedValue = $this->normalizeNumericValue($value);
1171
if ($this->isNormalizedValueMispresented($value, $normalizedValue)) {
1172
return $this->asDecimalStringFallback((string) $value, $decimals);
1175
if ($this->_intlLoaded) {
1176
$f = $this->createNumberFormatter(NumberFormatter::DECIMAL, $decimals, $options, $textOptions);
1177
if (($result = $f->format($normalizedValue)) === false) {
1178
throw new InvalidArgumentException('Formatting decimal value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1184
if ($decimals === null) {
1188
return number_format($normalizedValue, $decimals, $this->decimalSeparator, $this->thousandSeparator);
1192
* Formats the value as a percent number with "%" sign.
1194
* Since 2.0.16 numbers that are mispresented after normalization are formatted as strings using fallback function
1195
* without [PHP intl extension](https://www.php.net/manual/en/book.intl.php) support. For very big numbers it's
1196
* recommended to pass them as strings and not use scientific notation otherwise the output might be wrong.
1198
* @param mixed $value the value to be formatted. It must be a factor e.g. `0.75` will result in `75%`.
1199
* @param int|null $decimals the number of digits after the decimal point.
1200
* If not given, the number of digits depends in the input value and is determined based on
1201
* `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
1202
* using [[$numberFormatterOptions]].
1203
* If the PHP intl extension is not available, the default value is `0`.
1204
* If you want consistent behavior between environments where intl is available and not, you should explicitly
1205
* specify a value here.
1206
* @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1207
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1208
* @return string the formatted result.
1209
* @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1211
public function asPercent($value, $decimals = null, $options = [], $textOptions = [])
1213
if ($value === null) {
1214
return $this->nullDisplay;
1217
$normalizedValue = $this->normalizeNumericValue($value);
1219
if ($this->isNormalizedValueMispresented($value, $normalizedValue)) {
1220
return $this->asPercentStringFallback((string) $value, $decimals);
1223
if ($this->_intlLoaded) {
1224
$f = $this->createNumberFormatter(NumberFormatter::PERCENT, $decimals, $options, $textOptions);
1225
if (($result = $f->format($normalizedValue)) === false) {
1226
throw new InvalidArgumentException('Formatting percent value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1232
if ($decimals === null) {
1236
$normalizedValue *= 100;
1237
return number_format($normalizedValue, $decimals, $this->decimalSeparator, $this->thousandSeparator) . '%';
1241
* Formats the value as a scientific number.
1243
* @param mixed $value the value to be formatted.
1244
* @param int|null $decimals the number of digits after the decimal point.
1245
* If not given, the number of digits depends in the input value and is determined based on
1246
* `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
1247
* using [[$numberFormatterOptions]].
1248
* If the [PHP intl extension](https://www.php.net/manual/en/book.intl.php) is not available, the default value
1249
* depends on your PHP configuration.
1250
* If you want consistent behavior between environments where intl is available and not, you should explicitly
1251
* specify a value here.
1252
* @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1253
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1254
* @return string the formatted result.
1255
* @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1257
public function asScientific($value, $decimals = null, $options = [], $textOptions = [])
1259
if ($value === null) {
1260
return $this->nullDisplay;
1262
$value = $this->normalizeNumericValue($value);
1264
if ($this->_intlLoaded) {
1265
$f = $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $decimals, $options, $textOptions);
1266
if (($result = $f->format($value)) === false) {
1267
throw new InvalidArgumentException('Formatting scientific number value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1273
if ($decimals !== null) {
1274
return sprintf("%.{$decimals}E", $value);
1277
return sprintf('%.E', $value);
1281
* Formats the value as a currency number.
1283
* This function does not require the [PHP intl extension](https://www.php.net/manual/en/book.intl.php) to be installed
1284
* to work, but it is highly recommended to install it to get good formatting results.
1286
* Since 2.0.16 numbers that are mispresented after normalization are formatted as strings using fallback function
1287
* without PHP intl extension support. For very big numbers it's recommended to pass them as strings and not use
1288
* scientific notation otherwise the output might be wrong.
1290
* @param mixed $value the value to be formatted.
1291
* @param string|null $currency the 3-letter ISO 4217 currency code indicating the currency to use.
1292
* If null, [[currencyCode]] will be used.
1293
* @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1294
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1295
* @return string the formatted result.
1296
* @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1297
* @throws InvalidConfigException if no currency is given and [[currencyCode]] is not defined.
1299
public function asCurrency($value, $currency = null, $options = [], $textOptions = [])
1301
if ($value === null) {
1302
return $this->nullDisplay;
1305
$normalizedValue = $this->normalizeNumericValue($value);
1307
if ($this->isNormalizedValueMispresented($value, $normalizedValue)) {
1308
return $this->asCurrencyStringFallback((string) $value, $currency);
1311
if ($this->_intlLoaded) {
1312
$currency = $currency ?: $this->currencyCode;
1313
// currency code must be set before fraction digits
1314
// https://www.php.net/manual/en/numberformatter.formatcurrency.php#114376
1315
if ($currency && !isset($textOptions[NumberFormatter::CURRENCY_CODE])) {
1316
$textOptions[NumberFormatter::CURRENCY_CODE] = $currency;
1318
$formatter = $this->createNumberFormatter(NumberFormatter::CURRENCY, null, $options, $textOptions);
1319
if ($currency === null) {
1320
$result = $formatter->format($normalizedValue);
1322
$result = $formatter->formatCurrency($normalizedValue, $currency);
1324
if ($result === false) {
1325
throw new InvalidArgumentException('Formatting currency value failed: ' . $formatter->getErrorCode() . ' ' . $formatter->getErrorMessage());
1331
if ($currency === null) {
1332
if ($this->currencyCode === null) {
1333
throw new InvalidConfigException('The default currency code for the formatter is not defined and the php intl extension is not installed which could take the default currency from the locale.');
1335
$currency = $this->currencyCode;
1338
return $currency . ' ' . $this->asDecimal($normalizedValue, 2, $options, $textOptions);
1342
* Formats the value as a number spellout.
1344
* This function requires the [PHP intl extension](https://www.php.net/manual/en/book.intl.php) to be installed.
1346
* This formatter does not work well with very big numbers.
1348
* @param mixed $value the value to be formatted
1349
* @return string the formatted result.
1350
* @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1351
* @throws InvalidConfigException when the [PHP intl extension](https://www.php.net/manual/en/book.intl.php) is not available.
1353
public function asSpellout($value)
1355
if ($value === null) {
1356
return $this->nullDisplay;
1358
$value = $this->normalizeNumericValue($value);
1359
if ($this->_intlLoaded) {
1360
$f = $this->createNumberFormatter(NumberFormatter::SPELLOUT);
1361
if (($result = $f->format($value)) === false) {
1362
throw new InvalidArgumentException('Formatting number as spellout failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1368
throw new InvalidConfigException('Format as Spellout is only supported when PHP intl extension is installed.');
1372
* Formats the value as a ordinal value of a number.
1374
* This function requires the [PHP intl extension](https://www.php.net/manual/en/book.intl.php) to be installed.
1376
* This formatter does not work well with very big numbers.
1378
* @param mixed $value the value to be formatted
1379
* @return string the formatted result.
1380
* @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1381
* @throws InvalidConfigException when the [PHP intl extension](https://www.php.net/manual/en/book.intl.php) is not available.
1383
public function asOrdinal($value)
1385
if ($value === null) {
1386
return $this->nullDisplay;
1388
$value = $this->normalizeNumericValue($value);
1389
if ($this->_intlLoaded) {
1390
$f = $this->createNumberFormatter(NumberFormatter::ORDINAL);
1391
if (($result = $f->format($value)) === false) {
1392
throw new InvalidArgumentException('Formatting number as ordinal failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1398
throw new InvalidConfigException('Format as Ordinal is only supported when PHP intl extension is installed.');
1402
* Formats the value in bytes as a size in human readable form for example `12 kB`.
1404
* This is the short form of [[asSize]].
1406
* If [[sizeFormatBase]] is 1024, [binary prefixes](https://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...)
1407
* are used in the formatting result.
1409
* @param string|int|float|null $value value in bytes to be formatted.
1410
* @param int|null $decimals the number of digits after the decimal point.
1411
* @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1412
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1413
* @return string the formatted result.
1414
* @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1415
* @see sizeFormatBase
1418
public function asShortSize($value, $decimals = null, $options = [], $textOptions = [])
1420
if ($value === null) {
1421
return $this->nullDisplay;
1424
list($params, $position) = $this->formatNumber($value, $decimals, 4, $this->sizeFormatBase, $options, $textOptions);
1426
if ($this->sizeFormatBase == 1024) {
1427
switch ($position) {
1429
return Yii::t('yii', '{nFormatted} B', $params, $this->language);
1431
return Yii::t('yii', '{nFormatted} KiB', $params, $this->language);
1433
return Yii::t('yii', '{nFormatted} MiB', $params, $this->language);
1435
return Yii::t('yii', '{nFormatted} GiB', $params, $this->language);
1437
return Yii::t('yii', '{nFormatted} TiB', $params, $this->language);
1439
return Yii::t('yii', '{nFormatted} PiB', $params, $this->language);
1442
switch ($position) {
1444
return Yii::t('yii', '{nFormatted} B', $params, $this->language);
1446
return Yii::t('yii', '{nFormatted} kB', $params, $this->language);
1448
return Yii::t('yii', '{nFormatted} MB', $params, $this->language);
1450
return Yii::t('yii', '{nFormatted} GB', $params, $this->language);
1452
return Yii::t('yii', '{nFormatted} TB', $params, $this->language);
1454
return Yii::t('yii', '{nFormatted} PB', $params, $this->language);
1460
* Formats the value in bytes as a size in human readable form, for example `12 kilobytes`.
1462
* If [[sizeFormatBase]] is 1024, [binary prefixes](https://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...)
1463
* are used in the formatting result.
1465
* @param string|int|float|null $value value in bytes to be formatted.
1466
* @param int|null $decimals the number of digits after the decimal point.
1467
* @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1468
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1469
* @return string the formatted result.
1470
* @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1471
* @see sizeFormatBase
1474
public function asSize($value, $decimals = null, $options = [], $textOptions = [])
1476
if ($value === null) {
1477
return $this->nullDisplay;
1480
list($params, $position) = $this->formatNumber($value, $decimals, 4, $this->sizeFormatBase, $options, $textOptions);
1482
if ($this->sizeFormatBase == 1024) {
1483
switch ($position) {
1485
return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->language);
1487
return Yii::t('yii', '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}', $params, $this->language);
1489
return Yii::t('yii', '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}', $params, $this->language);
1491
return Yii::t('yii', '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}', $params, $this->language);
1493
return Yii::t('yii', '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}', $params, $this->language);
1495
return Yii::t('yii', '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}', $params, $this->language);
1498
switch ($position) {
1500
return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->language);
1502
return Yii::t('yii', '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}', $params, $this->language);
1504
return Yii::t('yii', '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}', $params, $this->language);
1506
return Yii::t('yii', '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}', $params, $this->language);
1508
return Yii::t('yii', '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}', $params, $this->language);
1510
return Yii::t('yii', '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}', $params, $this->language);
1516
* Formats the value as a length in human readable form for example `12 meters`.
1517
* Check properties [[baseUnits]] if you need to change unit of value as the multiplier
1518
* of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
1520
* @param float|int $value value to be formatted.
1521
* @param int|null $decimals the number of digits after the decimal point.
1522
* @param array $numberOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1523
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1524
* @return string the formatted result.
1525
* @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1526
* @throws InvalidConfigException when INTL is not installed or does not contain required information.
1529
* @author John Was <janek.jan@gmail.com>
1531
public function asLength($value, $decimals = null, $numberOptions = [], $textOptions = [])
1533
return $this->formatUnit(self::UNIT_LENGTH, self::FORMAT_WIDTH_LONG, $value, $decimals, $numberOptions, $textOptions);
1537
* Formats the value as a length in human readable form for example `12 m`.
1538
* This is the short form of [[asLength]].
1540
* Check properties [[baseUnits]] if you need to change unit of value as the multiplier
1541
* of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
1543
* @param float|int $value value to be formatted.
1544
* @param int|null $decimals the number of digits after the decimal point.
1545
* @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1546
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1547
* @return string the formatted result.
1548
* @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1549
* @throws InvalidConfigException when INTL is not installed or does not contain required information.
1552
* @author John Was <janek.jan@gmail.com>
1554
public function asShortLength($value, $decimals = null, $options = [], $textOptions = [])
1556
return $this->formatUnit(self::UNIT_LENGTH, self::FORMAT_WIDTH_SHORT, $value, $decimals, $options, $textOptions);
1560
* Formats the value as a weight in human readable form for example `12 kilograms`.
1561
* Check properties [[baseUnits]] if you need to change unit of value as the multiplier
1562
* of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
1564
* @param float|int $value value to be formatted.
1565
* @param int|null $decimals the number of digits after the decimal point.
1566
* @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1567
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1568
* @return string the formatted result.
1569
* @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1570
* @throws InvalidConfigException when INTL is not installed or does not contain required information.
1572
* @author John Was <janek.jan@gmail.com>
1574
public function asWeight($value, $decimals = null, $options = [], $textOptions = [])
1576
return $this->formatUnit(self::UNIT_WEIGHT, self::FORMAT_WIDTH_LONG, $value, $decimals, $options, $textOptions);
1580
* Formats the value as a weight in human readable form for example `12 kg`.
1581
* This is the short form of [[asWeight]].
1583
* Check properties [[baseUnits]] if you need to change unit of value as the multiplier
1584
* of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
1586
* @param float|int $value value to be formatted.
1587
* @param int|null $decimals the number of digits after the decimal point.
1588
* @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1589
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1590
* @return string the formatted result.
1591
* @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1592
* @throws InvalidConfigException when INTL is not installed or does not contain required information.
1594
* @author John Was <janek.jan@gmail.com>
1596
public function asShortWeight($value, $decimals = null, $options = [], $textOptions = [])
1598
return $this->formatUnit(self::UNIT_WEIGHT, self::FORMAT_WIDTH_SHORT, $value, $decimals, $options, $textOptions);
1602
* @param string $unitType one of [[UNIT_WEIGHT]], [[UNIT_LENGTH]]
1603
* @param string $unitFormat one of [[FORMAT_WIDTH_SHORT]], [[FORMAT_WIDTH_LONG]]
1604
* @param float|int|null $value to be formatted
1605
* @param int|null $decimals the number of digits after the decimal point.
1606
* @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1607
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1609
* @throws InvalidConfigException when INTL is not installed or does not contain required information
1611
private function formatUnit($unitType, $unitFormat, $value, $decimals, $options, $textOptions)
1613
if ($value === null) {
1614
return $this->nullDisplay;
1617
$multipliers = array_values($this->measureUnits[$unitType][$this->systemOfUnits]);
1619
list($params, $position) = $this->formatNumber(
1620
$this->normalizeNumericValue($value) * $this->baseUnits[$unitType][$this->systemOfUnits],
1628
$message = $this->getUnitMessage($unitType, $unitFormat, $this->systemOfUnits, $position);
1630
return (new \MessageFormatter($this->locale, $message))->format([
1631
'0' => $params['nFormatted'],
1632
'n' => $params['n'],
1637
* @param string $unitType one of [[UNIT_WEIGHT]], [[UNIT_LENGTH]]
1638
* @param string $unitFormat one of [[FORMAT_WIDTH_SHORT]], [[FORMAT_WIDTH_LONG]]
1639
* @param string|null $system either [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]]. When `null`, property [[systemOfUnits]] will be used.
1640
* @param int $position internal position of size unit
1642
* @throws InvalidConfigException when INTL is not installed or does not contain required information
1644
private function getUnitMessage($unitType, $unitFormat, $system, $position)
1646
if (isset($this->_unitMessages[$unitType][$unitFormat][$system][$position])) {
1647
return $this->_unitMessages[$unitType][$unitFormat][$system][$position];
1649
if (!$this->_intlLoaded) {
1650
throw new InvalidConfigException('Format of ' . $unitType . ' is only supported when PHP intl extension is installed.');
1653
if ($this->_resourceBundle === null) {
1655
$this->_resourceBundle = new \ResourceBundle($this->locale, 'ICUDATA-unit');
1656
} catch (\IntlException $e) {
1657
throw new InvalidConfigException('Current ICU data does not contain information about measure units. Check system requirements.');
1660
$unitNames = array_keys($this->measureUnits[$unitType][$system]);
1661
$bundleKey = 'units' . ($unitFormat === self::FORMAT_WIDTH_SHORT ? 'Short' : '');
1663
$unitBundle = $this->_resourceBundle[$bundleKey][$unitType][$unitNames[$position]];
1664
if ($unitBundle === null) {
1665
throw new InvalidConfigException(
1666
'Current ICU data version does not contain information about unit type "' . $unitType
1667
. '" and unit measure "' . $unitNames[$position] . '". Check system requirements.'
1672
foreach ($unitBundle as $key => $value) {
1673
if ($key === 'dnam') {
1676
$message[] = "$key{{$value}}";
1679
return $this->_unitMessages[$unitType][$unitFormat][$system][$position] = '{n, plural, ' . implode(' ', $message) . '}';
1683
* Given the value in bytes formats number part of the human readable form.
1685
* @param string|int|float $value value in bytes to be formatted.
1686
* @param int|null $decimals the number of digits after the decimal point
1687
* @param int $maxPosition maximum internal position of size unit, ignored if $formatBase is an array
1688
* @param array|int $formatBase the base at which each next unit is calculated, either 1000 or 1024, or an array
1689
* @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1690
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1691
* @return array [parameters for Yii::t containing formatted number, internal position of size unit]
1692
* @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1695
protected function formatNumber($value, $decimals, $maxPosition, $formatBase, $options, $textOptions)
1697
$value = $this->normalizeNumericValue($value);
1700
if (is_array($formatBase)) {
1701
$maxPosition = count($formatBase) - 1;
1704
if (is_array($formatBase)) {
1705
if (!isset($formatBase[$position + 1])) {
1709
if (abs($value) < $formatBase[$position + 1]) {
1713
if (abs($value) < $formatBase) {
1716
$value /= $formatBase;
1719
} while ($position < $maxPosition + 1);
1721
if (is_array($formatBase) && $position !== 0) {
1722
$value /= $formatBase[$position];
1725
// no decimals for smallest unit
1726
if ($position === 0) {
1728
} elseif ($decimals !== null) {
1729
$value = round($value, $decimals);
1731
// disable grouping for edge cases like 1023 to get 1023 B instead of 1,023 B
1732
$oldThousandSeparator = $this->thousandSeparator;
1733
$this->thousandSeparator = '';
1734
if ($this->_intlLoaded && !isset($options[NumberFormatter::GROUPING_USED])) {
1735
$options[NumberFormatter::GROUPING_USED] = 0;
1737
// format the size value
1739
// this is the unformatted number used for the plural rule
1740
// abs() to make sure the plural rules work correctly on negative numbers, intl does not cover this
1741
// https://english.stackexchange.com/questions/9735/is-1-followed-by-a-singular-or-plural-noun
1743
// this is the formatted number used for display
1744
'nFormatted' => $this->asDecimal($value, $decimals, $options, $textOptions),
1746
$this->thousandSeparator = $oldThousandSeparator;
1748
return [$params, $position];
1752
* Normalizes a numeric input value.
1754
* - everything [empty](https://www.php.net/manual/en/function.empty.php) will result in `0`
1755
* - a [numeric](https://www.php.net/manual/en/function.is-numeric.php) string will be casted to float
1756
* - everything else will be returned if it is [numeric](https://www.php.net/manual/en/function.is-numeric.php),
1757
* otherwise an exception is thrown.
1759
* @param mixed $value the input value
1760
* @return float|int the normalized number value
1761
* @throws InvalidArgumentException if the input value is not numeric.
1763
protected function normalizeNumericValue($value)
1765
if (empty($value)) {
1768
if (is_string($value) && is_numeric($value)) {
1769
$value = (float) $value;
1771
if (!is_numeric($value)) {
1772
throw new InvalidArgumentException("'$value' is not a numeric value.");
1779
* Creates a number formatter based on the given type and format.
1781
* You may override this method to create a number formatter based on patterns.
1783
* @param int $style the type of the number formatter.
1784
* Values: NumberFormatter::DECIMAL, ::CURRENCY, ::PERCENT, ::SCIENTIFIC, ::SPELLOUT, ::ORDINAL
1785
* ::DURATION, ::PATTERN_RULEBASED, ::DEFAULT_STYLE, ::IGNORE
1786
* @param int|null $decimals the number of digits after the decimal point.
1787
* @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1788
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1789
* @return NumberFormatter the created formatter instance
1791
protected function createNumberFormatter($style, $decimals = null, $options = [], $textOptions = [])
1793
$formatter = new NumberFormatter($this->locale, $style);
1795
// set text attributes
1796
foreach ($this->numberFormatterTextOptions as $attribute => $value) {
1797
$this->setFormatterTextAttribute($formatter, $attribute, $value, 'numberFormatterTextOptions', 'numberFormatterOptions');
1799
foreach ($textOptions as $attribute => $value) {
1800
$this->setFormatterTextAttribute($formatter, $attribute, $value, '$textOptions', '$options');
1804
foreach ($this->numberFormatterOptions as $attribute => $value) {
1805
$this->setFormatterIntAttribute($formatter, $attribute, $value, 'numberFormatterOptions', 'numberFormatterTextOptions');
1807
foreach ($options as $attribute => $value) {
1808
$this->setFormatterIntAttribute($formatter, $attribute, $value, '$options', '$textOptions');
1810
if ($decimals !== null) {
1811
$formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimals);
1812
$formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimals);
1816
if ($this->decimalSeparator !== null) {
1817
$formatter->setSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL, $this->decimalSeparator);
1819
if ($this->currencyDecimalSeparator !== null) {
1820
$formatter->setSymbol(NumberFormatter::MONETARY_SEPARATOR_SYMBOL, $this->currencyDecimalSeparator);
1822
if ($this->thousandSeparator !== null) {
1823
$formatter->setSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL, $this->thousandSeparator);
1824
$formatter->setSymbol(NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL, $this->thousandSeparator);
1826
foreach ($this->numberFormatterSymbols as $symbol => $value) {
1827
$this->setFormatterSymbol($formatter, $symbol, $value, 'numberFormatterSymbols');
1834
* @param NumberFormatter $formatter
1835
* @param mixed $attribute
1836
* @param mixed $value
1837
* @param string $source
1838
* @param string $alternative
1840
private function setFormatterTextAttribute($formatter, $attribute, $value, $source, $alternative)
1842
if (!is_int($attribute)) {
1843
throw new InvalidArgumentException(
1844
"The $source array keys must be integers recognizable by NumberFormatter::setTextAttribute(). \""
1845
. gettype($attribute) . '" provided instead.'
1848
if (!is_string($value)) {
1849
if (is_int($value)) {
1850
throw new InvalidArgumentException(
1851
"The $source array values must be strings. Did you mean to use $alternative?"
1854
throw new InvalidArgumentException(
1855
"The $source array values must be strings. \"" . gettype($value) . '" provided instead.'
1858
$formatter->setTextAttribute($attribute, $value);
1862
* @param NumberFormatter $formatter
1863
* @param mixed $symbol
1864
* @param mixed $value
1865
* @param string $source
1867
private function setFormatterSymbol($formatter, $symbol, $value, $source)
1869
if (!is_int($symbol)) {
1870
throw new InvalidArgumentException(
1871
"The $source array keys must be integers recognizable by NumberFormatter::setSymbol(). \""
1872
. gettype($symbol) . '" provided instead.'
1875
if (!is_string($value)) {
1876
throw new InvalidArgumentException(
1877
"The $source array values must be strings. \"" . gettype($value) . '" provided instead.'
1880
$formatter->setSymbol($symbol, $value);
1884
* @param NumberFormatter $formatter
1885
* @param mixed $attribute
1886
* @param mixed $value
1887
* @param string $source
1888
* @param string $alternative
1890
private function setFormatterIntAttribute($formatter, $attribute, $value, $source, $alternative)
1892
if (!is_int($attribute)) {
1893
throw new InvalidArgumentException(
1894
"The $source array keys must be integers recognizable by NumberFormatter::setAttribute(). \""
1895
. gettype($attribute) . '" provided instead.'
1898
if (!is_int($value)) {
1899
if (is_string($value)) {
1900
throw new InvalidArgumentException(
1901
"The $source array values must be integers. Did you mean to use $alternative?"
1904
throw new InvalidArgumentException(
1905
"The $source array values must be integers. \"" . gettype($value) . '" provided instead.'
1908
$formatter->setAttribute($attribute, $value);
1912
* Checks if string representations of given value and its normalized version are different.
1913
* @param string|float|int $value
1914
* @param float|int $normalizedValue
1918
protected function isNormalizedValueMispresented($value, $normalizedValue)
1920
if (empty($value)) {
1924
return (string) $normalizedValue !== $this->normalizeNumericStringValue((string) $value);
1928
* Normalizes a numeric string value.
1929
* @param string $value
1930
* @return string the normalized number value as a string
1933
protected function normalizeNumericStringValue($value)
1935
$powerPosition = strrpos($value, 'E');
1936
if ($powerPosition !== false) {
1937
$valuePart = substr($value, 0, $powerPosition);
1938
$powerPart = substr($value, $powerPosition + 1);
1941
$valuePart = $value;
1944
$separatorPosition = strrpos($valuePart, '.');
1946
if ($separatorPosition !== false) {
1947
$integerPart = substr($valuePart, 0, $separatorPosition);
1948
$fractionalPart = substr($valuePart, $separatorPosition + 1);
1950
$integerPart = $valuePart;
1951
$fractionalPart = null;
1954
// truncate insignificant zeros, keep minus
1955
$integerPart = preg_replace('/^\+?(-?)0*(\d+)$/', '$1$2', $integerPart);
1956
// for zeros only leave one zero, keep minus
1957
$integerPart = preg_replace('/^\+?(-?)0*$/', '${1}0', $integerPart);
1959
if ($fractionalPart !== null) {
1960
// truncate insignificant zeros
1961
$fractionalPart = rtrim($fractionalPart, '0');
1963
if (empty($fractionalPart)) {
1964
$fractionalPart = $powerPart !== null ? '0' : null;
1968
$normalizedValue = $integerPart;
1969
if ($fractionalPart !== null) {
1970
$normalizedValue .= '.' . $fractionalPart;
1971
} elseif ($normalizedValue === '-0') {
1972
$normalizedValue = '0';
1975
if ($powerPart !== null) {
1976
$normalizedValue .= 'E' . $powerPart;
1979
return $normalizedValue;
1983
* Fallback for formatting value as a decimal number.
1985
* Property [[decimalSeparator]] will be used to represent the decimal point. The value is rounded automatically
1986
* to the defined decimal digits.
1988
* @param string|int|float $value the value to be formatted.
1989
* @param int|null $decimals the number of digits after the decimal point. The default value is `2`.
1990
* @return string the formatted result.
1991
* @see decimalSeparator
1992
* @see thousandSeparator
1995
protected function asDecimalStringFallback($value, $decimals = 2)
1997
if (empty($value)) {
2001
$value = $this->normalizeNumericStringValue((string) $value);
2003
$separatorPosition = strrpos($value, '.');
2005
if ($separatorPosition !== false) {
2006
$integerPart = substr($value, 0, $separatorPosition);
2007
$fractionalPart = substr($value, $separatorPosition + 1);
2009
$integerPart = $value;
2010
$fractionalPart = null;
2013
$decimalOutput = '';
2015
if ($decimals === null) {
2021
if ($decimals > 0) {
2022
$decimalSeparator = $this->decimalSeparator;
2023
if ($this->decimalSeparator === null) {
2024
$decimalSeparator = '.';
2027
if ($fractionalPart === null) {
2028
$fractionalPart = str_repeat('0', $decimals);
2029
} elseif (strlen($fractionalPart) > $decimals) {
2030
$cursor = $decimals;
2032
// checking if fractional part must be rounded
2033
if ((int) substr($fractionalPart, $cursor, 1) >= 5) {
2034
while (--$cursor >= 0) {
2037
$oneUp = (int) substr($fractionalPart, $cursor, 1) + 1;
2038
if ($oneUp === 10) {
2043
$fractionalPart = substr($fractionalPart, 0, $cursor) . $oneUp . substr($fractionalPart, $cursor + 1);
2051
$fractionalPart = substr($fractionalPart, 0, $decimals);
2052
} elseif (strlen($fractionalPart) < $decimals) {
2053
$fractionalPart = str_pad($fractionalPart, $decimals, '0');
2056
$decimalOutput .= $decimalSeparator . $fractionalPart;
2059
// checking if integer part must be rounded
2060
if ($carry || ($decimals === 0 && $fractionalPart !== null && (int) substr($fractionalPart, 0, 1) >= 5)) {
2061
$integerPartLength = strlen($integerPart);
2064
while (++$cursor <= $integerPartLength) {
2067
$oneUp = (int) substr($integerPart, -$cursor, 1) + 1;
2068
if ($oneUp === 10) {
2073
$integerPart = substr($integerPart, 0, -$cursor) . $oneUp . substr($integerPart, $integerPartLength - $cursor + 1);
2080
$integerPart = '1' . $integerPart;
2084
if (strlen($integerPart) > 3) {
2085
$thousandSeparator = $this->thousandSeparator;
2086
if ($thousandSeparator === null) {
2087
$thousandSeparator = ',';
2090
$integerPart = strrev(implode(',', str_split(strrev($integerPart), 3)));
2091
if ($thousandSeparator !== ',') {
2092
$integerPart = str_replace(',', $thousandSeparator, $integerPart);
2096
return $integerPart . $decimalOutput;
2100
* Fallback for formatting value as an integer number by removing any decimal digits without rounding.
2102
* @param string|int|float $value the value to be formatted.
2103
* @return string the formatted result.
2106
protected function asIntegerStringFallback($value)
2108
if (empty($value)) {
2112
$value = $this->normalizeNumericStringValue((string) $value);
2113
$separatorPosition = strrpos($value, '.');
2115
if ($separatorPosition !== false) {
2116
$integerPart = substr($value, 0, $separatorPosition);
2118
$integerPart = $value;
2121
return $this->asDecimalStringFallback($integerPart, 0);
2125
* Fallback for formatting value as a percent number with "%" sign.
2127
* Property [[decimalSeparator]] will be used to represent the decimal point. The value is rounded automatically
2128
* to the defined decimal digits.
2130
* @param string|int|float $value the value to be formatted.
2131
* @param int|null $decimals the number of digits after the decimal point. The default value is `0`.
2132
* @return string the formatted result.
2135
protected function asPercentStringFallback($value, $decimals = null)
2137
if (empty($value)) {
2141
if ($decimals === null) {
2145
$value = $this->normalizeNumericStringValue((string) $value);
2146
$separatorPosition = strrpos($value, '.');
2148
if ($separatorPosition !== false) {
2149
$integerPart = substr($value, 0, $separatorPosition);
2150
$fractionalPart = str_pad(substr($value, $separatorPosition + 1), 2, '0');
2152
$integerPart .= substr($fractionalPart, 0, 2);
2153
$fractionalPart = substr($fractionalPart, 2);
2155
if ($fractionalPart === '') {
2156
$multipliedValue = $integerPart;
2158
$multipliedValue = $integerPart . '.' . $fractionalPart;
2161
$multipliedValue = $value . '00';
2164
return $this->asDecimalStringFallback($multipliedValue, $decimals) . '%';
2168
* Fallback for formatting value as a currency number.
2170
* @param string|int|float $value the value to be formatted.
2171
* @param string|null $currency the 3-letter ISO 4217 currency code indicating the currency to use.
2172
* If null, [[currencyCode]] will be used.
2173
* @return string the formatted result.
2174
* @throws InvalidConfigException if no currency is given and [[currencyCode]] is not defined.
2177
protected function asCurrencyStringFallback($value, $currency = null)
2179
if ($currency === null) {
2180
if ($this->currencyCode === null) {
2181
throw new InvalidConfigException('The default currency code for the formatter is not defined.');
2183
$currency = $this->currencyCode;
2186
return $currency . ' ' . $this->asDecimalStringFallback($value, 2);