Formatter.php 48.1 KB
Newer Older
Qiang Xue committed
1 2 3 4 5 6 7
<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

8
namespace yii\i18n;
Erik_r committed
9

Qiang Xue committed
10
use DateTime;
11
use DateTimeZone;
12 13
use IntlDateFormatter;
use NumberFormatter;
Carsten Brandt committed
14
use Yii;
15 16 17
use yii\base\Component;
use yii\base\InvalidConfigException;
use yii\base\InvalidParamException;
18
use yii\helpers\FormatConverter;
Qiang Xue committed
19 20
use yii\helpers\HtmlPurifier;
use yii\helpers\Html;
21

Qiang Xue committed
22
/**
23 24
 * Formatter provides a set of commonly used data formatting methods.
 *
Qiang Xue committed
25 26 27 28
 * The formatting methods provided by Formatter are all named in the form of `asXyz()`.
 * The behavior of some of them may be configured via the properties of Formatter. For example,
 * by configuring [[dateFormat]], one may control how [[asDate()]] formats the value into a date string.
 *
29
 * Formatter is configured as an application component in [[\yii\base\Application]] by default.
30 31
 * You can access that instance via `Yii::$app->formatter`.
 *
32 33 34
 * The Formatter class is designed to format values according to a [[locale]]. For this feature to work
 * the [PHP intl extension](http://php.net/manual/en/book.intl.php) has to be installed.
 * Most of the methods however work also if the PHP intl extension is not installed by providing
35
 * a fallback implementation. Without intl month and day names are in English only.
36 37 38 39 40 41 42
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @author Enrica Ruedin <e.ruedin@guggach.com>
 * @author Carsten Brandt <mail@cebe.cc>
 * @since 2.0
 */
