sfNumberFormat.class.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  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 11958 2008-10-05 16:51:39Z 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. $string = (string) $number;
  119. $decimal = $this->formatDecimal($string);
  120. $integer = $this->formatInteger(abs($number));
  121. $result = (strlen($decimal) > 0) ? $integer.$decimal : $integer;
  122. // get the suffix
  123. if ($number >= 0)
  124. {
  125. $suffix = $this->formatInfo->PositivePattern;
  126. }
  127. else if ($number < 0)
  128. {
  129. $suffix = $this->formatInfo->NegativePattern;
  130. }
  131. else
  132. {
  133. $suffix = array('', '');
  134. }
  135. // append and prepend suffix
  136. $result = $suffix[0].$result.$suffix[1];
  137. // replace currency sign
  138. $symbol = @$this->formatInfo->getCurrencySymbol($currency);
  139. if (is_null($symbol))
  140. {
  141. $symbol = $currency;
  142. }
  143. $result = str_replace('¤', $symbol, $result);
  144. return sfToolkit::I18N_toEncoding($result, $charset);
  145. }
  146. /**
  147. * Formats the integer, perform groupings and string padding.
  148. *
  149. * @param string $string the decimal number in string form.
  150. * @return string formatted integer string with grouping
  151. */
  152. protected function formatInteger($string)
  153. {
  154. $string = (string) $string;
  155. $decimalDigits = $this->formatInfo->DecimalDigits;
  156. // if not decimal digits, assume 0 decimal points.
  157. if (is_int($decimalDigits) && $decimalDigits > 0)
  158. {
  159. $string = (string) intval(round(floatval($string), $decimalDigits));
  160. }
  161. $dp = strpos($string, '.');
  162. if (is_int($dp))
  163. {
  164. $string = substr($string, 0, $dp);
  165. }
  166. $integer = '';
  167. $digitSize = $this->formatInfo->getDigitSize();
  168. $string = str_pad($string, $digitSize, '0', STR_PAD_LEFT);
  169. $len = strlen($string);
  170. $groupSeparator = $this->formatInfo->GroupSeparator;
  171. $groupSize = $this->formatInfo->GroupSizes;
  172. $firstGroup = true;
  173. $multiGroup = is_int($groupSize[1]);
  174. $count = 0;
  175. if (is_int($groupSize[0]))
  176. {
  177. // now for the integer groupings
  178. for ($i = 0; $i < $len; $i++)
  179. {
  180. $char = $string{$len - $i - 1};
  181. if ($multiGroup && $count == 0)
  182. {
  183. if ($i != 0 && $i % $groupSize[0] == 0)
  184. {
  185. $integer = $groupSeparator.$integer;
  186. $count++;
  187. }
  188. }
  189. else if ($multiGroup && $count >= 1)
  190. {
  191. if ($i != 0 && ($i - $groupSize[0]) % $groupSize[1] == 0)
  192. {
  193. $integer = $groupSeparator.$integer;
  194. $count++;
  195. }
  196. }
  197. else
  198. {
  199. if ($i != 0 && $i % $groupSize[0] == 0)
  200. {
  201. $integer = $groupSeparator.$integer;
  202. $count++;
  203. }
  204. }
  205. $integer = $char.$integer;
  206. }
  207. }
  208. else
  209. {
  210. $integer = $string;
  211. }
  212. return $integer;
  213. }
  214. /**
  215. * Formats the decimal places.
  216. *
  217. * @param string $decimal the decimal number in string form.
  218. * @return string formatted decimal places.
  219. */
  220. protected function formatDecimal($string)
  221. {
  222. $dp = strpos($string, '.');
  223. $decimal = '';
  224. $decimalDigits = $this->formatInfo->DecimalDigits;
  225. $decimalSeparator = $this->formatInfo->DecimalSeparator;
  226. if (is_int($dp))
  227. {
  228. if ($decimalDigits == -1)
  229. {
  230. $decimal = substr($string, $dp + 1);
  231. }
  232. else if (is_int($decimalDigits))
  233. {
  234. $string = $float = round((float) $string, $decimalDigits);
  235. if (strpos((string) $float, '.') === false)
  236. {
  237. $decimal = str_pad($decimal, $decimalDigits, '0');
  238. }
  239. else
  240. {
  241. $decimal = substr($float, strpos($float,'.') + 1);
  242. if (strlen($decimal)<$decimalDigits)
  243. {
  244. $decimal = str_pad($decimal, $decimalDigits, '0');
  245. }
  246. }
  247. }
  248. else
  249. {
  250. return $decimal;
  251. }
  252. return $decimalSeparator.$decimal;
  253. }
  254. else if ($decimalDigits > 0)
  255. {
  256. return $decimalSeparator.str_pad($decimal, $decimalDigits, '0');
  257. }
  258. return $decimal;
  259. }
  260. /**
  261. * Sets the pattern to format against. The default patterns
  262. * are retrieved from the sfNumberFormatInfo instance.
  263. *
  264. * @param string $pattern the requested patterns.
  265. * @return string a number format pattern.
  266. */
  267. protected function setPattern($pattern)
  268. {
  269. switch ($pattern)
  270. {
  271. case 'c':
  272. case 'C':
  273. $this->formatInfo->setPattern(sfNumberFormatInfo::CURRENCY);
  274. break;
  275. case 'd':
  276. case 'D':
  277. $this->formatInfo->setPattern(sfNumberFormatInfo::DECIMAL);
  278. break;
  279. case 'e':
  280. case 'E':
  281. $this->formatInfo->setPattern(sfNumberFormatInfo::SCIENTIFIC);
  282. break;
  283. case 'p':
  284. case 'P':
  285. $this->formatInfo->setPattern(sfNumberFormatInfo::PERCENTAGE);
  286. break;
  287. default:
  288. $this->formatInfo->setPattern($pattern);
  289. break;
  290. }
  291. }
  292. }