Completed
Push — 2.1 ( 83fa82...a136f4 )
by
unknown
12:35
created

Formatter::asText()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1011
        }
1012 2
        if ($interval->m > 0) {
1013 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 month} other{# months}}', ['delta' => $interval->m], $this->locale);
0 ignored issues
show
Bug introduced by
The variable $parts does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1014
        }
1015 2
        if ($interval->d > 0) {
1016 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 day} other{# days}}', ['delta' => $interval->d], $this->locale);
1017
        }
1018 2
        if ($interval->h > 0) {
1019 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 hour} other{# hours}}', ['delta' => $interval->h], $this->locale);
1020
        }
1021 2
        if ($interval->i > 0) {
1022 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 minute} other{# minutes}}', ['delta' => $interval->i], $this->locale);
1023
        }
1024 2
        if ($interval->s > 0) {
1025 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 second} other{# seconds}}', ['delta' => $interval->s], $this->locale);
1026
        }
1027 2
        if ($interval->s === 0 && empty($parts)) {
1028 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 second} other{# seconds}}', ['delta' => $interval->s], $this->locale);
1029 2
            $isNegative = false;
1030
        }
1031
1032 2
        return empty($parts) ? $this->nullDisplay : (($isNegative ? $negativeSign : '') . implode($implodeString, $parts));
1033
    }
1034
1035
1036
    // number formats
1037
1038
1039
    /**
1040
     * Formats the value as an integer number by removing any decimal digits without rounding.
1041
     *
1042
     * @param mixed $value the value to be formatted.
1043
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1044
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1045
     * @return string the formatted result.
1046
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1047
     */
1048 7
    public function asInteger($value, $options = [], $textOptions = [])
1049
    {
1050 7
        if ($value === null) {
1051 5
            return $this->nullDisplay;
1052
        }
1053 7
        $value = $this->normalizeNumericValue($value);
1054 5
        if ($this->_intlLoaded) {
1055 4
            $f = $this->createNumberFormatter(NumberFormatter::DECIMAL, null, $options, $textOptions);
1056 4
            $f->setAttribute(NumberFormatter::FRACTION_DIGITS, 0);
1057 4
            if (($result = $f->format($value, NumberFormatter::TYPE_INT64)) === false) {
1058
                throw new InvalidArgumentException('Formatting integer value failed: ' . $f->getErrorCode() . ' '
1059
                    . $f->getErrorMessage());
1060
            }
1061
1062 4
            return $result;
1063
        }
1064
1065 1
        return number_format((int) $value, 0, $this->decimalSeparator, $this->thousandSeparator);
1066
    }
1067
1068
    /**
1069
     * Formats the value as a decimal number.
1070
     *
1071
     * Property [[decimalSeparator]] will be used to represent the decimal point. The
1072
     * value is rounded automatically to the defined decimal digits.
1073
     *
1074
     * @param mixed $value the value to be formatted.
1075
     * @param int $decimals the number of digits after the decimal point.
1076
     * If not given, the number of digits depends in the input value and is determined based on
1077
     * `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
1078
     * using [[$numberFormatterOptions]].
1079
     * If the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value is `2`.
1080
     * If you want consistent behavior between environments where intl is available and not, you should explicitly
1081
     * specify a value here.
1082
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1083
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1084
     * @return string the formatted result.
1085
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1086
     * @see decimalSeparator
1087
     * @see thousandSeparator
1088
     */
1089 14
    public function asDecimal($value, $decimals = null, $options = [], $textOptions = [])
1090
    {
1091 14
        if ($value === null) {
1092 2
            return $this->nullDisplay;
1093
        }
1094 14
        $value = $this->normalizeNumericValue($value);
1095
1096 14
        if ($this->_intlLoaded) {
1097 6
            $f = $this->createNumberFormatter(NumberFormatter::DECIMAL, $decimals, $options, $textOptions);
1098 6
            if (($result = $f->format($value)) === false) {
1099
                throw new InvalidArgumentException('Formatting decimal value failed: ' . $f->getErrorCode() . ' '
1100
                    . $f->getErrorMessage());
1101
            }
1102
1103 6
            return $result;
1104
        }
1105
1106 8
        if ($decimals === null) {
1107 6
            $decimals = 2;
1108
        }
1109
1110 8
        return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator);
1111
    }