class Formatter extends Component
Qiang Xue committed
43
{
44 45
    /**
     * @var string the text to be displayed when formatting a `null` value.
Carsten Brandt committed
46 47
     * Defaults to `'<span class="not-set">(not set)</span>'`, where `(not set)`
     * will be translated according to [[locale]].
48 49 50 51 52
     */
    public $nullDisplay;
    /**
     * @var array the text to be displayed when formatting a boolean value. The first element corresponds
     * to the text displayed for `false`, the second element for `true`.
Carsten Brandt committed
53
     * Defaults to `['No', 'Yes']`, where `Yes` and `No`
54
     * will be translated according to [[locale]].
55 56
     */
    public $booleanFormat;
David Renty committed
57
    /**
58
     * @var string the locale ID that is used to localize the date and number formatting.
59 60
     * For number and date formatting this is only effective when the
     * [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed.
61 62 63
     * If not set, [[\yii\base\Application::language]] will be used.
     */
    public $locale;
64
    /**
David Renty committed
65 66 67 68
     * @var string the timezone to use for formatting time and date values.
     * 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)
     * e.g. `UTC`, `Europe/Berlin` or `America/Chicago`.
     * Refer to the [php manual](http://www.php.net/manual/en/timezones.php) for available timezones.
Carsten Brandt committed
69
     * If this property is not set, [[\yii\base\Application::timeZone]] will be used.
70 71 72
     *
     * Note that the input timezone is assumed to be UTC always if no timezone is included in the input date value.
     * Make sure to store datetime values in UTC in your database.
David Renty committed
73 74 75
     */
    public $timeZone;
    /**
76
     * @var string the default format string to be used to format a [[asDate()|date]].
77
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
78
     *
79
     * 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).
80 81
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
     * PHP [date()](http://php.net/manual/de/function.date.php)-function.
82 83 84 85 86 87 88
     *
     * For example:
     *
     * ```php
     * 'MM/dd/yyyy' // date in ICU format
     * 'php:m/d/Y' // the same date in PHP format
     * ```
David Renty committed
89
     */
90
    public $dateFormat = 'medium';
91
    /**
92
     * @var string the default format string to be used to format a [[asTime()|time]].
93
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
94
     *
95
     * 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).
96 97
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
     * PHP [date()](http://php.net/manual/de/function.date.php)-function.
98 99 100 101 102 103 104
     *
     * For example:
     *
     * ```php
     * 'HH:mm:ss' // time in ICU format
     * 'php:H:i:s' // the same time in PHP format
     * ```
105
     */
106
    public $timeFormat = 'medium';
David Renty committed
107
    /**
108
     * @var string the default format string to be used to format a [[asDateTime()|date and time]].
109
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
110
     *
111
     * 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).
112 113 114
     *
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
     * PHP [date()](http://php.net/manual/de/function.date.php)-function.
115 116 117 118 119 120 121
     *
     * For example:
     *
     * ```php
     * 'MM/dd/yyyy HH:mm:ss' // date and time in ICU format
     * 'php:m/d/Y H:i:s' // the same date and time in PHP format
     * ```
David Renty committed
122
     */
123
    public $datetimeFormat = 'medium';
David Renty committed
124 125
    /**
     * @var string the character displayed as the decimal point when formatting a number.
126
     * If not set, the decimal separator corresponding to [[locale]] will be used.
127
     * If [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value is '.'.
David Renty committed
128 129 130
     */
    public $decimalSeparator;
    /**
131
     * @var string the character displayed as the thousands separator (also called grouping separator) character when formatting a number.
132
     * If not set, the thousand separator corresponding to [[locale]] will be used.
133
     * If [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value is ','.
David Renty committed
134 135
     */
    public $thousandSeparator;
136 137 138 139 140 141 142 143 144
    /**
     * @var array a list of name value pairs that are passed to the
     * intl [Numberformatter::setAttribute()](http://php.net/manual/en/numberformatter.setattribute.php) method of all
     * the number formatter objects created by [[createNumberFormatter()]].
     * This property takes only effect if the [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed.
     *
     * Please refer to the [PHP manual](http://php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformatattribute)
     * for the possible options.
     *
Carsten Brandt committed
145
     * For example to adjust the maximum and minimum value of fraction digits you can configure this property like the following:
146 147 148
     *
     * ```php
     * [
Carsten Brandt committed
149 150
     *     NumberFormatter::MIN_FRACTION_DIGITS => 0,
     *     NumberFormatter::MAX_FRACTION_DIGITS => 2,
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
     * ]
     * ```
     */
    public $numberFormatterOptions = [];
    /**
     * @var array a list of name value pairs that are passed to the
     * intl [Numberformatter::setTextAttribute()](http://php.net/manual/en/numberformatter.settextattribute.php) method of all
     * the number formatter objects created by [[createNumberFormatter()]].
     * This property takes only effect if the [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed.
     *
     * Please refer to the [PHP manual](http://php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformattextattribute)
     * for the possible options.
     *
     * For example to change the minus sign for negative numbers you can configure this property like the following:
     *
     * ```php
     * [
     *     NumberFormatter::NEGATIVE_PREFIX => 'MINUS',
     * ]
     * ```
     */
    public $numberFormatterTextOptions = [];
173
    /**
174
     * @var string the 3-letter ISO 4217 currency code indicating the default currency to use for [[asCurrency]].
Carsten Brandt committed
175
     * If not set, the currency code corresponding to [[locale]] will be used.
Carsten Brandt committed
176 177
     * Note that in this case the [[locale]] has to be specified with a country code, e.g. `en-US` otherwise it
     * is not possible to determine the default currency.
178 179
     */
    public $currencyCode;
David Renty committed
180
    /**
Carsten Brandt committed
181 182
     * @var array the base at which a kilobyte is calculated (1000 or 1024 bytes per kilobyte), used by [[asSize]] and [[asShortSize]].
     * Defaults to 1024.
David Renty committed
183
     */
Carsten Brandt committed
184
    public $sizeFormatBase = 1024;
David Renty committed
185

186
    /**
187
     * @var boolean whether the [PHP intl extension](http://php.net/manual/en/book.intl.php) is loaded.
188 189 190
     */
    private $_intlLoaded = false;

191

192
    /**
193
     * @inheritdoc
David Renty committed
194 195 196 197 198 199
     */
    public function init()
    {
        if ($this->timeZone === null) {
            $this->timeZone = Yii::$app->timeZone;
        }
200 201 202
        if ($this->locale === null) {
            $this->locale = Yii::$app->language;
        }
203
        if ($this->booleanFormat === null) {
204
            $this->booleanFormat = [Yii::t('yii', 'No', [], $this->locale), Yii::t('yii', 'Yes', [], $this->locale)];
David Renty committed
205 206
        }
        if ($this->nullDisplay === null) {
207
            $this->nullDisplay = '<span class="not-set">' . Yii::t('yii', '(not set)', [], $this->locale) . '</span>';
David Renty committed
208
        }
209
        $this->_intlLoaded = extension_loaded('intl');
210
        if (!$this->_intlLoaded) {
211 212 213 214 215 216
            if ($this->decimalSeparator === null) {
                $this->decimalSeparator = '.';
            }
            if ($this->thousandSeparator === null) {
                $this->thousandSeparator = ',';
            }
217
        }
David Renty committed
218 219
    }

220
    /**
David Renty committed
221 222 223 224
     * Formats the value based on the given format type.
     * This method will call one of the "as" methods available in this class to do the formatting.
     * For type "xyz", the method "asXyz" will be used. For example, if the format is "html",
     * then [[asHtml()]] will be used. Format names are case insensitive.
225
     * @param mixed $value the value to be formatted.
226 227 228
     * @param string|array $format the format of the value, e.g., "html", "text". To specify additional
     * parameters of the formatting method, you may use an array. The first element of the array
     * specifies the format name, while the rest of the elements will be used as the parameters to the formatting
229 230 231
     * method. For example, a format of `['date', 'Y-m-d']` will cause the invocation of `asDate($value, 'Y-m-d')`.
     * @return string the formatting result.
     * @throws InvalidParamException if the format type is not supported by this class.
David Renty committed
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
     */
    public function format($value, $format)
    {
        if (is_array($format)) {
            if (!isset($format[0])) {
                throw new InvalidParamException('The $format array must contain at least one element.');
            }
            $f = $format[0];
            $format[0] = $value;
            $params = $format;
            $format = $f;
        } else {
            $params = [$value];
        }
        $method = 'as' . $format;
        if ($this->hasMethod($method)) {
            return call_user_func_array([$this, $method], $params);
        } else {
250
            throw new InvalidParamException("Unknown format type: $format");
251 252 253 254
        }
    }


255
    // simple formats
256 257


David Renty committed
258 259 260
    /**
     * Formats the value as is without any formatting.
     * This method simply returns back the parameter without any format.
261 262
     * @param mixed $value the value to be formatted.
     * @return string the formatted result.
David Renty committed
263 264 265 266 267 268 269 270
     */
    public function asRaw($value)
    {
        if ($value === null) {
            return $this->nullDisplay;
        }
        return $value;
    }
271

David Renty committed
272 273
    /**
     * Formats the value as an HTML-encoded plain text.
274 275
     * @param mixed $value the value to be formatted.
     * @return string the formatted result.
David Renty committed
276 277 278 279 280 281 282 283
     */
    public function asText($value)
    {
        if ($value === null) {
            return $this->nullDisplay;
        }
        return Html::encode($value);
    }
284

David Renty committed
285 286
    /**
     * Formats the value as an HTML-encoded plain text with newlines converted into breaks.
287 288
     * @param mixed $value the value to be formatted.
     * @return string the formatted result.
David Renty committed
289 290 291 292 293 294 295 296 297 298 299 300 301
     */
    public function asNtext($value)
    {
        if ($value === null) {
            return $this->nullDisplay;
        }
        return nl2br(Html::encode($value));
    }

    /**
     * Formats the value as HTML-encoded text paragraphs.
     * Each text paragraph is enclosed within a `<p>` tag.
     * One or multiple consecutive empty lines divide two paragraphs.
302 303
     * @param mixed $value the value to be formatted.
     * @return string the formatted result.
David Renty committed
304 305 306 307 308 309
     */
    public function asParagraphs($value)
    {
        if ($value === null) {
            return $this->nullDisplay;
        }
310
        return str_replace('<p></p>', '', '<p>' . preg_replace('/\R{2,}/', "</p>\n<p>", Html::encode($value)) . '</p>');
David Renty committed
311 312 313 314 315 316
    }

    /**
     * Formats the value as HTML text.
     * The value will be purified using [[HtmlPurifier]] to avoid XSS attacks.
     * Use [[asRaw()]] if you do not want any purification of the value.
317
     * @param mixed $value the value to be formatted.
318
     * @param array|null $config the configuration for the HTMLPurifier class.
319
     * @return string the formatted result.
David Renty committed
320 321 322 323 324 325 326 327 328 329 330
     */
    public function asHtml($value, $config = null)
    {
        if ($value === null) {
            return $this->nullDisplay;
        }
        return HtmlPurifier::process($value, $config);
    }

    /**
     * Formats the value as a mailto link.
331 332
     * @param mixed $value the value to be formatted.
     * @return string the formatted result.
David Renty committed
333 334 335 336 337 338 339 340 341 342 343
     */
    public function asEmail($value)
    {
        if ($value === null) {
            return $this->nullDisplay;
        }
        return Html::mailto(Html::encode($value), $value);
    }

    /**
     * Formats the value as an image tag.
344 345
     * @param mixed $value the value to be formatted.
     * @return string the formatted result.
David Renty committed
346 347 348 349 350 351 352 353 354 355 356
     */
    public function asImage($value)
    {
        if ($value === null) {
            return $this->nullDisplay;
        }
        return Html::img($value);
    }

    /**
     * Formats the value as a hyperlink.
357 358
     * @param mixed $value the value to be formatted.
     * @return string the formatted result.
David Renty committed
359 360 361 362 363 364 365
     */
    public function asUrl($value)
    {
        if ($value === null) {
            return $this->nullDisplay;
        }
        $url = $value;
Carsten Brandt committed
366
        if (strpos($url, '://') === false) {
David Renty committed
367 368
            $url = 'http://' . $url;
        }
369

David Renty committed
370 371 372 373 374
        return Html::a(Html::encode($value), $url);
    }

    /**
     * Formats the value as a boolean.
375 376
     * @param mixed $value the value to be formatted.
     * @return string the formatted result.
David Renty committed
377 378 379 380 381 382 383
     * @see booleanFormat
     */
    public function asBoolean($value)
    {
        if ($value === null) {
            return $this->nullDisplay;
        }
384

David Renty committed
385 386
        return $value ? $this->booleanFormat[1] : $this->booleanFormat[0];
    }
387 388 389 390 391


    // date and time formats


David Renty committed
392 393 394
    /**
     * Formats the value as a date.
     * @param integer|string|DateTime $value the value to be formatted. The following
395
     * types of value are supported:
David Renty committed
396 397
     *
     * - an integer representing a UNIX timestamp
398 399 400
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
     *   The timestamp is assumed to be in UTC unless a timezone is explicitly given.
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object
David Renty committed
401
     *
402 403 404 405 406 407 408 409 410
     * @param string $format the format used to convert the value into a date string.
     * If null, [[dateFormat]] will be used.
     *
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
     * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
     *
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
     * PHP [date()](http://php.net/manual/de/function.date.php)-function.
     *
411
     * @return string the formatted result.
412 413
     * @throws InvalidParamException if the input value can not be evaluated as a date value.
     * @throws InvalidConfigException if the date format is invalid.
David Renty committed
414 415
     * @see dateFormat
     */
416
    public function asDate($value, $format = null)
David Renty committed
417
    {
418 419
        if ($format === null) {
            $format = $this->dateFormat;
420
        }
421
        return $this->formatDateTimeValue($value, $format, 'date');
422
    }
423

David Renty committed
424 425 426
    /**
     * Formats the value as a time.
     * @param integer|string|DateTime $value the value to be formatted. The following
427
     * types of value are supported:
David Renty committed
428 429
     *
     * - an integer representing a UNIX timestamp
430 431 432
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
     *   The timestamp is assumed to be in UTC unless a timezone is explicitly given.
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object
David Renty committed
433
     *
434
     * @param string $format the format used to convert the value into a date string.
435 436 437 438 439 440 441 442
     * If null, [[timeFormat]] will be used.
     *
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
     * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
     *
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
     * PHP [date()](http://php.net/manual/de/function.date.php)-function.
     *
443
     * @return string the formatted result.
444 445
     * @throws InvalidParamException if the input value can not be evaluated as a date value.
     * @throws InvalidConfigException if the date format is invalid.
David Renty committed
446 447
     * @see timeFormat
     */
448
    public function asTime($value, $format = null)
David Renty committed
449
    {
450 451
        if ($format === null) {
            $format = $this->timeFormat;
David Renty committed
452
        }
453 454 455 456 457 458 459 460 461
        return $this->formatDateTimeValue($value, $format, 'time');
    }

    /**
     * Formats the value as a datetime.
     * @param integer|string|DateTime $value the value to be formatted. The following
     * types of value are supported:
     *
     * - an integer representing a UNIX timestamp
462 463 464
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
     *   The timestamp is assumed to be in UTC unless a timezone is explicitly given.
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object
465 466 467 468 469 470 471 472 473 474
     *
     * @param string $format the format used to convert the value into a date string.
     * If null, [[dateFormat]] will be used.
     *
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
     * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
     *
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
     * PHP [date()](http://php.net/manual/de/function.date.php)-function.
     *
475
     * @return string the formatted result.
476 477
     * @throws InvalidParamException if the input value can not be evaluated as a date value.
     * @throws InvalidConfigException if the date format is invalid.
478 479 480 481 482 483
     * @see datetimeFormat
     */
    public function asDatetime($value, $format = null)
    {
        if ($format === null) {
            $format = $this->datetimeFormat;
484
        }
485 486 487
        return $this->formatDateTimeValue($value, $format, 'datetime');
    }

488 489 490
    /**
     * @var array map of short format names to IntlDateFormatter constant values.
     */
491 492 493 494 495 496 497 498
    private $_dateFormats = [
        'short'  => 3, // IntlDateFormatter::SHORT,
        'medium' => 2, // IntlDateFormatter::MEDIUM,
        'long'   => 1, // IntlDateFormatter::LONG,
        'full'   => 0, // IntlDateFormatter::FULL,
    ];

    /**
499 500 501 502 503 504 505 506
     * @param integer|string|DateTime $value the value to be formatted. The following
     * types of value are supported:
     *
     * - an integer representing a UNIX timestamp
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
     *   The timestamp is assumed to be in UTC unless a timezone is explicitly given.
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object
     *
507
     * @param string $format the format used to convert the value into a date string.
508 509
     * @param string $type 'date', 'time', or 'datetime'.
     * @throws InvalidConfigException if the date format is invalid.
510
     * @return string the formatted result.
511 512 513
     */
    private function formatDateTimeValue($value, $format, $type)
    {
514 515
        $timestamp = $this->normalizeDatetimeValue($value);
        if ($timestamp === null) {
516
            return $this->nullDisplay;
517 518
        }

519
        if ($this->_intlLoaded) {
520
            if (strncmp($format, 'php:', 4) === 0) {
521 522
                $format = FormatConverter::convertDatePhpToIcu(substr($format, 4));
            }
523 524 525 526 527 528 529
            if (isset($this->_dateFormats[$format])) {
                if ($type === 'date') {
                    $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], IntlDateFormatter::NONE, $this->timeZone);
                } elseif ($type === 'time') {
                    $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, $this->_dateFormats[$format], $this->timeZone);
                } else {
                    $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], $this->_dateFormats[$format], $this->timeZone);
