Passed
Push — skip-scientific-format-test ( 645bf8 )
by Alexander
46:20 queued 12:27
created

Formatter::asScientific()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 5.9256

Importance

Changes 0
Metric Value
cc 5
eloc 11
nc 5
nop 4
dl 0
loc 21
ccs 8
cts 12
cp 0.6667
crap 5.9256
rs 9.6111
c 0
b 0
f 0
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\i18n;
9
10
use Closure;
11
use DateInterval;
12
use DateTime;
13
use DateTimeInterface;
14
use DateTimeZone;
15
use IntlDateFormatter;
16
use NumberFormatter;
17
use Yii;
18
use yii\base\Component;
19
use yii\base\InvalidArgumentException;
20
use yii\base\InvalidConfigException;
21
use yii\helpers\FormatConverter;
22
use yii\helpers\Html;
23
use yii\helpers\HtmlPurifier;
24
25
/**
26
 * Formatter provides a set of commonly used data formatting methods.
27
 *
28
 * The formatting methods provided by Formatter are all named in the form of `asXyz()`.
29
 * The behavior of some of them may be configured via the properties of Formatter. For example,
30
 * by configuring [[dateFormat]], one may control how [[asDate()]] formats the value into a date string.
31
 *
32
 * Formatter is configured as an application component in [[\yii\base\Application]] by default.
33
 * You can access that instance via `Yii::$app->formatter`.
34
 *
35
 * The Formatter class is designed to format values according to a [[locale]]. For this feature to work
36
 * the [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) has to be installed.
37
 * Most of the methods however work also if the PHP intl extension is not installed by providing
38
 * a fallback implementation. Without intl month and day names are in English only.
39
 * Note that even if the intl extension is installed, formatting date and time values for years >=2038 or <=1901
40
 * on 32bit systems will fall back to the PHP implementation because intl uses a 32bit UNIX timestamp internally.
41
 * On a 64bit system the intl formatter is used in all cases if installed.
42
 *
43
 * > Note: The Formatter class is meant to be used for formatting values for display to users in different
44
 * > languages and time zones. If you need to format a date or time in machine readable format, use the
45
 * > PHP [date()](https://secure.php.net/manual/en/function.date.php) function instead.
46
 *
47
 * @author Qiang Xue <[email protected]>
48
 * @author Enrica Ruedin <[email protected]>
49
 * @author Carsten Brandt <[email protected]>
50
 * @since 2.0
51
 */
