Completed
Push — master ( 324884...59e99a )
by Alexander
41:49 queued 38:19
created

Formatter::asPercent()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6.0106

Importance

Changes 0
Metric Value
dl 0
loc 28
ccs 14
cts 15
cp 0.9333
rs 8.8497
c 0
b 0
f 0
cc 6
nc 6
nop 4
crap 6.0106
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](http://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()](http://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](http://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 time zone to use for formatting time and date values.
101
     *
102
     * This can be any value that may be passed to [date_default_timezone_set()](http://www.php.net/manual/en/function.date-default-timezone-set.php)
103
     * e.g. `UTC`, `Europe/Berlin` or `America/Chicago`.
104
     * Refer to the [php manual](http://www.php.net/manual/en/timezones.php) for available time zones.
105
     * If this property is not set, [[\yii\base\Application::timeZone]] will be used.
106
     *
107
     * 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.
108
     * If you store your data in a different time zone in the database, you have to adjust [[defaultTimeZone]] accordingly.
109
     */
110
    public $timeZone;
111
    /**
112
     * @var string the time zone that is assumed for input values if they do not include a time zone explicitly.
113
     *
114
     * The value must be a valid time zone identifier, e.g. `UTC`, `Europe/Berlin` or `America/Chicago`.
115
     * Please refer to the [php manual](http://www.php.net/manual/en/timezones.php) for available time zones.
116
     *
117
     * It defaults to `UTC` so you only have to adjust this value if you store datetime values in another time zone in your database.
118
     *
119
     * Note that a UNIX timestamp is always in UTC by its definition. That means that specifying a default time zone different from
120
     * UTC has no effect on date values given as UNIX timestamp.
121
     *
122
     * @since 2.0.1
123
     */
124
    public $defaultTimeZone = 'UTC';
125
    /**
126
     * @var string the default format string to be used to format a [[asDate()|date]].
127
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
128
     *
129
     * 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).
130
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
131
     * PHP [date()](http://php.net/manual/en/function.date.php)-function.
132
     *
133
     * For example:
134
     *
135
     * ```php
136
     * 'MM/dd/yyyy' // date in ICU format
137
     * 'php:m/d/Y' // the same date in PHP format
138
     * ```
139
     */
140
    public $dateFormat = 'medium';
141
    /**
142
     * @var string the default format string to be used to format a [[asTime()|time]].
143
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
144
     *
145
     * 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).
146
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
147
     * PHP [date()](http://php.net/manual/en/function.date.php)-function.
148
     *
149
     * For example:
150
     *
151
     * ```php
152
     * 'HH:mm:ss' // time in ICU format
153
     * 'php:H:i:s' // the same time in PHP format
154
     * ```
155
     */
156
    public $timeFormat = 'medium';
157
    /**
158
     * @var string the default format string to be used to format a [[asDatetime()|date and time]].
159
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
160
     *
161
     * 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).
162
     *
163
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
164
     * PHP [date()](http://php.net/manual/en/function.date.php)-function.
165
     *
166
     * For example:
167
     *
168
     * ```php
169
     * 'MM/dd/yyyy HH:mm:ss' // date and time in ICU format
170
     * 'php:m/d/Y H:i:s' // the same date and time in PHP format
171
     * ```
172
     */
173
    public $datetimeFormat = 'medium';
174
    /**
175
     * @var \IntlCalendar|int|null the calendar to be used for date formatting. The value of this property will be directly
176
     * passed to the [constructor of the `IntlDateFormatter` class](http://php.net/manual/en/intldateformatter.create.php).
177
     *
178
     * Defaults to `null`, which means the Gregorian calendar will be used. You may also explicitly pass the constant
179
     * `\IntlDateFormatter::GREGORIAN` for Gregorian calendar.
180
     *
181
     * To use an alternative calendar like for example the [Jalali calendar](https://en.wikipedia.org/wiki/Jalali_calendar),
182
     * set this property to `\IntlDateFormatter::TRADITIONAL`.
183
     * The calendar must then be specified in the [[locale]], for example for the persian calendar the configuration for the formatter would be:
184
     *
185
     * ```php
186
     * 'formatter' => [
187
     *     'locale' => 'fa_IR@calendar=persian',
188
     *     'calendar' => \IntlDateFormatter::TRADITIONAL,
189
     * ],
190
     * ```
191
     *
192
     * Available calendar names can be found in the [ICU manual](http://userguide.icu-project.org/datetime/calendar).
193
     *
194
     * Since PHP 5.5 you may also use an instance of the [[\IntlCalendar]] class.
195
     * Check the [PHP manual](http://php.net/manual/en/intldateformatter.create.php) for more details.
196
     *
197
     * If the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, setting this property will have no effect.
198
     *
199
     * @see http://php.net/manual/en/intldateformatter.create.php
200
     * @see http://php.net/manual/en/class.intldateformatter.php#intl.intldateformatter-constants.calendartypes
201
     * @see http://php.net/manual/en/class.intlcalendar.php
202
     * @since 2.0.7
203
     */
204
    public $calendar;
205
    /**
206
     * @var string the character displayed as the decimal point when formatting a number.
207
     * If not set, the decimal separator corresponding to [[locale]] will be used.
208
     * If [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value is '.'.
209
     */
210
    public $decimalSeparator;
211
    /**
212
     * @var string the character displayed as the thousands separator (also called grouping separator) character when formatting a number.
213
     * If not set, the thousand separator corresponding to [[locale]] will be used.
214
     * If [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value is ','.
215
     */
216
    public $thousandSeparator;
217
    /**
218
     * @var array a list of name value pairs that are passed to the
219
     * intl [NumberFormatter::setAttribute()](http://php.net/manual/en/numberformatter.setattribute.php) method of all
220
     * the number formatter objects created by [[createNumberFormatter()]].
221
     * This property takes only effect if the [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed.
222
     *
223
     * Please refer to the [PHP manual](http://php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformatattribute)
224
     * for the possible options.
225
     *
226
     * For example to adjust the maximum and minimum value of fraction digits you can configure this property like the following:
227
     *
228
     * ```php
229
     * [
230
     *     NumberFormatter::MIN_FRACTION_DIGITS => 0,
231
     *     NumberFormatter::MAX_FRACTION_DIGITS => 2,
232
     * ]
233
     * ```
234
     */
235
    public $numberFormatterOptions = [];
236
    /**
237
     * @var array a list of name value pairs that are passed to the
238
     * intl [NumberFormatter::setTextAttribute()](http://php.net/manual/en/numberformatter.settextattribute.php) method of all
239
     * the number formatter objects created by [[createNumberFormatter()]].
240
     * This property takes only effect if the [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed.
241
     *
242
     * Please refer to the [PHP manual](http://php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformattextattribute)
243
     * for the possible options.
244
     *
245
     * For example to change the minus sign for negative numbers you can configure this property like the following:
246
     *
247
     * ```php
248
     * [
249
     *     NumberFormatter::NEGATIVE_PREFIX => 'MINUS',
250
     * ]
251
     * ```
252
     */
253
    public $numberFormatterTextOptions = [];
254
    /**
255
     * @var array a list of name value pairs that are passed to the
256
     * intl [NumberFormatter::setSymbol()](http://php.net/manual/en/numberformatter.setsymbol.php) method of all
257
     * the number formatter objects created by [[createNumberFormatter()]].
258
     * This property takes only effect if the [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed.
259
     *
260
     * Please refer to the [PHP manual](http://php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformatsymbol)
261
     * for the possible options.
262
     *
263
     * For example to choose a custom currency symbol, e.g. [U+20BD](http://unicode-table.com/en/20BD/) instead of `руб.` for Russian Ruble:
264
     *
265
     * ```php
266
     * [
267
     *     NumberFormatter::CURRENCY_SYMBOL => '₽',
268
     * ]
269
     * ```
270
     *
271
     * @since 2.0.4
272
     */
273
    public $numberFormatterSymbols = [];
274
    /**
275
     * @var string the 3-letter ISO 4217 currency code indicating the default currency to use for [[asCurrency]].
276
     * If not set, the currency code corresponding to [[locale]] will be used.
277
     * Note that in this case the [[locale]] has to be specified with a country code, e.g. `en-US` otherwise it
278
     * is not possible to determine the default currency.
279
     */
280
    public $currencyCode;
281
    /**
282
     * @var int the base at which a kilobyte is calculated (1000 or 1024 bytes per kilobyte), used by [[asSize]] and [[asShortSize]].
283
     * Defaults to 1024.
284
     */
285
    public $sizeFormatBase = 1024;
286
    /**
287
     * @var string default system of measure units. Defaults to [[UNIT_SYSTEM_METRIC]].
288
     * Possible values:
289
     *  - [[UNIT_SYSTEM_METRIC]]
290
     *  - [[UNIT_SYSTEM_IMPERIAL]]
291
     *
292
     * @see asLength
293
     * @see asWeight
294
     * @since 2.0.13
295
     */
296
    public $systemOfUnits = self::UNIT_SYSTEM_METRIC;
297
    /**
298
     * @var array configuration of weight and length measurement units.
299
     * This array contains the most usable measurement units, but you can change it
300
     * in case you have some special requirements.
301
     *
302
     * For example, you can add smaller measure unit:
303
     *
304
     * ```php
305
     * $this->measureUnits[self::UNIT_LENGTH][self::UNIT_SYSTEM_METRIC] = [
306
     *     'nanometer' => 0.000001
307
     * ]
308
     * ```
309
     * @see asLength
310
     * @see asWeight
311
     * @since 2.0.13
312
     */
313
    public $measureUnits = [
314
        self::UNIT_LENGTH => [
315
            self::UNIT_SYSTEM_IMPERIAL => [
316
                'inch' => 1,
317
                'foot' => 12,
318
                'yard' => 36,
319
                'chain' => 792,
320
                'furlong' => 7920,
321
                'mile' => 63360,
322
            ],
323
            self::UNIT_SYSTEM_METRIC => [
324
                'millimeter' => 1,
325
                'centimeter' => 10,
326
                'meter' => 1000,
327
                'kilometer' => 1000000,
328
            ],
329
        ],
330
        self::UNIT_WEIGHT => [
331
            self::UNIT_SYSTEM_IMPERIAL => [
332
                'grain' => 1,
333
                'drachm' => 27.34375,
334
                'ounce' => 437.5,
335
                'pound' => 7000,
336
                'stone' => 98000,
337
                'quarter' => 196000,
338
                'hundredweight' => 784000,
339
                'ton' => 15680000,
340
            ],
341
            self::UNIT_SYSTEM_METRIC => [
342
                'gram' => 1,
343
                'kilogram' => 1000,
344
                'ton' => 1000000,
345
            ],
346
        ],
347
    ];
348
    /**
349
     * @var array The base units that are used as multipliers for smallest possible unit from [[measureUnits]].
350
     * @since 2.0.13
351
     */
352
    public $baseUnits = [
353
        self::UNIT_LENGTH => [
354
            self::UNIT_SYSTEM_IMPERIAL => 12, // 1 feet = 12 inches
355
            self::UNIT_SYSTEM_METRIC => 1000, // 1 meter = 1000 millimeters
356
        ],
357
        self::UNIT_WEIGHT => [
358
            self::UNIT_SYSTEM_IMPERIAL => 7000, // 1 pound = 7000 grains
359
            self::UNIT_SYSTEM_METRIC => 1000, // 1 kilogram = 1000 grams
360
        ],
361
    ];
362
363
    /**
364
     * @var bool whether the [PHP intl extension](http://php.net/manual/en/book.intl.php) is loaded.
365
     */
366
    private $_intlLoaded = false;
367
    /**
368
     * @var \ResourceBundle cached ResourceBundle object used to read unit translations
369
     */
370
    private $_resourceBundle;
371
    /**
372
     * @var array cached unit translation patterns
373
     */
374
    private $_unitMessages = [];
375
376
377
    /**
378
     * {@inheritdoc}
379
     */
380 255
    public function init()
381
    {
382 255
        if ($this->timeZone === null) {
383 255
            $this->timeZone = Yii::$app->timeZone;
384
        }
385 255
        if ($this->locale === null) {
386 33
            $this->locale = Yii::$app->language;
387
        }
388 255
        if ($this->booleanFormat === null) {
389 255
            $this->booleanFormat = [Yii::t('yii', 'No', [], $this->locale), Yii::t('yii', 'Yes', [], $this->locale)];
390
        }
391 255
        if ($this->nullDisplay === null) {
392 255
            $this->nullDisplay = '<span class="not-set">' . Yii::t('yii', '(not set)', [], $this->locale) . '</span>';
393
        }
394 255
        $this->_intlLoaded = extension_loaded('intl');
395 255
        if (!$this->_intlLoaded) {
396 119
            if ($this->decimalSeparator === null) {
397 119
                $this->decimalSeparator = '.';
398
            }
399 119
            if ($this->thousandSeparator === null) {
400 119
                $this->thousandSeparator = ',';
401
            }
402
        }
403 255
    }
404
405
    /**
406
     * Formats the value based on the given format type.
407
     * This method will call one of the "as" methods available in this class to do the formatting.
408
     * For type "xyz", the method "asXyz" will be used. For example, if the format is "html",
409
     * then [[asHtml()]] will be used. Format names are case insensitive.
410
     * @param mixed $value the value to be formatted.
411
     * @param string|array|Closure $format the format of the value, e.g., "html", "text" or an anonymous function
412
     * returning the formatted value.
413
     *
414
     * To specify additional parameters of the formatting method, you may use an array.
415
     * The first element of the array specifies the format name, while the rest of the elements will be used as the
416
     * parameters to the formatting method. For example, a format of `['date', 'Y-m-d']` will cause the invocation
417
     * of `asDate($value, 'Y-m-d')`.
418
     *
419
     * The anonymous function signature should be: `function($value, $formatter)`,
420
     * where `$value` is the value that should be formatted and `$formatter` is an instance of the Formatter class,
421
     * which can be used to call other formatting functions.
422
     * The possibility to use an anonymous function is available since version 2.0.13.
423
     * @return string the formatting result.
424
     * @throws InvalidArgumentException if the format type is not supported by this class.
425
     */
426 11
    public function format($value, $format)
427
    {
428 11
        if ($format instanceof Closure) {
429
            return call_user_func($format, $value, $this);
430 11
        } elseif (is_array($format)) {
431 7
            if (!isset($format[0])) {
432
                throw new InvalidArgumentException('The $format array must contain at least one element.');
433
            }
434 7
            $f = $format[0];
435 7
            $format[0] = $value;
436 7
            $params = $format;
437 7
            $format = $f;
438
        } else {
439 6
            $params = [$value];
440
        }
441 11
        $method = 'as' . $format;
442 11
        if ($this->hasMethod($method)) {
443 11
            return call_user_func_array([$this, $method], $params);
444
        }
445
446 2
        throw new InvalidArgumentException("Unknown format type: $format");
447
    }
448
449
450
    // simple formats
451
452
453
    /**
454
     * Formats the value as is without any formatting.
455
     * This method simply returns back the parameter without any format.
456
     * The only exception is a `null` value which will be formatted using [[nullDisplay]].
457
     * @param mixed $value the value to be formatted.
458
     * @return string the formatted result.
459
     */
460 1
    public function asRaw($value)
461
    {
462 1
        if ($value === null) {
463 1
            return $this->nullDisplay;
464
        }
465
466 1
        return $value;
467
    }
468
469
    /**
470
     * Formats the value as an HTML-encoded plain text.
471
     * @param string $value the value to be formatted.
472
     * @return string the formatted result.
473
     */
474 5
    public function asText($value)
475
    {
476 5
        if ($value === null) {
477 2
            return $this->nullDisplay;
478
        }
479
480 5
        return Html::encode($value);
481
    }
482
483
    /**
484
     * Formats the value as an HTML-encoded plain text with newlines converted into breaks.
485
     * @param string $value the value to be formatted.
486
     * @return string the formatted result.
487
     */
488 1
    public function asNtext($value)
489
    {
490 1
        if ($value === null) {
491 1
            return $this->nullDisplay;
492
        }
493
494 1
        return nl2br(Html::encode($value));
495
    }
496
497
    /**
498
     * Formats the value as HTML-encoded text paragraphs.
499
     * Each text paragraph is enclosed within a `<p>` tag.
500
     * One or multiple consecutive empty lines divide two paragraphs.
501
     * @param string $value the value to be formatted.
502
     * @return string the formatted result.
503
     */
504 1
    public function asParagraphs($value)
505
    {
506 1
        if ($value === null) {
507 1
            return $this->nullDisplay;
508
        }
509
510 1
        return str_replace('<p></p>', '', '<p>' . preg_replace('/\R{2,}/u', "</p>\n<p>", Html::encode($value)) . '</p>');
511
    }
512
513
    /**
514
     * Formats the value as HTML text.
515
     * The value will be purified using [[HtmlPurifier]] to avoid XSS attacks.
516
     * Use [[asRaw()]] if you do not want any purification of the value.
517
     * @param string $value the value to be formatted.
518
     * @param array|null $config the configuration for the HTMLPurifier class.
519
     * @return string the formatted result.
520
     */
521
    public function asHtml($value, $config = null)
522
    {
523
        if ($value === null) {
524
            return $this->nullDisplay;
525
        }
526
527
        return HtmlPurifier::process($value, $config);
528
    }
529
530
    /**
531
     * Formats the value as a mailto link.
532
     * @param string $value the value to be formatted.
533
     * @param array $options the tag options in terms of name-value pairs. See [[Html::mailto()]].
534
     * @return string the formatted result.
535
     */
536 1
    public function asEmail($value, $options = [])
537
    {
538 1
        if ($value === null) {
539 1
            return $this->nullDisplay;
540
        }
541
542 1
        return Html::mailto(Html::encode($value), $value, $options);
543
    }
544
545
    /**
546
     * Formats the value as an image tag.
547
     * @param mixed $value the value to be formatted.
548
     * @param array $options the tag options in terms of name-value pairs. See [[Html::img()]].
549
     * @return string the formatted result.
550
     */
551 1
    public function asImage($value, $options = [])
552
    {
553 1
        if ($value === null) {
554 1
            return $this->nullDisplay;
555
        }
556
557 1
        return Html::img($value, $options);
558
    }
559
560
    /**
561
     * Formats the value as a hyperlink.
562
     * @param mixed $value the value to be formatted.
563
     * @param array $options the tag options in terms of name-value pairs. See [[Html::a()]].
564
     * @return string the formatted result.
565
     */
566 1
    public function asUrl($value, $options = [])
567
    {
568 1
        if ($value === null) {
569 1
            return $this->nullDisplay;
570
        }
571 1
        $url = $value;
572 1
        if (strpos($url, '://') === false) {
573 1
            $url = 'http://' . $url;
574
        }
575
576 1
        return Html::a(Html::encode($value), $url, $options);
577
    }
578
579
    /**
580
     * Formats the value as a boolean.
581
     * @param mixed $value the value to be formatted.
582
     * @return string the formatted result.
583
     * @see booleanFormat
584
     */
585 1
    public function asBoolean($value)
586
    {
587 1
        if ($value === null) {
588 1
            return $this->nullDisplay;
589
        }
590
591 1
        return $value ? $this->booleanFormat[1] : $this->booleanFormat[0];
592
    }
593
594
595
    // date and time formats
596
597
598
    /**
599
     * Formats the value as a date.
600
     * @param int|string|DateTime $value the value to be formatted. The following
601
     * types of value are supported:
602
     *
603
     * - an integer representing a UNIX timestamp. A UNIX timestamp is always in UTC by its definition.
604
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
605
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
606
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object. You may set the time zone
607
     *   for the DateTime object to specify the source time zone.
608
     *
609
     * The formatter will convert date values according to [[timeZone]] before formatting it.
610
     * If no timezone conversion should be performed, you need to set [[defaultTimeZone]] and [[timeZone]] to the same value.
611
     * Also no conversion will be performed on values that have no time information, e.g. `"2017-06-05"`.
612
     *
613
     * @param string $format the format used to convert the value into a date string.
614
     * If null, [[dateFormat]] will be used.
615
     *
616
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
617
     * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
618
     *
619
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
620
     * PHP [date()](http://php.net/manual/en/function.date.php)-function.
621
     *
622
     * @return string the formatted result.
623
     * @throws InvalidArgumentException if the input value can not be evaluated as a date value.
624
     * @throws InvalidConfigException if the date format is invalid.
625
     * @see dateFormat
626
     */
627 168
    public function asDate($value, $format = null)
628
    {
629 168
        if ($format === null) {
630 146
            $format = $this->dateFormat;
631
        }
632
633 168
        return $this->formatDateTimeValue($value, $format, 'date');
634
    }
635
636
    /**
637
     * Formats the value as a time.
638
     * @param int|string|DateTime $value the value to be formatted. The following
639
     * types of value are supported:
640
     *
641
     * - an integer representing a UNIX timestamp. A UNIX timestamp is always in UTC by its definition.
642
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
643
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
644
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object. You may set the time zone
645
     *   for the DateTime object to specify the source time zone.
646
     *
647
     * The formatter will convert date values according to [[timeZone]] before formatting it.
648
     * If no timezone conversion should be performed, you need to set [[defaultTimeZone]] and [[timeZone]] to the same value.
649
     *
650
     * @param string $format the format used to convert the value into a date string.
651
     * If null, [[timeFormat]] will be used.
652
     *
653
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
654
     * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
655
     *
656
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
657
     * PHP [date()](http://php.net/manual/en/function.date.php)-function.
658
     *
659
     * @return string the formatted result.
660
     * @throws InvalidArgumentException if the input value can not be evaluated as a date value.
661
     * @throws InvalidConfigException if the date format is invalid.
662
     * @see timeFormat
663
     */
664 148
    public function asTime($value, $format = null)
665
    {
666 148
        if ($format === null) {
667 144
            $format = $this->timeFormat;
668
        }
669
670 148
        return $this->formatDateTimeValue($value, $format, 'time');
671
    }
672
673
    /**
674
     * Formats the value as a datetime.
675
     * @param int|string|DateTime $value the value to be formatted. The following
676
     * types of value are supported:
677
     *
678
     * - an integer representing a UNIX timestamp. A UNIX timestamp is always in UTC by its definition.
679
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
680
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
681
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object. You may set the time zone
682
     *   for the DateTime object to specify the source time zone.
683
     *
684
     * The formatter will convert date values according to [[timeZone]] before formatting it.
685
     * If no timezone conversion should be performed, you need to set [[defaultTimeZone]] and [[timeZone]] to the same value.
686
     *
687
     * @param string $format the format used to convert the value into a date string.
688
     * If null, [[datetimeFormat]] will be used.
689
     *
690
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
691
     * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
692
     *
693
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
694
     * PHP [date()](http://php.net/manual/en/function.date.php)-function.
695
     *
696
     * @return string the formatted result.
697
     * @throws InvalidArgumentException if the input value can not be evaluated as a date value.
698
     * @throws InvalidConfigException if the date format is invalid.
699
     * @see datetimeFormat
700
     */
701 153
    public function asDatetime($value, $format = null)
702
    {
703 153
        if ($format === null) {
704 144
            $format = $this->datetimeFormat;
705
        }
706
707 153
        return $this->formatDateTimeValue($value, $format, 'datetime');
708
    }
709
710
    /**
711
     * @var array map of short format names to IntlDateFormatter constant values.
712
     */
713
    private $_dateFormats = [
714
        'short' => 3, // IntlDateFormatter::SHORT,
715
        'medium' => 2, // IntlDateFormatter::MEDIUM,
716
        'long' => 1, // IntlDateFormatter::LONG,
717
        'full' => 0, // IntlDateFormatter::FULL,
718
    ];
719
720
    /**
721
     * @param int|string|DateTime $value the value to be formatted. The following
722
     * types of value are supported:
723
     *
724
     * - an integer representing a UNIX timestamp
725
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
726
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
727
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object
728
     *
729
     * @param string $format the format used to convert the value into a date string.
730
     * @param string $type 'date', 'time', or 'datetime'.
731
     * @throws InvalidConfigException if the date format is invalid.
732
     * @return string the formatted result.
733
     */
734 177
    private function formatDateTimeValue($value, $format, $type)
735
    {
736 177
        $timeZone = $this->timeZone;
737
        // avoid time zone conversion for date-only and time-only values
738 177
        if ($type === 'date' || $type === 'time') {
739 170
            list($timestamp, $hasTimeInfo, $hasDateInfo) = $this->normalizeDatetimeValue($value, true);
740 168
            if ($type === 'date' && !$hasTimeInfo || $type === 'time' && !$hasDateInfo) {
741 168
                $timeZone = $this->defaultTimeZone;
742
            }
743
        } else {
744 153
            $timestamp = $this->normalizeDatetimeValue($value);
745
        }
746 175
        if ($timestamp === null) {
747 6
            return $this->nullDisplay;
748
        }
749
750
        // intl does not work with dates >=2038 or <=1901 on 32bit machines, fall back to PHP
751 175
        $year = $timestamp->format('Y');
752 175
        if ($this->_intlLoaded && !(PHP_INT_SIZE === 4 && ($year <= 1901 || $year >= 2038))) {
753 85
            if (strncmp($format, 'php:', 4) === 0) {
754 6
                $format = FormatConverter::convertDatePhpToIcu(substr($format, 4));
755
            }
756 85
            if (isset($this->_dateFormats[$format])) {
757 3
                if ($type === 'date') {
758 1
                    $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], IntlDateFormatter::NONE, $timeZone, $this->calendar);
759 2
                } elseif ($type === 'time') {
760 1
                    $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, $this->_dateFormats[$format], $timeZone, $this->calendar);
761
                } else {
762 3
                    $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], $this->_dateFormats[$format], $timeZone, $this->calendar);
763
                }
764
            } else {
765 85
                $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE, $timeZone, $this->calendar, $format);