530 531
                }
            } else {
532 533
                $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE, $this->timeZone, null, $format);
            }
534 535 536
            if ($formatter === null) {
                throw new InvalidConfigException(intl_get_error_message());
            }
537
            return $formatter->format($timestamp);
538
        } else {
539
            if (strncmp($format, 'php:', 4) === 0) {
540
                $format = substr($format, 4);
541
            } else {
542
                $format = FormatConverter::convertDateIcuToPhp($format, $type, $this->locale);
543
            }
544
            if ($this->timeZone != null) {
545
                $timestamp->setTimezone(new DateTimeZone($this->timeZone));
546 547
            }
            return $timestamp->format($format);
548 549
        }
    }
550

David Renty committed
551
    /**
552
     * Normalizes the given datetime value as a DateTime object that can be taken by various date/time formatting methods.
Kartik Visweswaran committed
553
     *
554 555 556 557 558 559 560 561
     * @param integer|string|DateTime $value the datetime value to be normalized. The following
     * types of value are supported:
     *
     * - an integer representing a UNIX timestamp
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
     *   The timestamp is assumed to be in UTC unless a timezone is explicitly given.
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object
     *
562 563
     * @return DateTime the normalized datetime value
     * @throws InvalidParamException if the input value can not be evaluated as a date value.
David Renty committed
564
     */