1112
1113
1114
    /**
1115
     * Formats the value as a percent number with "%" sign.
1116
     *
1117
     * @param mixed $value the value to be formatted. It must be a factor e.g. `0.75` will result in `75%`.
1118
     * @param int $decimals the number of digits after the decimal point.
1119
     * If not given, the number of digits depends in the input value and is determined based on
1120
     * `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
1121
     * using [[$numberFormatterOptions]].
1122
     * If the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value is `0`.
1123
     * If you want consistent behavior between environments where intl is available and not, you should explicitly
1124
     * specify a value here.
1125
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1126
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1127
     * @return string the formatted result.
1128
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1129
     */
1130 2
    public function asPercent($value, $decimals = null, $options = [], $textOptions = [])
1131
    {
1132 2
        if ($value === null) {
1133 2
            return $this->nullDisplay;
1134
        }
1135 2
        $value = $this->normalizeNumericValue($value);
1136
1137 2
        if ($this->_intlLoaded) {
1138 1
            $f = $this->createNumberFormatter(NumberFormatter::PERCENT, $decimals, $options, $textOptions);
1139 1
            if (($result = $f->format($value)) === false) {
1140
                throw new InvalidArgumentException('Formatting percent value failed: ' . $f->getErrorCode() . ' '
1141
                    . $f->getErrorMessage());
1142
            }
1143
1144 1
            return $result;
1145
        }
1146
1147 1
        if ($decimals === null) {
1148 1
            $decimals = 0;
1149
        }
1150
1151 1
        $value *= 100;
1152 1
        return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator) . '%';
1153
    }
1154
1155
    /**
1156
     * Formats the value as a scientific number.
1157
     *
1158
     * @param mixed $value the value to be formatted.
1159
     * @param int $decimals the number of digits after the decimal point.
1160
     * If not given, the number of digits depends in the input value and is determined based on
1161
     * `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
1162
     * using [[$numberFormatterOptions]].
1163
     * If the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value depends on your PHP configuration.
1164
     * If you want consistent behavior between environments where intl is available and not, you should explicitly
1165
     * specify a value here.
1166
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1167
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1168
     * @return string the formatted result.
1169
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1170
     */
1171 2
    public function asScientific($value, $decimals = null, $options = [], $textOptions = [])
1172
    {
1173 2
        if ($value === null) {
1174 2
            return $this->nullDisplay;
1175
        }
1176 2
        $value = $this->normalizeNumericValue($value);
1177
1178 2
        if ($this->_intlLoaded) {
1179 1
            $f = $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $decimals, $options, $textOptions);
1180 1
            if (($result = $f->format($value)) === false) {
1181
                throw new InvalidParamException('Formatting scientific number value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1182
            }
1183
1184 1
            return $result;
1185
        }
1186
1187 1
        if ($decimals !== null) {
1188 1
            return sprintf("%.{$decimals}E", $value);
1189
        }
1190
1191 1
        return sprintf('%.E', $value);
1192
    }
1193
1194
    /**
1195
     * Formats the value as a currency number.
1196
     *
1197
     * This function does not require the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed
1198
     * to work, but it is highly recommended to install it to get good formatting results.
1199
     *
1200
     * @param mixed $value the value to be formatted.
1201
     * @param string $currency the 3-letter ISO 4217 currency code indicating the currency to use.
1202
     * If null, [[currencyCode]] will be used.
1203
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1204
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1205
     * @return string the formatted result.
1206
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1207
     * @throws InvalidConfigException if no currency is given and [[currencyCode]] is not defined.
1208
     */
1209 4
    public function asCurrency($value, $currency = null, $options = [], $textOptions = [])
