Completed
Push — 2.1 ( bf116e...646cd7 )
by Alexander
09:33
created

Formatter   D

Complexity

Total Complexity 221

Size/Duplication

Total Lines 1689
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 88.49%

Importance

Changes 0
Metric Value
wmc 221
lcom 1
cbo 7
dl 0
loc 1689
ccs 415
cts 469
cp 0.8849
rs 4.4102
c 0
b 0
f 0

37 Methods

Rating   Name   Duplication   Size   Complexity  
C formatDateTimeValue() 0 59 21
D normalizeDatetimeValue() 0 36 18
A asTimestamp() 0 8 2
C asRelativeTime() 0 80 19
C init() 0 24 8
B format() 0 22 5
A asRaw() 0 7 2
A asText() 0 7 2
A asNtext() 0 7 2
A asParagraphs() 0 7 2
A asHtml() 0 7 2
A asEmail() 0 7 2
A asImage() 0 7 2
A asUrl() 0 12 3
A asBoolean() 0 8 3
A asDate() 0 8 2
A asTime() 0 8 2
A asDatetime() 0 8 2
F asDuration() 0 48 15
A asInteger() 0 19 4
B asDecimal() 0 23 5
B asPercent() 0 24 5
B asScientific() 0 22 5
D asCurrency() 0 36 10
A asSpellout() 0 17 4
A asOrdinal() 0 17 4
C asShortSize() 0 40 13
C asSize() 0 40 13
A asLength() 0 4 1
A asShortLength() 0 4 1
A asWeight() 0 4 1
A asShortWeight() 0 4 1
B formatUnit() 0 30 4
D getUnitMessage() 0 34 9
C formatNumber() 0 55 13
B normalizeNumericValue() 0 14 5
F createNumberFormatter() 0 38 9

How to fix   Complexity   

Complex Class

Complex classes like Formatter often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Formatter, and based on these observations, apply Extract Interface, too.

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 258
    public function init()
377
    {
378 258
        if ($this->timeZone === null) {
379 258
            $this->timeZone = Yii::$app->timeZone;
380
        }
381 258
        if ($this->locale === null) {
382 39
            $this->locale = Yii::$app->language;
383
        }
384 258
        if ($this->booleanFormat === null) {
385 258
            $this->booleanFormat = [Yii::t('yii', 'No', [], $this->locale), Yii::t('yii', 'Yes', [], $this->locale)];
386
        }
387 258
        if ($this->nullDisplay === null) {
388 258
            $this->nullDisplay = '<span class="not-set">' . Yii::t('yii', '(not set)', [], $this->locale) . '</span>';
389
        }
390 258
        $this->_intlLoaded = extension_loaded('intl');
391 258
        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 258
    }
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 12
    public function format($value, $format)
423
    {
424 12
        if ($format instanceof Closure) {
425
            return call_user_func($format, $value, $this);
426 12
        } 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 7
            $params = [$value];
436
        }
437 12
        $method = 'as' . $format;
