Development

/branches/1.2/lib/i18n/sfNumberFormat.class.php

You must first sign up to be able to contribute.

root/branches/1.2/lib/i18n/sfNumberFormat.class.php

Revision 26681, 8.8 kB (checked in by fabien, 4 years ago)

[1.2, 1.3, 1.4] fixed format_currency is rounding bad (closes #6788)

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