565
    protected function normalizeDatetimeValue($value)
David Renty committed
566
    {
567 568
        if ($value === null || $value instanceof DateTime) {
            // skip any processing
Kartik Visweswaran committed
569
            return $value;
570 571 572 573 574
        }
        if (empty($value)) {
            $value = 0;
        }
        try {
575
            if (is_numeric($value)) { // process as unix timestamp
576
                if (($timestamp = DateTime::createFromFormat('U', $value, new DateTimeZone('UTC'))) === false) {
577 578 579
                    throw new InvalidParamException("Failed to parse '$value' as a UNIX timestamp.");
                }
                return $timestamp;
580
            } elseif (($timestamp = DateTime::createFromFormat('Y-m-d', $value, new DateTimeZone('UTC'))) !== false) { // try Y-m-d format (support invalid dates like 2012-13-01)
581
                return $timestamp;
582
            } elseif (($timestamp = DateTime::createFromFormat('Y-m-d H:i:s', $value, new DateTimeZone('UTC'))) !== false) { // try Y-m-d H:i:s format (support invalid dates like 2012-13-01 12:63:12)
583
                return $timestamp;
584
            }
585
            // finally try to create a DateTime object with the value
586
            $timestamp = new DateTime($value, new DateTimeZone('UTC'));
587 588 589 590
            return $timestamp;
        } catch(\Exception $e) {
            throw new InvalidParamException("'$value' is not a valid date time value: " . $e->getMessage()
                . "\n" . print_r(DateTime::getLastErrors(), true), $e->getCode(), $e);
591 592 593
        }
    }