438 12
        if ($this->hasMethod($method)) {
439 12
            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 6
    public function asText($value)
470
    {
471 6
        if ($value === null) {
472 2
            return $this->nullDisplay;
473
        }
474 6
        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
        $parts = [];
1010 2
        if ($interval->y > 0) {
1011 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 year} other{# years}}', ['delta' => $interval->y], $this->locale);
1012
        }
1013 2
        if ($interval->m > 0) {
1014 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 month} other{# months}}', ['delta' => $interval->m], $this->locale);
1015
        }
1016 2
        if ($interval->d > 0) {
1017 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 day} other{# days}}', ['delta' => $interval->d], $this->locale);
1018
        }
1019 2
        if ($interval->h > 0) {
1020 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 hour} other{# hours}}', ['delta' => $interval->h], $this->locale);
1021
        }
1022 2
        if ($interval->i > 0) {
1023 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 minute} other{# minutes}}', ['delta' => $interval->i], $this->locale);
1024
        }
1025 2
        if ($interval->s > 0) {
1026 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 second} other{# seconds}}', ['delta' => $interval->s], $this->locale);
1027
        }
1028 2
        if ($interval->s === 0 && empty($parts)) {
1029 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 second} other{# seconds}}', ['delta' => $interval->s], $this->locale);
1030 2
            $isNegative = false;
1031
        }
1032
1033 2
        return empty($parts) ? $this->nullDisplay : (($isNegative ? $negativeSign : '') . implode($implodeString, $parts));
1034
    }
1035
1036
1037
    // number formats
1038
1039
1040
    /**
1041
     * Formats the value as an integer number by removing any decimal digits without rounding.
1042
     *
1043
     * @param mixed $value the value to be formatted.
1044
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1045
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1046
     * @return string the formatted result.
1047
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1048
     */
1049 7
    public function asInteger($value, $options = [], $textOptions = [])
1050
    {
1051 7
        if ($value === null) {
1052 5
            return $this->nullDisplay;
1053
        }
1054 7
        $value = $this->normalizeNumericValue($value);
1055 5
        if ($this->_intlLoaded) {
1056 4
            $f = $this->createNumberFormatter(NumberFormatter::DECIMAL, null, $options, $textOptions);
1057 4
            $f->setAttribute(NumberFormatter::FRACTION_DIGITS, 0);
1058 4
            if (($result = $f->format($value, NumberFormatter::TYPE_INT64)) === false) {
1059
                throw new InvalidArgumentException('Formatting integer value failed: ' . $f->getErrorCode() . ' '
1060
                    . $f->getErrorMessage());
1061
            }
1062
1063 4
            return $result;
1064
        }
1065
1066 1
        return number_format((int) $value, 0, $this->decimalSeparator, $this->thousandSeparator);
1067
    }
1068
1069
    /**
1070
     * Formats the value as a decimal number.
1071
     *
1072
     * Property [[decimalSeparator]] will be used to represent the decimal point. The
1073
     * value is rounded automatically to the defined decimal digits.
1074
     *
1075
     * @param mixed $value the value to be formatted.
1076
     * @param int $decimals the number of digits after the decimal point.
1077
     * If not given, the number of digits depends in the input value and is determined based on
1078
     * `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
1079
     * using [[$numberFormatterOptions]].
1080
     * If the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value is `2`.
1081
     * If you want consistent behavior between environments where intl is available and not, you should explicitly
1082
     * specify a value here.
1083
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1084
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1085
     * @return string the formatted result.
1086
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1087
     * @see decimalSeparator
1088
     * @see thousandSeparator
1089
     */
1090 14
    public function asDecimal($value, $decimals = null, $options = [], $textOptions = [])
1091
    {
1092 14
        if ($value === null) {
1093 2
            return $this->nullDisplay;
1094
        }
1095 14
        $value = $this->normalizeNumericValue($value);
1096
1097 14
        if ($this->_intlLoaded) {
1098 6
            $f = $this->createNumberFormatter(NumberFormatter::DECIMAL, $decimals, $options, $textOptions);
1099 6
            if (($result = $f->format($value)) === false) {
1100
                throw new InvalidArgumentException('Formatting decimal value failed: ' . $f->getErrorCode() . ' '
1101
                    . $f->getErrorMessage());
1102
            }
1103
1104 6
            return $result;
1105
        }
1106
1107 8
        if ($decimals === null) {
1108 6
            $decimals = 2;
1109
        }
1110
1111 8
        return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator);
1112
    }
1113
1114
1115
    /**
1116
     * Formats the value as a percent number with "%" sign.
1117
     *
1118
     * @param mixed $value the value to be formatted. It must be a factor e.g. `0.75` will result in `75%`.
1119
     * @param int $decimals the number of digits after the decimal point.
1120
     * If not given, the number of digits depends in the input value and is determined based on
1121
     * `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
1122
     * using [[$numberFormatterOptions]].
1123
     * If the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value is `0`.
1124
     * If you want consistent behavior between environments where intl is available and not, you should explicitly
1125
     * specify a value here.
1126
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1127
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1128
     * @return string the formatted result.
1129
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1130
     */
1131 2
    public function asPercent($value, $decimals = null, $options = [], $textOptions = [])
