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