Development

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

You must first sign up to be able to contribute.

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

Revision 28725, 17.5 kB (checked in by FabianLange, 5 years ago)

[1.3, 1.4] fixed invalid number formatting occurring with currency formats where no explicit negative format was defined (e.g for en_GB) (fixes #8433)

  • 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  * sfNumberFormatInfo 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  * sfNumberFormatInfo class
23  *
24  * Defines how numeric values are formatted and displayed,
25  * depending on the culture. Numeric values are formatted using
26  * standard or custom patterns stored in the properties of a
27  * sfNumberFormatInfo.
28  *
29  * This class contains information, such as currency, decimal
30  * separators, and other numeric symbols.
31  *
32  * To create a sfNumberFormatInfo for a specific culture,
33  * create a sfCultureInfo for that culture and retrieve the
34  * sfCultureInfo->NumberFormat property. Or use
35  * sfNumberFormatInfo::getInstance($culture).
36  * To create a sfNumberFormatInfo for the invariant culture, use the
37  * InvariantInfo::getInvariantInfo().
38  *
39  *
40  * @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>
41  * @version v1.0, last update on Sun Dec 05 14:48:26 EST 2004
42  * @package    symfony
43  * @subpackage i18n
44  */
45 class sfNumberFormatInfo
46 {
47   /**
48    * ICU number formatting data.
49    * @var array
50    */
51   protected $data = array();
52
53   /**
54    * A list of properties that are accessable/writable.
55    * @var array
56    */
57   protected $properties = array();
58
59   /**
60    * The number pattern.
61    * @var array
62    */
63   protected $pattern = array();
64
65   const DECIMAL = 0;
66   const CURRENCY = 1;
67   const PERCENTAGE = 2;
68   const SCIENTIFIC = 3;
69
70   /**
71    * Allows functions that begins with 'set' to be called directly
72    * as an attribute/property to retrieve the value.
73    *
74    * @return mixed
75    */
76   function __get($name)
77   {
78     $getProperty = 'get'.$name;
79     if (in_array($getProperty, $this->properties))
80     {
81       return $this->$getProperty();
82     }
83     else
84     {
85       throw new sfException(sprintf('Property %s does not exists.', $name));
86     }
87   }
88
89   /**
90    * Allows functions that begins with 'set' to be called directly
91    * as an attribute/property to set the value.
92    */
93   function __set($name, $value)
94   {
95     $setProperty = 'set'.$name;
96     if (in_array($setProperty, $this->properties))
97     {
98       $this->$setProperty($value);
99     }
100     else
101     {
102       throw new sfException(sprintf('Property %s can not be set.', $name));
103     }
104   }
105
106   /**
107    * Initializes a new writable instance of the sfNumberFormatInfo class
108    * that is dependent on the ICU data for number, decimal, and currency
109    * formatting information. <b>N.B.</b>You should not initialize this
110    * class directly unless you know what you are doing. Please use use
111    * sfNumberFormatInfo::getInstance() to create an instance.
112    *
113    * @param array $data ICU data for date time formatting.
114    * @param int   $type The sfNumberFormatInfo type
115    * @see getInstance()
116    */
117   function __construct($data = array(), $type = sfNumberFormatInfo::DECIMAL)
118   {
119     $this->properties = get_class_methods($this);
120
121     if (empty($data))
122     {
123       throw new sfException('Please provide the ICU data to initialize.');
124     }
125
126     $this->data = $data;
127
128     $this->setPattern($type);
129   }
130
131   /**
132    * Sets the pattern for a specific number pattern. The validate patterns
133    * sfNumberFormatInfo::DECIMAL, sfNumberFormatInfo::CURRENCY,
134    * sfNumberFormatInfo::PERCENTAGE, or sfNumberFormatInfo::SCIENTIFIC
135    *
136    * @param int $type pattern type.
137    */
138   function setPattern($type = sfNumberFormatInfo::DECIMAL)
139   {
140     if (is_int($type))
141     {
142       $this->pattern = $this->parsePattern($this->data['NumberPatterns'][$type]);
143     }
144     else
145     {
146       $this->pattern = $this->parsePattern($type);
147     }
148
149     $this->pattern['negInfty'] = $this->data['NumberElements'][6].$this->data['NumberElements'][9];
150
151     $this->pattern['posInfty'] = $this->data['NumberElements'][11].$this->data['NumberElements'][9];
152   }
153
154   function getPattern()
155   {
156     return $this->pattern;
157   }
158
159   /**
160    * Gets the default sfNumberFormatInfo that is culture-independent (invariant).
161    *
162    * @return sfNumberFormatInfo default sfNumberFormatInfo.
163    */
164   static public function getInvariantInfo($type = sfNumberFormatInfo::DECIMAL)
165   {
166     static $invariant;
167     if (null === $invariant)
168     {
169       $culture = sfCultureInfo::getInvariantCulture();
170       $invariant = $culture->NumberFormat;
171       $invariant->setPattern($type);
172     }
173
174     return $invariant;
175   }
176
177   /**
178    * Returns the sfNumberFormatInfo associated with the specified culture.
179    *
180    * @param sfCultureInfo $culture  the culture that gets the sfNumberFormat property.
181    * @param int           $type     the number formatting type, it should be
182    * sfNumberFormatInfo::DECIMAL, sfNumberFormatInfo::CURRENCY,
183    * sfNumberFormatInfo::PERCENTAGE, or sfNumberFormatInfo::SCIENTIFIC
184    * @return sfNumberFormatInfo sfNumberFormatInfo for the specified culture.
185    * @see getCurrencyInstance();
186    * @see getPercentageInstance();
187    * @see getScientificInstance();
188    */
189   public static function getInstance($culture = null, $type = sfNumberFormatInfo::DECIMAL)
190   {
191     if ($culture instanceof sfCultureInfo)
192     {
193       $formatInfo = $culture->getNumberFormat();
194       $formatInfo->setPattern($type);
195
196       return $formatInfo;
197     }
198     else if (is_string($culture))
199     {
200       $sfCultureInfo = sfCultureInfo::getInstance($culture);
201       $formatInfo = $sfCultureInfo->getNumberFormat();
202       $formatInfo->setPattern($type);
203
204       return $formatInfo;
205     }
206     else
207     {
208       $sfCultureInfo = sfCultureInfo::getInstance();
209       $formatInfo = $sfCultureInfo->getNumberFormat();
210       $formatInfo->setPattern($type);
211
212       return $formatInfo;
213     }
214   }
215
216   /**
217    * Returns the currency format info associated with the specified culture.
218    *
219    * @param sfCultureInfo $culture the culture that gets the NumberFormat property.
220    * @return sfNumberFormatInfo sfNumberFormatInfo for the specified culture.
221    */
222   public static function getCurrencyInstance($culture = null)
223   {
224     return self::getInstance($culture, self::CURRENCY);
225   }
226
227   /**
228    * Returns the percentage format info associated with the specified culture.
229    *
230    * @param sfCultureInfo $culture the culture that gets the NumberFormat property.
231    * @return sfNumberFormatInfo sfNumberFormatInfo for the specified culture.
232    */
233   public static function getPercentageInstance($culture = null)
234   {
235     return self::getInstance($culture, self::PERCENTAGE);
236   }
237
238   /**
239    * Returns the scientific format info associated with the specified culture.
240    *
241    * @param sfCultureInfo $culture the culture that gets the NumberFormat property.
242    * @return sfNumberFormatInfo sfNumberFormatInfo for the specified culture.
243    */
244   public static function getScientificInstance($culture = null)
245   {
246     return self::getInstance($culture, self::SCIENTIFIC);
247   }
248
249   /**
250    * Parses the given pattern and return a list of known properties.
251    *
252    * @param string $pattern a number pattern.
253    * @return array list of pattern properties.
254    */
255   protected function parsePattern($pattern)
256   {
257     $pattern = explode(';', $pattern);
258
259     $negative = null;
260     if (count($pattern) > 1)
261     {
262       $negative = $pattern[1];
263     }
264     $pattern = $pattern[0];
265
266     $comma = ',';
267     $dot = '.';
268     $digit = '0';
269     $hash = '#';
270
271     // find the first group point, and decimal point
272     $groupPos1 = strrpos($pattern, $comma);
273     $decimalPos = strrpos($pattern, $dot);
274
275     $groupPos2 = false;
276     $groupSize1 = false;
277     $groupSize2 = false;
278     $decimalPoints = is_int($decimalPos) ? -1 : false;
279
280     $info['negative'] = $negative;
281     $info['positive'] = $pattern;
282
283     $posfix = $this->getPrePostfix($pattern);
284     $info['posPref'] = $posfix[0];
285     $info['posPost'] = $posfix[1];
286
287     if ($negative)
288     {
289       // find the negative prefix and postfix
290       $prefixPostfix = $this->getPrePostfix($negative);
291       $info['negPref'] = $prefixPostfix[0];
292       $info['negPost'] = $prefixPostfix[1];
293     }
294     else
295     {
296       // use the positive prefix and postfix and add the NegativeSign
297       // http://www.unicode.org/reports/tr35/tr35-15.html#Number_Format_Patterns
298       // If there is no explicit negative subpattern, the negative subpattern is the localized minus sign prefixed to the positive subpattern.
299       $info['negPref'] = $this->getNegativeSign().$info['posPref'];
300       $info['negPost'] = $info['posPost'];
301     }
302
303     if (is_int($groupPos1))
304     {
305       // get the second group
306       $groupPos2 = strrpos(substr($pattern, 0, $groupPos1), $comma);
307
308       // get the number of decimal digits
309       if (is_int($decimalPos))
310       {
311         $groupSize1 = $decimalPos - $groupPos1 - 1;
312       }
313       else
314       {
315         // no decimal point, so traverse from the back
316         // to find the groupsize 1.
317         for ($i = strlen($pattern) - 1; $i >= 0; $i--)
318         {
319           if ($pattern{$i} == $digit || $pattern{$i} == $hash)
320           {
321             $groupSize1 = $i - $groupPos1;
322             break;
323           }
324         }
325       }
326
327       // get the second group size
328       if (is_int($groupPos2))
329       {
330         $groupSize2 = $groupPos1 - $groupPos2 - 1;
331       }
332     }
333
334     if (is_int($decimalPos))
335     {
336       for ($i = strlen($pattern) - 1; $i >= 0; $i--)
337       {
338         if ($pattern{$i} == $dot)
339         {
340           break;
341         }
342         if ($pattern{$i} == $digit)
343         {
344           $decimalPoints = $i - $decimalPos;
345           break;
346         }
347       }
348     }
349
350     $digitPattern = is_int($decimalPos) ? substr($pattern, 0, $decimalPos) : $pattern;
351     $digitPattern  = preg_replace('/[^0]/', '', $digitPattern);
352
353     $info['groupPos1']     = $groupPos1;
354     $info['groupSize1']    = $groupSize1;
355     $info['groupPos2']     = $groupPos2;
356     $info['groupSize2']    = $groupSize2;
357     $info['decimalPos']    = $decimalPos;
358     $info['decimalPoints'] = $decimalPoints;
359     $info['digitSize']     = strlen($digitPattern);
360
361     return $info;
362   }
363
364   /**
365    * Gets the prefix and postfix of a pattern.
366    *
367    * @param string $pattern pattern
368    * @return array of prefix and postfix, array(prefix,postfix).
369    */
370   protected function getPrePostfix($pattern)
371   {
372     $regexp = '/[#,\.0]+/';
373     $result = preg_split($regexp, $pattern);
374
375     return array($result[0], $result[1]);
376   }
377
378   /**
379    * Indicates the number of decimal places.
380    *
381    * @return int number of decimal places.
382    */
383   function getDecimalDigits()
384   {
385     return $this->pattern['decimalPoints'];
386   }
387
388   /**
389    * Sets the number of decimal places.
390    *
391    * @param int $value number of decimal places.
392    */
393   function setDecimalDigits($value)
394   {
395     return $this->pattern['decimalPoints'] = $value;
396   }
397
398   /**
399    * Indicates the digit size.
400    *
401    * @return int digit size.
402    */
403   function getDigitSize()
404   {
405     return $this->pattern['digitSize'];
406   }
407
408   /**
409    * Sets the digit size.
410    *
411    * @param int $value digit size.
412    */
413   function setDigitSize($value)
414   {
415     $this->pattern['digitSize'] = $value;
416   }
417
418   /**
419    * Gets the string to use as the decimal separator.
420    *
421    * @return string decimal separator.
422    */
423   function getDecimalSeparator()
424   {
425     return $this->data['NumberElements'][0];
426   }
427
428   /**
429    * Sets the string to use as the decimal separator.
430    *
431    * @param string $value the decimal point
432    */
433   function setDecimalSeparator($value)
434   {
435     return $this->data['NumberElements'][0] = $value;
436   }
437
438   /**
439    * Gets the string that separates groups of digits to the left
440    * of the decimal in currency values.
441    *
442    * @return string currency group separator.
443    */
444   function getGroupSeparator()
445   {
446     return $this->data['NumberElements'][1];
447   }
448
449   /**
450    * Sets the string to use as the group separator.
451    *
452    * @param string $value the group separator.
453    */
454   function setGroupSeparator($value)
455   {
456     return $this->data['NumberElements'][1] = $value;
457   }
458
459   /**
460    * Gets the number of digits in each group to the left of the decimal
461    * There can be two grouping sizes, this fucntion
462    * returns <b>array(group1, group2)</b>, if there is only 1 grouping size,
463    * group2 will be false.
464    *
465    * @return array grouping size(s).
466    */
467   function getGroupSizes()
468   {
469     $group1 = $this->pattern['groupSize1'];
470     $group2 = $this->pattern['groupSize2'];
471
472     return array($group1, $group2);
473   }
474
475   /**
476    * Sets the number of digits in each group to the left of the decimal.
477    * There can be two grouping sizes, the value should
478    * be an <b>array(group1, group2)</b>, if there is only 1 grouping size,
479    * group2 should be false.
480    *
481    * @param array $groupSize grouping size(s).
482    */
483   function setGroupSizes($groupSize)
484   {
485     $this->pattern['groupSize1'] = $groupSize[0];
486     $this->pattern['groupSize2'] = $groupSize[1];
487   }
488
489   /**
490    * Gets the format pattern for negative values.
491    * The negative pattern is composed of a prefix, and postfix.
492    * This function returns <b>array(prefix, postfix)</b>.
493    *
494    * @return arary negative pattern.
495    */
496   function getNegativePattern()
497   {
498     $prefix = $this->pattern['negPref'];
499     $postfix = $this->pattern['negPost'];
500
501     return array($prefix, $postfix);
502   }
503
504   /**
505    * Sets the format pattern for negative values.
506    * The negative pattern is composed of a prefix, and postfix in the form
507    * <b>array(prefix, postfix)</b>.
508    *
509    * @param arary $pattern negative pattern.
510    */
511   function setNegativePattern($pattern)
512   {
513     $this->pattern['negPref'] = $pattern[0];
514     $this->pattern['negPost'] = $pattern[1];
515   }
516
517   /**
518    * Gets the format pattern for positive values.
519    * The positive pattern is composed of a prefix, and postfix.
520    * This function returns <b>array(prefix, postfix)</b>.
521    *
522    * @return arary positive pattern.
523    */
524   function getPositivePattern()
525   {
526     $prefix = $this->pattern['posPref'];
527     $postfix = $this->pattern['posPost'];
528
529     return array($prefix, $postfix);
530   }
531
532   /**
533    * Sets the format pattern for positive values.
534    * The positive pattern is composed of a prefix, and postfix in the form
535    * <b>array(prefix, postfix)</b>.
536    *
537    * @param arary $pattern positive pattern.
538    */
539   function setPositivePattern($pattern)
540   {
541     $this->pattern['posPref'] = $pattern[0];
542     $this->pattern['posPost'] = $pattern[1];
543   }
544
545   /**
546    * Gets the string to use as the currency symbol.
547    *
548    * @return string $currency currency symbol.
549    */
550   function getCurrencySymbol($currency = 'USD')
551   {
552     if (isset($this->pattern['symbol']))
553     {
554       return $this->pattern['symbol'];
555     }
556     else
557     {
558       return $this->data['Currencies'][$currency][0];
559     }
560   }
561
562   /**
563    * Sets the string to use as the currency symbol.
564    *
565    * @param string $symbol currency symbol.
566    */
567   function setCurrencySymbol($symbol)
568   {
569     $this->pattern['symbol'] = $symbol;
570   }
571
572   /**
573    * Gets the string that represents negative infinity.
574    *
575    * @return string negative infinity.
576    */
577   function getNegativeInfinitySymbol()
578   {
579     return $this->pattern['negInfty'];
580   }
581
582   /**
583    * Sets the string that represents negative infinity.
584    *
585    * @param string $value negative infinity.
586    */
587   function setNegativeInfinitySymbol($value)
588   {
589     $this->pattern['negInfty'] = $value;
590   }
591
592   /**
593    * Gets the string that represents positive infinity.
594    *
595    * @return string positive infinity.
596    */
597   function getPositiveInfinitySymbol()
598   {
599     return $this->pattern['posInfty'];
600   }
601
602   /**
603    * Sets the string that represents positive infinity.
604    *
605    * @param string $value positive infinity.
606    */
607   function setPositiveInfinitySymbol($value)
608   {
609     $this->pattern['posInfty'] = $value;
610   }
611
612   /**
613    * Gets the string that denotes that the associated number is negative.
614    *
615    * @return string negative sign.
616    */
617   function getNegativeSign()
618   {
619     return $this->data['NumberElements'][6];
620   }
621
622   /**
623    * Sets the string that denotes that the associated number is negative.
624    *
625    * @param string $value negative sign.
626    */
627   function setNegativeSign($value)
628   {
629     $this->data['NumberElements'][6] = $value;
630   }
631
632   /**
633    * Gets the string that denotes that the associated number is positive.
634    *
635    * @return string positive sign.
636    */
637   function getPositiveSign()
638   {
639     return $this->data['NumberElements'][11];
640   }
641
642   /**
643    * Sets the string that denotes that the associated number is positive.
644    *
645    * @param string $value positive sign.
646    */
647   function setPositiveSign($value)
648   {
649     $this->data['NumberElements'][11] = $value;
650   }
651
652   /**
653    * Gets the string that represents the IEEE NaN (not a number) value.
654    *
655    * @return string NaN symbol.
656    */
657   function getNaNSymbol()
658   {
659     return $this->data['NumberElements'][10];
660   }
661
662   /**
663    * Sets the string that represents the IEEE NaN (not a number) value.
664    *
665    * @param string $value NaN symbol.
666    */
667   function setNaNSymbol($value)
668   {
669     $this->data['NumberElements'][10] = $value;
670   }
671
672   /**
673    * Gets the string to use as the percent symbol.
674    *
675    * @return string percent symbol.
676    */
677   function getPercentSymbol()
678   {
679     return $this->data['NumberElements'][3];
680   }
681
682   /**
683    * Sets the string to use as the percent symbol.
684    *
685    * @param string $value percent symbol.
686    */
687   function setPercentSymbol($value)
688   {
689     $this->data['NumberElements'][3] = $value;
690   }
691
692   /**
693    * Gets the string to use as the per mille symbol.
694    *
695    * @return string percent symbol.
696    */
697   function getPerMilleSymbol()
698   {
699     return $this->data['NumberElements'][8];
700   }
701
702   /**
703    * Sets the string to use as the per mille symbol.
704    *
705    * @param string $value percent symbol.
706    */
707   function setPerMilleSymbol($value)
708   {
709     $this->data['NumberElements'][8] = $value;
710   }
711 }
712
Note: See TracBrowser for help on using the browser.