1132
    {
1133 2
        if ($value === null) {
1134 2
            return $this->nullDisplay;
1135
        }
1136 2
        $value = $this->normalizeNumericValue($value);
1137
1138 2
        if ($this->_intlLoaded) {
1139 1
            $f = $this->createNumberFormatter(NumberFormatter::PERCENT, $decimals, $options, $textOptions);
1140 1
            if (($result = $f->format($value)) === false) {
1141
                throw new InvalidArgumentException('Formatting percent value failed: ' . $f->getErrorCode() . ' '
1142
                    . $f->getErrorMessage());
1143
            }
1144
1145 1
            return $result;
1146
        }
1147
1148 1
        if ($decimals === null) {
1149 1
            $decimals = 0;
1150
        }
1151
1152 1
        $value *= 100;
1153 1
        return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator) . '%';
1154
    }
1155
1156
    /**
1157
     * Formats the value as a scientific number.
1158
     *
1159
     * @param mixed $value the value to be formatted.
1160
     * @param int $decimals the number of digits after the decimal point.
1161
     * If not given, the number of digits depends in the input value and is determined based on
1162
     * `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
1163
     * using [[$numberFormatterOptions]].
1164
     * If the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value depends on your PHP configuration.
1165
     * If you want consistent behavior between environments where intl is available and not, you should explicitly
1166
     * specify a value here.
1167
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1168
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1169
     * @return string the formatted result.
1170
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1171
     */
1172 2
    public function asScientific($value, $decimals = null, $options = [], $textOptions = [])
1173
    {
1174 2
        if ($value === null) {
1175 2
            return $this->nullDisplay;
1176
        }
1177 2
        $value = $this->normalizeNumericValue($value);
1178
1179 2
        if ($this->_intlLoaded) {
1180 1
            $f = $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $decimals, $options, $textOptions);
1181 1
            if (($result = $f->format($value)) === false) {
1182
                throw new InvalidParamException('Formatting scientific number value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1183
            }
1184
1185 1
            return $result;
1186
        }
1187
1188 1
        if ($decimals !== null) {
1189 1
            return sprintf("%.{$decimals}E", $value);
1190
        }
1191
1192 1
        return sprintf('%.E', $value);
1193
    }
1194
1195
    /**
1196
     * Formats the value as a currency number.
1197
     *
1198
     * This function does not require the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed
1199
     * to work, but it is highly recommended to install it to get good formatting results.
1200
     *
1201
     * @param mixed $value the value to be formatted.
1202
     * @param string $currency the 3-letter ISO 4217 currency code indicating the currency to use.
1203
     * If null, [[currencyCode]] will be used.
1204
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1205
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1206
     * @return string the formatted result.
1207
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1208
     * @throws InvalidConfigException if no currency is given and [[currencyCode]] is not defined.
1209
     */
1210 4
    public function asCurrency($value, $currency = null, $options = [], $textOptions = [])
1211
    {
1212 4
        if ($value === null) {
1213 2
            return $this->nullDisplay;
1214
        }
1215 4
        $value = $this->normalizeNumericValue($value);
1216
1217 4
        if ($this->_intlLoaded) {
1218 3
            $currency = $currency ?: $this->currencyCode;
1219
            // currency code must be set before fraction digits
1220
            // http://php.net/manual/en/numberformatter.formatcurrency.php#114376
1221 3
            if ($currency && !isset($textOptions[NumberFormatter::CURRENCY_CODE])) {
1222 3
                $textOptions[NumberFormatter::CURRENCY_CODE] = $currency;
1223
            }
1224 3
            $formatter = $this->createNumberFormatter(NumberFormatter::CURRENCY, null, $options, $textOptions);
1225 3
            if ($currency === null) {
1226 2
                $result = $formatter->format($value);
1227
            } else {
1228 3
                $result = $formatter->formatCurrency($value, $currency);
1229
            }
1230 3
            if ($result === false) {
1231
                throw new InvalidParamException('Formatting currency value failed: ' . $formatter->getErrorCode() . ' ' . $formatter->getErrorMessage());
1232
            }
1233
1234 3
            return $result;
1235
        }
1236
1237 1
        if ($currency === null) {
1238 1
            if ($this->currencyCode === null) {
1239
                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.');
1240
            }
1241 1
            $currency = $this->currencyCode;
1242
        }
1243
1244 1
        return $currency . ' ' . $this->asDecimal($value, 2, $options, $textOptions);
1245
    }