52
class Formatter extends Component
53
{
54
    /**
55
     * @since 2.0.13
56
     */
57
    const UNIT_SYSTEM_METRIC = 'metric';
58
    /**
59
     * @since 2.0.13
60
     */
61
    const UNIT_SYSTEM_IMPERIAL = 'imperial';
62
    /**
63
     * @since 2.0.13
64
     */
65
    const FORMAT_WIDTH_LONG = 'long';
66
    /**
67
     * @since 2.0.13
68
     */
69
    const FORMAT_WIDTH_SHORT = 'short';
70
    /**
71
     * @since 2.0.13
72
     */
73
    const UNIT_LENGTH = 'length';
74
    /**
75
     * @since 2.0.13
76
     */
77
    const UNIT_WEIGHT = 'mass';
78
79
    /**
80
     * @var string the text to be displayed when formatting a `null` value.
81
     * Defaults to `'<span class="not-set">(not set)</span>'`, where `(not set)`
82
     * will be translated according to [[locale]].
83
     */
84
    public $nullDisplay;
85
    /**
86
     * @var array the text to be displayed when formatting a boolean value. The first element corresponds
87
     * to the text displayed for `false`, the second element for `true`.
88
     * Defaults to `['No', 'Yes']`, where `Yes` and `No`
89
     * will be translated according to [[locale]].
90
     */
91
    public $booleanFormat;
92
    /**
93
     * @var string the locale ID that is used to localize the date and number formatting.
94
     * For number and date formatting this is only effective when the
95
     * [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) is installed.
96
     * If not set, [[\yii\base\Application::language]] will be used.
97
     */
98
    public $locale;
99
    /**
100
     * @var string the language code (e.g. `en-US`, `en`) that is used to translate internal messages.
101
     * If not set, [[locale]] will be used (without the `@calendar` param, if included).
102
     *
103
     * @since 2.0.28
104
     */
105
    public $language;
106
    /**
107
     * @var string the time zone to use for formatting time and date values.
108
     *
109
     * This can be any value that may be passed to [date_default_timezone_set()](https://secure.php.net/manual/en/function.date-default-timezone-set.php)
110
     * e.g. `UTC`, `Europe/Berlin` or `America/Chicago`.
111
     * Refer to the [php manual](https://secure.php.net/manual/en/timezones.php) for available time zones.
112
     * If this property is not set, [[\yii\base\Application::timeZone]] will be used.
113
     *
114
     * 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.
115
     * If you store your data in a different time zone in the database, you have to adjust [[defaultTimeZone]] accordingly.
116
     */
117
    public $timeZone;
118
    /**
119
     * @var string the time zone that is assumed for input values if they do not include a time zone explicitly.
120
     *
121
     * The value must be a valid time zone identifier, e.g. `UTC`, `Europe/Berlin` or `America/Chicago`.
122
     * Please refer to the [php manual](https://secure.php.net/manual/en/timezones.php) for available time zones.
123
     *
124
     * It defaults to `UTC` so you only have to adjust this value if you store datetime values in another time zone in your database.
125
     *
126
     * Note that a UNIX timestamp is always in UTC by its definition. That means that specifying a default time zone different from
127
     * UTC has no effect on date values given as UNIX timestamp.
128
     *
129
     * @since 2.0.1
130
     */
131
    public $defaultTimeZone = 'UTC';
132
    /**
133
     * @var string the default format string to be used to format a [[asDate()|date]].
134
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
135
     *
136
     * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax).
137
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
138
     * PHP [date()](https://secure.php.net/manual/en/function.date.php)-function.
139
     *
140
     * For example:
141
     *
142
     * ```php
143
     * 'MM/dd/yyyy' // date in ICU format
144
     * 'php:m/d/Y' // the same date in PHP format
145
     * ```
146
     */
147
    public $dateFormat = 'medium';
148
    /**
149
     * @var string the default format string to be used to format a [[asTime()|time]].
150
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
151
     *
152
     * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax).
153
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
154
     * PHP [date()](https://secure.php.net/manual/en/function.date.php)-function.
155
     *
156
     * For example:
157
     *
158
     * ```php
159
     * 'HH:mm:ss' // time in ICU format
160
     * 'php:H:i:s' // the same time in PHP format
161
     * ```
162
     */
163
    public $timeFormat = 'medium';
164
    /**
165
     * @var string the default format string to be used to format a [[asDatetime()|date and time]].
166
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
167
     *
168
     * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax).
169
     *
170
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
171
     * PHP [date()](https://secure.php.net/manual/en/function.date.php)-function.
172
     *
173
     * For example:
174
     *
175
     * ```php
176
     * 'MM/dd/yyyy HH:mm:ss' // date and time in ICU format
177
     * 'php:m/d/Y H:i:s' // the same date and time in PHP format
178
     * ```
179
     */
180
    public $datetimeFormat = 'medium';
181
    /**
182
     * @var \IntlCalendar|int|null the calendar to be used for date formatting. The value of this property will be directly
183
     * passed to the [constructor of the `IntlDateFormatter` class](https://secure.php.net/manual/en/intldateformatter.create.php).
184
     *
185
     * Defaults to `null`, which means the Gregorian calendar will be used. You may also explicitly pass the constant
186
     * `\IntlDateFormatter::GREGORIAN` for Gregorian calendar.
187
     *
188
     * To use an alternative calendar like for example the [Jalali calendar](https://en.wikipedia.org/wiki/Jalali_calendar),
189
     * set this property to `\IntlDateFormatter::TRADITIONAL`.
190
     * The calendar must then be specified in the [[locale]], for example for the persian calendar the configuration for the formatter would be:
191
     *
192
     * ```php
193
     * 'formatter' => [
194
     *     'locale' => 'fa_IR@calendar=persian',
195
     *     'calendar' => \IntlDateFormatter::TRADITIONAL,
196
     * ],
197
     * ```
198
     *
199
     * Available calendar names can be found in the [ICU manual](http://userguide.icu-project.org/datetime/calendar).
200
     *
201
     * Since PHP 5.5 you may also use an instance of the [[\IntlCalendar]] class.
202
     * Check the [PHP manual](https://secure.php.net/manual/en/intldateformatter.create.php) for more details.
203
     *
204
     * If the [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) is not available, setting this property will have no effect.
205
     *
206
     * @see https://secure.php.net/manual/en/intldateformatter.create.php
207
     * @see https://secure.php.net/manual/en/class.intldateformatter.php#intl.intldateformatter-constants.calendartypes
208
     * @see https://secure.php.net/manual/en/class.intlcalendar.php
209
     * @since 2.0.7
210
     */
211
    public $calendar;
212
    /**
213
     * @var string the character displayed as the decimal point when formatting a number.
214
     * If not set, the decimal separator corresponding to [[locale]] will be used.
215
     * If [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) is not available, the default value is '.'.
216
     */
217
    public $decimalSeparator;
218
    /**
219
     * @var string the character displayed as the thousands separator (also called grouping separator) character when formatting a number.
220
     * If not set, the thousand separator corresponding to [[locale]] will be used.
221
     * If [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) is not available, the default value is ','.
222
     */
223
    public $thousandSeparator;
224
    /**
225
     * @var array a list of name value pairs that are passed to the
226
     * intl [NumberFormatter::setAttribute()](https://secure.php.net/manual/en/numberformatter.setattribute.php) method of all
227
     * the number formatter objects created by [[createNumberFormatter()]].
228
     * This property takes only effect if the [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) is installed.
229
     *
230
     * Please refer to the [PHP manual](https://secure.php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformatattribute)
231
     * for the possible options.
232
     *
233
     * For example to adjust the maximum and minimum value of fraction digits you can configure this property like the following:
234
     *
235
     * ```php
236
     * [
237
     *     NumberFormatter::MIN_FRACTION_DIGITS => 0,
238
     *     NumberFormatter::MAX_FRACTION_DIGITS => 2,
239
     * ]
240
     * ```
241
     */
242
    public $numberFormatterOptions = [];
243
    /**
244
     * @var array a list of name value pairs that are passed to the
245
     * intl [NumberFormatter::setTextAttribute()](https://secure.php.net/manual/en/numberformatter.settextattribute.php) method of all
246
     * the number formatter objects created by [[createNumberFormatter()]].
247
     * This property takes only effect if the [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) is installed.
248
     *
249
     * Please refer to the [PHP manual](https://secure.php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformattextattribute)
250
     * for the possible options.
251
     *
252
     * For example to change the minus sign for negative numbers you can configure this property like the following:
253
     *
254
     * ```php
255
     * [
256
     *     NumberFormatter::NEGATIVE_PREFIX => 'MINUS',
257
     * ]
258
     * ```
259
     */
260
    public $numberFormatterTextOptions = [];
261
    /**
262
     * @var array a list of name value pairs that are passed to the
263
     * intl [NumberFormatter::setSymbol()](https://secure.php.net/manual/en/numberformatter.setsymbol.php) method of all
264
     * the number formatter objects created by [[createNumberFormatter()]].
265
     * This property takes only effect if the [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) is installed.
266
     *
267
     * Please refer to the [PHP manual](https://secure.php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformatsymbol)
268
     * for the possible options.
269
     *
270
     * For example to choose a custom currency symbol, e.g. [U+20BD](http://unicode-table.com/en/20BD/) instead of `руб.` for Russian Ruble:
271
     *
272
     * ```php
273
     * [
274
     *     NumberFormatter::CURRENCY_SYMBOL => '₽',
275
     * ]
276
     * ```
277
     *
278
     * @since 2.0.4
279
     */
280
    public $numberFormatterSymbols = [];
281
    /**
282
     * @var string the 3-letter ISO 4217 currency code indicating the default currency to use for [[asCurrency]].
283
     * If not set, the currency code corresponding to [[locale]] will be used.
284
     * Note that in this case the [[locale]] has to be specified with a country code, e.g. `en-US` otherwise it
285
     * is not possible to determine the default currency.
286
     */
287
    public $currencyCode;
288
    /**
289
     * @var int the base at which a kilobyte is calculated (1000 or 1024 bytes per kilobyte), used by [[asSize]] and [[asShortSize]].
290
     * Defaults to 1024.
291
     */
292
    public $sizeFormatBase = 1024;
293
    /**
294
     * @var string default system of measure units. Defaults to [[UNIT_SYSTEM_METRIC]].
295
     * Possible values:
296
     *  - [[UNIT_SYSTEM_METRIC]]
297
     *  - [[UNIT_SYSTEM_IMPERIAL]]
298
     *
299
     * @see asLength
300
     * @see asWeight
301
     * @since 2.0.13
302
     */
303
    public $systemOfUnits = self::UNIT_SYSTEM_METRIC;
304
    /**
305
     * @var array configuration of weight and length measurement units.
306
     * This array contains the most usable measurement units, but you can change it
307
     * in case you have some special requirements.
308
     *
309
     * For example, you can add smaller measure unit:
310
     *
311
     * ```php
312
     * $this->measureUnits[self::UNIT_LENGTH][self::UNIT_SYSTEM_METRIC] = [
313
     *     'nanometer' => 0.000001
314
     * ]
315
     * ```
316
     * @see asLength
317
     * @see asWeight
318
     * @since 2.0.13
319
     */
320
    public $measureUnits = [
321
        self::UNIT_LENGTH => [
322
            self::UNIT_SYSTEM_IMPERIAL => [
323
                'inch' => 1,
324
                'foot' => 12,
325
                'yard' => 36,
326
                'chain' => 792,
327
                'furlong' => 7920,
328
                'mile' => 63360,
329
            ],
330
            self::UNIT_SYSTEM_METRIC => [
331
                'millimeter' => 1,
332
                'centimeter' => 10,
333
                'meter' => 1000,
334
                'kilometer' => 1000000,
335
            ],
336
        ],
337
        self::UNIT_WEIGHT => [
338
            self::UNIT_SYSTEM_IMPERIAL => [
339
                'grain' => 1,
340
                'drachm' => 27.34375,
341
                'ounce' => 437.5,
342
                'pound' => 7000,
343
                'stone' => 98000,
344
                'quarter' => 196000,
345
                'hundredweight' => 784000,
346
                'ton' => 15680000,
347
            ],
348
            self::UNIT_SYSTEM_METRIC => [
349
                'gram' => 1,
350
                'kilogram' => 1000,
351
                'ton' => 1000000,
352
            ],
353
        ],
354
    ];
355
    /**
356
     * @var array The base units that are used as multipliers for smallest possible unit from [[measureUnits]].
357
     * @since 2.0.13
358
     */
359
    public $baseUnits = [
360
        self::UNIT_LENGTH => [
361
            self::UNIT_SYSTEM_IMPERIAL => 12, // 1 feet = 12 inches
362
            self::UNIT_SYSTEM_METRIC => 1000, // 1 meter = 1000 millimeters
363
        ],
364
        self::UNIT_WEIGHT => [
365
            self::UNIT_SYSTEM_IMPERIAL => 7000, // 1 pound = 7000 grains
366
            self::UNIT_SYSTEM_METRIC => 1000, // 1 kilogram = 1000 grams
367
        ],
368
    ];
369
370
    /**
371
     * @var bool whether the [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) is loaded.
372
     */
373
    private $_intlLoaded = false;
374
    /**
375
     * @var \ResourceBundle cached ResourceBundle object used to read unit translations
376
     */
377
    private $_resourceBundle;
378
    /**
379
     * @var array cached unit translation patterns
380
     */
381
    private $_unitMessages = [];
382
383
384
    /**
385
     * {@inheritdoc}
386
     */
387 309
    public function init()
388
    {
389 309
        if ($this->timeZone === null) {
390 309
            $this->timeZone = Yii::$app->timeZone;
391
        }
392 309
        if ($this->locale === null) {
393 34
            $this->locale = Yii::$app->language;
394
        }
395 309
        if ($this->language === null) {
396 309
            $this->language = strtok($this->locale, '@');
397
        }
398 309
        if ($this->booleanFormat === null) {
399 309
            $this->booleanFormat = [Yii::t('yii', 'No', [], $this->language), Yii::t('yii', 'Yes', [], $this->language)];
400
        }
401 309
        if ($this->nullDisplay === null) {
402 309
            $this->nullDisplay = '<span class="not-set">' . Yii::t('yii', '(not set)', [], $this->language) . '</span>';
403
        }
404 309
        $this->_intlLoaded = extension_loaded('intl');
405 309
        if (!$this->_intlLoaded) {
406 121
            if ($this->decimalSeparator === null) {
407 121
                $this->decimalSeparator = '.';
408
            }
409 121
            if ($this->thousandSeparator === null) {
410 121
                $this->thousandSeparator = ',';
411
            }
412
        }
413 309
    }
414
415
    /**
416
     * Formats the value based on the given format type.
417
     * This method will call one of the "as" methods available in this class to do the formatting.
418
     * For type "xyz", the method "asXyz" will be used. For example, if the format is "html",
419
     * then [[asHtml()]] will be used. Format names are case insensitive.
420
     * @param mixed $value the value to be formatted.
421
     * @param string|array|Closure $format the format of the value, e.g., "html", "text" or an anonymous function
422
     * returning the formatted value.
423
     *
424
     * To specify additional parameters of the formatting method, you may use an array.
425
     * The first element of the array specifies the format name, while the rest of the elements will be used as the
426
     * parameters to the formatting method. For example, a format of `['date', 'Y-m-d']` will cause the invocation
427
     * of `asDate($value, 'Y-m-d')`.
428
     *
429
     * The anonymous function signature should be: `function($value, $formatter)`,
430
     * where `$value` is the value that should be formatted and `$formatter` is an instance of the Formatter class,
431
     * which can be used to call other formatting functions.
432
     * The possibility to use an anonymous function is available since version 2.0.13.
433
     * @return string the formatting result.
434
     * @throws InvalidArgumentException if the format type is not supported by this class.
435
     */
436 11
    public function format($value, $format)
437
    {
438 11
        if ($format instanceof Closure) {
439
            return call_user_func($format, $value, $this);
440 11
        } elseif (is_array($format)) {
441 7
            if (!isset($format[0])) {
442
                throw new InvalidArgumentException('The $format array must contain at least one element.');
443
            }
444 7
            $f = $format[0];
445 7
            $format[0] = $value;
446 7
            $params = $format;
447 7
            $format = $f;
448
        } else {
449 6
            $params = [$value];
450
        }
451 11
        $method = 'as' . $format;
452 11
        if ($this->hasMethod($method)) {
453 11
            return call_user_func_array([$this, $method], $params);
454
        }
455
456 2
        throw new InvalidArgumentException("Unknown format type: $format");
457
    }
458
459
460
    // simple formats
461
462
463
    /**
464
     * Formats the value as is without any formatting.
465
     * This method simply returns back the parameter without any format.
466
     * The only exception is a `null` value which will be formatted using [[nullDisplay]].
467
     * @param mixed $value the value to be formatted.
468
     * @return string the formatted result.
469
     */
470 1
    public function asRaw($value)
471
    {
472 1
        if ($value === null) {
473 1
            return $this->nullDisplay;
474
        }
475
476 1
        return $value;
477
    }
478
479
    /**
480
     * Formats the value as an HTML-encoded plain text.
481
     * @param string $value the value to be formatted.
482
     * @return string the formatted result.
483
     */
484 5
    public function asText($value)
485
    {
486 5
        if ($value === null) {
0 ignored issues
show
introduced by
The condition $value === null is always false.
Loading history...
487 2
            return $this->nullDisplay;
488
        }
489
490 5
        return Html::encode($value);
491
    }
492
493
    /**
494
     * Formats the value as an HTML-encoded plain text with newlines converted into breaks.
495
     * @param string $value the value to be formatted.
496
     * @return string the formatted result.
497
     */
498 1
    public function asNtext($value)
499
    {
500 1
        if ($value === null) {
0 ignored issues
show
introduced by
The condition $value === null is always false.
Loading history...
501 1
            return $this->nullDisplay;
502
        }
503
504 1
        return nl2br(Html::encode($value));
505
    }
506
507
    /**
508
     * Formats the value as HTML-encoded text paragraphs.
509
     * Each text paragraph is enclosed within a `<p>` tag.
510
     * One or multiple consecutive empty lines divide two paragraphs.
511
     * @param string $value the value to be formatted.
512
     * @return string the formatted result.
513
     */
514 1
    public function asParagraphs($value)
515
    {
516 1
        if ($value === null) {
0 ignored issues
show
introduced by
The condition $value === null is always false.
Loading history...
517 1
            return $this->nullDisplay;
518
        }
519
520 1
        return str_replace('<p></p>', '', '<p>' . preg_replace('/\R{2,}/u', "</p>\n<p>", Html::encode($value)) . '</p>');
521
    }
522
523
    /**
524
     * Formats the value as HTML text.
525
     * The value will be purified using [[HtmlPurifier]] to avoid XSS attacks.
526
     * Use [[asRaw()]] if you do not want any purification of the value.
527
     * @param string $value the value to be formatted.
528
     * @param array|null $config the configuration for the HTMLPurifier class.
529
     * @return string the formatted result.
530
     */
531
    public function asHtml($value, $config = null)
532
    {
533
        if ($value === null) {
0 ignored issues
show
introduced by
The condition $value === null is always false.
Loading history...
534
            return $this->nullDisplay;
535
        }
536
537
        return HtmlPurifier::process($value, $config);
538
    }
539
540
    /**
541
     * Formats the value as a mailto link.
542
     * @param string $value the value to be formatted.
543
     * @param array $options the tag options in terms of name-value pairs. See [[Html::mailto()]].
544
     * @return string the formatted result.
545
     */
546 1
    public function asEmail($value, $options = [])
547
    {
548 1
        if ($value === null) {
0 ignored issues
show
introduced by
The condition $value === null is always false.
Loading history...
549 1
            return $this->nullDisplay;
550
        }
551
552 1
        return Html::mailto(Html::encode($value), $value, $options);
553
    }
554
555
    /**
556
     * Formats the value as an image tag.
557
     * @param mixed $value the value to be formatted.
558
     * @param array $options the tag options in terms of name-value pairs. See [[Html::img()]].
559
     * @return string the formatted result.
560
     */
561 1
    public function asImage($value, $options = [])
562
    {
563 1
        if ($value === null) {
564 1
            return $this->nullDisplay;
565
        }
566
567 1
        return Html::img($value, $options);
568
    }
569
570
    /**
571
     * Formats the value as a hyperlink.
572
     * @param mixed $value the value to be formatted.
573
     * @param array $options the tag options in terms of name-value pairs. See [[Html::a()]].
574
     * @return string the formatted result.
575
     */
576 1
    public function asUrl($value, $options = [])
577
    {
578 1
        if ($value === null) {
579 1
            return $this->nullDisplay;
580
        }
581 1
        $url = $value;
582 1
        if (strpos($url, '://') === false) {
583 1
            $url = 'http://' . $url;
584
        }
585
586 1
        return Html::a(Html::encode($value), $url, $options);
587
    }
588
589
    /**
590
     * Formats the value as a boolean.
591
     * @param mixed $value the value to be formatted.
592
     * @return string the formatted result.
593
     * @see booleanFormat
594
     */
595 1
    public function asBoolean($value)
596
    {
597 1
        if ($value === null) {
598 1
            return $this->nullDisplay;
599
        }
600
601 1
        return $value ? $this->booleanFormat[1] : $this->booleanFormat[0];
602
    }
603
604
605
    // date and time formats
606
607
608
    /**
609
     * Formats the value as a date.
610
     * @param int|string|DateTime $value the value to be formatted. The following
611
     * types of value are supported:
612
     *
613
     * - an integer representing a UNIX timestamp. A UNIX timestamp is always in UTC by its definition.
614
     * - a string that can be [parsed to create a DateTime object](https://secure.php.net/manual/en/datetime.formats.php).
615
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
616
     * - a PHP [DateTime](https://secure.php.net/manual/en/class.datetime.php) object. You may set the time zone
617
     *   for the DateTime object to specify the source time zone.
618
     *
619
     * The formatter will convert date values according to [[timeZone]] before formatting it.
620
     * If no timezone conversion should be performed, you need to set [[defaultTimeZone]] and [[timeZone]] to the same value.
621
     * Also no conversion will be performed on values that have no time information, e.g. `"2017-06-05"`.
622
     *
623
     * @param string $format the format used to convert the value into a date string.
624
     * If null, [[dateFormat]] will be used.
625
     *
626
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
627
     * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
628
     *
629
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
630
     * PHP [date()](https://secure.php.net/manual/en/function.date.php)-function.
631
     *
632
     * @return string the formatted result.
633
     * @throws InvalidArgumentException if the input value can not be evaluated as a date value.
634
     * @throws InvalidConfigException if the date format is invalid.
635
     * @see dateFormat
636
     */
637 168
    public function asDate($value, $format = null)
638
    {
639 168
        if ($format === null) {
640 146
            $format = $this->dateFormat;
641
        }
642
643 168
        return $this->formatDateTimeValue($value, $format, 'date');
644
    }
645
646
    /**
647
     * Formats the value as a time.
648
     * @param int|string|DateTime $value the value to be formatted. The following
649
     * types of value are supported:
650
     *
651
     * - an integer representing a UNIX timestamp. A UNIX timestamp is always in UTC by its definition.
652
     * - a string that can be [parsed to create a DateTime object](https://secure.php.net/manual/en/datetime.formats.php).
653
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
654
     * - a PHP [DateTime](https://secure.php.net/manual/en/class.datetime.php) object. You may set the time zone
655
     *   for the DateTime object to specify the source time zone.
656
     *
657
     * The formatter will convert date values according to [[timeZone]] before formatting it.
658
     * If no timezone conversion should be performed, you need to set [[defaultTimeZone]] and [[timeZone]] to the same value.
659
     *
660
     * @param string $format the format used to convert the value into a date string.
661
     * If null, [[timeFormat]] will be used.
662
     *
663
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
664
     * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
665
     *
666
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
667
     * PHP [date()](https://secure.php.net/manual/en/function.date.php)-function.
668
     *
669
     * @return string the formatted result.
670
     * @throws InvalidArgumentException if the input value can not be evaluated as a date value.
671
     * @throws InvalidConfigException if the date format is invalid.
672
     * @see timeFormat
673
     */
674 150
    public function asTime($value, $format = null)
675
    {
676 150
        if ($format === null) {
677 146
            $format = $this->timeFormat;
678
        }
679
680 150
        return $this->formatDateTimeValue($value, $format, 'time');
681
    }
682
683
    /**
684
     * Formats the value as a datetime.
685
     * @param int|string|DateTime $value the value to be formatted. The following
686
     * types of value are supported:
687
     *
688
     * - an integer representing a UNIX timestamp. A UNIX timestamp is always in UTC by its definition.
689
     * - a string that can be [parsed to create a DateTime object](https://secure.php.net/manual/en/datetime.formats.php).
690
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
691
     * - a PHP [DateTime](https://secure.php.net/manual/en/class.datetime.php) object. You may set the time zone
692
     *   for the DateTime object to specify the source time zone.
693
     *
694
     * The formatter will convert date values according to [[timeZone]] before formatting it.
695
     * If no timezone conversion should be performed, you need to set [[defaultTimeZone]] and [[timeZone]] to the same value.
696
     *
697
     * @param string $format the format used to convert the value into a date string.
698
     * If null, [[datetimeFormat]] will be used.
699
     *
700
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
701
     * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
702
     *
703
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
704
     * PHP [date()](https://secure.php.net/manual/en/function.date.php)-function.
705
     *
706
     * @return string the formatted result.
707
     * @throws InvalidArgumentException if the input value can not be evaluated as a date value.
708
     * @throws InvalidConfigException if the date format is invalid.
709
     * @see datetimeFormat
710
     */
711 153
    public function asDatetime($value, $format = null)
712
    {
713 153
        if ($format === null) {
714 144
            $format = $this->datetimeFormat;
715
        }
716
717 153
        return $this->formatDateTimeValue($value, $format, 'datetime');
718
    }
719
720
    /**
721
     * @var array map of short format names to IntlDateFormatter constant values.
722
     */
723
    private $_dateFormats = [
724
        'short' => 3, // IntlDateFormatter::SHORT,
725
        'medium' => 2, // IntlDateFormatter::MEDIUM,
726
        'long' => 1, // IntlDateFormatter::LONG,
727
        'full' => 0, // IntlDateFormatter::FULL,
728
    ];
729
730
    /**
731
     * @param int|string|DateTime $value the value to be formatted. The following
732
     * types of value are supported:
733
     *
734
     * - an integer representing a UNIX timestamp
735
     * - a string that can be [parsed to create a DateTime object](https://secure.php.net/manual/en/datetime.formats.php).
736
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
737
     * - a PHP [DateTime](https://secure.php.net/manual/en/class.datetime.php) object
738
     *
739
     * @param string $format the format used to convert the value into a date string.
740
     * @param string $type 'date', 'time', or 'datetime'.
741
     * @throws InvalidConfigException if the date format is invalid.
742
     * @return string the formatted result.
743
     */
744 179
    private function formatDateTimeValue($value, $format, $type)
745
    {
746 179
        $timeZone = $this->timeZone;
747
        // avoid time zone conversion for date-only and time-only values
748 179
        if ($type === 'date' || $type === 'time') {
749 172
            list($timestamp, $hasTimeInfo, $hasDateInfo) = $this->normalizeDatetimeValue($value, true);
750 170
            if ($type === 'date' && !$hasTimeInfo || $type === 'time' && !$hasDateInfo) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($type === 'date' && ! $...time' && ! $hasDateInfo, Probably Intended Meaning: $type === 'date' && (! $...ime' && ! $hasDateInfo)
Loading history...
751 170
                $timeZone = $this->defaultTimeZone;
752
            }
753
        } else {
754 153
            $timestamp = $this->normalizeDatetimeValue($value);
755
        }
756 177
        if ($timestamp === null) {
757 6
            return $this->nullDisplay;
758
        }
759
760
        // intl does not work with dates >=2038 or <=1901 on 32bit machines, fall back to PHP
761 177
        $year = $timestamp->format('Y');
762 177
        if ($this->_intlLoaded && !(PHP_INT_SIZE === 4 && ($year <= 1901 || $year >= 2038))) {
763 86
            if (strncmp($format, 'php:', 4) === 0) {
764 7
                $format = FormatConverter::convertDatePhpToIcu(substr($format, 4));
765
            }
766 86
            if (isset($this->_dateFormats[$format])) {
767 3
                if ($type === 'date') {
768 1
                    $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], IntlDateFormatter::NONE, $timeZone, $this->calendar);
769 2
                } elseif ($type === 'time') {
770 1
                    $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, $this->_dateFormats[$format], $timeZone, $this->calendar);
771
                } else {
772 3
                    $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], $this->_dateFormats[$format], $timeZone, $this->calendar);
