Autodoc
  • Namespace
  • Class
  • Tree

Namespaces

  • BlueTihi
    • Context
  • Brickrouge
    • Element
      • Nodes
    • Renderer
    • Widget
  • ICanBoogie
    • ActiveRecord
    • AutoConfig
    • CLDR
    • Composer
    • Core
    • Event
    • Exception
    • HTTP
      • Dispatcher
      • Request
    • I18n
      • Translator
    • Mailer
    • Modules
      • Taxonomy
        • Support
      • Thumbnailer
        • Versions
    • Object
    • Operation
      • Dispatcher
    • Prototype
    • Routes
    • Routing
      • Dispatcher
    • Session
  • Icybee
    • ActiveRecord
      • Model
    • ConfigOperation
    • Document
    • EditBlock
    • Element
      • ActionbarContextual
      • ActionbarSearch
      • ActionbarToolbar
    • FormBlock
    • Installer
    • ManageBlock
    • Modules
      • Articles
      • Cache
        • Collection
        • ManageBlock
      • Comments
        • ManageBlock
      • Contents
        • ManageBlock
      • Dashboard
      • Editor
        • Collection
      • Files
        • File
        • ManageBlock
      • Forms
        • Form
        • ManageBlock
      • I18n
      • Images
        • ManageBlock
      • Members
      • Modules
        • ManageBlock
      • Nodes
        • ManageBlock
        • Module
      • Pages
        • BreadcrumbElement
        • LanguagesElement
        • ManageBlock
        • NavigationBranchElement
        • NavigationElement
        • Page
        • PageController
      • Registry
      • Search
      • Seo
      • Sites
        • ManageBlock
      • Taxonomy
        • Terms
          • ManageBlock
        • Vocabulary
          • ManageBlock
      • Users
        • ManageBlock
        • NonceLogin
        • Roles
      • Views
        • ActiveRecordProvider
        • Collection
        • View
    • Operation
      • ActiveRecord
      • Constructor
      • Module
      • Widget
    • Rendering
  • None
  • Patron
  • PHP

Classes

  • FormattedString
  • Helpers
  • Locale
  • NumberFormatter
  • Translator