766
            }
767 85
            if ($formatter === null) {
768
                throw new InvalidConfigException(intl_get_error_message());
769
            }
770
            // make IntlDateFormatter work with DateTimeImmutable
771 85
            if ($timestamp instanceof \DateTimeImmutable) {
0 ignored issues
show
Bug introduced by
The class DateTimeImmutable does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
772 14
                $timestamp = new DateTime($timestamp->format(DateTime::ISO8601), $timestamp->getTimezone());
773
            }
774
775 85
            return $formatter->format($timestamp);
776
        }
777
778 90
        if (strncmp($format, 'php:', 4) === 0) {
779 12
            $format = substr($format, 4);
780
        } else {
781 84
            $format = FormatConverter::convertDateIcuToPhp($format, $type, $this->locale);
782
        }
783 90
        if ($timeZone != null) {
784 90
            if ($timestamp instanceof \DateTimeImmutable) {
0 ignored issues
show
Bug introduced by
The class DateTimeImmutable does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
785 13
                $timestamp = $timestamp->setTimezone(new DateTimeZone($timeZone));
786
            } else {
787 80
                $timestamp->setTimezone(new DateTimeZone($timeZone));
788
            }
789
        }
790
791 90
        return $timestamp->format($format);
792
    }
793
794
    /**
795
     * Normalizes the given datetime value as a DateTime object that can be taken by various date/time formatting methods.
796
     *
797
     * @param int|string|DateTime $value the datetime value to be normalized. The following
798
     * types of value are supported:
799
     *
800
     * - an integer representing a UNIX timestamp
801
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
802
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
803
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object
804
     *
805
     * @param bool $checkDateTimeInfo whether to also check if the date/time value has some time and date information attached.
806
     * Defaults to `false`. If `true`, the method will then return an array with the first element being the normalized
807
     * timestamp, the second a boolean indicating whether the timestamp has time information and third a boolean indicating
808
     * whether the timestamp has date information.
809
     * This parameter is available since version 2.0.1.
810
     * @return DateTime|array the normalized datetime value.
811
     * Since version 2.0.1 this may also return an array if `$checkDateTimeInfo` is true.
812
     * The first element of the array is the normalized timestamp and the second is a boolean indicating whether
813
     * the timestamp has time information or it is just a date value.
814
     * Since version 2.0.12 the array has third boolean element indicating whether the timestamp has date information
815
     * or it is just a time value.
816
     * @throws InvalidArgumentException if the input value can not be evaluated as a date value.
817
     */