1246
1247
    /**
1248
     * Formats the value as a number spellout.
1249
     *
1250
     * This function requires the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed.
1251
     *
1252
     * @param mixed $value the value to be formatted
1253
     * @return string the formatted result.
1254
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1255
     * @throws InvalidConfigException when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available.
1256
     */
1257 1
    public function asSpellout($value)
1258
    {
1259 1
        if ($value === null) {
1260 1
            return $this->nullDisplay;
1261
        }
1262 1
        $value = $this->normalizeNumericValue($value);
1263 1
        if ($this->_intlLoaded) {
1264 1
            $f = $this->createNumberFormatter(NumberFormatter::SPELLOUT);
1265 1
            if (($result = $f->format($value)) === false) {
1266
                throw new InvalidParamException('Formatting number as spellout failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1267
            }
1268
1269 1
            return $result;
1270
        }
1271
1272
        throw new InvalidConfigException('Format as Spellout is only supported when PHP intl extension is installed.');
1273
    }
1274
1275
    /**
1276
     * Formats the value as a ordinal value of a number.
1277
     *
1278
     * This function requires the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed.
1279
     *
1280
     * @param mixed $value the value to be formatted
1281
     * @return string the formatted result.
1282
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1283
     * @throws InvalidConfigException when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available.
1284
     */
1285 2
    public function asOrdinal($value)
1286
    {
1287 2
        if ($value === null) {
1288 1
            return $this->nullDisplay;
1289
        }
1290 2
        $value = $this->normalizeNumericValue($value);
1291 2
        if ($this->_intlLoaded) {
1292 2
            $f = $this->createNumberFormatter(NumberFormatter::ORDINAL);
1293 2
            if (($result = $f->format($value)) === false) {
1294
                throw new InvalidParamException('Formatting number as ordinal failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1295
            }
1296
1297 2
            return $result;
1298
        }
1299
1300
        throw new InvalidConfigException('Format as Ordinal is only supported when PHP intl extension is installed.');
1301
    }
1302
1303
    /**
1304
     * Formats the value in bytes as a size in human readable form for example `12 KB`.
1305
     *
1306
     * This is the short form of [[asSize]].
1307
     *
1308
     * If [[sizeFormatBase]] is 1024, [binary prefixes](http://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...)
1309
     * are used in the formatting result.
1310
     *
1311
     * @param string|int|float $value value in bytes to be formatted.
1312
     * @param int $decimals the number of digits after the decimal point.
1313
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1314
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1315
     * @return string the formatted result.
1316
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1317
     * @see sizeFormatBase
1318
     * @see asSize
1319
     */
1320 5
    public function asShortSize($value, $decimals = null, $options = [], $textOptions = [])
1321
    {
1322 5
        if ($value === null) {
1323 2
            return $this->nullDisplay;
1324
        }
1325
1326 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...
1327
1328 5
        if ($this->sizeFormatBase == 1024) {
1329
            switch ($position) {
1330 5
                case 0:
1331 5
                    return Yii::t('yii', '{nFormatted} B', $params, $this->locale);
1332 3
                case 1:
1333 3
                    return Yii::t('yii', '{nFormatted} KiB', $params, $this->locale);
1334 3
                case 2:
1335 3
                    return Yii::t('yii', '{nFormatted} MiB', $params, $this->locale);
1336 2
                case 3:
1337 2
                    return Yii::t('yii', '{nFormatted} GiB', $params, $this->locale);
1338 2
                case 4:
1339
                    return Yii::t('yii', '{nFormatted} TiB', $params, $this->locale);
1340
                default:
1341 2
                    return Yii::t('yii', '{nFormatted} PiB', $params, $this->locale);
1342
            }
1343
        } else {
1344
            switch ($position) {
1345 2
                case 0:
1346 2
                    return Yii::t('yii', '{nFormatted} B', $params, $this->locale);
1347 2
                case 1:
1348 2
                    return Yii::t('yii', '{nFormatted} KB', $params, $this->locale);
1349 2
                case 2:
1350 2
                    return Yii::t('yii', '{nFormatted} MB', $params, $this->locale);
1351 2
                case 3:
1352 2
                    return Yii::t('yii', '{nFormatted} GB', $params, $this->locale);
1353 2
                case 4:
1354
                    return Yii::t('yii', '{nFormatted} TB', $params, $this->locale);
1355
                default:
1356 2
                    return Yii::t('yii', '{nFormatted} PB', $params, $this->locale);
1357
            }
1358
        }
1359
    }
1360
1361
    /**
1362
     * Formats the value in bytes as a size in human readable form, for example `12 kilobytes`.
1363
     *
1364
     * If [[sizeFormatBase]] is 1024, [binary prefixes](http://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...)
1365
     * are used in the formatting result.
1366
     *
1367
     * @param string|int|float $value value in bytes to be formatted.
1368
     * @param int $decimals the number of digits after the decimal point.
1369
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1370
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1371
     * @return string the formatted result.
1372
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1373
     * @see sizeFormatBase
1374
     * @see asShortSize
1375
     */
1376 6
    public function asSize($value, $decimals = null, $options = [], $textOptions = [])
1377
    {
1378 6
        if ($value === null) {
1379 2
            return $this->nullDisplay;
1380
        }
1381
1382 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...
1383
1384 6
        if ($this->sizeFormatBase == 1024) {
1385
            switch ($position) {
1386 6
                case 0:
1387 6
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->locale);
1388 4
                case 1:
1389 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}', $params, $this->locale);
1390 4
                case 2:
1391 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}', $params, $this->locale);
1392 4
                case 3:
1393 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}', $params, $this->locale);
1394 4
                case 4:
1395
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}', $params, $this->locale);
1396
                default:
1397 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}', $params, $this->locale);
1398
            }
1399
        } else {
1400
            switch ($position) {
1401 4
                case 0:
1402 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->locale);
1403 4
                case 1:
1404 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}', $params, $this->locale);
1405 4
                case 2:
1406 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}', $params, $this->locale);
1407 4
                case 3:
1408 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}', $params, $this->locale);
1409 4
                case 4:
1410
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}', $params, $this->locale);
1411
                default:
1412 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}', $params, $this->locale);
1413
            }
1414
        }
1415
    }
1416
1417
    /**
1418
     * Formats the value as a length in human readable form for example `12 meters`.
1419
     * Check properties [[baseUnits]] if you need to change unit of value as the multiplier
1420
     * of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
1421
     *
1422
     * @param float|int $value value to be formatted.
1423
     * @param int $decimals the number of digits after the decimal point.
1424
     * @param array $numberOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1425
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1426
     * @return string the formatted result.
1427
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1428
     * @throws InvalidConfigException when INTL is not installed or does not contain required information.
1429
     * @see asLength
1430
     * @since 2.0.13
1431
     * @author John Was <[email protected]>
1432
     */
1433
    public function asLength($value, $decimals = null, $numberOptions = [], $textOptions = [])
1434
    {
1435
        return $this->formatUnit(self::UNIT_LENGTH, self::FORMAT_WIDTH_LONG, $value, null, null, $decimals, $numberOptions, $textOptions);
1436
    }
1437
1438
    /**
1439
     * Formats the value as a length in human readable form for example `12 m`.
1440
     * This is the short form of [[asLength]].
1441
     *
1442
     * Check properties [[baseUnits]] if you need to change unit of value as the multiplier
1443
     * of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
1444
     *
1445
     * @param float|int $value value to be formatted.
1446
     * @param int $decimals the number of digits after the decimal point.
1447
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1448
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1449
     * @return string the formatted result.
1450
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1451
     * @throws InvalidConfigException when INTL is not installed or does not contain required information.
1452
     * @see asLength
1453
     * @since 2.0.13
1454
     * @author John Was <[email protected]>
1455
     */
1456 1
    public function asShortLength($value, $decimals = null, $options = [], $textOptions = [])
1457
    {
1458 1
        return $this->formatUnit(self::UNIT_LENGTH, self::FORMAT_WIDTH_SHORT, $value, null, null, $decimals, $options, $textOptions);
1459
    }