1210
    {
1211 4
        if ($value === null) {
1212 2
            return $this->nullDisplay;
1213
        }
1214 4
        $value = $this->normalizeNumericValue($value);
1215
1216 4
        if ($this->_intlLoaded) {
1217 3
            $currency = $currency ?: $this->currencyCode;
1218
            // currency code must be set before fraction digits
1219
            // http://php.net/manual/en/numberformatter.formatcurrency.php#114376
1220 3
            if ($currency && !isset($textOptions[NumberFormatter::CURRENCY_CODE])) {
1221 3
                $textOptions[NumberFormatter::CURRENCY_CODE] = $currency;
1222
            }
1223 3
            $formatter = $this->createNumberFormatter(NumberFormatter::CURRENCY, null, $options, $textOptions);
1224 3
            if ($currency === null) {
1225 2
                $result = $formatter->format($value);
1226
            } else {
1227 3
                $result = $formatter->formatCurrency($value, $currency);
1228
            }
1229 3
            if ($result === false) {
1230
                throw new InvalidParamException('Formatting currency value failed: ' . $formatter->getErrorCode() . ' ' . $formatter->getErrorMessage());
1231
            }
1232
1233 3
            return $result;
1234
        }
1235
1236 1
        if ($currency === null) {
1237 1
            if ($this->currencyCode === null) {
1238
                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.');
1239
            }
1240 1
            $currency = $this->currencyCode;
1241
        }
1242
1243 1
        return $currency . ' ' . $this->asDecimal($value, 2, $options, $textOptions);
1244
    }
1245
1246
    /**
1247
     * Formats the value as a number spellout.
1248
     *
1249
     * This function requires the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed.
1250
     *
1251
     * @param mixed $value the value to be formatted
1252
     * @return string the formatted result.
1253
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1254
     * @throws InvalidConfigException when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available.
1255
     */
1256 1
    public function asSpellout($value)
1257
    {
1258 1
        if ($value === null) {
1259 1
            return $this->nullDisplay;
1260
        }
1261 1
        $value = $this->normalizeNumericValue($value);
1262 1
        if ($this->_intlLoaded) {
1263 1
            $f = $this->createNumberFormatter(NumberFormatter::SPELLOUT);
1264 1
            if (($result = $f->format($value)) === false) {
1265
                throw new InvalidParamException('Formatting number as spellout failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1266
            }
1267
1268 1
            return $result;
1269
        }
1270
1271
        throw new InvalidConfigException('Format as Spellout is only supported when PHP intl extension is installed.');
1272
    }
1273
1274
    /**
1275
     * Formats the value as a ordinal value of a number.
1276
     *
1277
     * This function requires the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed.
1278
     *
1279
     * @param mixed $value the value to be formatted
1280
     * @return string the formatted result.
1281
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1282
     * @throws InvalidConfigException when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available.
1283
     */
1284 2
    public function asOrdinal($value)
1285
    {
1286 2
        if ($value === null) {
1287 1
            return $this->nullDisplay;
1288
        }
1289 2
        $value = $this->normalizeNumericValue($value);
1290 2
        if ($this->_intlLoaded) {
1291 2
            $f = $this->createNumberFormatter(NumberFormatter::ORDINAL);
1292 2
            if (($result = $f->format($value)) === false) {
1293
                throw new InvalidParamException('Formatting number as ordinal failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1294
            }
1295
1296 2
            return $result;
1297
        }
1298
1299
        throw new InvalidConfigException('Format as Ordinal is only supported when PHP intl extension is installed.');
1300
    }
1301
1302
    /**
1303
     * Formats the value in bytes as a size in human readable form for example `12 KB`.
1304
     *
1305
     * This is the short form of [[asSize]].
1306
     *
1307
     * If [[sizeFormatBase]] is 1024, [binary prefixes](http://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...)
1308
     * are used in the formatting result.
1309
     *
1310
     * @param string|int|float $value value in bytes to be formatted.
1311
     * @param int $decimals the number of digits after the decimal point.
1312
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1313
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1314
     * @return string the formatted result.
1315
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1316
     * @see sizeFormatBase
1317
     * @see asSize
1318
     */
1319 5
    public function asShortSize($value, $decimals = null, $options = [], $textOptions = [])
1320
    {
1321 5
        if ($value === null) {
1322 2
            return $this->nullDisplay;
1323
        }
1324
1325 5
        [$params, $position] = $this->formatNumber($value, $decimals, 4, $this->sizeFormatBase, $options, $textOptions);
0 ignored issues
show
Bug introduced by
The variable $params does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $position does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1326
1327 5
        if ($this->sizeFormatBase == 1024) {
1328
            switch ($position) {
1329 5
                case 0:
1330 5
                    return Yii::t('yii', '{nFormatted} B', $params, $this->locale);
1331 3
                case 1:
1332 3
                    return Yii::t('yii', '{nFormatted} KiB', $params, $this->locale);
1333 3
                case 2:
1334 3
                    return Yii::t('yii', '{nFormatted} MiB', $params, $this->locale);
1335 2
                case 3:
1336 2
                    return Yii::t('yii', '{nFormatted} GiB', $params, $this->locale);
1337 2
                case 4:
1338
                    return Yii::t('yii', '{nFormatted} TiB', $params, $this->locale);
1339
                default:
1340 2
                    return Yii::t('yii', '{nFormatted} PiB', $params, $this->locale);
1341
            }
1342
        } else {
1343
            switch ($position) {
1344 2
                case 0:
1345 2
                    return Yii::t('yii', '{nFormatted} B', $params, $this->locale);
1346 2
                case 1:
1347 2
                    return Yii::t('yii', '{nFormatted} KB', $params, $this->locale);
1348 2
                case 2:
1349 2
                    return Yii::t('yii', '{nFormatted} MB', $params, $this->locale);
1350 2
                case 3:
1351 2
                    return Yii::t('yii', '{nFormatted} GB', $params, $this->locale);
1352 2
                case 4:
1353
                    return Yii::t('yii', '{nFormatted} TB', $params, $this->locale);
1354
                default:
1355 2
                    return Yii::t('yii', '{nFormatted} PB', $params, $this->locale);
1356
            }
1357
        }
1358
    }
1359
1360
    /**
1361
     * Formats the value in bytes as a size in human readable form, for example `12 kilobytes`.
1362
     *
1363
     * If [[sizeFormatBase]] is 1024, [binary prefixes](http://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...)
1364
     * are used in the formatting result.
1365
     *
1366
     * @param string|int|float $value value in bytes to be formatted.
1367
     * @param int $decimals the number of digits after the decimal point.
1368
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1369
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1370
     * @return string the formatted result.
1371
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1372
     * @see sizeFormatBase
1373
     * @see asShortSize
1374
     */
1375 6
    public function asSize($value, $decimals = null, $options = [], $textOptions = [])
1376
    {
1377 6
        if ($value === null) {
1378 2
            return $this->nullDisplay;
1379
        }
1380
1381 6
        [$params, $position] = $this->formatNumber($value, $decimals, 4, $this->sizeFormatBase, $options, $textOptions);
0 ignored issues
show
Bug introduced by
The variable $params does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $position does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1382
1383 6
        if ($this->sizeFormatBase == 1024) {
1384
            switch ($position) {
1385 6
                case 0:
1386 6
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->locale);
1387 4
                case 1:
1388 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}', $params, $this->locale);
1389 4
                case 2:
1390 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}', $params, $this->locale);
1391 4
                case 3:
1392 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}', $params, $this->locale);
1393 4
                case 4:
1394
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}', $params, $this->locale);
1395
                default:
1396 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}', $params, $this->locale);
1397
            }
1398
        } else {
1399
            switch ($position) {
1400 4
                case 0:
1401 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->locale);
1402 4
                case 1:
1403 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}', $params, $this->locale);
1404 4
                case 2:
1405 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}', $params, $this->locale);
1406 4
                case 3:
1407 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}', $params, $this->locale);
1408 4
                case 4:
1409
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}', $params, $this->locale);
1410
                default:
1411 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}', $params, $this->locale);
1412
            }
1413
        }
1414
    }
1415
1416
    /**
1417
     * Formats the value as a length in human readable form for example `12 meters`.
1418
     * Check properties [[baseUnits]] if you need to change unit of value as the multiplier
1419
     * of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
1420
     *
1421
     * @param float|int $value value to be formatted.
1422
     * @param int $decimals the number of digits after the decimal point.
1423
     * @param array $numberOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1424
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1425
     * @return string the formatted result.
1426
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1427
     * @throws InvalidConfigException when INTL is not installed or does not contain required information.
1428
     * @see asLength
1429
     * @since 2.0.13
1430
     * @author John Was <[email protected]>
1431
     */
1432
    public function asLength($value, $decimals = null, $numberOptions = [], $textOptions = [])
1433
    {
1434
        return $this->formatUnit(self::UNIT_LENGTH, self::FORMAT_WIDTH_LONG, $value, null, null, $decimals, $numberOptions, $textOptions);
1435
    }