818 182
    protected function normalizeDatetimeValue($value, $checkDateTimeInfo = false)
819
    {
820
        // checking for DateTime and DateTimeInterface is not redundant, DateTimeInterface is only in PHP>5.5
821 182
        if ($value === null || $value instanceof DateTime || $value instanceof DateTimeInterface) {
0 ignored issues
show
Bug introduced by
The class DateTimeInterface does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
822
            // skip any processing
823 50
            return $checkDateTimeInfo ? [$value, true, true] : $value;
824
        }
825 142
        if (empty($value)) {
826 10
            $value = 0;
827
        }
828
        try {
829 142
            if (is_numeric($value)) { // process as unix timestamp, which is always in UTC
830 32
                $timestamp = new DateTime('@' . (int) $value, new DateTimeZone('UTC'));
831 32
                return $checkDateTimeInfo ? [$timestamp, true, true] : $timestamp;
832 115
            } 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)
833 12
                return $checkDateTimeInfo ? [$timestamp, false, true] : $timestamp;
834 103
            } 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)
835 19
                return $checkDateTimeInfo ? [$timestamp, true, true] : $timestamp;
836
            }
837
            // finally try to create a DateTime object with the value
838 88
            if ($checkDateTimeInfo) {
839 86
                $timestamp = new DateTime($value, new DateTimeZone($this->defaultTimeZone));
840 84
                $info = date_parse($value);
841
                return [
842 84
                    $timestamp,
843 84
                    !($info['hour'] === false && $info['minute'] === false && $info['second'] === false),
844 84
                    !($info['year'] === false && $info['month'] === false && $info['day'] === false),
845
                ];
846
            }