773
                }
774
            } else {
775 86
                $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE, $timeZone, $this->calendar, $format);
776
            }
777 86
            if ($formatter === null) {
778
                throw new InvalidConfigException(intl_get_error_message());
779
            }
780
            // make IntlDateFormatter work with DateTimeImmutable
781 86
            if ($timestamp instanceof \DateTimeImmutable) {
782 14
                $timestamp = new DateTime($timestamp->format(DateTime::ISO8601), $timestamp->getTimezone());
783
            }
784
785 86
            return $formatter->format($timestamp);
786
        }
787
788 91
        if (strncmp($format, 'php:', 4) === 0) {
789 13
            $format = substr($format, 4);
790
        } else {
791 85
            $format = FormatConverter::convertDateIcuToPhp($format, $type, $this->locale);
792
        }
793 91
        if ($timeZone != null) {
794 91
            if ($timestamp instanceof \DateTimeImmutable) {
795 13
                $timestamp = $timestamp->setTimezone(new DateTimeZone($timeZone));
796
            } else {
797 81
                $timestamp->setTimezone(new DateTimeZone($timeZone));
798
            }
799
        }
800
801 91
        return $timestamp->format($format);
802
    }
803
804
    /**
805
     * Normalizes the given datetime value as a DateTime object that can be taken by various date/time formatting methods.
806
     *
807
     * @param int|string|DateTime $value the datetime value to be normalized. The following
808
     * types of value are supported:
809
     *
810
     * - an integer representing a UNIX timestamp
811
     * - a string that can be [parsed to create a DateTime object](https://secure.php.net/manual/en/datetime.formats.php).
812
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
813
     * - a PHP [DateTime](https://secure.php.net/manual/en/class.datetime.php) object
814
     *
815
     * @param bool $checkDateTimeInfo whether to also check if the date/time value has some time and date information attached.
816
     * Defaults to `false`. If `true`, the method will then return an array with the first element being the normalized
817
     * timestamp, the second a boolean indicating whether the timestamp has time information and third a boolean indicating
818
     * whether the timestamp has date information.
819
     * This parameter is available since version 2.0.1.
820
     * @return DateTime|array the normalized datetime value.
821
     * Since version 2.0.1 this may also return an array if `$checkDateTimeInfo` is true.
822
     * The first element of the array is the normalized timestamp and the second is a boolean indicating whether
823
     * the timestamp has time information or it is just a date value.
824
     * Since version 2.0.12 the array has third boolean element indicating whether the timestamp has date information
825
     * or it is just a time value.
826
     * @throws InvalidArgumentException if the input value can not be evaluated as a date value.
827
     */