1436
1437
    /**
1438
     * Formats the value as a length in human readable form for example `12 m`.
1439
     * This is the short form of [[asLength]].
1440
     *
1441
     * Check properties [[baseUnits]] if you need to change unit of value as the multiplier
1442
     * of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
1443
     *
1444
     * @param float|int $value value to be formatted.
1445
     * @param int $decimals the number of digits after the decimal point.
1446
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1447
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1448
     * @return string the formatted result.
1449
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1450
     * @throws InvalidConfigException when INTL is not installed or does not contain required information.
1451
     * @see asLength
1452
     * @since 2.0.13
1453
     * @author John Was <[email protected]>
1454
     */
1455 1
    public function asShortLength($value, $decimals = null, $options = [], $textOptions = [])
1456
    {
1457 1
        return $this->formatUnit(self::UNIT_LENGTH, self::FORMAT_WIDTH_SHORT, $value, null, null, $decimals, $options, $textOptions);
1458
    }
1459
1460
    /**
1461
     * Formats the value as a weight in human readable form for example `12 kilograms`.
1462
     * Check properties [[baseUnits]] if you need to change unit of value as the multiplier
1463
     * of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
1464
     *
1465
     * @param float|int $value value to be formatted.
1466
     * @param int $decimals the number of digits after the decimal point.
1467
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1468
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1469
     * @return string the formatted result.
1470
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1471
     * @throws InvalidConfigException when INTL is not installed or does not contain required information.
1472
     * @since 2.0.13
1473
     * @author John Was <[email protected]>
1474
     */
1475 1
    public function asWeight($value, $decimals = null, $options = [], $textOptions = [])
1476
    {
1477 1
        return $this->formatUnit(self::UNIT_WEIGHT, self::FORMAT_WIDTH_LONG, $value, null, null, $decimals, $options, $textOptions);
1478
    }
1479
1480
    /**
1481
     * Formats the value as a weight in human readable form for example `12 kg`.
1482
     * This is the short form of [[asWeight]].
1483
     *
1484
     * Check properties [[baseUnits]] if you need to change unit of value as the multiplier
1485
     * of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
1486
     *
1487
     * @param float|int $value value to be formatted.
1488
     * @param int $decimals the number of digits after the decimal point.
1489
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1490
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1491
     * @return string the formatted result.
1492
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1493
     * @throws InvalidConfigException when INTL is not installed or does not contain required information.
1494
     * @since 2.0.13
1495
     * @author John Was <[email protected]>
1496
     */
1497
    public function asShortWeight($value, $decimals = null, $options = [], $textOptions = [])
1498
    {
1499
        return $this->formatUnit(self::UNIT_WEIGHT, self::FORMAT_WIDTH_SHORT, $value, null, null, $decimals, $options, $textOptions);
1500
    }
1501
1502
    /**
1503
     * @param string $unitType one of [[UNIT_WEIGHT]], [[UNIT_LENGTH]]
1504
     * @param string $unitFormat one of [[FORMAT_WIDTH_SHORT]], [[FORMAT_WIDTH_LONG]]
1505
     * @param float|int $value to be formatted
1506
     * @param float $baseUnit unit of value as the multiplier of the smallest unit. When `null`, property [[baseUnits]]
1507
     * will be used to determine base unit using $unitType and $unitSystem.
1508
     * @param string $unitSystem either [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]]. When `null`, property [[systemOfUnits]] will be used.
1509
     * @param int $decimals the number of digits after the decimal point.
1510
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1511
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1512
     * @return string
1513
     * @throws InvalidConfigException when INTL is not installed or does not contain required information
1514
     */
1515 2
    private function formatUnit($unitType, $unitFormat, $value, $baseUnit, $unitSystem, $decimals, $options, $textOptions)
1516
    {
1517 2
        if ($value === null) {
1518
            return $this->nullDisplay;
1519
        }
1520 2
        if ($unitSystem === null) {
1521 2
            $unitSystem = $this->systemOfUnits;
1522
        }
1523 2
        if ($baseUnit === null) {
1524 2
            $baseUnit = $this->baseUnits[$unitType][$unitSystem];
1525
        }
1526
1527 2
        $multipliers = array_values($this->measureUnits[$unitType][$unitSystem]);
1528
1529 2
        list($params, $position) = $this->formatNumber($value * $baseUnit, $decimals, null, $multipliers, $options, $textOptions);
1530
1531 2
        $message = $this->getUnitMessage($unitType, $unitFormat, $unitSystem, $position);
1532
1533
        return (new \MessageFormatter($this->locale, $message))->format([
1534
            '0' => $params['nFormatted'],
1535
            'n' => $params['n'],
1536
        ]);
1537
    }