847
848 86
            return new DateTime($value, new DateTimeZone($this->defaultTimeZone));
849 2
        } catch (\Exception $e) {
850 2
            throw new InvalidArgumentException("'$value' is not a valid date time value: " . $e->getMessage()
851 2
                . "\n" . print_r(DateTime::getLastErrors(), true), $e->getCode(), $e);
852
        }
853
    }
854
855
    /**
856
     * Formats a date, time or datetime in a float number as UNIX timestamp (seconds since 01-01-1970).
857
     * @param int|string|DateTime $value the value to be formatted. The following
858
     * types of value are supported:
859
     *
860
     * - an integer representing a UNIX timestamp
861
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
862
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
863
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object
864
     *
865
     * @return string the formatted result.
866
     */
867 145
    public function asTimestamp($value)
868
    {
869 145
        if ($value === null) {
870 2
            return $this->nullDisplay;
871
        }
872 145
        $timestamp = $this->normalizeDatetimeValue($value);
873 145
        return number_format($timestamp->format('U'), 0, '.', '');
874
    }
875
876
    /**
877
     * Formats the value as the time interval between a date and now in human readable form.
878
     *
879
     * This method can be used in three different ways:
880
     *
881
     * 1. Using a timestamp that is relative to `now`.
882
     * 2. Using a timestamp that is relative to the `$referenceTime`.
883
     * 3. Using a `DateInterval` object.
884
     *
885
     * @param int|string|DateTime|DateInterval $value the value to be formatted. The following
886
     * types of value are supported:
887
     *
888
     * - an integer representing a UNIX timestamp
889
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
890
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
891
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object
892
     * - a PHP DateInterval object (a positive time interval will refer to the past, a negative one to the future)
893
     *
894
     * @param int|string|DateTime $referenceTime if specified the value is used as a reference time instead of `now`
895
     * when `$value` is not a `DateInterval` object.
896
     * @return string the formatted result.
897
     * @throws InvalidArgumentException if the input value can not be evaluated as a date value.
898
     */
899 92
    public function asRelativeTime($value, $referenceTime = null)
900
    {
901 92
        if ($value === null) {
902 2
            return $this->nullDisplay;
903
        }
904
905 92
        if ($value instanceof DateInterval) {
906 2
            $interval = $value;
907
        } else {
908 92
            $timestamp = $this->normalizeDatetimeValue($value);
909
910 92
            if ($timestamp === false) {
911
                // $value is not a valid date/time value, so we try
912
                // to create a DateInterval with it
913
                try {
914
                    $interval = new DateInterval($value);
915
                } catch (\Exception $e) {
916
                    // invalid date/time and invalid interval
917
                    return $this->nullDisplay;
918
                }
919
            } else {
920 92
                $timeZone = new DateTimeZone($this->timeZone);
921
922 92
                if ($referenceTime === null) {
923
                    $dateNow = new DateTime('now', $timeZone);
924
                } else {
925 92
                    $dateNow = $this->normalizeDatetimeValue($referenceTime);
926 92
                    $dateNow->setTimezone($timeZone);
927
                }
928
929 92
                $dateThen = $timestamp->setTimezone($timeZone);
930
931 92
                $interval = $dateThen->diff($dateNow);
932
            }
933
        }
934
935 92
        if ($interval->invert) {
936 92
            if ($interval->y >= 1) {
937 2
                return Yii::t('yii', 'in {delta, plural, =1{a year} other{# years}}', ['delta' => $interval->y], $this->locale);
938
            }
939 92
            if ($interval->m >= 1) {
940 2
                return Yii::t('yii', 'in {delta, plural, =1{a month} other{# months}}', ['delta' => $interval->m], $this->locale);
941
            }
942 92
            if ($interval->d >= 1) {
943 2
                return Yii::t('yii', 'in {delta, plural, =1{a day} other{# days}}', ['delta' => $interval->d], $this->locale);
944
            }
945 92
            if ($interval->h >= 1) {
946 92
                return Yii::t('yii', 'in {delta, plural, =1{an hour} other{# hours}}', ['delta' => $interval->h], $this->locale);
947
            }
948 2
            if ($interval->i >= 1) {
949 2
                return Yii::t('yii', 'in {delta, plural, =1{a minute} other{# minutes}}', ['delta' => $interval->i], $this->locale);
950
            }
951 2
            if ($interval->s == 0) {
952
                return Yii::t('yii', 'just now', [], $this->locale);
953
            }
954
955 2
            return Yii::t('yii', 'in {delta, plural, =1{a second} other{# seconds}}', ['delta' => $interval->s], $this->locale);
956
        }
957
958 92
        if ($interval->y >= 1) {
959 2
            return Yii::t('yii', '{delta, plural, =1{a year} other{# years}} ago', ['delta' => $interval->y], $this->locale);
960
        }
961 92
        if ($interval->m >= 1) {
962 2
            return Yii::t('yii', '{delta, plural, =1{a month} other{# months}} ago', ['delta' => $interval->m], $this->locale);
963
        }
964 92
        if ($interval->d >= 1) {
965 2
            return Yii::t('yii', '{delta, plural, =1{a day} other{# days}} ago', ['delta' => $interval->d], $this->locale);
966
        }
967 92
        if ($interval->h >= 1) {
968 92
            return Yii::t('yii', '{delta, plural, =1{an hour} other{# hours}} ago', ['delta' => $interval->h], $this->locale);
969
        }
970 2
        if ($interval->i >= 1) {
971 2
            return Yii::t('yii', '{delta, plural, =1{a minute} other{# minutes}} ago', ['delta' => $interval->i], $this->locale);
972
        }
973 2
        if ($interval->s == 0) {
974 2
            return Yii::t('yii', 'just now', [], $this->locale);
975
        }
976
977 2
        return Yii::t('yii', '{delta, plural, =1{a second} other{# seconds}} ago', ['delta' => $interval->s], $this->locale);
978
    }
979
980
    /**
981
     * Represents the value as duration in human readable format.
982
     *
983
     * @param DateInterval|string|int $value the value to be formatted. Acceptable formats:
984
     *  - [DateInterval object](http://php.net/manual/ru/class.dateinterval.php)
985
     *  - integer - number of seconds. For example: value `131` represents `2 minutes, 11 seconds`
986
     *  - ISO8601 duration format. For example, all of these values represent `1 day, 2 hours, 30 minutes` duration:
987
     *    `2015-01-01T13:00:00Z/2015-01-02T13:30:00Z` - between two datetime values
988
     *    `2015-01-01T13:00:00Z/P1D2H30M` - time interval after datetime value
989
     *    `P1D2H30M/2015-01-02T13:30:00Z` - time interval before datetime value
990
     *    `P1D2H30M` - simply a date interval
991
     *    `P-1D2H30M` - a negative date interval (`-1 day, 2 hours, 30 minutes`)
992
     *
993
     * @param string $implodeString will be used to concatenate duration parts. Defaults to `, `.
994
     * @param string $negativeSign will be prefixed to the formatted duration, when it is negative. Defaults to `-`.
995
     * @return string the formatted duration.
996
     * @since 2.0.7
997
     */
998 2
    public function asDuration($value, $implodeString = ', ', $negativeSign = '-')
999
    {
1000 2
        if ($value === null) {
1001 2
            return $this->nullDisplay;
1002
        }
1003
1004 2
        if ($value instanceof DateInterval) {
1005 2
            $isNegative = $value->invert;
1006 2
            $interval = $value;
1007 2
        } elseif (is_numeric($value)) {
1008 2
            $isNegative = $value < 0;
1009 2
            $zeroDateTime = (new DateTime())->setTimestamp(0);
1010 2
            $valueDateTime = (new DateTime())->setTimestamp(abs($value));
1011 2
            $interval = $valueDateTime->diff($zeroDateTime);
1012 2
        } elseif (strncmp($value, 'P-', 2) === 0) {
1013 2
            $interval = new DateInterval('P' . substr($value, 2));
1014 2
            $isNegative = true;
1015
        } else {
1016 2
            $interval = new DateInterval($value);
1017 2
            $isNegative = $interval->invert;
1018
        }
1019
1020 2
        $parts = [];
1021 2
        if ($interval->y > 0) {
1022 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 year} other{# years}}', ['delta' => $interval->y], $this->locale);
1023
        }
1024 2
        if ($interval->m > 0) {
1025 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 month} other{# months}}', ['delta' => $interval->m], $this->locale);
1026
        }
1027 2
        if ($interval->d > 0) {
1028 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 day} other{# days}}', ['delta' => $interval->d], $this->locale);
1029
        }
1030 2
        if ($interval->h > 0) {
1031 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 hour} other{# hours}}', ['delta' => $interval->h], $this->locale);
1032
        }
1033 2
        if ($interval->i > 0) {
1034 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 minute} other{# minutes}}', ['delta' => $interval->i], $this->locale);
1035
        }
1036 2
        if ($interval->s > 0) {
1037 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 second} other{# seconds}}', ['delta' => $interval->s], $this->locale);
1038
        }
1039 2
        if ($interval->s === 0 && empty($parts)) {
1040 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 second} other{# seconds}}', ['delta' => $interval->s], $this->locale);
1041 2
            $isNegative = false;
1042
        }
1043
1044 2
        return empty($parts) ? $this->nullDisplay : (($isNegative ? $negativeSign : '') . implode($implodeString, $parts));
1045
    }
