sfNumberFormat.class.php 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. <?php
  2. /**
  3. * sfNumberFormat class file.
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the BSD License.
  7. *
  8. * Copyright(c) 2004 by Qiang Xue. All rights reserved.
  9. *
  10. * To contact the author write to {@link mailto:qiang.xue@gmail.com Qiang Xue}
  11. * The latest version of PRADO can be obtained from:
  12. * {@link http://prado.sourceforge.net/}
  13. *
  14. * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
  15. * @version $Id: sfNumberFormat.class.php 18607 2009-05-24 20:41:09Z fabien $
  16. * @package symfony
  17. * @subpackage i18n
  18. */
  19. /**
  20. * sfNumberFormat class.
  21. *
  22. * sfNumberFormat formats decimal numbers in any locale. The decimal
  23. * number is formatted according to a particular pattern. These
  24. * patterns can arise from the sfNumberFormatInfo object which is
  25. * culturally sensitive. The sfNumberFormat class can be instantiated in
  26. * many ways. E.g.
  27. *
  28. * <code>
  29. * //create a invariant number formatter.
  30. * $formatter = new sfNumberFormat();
  31. *
  32. * //create a number format for the french language locale.
  33. * $fr = new sfNumberFormat('fr');
  34. *
  35. * //create a number format base on a sfNumberFormatInfo instance $numberInfo.
  36. * $format = new sfNumberFormat($numberInfo);
  37. * </code>
  38. *
  39. * A normal decimal number can also be displayed as a currency
  40. * or as a percentage. For example
  41. * <code>
  42. * $format->format(1234.5); //Decimal number "1234.5"
  43. * $format->format(1234.5,'c'); //Default currency "$1234.50"
  44. * $format->format(0.25, 'p') //Percent "25%"
  45. * </code>
  46. *
  47. * Currency is formated using the localized currency pattern. For example
  48. * to format the number as Japanese Yen:
  49. * <code>
  50. * $ja = new sfNumberFormat('ja_JP');
  51. *
  52. * //Japanese currency pattern, and using Japanese Yen symbol
  53. * $ja->format(123.14,'c','JPY'); //ï¿?123 (Yen 123)
  54. * </code>
  55. * For each culture, the symbol for each currency may be different.
  56. *
  57. * @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>
  58. * @version v1.0, last update on Fri Dec 10 18:10:20 EST 2004
  59. * @package symfony
  60. * @subpackage i18n
  61. */
  62. class sfNumberFormat
  63. {
  64. /**
  65. * The DateTimeFormatInfo, containing culture specific patterns and names.
  66. * @var DateTimeFormatInfo
  67. */
  68. protected $formatInfo;
  69. /**
  70. * Creates a new number format instance. The constructor can be instantiated
  71. * with a string that represent a culture/locale. Similarly, passing
  72. * a sfCultureInfo or sfNumberFormatInfo instance will instantiated a instance
  73. * for that particular culture.
  74. *
  75. * @param mixed $formatInfo either null, a sfCultureInfo, a sfNumberFormatInfo, or string
  76. * @return sfNumberFormat
  77. */
  78. function __construct($formatInfo = null)
  79. {
  80. if (is_null($formatInfo))
  81. {
  82. $this->formatInfo = sfNumberFormatInfo::getInvariantInfo();
  83. }
  84. else if ($formatInfo instanceof sfCultureInfo)
  85. {
  86. $this->formatInfo = $formatInfo->sfNumberFormat;
  87. }
  88. else if ($formatInfo instanceof sfNumberFormatInfo)
  89. {
  90. $this->formatInfo = $formatInfo;
  91. }
  92. else
  93. {
  94. $this->formatInfo = sfNumberFormatInfo::getInstance($formatInfo);
  95. }
  96. }
  97. /**
  98. * Formats the number for a certain pattern. The valid patterns are
  99. * 'c', 'd', 'e', 'p' or a custom pattern, such as "#.000" for
  100. * 3 decimal places.
  101. *
  102. * @param mixed $number the number to format.
  103. * @param string $pattern the format pattern, either, 'c', 'd', 'e', 'p'
  104. * or a custom pattern. E.g. "#.000" will format the number to
  105. * 3 decimal places.
  106. * @param string $currency 3-letter ISO 4217 code. For example, the code
  107. * "USD" represents the US Dollar and "EUR" represents the Euro currency.
  108. * @param string $charset The charset
  109. * @return string formatted number string
  110. */
  111. function format($number, $pattern = 'd', $currency = 'USD', $charset = 'UTF-8')
  112. {
  113. $this->setPattern($pattern);
  114. if (strtolower($pattern) == 'p')
  115. {
  116. $number = $number * 100;
  117. }
  118. // avoid conversion with exponents
  119. // see http://trac.symfony-project.org/ticket/5715
  120. $precision = ini_set('precision', 14);
  121. $string = $this->fixFloat($number);
  122. ini_set('precision', $precision);
  123. $decimal = $this->formatDecimal($string);
  124. $integer = $this->formatInteger($this->fixFloat(abs($number)));
  125. $result = (strlen($decimal) > 0) ? $integer.$decimal : $integer;
  126. // get the suffix
  127. if ($number >= 0)
  128. {
  129. $suffix = $this->formatInfo->PositivePattern;
  130. }
  131. else if ($number < 0)
  132. {
  133. $suffix = $this->formatInfo->NegativePattern;
  134. }
  135. else
  136. {
  137. $suffix = array('', '');
  138. }
  139. // append and prepend suffix
  140. $result = $suffix[0].$result.$suffix[1];
  141. // replace currency sign
  142. $symbol = @$this->formatInfo->getCurrencySymbol($currency);
  143. if (is_null($symbol))
  144. {
  145. $symbol = $currency;
  146. }
  147. $result = str_replace('¤', $symbol, $result);
  148. return sfToolkit::I18N_toEncoding($result, $charset);
  149. }
  150. /**
  151. * Formats the integer, perform groupings and string padding.
  152. *
  153. * @param string $string the decimal number in string form.
  154. * @return string formatted integer string with grouping
  155. */
  156. protected function formatInteger($string)
  157. {
  158. $string = (string) $string;
  159. $decimalDigits = $this->formatInfo->DecimalDigits;
  160. // if not decimal digits, assume 0 decimal points.
  161. if (is_int($decimalDigits) && $decimalDigits > 0)
  162. {
  163. $string = (string) intval(round(floatval($string), $decimalDigits));
  164. }
  165. $dp = strpos($string, '.');
  166. if (is_int($dp))
  167. {
  168. $string = substr($string, 0, $dp);
  169. }
  170. $integer = '';
  171. $digitSize = $this->formatInfo->getDigitSize();
  172. $string = str_pad($string, $digitSize, '0', STR_PAD_LEFT);
  173. $len = strlen($string);
  174. $groupSeparator = $this->formatInfo->GroupSeparator;
  175. $groupSize = $this->formatInfo->GroupSizes;
  176. $firstGroup = true;
  177. $multiGroup = is_int($groupSize[1]);
  178. $count = 0;
  179. if (is_int($groupSize[0]))
  180. {
  181. // now for the integer groupings
  182. for ($i = 0; $i < $len; $i++)
  183. {
  184. $char = $string{$len - $i - 1};
  185. if ($multiGroup && $count == 0)
  186. {
  187. if ($i != 0 && $i % $groupSize[0] == 0)
  188. {
  189. $integer = $groupSeparator.$integer;
  190. $count++;
  191. }
  192. }
  193. else if ($multiGroup && $count >= 1)
  194. {
  195. if ($i != 0 && ($i - $groupSize[0]) % $groupSize[1] == 0)
  196. {
  197. $integer = $groupSeparator.$integer;
  198. $count++;
  199. }
  200. }
  201. else
  202. {
  203. if ($i != 0 && $i % $groupSize[0] == 0)
  204. {
  205. $integer = $groupSeparator.$integer;
  206. $count++;
  207. }
  208. }
  209. $integer = $char.$integer;
  210. }
  211. }
  212. else
  213. {
  214. $integer = $string;
  215. }
  216. return $integer;
  217. }
  218. /**
  219. * Formats the decimal places.
  220. *
  221. * @param string $decimal the decimal number in string form.
  222. * @return string formatted decimal places.
  223. */
  224. protected function formatDecimal($string)
  225. {
  226. $dp = strpos($string, '.');
  227. $decimal = '';
  228. $decimalDigits = $this->formatInfo->DecimalDigits;
  229. $decimalSeparator = $this->formatInfo->DecimalSeparator;
  230. if (is_int($dp))
  231. {
  232. if ($decimalDigits == -1)
  233. {
  234. $decimal = substr($string, $dp + 1);
  235. }
  236. else if (is_int($decimalDigits))
  237. {
  238. if (false === $pos = strpos($string, '.'))
  239. {
  240. $decimal = str_pad($decimal, $decimalDigits, '0');
  241. }
  242. else
  243. {
  244. $decimal = substr($string, $pos + 1);
  245. if (strlen($decimal) <= $decimalDigits)
  246. {
  247. $decimal = str_pad($decimal, $decimalDigits, '0');
  248. }
  249. else
  250. {
  251. $decimal = substr($decimal, 0, $decimalDigits);
  252. }
  253. }
  254. }
  255. else
  256. {
  257. return $decimal;
  258. }
  259. return $decimalSeparator.$decimal;
  260. }
  261. else if ($decimalDigits > 0)
  262. {
  263. return $decimalSeparator.str_pad($decimal, $decimalDigits, '0');
  264. }
  265. return $decimal;
  266. }
  267. /**
  268. * Sets the pattern to format against. The default patterns
  269. * are retrieved from the sfNumberFormatInfo instance.
  270. *
  271. * @param string $pattern the requested patterns.
  272. * @return string a number format pattern.
  273. */
  274. protected function setPattern($pattern)
  275. {
  276. switch ($pattern)
  277. {
  278. case 'c':
  279. case 'C':
  280. $this->formatInfo->setPattern(sfNumberFormatInfo::CURRENCY);
  281. break;
  282. case 'd':
  283. case 'D':
  284. $this->formatInfo->setPattern(sfNumberFormatInfo::DECIMAL);
  285. break;
  286. case 'e':
  287. case 'E':
  288. $this->formatInfo->setPattern(sfNumberFormatInfo::SCIENTIFIC);
  289. break;
  290. case 'p':
  291. case 'P':
  292. $this->formatInfo->setPattern(sfNumberFormatInfo::PERCENTAGE);
  293. break;
  294. default:
  295. $this->formatInfo->setPattern($pattern);
  296. break;
  297. }
  298. }
  299. protected function fixFloat($float)
  300. {
  301. $string = (string) $float;
  302. if (false === strstr($float, 'E'))
  303. {
  304. return $string;
  305. }
  306. list($significand, $exp) = explode('E', $string);
  307. list(, $decimal) = explode('.', $significand);
  308. $exp = str_replace('+', '', $exp) - strlen($decimal);
  309. return str_replace('.', '', $significand).str_repeat('0', $exp);
  310. }
  311. }