1460
1461
    /**
1462
     * Formats the value as a weight in human readable form for example `12 kilograms`.
1463
     * Check properties [[baseUnits]] if you need to change unit of value as the multiplier
1464
     * of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
1465
     *
1466
     * @param float|int $value value to be formatted.
1467
     * @param int $decimals the number of digits after the decimal point.
1468
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1469
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1470
     * @return string the formatted result.
1471
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1472
     * @throws InvalidConfigException when INTL is not installed or does not contain required information.
1473
     * @since 2.0.13
1474
     * @author John Was <[email protected]>
1475
     */
1476 1
    public function asWeight($value, $decimals = null, $options = [], $textOptions = [])
1477
    {
1478 1
        return $this->formatUnit(self::UNIT_WEIGHT, self::FORMAT_WIDTH_LONG, $value, null, null, $decimals, $options, $textOptions);
1479
    }
1480
1481
    /**
1482
     * Formats the value as a weight in human readable form for example `12 kg`.
1483
     * This is the short form of [[asWeight]].
1484
     *
1485
     * Check properties [[baseUnits]] if you need to change unit of value as the multiplier
1486
     * of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
1487
     *
1488
     * @param float|int $value value to be formatted.
1489
     * @param int $decimals the number of digits after the decimal point.
1490
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1491
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1492
     * @return string the formatted result.
1493
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1494
     * @throws InvalidConfigException when INTL is not installed or does not contain required information.
1495
     * @since 2.0.13
1496
     * @author John Was <[email protected]>
1497
     */
1498
    public function asShortWeight($value, $decimals = null, $options = [], $textOptions = [])
1499
    {
1500
        return $this->formatUnit(self::UNIT_WEIGHT, self::FORMAT_WIDTH_SHORT, $value, null, null, $decimals, $options, $textOptions);
1501
    }
1502
1503
    /**
1504
     * @param string $unitType one of [[UNIT_WEIGHT]], [[UNIT_LENGTH]]
1505
     * @param string $unitFormat one of [[FORMAT_WIDTH_SHORT]], [[FORMAT_WIDTH_LONG]]
1506
     * @param float|int $value to be formatted
1507
     * @param float $baseUnit unit of value as the multiplier of the smallest unit. When `null`, property [[baseUnits]]
1508
     * will be used to determine base unit using $unitType and $unitSystem.
1509
     * @param string $unitSystem either [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]]. When `null`, property [[systemOfUnits]] will be used.
1510
     * @param int $decimals the number of digits after the decimal point.
1511
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1512
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1513
     * @return string
1514
     * @throws InvalidConfigException when INTL is not installed or does not contain required information
1515
     */
1516 2
    private function formatUnit($unitType, $unitFormat, $value, $baseUnit, $unitSystem, $decimals, $options, $textOptions)
1517
    {
1518 2
        if ($value === null) {
1519
            return $this->nullDisplay;
1520
        }
1521 2
        if ($unitSystem === null) {
1522 2
            $unitSystem = $this->systemOfUnits;
1523
        }
1524 2
        if ($baseUnit === null) {
1525 2
            $baseUnit = $this->baseUnits[$unitType][$unitSystem];
1526
        }
1527
1528 2
        $multipliers = array_values($this->measureUnits[$unitType][$unitSystem]);
1529
1530 2
        list($params, $position) = $this->formatNumber(
1531 2
            $this->normalizeNumericValue($value) * $baseUnit,
1532 2
            $decimals,
1533 2
            null,
1534 2
            $multipliers,
1535 2
            $options,
1536 2
            $textOptions
1537
        );
1538
1539 2
        $message = $this->getUnitMessage($unitType, $unitFormat, $unitSystem, $position);
1540
1541
        return (new \MessageFormatter($this->locale, $message))->format([
1542
            '0' => $params['nFormatted'],
1543
            'n' => $params['n'],
1544
        ]);
1545
    }