828 184
    protected function normalizeDatetimeValue($value, $checkDateTimeInfo = false)
829
    {
830
        // checking for DateTime and DateTimeInterface is not redundant, DateTimeInterface is only in PHP>5.5
831 184
        if ($value === null || $value instanceof DateTime || $value instanceof DateTimeInterface) {
832
            // skip any processing
833 50
            return $checkDateTimeInfo ? [$value, true, true] : $value;
834
        }
835 144
        if (empty($value)) {
836 10
            $value = 0;
837
        }
838
        try {
839 144
            if (is_numeric($value)) { // process as unix timestamp, which is always in UTC
840 32
                $timestamp = new DateTime('@' . (int) $value, new DateTimeZone('UTC'));
841 32
                return $checkDateTimeInfo ? [$timestamp, true, true] : $timestamp;
842 117
            } elseif (($timestamp = DateTime::createFromFormat('Y-m-d|', $value, new DateTimeZone($this->defaultTimeZone))) !== false) { // try Y-m-d format (support invalid dates like 2012-13-01)
843 12
                return $checkDateTimeInfo ? [$timestamp, false, true] : $timestamp;
844 105
            } elseif (($timestamp = DateTime::createFromFormat('Y-m-d H:i:s', $value, new DateTimeZone($this->defaultTimeZone))) !== false) { // try Y-m-d H:i:s format (support invalid dates like 2012-13-01 12:63:12)
845 19
                return $checkDateTimeInfo ? [$timestamp, true, true] : $timestamp;
846
            }
847
            // finally try to create a DateTime object with the value
848 90
            if ($checkDateTimeInfo) {
849 88
                $timestamp = new DateTime($value, new DateTimeZone($this->defaultTimeZone));
850 86
                $info = date_parse($value);
851
                return [
852 86
                    $timestamp,
853 86
                    !($info['hour'] === false && $info['minute'] === false && $info['second'] === false),
854 86
                    !($info['year'] === false && $info['month'] === false && $info['day'] === false && empty($info['zone'])),
855
                ];
856
            }
857
858 86
            return new DateTime($value, new DateTimeZone($this->defaultTimeZone));
859 2
        } catch (\Exception $e) {
860 2
            throw new InvalidArgumentException("'$value' is not a valid date time value: " . $e->getMessage()
861 2
                . "\n" . print_r(DateTime::getLastErrors(), true), $e->getCode(), $e);
862
        }
863
    }
864
865
    /**
866
     * Formats a date, time or datetime in a float number as UNIX timestamp (seconds since 01-01-1970).
867
     * @param int|string|DateTime $value the value to be formatted. The following
868
     * types of value are supported:
869
     *
870
     * - an integer representing a UNIX timestamp
871
     * - a string that can be [parsed to create a DateTime object](https://secure.php.net/manual/en/datetime.formats.php).
872
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
873
     * - a PHP [DateTime](https://secure.php.net/manual/en/class.datetime.php) object
874
     *
875
     * @return string the formatted result.
876
     */
877 145
    public function asTimestamp($value)
