Development

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

You must first sign up to be able to contribute.

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

Revision 18607, 9.1 kB (checked in by fabien, 5 years ago)

[1.0, 1.1, 1.2, 1.3] fixed format_currency returns Exponents (closes #5715)

  • 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     $decimalDigits = $this->formatInfo->DecimalDigits;
175     // if not decimal digits, assume 0 decimal points.
176     if (is_int($decimalDigits) && $decimalDigits > 0)
177     {
178       $string = (string) intval(round(floatval($string), $decimalDigits));
179     }
180
181     $dp = strpos($string, '.');
182
183     if (is_int($dp))
184     {
185       $string = substr($string, 0, $dp);
186     }
187
188     $integer = '';
189
190     $digitSize = $this->formatInfo->getDigitSize();
191     $string = str_pad($string, $digitSize, '0', STR_PAD_LEFT);
192
193     $len = strlen($string);
194
195     $groupSeparator = $this->formatInfo->GroupSeparator;
196     $groupSize = $this->formatInfo->GroupSizes;
197
198     $firstGroup = true;
199     $multiGroup = is_int($groupSize[1]);
200     $count = 0;
201
202     if (is_int($groupSize[0]))
203     {
204       // now for the integer groupings
205       for ($i = 0; $i < $len; $i++)
206       {
207         $char = $string{$len - $i - 1};
208
209         if ($multiGroup && $count == 0)
210         {
211           if ($i != 0 && $i % $groupSize[0] == 0)
212           {
213             $integer = $groupSeparator.$integer;
214             $count++;
215           }
216         }
217         else if ($multiGroup && $count >= 1)
218         {
219           if ($i != 0 && ($i - $groupSize[0]) % $groupSize[1] == 0)
220           {
221             $integer = $groupSeparator.$integer;
222             $count++;
223           }
224         }
225         else
226         {
227           if ($i != 0 && $i % $groupSize[0] == 0)
228           {
229             $integer = $groupSeparator.$integer;
230             $count++;
231           }
232         }
233
234         $integer = $char.$integer;
235       }
236     }
237     else
238     {
239       $integer = $string;
240     }
241
242     return $integer;
243   }
244
245   /**
246    * Formats the decimal places.
247    *
248    * @param string $decimal the decimal number in string form.
249    * @return string formatted decimal places.
250    */
251   protected function formatDecimal($string)
252   {
253     $dp = strpos($string, '.');
254     $decimal = '';
255
256     $decimalDigits = $this->formatInfo->DecimalDigits;
257     $decimalSeparator = $this->formatInfo->DecimalSeparator;
258
259     if (is_int($dp))
260     {
261       if ($decimalDigits == -1)
262       {
263         $decimal = substr($string, $dp + 1);
264       }
265       else if (is_int($decimalDigits))
266       {
267         if (false === $pos = strpos($string, '.'))
268         {
269           $decimal = str_pad($decimal, $decimalDigits, '0');
270         }
271         else
272         {
273           $decimal = substr($string, $pos + 1);
274           if (strlen($decimal) <= $decimalDigits)
275           {
276             $decimal = str_pad($decimal, $decimalDigits, '0');
277           }
278           else
279           {
280             $decimal = substr($decimal, 0, $decimalDigits);
281           }
282         }
283       }
284       else
285       {
286         return $decimal;
287       }
288
289       return $decimalSeparator.$decimal;
290     }
291     else if ($decimalDigits > 0)
292     {
293       return $decimalSeparator.str_pad($decimal, $decimalDigits, '0');
294     }
295
296     return $decimal;
297   }
298
299   /**
300    * Sets the pattern to format against. The default patterns
301    * are retrieved from the sfNumberFormatInfo instance.
302    *
303    * @param string $pattern the requested patterns.
304    * @return string a number format pattern.
305    */
306   protected function setPattern($pattern)
307   {
308     switch ($pattern)
309     {
310       case 'c':
311       case 'C':
312         $this->formatInfo->setPattern(sfNumberFormatInfo::CURRENCY);
313         break;
314       case 'd':
315       case 'D':
316         $this->formatInfo->setPattern(sfNumberFormatInfo::DECIMAL);
317         break;
318       case 'e':
319       case 'E':
320         $this->formatInfo->setPattern(sfNumberFormatInfo::SCIENTIFIC);
321         break;
322       case 'p':
323       case 'P':
324         $this->formatInfo->setPattern(sfNumberFormatInfo::PERCENTAGE);
325         break;
326       default:
327         $this->formatInfo->setPattern($pattern);
328         break;
329     }
330   }
331
332   protected function fixFloat($float)
333   {
334     $string = (string) $float;
335
336     if (false === strstr($float, 'E'))
337     {
338       return $string;
339     }
340
341     list($significand, $exp) = explode('E', $string);
342     list(, $decimal) = explode('.', $significand);
343     $exp = str_replace('+', '', $exp) - strlen($decimal);
344
345     return str_replace('.', '', $significand).str_repeat('0', $exp);
346   }
347 }
348
Note: See TracBrowser for help on using the browser.