1046
1047
1048
    // number formats
1049
1050
1051
    /**
1052
     * Formats the value as an integer number by removing any decimal digits without rounding.
1053
     *
1054
     * Since 2.0.16 numbers that are mispresented after normalization are formatted as strings using fallback function
1055
     * without [PHP intl extension](http://php.net/manual/en/book.intl.php) support. For very big numbers it's
1056
     * recommended to pass them as strings otherwise the output can be wrong.
1057
     *
1058
     * @param mixed $value the value to be formatted.
1059
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1060
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1061
     * @return string the formatted result.
1062
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1063
     */
1064 8
    public function asInteger($value, $options = [], $textOptions = [])
1065
    {
1066 8
        if ($value === null) {
1067 5
            return $this->nullDisplay;
1068
        }
1069
1070 8
        $normalizedValue = $this->normalizeNumericValue($value);
1071
1072 6
        if ($this->isNormalizedValueMispresented($value, $normalizedValue)) {
1073 5
            return $this->asIntegerStringFallback((string) $value);
1074
        }
1075
1076 6
        if ($this->_intlLoaded) {
1077 5
            $f = $this->createNumberFormatter(NumberFormatter::DECIMAL, null, $options, $textOptions);
1078 5
            $f->setAttribute(NumberFormatter::FRACTION_DIGITS, 0);
1079 5
            if (($result = $f->format($normalizedValue, NumberFormatter::TYPE_INT64)) === false) {
1080
                throw new InvalidArgumentException('Formatting integer value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1081
            }
1082
1083 5
            return $result;
1084
        }
1085
1086 1
        return number_format((int) $normalizedValue, 0, $this->decimalSeparator, $this->thousandSeparator);
1087
    }
1088
1089
    /**
1090
     * Formats the value as a decimal number.
1091
     *
1092
     * Property [[decimalSeparator]] will be used to represent the decimal point. The
1093
     * value is rounded automatically to the defined decimal digits.
1094
     *
1095
     * Since 2.0.16 numbers that are mispresented after normalization are formatted as strings using fallback function
1096
     * without [PHP intl extension](http://php.net/manual/en/book.intl.php) support. For very big numbers it's
1097
     * recommended to pass them as strings otherwise the output can be wrong.
1098
     *
1099
     * @param mixed $value the value to be formatted.
1100
     * @param int $decimals the number of digits after the decimal point.
1101
     * If not given, the number of digits depends in the input value and is determined based on
1102
     * `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
1103
     * using [[$numberFormatterOptions]].
1104
     * If the PHP intl extension is not available, the default value is `2`.
1105
     * If you want consistent behavior between environments where intl is available and not, you should explicitly
1106
     * specify a value here.
1107
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1108
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1109
     * @return string the formatted result.
1110
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1111
     * @see decimalSeparator
1112
     * @see thousandSeparator
1113
     */
1114 14
    public function asDecimal($value, $decimals = null, $options = [], $textOptions = [])
1115
    {
1116 14
        if ($value === null) {
1117 2
            return $this->nullDisplay;
1118
        }
1119
1120 14
        $normalizedValue = $this->normalizeNumericValue($value);
1121
1122 14
        if ($this->isNormalizedValueMispresented($value, $normalizedValue)) {
1123 2
            return $this->asDecimalStringFallback((string) $value, $decimals);
1124
        }
1125
1126 14
        if ($this->_intlLoaded) {
1127 6
            $f = $this->createNumberFormatter(NumberFormatter::DECIMAL, $decimals, $options, $textOptions);
1128 6
            if (($result = $f->format($normalizedValue)) === false) {
1129
                throw new InvalidArgumentException('Formatting decimal value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1130
            }
1131
1132 6
            return $result;
1133
        }
1134
1135 8
        if ($decimals === null) {
1136 6
            $decimals = 2;
1137
        }
1138
1139 8
        return number_format($normalizedValue, $decimals, $this->decimalSeparator, $this->thousandSeparator);
1140
    }
1141
1142
    /**
1143
     * Formats the value as a percent number with "%" sign.
1144
     *
1145
     * Since 2.0.16 numbers that are mispresented after normalization are formatted as strings using fallback function
1146
     * without [PHP intl extension](http://php.net/manual/en/book.intl.php) support. For very big numbers it's
1147
     * recommended to pass them as strings otherwise the output can be wrong.
1148
     *
1149
     * @param mixed $value the value to be formatted. It must be a factor e.g. `0.75` will result in `75%`.
1150
     * @param int $decimals the number of digits after the decimal point.
1151
     * If not given, the number of digits depends in the input value and is determined based on
1152
     * `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
1153
     * using [[$numberFormatterOptions]].
1154
     * If the PHP intl extension is not available, the default value is `0`.
1155
     * If you want consistent behavior between environments where intl is available and not, you should explicitly
1156
     * specify a value here.
1157
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1158
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1159
     * @return string the formatted result.
1160
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1161
     */
1162 2
    public function asPercent($value, $decimals = null, $options = [], $textOptions = [])
1163
    {
1164 2
        if ($value === null) {
1165 2
            return $this->nullDisplay;
1166
        }
1167
1168 2
        $normalizedValue = $this->normalizeNumericValue($value);
1169
1170 2
        if ($this->isNormalizedValueMispresented($value, $normalizedValue)) {
1171 2
            return $this->asPercentStringFallback((string) $value, $decimals);
1172
        }
1173
1174 2
        if ($this->_intlLoaded) {
1175 1
            $f = $this->createNumberFormatter(NumberFormatter::PERCENT, $decimals, $options, $textOptions);
1176 1
            if (($result = $f->format($normalizedValue)) === false) {
1177
                throw new InvalidArgumentException('Formatting percent value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1178
            }
1179
1180 1
            return $result;
1181
        }
1182
1183 1
        if ($decimals === null) {
1184 1
            $decimals = 0;
1185
        }
1186
1187 1
        $normalizedValue *= 100;
1188 1
        return number_format($normalizedValue, $decimals, $this->decimalSeparator, $this->thousandSeparator) . '%';
1189
    }
1190
1191
    /**
1192
     * Formats the value as a scientific number.
1193
     *
1194
     * @param mixed $value the value to be formatted.
1195
     * @param int $decimals the number of digits after the decimal point.
1196
     * If not given, the number of digits depends in the input value and is determined based on
1197
     * `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
1198
     * using [[$numberFormatterOptions]].
1199
     * If the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value depends on your PHP configuration.
1200
     * If you want consistent behavior between environments where intl is available and not, you should explicitly
1201
     * specify a value here.
1202
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1203
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1204
     * @return string the formatted result.
1205
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1206
     */
1207 2
    public function asScientific($value, $decimals = null, $options = [], $textOptions = [])
1208
    {
1209 2
        if ($value === null) {
1210 2
            return $this->nullDisplay;
1211
        }
1212 2
        $value = $this->normalizeNumericValue($value);
1213
1214 2
        if ($this->_intlLoaded) {
1215 1
            $f = $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $decimals, $options, $textOptions);
1216 1
            if (($result = $f->format($value)) === false) {
1217
                throw new InvalidArgumentException('Formatting scientific number value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1218
            }
1219
1220 1
            return $result;
1221
        }
1222
1223 1
        if ($decimals !== null) {
1224 1
            return sprintf("%.{$decimals}E", $value);
1225
        }
1226
1227 1
        return sprintf('%.E', $value);
1228
    }
1229
1230
    /**
1231
     * Formats the value as a currency number.
1232
     *
1233
     * This function does not require the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed
1234
     * to work, but it is highly recommended to install it to get good formatting results.
1235
     *
1236
     * Since 2.0.16 numbers that are mispresented after normalization are formatted as strings using fallback function
1237
     * without PHP intl extension support. For very big numbers it's recommended to pass them as strings otherwise the output can be wrong.
1238
     *
1239
     * @param mixed $value the value to be formatted.
1240
     * @param string $currency the 3-letter ISO 4217 currency code indicating the currency to use.
1241
     * If null, [[currencyCode]] will be used.
1242
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1243
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1244
     * @return string the formatted result.
1245
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1246
     * @throws InvalidConfigException if no currency is given and [[currencyCode]] is not defined.
1247
     */
1248 5
    public function asCurrency($value, $currency = null, $options = [], $textOptions = [])
1249
    {
1250 5
        if ($value === null) {
1251 2
            return $this->nullDisplay;
1252
        }
1253
1254 5
        $normalizedValue = $this->normalizeNumericValue($value);
1255
1256 5
        if ($this->isNormalizedValueMispresented($value, $normalizedValue)) {
1257 3
            return $this->asCurrencyStringFallback((string) $value, $currency);
1258
        }
1259
1260 4
        if ($this->_intlLoaded) {
1261 3
            $currency = $currency ?: $this->currencyCode;
1262
            // currency code must be set before fraction digits
1263
            // http://php.net/manual/en/numberformatter.formatcurrency.php#114376
1264 3
            if ($currency && !isset($textOptions[NumberFormatter::CURRENCY_CODE])) {
1265 3
                $textOptions[NumberFormatter::CURRENCY_CODE] = $currency;
1266
            }
1267 3
            $formatter = $this->createNumberFormatter(NumberFormatter::CURRENCY, null, $options, $textOptions);
1268 3
            if ($currency === null) {
1269 2
                $result = $formatter->format($normalizedValue);
1270
            } else {
1271 3
                $result = $formatter->formatCurrency($normalizedValue, $currency);
1272
            }
1273 3
            if ($result === false) {
1274
                throw new InvalidArgumentException('Formatting currency value failed: ' . $formatter->getErrorCode() . ' ' . $formatter->getErrorMessage());
1275
            }
1276
1277 3
            return $result;
1278
        }
1279
1280 1
        if ($currency === null) {
1281 1
            if ($this->currencyCode === null) {
1282
                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.');
1283
            }
1284 1
            $currency = $this->currencyCode;
1285
        }
1286
1287 1
        return $currency . ' ' . $this->asDecimal($normalizedValue, 2, $options, $textOptions);
1288
    }
1289
1290
    /**
1291
     * Formats the value as a number spellout.
1292
     *
1293
     * This function requires the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed.
1294
     *
1295
     * @param mixed $value the value to be formatted
1296
     * @return string the formatted result.
1297
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1298
     * @throws InvalidConfigException when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available.
1299
     */
1300 1
    public function asSpellout($value)
1301
    {
1302 1
        if ($value === null) {
1303 1
            return $this->nullDisplay;
1304
        }
1305 1
        $value = $this->normalizeNumericValue($value);
1306 1
        if ($this->_intlLoaded) {
1307 1
            $f = $this->createNumberFormatter(NumberFormatter::SPELLOUT);
1308 1
            if (($result = $f->format($value)) === false) {
1309
                throw new InvalidArgumentException('Formatting number as spellout failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1310
            }
1311
1312 1
            return $result;
1313
        }
1314
1315
        throw new InvalidConfigException('Format as Spellout is only supported when PHP intl extension is installed.');
1316
    }
1317
1318
    /**
1319
     * Formats the value as a ordinal value of a number.
1320
     *
1321
     * This function requires the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed.
1322
     *
1323
     * @param mixed $value the value to be formatted
1324
     * @return string the formatted result.
1325
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1326
     * @throws InvalidConfigException when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available.
1327
     *
1328
     * This formatter does not work well with very big numbers.
1329
     */
1330 2
    public function asOrdinal($value)
1331
    {
1332 2
        if ($value === null) {
1333 1
            return $this->nullDisplay;
1334
        }
1335 2
        $value = $this->normalizeNumericValue($value);
1336 2
        if ($this->_intlLoaded) {
1337 2
            $f = $this->createNumberFormatter(NumberFormatter::ORDINAL);
1338 2
            if (($result = $f->format($value)) === false) {
1339
                throw new InvalidArgumentException('Formatting number as ordinal failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1340
            }
1341
1342 2
            return $result;
1343
        }
1344
1345
        throw new InvalidConfigException('Format as Ordinal is only supported when PHP intl extension is installed.');
1346
    }
1347
1348
    /**
1349
     * Formats the value in bytes as a size in human readable form for example `12 KB`.
1350
     *
1351
     * This is the short form of [[asSize]].
1352
     *
1353
     * If [[sizeFormatBase]] is 1024, [binary prefixes](http://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...)
1354
     * are used in the formatting result.
1355
     *
1356
     * @param string|int|float $value value in bytes to be formatted.
1357
     * @param int $decimals the number of digits after the decimal point.
1358
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1359
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1360
     * @return string the formatted result.
1361
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1362
     * @see sizeFormatBase
1363
     * @see asSize
1364
     */
1365 5
    public function asShortSize($value, $decimals = null, $options = [], $textOptions = [])
1366
    {
1367 5
        if ($value === null) {
1368 2
            return $this->nullDisplay;
1369
        }
1370
1371 5
        list($params, $position) = $this->formatNumber($value, $decimals, 4, $this->sizeFormatBase, $options, $textOptions);
1372
1373 5
        if ($this->sizeFormatBase == 1024) {
1374
            switch ($position) {
1375 5
                case 0:
1376 5
                    return Yii::t('yii', '{nFormatted} B', $params, $this->locale);
1377 3
                case 1:
1378 3
                    return Yii::t('yii', '{nFormatted} KiB', $params, $this->locale);
1379 3
                case 2:
1380 3
                    return Yii::t('yii', '{nFormatted} MiB', $params, $this->locale);
1381 2
                case 3:
1382 2
                    return Yii::t('yii', '{nFormatted} GiB', $params, $this->locale);
1383 2
                case 4:
1384
                    return Yii::t('yii', '{nFormatted} TiB', $params, $this->locale);
1385
                default:
1386 2
                    return Yii::t('yii', '{nFormatted} PiB', $params, $this->locale);
1387
            }
1388
        } else {
1389
            switch ($position) {
1390 2
                case 0:
1391 2
                    return Yii::t('yii', '{nFormatted} B', $params, $this->locale);
1392 2
                case 1:
1393 2
                    return Yii::t('yii', '{nFormatted} KB', $params, $this->locale);
1394 2
                case 2:
1395 2
                    return Yii::t('yii', '{nFormatted} MB', $params, $this->locale);
1396 2
                case 3:
1397 2
                    return Yii::t('yii', '{nFormatted} GB', $params, $this->locale);
1398 2
                case 4:
1399
                    return Yii::t('yii', '{nFormatted} TB', $params, $this->locale);
1400
                default:
1401 2
                    return Yii::t('yii', '{nFormatted} PB', $params, $this->locale);
1402
            }
1403
        }
1404
    }
1405
1406
    /**
1407
     * Formats the value in bytes as a size in human readable form, for example `12 kilobytes`.
1408
     *
1409
     * If [[sizeFormatBase]] is 1024, [binary prefixes](http://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...)
1410
     * are used in the formatting result.
1411
     *
1412
     * @param string|int|float $value value in bytes to be formatted.
1413
     * @param int $decimals the number of digits after the decimal point.
1414
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1415
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1416
     * @return string the formatted result.
1417
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1418
     * @see sizeFormatBase
1419
     * @see asShortSize
1420
     */
1421 6
    public function asSize($value, $decimals = null, $options = [], $textOptions = [])
1422
    {
1423 6
        if ($value === null) {
1424 2
            return $this->nullDisplay;
1425
        }
1426
1427 6
        list($params, $position) = $this->formatNumber($value, $decimals, 4, $this->sizeFormatBase, $options, $textOptions);
1428
1429 6
        if ($this->sizeFormatBase == 1024) {
1430
            switch ($position) {
1431 6
                case 0:
1432 6
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->locale);
1433 4
                case 1:
1434 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}', $params, $this->locale);
1435 4
                case 2:
1436 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}', $params, $this->locale);
1437 4
                case 3:
1438 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}', $params, $this->locale);
1439 4
                case 4:
1440
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}', $params, $this->locale);
1441
                default:
1442 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}', $params, $this->locale);
1443
            }
1444
        } else {
1445
            switch ($position) {
1446 4
                case 0:
1447 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->locale);
1448 4
                case 1:
1449 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}', $params, $this->locale);
1450 4
                case 2:
1451 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}', $params, $this->locale);
1452 4
                case 3:
1453 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}', $params, $this->locale);
1454 4
                case 4:
1455
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}', $params, $this->locale);
1456
                default:
1457 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}', $params, $this->locale);
1458
            }
1459
        }
1460
    }
1461
1462
    /**
1463
     * Formats the value as a length in human readable form for example `12 meters`.
1464
     * Check properties [[baseUnits]] if you need to change unit of value as the multiplier
1465
     * of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
1466
     *
1467
     * @param float|int $value value to be formatted.
1468
     * @param int $decimals the number of digits after the decimal point.
1469
     * @param array $numberOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1470
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1471
     * @return string the formatted result.
1472
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1473
     * @throws InvalidConfigException when INTL is not installed or does not contain required information.
1474
     * @see asLength
1475
     * @since 2.0.13
1476
     * @author John Was <[email protected]>
1477
     */
1478
    public function asLength($value, $decimals = null, $numberOptions = [], $textOptions = [])
1479
    {
1480
        return $this->formatUnit(self::UNIT_LENGTH, self::FORMAT_WIDTH_LONG, $value, null, null, $decimals, $numberOptions, $textOptions);
1481
    }
1482
1483
    /**
1484
     * Formats the value as a length in human readable form for example `12 m`.
1485
     * This is the short form of [[asLength]].
1486
     *
1487
     * Check properties [[baseUnits]] if you need to change unit of value as the multiplier
1488
     * of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
1489
     *
1490
     * @param float|int $value value to be formatted.
1491
     * @param int $decimals the number of digits after the decimal point.
1492
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1493
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1494
     * @return string the formatted result.
1495
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1496
     * @throws InvalidConfigException when INTL is not installed or does not contain required information.
1497
     * @see asLength
1498
     * @since 2.0.13
1499
     * @author John Was <[email protected]>
1500
     */
1501 1
    public function asShortLength($value, $decimals = null, $options = [], $textOptions = [])
1502
    {
1503 1
        return $this->formatUnit(self::UNIT_LENGTH, self::FORMAT_WIDTH_SHORT, $value, null, null, $decimals, $options, $textOptions);
1504
    }
1505
1506
    /**
1507
     * Formats the value as a weight in human readable form for example `12 kilograms`.
1508
     * Check properties [[baseUnits]] if you need to change unit of value as the multiplier
1509
     * of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
1510
     *
1511
     * @param float|int $value value to be formatted.
1512
     * @param int $decimals the number of digits after the decimal point.
1513
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1514
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1515
     * @return string the formatted result.
1516
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1517
     * @throws InvalidConfigException when INTL is not installed or does not contain required information.
1518
     * @since 2.0.13
1519
     * @author John Was <[email protected]>
1520
     */
1521 1
    public function asWeight($value, $decimals = null, $options = [], $textOptions = [])
1522
    {
1523 1
        return $this->formatUnit(self::UNIT_WEIGHT, self::FORMAT_WIDTH_LONG, $value, null, null, $decimals, $options, $textOptions);
1524
    }