594
    /**
595 596 597 598 599 600 601 602 603 604
     * Formats a date, time or datetime in a float number as UNIX timestamp (seconds since 01-01-1970).
     * @param integer|string|DateTime|\DateInterval $value the value to be formatted. The following
     * types of value are supported:
     *
     * - an integer representing a UNIX timestamp
     * - a string that can be parsed into a UNIX timestamp via `strtotime()` or that can be passed to a DateInterval constructor.
     * - a PHP DateTime object
     * - a PHP DateInterval object (a positive time interval will refer to the past, a negative one to the future)
     *
     * @return string the formatted result.
605 606 607 608 609 610
     */
    public function asTimestamp($value)
    {
        if ($value === null) {
            return $this->nullDisplay;
        }
611 612
        $timestamp = $this->normalizeDatetimeValue($value);
        return number_format($timestamp->format('U'), 0, '.', '');
613 614 615 616 617 618 619 620 621 622 623 624 625
    }

    /**
     * Formats the value as the time interval between a date and now in human readable form.
     *
     * @param integer|string|DateTime|\DateInterval $value the value to be formatted. The following
     * types of value are supported:
     *
     * - an integer representing a UNIX timestamp
     * - a string that can be parsed into a UNIX timestamp via `strtotime()` or that can be passed to a DateInterval constructor.
     * - a PHP DateTime object
     * - a PHP DateInterval object (a positive time interval will refer to the past, a negative one to the future)
     *
626 627
     * @param integer|string|DateTime|\DateInterval $referenceTime if specified the value is used instead of `now`.
     * @return string the formatted result.
628
     * @throws InvalidParamException if the input value can not be evaluated as a date value.
629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650
     */
    public function asRelativeTime($value, $referenceTime = null)
    {
        if ($value === null) {
            return $this->nullDisplay;
        }

        if ($value instanceof \DateInterval) {
            $interval = $value;
        } else {
            $timestamp = $this->normalizeDatetimeValue($value);

            if ($timestamp === false) {
                // $value is not a valid date/time value, so we try
                // to create a DateInterval with it
                try {
                    $interval = new \DateInterval($value);
                } catch (\Exception $e) {
                    // invalid date/time and invalid interval
                    return $this->nullDisplay;
                }
            } else {
651
                $timezone = new DateTimeZone($this->timeZone);
652 653 654 655

                if ($referenceTime === null) {
                    $dateNow = new DateTime('now', $timezone);
                } else {
656 657
                    $dateNow = $this->normalizeDatetimeValue($referenceTime);
                    $dateNow->setTimezone($timezone);
658 659
                }

660
                $dateThen = $timestamp->setTimezone($timezone);
661 662 663 664 665 666 667

                $interval = $dateThen->diff($dateNow);
            }
        }

        if ($interval->invert) {
            if ($interval->y >= 1) {
668
                return Yii::t('yii', 'in {delta, plural, =1{a year} other{# years}}', ['delta' => $interval->y], $this->locale);
669 670
            }
            if ($interval->m >= 1) {
671
                return Yii::t('yii', 'in {delta, plural, =1{a month} other{# months}}', ['delta' => $interval->m], $this->locale);
672 673
            }
            if ($interval->d >= 1) {
674
                return Yii::t('yii', 'in {delta, plural, =1{a day} other{# days}}', ['delta' => $interval->d], $this->locale);
675 676
            }
            if ($interval->h >= 1) {
677
                return Yii::t('yii', 'in {delta, plural, =1{an hour} other{# hours}}', ['delta' => $interval->h], $this->locale);
678 679
            }
            if ($interval->i >= 1) {
680
                return Yii::t('yii', 'in {delta, plural, =1{a minute} other{# minutes}}', ['delta' => $interval->i], $this->locale);
681
            }
682 683 684
            if ($interval->s == 0) {
                return Yii::t('yii', 'just now', [], $this->locale);
            }
685
            return Yii::t('yii', 'in {delta, plural, =1{a second} other{# seconds}}', ['delta' => $interval->s], $this->locale);
686 687
        } else {
            if ($interval->y >= 1) {
688
                return Yii::t('yii', '{delta, plural, =1{a year} other{# years}} ago', ['delta' => $interval->y], $this->locale);
689 690
            }
            if ($interval->m >= 1) {
691
                return Yii::t('yii', '{delta, plural, =1{a month} other{# months}} ago', ['delta' => $interval->m], $this->locale);
692 693
            }
            if ($interval->d >= 1) {
694
                return Yii::t('yii', '{delta, plural, =1{a day} other{# days}} ago', ['delta' => $interval->d], $this->locale);
695 696
            }
            if ($interval->h >= 1) {
697
                return Yii::t('yii', '{delta, plural, =1{an hour} other{# hours}} ago', ['delta' => $interval->h], $this->locale);
698 699
            }
            if ($interval->i >= 1) {
700
                return Yii::t('yii', '{delta, plural, =1{a minute} other{# minutes}} ago', ['delta' => $interval->i], $this->locale);
701
            }
702 703 704
            if ($interval->s == 0) {
                return Yii::t('yii', 'just now', [], $this->locale);
            }
705
            return Yii::t('yii', '{delta, plural, =1{a second} other{# seconds}} ago', ['delta' => $interval->s], $this->locale);
706 707 708
        }
    }

