Formatter.php 92 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083
  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 Closure;
  9. use DateInterval;
  10. use DateTime;
  11. use DateTimeInterface;
  12. use DateTimeZone;
  13. use IntlDateFormatter;
  14. use NumberFormatter;
  15. use Yii;
  16. use yii\base\Component;
  17. use yii\base\InvalidArgumentException;
  18. use yii\base\InvalidConfigException;
  19. use yii\helpers\FormatConverter;
  20. use yii\helpers\Html;
  21. use yii\helpers\HtmlPurifier;
  22. /**
  23. * Formatter provides a set of commonly used data formatting methods.
  24. *
  25. * The formatting methods provided by Formatter are all named in the form of `asXyz()`.
  26. * The behavior of some of them may be configured via the properties of Formatter. For example,
  27. * by configuring [[dateFormat]], one may control how [[asDate()]] formats the value into a date string.
  28. *
  29. * Formatter is configured as an application component in [[\yii\base\Application]] by default.
  30. * You can access that instance via `Yii::$app->formatter`.
  31. *
  32. * The Formatter class is designed to format values according to a [[locale]]. For this feature to work
  33. * the [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) has to be installed.
  34. * 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. * Note that even if the intl extension is installed, formatting date and time values for years >=2038 or <=1901
  37. * on 32bit systems will fall back to the PHP implementation because intl uses a 32bit UNIX timestamp internally.
  38. * On a 64bit system the intl formatter is used in all cases if installed.
  39. *
  40. * > Note: The Formatter class is meant to be used for formatting values for display to users in different
  41. * > languages and time zones. If you need to format a date or time in machine readable format, use the
  42. * > PHP [date()](https://secure.php.net/manual/en/function.date.php) function instead.
  43. *
  44. * @author Qiang Xue <qiang.xue@gmail.com>
  45. * @author Enrica Ruedin <e.ruedin@guggach.com>
  46. * @author Carsten Brandt <mail@cebe.cc>
  47. * @since 2.0
  48. */
  49. class Formatter extends Component
  50. {
  51. /**
  52. * @since 2.0.13
  53. */
  54. const UNIT_SYSTEM_METRIC = 'metric';
  55. /**
  56. * @since 2.0.13
  57. */
  58. const UNIT_SYSTEM_IMPERIAL = 'imperial';
  59. /**
  60. * @since 2.0.13
  61. */
  62. const FORMAT_WIDTH_LONG = 'long';
  63. /**
  64. * @since 2.0.13
  65. */
  66. const FORMAT_WIDTH_SHORT = 'short';
  67. /**
  68. * @since 2.0.13
  69. */
  70. const UNIT_LENGTH = 'length';
  71. /**
  72. * @since 2.0.13
  73. */
  74. const UNIT_WEIGHT = 'mass';
  75. /**
  76. * @var string the text to be displayed when formatting a `null` value.
  77. * Defaults to `'<span class="not-set">(not set)</span>'`, where `(not set)`
  78. * will be translated according to [[locale]].
  79. */
  80. public $nullDisplay;
  81. /**
  82. * @var array the text to be displayed when formatting a boolean value. The first element corresponds
  83. * to the text displayed for `false`, the second element for `true`.
  84. * Defaults to `['No', 'Yes']`, where `Yes` and `No`
  85. * will be translated according to [[locale]].
  86. */
  87. public $booleanFormat;
  88. /**
  89. * @var string the locale ID that is used to localize the date and number formatting.
  90. * For number and date formatting this is only effective when the
  91. * [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) is installed.
  92. * If not set, [[\yii\base\Application::language]] will be used.
  93. */
  94. public $locale;
  95. /**
  96. * @var string the language code (e.g. `en-US`, `en`) that is used to translate internal messages.
  97. * If not set, [[locale]] will be used (without the `@calendar` param, if included).
  98. *
  99. * @since 2.0.28
  100. */
  101. public $language;
  102. /**
  103. * @var string the time zone to use for formatting time and date values.
  104. *
  105. * This can be any value that may be passed to [date_default_timezone_set()](https://secure.php.net/manual/en/function.date-default-timezone-set.php)
  106. * e.g. `UTC`, `Europe/Berlin` or `America/Chicago`.
  107. * Refer to the [php manual](https://secure.php.net/manual/en/timezones.php) for available time zones.
  108. * If this property is not set, [[\yii\base\Application::timeZone]] will be used.
  109. *
  110. * 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.
  111. * If you store your data in a different time zone in the database, you have to adjust [[defaultTimeZone]] accordingly.
  112. */
  113. public $timeZone;
  114. /**
  115. * @var string the time zone that is assumed for input values if they do not include a time zone explicitly.
  116. *
  117. * The value must be a valid time zone identifier, e.g. `UTC`, `Europe/Berlin` or `America/Chicago`.
  118. * Please refer to the [php manual](https://secure.php.net/manual/en/timezones.php) for available time zones.
  119. *
  120. * It defaults to `UTC` so you only have to adjust this value if you store datetime values in another time zone in your database.
  121. *
  122. * Note that a UNIX timestamp is always in UTC by its definition. That means that specifying a default time zone different from
  123. * UTC has no effect on date values given as UNIX timestamp.
  124. *
  125. * @since 2.0.1
  126. */
  127. public $defaultTimeZone = 'UTC';
  128. /**
  129. * @var string the default format string to be used to format a [[asDate()|date]].
  130. * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
  131. *
  132. * 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).
  133. * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
  134. * PHP [date()](https://secure.php.net/manual/en/function.date.php)-function.
  135. *
  136. * For example:
  137. *
  138. * ```php
  139. * 'MM/dd/yyyy' // date in ICU format
  140. * 'php:m/d/Y' // the same date in PHP format
  141. * ```
  142. */
  143. public $dateFormat = 'medium';
  144. /**
  145. * @var string the default format string to be used to format a [[asTime()|time]].
  146. * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
  147. *
  148. * 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).
  149. * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
  150. * PHP [date()](https://secure.php.net/manual/en/function.date.php)-function.
  151. *
  152. * For example:
  153. *
  154. * ```php
  155. * 'HH:mm:ss' // time in ICU format
  156. * 'php:H:i:s' // the same time in PHP format
  157. * ```
  158. */
  159. public $timeFormat = 'medium';
  160. /**
  161. * @var string the default format string to be used to format a [[asDatetime()|date and time]].
  162. * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
  163. *
  164. * 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).
  165. *
  166. * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
  167. * PHP [date()](https://secure.php.net/manual/en/function.date.php)-function.
  168. *
  169. * For example:
  170. *
  171. * ```php
  172. * 'MM/dd/yyyy HH:mm:ss' // date and time in ICU format
  173. * 'php:m/d/Y H:i:s' // the same date and time in PHP format
  174. * ```
  175. */
  176. public $datetimeFormat = 'medium';
  177. /**
  178. * @var \IntlCalendar|int|null the calendar to be used for date formatting. The value of this property will be directly
  179. * passed to the [constructor of the `IntlDateFormatter` class](https://secure.php.net/manual/en/intldateformatter.create.php).
  180. *
  181. * Defaults to `null`, which means the Gregorian calendar will be used. You may also explicitly pass the constant
  182. * `\IntlDateFormatter::GREGORIAN` for Gregorian calendar.
  183. *
  184. * To use an alternative calendar like for example the [Jalali calendar](https://en.wikipedia.org/wiki/Jalali_calendar),
  185. * set this property to `\IntlDateFormatter::TRADITIONAL`.
  186. * The calendar must then be specified in the [[locale]], for example for the persian calendar the configuration for the formatter would be:
  187. *
  188. * ```php
  189. * 'formatter' => [
  190. * 'locale' => 'fa_IR@calendar=persian',
  191. * 'calendar' => \IntlDateFormatter::TRADITIONAL,
  192. * ],
  193. * ```
  194. *
  195. * Available calendar names can be found in the [ICU manual](http://userguide.icu-project.org/datetime/calendar).
  196. *
  197. * Since PHP 5.5 you may also use an instance of the [[\IntlCalendar]] class.
  198. * Check the [PHP manual](https://secure.php.net/manual/en/intldateformatter.create.php) for more details.
  199. *
  200. * If the [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) is not available, setting this property will have no effect.
  201. *
  202. * @see https://secure.php.net/manual/en/intldateformatter.create.php
  203. * @see https://secure.php.net/manual/en/class.intldateformatter.php#intl.intldateformatter-constants.calendartypes
  204. * @see https://secure.php.net/manual/en/class.intlcalendar.php
  205. * @since 2.0.7
  206. */
  207. public $calendar;
  208. /**
  209. * @var string the character displayed as the decimal point when formatting a number.
  210. * If not set, the decimal separator corresponding to [[locale]] will be used.
  211. * If [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) is not available, the default value is '.'.
  212. */
  213. public $decimalSeparator;
  214. /**
  215. * @var string the character displayed as the decimal point when formatting a currency.
  216. * If not set, the currency decimal separator corresponding to [[locale]] will be used.
  217. * If [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) is not available, setting this property will have no effect.
  218. * @since 2.0.35
  219. */
  220. public $currencyDecimalSeparator;
  221. /**
  222. * @var string the character displayed as the thousands separator (also called grouping separator) character when formatting a number.
  223. * If not set, the thousand separator corresponding to [[locale]] will be used.
  224. * If [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) is not available, the default value is ','.
  225. */
  226. public $thousandSeparator;
  227. /**
  228. * @var array a list of name value pairs that are passed to the
  229. * intl [NumberFormatter::setAttribute()](https://secure.php.net/manual/en/numberformatter.setattribute.php) method of all
  230. * the number formatter objects created by [[createNumberFormatter()]].
  231. * This property takes only effect if the [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) is installed.
  232. *
  233. * Please refer to the [PHP manual](https://secure.php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformatattribute)
  234. * for the possible options.
  235. *
  236. * For example to adjust the maximum and minimum value of fraction digits you can configure this property like the following:
  237. *
  238. * ```php
  239. * [
  240. * NumberFormatter::MIN_FRACTION_DIGITS => 0,
  241. * NumberFormatter::MAX_FRACTION_DIGITS => 2,
  242. * ]
  243. * ```
  244. */
  245. public $numberFormatterOptions = [];
  246. /**
  247. * @var array a list of name value pairs that are passed to the
  248. * intl [NumberFormatter::setTextAttribute()](https://secure.php.net/manual/en/numberformatter.settextattribute.php) method of all
  249. * the number formatter objects created by [[createNumberFormatter()]].
  250. * This property takes only effect if the [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) is installed.
  251. *
  252. * Please refer to the [PHP manual](https://secure.php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformattextattribute)
  253. * for the possible options.
  254. *
  255. * For example to change the minus sign for negative numbers you can configure this property like the following:
  256. *
  257. * ```php
  258. * [
  259. * NumberFormatter::NEGATIVE_PREFIX => 'MINUS',
  260. * ]
  261. * ```
  262. */
  263. public $numberFormatterTextOptions = [];
  264. /**
  265. * @var array a list of name value pairs that are passed to the
  266. * intl [NumberFormatter::setSymbol()](https://secure.php.net/manual/en/numberformatter.setsymbol.php) method of all
  267. * the number formatter objects created by [[createNumberFormatter()]].
  268. * This property takes only effect if the [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) is installed.
  269. *
  270. * Please refer to the [PHP manual](https://secure.php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformatsymbol)
  271. * for the possible options.
  272. *
  273. * For example to choose a custom currency symbol, e.g. [U+20BD](http://unicode-table.com/en/20BD/) instead of `руб.` for Russian Ruble:
  274. *
  275. * ```php
  276. * [
  277. * NumberFormatter::CURRENCY_SYMBOL => '₽',
  278. * ]
  279. * ```
  280. *
  281. * @since 2.0.4
  282. */
  283. public $numberFormatterSymbols = [];
  284. /**
  285. * @var string the 3-letter ISO 4217 currency code indicating the default currency to use for [[asCurrency]].
  286. * If not set, the currency code corresponding to [[locale]] will be used.
  287. * Note that in this case the [[locale]] has to be specified with a country code, e.g. `en-US` otherwise it
  288. * is not possible to determine the default currency.
  289. */
  290. public $currencyCode;
  291. /**
  292. * @var int the base at which a kilobyte is calculated (1000 or 1024 bytes per kilobyte), used by [[asSize]] and [[asShortSize]].
  293. * Defaults to 1024.
  294. */
  295. public $sizeFormatBase = 1024;
  296. /**
  297. * @var string default system of measure units. Defaults to [[UNIT_SYSTEM_METRIC]].
  298. * Possible values:
  299. * - [[UNIT_SYSTEM_METRIC]]
  300. * - [[UNIT_SYSTEM_IMPERIAL]]
  301. *
  302. * @see asLength
  303. * @see asWeight
  304. * @since 2.0.13
  305. */
  306. public $systemOfUnits = self::UNIT_SYSTEM_METRIC;
  307. /**
  308. * @var array configuration of weight and length measurement units.
  309. * This array contains the most usable measurement units, but you can change it
  310. * in case you have some special requirements.
  311. *
  312. * For example, you can add smaller measure unit:
  313. *
  314. * ```php
  315. * $this->measureUnits[self::UNIT_LENGTH][self::UNIT_SYSTEM_METRIC] = [
  316. * 'nanometer' => 0.000001
  317. * ]
  318. * ```
  319. * @see asLength
  320. * @see asWeight
  321. * @since 2.0.13
  322. */
  323. public $measureUnits = [
  324. self::UNIT_LENGTH => [
  325. self::UNIT_SYSTEM_IMPERIAL => [
  326. 'inch' => 1,
  327. 'foot' => 12,
  328. 'yard' => 36,
  329. 'chain' => 792,
  330. 'furlong' => 7920,
  331. 'mile' => 63360,
  332. ],
  333. self::UNIT_SYSTEM_METRIC => [
  334. 'millimeter' => 1,
  335. 'centimeter' => 10,
  336. 'meter' => 1000,
  337. 'kilometer' => 1000000,
  338. ],
  339. ],
  340. self::UNIT_WEIGHT => [
  341. self::UNIT_SYSTEM_IMPERIAL => [
  342. 'grain' => 1,
  343. 'drachm' => 27.34375,
  344. 'ounce' => 437.5,
  345. 'pound' => 7000,
  346. 'stone' => 98000,
  347. 'quarter' => 196000,
  348. 'hundredweight' => 784000,
  349. 'ton' => 15680000,
  350. ],
  351. self::UNIT_SYSTEM_METRIC => [
  352. 'gram' => 1,
  353. 'kilogram' => 1000,
  354. 'ton' => 1000000,
  355. ],
  356. ],
  357. ];
  358. /**
  359. * @var array The base units that are used as multipliers for smallest possible unit from [[measureUnits]].
  360. * @since 2.0.13
  361. */
  362. public $baseUnits = [
  363. self::UNIT_LENGTH => [
  364. self::UNIT_SYSTEM_IMPERIAL => 12, // 1 feet = 12 inches
  365. self::UNIT_SYSTEM_METRIC => 1000, // 1 meter = 1000 millimeters
  366. ],
  367. self::UNIT_WEIGHT => [
  368. self::UNIT_SYSTEM_IMPERIAL => 7000, // 1 pound = 7000 grains
  369. self::UNIT_SYSTEM_METRIC => 1000, // 1 kilogram = 1000 grams
  370. ],
  371. ];
  372. /**
  373. * @var bool whether the [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) is loaded.
  374. */
  375. private $_intlLoaded = false;
  376. /**
  377. * @var \ResourceBundle cached ResourceBundle object used to read unit translations
  378. */
  379. private $_resourceBundle;
  380. /**
  381. * @var array cached unit translation patterns
  382. */
  383. private $_unitMessages = [];
  384. /**
  385. * {@inheritdoc}
  386. */
  387. public function init()
  388. {
  389. if ($this->timeZone === null) {
  390. $this->timeZone = Yii::$app->timeZone;
  391. }
  392. if ($this->locale === null) {
  393. $this->locale = Yii::$app->language;
  394. }
  395. if ($this->language === null) {
  396. $this->language = strtok($this->locale, '@');
  397. }
  398. if ($this->booleanFormat === null) {
  399. $this->booleanFormat = [Yii::t('yii', 'No', [], $this->language), Yii::t('yii', 'Yes', [], $this->language)];
  400. }
  401. if ($this->nullDisplay === null) {
  402. $this->nullDisplay = '<span class="not-set">' . Yii::t('yii', '(not set)', [], $this->language) . '</span>';
  403. }
  404. $this->_intlLoaded = extension_loaded('intl');
  405. if (!$this->_intlLoaded) {
  406. if ($this->decimalSeparator === null) {
  407. $this->decimalSeparator = '.';
  408. }
  409. if ($this->thousandSeparator === null) {
  410. $this->thousandSeparator = ',';
  411. }
  412. }
  413. }
  414. /**
  415. * Formats the value based on the given format type.
  416. * This method will call one of the "as" methods available in this class to do the formatting.
  417. * For type "xyz", the method "asXyz" will be used. For example, if the format is "html",
  418. * then [[asHtml()]] will be used. Format names are case insensitive.
  419. * @param mixed $value the value to be formatted.
  420. * @param string|array|Closure $format the format of the value, e.g., "html", "text" or an anonymous function
  421. * returning the formatted value.
  422. *
  423. * To specify additional parameters of the formatting method, you may use an array.
  424. * The first element of the array specifies the format name, while the rest of the elements will be used as the
  425. * parameters to the formatting method. For example, a format of `['date', 'Y-m-d']` will cause the invocation
  426. * of `asDate($value, 'Y-m-d')`.
  427. *
  428. * The anonymous function signature should be: `function($value, $formatter)`,
  429. * where `$value` is the value that should be formatted and `$formatter` is an instance of the Formatter class,
  430. * which can be used to call other formatting functions.
  431. * The possibility to use an anonymous function is available since version 2.0.13.
  432. * @return string the formatting result.
  433. * @throws InvalidArgumentException if the format type is not supported by this class.
  434. */
  435. public function format($value, $format)
  436. {
  437. if ($format instanceof Closure) {
  438. return call_user_func($format, $value, $this);
  439. } elseif (is_array($format)) {
  440. if (!isset($format[0])) {
  441. throw new InvalidArgumentException('The $format array must contain at least one element.');
  442. }
  443. $f = $format[0];
  444. $format[0] = $value;
  445. $params = $format;
  446. $format = $f;
  447. } else {
  448. $params = [$value];
  449. }
  450. $method = 'as' . $format;
  451. if ($this->hasMethod($method)) {
  452. return call_user_func_array([$this, $method], $params);
  453. }
  454. throw new InvalidArgumentException("Unknown format type: $format");
  455. }
  456. // simple formats
  457. /**
  458. * Formats the value as is without any formatting.
  459. * This method simply returns back the parameter without any format.
  460. * The only exception is a `null` value which will be formatted using [[nullDisplay]].
  461. * @param mixed $value the value to be formatted.
  462. * @return string the formatted result.
  463. */
  464. public function asRaw($value)
  465. {
  466. if ($value === null) {
  467. return $this->nullDisplay;
  468. }
  469. return $value;
  470. }
  471. /**
  472. * Formats the value as an HTML-encoded plain text.
  473. * @param string|null $value the value to be formatted.
  474. * @return string the formatted result.
  475. */
  476. public function asText($value)
  477. {
  478. if ($value === null) {
  479. return $this->nullDisplay;
  480. }
  481. return Html::encode($value);
  482. }
  483. /**
  484. * Formats the value as an HTML-encoded plain text with newlines converted into breaks.
  485. * @param string|null $value the value to be formatted.
  486. * @return string the formatted result.
  487. */
  488. public function asNtext($value)
  489. {
  490. if ($value === null) {
  491. return $this->nullDisplay;
  492. }
  493. return nl2br(Html::encode($value));
  494. }
  495. /**
  496. * Formats the value as HTML-encoded text paragraphs.
  497. * Each text paragraph is enclosed within a `<p>` tag.
  498. * One or multiple consecutive empty lines divide two paragraphs.
  499. * @param string|null $value the value to be formatted.
  500. * @return string the formatted result.
  501. */
  502. public function asParagraphs($value)
  503. {
  504. if ($value === null) {
  505. return $this->nullDisplay;
  506. }
  507. return str_replace('<p></p>', '', '<p>' . preg_replace('/\R{2,}/u', "</p>\n<p>", Html::encode($value)) . '</p>');
  508. }
  509. /**
  510. * Formats the value as HTML text.
  511. * The value will be purified using [[HtmlPurifier]] to avoid XSS attacks.
  512. * Use [[asRaw()]] if you do not want any purification of the value.
  513. * @param string|null $value the value to be formatted.
  514. * @param array|null $config the configuration for the HTMLPurifier class.
  515. * @return string the formatted result.
  516. */
  517. public function asHtml($value, $config = null)
  518. {
  519. if ($value === null) {
  520. return $this->nullDisplay;
  521. }
  522. return HtmlPurifier::process($value, $config);
  523. }
  524. /**
  525. * Formats the value as a mailto link.
  526. * @param string|null $value the value to be formatted.
  527. * @param array $options the tag options in terms of name-value pairs. See [[Html::mailto()]].
  528. * @return string the formatted result.
  529. */
  530. public function asEmail($value, $options = [])
  531. {
  532. if ($value === null) {
  533. return $this->nullDisplay;
  534. }
  535. return Html::mailto(Html::encode($value), $value, $options);
  536. }
  537. /**
  538. * Formats the value as an image tag.
  539. * @param mixed $value the value to be formatted.
  540. * @param array $options the tag options in terms of name-value pairs. See [[Html::img()]].
  541. * @return string the formatted result.
  542. */
  543. public function asImage($value, $options = [])
  544. {
  545. if ($value === null) {
  546. return $this->nullDisplay;
  547. }
  548. return Html::img($value, $options);
  549. }
  550. /**
  551. * Formats the value as a hyperlink.
  552. * @param mixed $value the value to be formatted.
  553. * @param array $options the tag options in terms of name-value pairs. See [[Html::a()]].
  554. * @return string the formatted result.
  555. */
  556. public function asUrl($value, $options = [])
  557. {
  558. if ($value === null) {
  559. return $this->nullDisplay;
  560. }
  561. $url = $value;
  562. if (strpos($url, '://') === false) {
  563. $url = 'http://' . $url;
  564. }
  565. return Html::a(Html::encode($value), $url, $options);
  566. }
  567. /**
  568. * Formats the value as a boolean.
  569. * @param mixed $value the value to be formatted.
  570. * @return string the formatted result.
  571. * @see booleanFormat
  572. */
  573. public function asBoolean($value)
  574. {
  575. if ($value === null) {
  576. return $this->nullDisplay;
  577. }
  578. return $value ? $this->booleanFormat[1] : $this->booleanFormat[0];
  579. }
  580. // date and time formats
  581. /**
  582. * Formats the value as a date.
  583. * @param int|string|DateTime|DateTimeInterface $value the value to be formatted. The following
  584. * types of value are supported:
  585. *
  586. * - an integer representing a UNIX timestamp. A UNIX timestamp is always in UTC by its definition.
  587. * - a string that can be [parsed to create a DateTime object](https://secure.php.net/manual/en/datetime.formats.php).
  588. * The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
  589. * - a PHP [DateTime](https://secure.php.net/manual/en/class.datetime.php) object. You may set the time zone
  590. * for the DateTime object to specify the source time zone.
  591. *
  592. * The formatter will convert date values according to [[timeZone]] before formatting it.
  593. * If no timezone conversion should be performed, you need to set [[defaultTimeZone]] and [[timeZone]] to the same value.
  594. * Also no conversion will be performed on values that have no time information, e.g. `"2017-06-05"`.
  595. *
  596. * @param string|null $format the format used to convert the value into a date string.
  597. * If null, [[dateFormat]] will be used.
  598. *
  599. * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
  600. * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
  601. *
  602. * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
  603. * PHP [date()](https://secure.php.net/manual/en/function.date.php)-function.
  604. *
  605. * @return string the formatted result.
  606. * @throws InvalidArgumentException if the input value can not be evaluated as a date value.
  607. * @throws InvalidConfigException if the date format is invalid.
  608. * @see dateFormat
  609. */
  610. public function asDate($value, $format = null)
  611. {
  612. if ($format === null) {
  613. $format = $this->dateFormat;
  614. }
  615. return $this->formatDateTimeValue($value, $format, 'date');
  616. }
  617. /**
  618. * Formats the value as a time.
  619. * @param int|string|DateTime|DateTimeInterface $value the value to be formatted. The following
  620. * types of value are supported:
  621. *
  622. * - an integer representing a UNIX timestamp. A UNIX timestamp is always in UTC by its definition.
  623. * - a string that can be [parsed to create a DateTime object](https://secure.php.net/manual/en/datetime.formats.php).
  624. * The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
  625. * - a PHP [DateTime](https://secure.php.net/manual/en/class.datetime.php) object. You may set the time zone
  626. * for the DateTime object to specify the source time zone.
  627. *
  628. * The formatter will convert date values according to [[timeZone]] before formatting it.
  629. * If no timezone conversion should be performed, you need to set [[defaultTimeZone]] and [[timeZone]] to the same value.
  630. *
  631. * @param string|null $format the format used to convert the value into a date string.
  632. * If null, [[timeFormat]] will be used.
  633. *
  634. * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
  635. * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
  636. *
  637. * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
  638. * PHP [date()](https://secure.php.net/manual/en/function.date.php)-function.
  639. *
  640. * @return string the formatted result.
  641. * @throws InvalidArgumentException if the input value can not be evaluated as a date value.
  642. * @throws InvalidConfigException if the date format is invalid.
  643. * @see timeFormat
  644. */
  645. public function asTime($value, $format = null)
  646. {
  647. if ($format === null) {
  648. $format = $this->timeFormat;
  649. }
  650. return $this->formatDateTimeValue($value, $format, 'time');
  651. }
  652. /**
  653. * Formats the value as a datetime.
  654. * @param int|string|DateTime|DateTimeInterface $value the value to be formatted. The following
  655. * types of value are supported:
  656. *
  657. * - an integer representing a UNIX timestamp. A UNIX timestamp is always in UTC by its definition.
  658. * - a string that can be [parsed to create a DateTime object](https://secure.php.net/manual/en/datetime.formats.php).
  659. * The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
  660. * - a PHP [DateTime](https://secure.php.net/manual/en/class.datetime.php) object. You may set the time zone
  661. * for the DateTime object to specify the source time zone.
  662. *
  663. * The formatter will convert date values according to [[timeZone]] before formatting it.
  664. * If no timezone conversion should be performed, you need to set [[defaultTimeZone]] and [[timeZone]] to the same value.
  665. *
  666. * @param string|null $format the format used to convert the value into a date string.
  667. * If null, [[datetimeFormat]] will be used.
  668. *
  669. * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
  670. * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
  671. *
  672. * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
  673. * PHP [date()](https://secure.php.net/manual/en/function.date.php)-function.
  674. *
  675. * @return string the formatted result.
  676. * @throws InvalidArgumentException if the input value can not be evaluated as a date value.
  677. * @throws InvalidConfigException if the date format is invalid.
  678. * @see datetimeFormat
  679. */
  680. public function asDatetime($value, $format = null)
  681. {
  682. if ($format === null) {
  683. $format = $this->datetimeFormat;
  684. }
  685. return $this->formatDateTimeValue($value, $format, 'datetime');
  686. }
  687. /**
  688. * @var array map of short format names to IntlDateFormatter constant values.
  689. */
  690. private $_dateFormats = [
  691. 'short' => 3, // IntlDateFormatter::SHORT,
  692. 'medium' => 2, // IntlDateFormatter::MEDIUM,
  693. 'long' => 1, // IntlDateFormatter::LONG,
  694. 'full' => 0, // IntlDateFormatter::FULL,
  695. ];
  696. /**
  697. * @param int|string|DateTime|DateTimeInterface $value the value to be formatted. The following
  698. * types of value are supported:
  699. *
  700. * - an integer representing a UNIX timestamp
  701. * - a string that can be [parsed to create a DateTime object](https://secure.php.net/manual/en/datetime.formats.php).
  702. * The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
  703. * - a PHP [DateTime](https://secure.php.net/manual/en/class.datetime.php) object
  704. *
  705. * @param string $format the format used to convert the value into a date string.
  706. * @param string $type 'date', 'time', or 'datetime'.
  707. * @throws InvalidConfigException if the date format is invalid.
  708. * @return string the formatted result.
  709. */
  710. private function formatDateTimeValue($value, $format, $type)
  711. {
  712. $timeZone = $this->timeZone;
  713. // avoid time zone conversion for date-only and time-only values
  714. if ($type === 'date' || $type === 'time') {
  715. list($timestamp, $hasTimeInfo, $hasDateInfo) = $this->normalizeDatetimeValue($value, true);
  716. if ($type === 'date' && !$hasTimeInfo || $type === 'time' && !$hasDateInfo) {
  717. $timeZone = $this->defaultTimeZone;
  718. }
  719. } else {
  720. $timestamp = $this->normalizeDatetimeValue($value);
  721. }
  722. if ($timestamp === null) {
  723. return $this->nullDisplay;
  724. }
  725. // intl does not work with dates >=2038 or <=1901 on 32bit machines, fall back to PHP
  726. $year = $timestamp->format('Y');
  727. if ($this->_intlLoaded && !(PHP_INT_SIZE === 4 && ($year <= 1901 || $year >= 2038))) {
  728. if (strncmp($format, 'php:', 4) === 0) {
  729. $format = FormatConverter::convertDatePhpToIcu(substr($format, 4));
  730. }
  731. if (isset($this->_dateFormats[$format])) {
  732. if ($type === 'date') {
  733. $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], IntlDateFormatter::NONE, $timeZone, $this->calendar);
  734. } elseif ($type === 'time') {
  735. $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, $this->_dateFormats[$format], $timeZone, $this->calendar);
  736. } else {
  737. $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], $this->_dateFormats[$format], $timeZone, $this->calendar);
  738. }
  739. } else {
  740. $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE, $timeZone, $this->calendar, $format);
  741. }
  742. if ($formatter === null) {
  743. throw new InvalidConfigException(intl_get_error_message());
  744. }
  745. // make IntlDateFormatter work with DateTimeImmutable
  746. if ($timestamp instanceof \DateTimeImmutable) {
  747. $timestamp = new DateTime($timestamp->format(DateTime::ISO8601), $timestamp->getTimezone());
  748. }
  749. return $formatter->format($timestamp);
  750. }
  751. if (strncmp($format, 'php:', 4) === 0) {
  752. $format = substr($format, 4);
  753. } else {
  754. $format = FormatConverter::convertDateIcuToPhp($format, $type, $this->locale);
  755. }
  756. if ($timeZone != null) {
  757. if ($timestamp instanceof \DateTimeImmutable) {
  758. $timestamp = $timestamp->setTimezone(new DateTimeZone($timeZone));
  759. } else {
  760. $timestamp->setTimezone(new DateTimeZone($timeZone));
  761. }
  762. }
  763. return $timestamp->format($format);
  764. }
  765. /**
  766. * Normalizes the given datetime value as a DateTime object that can be taken by various date/time formatting methods.
  767. *
  768. * @param int|string|DateTime|DateTimeInterface $value the datetime value to be normalized. The following
  769. * types of value are supported:
  770. *
  771. * - an integer representing a UNIX timestamp
  772. * - a string that can be [parsed to create a DateTime object](https://secure.php.net/manual/en/datetime.formats.php).
  773. * The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
  774. * - a PHP [DateTime](https://secure.php.net/manual/en/class.datetime.php) object
  775. *
  776. * @param bool $checkDateTimeInfo whether to also check if the date/time value has some time and date information attached.
  777. * Defaults to `false`. If `true`, the method will then return an array with the first element being the normalized
  778. * timestamp, the second a boolean indicating whether the timestamp has time information and third a boolean indicating
  779. * whether the timestamp has date information.
  780. * This parameter is available since version 2.0.1.
  781. * @return DateTime|array the normalized datetime value.
  782. * Since version 2.0.1 this may also return an array if `$checkDateTimeInfo` is true.
  783. * The first element of the array is the normalized timestamp and the second is a boolean indicating whether
  784. * the timestamp has time information or it is just a date value.
  785. * Since version 2.0.12 the array has third boolean element indicating whether the timestamp has date information
  786. * or it is just a time value.
  787. * @throws InvalidArgumentException if the input value can not be evaluated as a date value.
  788. */
  789. protected function normalizeDatetimeValue($value, $checkDateTimeInfo = false)
  790. {
  791. // checking for DateTime and DateTimeInterface is not redundant, DateTimeInterface is only in PHP>5.5
  792. if ($value === null || $value instanceof DateTime || $value instanceof DateTimeInterface) {
  793. // skip any processing
  794. return $checkDateTimeInfo ? [$value, true, true] : $value;
  795. }
  796. if (empty($value)) {
  797. $value = 0;
  798. }
  799. try {
  800. if (is_numeric($value)) { // process as unix timestamp, which is always in UTC
  801. $timestamp = new DateTime('@' . (int) $value, new DateTimeZone('UTC'));
  802. return $checkDateTimeInfo ? [$timestamp, true, true] : $timestamp;
  803. } 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)
  804. return $checkDateTimeInfo ? [$timestamp, false, true] : $timestamp;
  805. } 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)
  806. return $checkDateTimeInfo ? [$timestamp, true, true] : $timestamp;
  807. }
  808. // finally try to create a DateTime object with the value
  809. if ($checkDateTimeInfo) {
  810. $timestamp = new DateTime($value, new DateTimeZone($this->defaultTimeZone));
  811. $info = date_parse($value);
  812. return [
  813. $timestamp,
  814. !($info['hour'] === false && $info['minute'] === false && $info['second'] === false),
  815. !($info['year'] === false && $info['month'] === false && $info['day'] === false && empty($info['zone'])),
  816. ];
  817. }
  818. return new DateTime($value, new DateTimeZone($this->defaultTimeZone));
  819. } catch (\Exception $e) {
  820. throw new InvalidArgumentException("'$value' is not a valid date time value: " . $e->getMessage()
  821. . "\n" . print_r(DateTime::getLastErrors(), true), $e->getCode(), $e);
  822. }
  823. }
  824. /**
  825. * Formats a date, time or datetime in a float number as UNIX timestamp (seconds since 01-01-1970).
  826. * @param int|string|DateTime|DateTimeInterface|null $value the value to be formatted. The following
  827. * types of value are supported:
  828. *
  829. * - an integer representing a UNIX timestamp
  830. * - a string that can be [parsed to create a DateTime object](https://secure.php.net/manual/en/datetime.formats.php).
  831. * The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
  832. * - a PHP [DateTime](https://secure.php.net/manual/en/class.datetime.php) object
  833. *
  834. * @return string the formatted result.
  835. */
  836. public function asTimestamp($value)
  837. {
  838. if ($value === null) {
  839. return $this->nullDisplay;
  840. }
  841. $timestamp = $this->normalizeDatetimeValue($value);
  842. return number_format($timestamp->format('U'), 0, '.', '');
  843. }
  844. /**
  845. * Formats the value as the time interval between a date and now in human readable form.
  846. *
  847. * This method can be used in three different ways:
  848. *
  849. * 1. Using a timestamp that is relative to `now`.
  850. * 2. Using a timestamp that is relative to the `$referenceTime`.
  851. * 3. Using a `DateInterval` object.
  852. *
  853. * @param int|string|DateTime|DateTimeInterface|DateInterval $value the value to be formatted. The following
  854. * types of value are supported:
  855. *
  856. * - an integer representing a UNIX timestamp
  857. * - a string that can be [parsed to create a DateTime object](https://secure.php.net/manual/en/datetime.formats.php).
  858. * The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
  859. * - a PHP [DateTime](https://secure.php.net/manual/en/class.datetime.php) object
  860. * - a PHP DateInterval object (a positive time interval will refer to the past, a negative one to the future)
  861. *
  862. * @param int|string|DateTime|DateTimeInterface|null $referenceTime if specified the value is used as a reference time instead of `now`
  863. * when `$value` is not a `DateInterval` object.
  864. * @return string the formatted result.
  865. * @throws InvalidArgumentException if the input value can not be evaluated as a date value.
  866. */
  867. public function asRelativeTime($value, $referenceTime = null)
  868. {
  869. if ($value === null) {
  870. return $this->nullDisplay;
  871. }
  872. if ($value instanceof DateInterval) {
  873. $interval = $value;
  874. } else {
  875. $timestamp = $this->normalizeDatetimeValue($value);
  876. if ($timestamp === false) {
  877. // $value is not a valid date/time value, so we try
  878. // to create a DateInterval with it
  879. try {
  880. $interval = new DateInterval($value);
  881. } catch (\Exception $e) {
  882. // invalid date/time and invalid interval
  883. return $this->nullDisplay;
  884. }
  885. } else {
  886. $timeZone = new DateTimeZone($this->timeZone);
  887. if ($referenceTime === null) {
  888. $dateNow = new DateTime('now', $timeZone);
  889. } else {
  890. $dateNow = $this->normalizeDatetimeValue($referenceTime);
  891. $dateNow->setTimezone($timeZone);
  892. }
  893. $dateThen = $timestamp->setTimezone($timeZone);
  894. $interval = $dateThen->diff($dateNow);
  895. }
  896. }
  897. if ($interval->invert) {
  898. if ($interval->y >= 1) {
  899. return Yii::t('yii', 'in {delta, plural, =1{a year} other{# years}}', ['delta' => $interval->y], $this->language);
  900. }
  901. if ($interval->m >= 1) {
  902. return Yii::t('yii', 'in {delta, plural, =1{a month} other{# months}}', ['delta' => $interval->m], $this->language);
  903. }
  904. if ($interval->d >= 1) {
  905. return Yii::t('yii', 'in {delta, plural, =1{a day} other{# days}}', ['delta' => $interval->d], $this->language);
  906. }
  907. if ($interval->h >= 1) {
  908. return Yii::t('yii', 'in {delta, plural, =1{an hour} other{# hours}}', ['delta' => $interval->h], $this->language);
  909. }
  910. if ($interval->i >= 1) {
  911. return Yii::t('yii', 'in {delta, plural, =1{a minute} other{# minutes}}', ['delta' => $interval->i], $this->language);
  912. }
  913. if ($interval->s == 0) {
  914. return Yii::t('yii', 'just now', [], $this->language);
  915. }
  916. return Yii::t('yii', 'in {delta, plural, =1{a second} other{# seconds}}', ['delta' => $interval->s], $this->language);
  917. }
  918. if ($interval->y >= 1) {
  919. return Yii::t('yii', '{delta, plural, =1{a year} other{# years}} ago', ['delta' => $interval->y], $this->language);
  920. }
  921. if ($interval->m >= 1) {
  922. return Yii::t('yii', '{delta, plural, =1{a month} other{# months}} ago', ['delta' => $interval->m], $this->language);
  923. }
  924. if ($interval->d >= 1) {
  925. return Yii::t('yii', '{delta, plural, =1{a day} other{# days}} ago', ['delta' => $interval->d], $this->language);
  926. }
  927. if ($interval->h >= 1) {
  928. return Yii::t('yii', '{delta, plural, =1{an hour} other{# hours}} ago', ['delta' => $interval->h], $this->language);
  929. }
  930. if ($interval->i >= 1) {
  931. return Yii::t('yii', '{delta, plural, =1{a minute} other{# minutes}} ago', ['delta' => $interval->i], $this->language);
  932. }
  933. if ($interval->s == 0) {
  934. return Yii::t('yii', 'just now', [], $this->language);
  935. }
  936. return Yii::t('yii', '{delta, plural, =1{a second} other{# seconds}} ago', ['delta' => $interval->s], $this->language);
  937. }
  938. /**
  939. * Represents the value as duration in human readable format.
  940. *
  941. * @param DateInterval|string|int|null $value the value to be formatted. Acceptable formats:
  942. * - [DateInterval object](https://secure.php.net/manual/ru/class.dateinterval.php)
  943. * - integer - number of seconds. For example: value `131` represents `2 minutes, 11 seconds`
  944. * - ISO8601 duration format. For example, all of these values represent `1 day, 2 hours, 30 minutes` duration:
  945. * `2015-01-01T13:00:00Z/2015-01-02T13:30:00Z` - between two datetime values
  946. * `2015-01-01T13:00:00Z/P1D2H30M` - time interval after datetime value
  947. * `P1D2H30M/2015-01-02T13:30:00Z` - time interval before datetime value
  948. * `P1D2H30M` - simply a date interval
  949. * `P-1D2H30M` - a negative date interval (`-1 day, 2 hours, 30 minutes`)
  950. *
  951. * @param string $implodeString will be used to concatenate duration parts. Defaults to `, `.
  952. * @param string $negativeSign will be prefixed to the formatted duration, when it is negative. Defaults to `-`.
  953. * @return string the formatted duration.
  954. * @since 2.0.7
  955. */
  956. public function asDuration($value, $implodeString = ', ', $negativeSign = '-')
  957. {
  958. if ($value === null) {
  959. return $this->nullDisplay;
  960. }
  961. if ($value instanceof DateInterval) {
  962. $isNegative = $value->invert;
  963. $interval = $value;
  964. } elseif (is_numeric($value)) {
  965. $isNegative = $value < 0;
  966. $zeroDateTime = (new DateTime())->setTimestamp(0);
  967. $valueDateTime = (new DateTime())->setTimestamp(abs($value));
  968. $interval = $valueDateTime->diff($zeroDateTime);
  969. } elseif (strncmp($value, 'P-', 2) === 0) {
  970. $interval = new DateInterval('P' . substr($value, 2));
  971. $isNegative = true;
  972. } else {
  973. $interval = new DateInterval($value);
  974. $isNegative = $interval->invert;
  975. }
  976. $parts = [];
  977. if ($interval->y > 0) {
  978. $parts[] = Yii::t('yii', '{delta, plural, =1{1 year} other{# years}}', ['delta' => $interval->y], $this->language);
  979. }
  980. if ($interval->m > 0) {
  981. $parts[] = Yii::t('yii', '{delta, plural, =1{1 month} other{# months}}', ['delta' => $interval->m], $this->language);
  982. }
  983. if ($interval->d > 0) {
  984. $parts[] = Yii::t('yii', '{delta, plural, =1{1 day} other{# days}}', ['delta' => $interval->d], $this->language);
  985. }
  986. if ($interval->h > 0) {
  987. $parts[] = Yii::t('yii', '{delta, plural, =1{1 hour} other{# hours}}', ['delta' => $interval->h], $this->language);
  988. }
  989. if ($interval->i > 0) {
  990. $parts[] = Yii::t('yii', '{delta, plural, =1{1 minute} other{# minutes}}', ['delta' => $interval->i], $this->language);
  991. }
  992. if ($interval->s > 0) {
  993. $parts[] = Yii::t('yii', '{delta, plural, =1{1 second} other{# seconds}}', ['delta' => $interval->s], $this->language);
  994. }
  995. if ($interval->s === 0 && empty($parts)) {
  996. $parts[] = Yii::t('yii', '{delta, plural, =1{1 second} other{# seconds}}', ['delta' => $interval->s], $this->language);
  997. $isNegative = false;
  998. }
  999. return empty($parts) ? $this->nullDisplay : (($isNegative ? $negativeSign : '') . implode($implodeString, $parts));
  1000. }
  1001. // number formats
  1002. /**
  1003. * Formats the value as an integer number by removing any decimal digits without rounding.
  1004. *
  1005. * Since 2.0.16 numbers that are mispresented after normalization are formatted as strings using fallback function
  1006. * without [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) support. For very big numbers it's
  1007. * recommended to pass them as strings and not use scientific notation otherwise the output might be wrong.
  1008. *
  1009. * @param mixed $value the value to be formatted.
  1010. * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
  1011. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
  1012. * @return string the formatted result.
  1013. * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
  1014. */
  1015. public function asInteger($value, $options = [], $textOptions = [])
  1016. {
  1017. if ($value === null) {
  1018. return $this->nullDisplay;
  1019. }
  1020. $normalizedValue = $this->normalizeNumericValue($value);
  1021. if ($this->isNormalizedValueMispresented($value, $normalizedValue)) {
  1022. return $this->asIntegerStringFallback((string) $value);
  1023. }
  1024. if ($this->_intlLoaded) {
  1025. $f = $this->createNumberFormatter(NumberFormatter::DECIMAL, null, $options, $textOptions);
  1026. $f->setAttribute(NumberFormatter::FRACTION_DIGITS, 0);
  1027. if (($result = $f->format($normalizedValue, NumberFormatter::TYPE_INT64)) === false) {
  1028. throw new InvalidArgumentException('Formatting integer value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
  1029. }
  1030. return $result;
  1031. }
  1032. return number_format((int) $normalizedValue, 0, $this->decimalSeparator, $this->thousandSeparator);
  1033. }
  1034. /**
  1035. * Formats the value as a decimal number.
  1036. *
  1037. * Property [[decimalSeparator]] will be used to represent the decimal point. The
  1038. * value is rounded automatically to the defined decimal digits.
  1039. *
  1040. * Since 2.0.16 numbers that are mispresented after normalization are formatted as strings using fallback function
  1041. * without [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) support. For very big numbers it's
  1042. * recommended to pass them as strings and not use scientific notation otherwise the output might be wrong.
  1043. *
  1044. * @param mixed $value the value to be formatted.
  1045. * @param int|null $decimals the number of digits after the decimal point.
  1046. * If not given, the number of digits depends in the input value and is determined based on
  1047. * `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
  1048. * using [[$numberFormatterOptions]].
  1049. * If the PHP intl extension is not available, the default value is `2`.
  1050. * If you want consistent behavior between environments where intl is available and not, you should explicitly
  1051. * specify a value here.
  1052. * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
  1053. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
  1054. * @return string the formatted result.
  1055. * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
  1056. * @see decimalSeparator
  1057. * @see thousandSeparator
  1058. */
  1059. public function asDecimal($value, $decimals = null, $options = [], $textOptions = [])
  1060. {
  1061. if ($value === null) {
  1062. return $this->nullDisplay;
  1063. }
  1064. $normalizedValue = $this->normalizeNumericValue($value);
  1065. if ($this->isNormalizedValueMispresented($value, $normalizedValue)) {
  1066. return $this->asDecimalStringFallback((string) $value, $decimals);
  1067. }
  1068. if ($this->_intlLoaded) {
  1069. $f = $this->createNumberFormatter(NumberFormatter::DECIMAL, $decimals, $options, $textOptions);
  1070. if (($result = $f->format($normalizedValue)) === false) {
  1071. throw new InvalidArgumentException('Formatting decimal value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
  1072. }
  1073. return $result;
  1074. }
  1075. if ($decimals === null) {
  1076. $decimals = 2;
  1077. }
  1078. return number_format($normalizedValue, $decimals, $this->decimalSeparator, $this->thousandSeparator);
  1079. }
  1080. /**
  1081. * Formats the value as a percent number with "%" sign.
  1082. *
  1083. * Since 2.0.16 numbers that are mispresented after normalization are formatted as strings using fallback function
  1084. * without [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) support. For very big numbers it's
  1085. * recommended to pass them as strings and not use scientific notation otherwise the output might be wrong.
  1086. *
  1087. * @param mixed $value the value to be formatted. It must be a factor e.g. `0.75` will result in `75%`.
  1088. * @param int|null $decimals the number of digits after the decimal point.
  1089. * If not given, the number of digits depends in the input value and is determined based on
  1090. * `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
  1091. * using [[$numberFormatterOptions]].
  1092. * If the PHP intl extension is not available, the default value is `0`.
  1093. * If you want consistent behavior between environments where intl is available and not, you should explicitly
  1094. * specify a value here.
  1095. * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
  1096. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
  1097. * @return string the formatted result.
  1098. * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
  1099. */
  1100. public function asPercent($value, $decimals = null, $options = [], $textOptions = [])
  1101. {
  1102. if ($value === null) {
  1103. return $this->nullDisplay;
  1104. }
  1105. $normalizedValue = $this->normalizeNumericValue($value);
  1106. if ($this->isNormalizedValueMispresented($value, $normalizedValue)) {
  1107. return $this->asPercentStringFallback((string) $value, $decimals);
  1108. }
  1109. if ($this->_intlLoaded) {
  1110. $f = $this->createNumberFormatter(NumberFormatter::PERCENT, $decimals, $options, $textOptions);
  1111. if (($result = $f->format($normalizedValue)) === false) {
  1112. throw new InvalidArgumentException('Formatting percent value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
  1113. }
  1114. return $result;
  1115. }
  1116. if ($decimals === null) {
  1117. $decimals = 0;
  1118. }
  1119. $normalizedValue *= 100;
  1120. return number_format($normalizedValue, $decimals, $this->decimalSeparator, $this->thousandSeparator) . '%';
  1121. }
  1122. /**
  1123. * Formats the value as a scientific number.
  1124. *
  1125. * @param mixed $value the value to be formatted.
  1126. * @param int|null $decimals the number of digits after the decimal point.
  1127. * If not given, the number of digits depends in the input value and is determined based on
  1128. * `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
  1129. * using [[$numberFormatterOptions]].
  1130. * If the [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) is not available, the default value depends on your PHP configuration.
  1131. * If you want consistent behavior between environments where intl is available and not, you should explicitly
  1132. * specify a value here.
  1133. * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
  1134. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
  1135. * @return string the formatted result.
  1136. * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
  1137. */
  1138. public function asScientific($value, $decimals = null, $options = [], $textOptions = [])
  1139. {
  1140. if ($value === null) {
  1141. return $this->nullDisplay;
  1142. }
  1143. $value = $this->normalizeNumericValue($value);
  1144. if ($this->_intlLoaded) {
  1145. $f = $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $decimals, $options, $textOptions);
  1146. if (($result = $f->format($value)) === false) {
  1147. throw new InvalidArgumentException('Formatting scientific number value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
  1148. }
  1149. return $result;
  1150. }
  1151. if ($decimals !== null) {
  1152. return sprintf("%.{$decimals}E", $value);
  1153. }
  1154. return sprintf('%.E', $value);
  1155. }
  1156. /**
  1157. * Formats the value as a currency number.
  1158. *
  1159. * This function does not require the [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) to be installed
  1160. * to work, but it is highly recommended to install it to get good formatting results.
  1161. *
  1162. * Since 2.0.16 numbers that are mispresented after normalization are formatted as strings using fallback function
  1163. * without PHP intl extension support. For very big numbers it's recommended to pass them as strings and not use
  1164. * scientific notation otherwise the output might be wrong.
  1165. *
  1166. * @param mixed $value the value to be formatted.
  1167. * @param string|null $currency the 3-letter ISO 4217 currency code indicating the currency to use.
  1168. * If null, [[currencyCode]] will be used.
  1169. * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
  1170. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
  1171. * @return string the formatted result.
  1172. * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
  1173. * @throws InvalidConfigException if no currency is given and [[currencyCode]] is not defined.
  1174. */
  1175. public function asCurrency($value, $currency = null, $options = [], $textOptions = [])
  1176. {
  1177. if ($value === null) {
  1178. return $this->nullDisplay;
  1179. }
  1180. $normalizedValue = $this->normalizeNumericValue($value);
  1181. if ($this->isNormalizedValueMispresented($value, $normalizedValue)) {
  1182. return $this->asCurrencyStringFallback((string) $value, $currency);
  1183. }
  1184. if ($this->_intlLoaded) {
  1185. $currency = $currency ?: $this->currencyCode;
  1186. // currency code must be set before fraction digits
  1187. // https://secure.php.net/manual/en/numberformatter.formatcurrency.php#114376
  1188. if ($currency && !isset($textOptions[NumberFormatter::CURRENCY_CODE])) {
  1189. $textOptions[NumberFormatter::CURRENCY_CODE] = $currency;
  1190. }
  1191. $formatter = $this->createNumberFormatter(NumberFormatter::CURRENCY, null, $options, $textOptions);
  1192. if ($currency === null) {
  1193. $result = $formatter->format($normalizedValue);
  1194. } else {
  1195. $result = $formatter->formatCurrency($normalizedValue, $currency);
  1196. }
  1197. if ($result === false) {
  1198. throw new InvalidArgumentException('Formatting currency value failed: ' . $formatter->getErrorCode() . ' ' . $formatter->getErrorMessage());
  1199. }
  1200. return $result;
  1201. }
  1202. if ($currency === null) {
  1203. if ($this->currencyCode === null) {
  1204. throw new InvalidConfigException('The default currency code for the formatter is not defined and the php intl extension is not installed which could take the default currency from the locale.');
  1205. }
  1206. $currency = $this->currencyCode;
  1207. }
  1208. return $currency . ' ' . $this->asDecimal($normalizedValue, 2, $options, $textOptions);
  1209. }
  1210. /**
  1211. * Formats the value as a number spellout.
  1212. *
  1213. * This function requires the [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) to be installed.
  1214. *
  1215. * This formatter does not work well with very big numbers.
  1216. *
  1217. * @param mixed $value the value to be formatted
  1218. * @return string the formatted result.
  1219. * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
  1220. * @throws InvalidConfigException when the [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) is not available.
  1221. */
  1222. public function asSpellout($value)
  1223. {
  1224. if ($value === null) {
  1225. return $this->nullDisplay;
  1226. }
  1227. $value = $this->normalizeNumericValue($value);
  1228. if ($this->_intlLoaded) {
  1229. $f = $this->createNumberFormatter(NumberFormatter::SPELLOUT);
  1230. if (($result = $f->format($value)) === false) {
  1231. throw new InvalidArgumentException('Formatting number as spellout failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
  1232. }
  1233. return $result;
  1234. }
  1235. throw new InvalidConfigException('Format as Spellout is only supported when PHP intl extension is installed.');
  1236. }
  1237. /**
  1238. * Formats the value as a ordinal value of a number.
  1239. *
  1240. * This function requires the [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) to be installed.
  1241. *
  1242. * This formatter does not work well with very big numbers.
  1243. *
  1244. * @param mixed $value the value to be formatted
  1245. * @return string the formatted result.
  1246. * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
  1247. * @throws InvalidConfigException when the [PHP intl extension](https://secure.php.net/manual/en/book.intl.php) is not available.
  1248. */
  1249. public function asOrdinal($value)
  1250. {
  1251. if ($value === null) {
  1252. return $this->nullDisplay;
  1253. }
  1254. $value = $this->normalizeNumericValue($value);
  1255. if ($this->_intlLoaded) {
  1256. $f = $this->createNumberFormatter(NumberFormatter::ORDINAL);
  1257. if (($result = $f->format($value)) === false) {
  1258. throw new InvalidArgumentException('Formatting number as ordinal failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
  1259. }
  1260. return $result;
  1261. }
  1262. throw new InvalidConfigException('Format as Ordinal is only supported when PHP intl extension is installed.');
  1263. }
  1264. /**
  1265. * Formats the value in bytes as a size in human readable form for example `12 kB`.
  1266. *
  1267. * This is the short form of [[asSize]].
  1268. *
  1269. * If [[sizeFormatBase]] is 1024, [binary prefixes](http://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...)
  1270. * are used in the formatting result.
  1271. *
  1272. * @param string|int|float|null $value value in bytes to be formatted.
  1273. * @param int|null $decimals the number of digits after the decimal point.
  1274. * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
  1275. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
  1276. * @return string the formatted result.
  1277. * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
  1278. * @see sizeFormatBase
  1279. * @see asSize
  1280. */
  1281. public function asShortSize($value, $decimals = null, $options = [], $textOptions = [])
  1282. {
  1283. if ($value === null) {
  1284. return $this->nullDisplay;
  1285. }
  1286. list($params, $position) = $this->formatNumber($value, $decimals, 4, $this->sizeFormatBase, $options, $textOptions);
  1287. if ($this->sizeFormatBase == 1024) {
  1288. switch ($position) {
  1289. case 0:
  1290. return Yii::t('yii', '{nFormatted} B', $params, $this->language);
  1291. case 1:
  1292. return Yii::t('yii', '{nFormatted} KiB', $params, $this->language);
  1293. case 2:
  1294. return Yii::t('yii', '{nFormatted} MiB', $params, $this->language);
  1295. case 3:
  1296. return Yii::t('yii', '{nFormatted} GiB', $params, $this->language);
  1297. case 4:
  1298. return Yii::t('yii', '{nFormatted} TiB', $params, $this->language);
  1299. default:
  1300. return Yii::t('yii', '{nFormatted} PiB', $params, $this->language);
  1301. }
  1302. } else {
  1303. switch ($position) {
  1304. case 0:
  1305. return Yii::t('yii', '{nFormatted} B', $params, $this->language);
  1306. case 1:
  1307. return Yii::t('yii', '{nFormatted} kB', $params, $this->language);
  1308. case 2:
  1309. return Yii::t('yii', '{nFormatted} MB', $params, $this->language);
  1310. case 3:
  1311. return Yii::t('yii', '{nFormatted} GB', $params, $this->language);
  1312. case 4:
  1313. return Yii::t('yii', '{nFormatted} TB', $params, $this->language);
  1314. default:
  1315. return Yii::t('yii', '{nFormatted} PB', $params, $this->language);
  1316. }
  1317. }
  1318. }
  1319. /**
  1320. * Formats the value in bytes as a size in human readable form, for example `12 kilobytes`.
  1321. *
  1322. * If [[sizeFormatBase]] is 1024, [binary prefixes](http://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...)
  1323. * are used in the formatting result.
  1324. *
  1325. * @param string|int|float|null $value value in bytes to be formatted.
  1326. * @param int|null $decimals the number of digits after the decimal point.
  1327. * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
  1328. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
  1329. * @return string the formatted result.
  1330. * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
  1331. * @see sizeFormatBase
  1332. * @see asShortSize
  1333. */
  1334. public function asSize($value, $decimals = null, $options = [], $textOptions = [])
  1335. {
  1336. if ($value === null) {
  1337. return $this->nullDisplay;
  1338. }
  1339. list($params, $position) = $this->formatNumber($value, $decimals, 4, $this->sizeFormatBase, $options, $textOptions);
  1340. if ($this->sizeFormatBase == 1024) {
  1341. switch ($position) {
  1342. case 0:
  1343. return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->language);
  1344. case 1:
  1345. return Yii::t('yii', '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}', $params, $this->language);
  1346. case 2:
  1347. return Yii::t('yii', '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}', $params, $this->language);
  1348. case 3:
  1349. return Yii::t('yii', '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}', $params, $this->language);
  1350. case 4:
  1351. return Yii::t('yii', '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}', $params, $this->language);
  1352. default:
  1353. return Yii::t('yii', '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}', $params, $this->language);
  1354. }
  1355. } else {
  1356. switch ($position) {
  1357. case 0:
  1358. return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->language);
  1359. case 1:
  1360. return Yii::t('yii', '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}', $params, $this->language);
  1361. case 2:
  1362. return Yii::t('yii', '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}', $params, $this->language);
  1363. case 3:
  1364. return Yii::t('yii', '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}', $params, $this->language);
  1365. case 4:
  1366. return Yii::t('yii', '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}', $params, $this->language);
  1367. default:
  1368. return Yii::t('yii', '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}', $params, $this->language);
  1369. }
  1370. }
  1371. }
  1372. /**
  1373. * Formats the value as a length in human readable form for example `12 meters`.
  1374. * Check properties [[baseUnits]] if you need to change unit of value as the multiplier
  1375. * of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
  1376. *
  1377. * @param float|int $value value to be formatted.
  1378. * @param int|null $decimals the number of digits after the decimal point.
  1379. * @param array $numberOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
  1380. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
  1381. * @return string the formatted result.
  1382. * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
  1383. * @throws InvalidConfigException when INTL is not installed or does not contain required information.
  1384. * @see asLength
  1385. * @since 2.0.13
  1386. * @author John Was <janek.jan@gmail.com>
  1387. */
  1388. public function asLength($value, $decimals = null, $numberOptions = [], $textOptions = [])
  1389. {
  1390. return $this->formatUnit(self::UNIT_LENGTH, self::FORMAT_WIDTH_LONG, $value, null, null, $decimals, $numberOptions, $textOptions);
  1391. }
  1392. /**
  1393. * Formats the value as a length in human readable form for example `12 m`.
  1394. * This is the short form of [[asLength]].
  1395. *
  1396. * Check properties [[baseUnits]] if you need to change unit of value as the multiplier
  1397. * of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
  1398. *
  1399. * @param float|int $value value to be formatted.
  1400. * @param int|null $decimals the number of digits after the decimal point.
  1401. * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
  1402. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
  1403. * @return string the formatted result.
  1404. * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
  1405. * @throws InvalidConfigException when INTL is not installed or does not contain required information.
  1406. * @see asLength
  1407. * @since 2.0.13
  1408. * @author John Was <janek.jan@gmail.com>
  1409. */
  1410. public function asShortLength($value, $decimals = null, $options = [], $textOptions = [])
  1411. {
  1412. return $this->formatUnit(self::UNIT_LENGTH, self::FORMAT_WIDTH_SHORT, $value, null, null, $decimals, $options, $textOptions);
  1413. }
  1414. /**
  1415. * Formats the value as a weight in human readable form for example `12 kilograms`.
  1416. * Check properties [[baseUnits]] if you need to change unit of value as the multiplier
  1417. * of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
  1418. *
  1419. * @param float|int $value value to be formatted.
  1420. * @param int|null $decimals the number of digits after the decimal point.
  1421. * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
  1422. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
  1423. * @return string the formatted result.
  1424. * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
  1425. * @throws InvalidConfigException when INTL is not installed or does not contain required information.
  1426. * @since 2.0.13
  1427. * @author John Was <janek.jan@gmail.com>
  1428. */
  1429. public function asWeight($value, $decimals = null, $options = [], $textOptions = [])
  1430. {
  1431. return $this->formatUnit(self::UNIT_WEIGHT, self::FORMAT_WIDTH_LONG, $value, null, null, $decimals, $options, $textOptions);
  1432. }
  1433. /**
  1434. * Formats the value as a weight in human readable form for example `12 kg`.
  1435. * This is the short form of [[asWeight]].
  1436. *
  1437. * Check properties [[baseUnits]] if you need to change unit of value as the multiplier
  1438. * of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
  1439. *
  1440. * @param float|int $value value to be formatted.
  1441. * @param int|null $decimals the number of digits after the decimal point.
  1442. * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
  1443. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
  1444. * @return string the formatted result.
  1445. * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
  1446. * @throws InvalidConfigException when INTL is not installed or does not contain required information.
  1447. * @since 2.0.13
  1448. * @author John Was <janek.jan@gmail.com>
  1449. */
  1450. public function asShortWeight($value, $decimals = null, $options = [], $textOptions = [])
  1451. {
  1452. return $this->formatUnit(self::UNIT_WEIGHT, self::FORMAT_WIDTH_SHORT, $value, null, null, $decimals, $options, $textOptions);
  1453. }
  1454. /**
  1455. * @param string $unitType one of [[UNIT_WEIGHT]], [[UNIT_LENGTH]]
  1456. * @param string $unitFormat one of [[FORMAT_WIDTH_SHORT]], [[FORMAT_WIDTH_LONG]]
  1457. * @param float|int|null $value to be formatted
  1458. * @param float $baseUnit unit of value as the multiplier of the smallest unit. When `null`, property [[baseUnits]]
  1459. * will be used to determine base unit using $unitType and $unitSystem.
  1460. * @param string $unitSystem either [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]]. When `null`, property [[systemOfUnits]] will be used.
  1461. * @param int $decimals the number of digits after the decimal point.
  1462. * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
  1463. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
  1464. * @return string
  1465. * @throws InvalidConfigException when INTL is not installed or does not contain required information
  1466. */
  1467. private function formatUnit($unitType, $unitFormat, $value, $baseUnit, $unitSystem, $decimals, $options, $textOptions)
  1468. {
  1469. if ($value === null) {
  1470. return $this->nullDisplay;
  1471. }
  1472. if ($unitSystem === null) {
  1473. $unitSystem = $this->systemOfUnits;
  1474. }
  1475. if ($baseUnit === null) {
  1476. $baseUnit = $this->baseUnits[$unitType][$unitSystem];
  1477. }
  1478. $multipliers = array_values($this->measureUnits[$unitType][$unitSystem]);
  1479. list($params, $position) = $this->formatNumber(
  1480. $this->normalizeNumericValue($value) * $baseUnit,
  1481. $decimals,
  1482. null,
  1483. $multipliers,
  1484. $options,
  1485. $textOptions
  1486. );
  1487. $message = $this->getUnitMessage($unitType, $unitFormat, $unitSystem, $position);
  1488. return (new \MessageFormatter($this->locale, $message))->format([
  1489. '0' => $params['nFormatted'],
  1490. 'n' => $params['n'],
  1491. ]);
  1492. }
  1493. /**
  1494. * @param string $unitType one of [[UNIT_WEIGHT]], [[UNIT_LENGTH]]
  1495. * @param string $unitFormat one of [[FORMAT_WIDTH_SHORT]], [[FORMAT_WIDTH_LONG]]
  1496. * @param string $system either [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]]. When `null`, property [[systemOfUnits]] will be used.
  1497. * @param int $position internal position of size unit
  1498. * @return string
  1499. * @throws InvalidConfigException when INTL is not installed or does not contain required information
  1500. */
  1501. private function getUnitMessage($unitType, $unitFormat, $system, $position)
  1502. {
  1503. if (isset($this->_unitMessages[$unitType][$system][$position])) {
  1504. return $this->_unitMessages[$unitType][$system][$position];
  1505. }
  1506. if (!$this->_intlLoaded) {
  1507. throw new InvalidConfigException('Format of ' . $unitType . ' is only supported when PHP intl extension is installed.');
  1508. }
  1509. if ($this->_resourceBundle === null) {
  1510. try {
  1511. $this->_resourceBundle = new \ResourceBundle($this->locale, 'ICUDATA-unit');
  1512. } catch (\IntlException $e) {
  1513. throw new InvalidConfigException('Current ICU data does not contain information about measure units. Check system requirements.');
  1514. }
  1515. }
  1516. $unitNames = array_keys($this->measureUnits[$unitType][$system]);
  1517. $bundleKey = 'units' . ($unitFormat === self::FORMAT_WIDTH_SHORT ? 'Short' : '');
  1518. $unitBundle = $this->_resourceBundle[$bundleKey][$unitType][$unitNames[$position]];
  1519. if ($unitBundle === null) {
  1520. throw new InvalidConfigException('Current ICU data version does not contain information about unit type "' . $unitType . '" and unit measure "' . $unitNames[$position] . '". Check system requirements.');
  1521. }
  1522. $message = [];
  1523. foreach ($unitBundle as $key => $value) {
  1524. if ($key === 'dnam') {
  1525. continue;
  1526. }
  1527. $message[] = "$key{{$value}}";
  1528. }
  1529. return $this->_unitMessages[$unitType][$system][$position] = '{n, plural, ' . implode(' ', $message) . '}';
  1530. }
  1531. /**
  1532. * Given the value in bytes formats number part of the human readable form.
  1533. *
  1534. * @param string|int|float $value value in bytes to be formatted.
  1535. * @param int $decimals the number of digits after the decimal point
  1536. * @param int $maxPosition maximum internal position of size unit, ignored if $formatBase is an array
  1537. * @param array|int $formatBase the base at which each next unit is calculated, either 1000 or 1024, or an array
  1538. * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
  1539. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
  1540. * @return array [parameters for Yii::t containing formatted number, internal position of size unit]
  1541. * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
  1542. * @since 2.0.32
  1543. */
  1544. protected function formatNumber($value, $decimals, $maxPosition, $formatBase, $options, $textOptions)
  1545. {
  1546. $value = $this->normalizeNumericValue($value);
  1547. $position = 0;
  1548. if (is_array($formatBase)) {
  1549. $maxPosition = count($formatBase) - 1;
  1550. }
  1551. do {
  1552. if (is_array($formatBase)) {
  1553. if (!isset($formatBase[$position + 1])) {
  1554. break;
  1555. }
  1556. if (abs($value) < $formatBase[$position + 1]) {
  1557. break;
  1558. }
  1559. } else {
  1560. if (abs($value) < $formatBase) {
  1561. break;
  1562. }
  1563. $value /= $formatBase;
  1564. }
  1565. $position++;
  1566. } while ($position < $maxPosition + 1);
  1567. if (is_array($formatBase) && $position !== 0) {
  1568. $value /= $formatBase[$position];
  1569. }
  1570. // no decimals for smallest unit
  1571. if ($position === 0) {
  1572. $decimals = 0;
  1573. } elseif ($decimals !== null) {
  1574. $value = round($value, $decimals);
  1575. }
  1576. // disable grouping for edge cases like 1023 to get 1023 B instead of 1,023 B
  1577. $oldThousandSeparator = $this->thousandSeparator;
  1578. $this->thousandSeparator = '';
  1579. if ($this->_intlLoaded && !isset($options[NumberFormatter::GROUPING_USED])) {
  1580. $options[NumberFormatter::GROUPING_USED] = false;
  1581. }
  1582. // format the size value
  1583. $params = [
  1584. // this is the unformatted number used for the plural rule
  1585. // abs() to make sure the plural rules work correctly on negative numbers, intl does not cover this
  1586. // http://english.stackexchange.com/questions/9735/is-1-singular-or-plural
  1587. 'n' => abs($value),
  1588. // this is the formatted number used for display
  1589. 'nFormatted' => $this->asDecimal($value, $decimals, $options, $textOptions),
  1590. ];
  1591. $this->thousandSeparator = $oldThousandSeparator;
  1592. return [$params, $position];
  1593. }
  1594. /**
  1595. * Normalizes a numeric input value.
  1596. *
  1597. * - everything [empty](https://secure.php.net/manual/en/function.empty.php) will result in `0`
  1598. * - a [numeric](https://secure.php.net/manual/en/function.is-numeric.php) string will be casted to float
  1599. * - everything else will be returned if it is [numeric](https://secure.php.net/manual/en/function.is-numeric.php),
  1600. * otherwise an exception is thrown.
  1601. *
  1602. * @param mixed $value the input value
  1603. * @return float|int the normalized number value
  1604. * @throws InvalidArgumentException if the input value is not numeric.
  1605. */
  1606. protected function normalizeNumericValue($value)
  1607. {
  1608. if (empty($value)) {
  1609. return 0;
  1610. }
  1611. if (is_string($value) && is_numeric($value)) {
  1612. $value = (float) $value;
  1613. }
  1614. if (!is_numeric($value)) {
  1615. throw new InvalidArgumentException("'$value' is not a numeric value.");
  1616. }
  1617. return $value;
  1618. }
  1619. /**
  1620. * Creates a number formatter based on the given type and format.
  1621. *
  1622. * You may override this method to create a number formatter based on patterns.
  1623. *
  1624. * @param int $style the type of the number formatter.
  1625. * Values: NumberFormatter::DECIMAL, ::CURRENCY, ::PERCENT, ::SCIENTIFIC, ::SPELLOUT, ::ORDINAL
  1626. * ::DURATION, ::PATTERN_RULEBASED, ::DEFAULT_STYLE, ::IGNORE
  1627. * @param int|null $decimals the number of digits after the decimal point.
  1628. * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
  1629. * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
  1630. * @return NumberFormatter the created formatter instance
  1631. */
  1632. protected function createNumberFormatter($style, $decimals = null, $options = [], $textOptions = [])
  1633. {
  1634. $formatter = new NumberFormatter($this->locale, $style);
  1635. // set text attributes
  1636. foreach ($this->numberFormatterTextOptions as $name => $attribute) {
  1637. $formatter->setTextAttribute($name, $attribute);
  1638. }
  1639. foreach ($textOptions as $name => $attribute) {
  1640. $formatter->setTextAttribute($name, $attribute);
  1641. }
  1642. // set attributes
  1643. foreach ($this->numberFormatterOptions as $name => $value) {
  1644. $formatter->setAttribute($name, $value);
  1645. }
  1646. foreach ($options as $name => $value) {
  1647. $formatter->setAttribute($name, $value);
  1648. }
  1649. if ($decimals !== null) {
  1650. $formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimals);
  1651. $formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimals);
  1652. }
  1653. // set symbols
  1654. if ($this->decimalSeparator !== null) {
  1655. $formatter->setSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL, $this->decimalSeparator);
  1656. }
  1657. if ($this->currencyDecimalSeparator !== null) {
  1658. $formatter->setSymbol(NumberFormatter::MONETARY_SEPARATOR_SYMBOL, $this->currencyDecimalSeparator);
  1659. }
  1660. if ($this->thousandSeparator !== null) {
  1661. $formatter->setSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL, $this->thousandSeparator);
  1662. $formatter->setSymbol(NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL, $this->thousandSeparator);
  1663. }
  1664. foreach ($this->numberFormatterSymbols as $name => $symbol) {
  1665. $formatter->setSymbol($name, $symbol);
  1666. }
  1667. return $formatter;
  1668. }
  1669. /**
  1670. * Checks if string representations of given value and its normalized version are different.
  1671. * @param string|float|int $value
  1672. * @param float|int $normalizedValue
  1673. * @return bool
  1674. * @since 2.0.16
  1675. */
  1676. protected function isNormalizedValueMispresented($value, $normalizedValue)
  1677. {
  1678. if (empty($value)) {
  1679. $value = 0;
  1680. }
  1681. return (string) $normalizedValue !== $this->normalizeNumericStringValue((string) $value);
  1682. }
  1683. /**
  1684. * Normalizes a numeric string value.
  1685. * @param string $value
  1686. * @return string the normalized number value as a string
  1687. * @since 2.0.16
  1688. */
  1689. protected function normalizeNumericStringValue($value)
  1690. {
  1691. $powerPosition = strrpos($value, 'E');
  1692. if ($powerPosition !== false) {
  1693. $valuePart = substr($value, 0, $powerPosition);
  1694. $powerPart = substr($value, $powerPosition + 1);
  1695. } else {
  1696. $powerPart = null;
  1697. $valuePart = $value;
  1698. }
  1699. $separatorPosition = strrpos($valuePart, '.');
  1700. if ($separatorPosition !== false) {
  1701. $integerPart = substr($valuePart, 0, $separatorPosition);
  1702. $fractionalPart = substr($valuePart, $separatorPosition + 1);
  1703. } else {
  1704. $integerPart = $valuePart;
  1705. $fractionalPart = null;
  1706. }
  1707. // truncate insignificant zeros, keep minus
  1708. $integerPart = preg_replace('/^\+?(-?)0*(\d+)$/', '$1$2', $integerPart);
  1709. // for zeros only leave one zero, keep minus
  1710. $integerPart = preg_replace('/^\+?(-?)0*$/', '${1}0', $integerPart);
  1711. if ($fractionalPart !== null) {
  1712. // truncate insignificant zeros
  1713. $fractionalPart = rtrim($fractionalPart, '0');
  1714. if (empty($fractionalPart)) {
  1715. $fractionalPart = $powerPart !== null ? '0' : null;
  1716. }
  1717. }
  1718. $normalizedValue = $integerPart;
  1719. if ($fractionalPart !== null) {
  1720. $normalizedValue .= '.' . $fractionalPart;
  1721. } elseif ($normalizedValue === '-0') {
  1722. $normalizedValue = '0';
  1723. }
  1724. if ($powerPart !== null) {
  1725. $normalizedValue .= 'E' . $powerPart;
  1726. }
  1727. return $normalizedValue;
  1728. }
  1729. /**
  1730. * Fallback for formatting value as a decimal number.
  1731. *
  1732. * Property [[decimalSeparator]] will be used to represent the decimal point. The value is rounded automatically
  1733. * to the defined decimal digits.
  1734. *
  1735. * @param string|int|float $value the value to be formatted.
  1736. * @param int $decimals the number of digits after the decimal point. The default value is `2`.
  1737. * @return string the formatted result.
  1738. * @see decimalSeparator
  1739. * @see thousandSeparator
  1740. * @since 2.0.16
  1741. */
  1742. protected function asDecimalStringFallback($value, $decimals = 2)
  1743. {
  1744. if (empty($value)) {
  1745. $value = 0;
  1746. }
  1747. $value = $this->normalizeNumericStringValue((string) $value);
  1748. $separatorPosition = strrpos($value, '.');
  1749. if ($separatorPosition !== false) {
  1750. $integerPart = substr($value, 0, $separatorPosition);
  1751. $fractionalPart = substr($value, $separatorPosition + 1);
  1752. } else {
  1753. $integerPart = $value;
  1754. $fractionalPart = null;
  1755. }
  1756. $decimalOutput = '';
  1757. if ($decimals === null) {
  1758. $decimals = 2;
  1759. }
  1760. $carry = 0;
  1761. if ($decimals > 0) {
  1762. $decimalSeparator = $this->decimalSeparator;
  1763. if ($this->decimalSeparator === null) {
  1764. $decimalSeparator = '.';
  1765. }
  1766. if ($fractionalPart === null) {
  1767. $fractionalPart = str_repeat('0', $decimals);
  1768. } elseif (strlen($fractionalPart) > $decimals) {
  1769. $cursor = $decimals;
  1770. // checking if fractional part must be rounded
  1771. if ((int) substr($fractionalPart, $cursor, 1) >= 5) {
  1772. while (--$cursor >= 0) {
  1773. $carry = 0;
  1774. $oneUp = (int) substr($fractionalPart, $cursor, 1) + 1;
  1775. if ($oneUp === 10) {
  1776. $oneUp = 0;
  1777. $carry = 1;
  1778. }
  1779. $fractionalPart = substr($fractionalPart, 0, $cursor) . $oneUp . substr($fractionalPart, $cursor + 1);
  1780. if ($carry === 0) {
  1781. break;
  1782. }
  1783. }
  1784. }
  1785. $fractionalPart = substr($fractionalPart, 0, $decimals);
  1786. } elseif (strlen($fractionalPart) < $decimals) {
  1787. $fractionalPart = str_pad($fractionalPart, $decimals, '0');
  1788. }
  1789. $decimalOutput .= $decimalSeparator . $fractionalPart;
  1790. }
  1791. // checking if integer part must be rounded
  1792. if ($carry || ($decimals === 0 && $fractionalPart !== null && (int) substr($fractionalPart, 0, 1) >= 5)) {
  1793. $integerPartLength = strlen($integerPart);
  1794. $cursor = 0;
  1795. while (++$cursor <= $integerPartLength) {
  1796. $carry = 0;
  1797. $oneUp = (int) substr($integerPart, -$cursor, 1) + 1;
  1798. if ($oneUp === 10) {
  1799. $oneUp = 0;
  1800. $carry = 1;
  1801. }
  1802. $integerPart = substr($integerPart, 0, -$cursor) . $oneUp . substr($integerPart, $integerPartLength - $cursor + 1);
  1803. if ($carry === 0) {
  1804. break;
  1805. }
  1806. }
  1807. if ($carry === 1) {
  1808. $integerPart = '1' . $integerPart;
  1809. }
  1810. }
  1811. if (strlen($integerPart) > 3) {
  1812. $thousandSeparator = $this->thousandSeparator;
  1813. if ($thousandSeparator === null) {
  1814. $thousandSeparator = ',';
  1815. }
  1816. $integerPart = strrev(implode(',', str_split(strrev($integerPart), 3)));
  1817. if ($thousandSeparator !== ',') {
  1818. $integerPart = str_replace(',', $thousandSeparator, $integerPart);
  1819. }
  1820. }
  1821. return $integerPart . $decimalOutput;
  1822. }
  1823. /**
  1824. * Fallback for formatting value as an integer number by removing any decimal digits without rounding.
  1825. *
  1826. * @param string|int|float $value the value to be formatted.
  1827. * @return string the formatted result.
  1828. * @since 2.0.16
  1829. */
  1830. protected function asIntegerStringFallback($value)
  1831. {
  1832. if (empty($value)) {
  1833. $value = 0;
  1834. }
  1835. $value = $this->normalizeNumericStringValue((string) $value);
  1836. $separatorPosition = strrpos($value, '.');
  1837. if ($separatorPosition !== false) {
  1838. $integerPart = substr($value, 0, $separatorPosition);
  1839. } else {
  1840. $integerPart = $value;
  1841. }
  1842. return $this->asDecimalStringFallback($integerPart, 0);
  1843. }
  1844. /**
  1845. * Fallback for formatting value as a percent number with "%" sign.
  1846. *
  1847. * Property [[decimalSeparator]] will be used to represent the decimal point. The value is rounded automatically
  1848. * to the defined decimal digits.
  1849. *
  1850. * @param string|int|float $value the value to be formatted.
  1851. * @param int|null $decimals the number of digits after the decimal point. The default value is `0`.
  1852. * @return string the formatted result.
  1853. * @since 2.0.16
  1854. */
  1855. protected function asPercentStringFallback($value, $decimals = null)
  1856. {
  1857. if (empty($value)) {
  1858. $value = 0;
  1859. }
  1860. if ($decimals === null) {
  1861. $decimals = 0;
  1862. }
  1863. $value = $this->normalizeNumericStringValue((string) $value);
  1864. $separatorPosition = strrpos($value, '.');
  1865. if ($separatorPosition !== false) {
  1866. $integerPart = substr($value, 0, $separatorPosition);
  1867. $fractionalPart = str_pad(substr($value, $separatorPosition + 1), 2, '0');
  1868. $integerPart .= substr($fractionalPart, 0, 2);
  1869. $fractionalPart = substr($fractionalPart, 2);
  1870. if ($fractionalPart === '') {
  1871. $multipliedValue = $integerPart;
  1872. } else {
  1873. $multipliedValue = $integerPart . '.' . $fractionalPart;
  1874. }
  1875. } else {
  1876. $multipliedValue = $value . '00';
  1877. }
  1878. return $this->asDecimalStringFallback($multipliedValue, $decimals) . '%';
  1879. }
  1880. /**
  1881. * Fallback for formatting value as a currency number.
  1882. *
  1883. * @param string|int|float $value the value to be formatted.
  1884. * @param string|null $currency the 3-letter ISO 4217 currency code indicating the currency to use.
  1885. * If null, [[currencyCode]] will be used.
  1886. * @return string the formatted result.
  1887. * @throws InvalidConfigException if no currency is given and [[currencyCode]] is not defined.
  1888. * @since 2.0.16
  1889. */
  1890. protected function asCurrencyStringFallback($value, $currency = null)
  1891. {
  1892. if ($currency === null) {
  1893. if ($this->currencyCode === null) {
  1894. throw new InvalidConfigException('The default currency code for the formatter is not defined.');
  1895. }
  1896. $currency = $this->currencyCode;
  1897. }
  1898. return $currency . ' ' . $this->asDecimalStringFallback($value, 2);
  1899. }
  1900. }