878
    {
879 145
        if ($value === null) {
880 2
            return $this->nullDisplay;
881
        }
882 145
        $timestamp = $this->normalizeDatetimeValue($value);
883 145
        return number_format($timestamp->format('U'), 0, '.', '');
0 ignored issues
show
Bug introduced by
$timestamp->format('U') of type string is incompatible with the type double expected by parameter $number of number_format(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

883
        return number_format(/** @scrutinizer ignore-type */ $timestamp->format('U'), 0, '.', '');
Loading history...
884
    }
885
886
    /**
887
     * Formats the value as the time interval between a date and now in human readable form.
888
     *
889
     * This method can be used in three different ways:
890
     *
891
     * 1. Using a timestamp that is relative to `now`.
892
     * 2. Using a timestamp that is relative to the `$referenceTime`.
893
     * 3. Using a `DateInterval` object.
894
     *
895
     * @param int|string|DateTime|DateInterval $value the value to be formatted. The following
896
     * types of value are supported:
897
     *
898
     * - an integer representing a UNIX timestamp
899
     * - a string that can be [parsed to create a DateTime object](https://secure.php.net/manual/en/datetime.formats.php).
900
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
901
     * - a PHP [DateTime](https://secure.php.net/manual/en/class.datetime.php) object
902
     * - a PHP DateInterval object (a positive time interval will refer to the past, a negative one to the future)
903
     *
904
     * @param int|string|DateTime $referenceTime if specified the value is used as a reference time instead of `now`
905
     * when `$value` is not a `DateInterval` object.
906
     * @return string the formatted result.
907
     * @throws InvalidArgumentException if the input value can not be evaluated as a date value.
908
     */
909 92
    public function asRelativeTime($value, $referenceTime = null)
910
    {
911 92
        if ($value === null) {
912 2
            return $this->nullDisplay;
913
        }
914
915 92
        if ($value instanceof DateInterval) {
916 2
            $interval = $value;
917
        } else {
918 92
            $timestamp = $this->normalizeDatetimeValue($value);
919
920 92
            if ($timestamp === false) {
0 ignored issues
show
introduced by
The condition $timestamp === false is always false.
Loading history...
921
                // $value is not a valid date/time value, so we try
922
                // to create a DateInterval with it
923
                try {
924
                    $interval = new DateInterval($value);
925
                } catch (\Exception $e) {
926
                    // invalid date/time and invalid interval
927
                    return $this->nullDisplay;
928
                }
929
            } else {
930 92
                $timeZone = new DateTimeZone($this->timeZone);
931
932 92
                if ($referenceTime === null) {
933
                    $dateNow = new DateTime('now', $timeZone);
934
                } else {
935 92
                    $dateNow = $this->normalizeDatetimeValue($referenceTime);
936 92
                    $dateNow->setTimezone($timeZone);
937
                }
938
939 92
                $dateThen = $timestamp->setTimezone($timeZone);
940
941 92
                $interval = $dateThen->diff($dateNow);
942
            }
943
        }
944
945 92
        if ($interval->invert) {
946 92
            if ($interval->y >= 1) {
947 2
                return Yii::t('yii', 'in {delta, plural, =1{a year} other{# years}}', ['delta' => $interval->y], $this->language);
948
            }
949 92
            if ($interval->m >= 1) {
950 2
                return Yii::t('yii', 'in {delta, plural, =1{a month} other{# months}}', ['delta' => $interval->m], $this->language);
951
            }
952 92
            if ($interval->d >= 1) {
953 2
                return Yii::t('yii', 'in {delta, plural, =1{a day} other{# days}}', ['delta' => $interval->d], $this->language);
954
            }
955 92
            if ($interval->h >= 1) {
956 92
                return Yii::t('yii', 'in {delta, plural, =1{an hour} other{# hours}}', ['delta' => $interval->h], $this->language);
957
            }
958 2
            if ($interval->i >= 1) {
959 2
                return Yii::t('yii', 'in {delta, plural, =1{a minute} other{# minutes}}', ['delta' => $interval->i], $this->language);
960
            }
961 2
            if ($interval->s == 0) {
962
                return Yii::t('yii', 'just now', [], $this->language);
963
            }
964
965 2
            return Yii::t('yii', 'in {delta, plural, =1{a second} other{# seconds}}', ['delta' => $interval->s], $this->language);
966
        }
967
968 92
        if ($interval->y >= 1) {
969 2
            return Yii::t('yii', '{delta, plural, =1{a year} other{# years}} ago', ['delta' => $interval->y], $this->language);
970
        }
971 92
        if ($interval->m >= 1) {
972 2
            return Yii::t('yii', '{delta, plural, =1{a month} other{# months}} ago', ['delta' => $interval->m], $this->language);
973
        }
974 92
        if ($interval->d >= 1) {
975 2
            return Yii::t('yii', '{delta, plural, =1{a day} other{# days}} ago', ['delta' => $interval->d], $this->language);
976
        }
977 92
        if ($interval->h >= 1) {
978 92
            return Yii::t('yii', '{delta, plural, =1{an hour} other{# hours}} ago', ['delta' => $interval->h], $this->language);
979
        }
980 2
        if ($interval->i >= 1) {
981 2
            return Yii::t('yii', '{delta, plural, =1{a minute} other{# minutes}} ago', ['delta' => $interval->i], $this->language);
982
        }
983 2
        if ($interval->s == 0) {
984 2
            return Yii::t('yii', 'just now', [], $this->language);
985
        }
986
987 2
        return Yii::t('yii', '{delta, plural, =1{a second} other{# seconds}} ago', ['delta' => $interval->s], $this->language);
988
    }
989
990
    /**
991
     * Represents the value as duration in human readable format.
992
     *
993
     * @param DateInterval|string|int $value the value to be formatted. Acceptable formats:
994
     *  - [DateInterval object](https://secure.php.net/manual/ru/class.dateinterval.php)
995
     *  - integer - number of seconds. For example: value `131` represents `2 minutes, 11 seconds`
996
     *  - ISO8601 duration format. For example, all of these values represent `1 day, 2 hours, 30 minutes` duration:
997
     *    `2015-01-01T13:00:00Z/2015-01-02T13:30:00Z` - between two datetime values
998
     *    `2015-01-01T13:00:00Z/P1D2H30M` - time interval after datetime value
999
     *    `P1D2H30M/2015-01-02T13:30:00Z` - time interval before datetime value
1000
     *    `P1D2H30M` - simply a date interval
1001
     *    `P-1D2H30M` - a negative date interval (`-1 day, 2 hours, 30 minutes`)
1002
     *
1003
     * @param string $implodeString will be used to concatenate duration parts. Defaults to `, `.
1004
     * @param string $negativeSign will be prefixed to the formatted duration, when it is negative. Defaults to `-`.
1005
     * @return string the formatted duration.
1006
     * @since 2.0.7
1007
     */
1008 2
    public function asDuration($value, $implodeString = ', ', $negativeSign = '-')
1009
    {
1010 2
        if ($value === null) {
1011 2
            return $this->nullDisplay;
1012
        }
1013
1014 2
        if ($value instanceof DateInterval) {
1015 2
            $isNegative = $value->invert;
1016 2
            $interval = $value;
1017 2
        } elseif (is_numeric($value)) {
1018 2
            $isNegative = $value < 0;
1019 2
            $zeroDateTime = (new DateTime())->setTimestamp(0);
1020 2
            $valueDateTime = (new DateTime())->setTimestamp(abs($value));
0 ignored issues
show
Bug introduced by
It seems like abs($value) can also be of type double; however, parameter $unixtimestamp of DateTime::setTimestamp() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1020
            $valueDateTime = (new DateTime())->setTimestamp(/** @scrutinizer ignore-type */ abs($value));
Loading history...
1021 2
            $interval = $valueDateTime->diff($zeroDateTime);
1022 2
        } elseif (strncmp($value, 'P-', 2) === 0) {
1023 2
            $interval = new DateInterval('P' . substr($value, 2));
1024 2
            $isNegative = true;
1025
        } else {
1026 2
            $interval = new DateInterval($value);
1027 2
            $isNegative = $interval->invert;
1028
        }
1029
1030 2
        $parts = [];
1031 2
        if ($interval->y > 0) {
1032 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 year} other{# years}}', ['delta' => $interval->y], $this->language);
1033
        }
1034 2
        if ($interval->m > 0) {
1035 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 month} other{# months}}', ['delta' => $interval->m], $this->language);
1036
        }
1037 2
        if ($interval->d > 0) {
1038 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 day} other{# days}}', ['delta' => $interval->d], $this->language);
1039
        }
1040 2
        if ($interval->h > 0) {
1041 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 hour} other{# hours}}', ['delta' => $interval->h], $this->language);
1042
        }
1043 2
        if ($interval->i > 0) {
1044 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 minute} other{# minutes}}', ['delta' => $interval->i], $this->language);
1045
        }
1046 2
        if ($interval->s > 0) {
1047 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 second} other{# seconds}}', ['delta' => $interval->s], $this->language);
1048
        }
1049 2
        if ($interval->s === 0 && empty($parts)) {
1050 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 second} other{# seconds}}', ['delta' => $interval->s], $this->language);
1051 2
            $isNegative = false;
1052
        }
1053
1054 2
        return empty($parts) ? $this->nullDisplay : (($isNegative ? $negativeSign : '') . implode($implodeString, $parts));
1055
    }
1056
1057
1058
    // number formats
1059
1060
1061
    /**
1062
     * Formats the value as an integer number by removing any decimal digits without rounding.
1063
     *
1064
     * Since 2.0.16 numbers that are mispresented after normalization are formatted as strings using fallback function
1065
     * without [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) support. For very big numbers it's
1066
     * recommended to pass them as strings and not use scientific notation otherwise the output might be wrong.
1067
     *
1068
     * @param mixed $value the value to be formatted.
1069
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1070
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1071
     * @return string the formatted result.
1072
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1073
     */
1074 8
    public function asInteger($value, $options = [], $textOptions = [])
1075
    {
1076 8
        if ($value === null) {
1077 5
            return $this->nullDisplay;
1078
        }
1079
1080 8
        $normalizedValue = $this->normalizeNumericValue($value);
1081
1082 6
        if ($this->isNormalizedValueMispresented($value, $normalizedValue)) {
1083 5
            return $this->asIntegerStringFallback((string) $value);
1084
        }
1085
1086 6
        if ($this->_intlLoaded) {
1087 5
            $f = $this->createNumberFormatter(NumberFormatter::DECIMAL, null, $options, $textOptions);
1088 5
            $f->setAttribute(NumberFormatter::FRACTION_DIGITS, 0);
1089 5
            if (($result = $f->format($normalizedValue, NumberFormatter::TYPE_INT64)) === false) {
1090
                throw new InvalidArgumentException('Formatting integer value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1091
            }
1092
1093 5
            return $result;
1094
        }
1095
1096 1
        return number_format((int) $normalizedValue, 0, $this->decimalSeparator, $this->thousandSeparator);
1097
    }
1098
1099
    /**
1100
     * Formats the value as a decimal number.
1101
     *
1102
     * Property [[decimalSeparator]] will be used to represent the decimal point. The
1103
     * value is rounded automatically to the defined decimal digits.
1104
     *
1105
     * Since 2.0.16 numbers that are mispresented after normalization are formatted as strings using fallback function
1106
     * without [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) support. For very big numbers it's
1107
     * recommended to pass them as strings and not use scientific notation otherwise the output might be wrong.
1108
     *
1109
     * @param mixed $value the value to be formatted.
1110
     * @param int $decimals the number of digits after the decimal point.
1111
     * If not given, the number of digits depends in the input value and is determined based on
1112
     * `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
1113
     * using [[$numberFormatterOptions]].
1114
     * If the PHP intl extension is not available, the default value is `2`.
1115
     * If you want consistent behavior between environments where intl is available and not, you should explicitly
1116
     * specify a value here.
1117
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1118
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1119
     * @return string the formatted result.
1120
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1121
     * @see decimalSeparator
1122
     * @see thousandSeparator
1123
     */
1124 58
    public function asDecimal($value, $decimals = null, $options = [], $textOptions = [])
1125
    {
1126 58
        if ($value === null) {
1127 2
            return $this->nullDisplay;
1128
        }
1129
1130 58
        $normalizedValue = $this->normalizeNumericValue($value);
1131
1132 58
        if ($this->isNormalizedValueMispresented($value, $normalizedValue)) {
1133 2
            return $this->asDecimalStringFallback((string) $value, $decimals);
1134
        }
1135
1136 58
        if ($this->_intlLoaded) {
1137 50
            $f = $this->createNumberFormatter(NumberFormatter::DECIMAL, $decimals, $options, $textOptions);
1138 50
            if (($result = $f->format($normalizedValue)) === false) {
1139
                throw new InvalidArgumentException('Formatting decimal value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1140
            }
1141
1142 50
            return $result;
1143
        }
1144
1145 8
        if ($decimals === null) {
1146 6
            $decimals = 2;
1147
        }
1148
1149 8
        return number_format($normalizedValue, $decimals, $this->decimalSeparator, $this->thousandSeparator);
1150
    }
1151
1152
    /**
1153
     * Formats the value as a percent number with "%" sign.
1154
     *
1155
     * Since 2.0.16 numbers that are mispresented after normalization are formatted as strings using fallback function
1156
     * without [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) support. For very big numbers it's
1157
     * recommended to pass them as strings and not use scientific notation otherwise the output might be wrong.
1158
     *
1159
     * @param mixed $value the value to be formatted. It must be a factor e.g. `0.75` will result in `75%`.
1160
     * @param int $decimals the number of digits after the decimal point.
1161
     * If not given, the number of digits depends in the input value and is determined based on
1162
     * `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
1163
     * using [[$numberFormatterOptions]].
1164
     * If the PHP intl extension is not available, the default value is `0`.
1165
     * If you want consistent behavior between environments where intl is available and not, you should explicitly
1166
     * specify a value here.
1167
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1168
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1169
     * @return string the formatted result.
1170
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1171
     */
1172 2
    public function asPercent($value, $decimals = null, $options = [], $textOptions = [])
1173
    {
1174 2
        if ($value === null) {
1175 2
            return $this->nullDisplay;
1176
        }
1177
1178 2
        $normalizedValue = $this->normalizeNumericValue($value);
1179
1180 2
        if ($this->isNormalizedValueMispresented($value, $normalizedValue)) {
1181 2
            return $this->asPercentStringFallback((string) $value, $decimals);
1182
        }
1183
1184 2
        if ($this->_intlLoaded) {
1185 1
            $f = $this->createNumberFormatter(NumberFormatter::PERCENT, $decimals, $options, $textOptions);
1186 1
            if (($result = $f->format($normalizedValue)) === false) {
1187
                throw new InvalidArgumentException('Formatting percent value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1188
            }
1189
1190 1
            return $result;
1191
        }
1192
1193 1
        if ($decimals === null) {
1194 1
            $decimals = 0;
1195
        }
1196
1197 1
        $normalizedValue *= 100;
1198 1
        return number_format($normalizedValue, $decimals, $this->decimalSeparator, $this->thousandSeparator) . '%';
1199
    }
1200
1201
    /**
1202
     * Formats the value as a scientific number.
1203
     *
1204
     * @param mixed $value the value to be formatted.
1205
     * @param int $decimals the number of digits after the decimal point.
1206
     * If not given, the number of digits depends in the input value and is determined based on
1207
     * `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
1208
     * using [[$numberFormatterOptions]].
1209
     * If the [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) is not available, the default value depends on your PHP configuration.
1210
     * If you want consistent behavior between environments where intl is available and not, you should explicitly
1211
     * specify a value here.
1212
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1213
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1214
     * @return string the formatted result.
1215
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1216
     */
1217 1
    public function asScientific($value, $decimals = null, $options = [], $textOptions = [])
1218
    {
1219 1
        if ($value === null) {
1220 1
            return $this->nullDisplay;
1221
        }
1222 1
        $value = $this->normalizeNumericValue($value);
1223
1224 1
        if ($this->_intlLoaded) {
1225
            $f = $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $decimals, $options, $textOptions);
1226
            if (($result = $f->format($value)) === false) {
1227
                throw new InvalidArgumentException('Formatting scientific number value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1228
            }
1229
1230
            return $result;
1231
        }
1232
1233 1
        if ($decimals !== null) {
1234 1
            return sprintf("%.{$decimals}E", $value);
1235
        }
1236
1237 1
        return sprintf('%.E', $value);
1238
    }
1239
1240
    /**
1241
     * Formats the value as a currency number.
1242
     *
1243
     * This function does not require the [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) to be installed
1244
     * to work, but it is highly recommended to install it to get good formatting results.
1245
     *
1246
     * Since 2.0.16 numbers that are mispresented after normalization are formatted as strings using fallback function
1247
     * without PHP intl extension support. For very big numbers it's recommended to pass them as strings and not use
1248
     * scientific notation otherwise the output might be wrong.
1249
     *
1250
     * @param mixed $value the value to be formatted.
1251
     * @param string $currency the 3-letter ISO 4217 currency code indicating the currency to use.
1252
     * If null, [[currencyCode]] will be used.
1253
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1254
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1255
     * @return string the formatted result.
1256
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1257
     * @throws InvalidConfigException if no currency is given and [[currencyCode]] is not defined.
1258
     */
1259 5
    public function asCurrency($value, $currency = null, $options = [], $textOptions = [])
1260
    {
1261 5
        if ($value === null) {
1262 2
            return $this->nullDisplay;
1263
        }
1264
1265 5
        $normalizedValue = $this->normalizeNumericValue($value);
1266
1267 5
        if ($this->isNormalizedValueMispresented($value, $normalizedValue)) {
1268 3
            return $this->asCurrencyStringFallback((string) $value, $currency);
1269
        }
1270
1271 4
        if ($this->_intlLoaded) {
1272 3
            $currency = $currency ?: $this->currencyCode;
1273
            // currency code must be set before fraction digits
1274
            // https://secure.php.net/manual/en/numberformatter.formatcurrency.php#114376
1275 3
            if ($currency && !isset($textOptions[NumberFormatter::CURRENCY_CODE])) {
1276 3
                $textOptions[NumberFormatter::CURRENCY_CODE] = $currency;
1277
            }
1278 3
            $formatter = $this->createNumberFormatter(NumberFormatter::CURRENCY, null, $options, $textOptions);
1279 3
            if ($currency === null) {
0 ignored issues
show
introduced by
The condition $currency === null is always false.
Loading history...
1280 2
                $result = $formatter->format($normalizedValue);
1281
            } else {
1282 3
                $result = $formatter->formatCurrency($normalizedValue, $currency);
1283
            }
1284 3
            if ($result === false) {
1285
                throw new InvalidArgumentException('Formatting currency value failed: ' . $formatter->getErrorCode() . ' ' . $formatter->getErrorMessage());
1286
            }
1287
1288 3
            return $result;
1289
        }
1290
1291 1
        if ($currency === null) {
1292 1
            if ($this->currencyCode === null) {
1293
                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.');
1294
            }
1295 1
            $currency = $this->currencyCode;
1296
        }
1297
1298 1
        return $currency . ' ' . $this->asDecimal($normalizedValue, 2, $options, $textOptions);
1299
    }
1300
1301
    /**
1302
     * Formats the value as a number spellout.
1303
     *
1304
     * This function requires the [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) to be installed.
1305
     *
1306
     * This formatter does not work well with very big numbers.
1307
     *
1308
     * @param mixed $value the value to be formatted
1309
     * @return string the formatted result.
1310
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1311
     * @throws InvalidConfigException when the [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) is not available.
1312
     */
1313 1
    public function asSpellout($value)
1314
    {
1315 1
        if ($value === null) {
1316 1
            return $this->nullDisplay;
1317
        }
1318 1
        $value = $this->normalizeNumericValue($value);
1319 1
        if ($this->_intlLoaded) {
1320 1
            $f = $this->createNumberFormatter(NumberFormatter::SPELLOUT);
1321 1
            if (($result = $f->format($value)) === false) {
1322
                throw new InvalidArgumentException('Formatting number as spellout failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1323
            }
1324
1325 1
            return $result;
1326
        }
1327
1328
        throw new InvalidConfigException('Format as Spellout is only supported when PHP intl extension is installed.');
1329
    }
1330
1331
    /**
1332
     * Formats the value as a ordinal value of a number.
1333
     *
1334
     * This function requires the [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) to be installed.
1335
     *
1336
     * This formatter does not work well with very big numbers.
1337
     *
1338
     * @param mixed $value the value to be formatted
1339
     * @return string the formatted result.
1340
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1341
     * @throws InvalidConfigException when the [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) is not available.
1342
     */
1343 2
    public function asOrdinal($value)
1344
    {
1345 2
        if ($value === null) {
1346 1
            return $this->nullDisplay;
1347
        }
1348 2
        $value = $this->normalizeNumericValue($value);
1349 2
        if ($this->_intlLoaded) {
1350 2
            $f = $this->createNumberFormatter(NumberFormatter::ORDINAL);
1351 2
            if (($result = $f->format($value)) === false) {
1352
                throw new InvalidArgumentException('Formatting number as ordinal failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1353
            }
1354
1355 2
            return $result;
1356
        }
1357
1358
        throw new InvalidConfigException('Format as Ordinal is only supported when PHP intl extension is installed.');
1359
    }
1360
1361
    /**
1362
     * Formats the value in bytes as a size in human readable form for example `12 kB`.
1363
     *
1364
     * This is the short form of [[asSize]].
1365
     *
1366
     * If [[sizeFormatBase]] is 1024, [binary prefixes](http://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...)
1367
     * are used in the formatting result.
1368
     *
1369
     * @param string|int|float $value value in bytes to be formatted.
1370
     * @param int $decimals the number of digits after the decimal point.
1371
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1372
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1373
     * @return string the formatted result.
1374
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1375
     * @see sizeFormatBase
1376
     * @see asSize
1377
     */
1378 5
    public function asShortSize($value, $decimals = null, $options = [], $textOptions = [])
1379
    {
1380 5
        if ($value === null) {
0 ignored issues
show
introduced by
The condition $value === null is always false.
Loading history...
1381 2
            return $this->nullDisplay;
1382
        }
1383
1384 5
        list($params, $position) = $this->formatNumber($value, $decimals, 4, $this->sizeFormatBase, $options, $textOptions);
1385
1386 5
        if ($this->sizeFormatBase == 1024) {
1387
            switch ($position) {
1388 5
                case 0:
1389 5
                    return Yii::t('yii', '{nFormatted} B', $params, $this->language);
1390 3
                case 1:
1391 3
                    return Yii::t('yii', '{nFormatted} KiB', $params, $this->language);
1392 3
                case 2:
1393 3
                    return Yii::t('yii', '{nFormatted} MiB', $params, $this->language);
1394 2
                case 3:
1395 2
                    return Yii::t('yii', '{nFormatted} GiB', $params, $this->language);
1396 2
                case 4:
1397
                    return Yii::t('yii', '{nFormatted} TiB', $params, $this->language);
1398
                default:
1399 2
                    return Yii::t('yii', '{nFormatted} PiB', $params, $this->language);
1400
            }
1401
        } else {
1402
            switch ($position) {
1403 2
                case 0:
1404 2
                    return Yii::t('yii', '{nFormatted} B', $params, $this->language);
1405 2
                case 1:
1406 2
                    return Yii::t('yii', '{nFormatted} kB', $params, $this->language);
1407 2
                case 2:
1408 2
                    return Yii::t('yii', '{nFormatted} MB', $params, $this->language);
1409 2
                case 3:
1410 2
                    return Yii::t('yii', '{nFormatted} GB', $params, $this->language);
1411 2
                case 4:
1412
                    return Yii::t('yii', '{nFormatted} TB', $params, $this->language);
1413
                default:
1414 2
                    return Yii::t('yii', '{nFormatted} PB', $params, $this->language);
1415
            }
1416
        }
1417
    }
1418
1419
    /**
1420
     * Formats the value in bytes as a size in human readable form, for example `12 kilobytes`.
1421
     *
1422
     * If [[sizeFormatBase]] is 1024, [binary prefixes](http://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...)
1423
     * are used in the formatting result.
1424
     *
1425
     * @param string|int|float $value value in bytes to be formatted.
1426
     * @param int $decimals the number of digits after the decimal point.
1427
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1428
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1429
     * @return string the formatted result.
1430
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1431
     * @see sizeFormatBase
1432
     * @see asShortSize
1433
     */
1434 6
    public function asSize($value, $decimals = null, $options = [], $textOptions = [])
1435
    {
1436 6
        if ($value === null) {
0 ignored issues
show
introduced by
The condition $value === null is always false.
Loading history...
1437 2
            return $this->nullDisplay;
1438
        }
1439
1440 6
        list($params, $position) = $this->formatNumber($value, $decimals, 4, $this->sizeFormatBase, $options, $textOptions);
1441
1442 6
        if ($this->sizeFormatBase == 1024) {
1443
            switch ($position) {
1444 6
                case 0:
1445 6
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->language);
1446 4
                case 1:
1447 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}', $params, $this->language);
1448 4
                case 2:
1449 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}', $params, $this->language);
1450 4
                case 3:
1451 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}', $params, $this->language);
1452 4
                case 4:
1453
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}', $params, $this->language);
1454
                default:
1455 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}', $params, $this->language);
1456
            }
1457
        } else {
1458
            switch ($position) {
1459 4
                case 0:
1460 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->language);
1461 4
                case 1:
1462 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}', $params, $this->language);
1463 4
                case 2:
1464 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}', $params, $this->language);
1465 4
                case 3:
1466 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}', $params, $this->language);
1467 4
                case 4:
1468
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}', $params, $this->language);
1469
                default:
1470 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}', $params, $this->language);
1471
            }
1472
        }
1473
    }
1474
1475
    /**
1476
     * Formats the value as a length in human readable form for example `12 meters`.
1477
     * Check properties [[baseUnits]] if you need to change unit of value as the multiplier
1478
     * of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
1479
     *
1480
     * @param float|int $value value to be formatted.
1481
     * @param int $decimals the number of digits after the decimal point.
1482
     * @param array $numberOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1483
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1484
     * @return string the formatted result.
1485
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1486
     * @throws InvalidConfigException when INTL is not installed or does not contain required information.
1487
     * @see asLength
1488
     * @since 2.0.13
1489
     * @author John Was <[email protected]>
1490
     */
1491 13
    public function asLength($value, $decimals = null, $numberOptions = [], $textOptions = [])
1492
    {
1493 13
        return $this->formatUnit(self::UNIT_LENGTH, self::FORMAT_WIDTH_LONG, $value, null, null, $decimals, $numberOptions, $textOptions);
1494
    }
1495
1496
    /**
1497
     * Formats the value as a length in human readable form for example `12 m`.
1498
     * This is the short form of [[asLength]].
1499
     *
1500
     * Check properties [[baseUnits]] if you need to change unit of value as the multiplier
1501
     * of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
1502
     *
1503
     * @param float|int $value value to be formatted.
1504
     * @param int $decimals the number of digits after the decimal point.
1505
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1506
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1507
     * @return string the formatted result.
1508
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1509
     * @throws InvalidConfigException when INTL is not installed or does not contain required information.
1510
     * @see asLength
1511
     * @since 2.0.13
1512
     * @author John Was <[email protected]>
1513
     */
1514 14
    public function asShortLength($value, $decimals = null, $options = [], $textOptions = [])
1515
    {
1516 14
        return $this->formatUnit(self::UNIT_LENGTH, self::FORMAT_WIDTH_SHORT, $value, null, null, $decimals, $options, $textOptions);
1517
    }
1518
1519
    /**
1520
     * Formats the value as a weight in human readable form for example `12 kilograms`.
1521
     * Check properties [[baseUnits]] if you need to change unit of value as the multiplier
1522
     * of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
1523
     *
1524
     * @param float|int $value value to be formatted.
1525
     * @param int $decimals the number of digits after the decimal point.
1526
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1527
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1528
     * @return string the formatted result.
1529
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1530
     * @throws InvalidConfigException when INTL is not installed or does not contain required information.
1531
     * @since 2.0.13
1532
     * @author John Was <[email protected]>
1533
     */
1534 14
    public function asWeight($value, $decimals = null, $options = [], $textOptions = [])
1535
    {
1536 14
        return $this->formatUnit(self::UNIT_WEIGHT, self::FORMAT_WIDTH_LONG, $value, null, null, $decimals, $options, $textOptions);
1537
    }
1538
1539
    /**
1540
     * Formats the value as a weight in human readable form for example `12 kg`.
1541
     * This is the short form of [[asWeight]].
1542
     *
1543
     * Check properties [[baseUnits]] if you need to change unit of value as the multiplier
1544
     * of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
1545
     *
1546
     * @param float|int $value value to be formatted.
1547
     * @param int $decimals the number of digits after the decimal point.
1548
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1549
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1550
     * @return string the formatted result.
1551
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1552
     * @throws InvalidConfigException when INTL is not installed or does not contain required information.
1553
     * @since 2.0.13
1554
     * @author John Was <[email protected]>
1555
     */
1556 13
    public function asShortWeight($value, $decimals = null, $options = [], $textOptions = [])