709 710 711 712

    // number formats


David Renty committed
713
    /**
714 715 716 717 718 719
     * Formats the value as an integer number by removing any decimal digits without rounding.
     *
     * @param mixed $value the value to be formatted.
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
     * @return string the formatted result.
720
     * @throws InvalidParamException if the input value is not numeric.
David Renty committed
721
     */
722
    public function asInteger($value, $options = [], $textOptions = [])
723
    {
David Renty committed
724 725 726
        if ($value === null) {
            return $this->nullDisplay;
        }
727 728
        $value = $this->normalizeNumericValue($value);
        if ($this->_intlLoaded) {
729
            $f = $this->createNumberFormatter(NumberFormatter::DECIMAL, null, $options, $textOptions);
730
            $f->setAttribute(NumberFormatter::FRACTION_DIGITS, 0);
731
            return $f->format($value, NumberFormatter::TYPE_INT64);
David Renty committed
732
        } else {
733
            return number_format((int) $value, 0, $this->decimalSeparator, $this->thousandSeparator);
David Renty committed
734 735
        }
    }
736 737

    /**
738 739
     * Formats the value as a decimal number.
     *
740
     * Property [[decimalSeparator]] will be used to represent the decimal point. The
741 742
     * value is rounded automatically to the defined decimal digits.
     *
743
     * @param mixed $value the value to be formatted.
744 745
     * @param integer $decimals the number of digits after the decimal point. If not given the number of digits is determined from the
     * [[locale]] and if the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available defaults to `2`.
746 747 748
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
     * @return string the formatted result.
749
     * @throws InvalidParamException if the input value is not numeric.
David Renty committed
750
     * @see decimalSeparator
751
     * @see thousandSeparator
David Renty committed
752
     */
753
    public function asDecimal($value, $decimals = null, $options = [], $textOptions = [])
David Renty committed
754 755 756 757
    {
        if ($value === null) {
            return $this->nullDisplay;
        }
758
        $value = $this->normalizeNumericValue($value);
759

760
        if ($this->_intlLoaded) {
761
            $f = $this->createNumberFormatter(NumberFormatter::DECIMAL, $decimals, $options, $textOptions);
762
            return $f->format($value);
David Renty committed
763
        } else {
764 765 766
            if ($decimals === null){
                $decimals = 2;
            }
767
            return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator);
David Renty committed
768 769 770
        }
    }

771

David Renty committed
772
    /**
773
     * Formats the value as a percent number with "%" sign.
774 775 776 777 778
     *
     * @param mixed $value the value to be formatted. It must be a factor e.g. `0.75` will result in `75%`.
     * @param integer $decimals the number of digits after the decimal point.
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
779
     * @return string the formatted result.
780
     * @throws InvalidParamException if the input value is not numeric.
781
     */
782
    public function asPercent($value, $decimals = null, $options = [], $textOptions = [])
783 784 785 786
    {
        if ($value === null) {
            return $this->nullDisplay;
        }
787
        $value = $this->normalizeNumericValue($value);
788

789
        if ($this->_intlLoaded) {
790
            $f = $this->createNumberFormatter(NumberFormatter::PERCENT, $decimals, $options, $textOptions);
791 792
            return $f->format($value);
        } else {
793 794 795
            if ($decimals === null){
                $decimals = 0;
            }
796
            $value = $value * 100;
797
            return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator) . '%';
798 799
        }
    }
800 801

    /**
802
     * Formats the value as a scientific number.
803
     *
804 805 806 807
     * @param mixed $value the value to be formatted.
     * @param integer $decimals the number of digits after the decimal point.
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
808
     * @return string the formatted result.
809
     * @throws InvalidParamException if the input value is not numeric.
810
     */
811
    public function asScientific($value, $decimals = null, $options = [], $textOptions = [])
812
    {
David Renty committed
813 814 815
        if ($value === null) {
            return $this->nullDisplay;
        }
816
        $value = $this->normalizeNumericValue($value);
817

818
        if ($this->_intlLoaded){
819
            $f = $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $decimals, $options, $textOptions);
820 821
            return $f->format($value);
        } else {
822
            if ($decimals !== null) {
823
                return sprintf("%.{$decimals}E", $value);
824 825 826
            } else {
                return sprintf("%.E", $value);
            }
827
        }
David Renty committed
828
    }
829 830

    /**
831
     * Formats the value as a currency number.
832 833 834 835
     *
     * This function does not requires the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed
     * to work but it is highly recommended to install it to get good formatting results.
     *
836
     * @param mixed $value the value to be formatted.
837
     * @param string $currency the 3-letter ISO 4217 currency code indicating the currency to use.
Carsten Brandt committed
838
     * If null, [[currencyCode]] will be used.
839 840
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
841
     * @return string the formatted result.
842
     * @throws InvalidParamException if the input value is not numeric.
843
     * @throws InvalidConfigException if no currency is given and [[currencyCode]] is not defined.
844
     */
845
    public function asCurrency($value, $currency = null, $options = [], $textOptions = [])