1538
1539
    /**
1540
     * @param string $unitType one of [[UNIT_WEIGHT]], [[UNIT_LENGTH]]
1541
     * @param string $unitFormat one of [[FORMAT_WIDTH_SHORT]], [[FORMAT_WIDTH_LONG]]
1542
     * @param string $system either [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]]. When `null`, property [[systemOfUnits]] will be used.
1543
     * @param int $position internal position of size unit
1544
     * @return string
1545
     * @throws InvalidConfigException when INTL is not installed or does not contain required information
1546
     */
1547 2
    private function getUnitMessage($unitType, $unitFormat, $system, $position)
1548
    {
1549 2
        if (isset($this->_unitMessages[$unitType][$system][$position])) {
1550
            return $this->_unitMessages[$unitType][$system][$position];
1551
        }
1552 2
        if (!$this->_intlLoaded) {
1553 2
            throw new InvalidConfigException('Format of ' . $unitType . ' is only supported when PHP intl extension is installed.');
1554
        }
1555
1556
        if ($this->_resourceBundle === null) {
1557
            try {
1558
                $this->_resourceBundle = new \ResourceBundle($this->locale, 'ICUDATA-unit');
1559
            } 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...
1560
                throw new InvalidConfigException('Current ICU data does not contain information about measure units. Check system requirements.');
1561
            }
1562
        }
1563
        $unitNames = array_keys($this->measureUnits[$unitType][$system]);
1564
        $bundleKey = 'units' . ($unitFormat === self::FORMAT_WIDTH_SHORT ? 'Short' : '');
1565
1566
        $unitBundle = $this->_resourceBundle[$bundleKey][$unitType][$unitNames[$position]];
1567
        if ($unitBundle === null) {
1568
            throw new InvalidConfigException('Current ICU data version does not contain information about unit type "' . $unitType . '" and unit measure "' . $unitNames[$position] . '". Check system requirements.');
1569
        }
1570
1571
        $message = [];
1572
        foreach ($unitBundle as $key => $value) {
1573
            if ($key === 'dnam') {
1574
                continue;
1575
            }
1576
            $message[] = "$key{{$value}}";
1577
        }
1578
1579
        return $this->_unitMessages[$unitType][$system][$position] = '{n, plural, ' . implode(' ', $message) . '}';
1580
    }
1581
1582
    /**
1583
     * Given the value in bytes formats number part of the human readable form.
1584
     *
1585
     * @param string|int|float $value value in bytes to be formatted.
1586
     * @param int $decimals the number of digits after the decimal point
1587
     * @param int $maxPosition maximum internal position of size unit, ignored if $formatBase is an array
1588
     * @param array|int $formatBase the base at which each next unit is calculated, either 1000 or 1024, or an array
1589
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1590
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1591
     * @return array [parameters for Yii::t containing formatted number, internal position of size unit]
1592
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1593
     */
1594 11
    private function formatNumber($value, $decimals, $maxPosition, $formatBase, $options, $textOptions)
1595
    {
1596 11
        $value = $this->normalizeNumericValue($value);
1597
1598 11
        $position = 0;
1599 11
        if (is_array($formatBase)) {
1600 2
            $maxPosition = count($formatBase) - 1;
1601
        }
1602
        do {
1603 11
            if (is_array($formatBase)) {
1604 2
                if (!isset($formatBase[$position + 1])) {
1605
                    break;
1606
                }
1607
1608 2
                if (abs($value) < $formatBase[$position + 1]) {
1609 2
                    break;
1610
                }
1611
            } else {
1612 9
                if (abs($value) < $formatBase) {
1613 9
                    break;
1614
                }
1615 7
                $value = $value / $formatBase;
1616
            }
1617 9
            $position++;
1618 9
        } while ($position < $maxPosition + 1);
1619
1620 11
        if (is_array($formatBase) && $position !== 0) {
1621 2
            $value /= $formatBase[$position];
1622
        }
1623
1624
        // no decimals for smallest unit
1625 11
        if ($position === 0) {
1626 9
            $decimals = 0;
1627 9
        } elseif ($decimals !== null) {
1628 6
            $value = round($value, $decimals);
1629
        }
1630
        // disable grouping for edge cases like 1023 to get 1023 B instead of 1,023 B
1631 11
        $oldThousandSeparator = $this->thousandSeparator;
1632 11
        $this->thousandSeparator = '';
1633 11
        if ($this->_intlLoaded && !isset($options[NumberFormatter::GROUPING_USED])) {
1634 5
            $options[NumberFormatter::GROUPING_USED] = false;
1635
        }
1636
        // format the size value
1637
        $params = [
1638
            // this is the unformatted number used for the plural rule
1639
            // abs() to make sure the plural rules work correctly on negative numbers, intl does not cover this
1640
            // http://english.stackexchange.com/questions/9735/is-1-singular-or-plural
1641 11
            'n' => abs($value),
1642
            // this is the formatted number used for display
1643 11
            'nFormatted' => $this->asDecimal($value, $decimals, $options, $textOptions),
1644
        ];
1645 11
        $this->thousandSeparator = $oldThousandSeparator;
1646
1647 11
        return [$params, $position];
1648
    }