1525
1526
    /**
1527
     * Formats the value as a weight in human readable form for example `12 kg`.
1528
     * This is the short form of [[asWeight]].
1529
     *
1530
     * Check properties [[baseUnits]] if you need to change unit of value as the multiplier
1531
     * of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
1532
     *
1533
     * @param float|int $value value to be formatted.
1534
     * @param int $decimals the number of digits after the decimal point.
1535
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1536
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1537
     * @return string the formatted result.
1538
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1539
     * @throws InvalidConfigException when INTL is not installed or does not contain required information.
1540
     * @since 2.0.13
1541
     * @author John Was <[email protected]>
1542
     */
1543
    public function asShortWeight($value, $decimals = null, $options = [], $textOptions = [])
1544
    {
1545
        return $this->formatUnit(self::UNIT_WEIGHT, self::FORMAT_WIDTH_SHORT, $value, null, null, $decimals, $options, $textOptions);
1546
    }
1547
1548
    /**
1549
     * @param string $unitType one of [[UNIT_WEIGHT]], [[UNIT_LENGTH]]
1550
     * @param string $unitFormat one of [[FORMAT_WIDTH_SHORT]], [[FORMAT_WIDTH_LONG]]
1551
     * @param float|int $value to be formatted
1552
     * @param float $baseUnit unit of value as the multiplier of the smallest unit. When `null`, property [[baseUnits]]
1553
     * will be used to determine base unit using $unitType and $unitSystem.
1554
     * @param string $unitSystem either [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]]. When `null`, property [[systemOfUnits]] will be used.
1555
     * @param int $decimals the number of digits after the decimal point.
1556
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1557
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1558
     * @return string
1559
     * @throws InvalidConfigException when INTL is not installed or does not contain required information
1560
     */
1561 2
    private function formatUnit($unitType, $unitFormat, $value, $baseUnit, $unitSystem, $decimals, $options, $textOptions)
1562
    {
1563 2
        if ($value === null) {
1564
            return $this->nullDisplay;
1565
        }
1566 2
        if ($unitSystem === null) {
1567 2
            $unitSystem = $this->systemOfUnits;
1568
        }
1569 2
        if ($baseUnit === null) {
1570 2
            $baseUnit = $this->baseUnits[$unitType][$unitSystem];
1571
        }
1572
1573 2
        $multipliers = array_values($this->measureUnits[$unitType][$unitSystem]);
1574
1575 2
        list($params, $position) = $this->formatNumber(
1576 2
            $this->normalizeNumericValue($value) * $baseUnit,
1577 2
            $decimals,
1578 2
            null,
1579 2
            $multipliers,
1580 2
            $options,
1581 2
            $textOptions
1582
        );
1583
1584 2
        $message = $this->getUnitMessage($unitType, $unitFormat, $unitSystem, $position);
1585
1586
        return (new \MessageFormatter($this->locale, $message))->format([
1587
            '0' => $params['nFormatted'],
1588
            'n' => $params['n'],
1589
        ]);
1590
    }
1591
1592
    /**
1593
     * @param string $unitType one of [[UNIT_WEIGHT]], [[UNIT_LENGTH]]
1594
     * @param string $unitFormat one of [[FORMAT_WIDTH_SHORT]], [[FORMAT_WIDTH_LONG]]
1595
     * @param string $system either [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]]. When `null`, property [[systemOfUnits]] will be used.
1596
     * @param int $position internal position of size unit
1597
     * @return string
1598
     * @throws InvalidConfigException when INTL is not installed or does not contain required information
1599
     */
1600 2
    private function getUnitMessage($unitType, $unitFormat, $system, $position)
1601
    {
1602 2
        if (isset($this->_unitMessages[$unitType][$system][$position])) {
1603
            return $this->_unitMessages[$unitType][$system][$position];
1604
        }
1605 2
        if (!$this->_intlLoaded) {
1606 2
            throw new InvalidConfigException('Format of ' . $unitType . ' is only supported when PHP intl extension is installed.');
1607
        }
1608
1609
        if ($this->_resourceBundle === null) {
1610
            try {
1611
                $this->_resourceBundle = new \ResourceBundle($this->locale, 'ICUDATA-unit');
1612
            } catch (\IntlException $e) {
0 ignored issues
show
Bug introduced by
The class IntlException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
1613
                throw new InvalidConfigException('Current ICU data does not contain information about measure units. Check system requirements.');
1614
            }
1615
        }
1616
        $unitNames = array_keys($this->measureUnits[$unitType][$system]);
1617
        $bundleKey = 'units' . ($unitFormat === self::FORMAT_WIDTH_SHORT ? 'Short' : '');
1618
1619
        $unitBundle = $this->_resourceBundle[$bundleKey][$unitType][$unitNames[$position]];
1620
        if ($unitBundle === null) {
1621
            throw new InvalidConfigException('Current ICU data version does not contain information about unit type "' . $unitType . '" and unit measure "' . $unitNames[$position] . '". Check system requirements.');
1622
        }
1623
1624
        $message = [];
1625
        foreach ($unitBundle as $key => $value) {
1626
            if ($key === 'dnam') {
1627
                continue;
1628
            }
1629
            $message[] = "$key{{$value}}";
1630
        }
1631
1632
        return $this->_unitMessages[$unitType][$system][$position] = '{n, plural, ' . implode(' ', $message) . '}';
1633
    }
1634
1635
    /**
1636
     * Given the value in bytes formats number part of the human readable form.
1637
     *
1638
     * @param string|int|float $value value in bytes to be formatted.
1639
     * @param int $decimals the number of digits after the decimal point
1640
     * @param int $maxPosition maximum internal position of size unit, ignored if $formatBase is an array
1641
     * @param array|int $formatBase the base at which each next unit is calculated, either 1000 or 1024, or an array
1642
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1643
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1644
     * @return array [parameters for Yii::t containing formatted number, internal position of size unit]
1645
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1646
     */
1647 11
    private function formatNumber($value, $decimals, $maxPosition, $formatBase, $options, $textOptions)
1648
    {
1649 11
        $value = $this->normalizeNumericValue($value);
1650
1651 11
        $position = 0;
1652 11
        if (is_array($formatBase)) {
1653 2
            $maxPosition = count($formatBase) - 1;
1654
        }
1655
        do {
1656 11
            if (is_array($formatBase)) {
1657 2
                if (!isset($formatBase[$position + 1])) {
1658
                    break;
1659
                }
1660
1661 2
                if (abs($value) < $formatBase[$position + 1]) {
1662 2
                    break;
1663
                }
1664
            } else {
1665 9
                if (abs($value) < $formatBase) {
1666 9
                    break;
1667
                }
1668 7
                $value /= $formatBase;
1669
            }
1670 9
            $position++;
1671 9
        } while ($position < $maxPosition + 1);
1672
1673 11
        if (is_array($formatBase) && $position !== 0) {
1674 2
            $value /= $formatBase[$position];
1675
        }
1676
1677
        // no decimals for smallest unit
1678 11
        if ($position === 0) {
1679 9
            $decimals = 0;
1680 9
        } elseif ($decimals !== null) {
1681 6
            $value = round($value, $decimals);
1682
        }
1683
        // disable grouping for edge cases like 1023 to get 1023 B instead of 1,023 B
1684 11
        $oldThousandSeparator = $this->thousandSeparator;
1685 11
        $this->thousandSeparator = '';
1686 11
        if ($this->_intlLoaded && !isset($options[NumberFormatter::GROUPING_USED])) {
1687 5
            $options[NumberFormatter::GROUPING_USED] = false;
1688
        }
1689
        // format the size value
1690
        $params = [
1691
            // this is the unformatted number used for the plural rule
1692
            // abs() to make sure the plural rules work correctly on negative numbers, intl does not cover this
1693
            // http://english.stackexchange.com/questions/9735/is-1-singular-or-plural
1694 11
            'n' => abs($value),
1695
            // this is the formatted number used for display
1696 11
            'nFormatted' => $this->asDecimal($value, $decimals, $options, $textOptions),
1697
        ];
1698 11
        $this->thousandSeparator = $oldThousandSeparator;
1699
1700 11
        return [$params, $position];
1701
    }
1702
1703
    /**
1704
     * Normalizes a numeric input value.
1705
     *
1706
     * - everything [empty](http://php.net/manual/en/function.empty.php) will result in `0`
1707
     * - a [numeric](http://php.net/manual/en/function.is-numeric.php) string will be casted to float
1708
     * - everything else will be returned if it is [numeric](http://php.net/manual/en/function.is-numeric.php),
1709
     *   otherwise an exception is thrown.
1710
     *
1711
     * @param mixed $value the input value
1712
     * @return float|int the normalized number value
1713
     * @throws InvalidArgumentException if the input value is not numeric.
1714
     */
1715 33
    protected function normalizeNumericValue($value)
1716
    {
1717 33
        if (empty($value)) {
1718 17
            return 0;
1719
        }
1720 33
        if (is_string($value) && is_numeric($value)) {
1721 23
            $value = (float) $value;
1722
        }
1723 33
        if (!is_numeric($value)) {
1724 2
            throw new InvalidArgumentException("'$value' is not a numeric value.");
1725
        }
1726
1727 31
        return $value;
1728
    }
1729
1730
    /**
1731
     * Creates a number formatter based on the given type and format.
1732
     *
1733
     * You may override this method to create a number formatter based on patterns.
1734
     *
1735
     * @param int $style the type of the number formatter.
1736
     * Values: NumberFormatter::DECIMAL, ::CURRENCY, ::PERCENT, ::SCIENTIFIC, ::SPELLOUT, ::ORDINAL
1737
     * ::DURATION, ::PATTERN_RULEBASED, ::DEFAULT_STYLE, ::IGNORE
1738
     * @param int $decimals the number of digits after the decimal point.
1739
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1740
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1741
     * @return NumberFormatter the created formatter instance
1742
     */
1743 19
    protected function createNumberFormatter($style, $decimals = null, $options = [], $textOptions = [])