846 847 848 849
    {
        if ($value === null) {
            return $this->nullDisplay;
        }
850
        $value = $this->normalizeNumericValue($value);
851 852

        if ($this->_intlLoaded) {
853
            $formatter = $this->createNumberFormatter(NumberFormatter::CURRENCY, null, $options, $textOptions);
Carsten Brandt committed
854 855 856 857 858 859 860
            if ($currency === null) {
                if ($this->currencyCode === null) {
                    $currency = $formatter->getSymbol(NumberFormatter::INTL_CURRENCY_SYMBOL);
                } else {
                    $currency = $this->currencyCode;
                }
            }
861
            return $formatter->formatCurrency($value, $currency);
862
        } else {
Carsten Brandt committed
863 864 865 866 867 868
            if ($currency === null) {
                if ($this->currencyCode === null) {
                    throw new InvalidConfigException('The default currency code for the formatter is not defined.');
                }
                $currency = $this->currencyCode;
            }
869
            return $currency . ' ' . $this->asDecimal($value, 2, $options, $textOptions);
870 871
        }
    }
872

873 874
    /**
     * Formats the value as a number spellout.
875 876 877
     *
     * This function requires the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed.
     *
878 879 880
     * @param mixed $value the value to be formatted
     * @return string the formatted result.
     * @throws InvalidParamException if the input value is not numeric.
881
     * @throws InvalidConfigException when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available.
882 883 884 885 886 887 888 889 890 891 892
     */
    public function asSpellout($value)
    {
        if ($value === null) {
            return $this->nullDisplay;
        }
        $value = $this->normalizeNumericValue($value);
        if ($this->_intlLoaded){
            $f = $this->createNumberFormatter(NumberFormatter::SPELLOUT);
            return $f->format($value);
        } else {
893
            throw new InvalidConfigException('Format as Spellout is only supported when PHP intl extension is installed.');
894 895
        }
    }
896

897 898
    /**
     * Formats the value as a ordinal value of a number.
899 900 901
     *
     * This function requires the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed.
     *
902 903 904
     * @param mixed $value the value to be formatted
     * @return string the formatted result.
     * @throws InvalidParamException if the input value is not numeric.
905
     * @throws InvalidConfigException when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available.
906 907 908 909 910 911 912 913 914 915 916
     */
    public function asOrdinal($value)
    {
        if ($value === null) {
            return $this->nullDisplay;
        }
        $value = $this->normalizeNumericValue($value);
        if ($this->_intlLoaded){
            $f = $this->createNumberFormatter(NumberFormatter::ORDINAL);
            return $f->format($value);
        } else {
917
            throw new InvalidConfigException('Format as Ordinal is only supported when PHP intl extension is installed.');
918 919
        }
    }
920

David Renty committed
921
    /**
Carsten Brandt committed
922 923 924 925 926 927 928 929 930 931 932
     * Formats the value in bytes as a size in human readable form for example `12 KB`.
     *
     * This is the short form of [[asSize]].
     *
     * If [[sizeFormatBase]] is 1024, [binary prefixes](http://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...)
     * are used in the formatting result.
     *
     * @param integer $value value in bytes to be formatted.
     * @param integer $decimals the number of digits after the decimal point.
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
933
     * @return string the formatted result.
934
     * @throws InvalidParamException if the input value is not numeric.
David Renty committed
935
     * @see sizeFormat
Carsten Brandt committed
936
     * @see asSize
David Renty committed
937
     */
Carsten Brandt committed
938
    public function asShortSize($value, $decimals = null, $options = [], $textOptions = [])
David Renty committed
939
    {
940 941 942
        if ($value === null) {
            return $this->nullDisplay;
        }
Carsten Brandt committed
943 944 945 946 947

        list($params, $position) = $this->formatSizeNumber($value, $decimals, $options, $textOptions);

        if ($this->sizeFormatBase == 1024) {
            switch ($position) {
948 949 950 951 952 953
                case 0:  return Yii::t('yii', '{nFormatted} B', $params, $this->locale);
                case 1:  return Yii::t('yii', '{nFormatted} KiB', $params, $this->locale);
                case 2:  return Yii::t('yii', '{nFormatted} MiB', $params, $this->locale);
                case 3:  return Yii::t('yii', '{nFormatted} GiB', $params, $this->locale);
                case 4:  return Yii::t('yii', '{nFormatted} TiB', $params, $this->locale);
                default: return Yii::t('yii', '{nFormatted} PiB', $params, $this->locale);
David Renty committed
954
            }
Carsten Brandt committed
955 956
        } else {
            switch ($position) {
957 958 959 960 961 962
                case 0:  return Yii::t('yii', '{nFormatted} B', $params, $this->locale);
                case 1:  return Yii::t('yii', '{nFormatted} KB', $params, $this->locale);
                case 2:  return Yii::t('yii', '{nFormatted} MB', $params, $this->locale);
                case 3:  return Yii::t('yii', '{nFormatted} GB', $params, $this->locale);
                case 4:  return Yii::t('yii', '{nFormatted} TB', $params, $this->locale);
                default: return Yii::t('yii', '{nFormatted} PB', $params, $this->locale);
Carsten Brandt committed
963 964 965
            }
        }
    }
David Renty committed
966