1649
1650
    /**
1651
     * Normalizes a numeric input value.
1652
     *
1653
     * - everything [empty](http://php.net/manual/en/function.empty.php) will result in `0`
1654
     * - a [numeric](http://php.net/manual/en/function.is-numeric.php) string will be casted to float
1655
     * - everything else will be returned if it is [numeric](http://php.net/manual/en/function.is-numeric.php),
1656
     *   otherwise an exception is thrown.
1657
     *
1658
     * @param mixed $value the input value
1659
     * @return float|int the normalized number value
1660
     * @throws InvalidArgumentException if the input value is not numeric.
1661
     */
1662 31
    protected function normalizeNumericValue($value)
1663
    {
1664 31
        if (empty($value)) {
1665 16
            return 0;
1666
        }
1667 31
        if (is_string($value) && is_numeric($value)) {
1668 16
            $value = (float) $value;
1669
        }
1670 31
        if (!is_numeric($value)) {
1671 2
            throw new InvalidArgumentException("'$value' is not a numeric value.");
1672
        }
1673
1674 29
        return $value;
1675
    }
1676
1677
    /**
1678
     * Creates a number formatter based on the given type and format.
1679
     *
1680
     * You may override this method to create a number formatter based on patterns.
1681
     *
1682
     * @param int $style the type of the number formatter.
1683
     * Values: NumberFormatter::DECIMAL, ::CURRENCY, ::PERCENT, ::SCIENTIFIC, ::SPELLOUT, ::ORDINAL
1684
     * ::DURATION, ::PATTERN_RULEBASED, ::DEFAULT_STYLE, ::IGNORE
1685
     * @param int $decimals the number of digits after the decimal point.
1686
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1687
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1688
     * @return NumberFormatter the created formatter instance
1689
     */
1690 18
    protected function createNumberFormatter($style, $decimals = null, $options = [], $textOptions = [])
1691
    {
1692 18
        $formatter = new NumberFormatter($this->locale, $style);
1693
1694
        // set text attributes
1695 18
        foreach ($this->numberFormatterTextOptions as $name => $attribute) {
1696 1
            $formatter->setTextAttribute($name, $attribute);
1697
        }
1698 18
        foreach ($textOptions as $name => $attribute) {
1699 3
            $formatter->setTextAttribute($name, $attribute);
1700
        }
1701
1702
        // set attributes
1703 18
        foreach ($this->numberFormatterOptions as $name => $value) {
1704 9
            $formatter->setAttribute($name, $value);
1705
        }
1706 18
        foreach ($options as $name => $value) {
1707 7
            $formatter->setAttribute($name, $value);
1708
        }
1709 18
        if ($decimals !== null) {
1710 6
            $formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimals);
1711 6
            $formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimals);
1712
        }
1713
1714
        // set symbols
1715 18
        if ($this->decimalSeparator !== null) {
1716 4
            $formatter->setSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL, $this->decimalSeparator);
1717
        }
1718 18
        if ($this->thousandSeparator !== null) {
1719 7
            $formatter->setSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL, $this->thousandSeparator);
1720 7
            $formatter->setSymbol(NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL, $this->thousandSeparator);
1721
        }
1722 18
        foreach ($this->numberFormatterSymbols as $name => $symbol) {
1723 2
            $formatter->setSymbol($name, $symbol);
1724
        }
1725
1726 18
        return $formatter;
1727
    }
1728
}
1729