1744
    {
1745 19
        $formatter = new NumberFormatter($this->locale, $style);
1746
1747
        // set text attributes
1748 19
        foreach ($this->numberFormatterTextOptions as $name => $attribute) {
1749 2
            $formatter->setTextAttribute($name, $attribute);
1750
        }
1751 19
        foreach ($textOptions as $name => $attribute) {
1752 3
            $formatter->setTextAttribute($name, $attribute);
1753
        }
1754
1755
        // set attributes
1756 19
        foreach ($this->numberFormatterOptions as $name => $value) {
1757 9
            $formatter->setAttribute($name, $value);
1758
        }
1759 19
        foreach ($options as $name => $value) {
1760 7
            $formatter->setAttribute($name, $value);
1761
        }
1762 19
        if ($decimals !== null) {
1763 6
            $formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimals);
1764 6
            $formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimals);
1765
        }
1766
1767
        // set symbols
1768 19
        if ($this->decimalSeparator !== null) {
1769 4
            $formatter->setSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL, $this->decimalSeparator);
1770
        }
1771 19
        if ($this->thousandSeparator !== null) {
1772 7
            $formatter->setSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL, $this->thousandSeparator);
1773 7
            $formatter->setSymbol(NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL, $this->thousandSeparator);
1774
        }
1775 19
        foreach ($this->numberFormatterSymbols as $name => $symbol) {
1776 2
            $formatter->setSymbol($name, $symbol);
1777
        }
1778
1779 19
        return $formatter;
1780
    }
1781
1782
    /**
1783
     * Checks if string representations of given value and its normalized version are different.
1784
     * @param string|float|int $value
1785
     * @param float|int $normalizedValue
1786
     * @return bool
1787
     * @since 2.0.16
1788
     */
1789 26
    protected function isNormalizedValueMispresented($value, $normalizedValue)
1790
    {
1791 26
        if (empty($value)) {
1792 14
            $value = 0;
1793
        }
1794
1795 26
        return (string) $normalizedValue !== (string) $value;
1796
    }
1797
1798
    /**
1799
     * Fallback for formatting value as a decimal number.
1800
     *
1801
     * Property [[decimalSeparator]] will be used to represent the decimal point. The value is rounded automatically
1802
     * to the defined decimal digits.
1803
     *
1804
     * @param string|int|float $value the value to be formatted.
1805
     * @param int $decimals the number of digits after the decimal point. The default value is `2`.
1806
     * @return string the formatted result.
1807
     * @see decimalSeparator
1808
     * @see thousandSeparator
1809
     * @since 2.0.16
1810
     */
1811 11
    protected function asDecimalStringFallback($value, $decimals = 2)
1812
    {
1813 11
        if (empty($value)) {
1814
            $value = 0;
1815
        }
1816
1817 11
        $value = (string) $value;
1818
1819 11
        $separatorPosition = strrpos($value, '.');
1820
1821 11
        if ($separatorPosition !== false) {
1822 6
            $integerPart = substr($value, 0, $separatorPosition);
1823 6
            $fractionalPart = substr($value, $separatorPosition + 1);
1824
        } else {
1825 11
            $integerPart = $value;
1826 11
            $fractionalPart = null;
1827
        }
1828
1829 11
        $decimalOutput = '';
1830
1831 11
        if ($decimals === null) {
1832 2
            $decimals = 2;
1833
        }
1834
1835 11
        $carry = 0;
1836
1837 11
        if ($decimals > 0) {
1838 6
            $decimalSeparator = $this->decimalSeparator;
1839 6
            if ($this->decimalSeparator === null) {
1840 3
                $decimalSeparator = '.';
1841
            }
1842
1843 6
            if ($fractionalPart === null) {
1844 4
                $fractionalPart = str_repeat('0', $decimals);
1845 6
            } elseif (strlen($fractionalPart) > $decimals) {
1846 4
                $cursor = $decimals;
1847
1848
                // checking if fractional part must be rounded
1849 4
                if ((int) substr($fractionalPart, $cursor, 1) >= 5) {
1850
                    while (--$cursor >= 0) {
1851
                        $carry = 0;
1852
1853
                        $oneUp = (int) substr($fractionalPart, $cursor, 1) + 1;
1854
                        if ($oneUp === 10) {
1855
                            $oneUp = 0;
1856
                            $carry = 1;
1857
                        }
1858
1859
                        $fractionalPart = substr($fractionalPart, 0, $cursor) . $oneUp . substr($fractionalPart, $cursor + 1);
1860
1861
                        if ($carry === 0) {
1862
                            break;
1863
                        }
1864
                    }
1865
                }
1866
1867 4
                $fractionalPart = substr($fractionalPart, 0, $decimals);
1868 2
            } elseif (strlen($fractionalPart) < $decimals) {
1869 2
                $fractionalPart = str_pad($fractionalPart, $decimals, '0');
1870
            }
1871
1872 6
            $decimalOutput .= $decimalSeparator . $fractionalPart;
1873
        }
1874
1875
        // checking if integer part must be rounded
1876 11
        if ($carry || ($decimals === 0 && $fractionalPart !== null && (int) substr($fractionalPart, 0, 1) >= 5)) {
1877 4
            $integerPartLength = strlen($integerPart);
1878 4
            $cursor = 0;
1879
1880 4
            while (++$cursor <= $integerPartLength) {
1881 4
                $carry = 0;
1882
1883 4
                $oneUp = (int) substr($integerPart, -$cursor, 1) + 1;
1884 4
                if ($oneUp === 10) {
1885
                    $oneUp = 0;
1886
                    $carry = 1;
1887
                }
1888
1889 4
                $integerPart = substr($integerPart, 0, -$cursor) . $oneUp . substr($integerPart, $integerPartLength - $cursor + 1);
1890
1891 4
                if ($carry === 0) {
1892 4
                    break;
1893
                }
1894
            }
1895 4
            if ($carry === 1) {
1896
                $integerPart = '1' . $integerPart;
1897
            }
1898
        }
1899
1900 11
        if (strlen($integerPart) > 3) {
1901 11
            $thousandSeparator = $this->thousandSeparator;
1902 11
            if ($thousandSeparator === null) {
1903 8
                $thousandSeparator = ',';
1904
            }
1905
1906 11
            $integerPart = strrev(implode(',', str_split(strrev($integerPart), 3)));
1907 11
            if ($thousandSeparator !== ',') {
1908 11
                $integerPart = str_replace(',', $thousandSeparator, $integerPart);
1909
            }
1910
        }
1911
1912 11
        return $integerPart . $decimalOutput;
1913
    }
1914
1915
    /**
1916
     * Fallback for formatting value as an integer number by removing any decimal digits without rounding.
1917
     *
1918
     * @param string|int|float $value the value to be formatted.
1919
     * @return string the formatted result.
1920
     * @since 2.0.16
1921
     */
1922 5
    protected function asIntegerStringFallback($value)
1923
    {
1924 5
        if (empty($value)) {
1925
            $value = 0;
1926
        }
1927
1928 5
        $value = (string) $value;
1929 5
        $separatorPosition = strrpos($value, '.');
1930
1931 5
        if ($separatorPosition !== false) {
1932 5
            $integerPart = substr($value, 0, $separatorPosition);
1933
        } else {
1934 5
            $integerPart = $value;
1935
        }
1936
1937 5
        return $this->asDecimalStringFallback($integerPart, 0);
1938
    }
1939
1940
    /**
1941
     * Fallback for formatting value as a percent number with "%" sign.
1942
     *
1943
     * Property [[decimalSeparator]] will be used to represent the decimal point. The value is rounded automatically
1944
     * to the defined decimal digits.
1945
     *
1946
     * @param string|int|float $value the value to be formatted.
1947
     * @param int $decimals the number of digits after the decimal point. The default value is `0`.
1948
     * @return string the formatted result.
1949
     * @since 2.0.16
1950
     */
1951 2
    protected function asPercentStringFallback($value, $decimals = null)
1952
    {
1953 2
        if (empty($value)) {
1954
            $value = 0;
1955
        }
1956
1957 2
        if ($decimals === null) {
1958 2
            $decimals = 0;
1959
        }
1960
1961 2
        $value = (string) $value;
1962 2
        $separatorPosition = strrpos($value, '.');
1963
1964 2
        if ($separatorPosition !== false) {
1965 2
            $integerPart = substr($value, 0, $separatorPosition);
1966 2
            $fractionalPart = str_pad(substr($value, $separatorPosition + 1), 2, '0');
1967
1968 2
            $integerPart .= substr($fractionalPart, 0, 2);
1969 2
            $fractionalPart = substr($fractionalPart, 2);
1970
1971 2
            if ($fractionalPart === '') {
1972
                $multipliedValue = $integerPart;
1973
            } else {
1974 2
                $multipliedValue = $integerPart . '.' . $fractionalPart;
1975
            }
1976
        } else {
1977 2
            $multipliedValue = $value . '00';
1978
        }
1979
1980 2
        return $this->asDecimalStringFallback($multipliedValue, $decimals) . '%';
1981
    }
1982
1983
    /**
1984
     * Fallback for formatting value as a currency number.
1985
     *
1986
     * @param string|int|float $value the value to be formatted.
1987
     * @param string $currency the 3-letter ISO 4217 currency code indicating the currency to use.
1988
     * If null, [[currencyCode]] will be used.
1989
     * @return string the formatted result.
1990
     * @throws InvalidConfigException if no currency is given and [[currencyCode]] is not defined.
1991
     * @since 2.0.16
1992
     */
1993 3
    protected function asCurrencyStringFallback($value, $currency = null)
1994
    {
1995 3
        if ($currency === null) {
1996 2
            if ($this->currencyCode === null) {
1997 1
                throw new InvalidConfigException('The default currency code for the formatter is not defined.');
1998
            }
1999 1
            $currency = $this->currencyCode;
2000
        }
2001
2002 2
        return $currency . ' ' . $this->asDecimalStringFallback($value, 2);
2003
    }
2004
}
2005