1546
1547
    /**
1548
     * @param string $unitType one of [[UNIT_WEIGHT]], [[UNIT_LENGTH]]
1549
     * @param string $unitFormat one of [[FORMAT_WIDTH_SHORT]], [[FORMAT_WIDTH_LONG]]
1550
     * @param string $system either [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]]. When `null`, property [[systemOfUnits]] will be used.
1551
     * @param int $position internal position of size unit
1552
     * @return string
1553
     * @throws InvalidConfigException when INTL is not installed or does not contain required information
1554
     */
1555 2
    private function getUnitMessage($unitType, $unitFormat, $system, $position)
1556
    {
1557 2
        if (isset($this->_unitMessages[$unitType][$system][$position])) {
1558
            return $this->_unitMessages[$unitType][$system][$position];
1559
        }
1560 2
        if (!$this->_intlLoaded) {
1561 2
            throw new InvalidConfigException('Format of ' . $unitType . ' is only supported when PHP intl extension is installed.');
1562
        }
1563
1564
        if ($this->_resourceBundle === null) {
1565
            try {
1566
                $this->_resourceBundle = new \ResourceBundle($this->locale, 'ICUDATA-unit');
1567
            } 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...
1568
                throw new InvalidConfigException('Current ICU data does not contain information about measure units. Check system requirements.');
1569
            }
1570
        }
1571
        $unitNames = array_keys($this->measureUnits[$unitType][$system]);
1572
        $bundleKey = 'units' . ($unitFormat === self::FORMAT_WIDTH_SHORT ? 'Short' : '');
1573
1574
        $unitBundle = $this->_resourceBundle[$bundleKey][$unitType][$unitNames[$position]];
1575
        if ($unitBundle === null) {
1576
            throw new InvalidConfigException('Current ICU data version does not contain information about unit type "' . $unitType . '" and unit measure "' . $unitNames[$position] . '". Check system requirements.');
1577
        }
1578
1579
        $message = [];
1580
        foreach ($unitBundle as $key => $value) {
1581
            if ($key === 'dnam') {
1582
                continue;
1583
            }
1584
            $message[] = "$key{{$value}}";
1585
        }
1586
1587
        return $this->_unitMessages[$unitType][$system][$position] = '{n, plural, ' . implode(' ', $message) . '}';
1588
    }
1589
1590
    /**
1591
     * Given the value in bytes formats number part of the human readable form.
1592
     *
1593
     * @param string|int|float $value value in bytes to be formatted.
1594
     * @param int $decimals the number of digits after the decimal point
1595
     * @param int $maxPosition maximum internal position of size unit, ignored if $formatBase is an array
1596
     * @param array|int $formatBase the base at which each next unit is calculated, either 1000 or 1024, or an array
1597
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1598
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1599
     * @return array [parameters for Yii::t containing formatted number, internal position of size unit]
1600
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1601
     */
1602 11
    private function formatNumber($value, $decimals, $maxPosition, $formatBase, $options, $textOptions)
1603
    {
1604 11
        $value = $this->normalizeNumericValue($value);
1605
1606 11
        $position = 0;
1607 11
        if (is_array($formatBase)) {
1608 2
            $maxPosition = count($formatBase) - 1;
1609
        }
1610
        do {
1611 11
            if (is_array($formatBase)) {
1612 2
                if (!isset($formatBase[$position + 1])) {
1613
                    break;
1614
                }
1615
1616 2
                if (abs($value) < $formatBase[$position + 1]) {
1617 2
                    break;
1618
                }
1619
            } else {
1620 9
                if (abs($value) < $formatBase) {
1621 9
                    break;
1622
                }
1623 7
                $value = $value / $formatBase;
1624
            }
1625 9
            $position++;
1626 9
        } while ($position < $maxPosition + 1);
1627
1628 11
        if (is_array($formatBase) && $position !== 0) {
1629 2
            $value /= $formatBase[$position];
1630
        }
1631
1632
        // no decimals for smallest unit
1633 11
        if ($position === 0) {
1634 9
            $decimals = 0;
1635 9
        } elseif ($decimals !== null) {
1636 6
            $value = round($value, $decimals);
1637
        }
1638
        // disable grouping for edge cases like 1023 to get 1023 B instead of 1,023 B
1639 11
        $oldThousandSeparator = $this->thousandSeparator;
1640 11
        $this->thousandSeparator = '';
1641 11
        if ($this->_intlLoaded && !isset($options[NumberFormatter::GROUPING_USED])) {
1642 5
            $options[NumberFormatter::GROUPING_USED] = false;
1643
        }
1644
        // format the size value
1645
        $params = [
1646
            // this is the unformatted number used for the plural rule
1647
            // abs() to make sure the plural rules work correctly on negative numbers, intl does not cover this
1648
            // http://english.stackexchange.com/questions/9735/is-1-singular-or-plural
1649 11
            'n' => abs($value),
1650
            // this is the formatted number used for display
1651 11
            'nFormatted' => $this->asDecimal($value, $decimals, $options, $textOptions),
1652
        ];
1653 11
        $this->thousandSeparator = $oldThousandSeparator;
1654
1655 11
        return [$params, $position];
1656
    }