Carsten Brandt committed
967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988
    /**
     * Formats the value in bytes as a size in human readable form, for example `12 kilobytes`.
     *
     * If [[sizeFormatBase]] is 1024, [binary prefixes](http://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...)
     * are used in the formatting result.
     *
     * @param integer $value value in bytes to be formatted.
     * @param integer $decimals the number of digits after the decimal point.
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
     * @return string the formatted result.
     * @throws InvalidParamException if the input value is not numeric.
     * @see sizeFormat
     * @see asShortSize
     */
    public function asSize($value, $decimals = null, $options = [], $textOptions = [])
    {
        if ($value === null) {
            return $this->nullDisplay;
        }

        list($params, $position) = $this->formatSizeNumber($value, $decimals, $options, $textOptions);
989

Carsten Brandt committed
990 991
        if ($this->sizeFormatBase == 1024) {
            switch ($position) {
992 993 994 995 996 997
                case 0:  return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->locale);
                case 1:  return Yii::t('yii', '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}', $params, $this->locale);
                case 2:  return Yii::t('yii', '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}', $params, $this->locale);
                case 3:  return Yii::t('yii', '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}', $params, $this->locale);
                case 4:  return Yii::t('yii', '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}', $params, $this->locale);
                default: return Yii::t('yii', '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}', $params, $this->locale);
Carsten Brandt committed
998 999
            }
        } else {
1000
            switch ($position) {
1001 1002 1003 1004 1005 1006
                case 0:  return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->locale);
                case 1:  return Yii::t('yii', '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}', $params, $this->locale);
                case 2:  return Yii::t('yii', '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}', $params, $this->locale);
                case 3:  return Yii::t('yii', '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}', $params, $this->locale);
                case 4:  return Yii::t('yii', '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}', $params, $this->locale);
                default: return Yii::t('yii', '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}', $params, $this->locale);
1007 1008
            }
        }
Carsten Brandt committed
1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027
    }

    private function formatSizeNumber($value, $decimals, $options, $textOptions)
    {
        if (is_string($value) && is_numeric($value)) {
            $value = (int) $value;
        }
        if (!is_numeric($value)) {
            throw new InvalidParamException("'$value' is not a numeric value.");
        }

        $position = 0;
        do {
            if ($value < $this->sizeFormatBase) {
                break;
            }
            $value = $value / $this->sizeFormatBase;
            $position++;
        } while ($position < 5);
1028

Carsten Brandt committed
1029 1030 1031 1032 1033
        // no decimals for bytes
        if ($position === 0) {
            $decimals = 0;
        } elseif ($decimals !== null) {
            $value = round($value, $decimals);
David Renty committed
1034
        }
Carsten Brandt committed
1035 1036 1037
        // disable grouping for edge cases like 1023 to get 1023 B instead of 1,023 B
        $oldThousandSeparator = $this->thousandSeparator;
        $this->thousandSeparator = '';
1038 1039 1040
        if ($this->_intlLoaded) {
            $options[NumberFormatter::GROUPING_USED] = false;
        }
Carsten Brandt committed
1041
        // format the size value
1042 1043 1044 1045 1046 1047
        $params = [
            // this is the unformatted number used for the plural rule
            'n' => $value,
            // this is the formatted number used for display
            'nFormatted' => $this->asDecimal($value, $decimals, $options, $textOptions),
        ];
Carsten Brandt committed
1048 1049 1050
        $this->thousandSeparator = $oldThousandSeparator;

        return [$params, $position];
David Renty committed
1051 1052
    }

1053 1054 1055 1056 1057
    /**
     * @param $value
     * @return float
     * @throws InvalidParamException
     */
1058 1059
    protected function normalizeNumericValue($value)
    {
1060 1061 1062
        if (empty($value)) {
            return 0;
        }
1063 1064 1065 1066 1067 1068 1069 1070 1071
        if (is_string($value) && is_numeric($value)) {
            $value = (float) $value;
        }
        if (!is_numeric($value)) {
            throw new InvalidParamException("'$value' is not a numeric value.");
        }
        return $value;
    }

1072 1073
    /**
     * Creates a number formatter based on the given type and format.
1074
     *
Carsten Brandt committed
1075
     * You may overide this method to create a number formatter based on patterns.
1076
     *
Carsten Brandt committed
1077
     * @param integer $style the type of the number formatter.
1078 1079
     * Values: NumberFormatter::DECIMAL, ::CURRENCY, ::PERCENT, ::SCIENTIFIC, ::SPELLOUT, ::ORDINAL
     *          ::DURATION, ::PATTERN_RULEBASED, ::DEFAULT_STYLE, ::IGNORE
Carsten Brandt committed
1080 1081 1082
     * @param integer $decimals the number of digits after the decimal point.
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1083 1084
     * @return NumberFormatter the created formatter instance
     */
1085
    protected function createNumberFormatter($style, $decimals = null, $options = [], $textOptions = [])
1086
    {
1087 1088 1089
        $formatter = new NumberFormatter($this->locale, $style);

        if ($this->decimalSeparator !== null) {
1090
            $formatter->setSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL, $this->decimalSeparator);
1091 1092
        }
        if ($this->thousandSeparator !== null) {
1093
            $formatter->setSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL, $this->thousandSeparator);
1094
        }
1095

1096 1097 1098 1099
        if ($decimals !== null) {
            $formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimals);
            $formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimals);
        }
1100

Carsten Brandt committed
1101 1102 1103 1104
        foreach ($this->numberFormatterTextOptions as $name => $attribute) {
            $formatter->setTextAttribute($name, $attribute);
        }
        foreach ($textOptions as $name => $attribute) {
1105 1106
            $formatter->setTextAttribute($name, $attribute);
        }
1107 1108 1109 1110 1111 1112
        foreach ($this->numberFormatterOptions as $name => $value) {
            $formatter->setAttribute($name, $value);
        }
        foreach ($options as $name => $value) {
            $formatter->setAttribute($name, $value);
        }
1113 1114
        return $formatter;
    }
Qiang Xue committed
1115
}