1557
    {
1558 13
        return $this->formatUnit(self::UNIT_WEIGHT, self::FORMAT_WIDTH_SHORT, $value, null, null, $decimals, $options, $textOptions);
1559
    }
1560
1561
    /**
1562
     * @param string $unitType one of [[UNIT_WEIGHT]], [[UNIT_LENGTH]]
1563
     * @param string $unitFormat one of [[FORMAT_WIDTH_SHORT]], [[FORMAT_WIDTH_LONG]]
1564
     * @param float|int $value to be formatted
1565
     * @param float $baseUnit unit of value as the multiplier of the smallest unit. When `null`, property [[baseUnits]]
1566
     * will be used to determine base unit using $unitType and $unitSystem.
1567
     * @param string $unitSystem either [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]]. When `null`, property [[systemOfUnits]] will be used.
1568
     * @param int $decimals the number of digits after the decimal point.
1569
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1570
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1571
     * @return string
1572
     * @throws InvalidConfigException when INTL is not installed or does not contain required information
1573
     */
1574 54
    private function formatUnit($unitType, $unitFormat, $value, $baseUnit, $unitSystem, $decimals, $options, $textOptions)
1575
    {
1576 54
        if ($value === null) {
0 ignored issues
show
introduced by
The condition $value === null is always false.
Loading history...
1577 4
            return $this->nullDisplay;
1578
        }
1579 50
        if ($unitSystem === null) {
0 ignored issues
show
introduced by
The condition $unitSystem === null is always false.
Loading history...
1580 50
            $unitSystem = $this->systemOfUnits;
1581
        }
1582 50
        if ($baseUnit === null) {
0 ignored issues
show
introduced by
The condition $baseUnit === null is always false.
Loading history...
1583 50
            $baseUnit = $this->baseUnits[$unitType][$unitSystem];
1584
        }
1585
1586 50
        $multipliers = array_values($this->measureUnits[$unitType][$unitSystem]);
1587
1588 50
        list($params, $position) = $this->formatNumber(
1589 50
            $this->normalizeNumericValue($value) * $baseUnit,
1590 46
            $decimals,
1591 46
            null,
1592 46
            $multipliers,
1593 46
            $options,
1594 46
            $textOptions
1595
        );
1596
1597 46
        $message = $this->getUnitMessage($unitType, $unitFormat, $unitSystem, $position);
1598
1599 44
        return (new \MessageFormatter($this->locale, $message))->format([
1600 44
            '0' => $params['nFormatted'],
1601 44
            'n' => $params['n'],
1602
        ]);
1603
    }
1604
1605
    /**
1606
     * @param string $unitType one of [[UNIT_WEIGHT]], [[UNIT_LENGTH]]
1607
     * @param string $unitFormat one of [[FORMAT_WIDTH_SHORT]], [[FORMAT_WIDTH_LONG]]
1608
     * @param string $system either [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]]. When `null`, property [[systemOfUnits]] will be used.
1609
     * @param int $position internal position of size unit
1610
     * @return string
1611
     * @throws InvalidConfigException when INTL is not installed or does not contain required information
1612
     */
1613 46
    private function getUnitMessage($unitType, $unitFormat, $system, $position)
1614
    {
1615 46
        if (isset($this->_unitMessages[$unitType][$system][$position])) {
1616
            return $this->_unitMessages[$unitType][$system][$position];
1617
        }
1618 46
        if (!$this->_intlLoaded) {
1619 2
            throw new InvalidConfigException('Format of ' . $unitType . ' is only supported when PHP intl extension is installed.');
1620
        }
1621
1622 44
        if ($this->_resourceBundle === null) {
1623
            try {
1624 44
                $this->_resourceBundle = new \ResourceBundle($this->locale, 'ICUDATA-unit');
1625
            } catch (\IntlException $e) {
1626
                throw new InvalidConfigException('Current ICU data does not contain information about measure units. Check system requirements.');
1627
            }
1628
        }
1629 44
        $unitNames = array_keys($this->measureUnits[$unitType][$system]);
1630 44
        $bundleKey = 'units' . ($unitFormat === self::FORMAT_WIDTH_SHORT ? 'Short' : '');
1631
1632 44
        $unitBundle = $this->_resourceBundle[$bundleKey][$unitType][$unitNames[$position]];
1633 44
        if ($unitBundle === null) {
1634
            throw new InvalidConfigException('Current ICU data version does not contain information about unit type "' . $unitType . '" and unit measure "' . $unitNames[$position] . '". Check system requirements.');
1635
        }
1636
1637 44
        $message = [];
1638 44
        foreach ($unitBundle as $key => $value) {
1639 44
            if ($key === 'dnam') {
1640 44
                continue;
1641
            }
1642 44
            $message[] = "$key{{$value}}";
1643
        }
1644
1645 44
        return $this->_unitMessages[$unitType][$system][$position] = '{n, plural, ' . implode(' ', $message) . '}';
1646
    }
1647
1648
    /**
1649
     * Given the value in bytes formats number part of the human readable form.
1650
     *
1651
     * @param string|int|float $value value in bytes to be formatted.
1652
     * @param int $decimals the number of digits after the decimal point
1653
     * @param int $maxPosition maximum internal position of size unit, ignored if $formatBase is an array
1654
     * @param array|int $formatBase the base at which each next unit is calculated, either 1000 or 1024, or an array
1655
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1656
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1657
     * @return array [parameters for Yii::t containing formatted number, internal position of size unit]
1658
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1659
     * @since 2.0.32
1660
     */
1661 55
    protected function formatNumber($value, $decimals, $maxPosition, $formatBase, $options, $textOptions)
1662
    {
1663 55
        $value = $this->normalizeNumericValue($value);
1664
1665 55
        $position = 0;
1666 55
        if (is_array($formatBase)) {
1667 46
            $maxPosition = count($formatBase) - 1;
1668
        }
1669
        do {
1670 55
            if (is_array($formatBase)) {
1671 46
                if (!isset($formatBase[$position + 1])) {
1672 12
                    break;
1673
                }
1674
1675 46
                if (abs($value) < $formatBase[$position + 1]) {
1676 46
                    break;
1677
                }
1678
            } else {
1679 9
                if (abs($value) < $formatBase) {
1680 9
                    break;
1681
                }
1682 7
                $value /= $formatBase;
1683
            }
1684 37
            $position++;
1685 37
        } while ($position < $maxPosition + 1);
1686
1687 55
        if (is_array($formatBase) && $position !== 0) {
1688 30
            $value /= $formatBase[$position];
1689
        }
1690
1691
        // no decimals for smallest unit
1692 55
        if ($position === 0) {
1693 25
            $decimals = 0;
1694 37
        } elseif ($decimals !== null) {
0 ignored issues
show
introduced by
The condition $decimals !== null is always true.
Loading history...
1695 10
            $value = round($value, $decimals);
1696
        }
1697
        // disable grouping for edge cases like 1023 to get 1023 B instead of 1,023 B
1698 55
        $oldThousandSeparator = $this->thousandSeparator;
1699 55
        $this->thousandSeparator = '';
1700 55
        if ($this->_intlLoaded && !isset($options[NumberFormatter::GROUPING_USED])) {
1701 49
            $options[NumberFormatter::GROUPING_USED] = false;
1702
        }
1703
        // format the size value
1704
        $params = [
1705
            // this is the unformatted number used for the plural rule
1706
            // abs() to make sure the plural rules work correctly on negative numbers, intl does not cover this
1707
            // http://english.stackexchange.com/questions/9735/is-1-singular-or-plural
1708 55
            'n' => abs($value),
1709
            // this is the formatted number used for display
1710 55
            'nFormatted' => $this->asDecimal($value, $decimals, $options, $textOptions),
1711
        ];
1712 55
        $this->thousandSeparator = $oldThousandSeparator;
1713
1714 55
        return [$params, $position];
1715
    }
1716
1717
    /**
1718
     * Normalizes a numeric input value.
1719
     *
1720
     * - everything [empty](https://secure.php.net/manual/en/function.empty.php) will result in `0`
1721
     * - a [numeric](https://secure.php.net/manual/en/function.is-numeric.php) string will be casted to float
1722
     * - everything else will be returned if it is [numeric](https://secure.php.net/manual/en/function.is-numeric.php),
1723
     *   otherwise an exception is thrown.
1724
     *
1725
     * @param mixed $value the input value
1726
     * @return float|int the normalized number value
1727
     * @throws InvalidArgumentException if the input value is not numeric.
1728
     */
1729 80
    protected function normalizeNumericValue($value)
1730
    {
1731 80
        if (empty($value)) {
1732 20
            return 0;
1733
        }
1734 76
        if (is_string($value) && is_numeric($value)) {
1735 21
            $value = (float) $value;
1736
        }
1737 76
        if (!is_numeric($value)) {
1738 6
            throw new InvalidArgumentException("'$value' is not a numeric value.");
1739
        }
1740
1741 70
        return $value;
1742
    }
1743
1744
    /**
1745
     * Creates a number formatter based on the given type and format.
1746
     *
1747
     * You may override this method to create a number formatter based on patterns.
1748
     *
1749
     * @param int $style the type of the number formatter.
1750
     * Values: NumberFormatter::DECIMAL, ::CURRENCY, ::PERCENT, ::SCIENTIFIC, ::SPELLOUT, ::ORDINAL
1751
     * ::DURATION, ::PATTERN_RULEBASED, ::DEFAULT_STYLE, ::IGNORE
1752
     * @param int $decimals the number of digits after the decimal point.
1753
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1754
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1755
     * @return NumberFormatter the created formatter instance
1756
     */
1757 62
    protected function createNumberFormatter($style, $decimals = null, $options = [], $textOptions = [])
1758
    {
1759 62
        $formatter = new NumberFormatter($this->locale, $style);
1760
1761
        // set text attributes
1762 62
        foreach ($this->numberFormatterTextOptions as $name => $attribute) {
1763 2
            $formatter->setTextAttribute($name, $attribute);
1764
        }
1765 62
        foreach ($textOptions as $name => $attribute) {
1766 7
            $formatter->setTextAttribute($name, $attribute);
1767
        }
1768
1769
        // set attributes
1770 62
        foreach ($this->numberFormatterOptions as $name => $value) {
1771 9
            $formatter->setAttribute($name, $value);
1772
        }
1773 62
        foreach ($options as $name => $value) {
1774 51
            $formatter->setAttribute($name, $value);
1775
        }
1776 62
        if ($decimals !== null) {
1777 26
            $formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimals);
1778 26
            $formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimals);
1779
        }
1780
1781
        // set symbols
1782 62
        if ($this->decimalSeparator !== null) {
1783 4
            $formatter->setSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL, $this->decimalSeparator);
1784
        }
1785 62
        if ($this->thousandSeparator !== null) {
1786 51
            $formatter->setSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL, $this->thousandSeparator);
1787 51
            $formatter->setSymbol(NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL, $this->thousandSeparator);
1788
        }
1789 62
        foreach ($this->numberFormatterSymbols as $name => $symbol) {
1790 2
            $formatter->setSymbol($name, $symbol);
1791
        }