Functions

  • date_period
  • format_currency
  • format_date
  • format_datetime
  • format_number
  • format_size
  • format_time
  • get_cldr
  • get_language
  • get_locale
  • set_locale
  • t
  1 <?php
  2 
  3 /*
  4  * This file is part of the ICanBoogie package.
  5  *
  6  * (c) Olivier Laviale <olivier.laviale@gmail.com>
  7  *
  8  * For the full copyright and license information, please view the LICENSE
  9  * file that was distributed with this source code.
 10  *
 11  * Original code: http://code.google.com/p/yii/source/browse/tags/1.1.6/framework/i18n/CNumberFormatter.php
 12  */
 13 
 14 namespace ICanBoogie\I18n;
 15 
 16 /**
 17  * NumberFormatter provides number localization features.
 18  *
 19  * NumberFormatter formats a number (integer or float) and outputs a string
 20  * based on the specified format. A NumberFormatter instance is associated with a locale,
 21  * and thus generates the string representation of the number in a locale-dependent fashion.
 22  *
 23  * NumberFormatter currently supports currency format, percentage format, decimal format,
 24  * and custom format. The first three formats are specified in the locale data, while the custom
 25  * format allows you to enter an arbitrary format string.
 26  *
 27  * A format string may consist of the following special characters:
 28  * <ul>
 29  * <li>dot (.): the decimal point. It will be replaced with the localized decimal point.</li>
 30  * <li>comma (,): the grouping separator. It will be replaced with the localized grouping separator.</li>
 31  * <li>zero (0): required digit. This specifies the places where a digit must appear (will pad 0 if not).</li>
 32  * <li>hash (#): optional digit. This is mainly used to specify the location of decimal point and grouping separators.</li>
 33  * <li>currency (¤): the currency placeholder. It will be replaced with the localized currency symbol.</li>
 34  * <li>percentage (%): the percetage mark. If appearing, the number will be multiplied by 100 before being formatted.</li>
 35  * <li>permillage (‰): the permillage mark. If appearing, the number will be multiplied by 1000 before being formatted.</li>
 36  * <li>semicolon (;): the character separating positive and negative number sub-patterns.</li>
 37  * </ul>
 38  *
 39  * Anything surrounding the pattern (or sub-patterns) will be kept.
 40  *
 41  * The followings are some examples:
 42  * <pre>
 43  * Pattern "#,##0.00" will format 12345.678 as "12,345.68".
 44  * Pattern "#,#,#0.00" will format 12345.6 as "1,2,3,45.60".
 45  * </pre>
 46  * Note, in the first example, the number is rounded first before applying the formatting.
 47  * And in the second example, the pattern specifies two grouping sizes.
 48  *
 49  * NumberFormatter attempts to implement number formatting according to
 50  * the {@link http://www.unicode.org/reports/tr35/ Unicode Technical Standard #35}.
 51  * The following features are NOT implemented:
 52  * <ul>
 53  * <li>significant digit</li>
 54  * <li>scientific format</li>
 55  * <li>arbitrary literal characters</li>
 56  * <li>arbitrary padding</li>
 57  * </ul>
 58  *
 59  * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
 60  * @author Qiang Xue <qiang.xue@gmail.com>
 61  * @author Olivier Laviale <gmail@olvlvl.com>
 62  */
 63 class NumberFormatter
 64 {
 65     /**
 66      * Locale.
 67      *
 68      * @var Locale
 69      */
 70     protected $locale;
 71 
 72     /**
 73      * Shortcut to the locale `numbers` convention.
 74      *
 75      * @var array
 76      */
 77     protected $conventions;
 78 
 79     /**
 80      * Parsed formats cache.
 81      *
 82      * @var array
 83      */
 84     private $formats = array();
 85 
 86 
 87     /**
 88      * Constructor.
 89      *
 90      * @param Locale $locale Locale instance.
 91      */
 92     public function __construct(Locale $locale)
 93     {
 94         $this->locale = $locale;
 95         $this->conventions = $locale['numbers'];
 96     }
 97 
 98     /**
 99      * Formats a number based on the specified pattern.
100      * Note, if the format contains '%', the number will be multiplied by 100 first.
101      * If the format contains '‰', the number will be multiplied by 1000.
102      * If the format contains currency placeholder, it will be replaced by
103      * the specified localized currency symbol.
104      * @param string $pattern format pattern
105      * @param mixed $value the number to be formatted
106      * @param string $currency 3-letter ISO 4217 code. For example, the code "USD" represents the US Dollar and "EUR" represents the Euro currency.
107      * The currency placeholder in the pattern will be replaced with the currency symbol.
108      * If null, no replacement will be done.
109      * @return string the formatting result.
110      */
111     public function format($value, $pattern=null, $currency=null)
112     {
113         if (!$pattern)
114         {
115             $pattern = $this->conventions['decimalFormats']['decimalFormatLength']['decimalFormat']['pattern'];
116         }
117 
118         $format = $this->parse_format($pattern);
119         $result = $this->format_number($value, $format);
120 
121         if ($currency === null)
122         {
123             return $result;
124         }
125 
126         $currencies = $this->conventions['currencies'];
127         $symbol = isset($currencies[$currency]['symbol']) ? $currencies[$currency]['symbol'] : $currency;
128 
129         return str_replace('¤', $symbol, $result);
130     }
131 
132     /**
133      * Formats a number using the currency format defined in the locale.
134      * @param mixed $value the number to be formatted
135      * @param string $currency 3-letter ISO 4217 code. For example, the code "USD" represents the US Dollar and "EUR" represents the Euro currency.
136      * The currency placeholder in the pattern will be replaced with the currency symbol.
137      * @return string the formatting result.
138      */
139     public function format_currency($value, $currency)
140     {
141         return $this->format($value, $this->conventions['currencyFormats']['currencyFormatLength']['currencyFormat']['pattern'], $currency);
142     }
143 
144     /**
145      * Formats a number using the percentage format defined in the locale.
146      * Note, if the percentage format contains '%', the number will be multiplied by 100 first.
147      * If the percentage format contains '‰', the number will be multiplied by 1000.
148      * @param mixed $value the number to be formatted
149      * @return string the formatting result.
150      */
151     public function format_percentage($value)
152     {
153         return $this->format($this->locale->getPercentFormat(), $value);
154     }
155 
156     /**
157      * Formats a number using the decimal format defined in the locale.
158      * @param mixed $value the number to be formatted
159      * @return string the formatting result.
160      */
161     public function format_decimal($value)
162     {
163         return $this->format($this->locale->getDecimalFormat(),$value);
164     }
165 
166     /**
167      * Formats a number based on a format.
168      * This is the method that does actual number formatting.
169      * @param array $format format with the following structure:
170      * <pre>
171      * array(
172      *  'decimalDigits'=>2,     // number of required digits after decimal point; 0s will be padded if not enough digits; if -1, it means we should drop decimal point
173      *  'maxDecimalDigits'=>3,  // maximum number of digits after decimal point. Additional digits will be truncated.
174      *  'integerDigits'=>1,     // number of required digits before decimal point; 0s will be padded if not enough digits
175      *  'groupSize1'=>3,        // the primary grouping size; if 0, it means no grouping
176      *  'groupSize2'=>0,        // the secondary grouping size; if 0, it means no secondary grouping
177      *  'positivePrefix'=>'+',  // prefix to positive number
178      *  'positiveSuffix'=>'',   // suffix to positive number
179      *  'negativePrefix'=>'(',  // prefix to negative number
180      *  'negativeSuffix'=>')',  // suffix to negative number
181      *  'multiplier'=>1,        // 100 for percent, 1000 for per mille
182      * );
183      * </pre>
184      * @param mixed $value the number to be formatted
185      * @return string the formatted result
186      */
187     protected function format_number($value, $format)
188     {
189         $negative=$value<0;
190         $value=abs($value*$format['multiplier']);
191         if($format['maxDecimalDigits']>=0)
192             $value=round($value,$format['maxDecimalDigits']);
193         $value="$value";
194         if(($pos=strpos($value,'.'))!==false)
195         {
196             $integer=substr($value,0,$pos);
197             $decimal=substr($value,$pos+1);
198         }
199         else
200         {
201             $integer=$value;
202             $decimal='';
203         }
204 
205         if($format['decimalDigits']>strlen($decimal))
206             $decimal=str_pad($decimal,$format['decimalDigits'],'0');
207 
208         if (strlen($decimal))
209         {
210             $decimal = $this->conventions['symbols']['decimal'] . $decimal;
211         }
212 
213         $integer=str_pad($integer,$format['integerDigits'],'0',STR_PAD_LEFT);
214         if($format['groupSize1']>0 && strlen($integer)>$format['groupSize1'])
215         {
216             $str1=substr($integer,0,-$format['groupSize1']);
217             $str2=substr($integer,-$format['groupSize1']);
218             $size=$format['groupSize2']>0?$format['groupSize2']:$format['groupSize1'];
219             $str1=str_pad($str1,(int)((strlen($str1)+$size-1)/$size)*$size,' ',STR_PAD_LEFT);
220             $integer=ltrim(implode($this->conventions['symbols']['group'],str_split($str1,$size))).$this->conventions['symbols']['group'].$str2;
221         }
222 
223         if($negative)
224             $number=$format['negativePrefix'].$integer.$decimal.$format['negativeSuffix'];
225         else
226             $number=$format['positivePrefix'].$integer.$decimal.$format['positiveSuffix'];
227 
228         return strtr($number,array('%'=>$this->conventions['symbols']['percentSign'],'‰'=>$this->conventions['symbols']['perMille']));
229     }
230 
231     /**
232      * Parses a given string pattern.
233      *
234      * @param string $pattern the pattern to be parsed
235      *
236      * @return array the parsed pattern
237      *
238      * @see format_number
239      */
240     protected function parse_format($pattern)
241     {
242         if (isset($this->formats[$pattern]))
243         {
244             return $this->formats[$pattern];
245         }
246 
247         $format = array
248         (
249             'positivePrefix' => '',
250             'positiveSuffix' => '',
251             'negativePrefix' => '',
252             'negativeSuffix' => ''
253         );
254 
255         // find out prefix and suffix for positive and negative patterns
256         $patterns = explode(';',$pattern);
257 
258         if (preg_match('/^(.*?)[#,\.0]+(.*?)$/', $patterns[0], $matches))
259         {
260             $format['positivePrefix'] = $matches[1];
261             $format['positiveSuffix'] = $matches[2];
262         }
263 
264         if (isset($patterns[1]) && preg_match('/^(.*?)[#,\.0]+(.*?)$/', $patterns[1], $matches))  // with a negative pattern
265         {
266             $format['negativePrefix'] = $matches[1];
267             $format['negativeSuffix'] = $matches[2];
268         }
269         else
270         {
271             $format['negativePrefix'] = $this->conventions['symbols']['minusSign'] . $format['positivePrefix'];
272             $format['negativeSuffix'] = $format['positiveSuffix'];
273         }
274 
275         $pat = $patterns[0];
276 
277         // find out multiplier
278         if (strpos($pat,'%') !== false)
279         {
280             $format['multiplier'] = 100;
281         }
282         else if (strpos($pat,'‰') !== false)
283         {
284             $format['multiplier'] = 1000;
285         }
286         else
287         {
288             $format['multiplier'] = 1;
289         }
290 
291         // find out things about decimal part
292         if (($pos = strpos($pat,'.')) !== false)
293         {
294             if (($pos2 = strrpos($pat,'0')) > $pos)
295             {
296                 $format['decimalDigits'] = $pos2-$pos;
297             }
298             else
299             {
300                 $format['decimalDigits'] = 0;
301             }
302 
303             if (($pos3 = strrpos($pat,'#')) >= $pos2)
304             {
305                 $format['maxDecimalDigits'] = $pos3 - $pos;
306             }
307             else
308             {
309                 $format['maxDecimalDigits'] = $format['decimalDigits'];
310             }
311 
312             $pat = substr($pat, 0, $pos);
313         }
314         else   // no decimal part
315         {
316             $format['decimalDigits'] = 0;
317             $format['maxDecimalDigits'] = 0;
318         }
319 
320         // find out things about integer part
321         $p = str_replace(',','',$pat);
322 
323         if (($pos=strpos($p,'0')) !== false)
324         {
325             $format['integerDigits'] = strrpos($p, '0') - $pos + 1;
326         }
327         else
328         {
329             $format['integerDigits'] = 0;
330         }
331 
332         // find out group sizes. some patterns may have two different group sizes
333 
334         $p = str_replace('#', '0', $pat);
335 
336         if (($pos = strrpos($pat, ',')) !== false)
337         {
338             $format['groupSize1'] = strrpos($p, '0') - $pos;
339 
340             if (($pos2 = strrpos(substr($p, 0, $pos), ',')) !== false)
341             {
342                 $format['groupSize2'] = $pos - $pos2 - 1;
343             }
344             else
345             {
346                 $format['groupSize2'] = 0;
347             }
348         }
349         else
350         {
351             $format['groupSize1'] = $format['groupSize2'] = 0;
352         }
353 
354         return $this->formats[$pattern] = $format;
355     }
356 }
Autodoc API documentation generated by ApiGen 2.8.0