Development

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

You must first sign up to be able to contribute.

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

Revision 32678, 8.9 kB (checked in by fabien, 3 years ago)

fixed sfNumberFormat::format() when the value has a negative exponent (closes #9836)

  • Property svn:mime-type set to text/x-php
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
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 (null === $formatInfo)
84     {
85       $this->formatInfo = sfNumberFormatInfo::getInvariantInfo();
86     }
87     else if ($formatInfo instanceof sfCultureInfo)
88     {
89       $this->formatInfo = $formatInfo->getNumberFormat();
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
145     // append and prepend suffix
146     $result = $suffix[0].$result.$suffix[1];
147
148     // replace currency sign
149     $symbol = @$this->formatInfo->getCurrencySymbol($currency);
150     if (null === $symbol)
151     {
152       $symbol = $currency;
153     }
154
155     $result = str_replace('¤', $symbol, $result);
156
157     return sfToolkit::I18N_toEncoding($result, $charset);
158   }
159
160   /**
161    * Formats the integer, perform groupings and string padding.
162    *
163    * @param string  $string the decimal number in string form.
164    * @return string  formatted integer string with grouping
165    */
166   protected function formatInteger($string)
167   {
168     $string = (string) $string;
169
170     $dp = strpos($string, '.');
171
172     if (is_int($dp))
173     {
174       $string = substr($string, 0, $dp);
175     }
176
177     $integer = '';
178
179     $digitSize = $this->formatInfo->getDigitSize();
180     $string = str_pad($string, $digitSize, '0', STR_PAD_LEFT);
181
182     $len = strlen($string);
183
184     $groupSeparator = $this->formatInfo->GroupSeparator;
185     $groupSize = $this->formatInfo->GroupSizes;
186
187     $firstGroup = true;
188     $multiGroup = is_int($groupSize[1]);
189     $count = 0;
190
191     if (is_int($groupSize[0]))
192     {
193       // now for the integer groupings
194       for ($i = 0; $i < $len; $i++)
195       {
196         $char = $string{$len - $i - 1};
197
198         if ($multiGroup && $count == 0)
199         {
200           if ($i != 0 && $i % $groupSize[0] == 0)
201           {
202             $integer = $groupSeparator.$integer;
203             $count++;
204           }
205         }
206         else if ($multiGroup && $count >= 1)
207         {
208           if ($i != 0 && ($i - $groupSize[0]) % $groupSize[1] == 0)
209           {
210             $integer = $groupSeparator.$integer;
211             $count++;
212           }
213         }
214         else
215         {
216           if ($i != 0 && $i % $groupSize[0] == 0)
217           {
218             $integer = $groupSeparator.$integer;
219             $count++;
220           }
221         }
222
223         $integer = $char.$integer;
224       }
225     }
226     else
227     {
228       $integer = $string;
229     }
230
231     return $integer;
232   }
233
234   /**
235    * Formats the decimal places.
236    *
237    * @param string $string the decimal number in string form.
238    * @return string formatted decimal places.
239    */
240   protected function formatDecimal($string)
241   {
242     $dp = strpos($string, '.');
243     $decimal = '';
244
245     $decimalDigits = $this->formatInfo->DecimalDigits;
246     $decimalSeparator = $this->formatInfo->DecimalSeparator;
247
248     if (is_int($dp))
249     {
250       if ($decimalDigits == -1)
251       {
252         $decimal = substr($string, $dp + 1);
253       }
254       else if (is_int($decimalDigits))
255       {
256         if (false === $pos = strpos($string, '.'))
257         {
258           $decimal = str_pad($decimal, $decimalDigits, '0');
259         }
260         else
261         {
262           $decimal = substr($string, $pos + 1);
263           if (strlen($decimal) <= $decimalDigits)
264           {
265             $decimal = str_pad($decimal, $decimalDigits, '0');
266           }
267           else
268           {
269             $decimal = substr($decimal, 0, $decimalDigits);
270           }
271         }
272       }
273       else
274       {
275         return $decimal;
276       }
277
278       return $decimalSeparator.$decimal;
279     }
280     else if ($decimalDigits > 0)
281     {
282       return $decimalSeparator.str_pad($decimal, $decimalDigits, '0');
283     }
284
285     return $decimal;
286   }
287
288   /**
289    * Sets the pattern to format against. The default patterns
290    * are retrieved from the sfNumberFormatInfo instance.
291    *
292    * @param string $pattern the requested patterns.
293    * @return string a number format pattern.
294    */
295   protected function setPattern($pattern)
296   {
297     switch ($pattern)
298     {
299       case 'c':
300       case 'C':
301         $this->formatInfo->setPattern(sfNumberFormatInfo::CURRENCY);
302         break;
303       case 'd':
304       case 'D':
305         $this->formatInfo->setPattern(sfNumberFormatInfo::DECIMAL);
306         break;
307       case 'e':
308       case 'E':
309         $this->formatInfo->setPattern(sfNumberFormatInfo::SCIENTIFIC);
310         break;
311       case 'p':
312       case 'P':
313         $this->formatInfo->setPattern(sfNumberFormatInfo::PERCENTAGE);
314         break;
315       default:
316         $this->formatInfo->setPattern($pattern);
317         break;
318     }
319   }
320
321   protected function fixFloat($float)
322   {
323     $string = (string) $float;
324
325     if (false === strstr($float, 'E'))
326     {
327       return $string;
328     }
329
330     list($significand, $exp) = explode('E', $string);
331     list(, $decimal) = explode('.', $significand);
332     if ('-' === $exp[0]) {
333         $exp = str_replace('-', '', $exp);
334
335         return '0.'.str_repeat('0', $exp).str_replace('.', '', $significand);
336     } else {
337         $exp = str_replace('+', '', $exp) - strlen($decimal);
338
339         return str_replace('.', '', $significand).str_repeat('0', $exp);
340     }
341   }
342 }
343
Note: See TracBrowser for help on using the browser.