You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1188 lines
53KB

  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. namespace yii\i18n;
  8. use DateInterval;
  9. use DateTime;
  10. use DateTimeInterface;
  11. use DateTimeZone;
  12. use IntlDateFormatter;
  13. use NumberFormatter;
  14. use Yii;
  15. use yii\base\Component;
  16. use yii\base\InvalidConfigException;
  17. use yii\base\InvalidParamException;
  18. use yii\helpers\FormatConverter;
  19. use yii\helpers\HtmlPurifier;
  20. use yii\helpers\Html;
  21. /**
  22. * Formatter provides a set of commonly used data formatting methods.
  23. *
  24. * The formatting methods provided by Formatter are all named in the form of `asXyz()`.
  25. * The behavior of some of them may be configured via the properties of Formatter. For example,
  26. * by configuring [[dateFormat]], one may control how [[asDate()]] formats the value into a date string.
  27. *
  28. * Formatter is configured as an application component in [[\yii\base\Application]] by default.
  29. * You can access that instance via `Yii::$app->formatter`.
  30. *
  31. * The Formatter class is designed to format values according to a [[locale]]. For this feature to work
  32. * the [PHP intl extension](http://php.net/manual/en/book.intl.php) has to be installed.
  33. * Most of the methods however work also if the PHP intl extension is not installed by providing
  34. * a fallback implementation. Without intl month and day names are in English only.
  35. * Note that even if the intl extension is installed, formatting date and time values for years >=2038 or <=1901
  36. * on 32bit systems will fall back to the PHP implementation because intl uses a 32bit UNIX timestamp internally.
  37. * On a 64bit system the intl formatter is used in all cases if installed.
  38. *
  39. * @author Qiang Xue <qiang.xue@gmail.com>
  40. * @author Enrica Ruedin <e.ruedin@guggach.com>
  41. * @author Carsten Brandt <mail@cebe.cc>
  42. * @since 2.0
  43. */
  44. class Formatter extends Component
  45. {
  46. /**
  47. * @var string the text to be displayed when formatting a `null` value.
  48. * Defaults to `'<span class="not-set">(not set)</span>'`, where `(not set)`
  49. * will be translated according to [[locale]].
  50. */
  51. public $nullDisplay;
  52. /**
  53. * @var array the text to be displayed when formatting a boolean value. The first element corresponds
  54. * to the text displayed for `false`, the second element for `true`.
  55. * Defaults to `['No', 'Yes']`, where `Yes` and `No`
  56. * will be translated according to [[locale]].
  57. */
  58. public $booleanFormat;
  59. /**
  60. * @var string the locale ID that is used to localize the date and number formatting.
  61. * For number and date formatting this is only effective when the
  62. * [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed.
  63. * If not set, [[\yii\base\Application::language]] will be used.
  64. */
  65. public $locale;
  66. /**
  67. * @var string the time zone to use for formatting time and date values.
  68. *
  69. * 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)
  70. * e.g. `UTC`, `Europe/Berlin` or `America/Chicago`.
  71. * Refer to the [php manual](http://www.php.net/manual/en/timezones.php) for available time zones.
  72. * If this property is not set, [[\yii\base\Application::timeZone]] will be used.
  73. *
  74. * 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.
  75. * If you store your data in a different time zone in the database, you have to adjust [[defaultTimeZone]] accordingly.
  76. */
  77. public $timeZone;
  78. /**
  79. * @var string the time zone that is assumed for input values if they do not include a time zone explicitly.
  80. *
  81. * The value must be a valid time zone identifier, e.g. `UTC`, `Europe/Berlin` or `America/Chicago`.
  82. * Please refer to the [php manual](http://www.php.net/manual/en/timezones.php) for available time zones.
  83. *
  84. * It defaults to `UTC` so you only have to adjust this value if you store datetime values in another time zone in your database.
  85. *
  86. * @since 2.0.1
  87. */
  88. public $defaultTimeZone = 'UTC';
  89. /**
  90. * @var string the default format string to be used to format a [[asDate()|date]].
  91. * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
  92. *
  93. * 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).
  94. * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
  95. * PHP [date()](http://php.net/manual/de/function.date.php)-function.
  96. *
  97. * For example:
  98. *
  99. * ```php
  100. * 'MM/dd/yyyy' // date in ICU format
  101. * 'php:m/d/Y' // the same date in PHP format
  102. * ```
  103. */
  104. public $dateFormat = 'medium';
  105. /**
  106. * @var string the default format string to be used to format a [[asTime()|time]].
  107. * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
  108. *
  109. * 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).
  110. * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
  111. * PHP [date()](http://php.net/manual/de/function.date.php)-function.
  112. *
  113. * For example:
  114. *
  115. * ```php
  116. * 'HH:mm:ss' // time in ICU format
  117. * 'php:H:i:s' // the same time in PHP format
  118. * ```
  119. */
  120. public $timeFormat = 'medium';
  121. /**
  122. * @var string the default format string to be used to format a [[asDatetime()|date and time]].
  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. *
  127. * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
  128. * PHP [date()](http://php.net/manual/de/function.date.php)-function.
  129. *
  130. * For example:
  131. *
  132. * ```php
  133. * 'MM/dd/yyyy HH:mm:ss' // date and time in ICU format
  134. * 'php:m/d/Y H:i:s' // the same date and time in PHP format
  135. * ```
  136. */
  137. public $datetimeFormat = 'medium';
  138. /**
  139. * @var string the character displayed as the decimal point when formatting a number.
  140. * If not set, the decimal separator corresponding to [[locale]] will be used.
  141. * If [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value is '.'.
  142. */
  143. public $decimalSeparator;
  144. /**
  145. * @var string the character displayed as the thousands separator (also called grouping separator) character when formatting a number.
  146. * If not set, the thousand separator corresponding to [[locale]] will be used.
  147. * If [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value is ','.
  148. */
  149. public $thousandSeparator;
  150. /**
  151. * @var array a list of name value pairs that are passed to the
  152. * intl [Numberformatter::setAttribute()](http://php.net/manual/en/numberformatter.setattribute.php) method of all
  153. * the number formatter objects created by [[createNumberFormatter()]].
  154. * This property takes only effect if the [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed.
  155. *
  156. * Please refer to the [PHP manual](http://php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformatattribute)
  157. * for the possible options.
  158. *
  159. * For example to adjust the maximum and minimum value of fraction digits you can configure this property like the following:
  160. *
  161. * ```php
  162. * [
  163. * NumberFormatter::MIN_FRACTION_DIGITS => 0,
  164. * NumberFormatter::MAX_FRACTION_DIGITS => 2,
  165. * ]
  166. * ```
  167. */
  168. public $numberFormatterOptions = [];
  169. /**
  170. * @var array a list of name value pairs that are passed to the
  171. * intl [Numberformatter::setTextAttribute()](http://php.net/manual/en/numberformatter.settextattribute.php) method of all
  172. * the number formatter objects created by [[createNumberFormatter()]].
  173. * This property takes only effect if the [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed.
  174. *
  175. * Please refer to the [PHP manual](http://php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformattextattribute)
  176. * for the possible options.
  177. *
  178. * For example to change the minus sign for negative numbers you can configure this property like the following:
  179. *
  180. * ```php
  181. * [
  182. * NumberFormatter::NEGATIVE_PREFIX => 'MINUS',
  183. * ]
  184. * ```
  185. */
  186. public $numberFormatterTextOptions = [];
  187. /**
  188. * @var string the 3-letter ISO 4217 currency code indicating the default currency to use for [[asCurrency]].
  189. * If not set, the currency code corresponding to [[locale]] will be used.
  190. * Note that in this case the [[locale]] has to be specified with a country code, e.g. `en-US` otherwise it
  191. * is not possible to determine the default currency.
  192. */
  193. public $currencyCode;
  194. /**
  195. * @var array the base at which a kilobyte is calculated (1000 or 1024 bytes per kilobyte), used by [[asSize]] and [[asShortSize]].
  196. * Defaults to 1024.
  197. */
  198. public $sizeFormatBase = 1024;
  199. /**
  200. * @var boolean whether the [PHP intl extension](http://php.net/manual/en/book.intl.php) is loaded.
  201. */
  202. private $_intlLoaded = false;
  203. /**
  204. * @inheritdoc
  205. */
  206. public function init()
  207. {
  208. if ($this->timeZone === null) {
  209. $this->timeZone = Yii::$app->timeZone;
  210. }
  211. if ($this->locale === null) {
  212. $this->locale = Yii::$app->language;
  213. }
  214. if ($this->booleanFormat === null) {
  215. $this->booleanFormat = [Yii::t('yii', 'No', [], $this->locale), Yii::t('yii', 'Yes', [], $this->locale)];
  216. }
  217. if ($this->nullDisplay === null) {
  218. $this->nullDisplay = '<span class="not-set">' . Yii::t('yii', '(not set)', [], $this->locale) . '</span>';
  219. }
  220. $this->_intlLoaded = extension_loaded('intl');
  221. if (!$this->_intlLoaded) {
  222. if ($this->decimalSeparator === null) {
  223. $this->decimalSeparator = '.';
  224. }
  225. if ($this->thousandSeparator === null) {
  226. $this->thousandSeparator = ',';
  227. }
  228. }
  229. }
  230. /**
  231. * Formats the value based on the given format type.
  232. * This method will call one of the "as" methods available in this class to do the formatting.
  233. * For type "xyz", the method "asXyz" will be used. For example, if the format is "html",
  234. * then [[asHtml()]] will be used. Format names are case insensitive.
  235. * @param mixed $value the value to be formatted.
  236. * @param string|array $format the format of the value, e.g., "html", "text". To specify additional
  237. * parameters of the formatting method, you may use an array. The first element of the array
  238. * specifies the format name, while the rest of the elements will be used as the parameters to the formatting
  239. * method. For example, a format of `['date', 'Y-m-d']` will cause the invocation of `asDate($value, 'Y-m-d')`.
  240. * @return string the formatting result.
  241. * @throws InvalidParamException if the format type is not supported by this class.
  242. */
  243. public function format($value, $format)
  244. {
  245. if (is_array($format)) {
  246. if (!isset($format[0])) {
  247. throw new InvalidParamException('The $format array must contain at least one element.');
  248. }
  249. $f = $format[0];
  250. $format[0] = $value;
  251. $params = $format;
  252. $format = $f;
  253. } else {
  254. $params = [$value];
  255. }
  256. $method = 'as' . $format;
  257. if ($this->hasMethod($method)) {
  258. return call_user_func_array([$this, $method], $params);
  259. } else {
  260. throw new InvalidParamException("Unknown format type: $format");
  261. }
  262. }
  263. // simple formats
  264. /**
  265. * Formats the value as is without any formatting.
  266. * This method simply returns back the parameter without any format.
  267. * The only exception is a `null` value which will be formatted using [[nullDisplay]].
  268. * @param mixed $value the value to be formatted.
  269. * @return string the formatted result.
  270. */
  271. public function asRaw($value)
  272. {
  273. if ($value === null) {
  274. return $this->nullDisplay;
  275. }
  276. return $value;
  277. }
  278. /**
  279. * Formats the value as an HTML-encoded plain text.
  280. * @param string $value the value to be formatted.
  281. * @return string the formatted result.
  282. */
  283. public function asText($value)
  284. {
  285. if ($value === null) {
  286. return $this->nullDisplay;
  287. }
  288. return Html::encode($value);
  289. }
  290. /**
  291. * Formats the value as an HTML-encoded plain text with newlines converted into breaks.
  292. * @param string $value the value to be formatted.
  293. * @return string the formatted result.
  294. */
  295. public function asNtext($value)
  296. {
  297. if ($value === null) {
  298. return $this->nullDisplay;
  299. }
  300. return nl2br(Html::encode($value));
  301. }
  302. /**
  303. * Formats the value as HTML-encoded text paragraphs.
  304. * Each text paragraph is enclosed within a `<p>` tag.
  305. * One or multiple consecutive empty lines divide two paragraphs.
  306. * @param string $value the value to be formatted.
  307. * @return string the formatted result.
  308. */
  309. public function asParagraphs($value)
  310. {
  311. if ($value === null) {
  312. return $this->nullDisplay;
  313. }
  314. return str_replace('<p></p>', '', '<p>' . preg_replace('/\R{2,}/u', "</p>\n<p>", Html::encode($value)) . '</p>');
  315. }
  316. /**
  317. * Formats the value as HTML text.
  318. * The value will be purified using [[HtmlPurifier]] to avoid XSS attacks.
  319. * Use [[asRaw()]] if you do not want any purification of the value.
  320. * @param string $value the value to be formatted.
  321. * @param array|null $config the configuration for the HTMLPurifier class.
  322. * @return string the formatted result.
  323. */
  324. public function asHtml($value, $config = null)
  325. {
  326. if ($value === null) {
  327. return $this->nullDisplay;
  328. }
  329. return HtmlPurifier::process($value, $config);
  330. }
  331. /**
  332. * Formats the value as a mailto link.
  333. * @param string $value the value to be formatted.
  334. * @param array $options the tag options in terms of name-value pairs. See [[Html::mailto()]].
  335. * @return string the formatted result.
  336. */
  337. public function asEmail($value, $options = [])
  338. {
  339. if ($value === null) {
  340. return $this->nullDisplay;
  341. }
  342. return Html::mailto(Html::encode($value), $value, $options);
  343. }
  344. /**
  345. * Formats the value as an image tag.
  346. * @param mixed $value the value to be formatted.
  347. * @param array $options the tag options in terms of name-value pairs. See [[Html::img()]].
  348. * @return string the formatted result.
  349. */
  350. public function asImage($value, $options = [])
  351. {
  352. if ($value === null) {
  353. return $this->nullDisplay;
  354. }
  355. return Html::img($value, $options);
  356. }
  357. /**
  358. * Formats the value as a hyperlink.
  359. * @param mixed $value the value to be formatted.
  360. * @param array $options the tag options in terms of name-value pairs. See [[Html::a()]].
  361. * @return string the formatted result.
  362. */
  363. public function asUrl($value, $options = [])
  364. {
  365. if ($value === null) {
  366. return $this->nullDisplay;
  367. }
  368. $url = $value;
  369. if (strpos($url, '://') === false) {
  370. $url = 'http://' . $url;
  371. }
  372. return Html::a(Html::encode($value), $url, $options);
  373. }
  374. /**
  375. * Formats the value as a boolean.
  376. * @param mixed $value the value to be formatted.
  377. * @return string the formatted result.
  378. * @see booleanFormat
  379. */
  380. public function asBoolean($value)
  381. {
  382. if ($value === null) {
  383. return $this->nullDisplay;
  384. }
  385. return $value ? $this->booleanFormat[1] : $this->booleanFormat[0];
  386. }
  387. // date and time formats
  388. /**
  389. * Formats the value as a date.
  390. * @param integer|string|DateTime $value the value to be formatted. The following
  391. * types of value are supported:
  392. *
  393. * - an integer representing a UNIX timestamp
  394. * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
  395. * The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
  396. * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object
  397. *
  398. * @param string $format the format used to convert the value into a date string.
  399. * If null, [[dateFormat]] will be used.
  400. *
  401. * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
  402. * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
  403. *
  404. * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
  405. * PHP [date()](http://php.net/manual/de/function.date.php)-function.
  406. *
  407. * @return string the formatted result.
  408. * @throws InvalidParamException if the input value can not be evaluated as a date value.
  409. * @throws InvalidConfigException if the date format is invalid.
  410. * @see dateFormat
  411. */
  412. public function asDate($value, $format = null)
  413. {
  414. if ($format === null) {
  415. $format = $this->dateFormat;
  416. }
  417. return $this->formatDateTimeValue($value, $format, 'date');
  418. }
  419. /**
  420. * Formats the value as a time.
  421. * @param integer|string|DateTime $value the value to be formatted. The following
  422. * types of value are supported:
  423. *
  424. * - an integer representing a UNIX timestamp
  425. * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
  426. * The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
  427. * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object
  428. *
  429. * @param string $format the format used to convert the value into a date string.
  430. * If null, [[timeFormat]] will be used.
  431. *
  432. * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
  433. * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
  434. *
  435. * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
  436. * PHP [date()](http://php.net/manual/de/function.date.php)-function.
  437. *
  438. * @return string the formatted result.
  439. * @throws InvalidParamException if the input value can not be evaluated as a date value.
  440. * @throws InvalidConfigException if the date format is invalid.
  441. * @see timeFormat
  442. */
  443. public function asTime($value, $format = null)
  444. {
  445. if ($format === null) {
  446. $format = $this->timeFormat;
  447. }
  448. return $this->formatDateTimeValue($value, $format, 'time');
  449. }
  450. /**
  451. * Formats the value as a datetime.
  452. * @param integer|string|DateTime $value the value to be formatted. The following
  453. * types of value are supported:
  454. *
  455. * - an integer representing a UNIX timestamp
  456. * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
  457. * The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
  458. * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object
  459. *
  460. * @param string $format the format used to convert the value into a date string.
  461. * If null, [[dateFormat]] will be used.
  462. *
  463. * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
  464. * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
  465. *
  466. * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
  467. * PHP [date()](http://php.net/manual/de/function.date.php)-function.
  468. *
  469. * @return string the formatted result.
  470. * @throws InvalidParamException if the input value can not be evaluated as a date value.
  471. * @throws InvalidConfigException if the date format is invalid.
  472. * @see datetimeFormat
  473. */
  474. public function asDatetime($value, $format = null)
  475. {
  476. if ($format === null) {
  477. $format = $this->datetimeFormat;
  478. }
  479. return $this->formatDateTimeValue($value, $format, 'datetime');
  480. }
  481. /**
  482. * @var array map of short format names to IntlDateFormatter constant values.
  483. */
  484. private $_dateFormats = [
  485. 'short' => 3, // IntlDateFormatter::SHORT,
  486. 'medium' => 2, // IntlDateFormatter::MEDIUM,
  487. 'long' => 1, // IntlDateFormatter::LONG,
  488. 'full' => 0, // IntlDateFormatter::FULL,
  489. ];
  490. /**
  491. * @param integer|string|DateTime $value the value to be formatted. The following
  492. * types of value are supported:
  493. *
  494. * - an integer representing a UNIX timestamp
  495. * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
  496. * The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
  497. * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object
  498. *
  499. * @param string $format the format used to convert the value into a date string.
  500. * @param string $type 'date', 'time', or 'datetime'.
  501. * @throws InvalidConfigException if the date format is invalid.
  502. * @return string the formatted result.
  503. */
  504. private function formatDateTimeValue($value, $format, $type)
  505. {
  506. $timeZone = $this->timeZone;
  507. // avoid time zone conversion for date-only values
  508. if ($type === 'date') {
  509. list($timestamp, $hasTimeInfo) = $this->normalizeDatetimeValue($value, true);
  510. if (!$hasTimeInfo) {
  511. $timeZone = $this->defaultTimeZone;
  512. }
  513. } else {
  514. $timestamp = $this->normalizeDatetimeValue($value);
  515. }
  516. if ($timestamp === null) {
  517. return $this->nullDisplay;
  518. }
  519. // intl does not work with dates >=2038 or <=1901 on 32bit machines, fall back to PHP
  520. $year = $timestamp->format('Y');
  521. if ($this->_intlLoaded && !(PHP_INT_SIZE == 4 && ($year <= 1901 || $year >= 2038))) {
  522. if (strncmp($format, 'php:', 4) === 0) {
  523. $format = FormatConverter::convertDatePhpToIcu(substr($format, 4));
  524. }
  525. if (isset($this->_dateFormats[$format])) {
  526. if ($type === 'date') {
  527. $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], IntlDateFormatter::NONE, $timeZone);
  528. } elseif ($type === 'time') {
  529. $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, $this->_dateFormats[$format], $timeZone);
  530. } else {
  531. $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], $this->_dateFormats[$format], $timeZone);
  532. }
  533. } else {
  534. $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE, $timeZone, null, $format);
  535. }
  536. if ($formatter === null) {
  537. throw new InvalidConfigException(intl_get_error_message());
  538. }
  539. // make IntlDateFormatter work with DateTimeImmutable
  540. if ($timestamp instanceof \DateTimeImmutable) {
  541. $timestamp = new DateTime($timestamp->format(DateTime::ISO8601), $timestamp->getTimezone());
  542. }
  543. return $formatter->format($timestamp);
  544. } else {
  545. if (strncmp($format, 'php:', 4) === 0) {
  546. $format = substr($format, 4);
  547. } else {
  548. $format = FormatConverter::convertDateIcuToPhp($format, $type, $this->locale);
  549. }
  550. if ($timeZone != null) {
  551. if ($timestamp instanceof \DateTimeImmutable) {
  552. $timestamp = $timestamp->setTimezone(new DateTimeZone($timeZone));
  553. } else {
  554. $timestamp->setTimezone(new DateTimeZone($timeZone));
  555. }
  556. }
  557. return $timestamp->format($format);
  558. }
  559. }
  560. /**
  561. * Normalizes the given datetime value as a DateTime object that can be taken by various date/time formatting methods.
  562. *
  563. * @param integer|string|DateTime $value the datetime value to be normalized. The following
  564. * types of value are supported:
  565. *
  566. * - an integer representing a UNIX timestamp
  567. * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
  568. * The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
  569. * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object
  570. *
  571. * @param boolean $checkTimeInfo whether to also check if the date/time value has some time information attached.
  572. * Defaults to `false`. If `true`, the method will then return an array with the first element being the normalized
  573. * timestamp and the second a boolean indicating whether the timestamp has time information or it is just a date value.
  574. * This parameter is available since version 2.0.1.
  575. * @return DateTime|array the normalized datetime value.
  576. * Since version 2.0.1 this may also return an array if `$checkTimeInfo` is true.
  577. * The first element of the array is the normalized timestamp and the second is a boolean indicating whether
  578. * the timestamp has time information or it is just a date value.
  579. * @throws InvalidParamException if the input value can not be evaluated as a date value.
  580. */
  581. protected function normalizeDatetimeValue($value, $checkTimeInfo = false)
  582. {
  583. // checking for DateTime and DateTimeInterface is not redundant, DateTimeInterface is only in PHP>5.5
  584. if ($value === null || $value instanceof DateTime || $value instanceof DateTimeInterface) {
  585. // skip any processing
  586. return $checkTimeInfo ? [$value, true] : $value;
  587. }
  588. if (empty($value)) {
  589. $value = 0;
  590. }
  591. try {
  592. if (is_numeric($value)) { // process as unix timestamp, which is always in UTC
  593. if (($timestamp = DateTime::createFromFormat('U', $value, new DateTimeZone('UTC'))) === false) {
  594. throw new InvalidParamException("Failed to parse '$value' as a UNIX timestamp.");
  595. }
  596. return $checkTimeInfo ? [$timestamp, true] : $timestamp;
  597. } 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)
  598. return $checkTimeInfo ? [$timestamp, false] : $timestamp;
  599. } 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)
  600. return $checkTimeInfo ? [$timestamp, true] : $timestamp;
  601. }
  602. // finally try to create a DateTime object with the value
  603. if ($checkTimeInfo) {
  604. $timestamp = new DateTime($value, new DateTimeZone($this->defaultTimeZone));
  605. $info = date_parse($value);
  606. return [$timestamp, !($info['hour'] === false && $info['minute'] === false && $info['second'] === false)];
  607. } else {
  608. return new DateTime($value, new DateTimeZone($this->defaultTimeZone));
  609. }
  610. } catch(\Exception $e) {
  611. throw new InvalidParamException("'$value' is not a valid date time value: " . $e->getMessage()
  612. . "\n" . print_r(DateTime::getLastErrors(), true), $e->getCode(), $e);
  613. }
  614. }
  615. /**
  616. * Formats a date, time or datetime in a float number as UNIX timestamp (seconds since 01-01-1970).
  617. * @param integer|string|DateTime $value the value to be formatted. The following
  618. * types of value are supported:
  619. *
  620. * - an integer representing a UNIX timestamp
  621. * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
  622. * The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
  623. * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object
  624. *
  625. * @return string the formatted result.
  626. */
  627. public function asTimestamp($value)
  628. {
  629. if ($value === null) {
  630. return $this->nullDisplay;
  631. }
  632. $timestamp = $this->normalizeDatetimeValue($value);
  633. return number_format($timestamp->format('U'), 0, '.', '');
  634. }
  635. /**
  636. * Formats the value as the time interval between a date and now in human readable form.
  637. *
  638. * This method can be used in three different ways:
  639. *
  640. * 1. Using a timestamp that is relative to `now`.
  641. * 2. Using a timestamp that is relative to the `$referenceTime`.
  642. * 3. Using a `DateInterval` object.
  643. *
  644. * @param integer|string|DateTime|DateInterval $value the value to be formatted. The following
  645. * types of value are supported:
  646. *
  647. * - an integer representing a UNIX timestamp
  648. * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
  649. * The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
  650. * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object
  651. * - a PHP DateInterval object (a positive time interval will refer to the past, a negative one to the future)
  652. *
  653. * @param integer|string|DateTime $referenceTime if specified the value is used as a reference time instead of `now`
  654. * when `$value` is not a `DateInterval` object.
  655. * @return string the formatted result.
  656. * @throws InvalidParamException if the input value can not be evaluated as a date value.
  657. */
  658. public function asRelativeTime($value, $referenceTime = null)
  659. {
  660. if ($value === null) {
  661. return $this->nullDisplay;
  662. }
  663. if ($value instanceof DateInterval) {
  664. $interval = $value;
  665. } else {
  666. $timestamp = $this->normalizeDatetimeValue($value);
  667. if ($timestamp === false) {
  668. // $value is not a valid date/time value, so we try
  669. // to create a DateInterval with it
  670. try {
  671. $interval = new DateInterval($value);
  672. } catch (\Exception $e) {
  673. // invalid date/time and invalid interval
  674. return $this->nullDisplay;
  675. }
  676. } else {
  677. $timeZone = new DateTimeZone($this->timeZone);
  678. if ($referenceTime === null) {
  679. $dateNow = new DateTime('now', $timeZone);
  680. } else {
  681. $dateNow = $this->normalizeDatetimeValue($referenceTime);
  682. $dateNow->setTimezone($timeZone);
  683. }
  684. $dateThen = $timestamp->setTimezone($timeZone);
  685. $interval = $dateThen->diff($dateNow);
  686. }
  687. }
  688. if ($interval->invert) {
  689. if ($interval->y >= 1) {
  690. return Yii::t('yii', 'in {delta, plural, =1{a year} other{# years}}', ['delta' => $interval->y], $this->locale);
  691. }
  692. if ($interval->m >= 1) {
  693. return Yii::t('yii', 'in {delta, plural, =1{a month} other{# months}}', ['delta' => $interval->m], $this->locale);
  694. }
  695. if ($interval->d >= 1) {
  696. return Yii::t('yii', 'in {delta, plural, =1{a day} other{# days}}', ['delta' => $interval->d], $this->locale);
  697. }
  698. if ($interval->h >= 1) {
  699. return Yii::t('yii', 'in {delta, plural, =1{an hour} other{# hours}}', ['delta' => $interval->h], $this->locale);
  700. }
  701. if ($interval->i >= 1) {
  702. return Yii::t('yii', 'in {delta, plural, =1{a minute} other{# minutes}}', ['delta' => $interval->i], $this->locale);
  703. }
  704. if ($interval->s == 0) {
  705. return Yii::t('yii', 'just now', [], $this->locale);
  706. }
  707. return Yii::t('yii', 'in {delta, plural, =1{a second} other{# seconds}}', ['delta' => $interval->s], $this->locale);
  708. } else {
  709. if ($interval->y >= 1) {
  710. return Yii::t('yii', '{delta, plural, =1{a year} other{# years}} ago', ['delta' => $interval->y], $this->locale);
  711. }
  712. if ($interval->m >= 1) {
  713. return Yii::t('yii', '{delta, plural, =1{a month} other{# months}} ago', ['delta' => $interval->m], $this->locale);
  714. }
  715. if ($interval->d >= 1) {
  716. return Yii::t('yii', '{delta, plural, =1{a day} other{# days}} ago', ['delta' => $interval->d], $this->locale);
  717. }
  718. if ($interval->h >= 1) {
  719. return Yii::t('yii', '{delta, plural, =1{an hour} other{# hours}} ago', ['delta' => $interval->h], $this->locale);
  720. }
  721. if ($interval->i >= 1) {
  722. return Yii::t('yii', '{delta, plural, =1{a minute} other{# minutes}} ago', ['delta' => $interval->i], $this->locale);
  723. }
  724. if ($interval->s == 0) {
  725. return Yii::t('yii', 'just now', [], $this->locale);
  726. }
  727. return Yii::t('yii', '{delta, plural, =1{a second} other{# seconds}} ago', ['delta' => $interval->s], $this->locale);
  728. }
  729. }
  730. // number formats
  731. /**
  732. * Formats the value as an integer number by removing any decimal digits without rounding.
  733. *
  734. * @param mixed $value the value to be formatted.
  735. * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
  736. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
  737. * @return string the formatted result.
  738. * @throws InvalidParamException if the input value is not numeric.
  739. */
  740. public function asInteger($value, $options = [], $textOptions = [])
  741. {
  742. if ($value === null) {
  743. return $this->nullDisplay;
  744. }
  745. $value = $this->normalizeNumericValue($value);
  746. if ($this->_intlLoaded) {
  747. $f = $this->createNumberFormatter(NumberFormatter::DECIMAL, null, $options, $textOptions);
  748. $f->setAttribute(NumberFormatter::FRACTION_DIGITS, 0);
  749. return $f->format($value, NumberFormatter::TYPE_INT64);
  750. } else {
  751. return number_format((int) $value, 0, $this->decimalSeparator, $this->thousandSeparator);
  752. }
  753. }
  754. /**
  755. * Formats the value as a decimal number.
  756. *
  757. * Property [[decimalSeparator]] will be used to represent the decimal point. The
  758. * value is rounded automatically to the defined decimal digits.
  759. *
  760. * @param mixed $value the value to be formatted.
  761. * @param integer $decimals the number of digits after the decimal point. If not given the number of digits is determined from the
  762. * [[locale]] and if the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available defaults to `2`.
  763. * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
  764. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
  765. * @return string the formatted result.
  766. * @throws InvalidParamException if the input value is not numeric.
  767. * @see decimalSeparator
  768. * @see thousandSeparator
  769. */
  770. public function asDecimal($value, $decimals = null, $options = [], $textOptions = [])
  771. {
  772. if ($value === null) {
  773. return $this->nullDisplay;
  774. }
  775. $value = $this->normalizeNumericValue($value);
  776. if ($this->_intlLoaded) {
  777. $f = $this->createNumberFormatter(NumberFormatter::DECIMAL, $decimals, $options, $textOptions);
  778. return $f->format($value);
  779. } else {
  780. if ($decimals === null){
  781. $decimals = 2;
  782. }
  783. return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator);
  784. }
  785. }
  786. /**
  787. * Formats the value as a percent number with "%" sign.
  788. *
  789. * @param mixed $value the value to be formatted. It must be a factor e.g. `0.75` will result in `75%`.
  790. * @param integer $decimals the number of digits after the decimal point.
  791. * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
  792. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
  793. * @return string the formatted result.
  794. * @throws InvalidParamException if the input value is not numeric.
  795. */
  796. public function asPercent($value, $decimals = null, $options = [], $textOptions = [])
  797. {
  798. if ($value === null) {
  799. return $this->nullDisplay;
  800. }
  801. $value = $this->normalizeNumericValue($value);
  802. if ($this->_intlLoaded) {
  803. $f = $this->createNumberFormatter(NumberFormatter::PERCENT, $decimals, $options, $textOptions);
  804. return $f->format($value);
  805. } else {
  806. if ($decimals === null){
  807. $decimals = 0;
  808. }
  809. $value = $value * 100;
  810. return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator) . '%';
  811. }
  812. }
  813. /**
  814. * Formats the value as a scientific number.
  815. *
  816. * @param mixed $value the value to be formatted.
  817. * @param integer $decimals the number of digits after the decimal point.
  818. * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
  819. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
  820. * @return string the formatted result.
  821. * @throws InvalidParamException if the input value is not numeric.
  822. */
  823. public function asScientific($value, $decimals = null, $options = [], $textOptions = [])
  824. {
  825. if ($value === null) {
  826. return $this->nullDisplay;
  827. }
  828. $value = $this->normalizeNumericValue($value);
  829. if ($this->_intlLoaded){
  830. $f = $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $decimals, $options, $textOptions);
  831. return $f->format($value);
  832. } else {
  833. if ($decimals !== null) {
  834. return sprintf("%.{$decimals}E", $value);
  835. } else {
  836. return sprintf("%.E", $value);
  837. }
  838. }
  839. }
  840. /**
  841. * Formats the value as a currency number.
  842. *
  843. * This function does not requires the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed
  844. * to work but it is highly recommended to install it to get good formatting results.
  845. *
  846. * @param mixed $value the value to be formatted.
  847. * @param string $currency the 3-letter ISO 4217 currency code indicating the currency to use.
  848. * If null, [[currencyCode]] will be used.
  849. * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
  850. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
  851. * @return string the formatted result.
  852. * @throws InvalidParamException if the input value is not numeric.
  853. * @throws InvalidConfigException if no currency is given and [[currencyCode]] is not defined.
  854. */
  855. public function asCurrency($value, $currency = null, $options = [], $textOptions = [])
  856. {
  857. if ($value === null) {
  858. return $this->nullDisplay;
  859. }
  860. $value = $this->normalizeNumericValue($value);
  861. if ($this->_intlLoaded) {
  862. $formatter = $this->createNumberFormatter(NumberFormatter::CURRENCY, null, $options, $textOptions);
  863. if ($currency === null) {
  864. if ($this->currencyCode === null) {
  865. $currency = $formatter->getSymbol(NumberFormatter::INTL_CURRENCY_SYMBOL);
  866. } else {
  867. $currency = $this->currencyCode;
  868. }
  869. }
  870. return $formatter->formatCurrency($value, $currency);
  871. } else {
  872. if ($currency === null) {
  873. if ($this->currencyCode === null) {
  874. throw new InvalidConfigException('The default currency code for the formatter is not defined.');
  875. }
  876. $currency = $this->currencyCode;
  877. }
  878. return $currency . ' ' . $this->asDecimal($value, 2, $options, $textOptions);
  879. }
  880. }
  881. /**
  882. * Formats the value as a number spellout.
  883. *
  884. * This function requires the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed.
  885. *
  886. * @param mixed $value the value to be formatted
  887. * @return string the formatted result.
  888. * @throws InvalidParamException if the input value is not numeric.
  889. * @throws InvalidConfigException when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available.
  890. */
  891. public function asSpellout($value)
  892. {
  893. if ($value === null) {
  894. return $this->nullDisplay;
  895. }
  896. $value = $this->normalizeNumericValue($value);
  897. if ($this->_intlLoaded){
  898. $f = $this->createNumberFormatter(NumberFormatter::SPELLOUT);
  899. return $f->format($value);
  900. } else {
  901. throw new InvalidConfigException('Format as Spellout is only supported when PHP intl extension is installed.');
  902. }
  903. }
  904. /**
  905. * Formats the value as a ordinal value of a number.
  906. *
  907. * This function requires the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed.
  908. *
  909. * @param mixed $value the value to be formatted
  910. * @return string the formatted result.
  911. * @throws InvalidParamException if the input value is not numeric.
  912. * @throws InvalidConfigException when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available.
  913. */
  914. public function asOrdinal($value)
  915. {
  916. if ($value === null) {
  917. return $this->nullDisplay;
  918. }
  919. $value = $this->normalizeNumericValue($value);
  920. if ($this->_intlLoaded){
  921. $f = $this->createNumberFormatter(NumberFormatter::ORDINAL);
  922. return $f->format($value);
  923. } else {
  924. throw new InvalidConfigException('Format as Ordinal is only supported when PHP intl extension is installed.');
  925. }
  926. }
  927. /**
  928. * Formats the value in bytes as a size in human readable form for example `12 KB`.
  929. *
  930. * This is the short form of [[asSize]].
  931. *
  932. * If [[sizeFormatBase]] is 1024, [binary prefixes](http://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...)
  933. * are used in the formatting result.
  934. *
  935. * @param integer $value value in bytes to be formatted.
  936. * @param integer $decimals the number of digits after the decimal point.
  937. * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
  938. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
  939. * @return string the formatted result.
  940. * @throws InvalidParamException if the input value is not numeric.
  941. * @see sizeFormat
  942. * @see asSize
  943. */
  944. public function asShortSize($value, $decimals = null, $options = [], $textOptions = [])
  945. {
  946. if ($value === null) {
  947. return $this->nullDisplay;
  948. }
  949. list($params, $position) = $this->formatSizeNumber($value, $decimals, $options, $textOptions);
  950. if ($this->sizeFormatBase == 1024) {
  951. switch ($position) {
  952. case 0: return Yii::t('yii', '{nFormatted} B', $params, $this->locale);
  953. case 1: return Yii::t('yii', '{nFormatted} KiB', $params, $this->locale);
  954. case 2: return Yii::t('yii', '{nFormatted} MiB', $params, $this->locale);
  955. case 3: return Yii::t('yii', '{nFormatted} GiB', $params, $this->locale);
  956. case 4: return Yii::t('yii', '{nFormatted} TiB', $params, $this->locale);
  957. default: return Yii::t('yii', '{nFormatted} PiB', $params, $this->locale);
  958. }
  959. } else {
  960. switch ($position) {
  961. case 0: return Yii::t('yii', '{nFormatted} B', $params, $this->locale);
  962. case 1: return Yii::t('yii', '{nFormatted} KB', $params, $this->locale);
  963. case 2: return Yii::t('yii', '{nFormatted} MB', $params, $this->locale);
  964. case 3: return Yii::t('yii', '{nFormatted} GB', $params, $this->locale);
  965. case 4: return Yii::t('yii', '{nFormatted} TB', $params, $this->locale);
  966. default: return Yii::t('yii', '{nFormatted} PB', $params, $this->locale);
  967. }
  968. }
  969. }
  970. /**
  971. * Formats the value in bytes as a size in human readable form, for example `12 kilobytes`.
  972. *
  973. * If [[sizeFormatBase]] is 1024, [binary prefixes](http://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...)
  974. * are used in the formatting result.
  975. *
  976. * @param integer $value value in bytes to be formatted.
  977. * @param integer $decimals the number of digits after the decimal point.
  978. * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
  979. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
  980. * @return string the formatted result.
  981. * @throws InvalidParamException if the input value is not numeric.
  982. * @see sizeFormat
  983. * @see asShortSize
  984. */
  985. public function asSize($value, $decimals = null, $options = [], $textOptions = [])
  986. {
  987. if ($value === null) {
  988. return $this->nullDisplay;
  989. }
  990. list($params, $position) = $this->formatSizeNumber($value, $decimals, $options, $textOptions);
  991. if ($this->sizeFormatBase == 1024) {
  992. switch ($position) {
  993. case 0: return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->locale);
  994. case 1: return Yii::t('yii', '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}', $params, $this->locale);
  995. case 2: return Yii::t('yii', '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}', $params, $this->locale);
  996. case 3: return Yii::t('yii', '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}', $params, $this->locale);
  997. case 4: return Yii::t('yii', '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}', $params, $this->locale);
  998. default: return Yii::t('yii', '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}', $params, $this->locale);
  999. }
  1000. } else {
  1001. switch ($position) {
  1002. case 0: return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->locale);
  1003. case 1: return Yii::t('yii', '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}', $params, $this->locale);
  1004. case 2: return Yii::t('yii', '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}', $params, $this->locale);
  1005. case 3: return Yii::t('yii', '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}', $params, $this->locale);
  1006. case 4: return Yii::t('yii', '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}', $params, $this->locale);
  1007. default: return Yii::t('yii', '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}', $params, $this->locale);
  1008. }
  1009. }
  1010. }
  1011. /**
  1012. * Given the value in bytes formats number part of the human readable form.
  1013. *
  1014. * @param string|integer|float $value value in bytes to be formatted.
  1015. * @param integer $decimals the number of digits after the decimal point
  1016. * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
  1017. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
  1018. * @return array [parameters for Yii::t containing formatted number, internal position of size unit]
  1019. * @throws InvalidParamException if the input value is not numeric.
  1020. */
  1021. private function formatSizeNumber($value, $decimals, $options, $textOptions)
  1022. {
  1023. if (is_string($value) && is_numeric($value)) {
  1024. $value = (int) $value;
  1025. }
  1026. if (!is_numeric($value)) {
  1027. throw new InvalidParamException("'$value' is not a numeric value.");
  1028. }
  1029. $position = 0;
  1030. do {
  1031. if ($value < $this->sizeFormatBase) {
  1032. break;
  1033. }
  1034. $value = $value / $this->sizeFormatBase;
  1035. $position++;
  1036. } while ($position < 5);
  1037. // no decimals for bytes
  1038. if ($position === 0) {
  1039. $decimals = 0;
  1040. } elseif ($decimals !== null) {
  1041. $value = round($value, $decimals);
  1042. }
  1043. // disable grouping for edge cases like 1023 to get 1023 B instead of 1,023 B
  1044. $oldThousandSeparator = $this->thousandSeparator;
  1045. $this->thousandSeparator = '';
  1046. if ($this->_intlLoaded) {
  1047. $options[NumberFormatter::GROUPING_USED] = false;
  1048. }
  1049. // format the size value
  1050. $params = [
  1051. // this is the unformatted number used for the plural rule
  1052. 'n' => $value,
  1053. // this is the formatted number used for display
  1054. 'nFormatted' => $this->asDecimal($value, $decimals, $options, $textOptions),
  1055. ];
  1056. $this->thousandSeparator = $oldThousandSeparator;
  1057. return [$params, $position];
  1058. }
  1059. /**
  1060. * @param $value
  1061. * @return float
  1062. * @throws InvalidParamException
  1063. */
  1064. protected function normalizeNumericValue($value)
  1065. {
  1066. if (empty($value)) {
  1067. return 0;
  1068. }
  1069. if (is_string($value) && is_numeric($value)) {
  1070. $value = (float) $value;
  1071. }
  1072. if (!is_numeric($value)) {
  1073. throw new InvalidParamException("'$value' is not a numeric value.");
  1074. }
  1075. return $value;
  1076. }
  1077. /**
  1078. * Creates a number formatter based on the given type and format.
  1079. *
  1080. * You may override this method to create a number formatter based on patterns.
  1081. *
  1082. * @param integer $style the type of the number formatter.
  1083. * Values: NumberFormatter::DECIMAL, ::CURRENCY, ::PERCENT, ::SCIENTIFIC, ::SPELLOUT, ::ORDINAL
  1084. * ::DURATION, ::PATTERN_RULEBASED, ::DEFAULT_STYLE, ::IGNORE
  1085. * @param integer $decimals the number of digits after the decimal point.
  1086. * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
  1087. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
  1088. * @return NumberFormatter the created formatter instance
  1089. */
  1090. protected function createNumberFormatter($style, $decimals = null, $options = [], $textOptions = [])
  1091. {
  1092. $formatter = new NumberFormatter($this->locale, $style);
  1093. if ($this->decimalSeparator !== null) {
  1094. $formatter->setSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL, $this->decimalSeparator);
  1095. }
  1096. if ($this->thousandSeparator !== null) {
  1097. $formatter->setSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL, $this->thousandSeparator);
  1098. }
  1099. if ($decimals !== null) {
  1100. $formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimals);
  1101. $formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimals);
  1102. }
  1103. foreach ($this->numberFormatterTextOptions as $name => $attribute) {
  1104. $formatter->setTextAttribute($name, $attribute);
  1105. }
  1106. foreach ($textOptions as $name => $attribute) {
  1107. $formatter->setTextAttribute($name, $attribute);
  1108. }
  1109. foreach ($this->numberFormatterOptions as $name => $value) {
  1110. $formatter->setAttribute($name, $value);
  1111. }
  1112. foreach ($options as $name => $value) {
  1113. $formatter->setAttribute($name, $value);
  1114. }
  1115. return $formatter;
  1116. }
  1117. }