CakeNumber.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. <?php
  2. /**
  3. * CakeNumber Utility.
  4. *
  5. * Methods to make numbers more readable.
  6. *
  7. * PHP 5
  8. *
  9. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  10. * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. *
  12. * Licensed under The MIT License
  13. * Redistributions of files must retain the above copyright notice.
  14. *
  15. * @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
  16. * @link http://cakephp.org CakePHP(tm) Project
  17. * @package Cake.Utility
  18. * @since CakePHP(tm) v 0.10.0.1076
  19. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  20. */
  21. /**
  22. * Number helper library.
  23. *
  24. * Methods to make numbers more readable.
  25. *
  26. * @package Cake.Utility
  27. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html
  28. */
  29. class CakeNumber {
  30. /**
  31. * Currencies supported by the helper. You can add additional currency formats
  32. * with CakeNumber::addFormat
  33. *
  34. * @var array
  35. */
  36. protected static $_currencies = array(
  37. 'USD' => array(
  38. 'wholeSymbol' => '$', 'wholePosition' => 'before', 'fractionSymbol' => 'c', 'fractionPosition' => 'after',
  39. 'zero' => 0, 'places' => 2, 'thousands' => ',', 'decimals' => '.', 'negative' => '()', 'escape' => true
  40. ),
  41. 'GBP' => array(
  42. 'wholeSymbol' => '&#163;', 'wholePosition' => 'before', 'fractionSymbol' => 'p', 'fractionPosition' => 'after',
  43. 'zero' => 0, 'places' => 2, 'thousands' => ',', 'decimals' => '.', 'negative' => '()','escape' => false
  44. ),
  45. 'EUR' => array(
  46. 'wholeSymbol' => '&#8364;', 'wholePosition' => 'before', 'fractionSymbol' => false, 'fractionPosition' => 'after',
  47. 'zero' => 0, 'places' => 2, 'thousands' => '.', 'decimals' => ',', 'negative' => '()', 'escape' => false
  48. )
  49. );
  50. /**
  51. * Default options for currency formats
  52. *
  53. * @var array
  54. */
  55. protected static $_currencyDefaults = array(
  56. 'wholeSymbol' => '', 'wholePosition' => 'before', 'fractionSymbol' => '', 'fractionPosition' => 'after',
  57. 'zero' => '0', 'places' => 2, 'thousands' => ',', 'decimals' => '.','negative' => '()', 'escape' => true,
  58. );
  59. /**
  60. * Default currency used by CakeNumber::currency()
  61. *
  62. * @var string
  63. */
  64. protected static $_defaultCurrency = 'USD';
  65. /**
  66. * If native number_format() should be used. If >= PHP5.4
  67. *
  68. * @var boolean
  69. */
  70. protected static $_numberFormatSupport = null;
  71. /**
  72. * Formats a number with a level of precision.
  73. *
  74. * @param float $value A floating point number.
  75. * @param integer $precision The precision of the returned number.
  76. * @return float Formatted float.
  77. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::precision
  78. */
  79. public static function precision($value, $precision = 3) {
  80. return sprintf("%01.{$precision}F", $value);
  81. }
  82. /**
  83. * Returns a formatted-for-humans file size.
  84. *
  85. * @param integer $size Size in bytes
  86. * @return string Human readable size
  87. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::toReadableSize
  88. */
  89. public static function toReadableSize($size) {
  90. switch (true) {
  91. case $size < 1024:
  92. return __dn('cake', '%d Byte', '%d Bytes', $size, $size);
  93. case round($size / 1024) < 1024:
  94. return __d('cake', '%d KB', self::precision($size / 1024, 0));
  95. case round($size / 1024 / 1024, 2) < 1024:
  96. return __d('cake', '%.2f MB', self::precision($size / 1024 / 1024, 2));
  97. case round($size / 1024 / 1024 / 1024, 2) < 1024:
  98. return __d('cake', '%.2f GB', self::precision($size / 1024 / 1024 / 1024, 2));
  99. default:
  100. return __d('cake', '%.2f TB', self::precision($size / 1024 / 1024 / 1024 / 1024, 2));
  101. }
  102. }
  103. /**
  104. * Converts filesize from human readable string to bytes
  105. *
  106. * @param string $size Size in human readable string like '5MB', '5M', '500B', '50kb' etc.
  107. * @param mixed $default Value to be returned when invalid size was used, for example 'Unknown type'
  108. * @return mixed Number of bytes as integer on success, `$default` on failure if not false
  109. * @throws CakeException On invalid Unit type.
  110. */
  111. public static function fromReadableSize($size, $default = false) {
  112. if (ctype_digit($size)) {
  113. return (int)$size;
  114. }
  115. $size = strtoupper($size);
  116. $l = -2;
  117. $i = array_search(substr($size, -2), array('KB', 'MB', 'GB', 'TB', 'PB'));
  118. if ($i === false) {
  119. $l = -1;
  120. $i = array_search(substr($size, -1), array('K', 'M', 'G', 'T', 'P'));
  121. }
  122. if ($i !== false) {
  123. $size = substr($size, 0, $l);
  124. return $size * pow(1024, $i + 1);
  125. }
  126. if (substr($size, -1) == 'B' && ctype_digit(substr($size, 0, -1))) {
  127. $size = substr($size, 0, -1);
  128. return (int)$size;
  129. }
  130. if ($default !== false) {
  131. return $default;
  132. }
  133. throw new CakeException(__d('cake_dev', 'No unit type.'));
  134. }
  135. /**
  136. * Formats a number into a percentage string.
  137. *
  138. * @param float $value A floating point number
  139. * @param integer $precision The precision of the returned number
  140. * @return string Percentage string
  141. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::toPercentage
  142. */
  143. public static function toPercentage($value, $precision = 2) {
  144. return self::precision($value, $precision) . '%';
  145. }
  146. /**
  147. * Formats a number into a currency format.
  148. *
  149. * @param float $value A floating point number
  150. * @param integer $options if int then places, if string then before, if (,.-) then use it
  151. * or array with places and before keys
  152. * @return string formatted number
  153. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::format
  154. */
  155. public static function format($value, $options = false) {
  156. $places = 0;
  157. if (is_int($options)) {
  158. $places = $options;
  159. }
  160. $separators = array(',', '.', '-', ':');
  161. $before = $after = null;
  162. if (is_string($options) && !in_array($options, $separators)) {
  163. $before = $options;
  164. }
  165. $thousands = ',';
  166. if (!is_array($options) && in_array($options, $separators)) {
  167. $thousands = $options;
  168. }
  169. $decimals = '.';
  170. if (!is_array($options) && in_array($options, $separators)) {
  171. $decimals = $options;
  172. }
  173. $escape = true;
  174. if (is_array($options)) {
  175. $options = array_merge(array('before' => '$', 'places' => 2, 'thousands' => ',', 'decimals' => '.'), $options);
  176. extract($options);
  177. }
  178. $value = self::_numberFormat($value, $places, '.', '');
  179. $out = $before . self::_numberFormat($value, $places, $decimals, $thousands) . $after;
  180. if ($escape) {
  181. return h($out);
  182. }
  183. return $out;
  184. }
  185. /**
  186. * Formats a number into a currency format to show deltas (signed differences in value).
  187. *
  188. * ### Options
  189. *
  190. * - `places` - Number of decimal places to use. ie. 2
  191. * - `before` - The string to place before whole numbers. ie. '['
  192. * - `after` - The string to place after decimal numbers. ie. ']'
  193. * - `thousands` - Thousands separator ie. ','
  194. * - `decimals` - Decimal separator symbol ie. '.'
  195. *
  196. * @param float $value A floating point number
  197. * @param array $options
  198. * @return string formatted delta
  199. */
  200. public static function formatDelta($value, $options = array()) {
  201. $places = isset($options['places']) ? $options['places'] : 0;
  202. $value = self::_numberFormat($value, $places, '.', '');
  203. $sign = $value > 0 ? '+' : '';
  204. $options['before'] = isset($options['before']) ? $options['before'] . $sign : $sign;
  205. return self::format($value, $options);
  206. }
  207. /**
  208. * Alternative number_format() to accommodate multibyte decimals and thousands < PHP 5.4
  209. *
  210. * @param float $value
  211. * @param integer $places
  212. * @param string $decimals
  213. * @param string $thousands
  214. * @return string
  215. */
  216. protected static function _numberFormat($value, $places = 0, $decimals = '.', $thousands = ',') {
  217. if (!isset(self::$_numberFormatSupport)) {
  218. self::$_numberFormatSupport = version_compare(PHP_VERSION, '5.4.0', '>=');
  219. }
  220. if (self::$_numberFormatSupport) {
  221. return number_format($value, $places, $decimals, $thousands);
  222. }
  223. $value = number_format($value, $places, '.', '');
  224. $after = '';
  225. $foundDecimal = strpos($value, '.');
  226. if ($foundDecimal !== false) {
  227. $after = substr($value, $foundDecimal);
  228. $value = substr($value, 0, $foundDecimal);
  229. }
  230. while (($foundThousand = preg_replace('/(\d+)(\d\d\d)/', '\1 \2', $value)) != $value) {
  231. $value = $foundThousand;
  232. }
  233. $value .= $after;
  234. return strtr($value, array(' ' => $thousands, '.' => $decimals));
  235. }
  236. /**
  237. * Formats a number into a currency format.
  238. *
  239. * ### Options
  240. *
  241. * - `wholeSymbol` - The currency symbol to use for whole numbers,
  242. * greater than 1, or less than -1.
  243. * - `wholePosition` - The position the whole symbol should be placed
  244. * valid options are 'before' & 'after'.
  245. * - `fractionSymbol` - The currency symbol to use for fractional numbers.
  246. * - `fractionPosition` - The position the fraction symbol should be placed
  247. * valid options are 'before' & 'after'.
  248. * - `before` - The currency symbol to place before whole numbers
  249. * ie. '$'. `before` is an alias for `wholeSymbol`.
  250. * - `after` - The currency symbol to place after decimal numbers
  251. * ie. 'c'. Set to boolean false to use no decimal symbol.
  252. * eg. 0.35 => $0.35. `after` is an alias for `fractionSymbol`
  253. * - `zero` - The text to use for zero values, can be a
  254. * string or a number. ie. 0, 'Free!'
  255. * - `places` - Number of decimal places to use. ie. 2
  256. * - `thousands` - Thousands separator ie. ','
  257. * - `decimals` - Decimal separator symbol ie. '.'
  258. * - `negative` - Symbol for negative numbers. If equal to '()',
  259. * the number will be wrapped with ( and )
  260. * - `escape` - Should the output be escaped for html special characters.
  261. * The default value for this option is controlled by the currency settings.
  262. * By default the EUR, and GBP contain HTML encoded symbols. If you require non HTML
  263. * encoded symbols you will need to update the settings with the correct bytes.
  264. *
  265. * @param float $value
  266. * @param string $currency Shortcut to default options. Valid values are
  267. * 'USD', 'EUR', 'GBP', otherwise set at least 'before' and 'after' options.
  268. * @param array $options
  269. * @return string Number formatted as a currency.
  270. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::currency
  271. */
  272. public static function currency($value, $currency = null, $options = array()) {
  273. $default = self::$_currencyDefaults;
  274. if ($currency === null) {
  275. $currency = self::defaultCurrency();
  276. }
  277. if (isset(self::$_currencies[$currency])) {
  278. $default = self::$_currencies[$currency];
  279. } elseif (is_string($currency)) {
  280. $options['before'] = $currency;
  281. }
  282. $options = array_merge($default, $options);
  283. if (isset($options['before']) && $options['before'] !== '') {
  284. $options['wholeSymbol'] = $options['before'];
  285. }
  286. if (isset($options['after']) && !$options['after'] !== '') {
  287. $options['fractionSymbol'] = $options['after'];
  288. }
  289. $result = $options['before'] = $options['after'] = null;
  290. $symbolKey = 'whole';
  291. $value = (float)$value;
  292. if (!$value) {
  293. if ($options['zero'] !== 0 ) {
  294. return $options['zero'];
  295. }
  296. } elseif ($value < 1 && $value > -1) {
  297. if ($options['fractionSymbol'] !== false) {
  298. $multiply = intval('1' . str_pad('', $options['places'], '0'));
  299. $value = $value * $multiply;
  300. $options['places'] = null;
  301. $symbolKey = 'fraction';
  302. }
  303. }
  304. $position = $options[$symbolKey . 'Position'] != 'after' ? 'before' : 'after';
  305. $options[$position] = $options[$symbolKey . 'Symbol'];
  306. $abs = abs($value);
  307. $result = self::format($abs, $options);
  308. if ($value < 0) {
  309. if ($options['negative'] == '()') {
  310. $result = '(' . $result . ')';
  311. } else {
  312. $result = $options['negative'] . $result;
  313. }
  314. }
  315. return $result;
  316. }
  317. /**
  318. * Add a currency format to the Number helper. Makes reusing
  319. * currency formats easier.
  320. *
  321. * {{{ $number->addFormat('NOK', array('before' => 'Kr. ')); }}}
  322. *
  323. * You can now use `NOK` as a shortform when formatting currency amounts.
  324. *
  325. * {{{ $number->currency($value, 'NOK'); }}}
  326. *
  327. * Added formats are merged with the defaults defined in CakeNumber::$_currencyDefaults
  328. * See CakeNumber::currency() for more information on the various options and their function.
  329. *
  330. * @param string $formatName The format name to be used in the future.
  331. * @param array $options The array of options for this format.
  332. * @return void
  333. * @see NumberHelper::currency()
  334. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::addFormat
  335. */
  336. public static function addFormat($formatName, $options) {
  337. self::$_currencies[$formatName] = $options + self::$_currencyDefaults;
  338. }
  339. /**
  340. * Getter/setter for default currency
  341. *
  342. * @param string $currency Default currency string used by currency() if $currency argument is not provided
  343. * @return string Currency
  344. */
  345. public static function defaultCurrency($currency = null) {
  346. if ($currency) {
  347. self::$_defaultCurrency = $currency;
  348. }
  349. return self::$_defaultCurrency;
  350. }
  351. }