1792
1793 62
        return $formatter;
1794
    }
1795
1796
    /**
1797
     * Checks if string representations of given value and its normalized version are different.
1798
     * @param string|float|int $value
1799
     * @param float|int $normalizedValue
1800
     * @return bool
1801
     * @since 2.0.16
1802
     */
1803 70
    protected function isNormalizedValueMispresented($value, $normalizedValue)
1804
    {
1805 70
        if (empty($value)) {
1806 18
            $value = 0;
1807
        }
1808
1809 70
        return (string) $normalizedValue !== $this->normalizeNumericStringValue((string) $value);
1810
    }
1811
1812
    /**
1813
     * Normalizes a numeric string value.
1814
     * @param string $value
1815
     * @return string the normalized number value as a string
1816
     * @since 2.0.16
1817
     */
1818 70
    protected function normalizeNumericStringValue($value)
1819
    {
1820 70
        $powerPosition = strrpos($value, 'E');
1821 70
        if ($powerPosition !== false) {
1822 4
            $valuePart = substr($value, 0, $powerPosition);
1823 4
            $powerPart = substr($value, $powerPosition + 1);
1824
        } else {
1825 70
            $powerPart = null;
1826 70
            $valuePart = $value;
1827
        }
1828
1829 70
        $separatorPosition = strrpos($valuePart, '.');
1830
1831 70
        if ($separatorPosition !== false) {
1832 39
            $integerPart = substr($valuePart, 0, $separatorPosition);
1833 39
            $fractionalPart = substr($valuePart, $separatorPosition + 1);
1834
        } else {
1835 50
            $integerPart = $valuePart;
1836 50
            $fractionalPart = null;
1837
        }
1838
1839
        // truncate insignificant zeros, keep minus
1840 70
        $integerPart = preg_replace('/^\+?(-?)0*(\d+)$/', '$1$2', $integerPart);
1841
        // for zeros only leave one zero, keep minus
1842 70
        $integerPart = preg_replace('/^\+?(-?)0*$/', '${1}0', $integerPart);
1843
1844 70
        if ($fractionalPart !== null) {
1845
            // truncate insignificant zeros
1846 39
            $fractionalPart = rtrim($fractionalPart, '0');
1847
1848 39
            if (empty($fractionalPart)) {
1849 7
                $fractionalPart = $powerPart !== null ? '0' : null;
1850
            }
1851
        }
1852
1853 70
        $normalizedValue = $integerPart;
1854 70
        if ($fractionalPart !== null) {
1855 38
            $normalizedValue .= '.' . $fractionalPart;
1856 50
        } elseif ($normalizedValue === '-0') {
1857
            $normalizedValue = '0';
1858
        }
1859
1860 70
        if ($powerPart !== null) {
1861 4
            $normalizedValue .= 'E' . $powerPart;
1862
        }
1863
1864 70
        return $normalizedValue;
1865
    }
1866
1867
    /**
1868
     * Fallback for formatting value as a decimal number.
1869
     *
1870
     * Property [[decimalSeparator]] will be used to represent the decimal point. The value is rounded automatically
1871
     * to the defined decimal digits.
1872
     *
1873
     * @param string|int|float $value the value to be formatted.
1874
     * @param int $decimals the number of digits after the decimal point. The default value is `2`.
1875
     * @return string the formatted result.
1876
     * @see decimalSeparator
1877
     * @see thousandSeparator
1878
     * @since 2.0.16
1879
     */
1880 11
    protected function asDecimalStringFallback($value, $decimals = 2)
1881
    {
1882 11
        if (empty($value)) {
1883
            $value = 0;
1884
        }
1885
1886 11
        $value = $this->normalizeNumericStringValue((string) $value);
1887
1888 11
        $separatorPosition = strrpos($value, '.');
1889
1890 11
        if ($separatorPosition !== false) {
1891 6
            $integerPart = substr($value, 0, $separatorPosition);
1892 6
            $fractionalPart = substr($value, $separatorPosition + 1);
1893
        } else {
1894 11
            $integerPart = $value;
1895 11
            $fractionalPart = null;
1896
        }
1897
1898 11
        $decimalOutput = '';
1899
1900 11
        if ($decimals === null) {
0 ignored issues
show
introduced by
The condition $decimals === null is always false.
Loading history...
1901 2
            $decimals = 2;
1902
        }
1903
1904 11
        $carry = 0;
1905
1906 11
        if ($decimals > 0) {
1907 6
            $decimalSeparator = $this->decimalSeparator;
1908 6
            if ($this->decimalSeparator === null) {
1909 3
                $decimalSeparator = '.';
1910
            }
1911
1912 6
            if ($fractionalPart === null) {
1913 4
                $fractionalPart = str_repeat('0', $decimals);
1914 6
            } elseif (strlen($fractionalPart) > $decimals) {
1915 4
                $cursor = $decimals;
1916
1917
                // checking if fractional part must be rounded
1918 4
                if ((int) substr($fractionalPart, $cursor, 1) >= 5) {
1919
                    while (--$cursor >= 0) {
1920
                        $carry = 0;
1921
1922
                        $oneUp = (int) substr($fractionalPart, $cursor, 1) + 1;
1923
                        if ($oneUp === 10) {
1924
                            $oneUp = 0;
1925
                            $carry = 1;
1926
                        }
1927
1928
                        $fractionalPart = substr($fractionalPart, 0, $cursor) . $oneUp . substr($fractionalPart, $cursor + 1);
1929
1930
                        if ($carry === 0) {
1931
                            break;
1932
                        }
1933
                    }
1934
                }
1935
1936 4
                $fractionalPart = substr($fractionalPart, 0, $decimals);
1937 2
            } elseif (strlen($fractionalPart) < $decimals) {
1938 2
                $fractionalPart = str_pad($fractionalPart, $decimals, '0');
1939
            }
1940
1941 6
            $decimalOutput .= $decimalSeparator . $fractionalPart;
1942
        }
1943
1944
        // checking if integer part must be rounded
1945 11
        if ($carry || ($decimals === 0 && $fractionalPart !== null && (int) substr($fractionalPart, 0, 1) >= 5)) {
1946 4
            $integerPartLength = strlen($integerPart);
1947 4
            $cursor = 0;
1948
1949 4
            while (++$cursor <= $integerPartLength) {
1950 4
                $carry = 0;
1951
1952 4
                $oneUp = (int) substr($integerPart, -$cursor, 1) + 1;
1953 4
                if ($oneUp === 10) {
1954
                    $oneUp = 0;
1955
                    $carry = 1;
1956
                }
1957
1958 4
                $integerPart = substr($integerPart, 0, -$cursor) . $oneUp . substr($integerPart, $integerPartLength - $cursor + 1);
1959
1960 4
                if ($carry === 0) {
1961 4
                    break;
1962
                }
1963
            }
1964 4
            if ($carry === 1) {
1965
                $integerPart = '1' . $integerPart;
1966
            }
1967
        }
1968
1969 11
        if (strlen($integerPart) > 3) {
1970 11
            $thousandSeparator = $this->thousandSeparator;
1971 11
            if ($thousandSeparator === null) {
0 ignored issues
show
introduced by
The condition $thousandSeparator === null is always false.
Loading history...
1972 8
                $thousandSeparator = ',';
1973
            }
1974
1975 11
            $integerPart = strrev(implode(',', str_split(strrev($integerPart), 3)));
1976 11
            if ($thousandSeparator !== ',') {
1977 11
                $integerPart = str_replace(',', $thousandSeparator, $integerPart);
1978
            }
1979
        }
1980
1981 11
        return $integerPart . $decimalOutput;
1982
    }
1983
1984
    /**
1985
     * Fallback for formatting value as an integer number by removing any decimal digits without rounding.
1986
     *
1987
     * @param string|int|float $value the value to be formatted.
1988
     * @return string the formatted result.
1989
     * @since 2.0.16
1990
     */
1991 5
    protected function asIntegerStringFallback($value)
1992
    {
1993 5
        if (empty($value)) {
1994
            $value = 0;
1995
        }
1996
1997 5
        $value = $this->normalizeNumericStringValue((string) $value);
1998 5
        $separatorPosition = strrpos($value, '.');
1999
2000 5
        if ($separatorPosition !== false) {
2001 5
            $integerPart = substr($value, 0, $separatorPosition);
2002
        } else {
2003 5
            $integerPart = $value;
2004
        }
2005
2006 5
        return $this->asDecimalStringFallback($integerPart, 0);
2007
    }
2008
2009
    /**
2010
     * Fallback for formatting value as a percent number with "%" sign.
2011
     *
2012
     * Property [[decimalSeparator]] will be used to represent the decimal point. The value is rounded automatically
2013
     * to the defined decimal digits.
2014
     *
2015
     * @param string|int|float $value the value to be formatted.
2016
     * @param int $decimals the number of digits after the decimal point. The default value is `0`.
2017
     * @return string the formatted result.
2018
     * @since 2.0.16
2019
     */
2020 2
    protected function asPercentStringFallback($value, $decimals = null)
2021
    {
2022 2
        if (empty($value)) {
2023
            $value = 0;
2024
        }
2025
2026 2
        if ($decimals === null) {
2027 2
            $decimals = 0;
2028
        }
2029
2030 2
        $value = $this->normalizeNumericStringValue((string) $value);
2031 2
        $separatorPosition = strrpos($value, '.');
2032
2033 2
        if ($separatorPosition !== false) {
2034 2
            $integerPart = substr($value, 0, $separatorPosition);
2035 2
            $fractionalPart = str_pad(substr($value, $separatorPosition + 1), 2, '0');
2036
2037 2
            $integerPart .= substr($fractionalPart, 0, 2);
2038 2
            $fractionalPart = substr($fractionalPart, 2);
2039
2040 2
            if ($fractionalPart === '') {
2041
                $multipliedValue = $integerPart;
2042
            } else {
2043 2
                $multipliedValue = $integerPart . '.' . $fractionalPart;
2044
            }
2045
        } else {
2046 2
            $multipliedValue = $value . '00';
2047
        }
2048
2049 2
        return $this->asDecimalStringFallback($multipliedValue, $decimals) . '%';
2050
    }
2051
2052
    /**
2053
     * Fallback for formatting value as a currency number.
2054
     *
2055
     * @param string|int|float $value the value to be formatted.
2056
     * @param string $currency the 3-letter ISO 4217 currency code indicating the currency to use.
2057
     * If null, [[currencyCode]] will be used.
2058
     * @return string the formatted result.
2059
     * @throws InvalidConfigException if no currency is given and [[currencyCode]] is not defined.
2060
     * @since 2.0.16
2061
     */
2062 3
    protected function asCurrencyStringFallback($value, $currency = null)
2063
    {
2064 3
        if ($currency === null) {
2065 2
            if ($this->currencyCode === null) {
2066 1
                throw new InvalidConfigException('The default currency code for the formatter is not defined.');
2067
            }
2068 1
            $currency = $this->currencyCode;
2069
        }
2070
2071 2
        return $currency . ' ' . $this->asDecimalStringFallback($value, 2);
2072
    }
2073
}
2074