1657
1658
    /**
1659
     * Normalizes a numeric input value.
1660
     *
1661
     * - everything [empty](http://php.net/manual/en/function.empty.php) will result in `0`
1662
     * - a [numeric](http://php.net/manual/en/function.is-numeric.php) string will be casted to float
1663
     * - everything else will be returned if it is [numeric](http://php.net/manual/en/function.is-numeric.php),
1664
     *   otherwise an exception is thrown.
1665
     *
1666
     * @param mixed $value the input value
1667
     * @return float|int the normalized number value
1668
     * @throws InvalidArgumentException if the input value is not numeric.
1669
     */
1670 31
    protected function normalizeNumericValue($value)
1671
    {
1672 31
        if (empty($value)) {
1673 16
            return 0;
1674
        }
1675 31
        if (is_string($value) && is_numeric($value)) {
1676 16
            $value = (float) $value;
1677
        }
1678 31
        if (!is_numeric($value)) {
1679 2
            throw new InvalidArgumentException("'$value' is not a numeric value.");
1680
        }
1681
1682 29
        return $value;
1683
    }
1684
1685
    /**
1686
     * Creates a number formatter based on the given type and format.
1687
     *
1688
     * You may override this method to create a number formatter based on patterns.
1689
     *
1690
     * @param int $style the type of the number formatter.
1691
     * Values: NumberFormatter::DECIMAL, ::CURRENCY, ::PERCENT, ::SCIENTIFIC, ::SPELLOUT, ::ORDINAL
1692
     * ::DURATION, ::PATTERN_RULEBASED, ::DEFAULT_STYLE, ::IGNORE
1693
     * @param int $decimals the number of digits after the decimal point.
1694
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1695
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1696
     * @return NumberFormatter the created formatter instance
1697
     */
1698 18
    protected function createNumberFormatter($style, $decimals = null, $options = [], $textOptions = [])
1699
    {
1700 18
        $formatter = new NumberFormatter($this->locale, $style);
1701
1702
        // set text attributes
1703 18
        foreach ($this->numberFormatterTextOptions as $name => $attribute) {
1704 1
            $formatter->setTextAttribute($name, $attribute);
1705
        }
1706 18
        foreach ($textOptions as $name => $attribute) {
1707 3
            $formatter->setTextAttribute($name, $attribute);
1708
        }
1709
1710
        // set attributes
1711 18
        foreach ($this->numberFormatterOptions as $name => $value) {
1712 9
            $formatter->setAttribute($name, $value);
1713
        }
1714 18
        foreach ($options as $name => $value) {
1715 7
            $formatter->setAttribute($name, $value);
1716
        }
1717 18
        if ($decimals !== null) {
1718 6
            $formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimals);
1719 6
            $formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimals);
1720
        }
1721
1722
        // set symbols
1723 18
        if ($this->decimalSeparator !== null) {
1724 4
            $formatter->setSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL, $this->decimalSeparator);
1725
        }
1726 18
        if ($this->thousandSeparator !== null) {
1727 7
            $formatter->setSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL, $this->thousandSeparator);
1728 7
            $formatter->setSymbol(NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL, $this->thousandSeparator);
1729
        }
1730 18
        foreach ($this->numberFormatterSymbols as $name => $symbol) {
1731 2
            $formatter->setSymbol($name, $symbol);
1732
        }
1733
1734 18
        return $formatter;
1735
    }
1736
}
1737