]>
Commit | Line | Data |
---|---|---|
4f5b44bd NL |
1 | <?php |
2 | ||
3 | /* | |
4 | * This file is part of the Symfony package. | |
5 | * | |
6 | * (c) Fabien Potencier <fabien@symfony.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 | ||
12 | namespace Symfony\Component\Intl\NumberFormatter; | |
13 | ||
14 | use Symfony\Component\Intl\Exception\NotImplementedException; | |
15 | use Symfony\Component\Intl\Exception\MethodNotImplementedException; | |
16 | use Symfony\Component\Intl\Exception\MethodArgumentNotImplementedException; | |
17 | use Symfony\Component\Intl\Exception\MethodArgumentValueNotImplementedException; | |
18 | use Symfony\Component\Intl\Globals\IntlGlobals; | |
19 | use Symfony\Component\Intl\Intl; | |
20 | use Symfony\Component\Intl\Locale\Locale; | |
21 | ||
22 | /** | |
23 | * Replacement for PHP's native {@link \NumberFormatter} class. | |
24 | * | |
25 | * The only methods currently supported in this class are: | |
26 | * | |
27 | * - {@link __construct} | |
28 | * - {@link create} | |
29 | * - {@link formatCurrency} | |
30 | * - {@link format} | |
31 | * - {@link getAttribute} | |
32 | * - {@link getErrorCode} | |
33 | * - {@link getErrorMessage} | |
34 | * - {@link getLocale} | |
35 | * - {@link parse} | |
36 | * - {@link setAttribute} | |
37 | * | |
38 | * @author Eriksen Costa <eriksen.costa@infranology.com.br> | |
39 | * @author Bernhard Schussek <bschussek@gmail.com> | |
40 | */ | |
41 | class NumberFormatter | |
42 | { | |
43 | /* Format style constants */ | |
44 | const PATTERN_DECIMAL = 0; | |
45 | const DECIMAL = 1; | |
46 | const CURRENCY = 2; | |
47 | const PERCENT = 3; | |
48 | const SCIENTIFIC = 4; | |
49 | const SPELLOUT = 5; | |
50 | const ORDINAL = 6; | |
51 | const DURATION = 7; | |
52 | const PATTERN_RULEBASED = 9; | |
53 | const IGNORE = 0; | |
54 | const DEFAULT_STYLE = 1; | |
55 | ||
56 | /* Format type constants */ | |
57 | const TYPE_DEFAULT = 0; | |
58 | const TYPE_INT32 = 1; | |
59 | const TYPE_INT64 = 2; | |
60 | const TYPE_DOUBLE = 3; | |
61 | const TYPE_CURRENCY = 4; | |
62 | ||
63 | /* Numeric attribute constants */ | |
64 | const PARSE_INT_ONLY = 0; | |
65 | const GROUPING_USED = 1; | |
66 | const DECIMAL_ALWAYS_SHOWN = 2; | |
67 | const MAX_INTEGER_DIGITS = 3; | |
68 | const MIN_INTEGER_DIGITS = 4; | |
69 | const INTEGER_DIGITS = 5; | |
70 | const MAX_FRACTION_DIGITS = 6; | |
71 | const MIN_FRACTION_DIGITS = 7; | |
72 | const FRACTION_DIGITS = 8; | |
73 | const MULTIPLIER = 9; | |
74 | const GROUPING_SIZE = 10; | |
75 | const ROUNDING_MODE = 11; | |
76 | const ROUNDING_INCREMENT = 12; | |
77 | const FORMAT_WIDTH = 13; | |
78 | const PADDING_POSITION = 14; | |
79 | const SECONDARY_GROUPING_SIZE = 15; | |
80 | const SIGNIFICANT_DIGITS_USED = 16; | |
81 | const MIN_SIGNIFICANT_DIGITS = 17; | |
82 | const MAX_SIGNIFICANT_DIGITS = 18; | |
83 | const LENIENT_PARSE = 19; | |
84 | ||
85 | /* Text attribute constants */ | |
86 | const POSITIVE_PREFIX = 0; | |
87 | const POSITIVE_SUFFIX = 1; | |
88 | const NEGATIVE_PREFIX = 2; | |
89 | const NEGATIVE_SUFFIX = 3; | |
90 | const PADDING_CHARACTER = 4; | |
91 | const CURRENCY_CODE = 5; | |
92 | const DEFAULT_RULESET = 6; | |
93 | const PUBLIC_RULESETS = 7; | |
94 | ||
95 | /* Format symbol constants */ | |
96 | const DECIMAL_SEPARATOR_SYMBOL = 0; | |
97 | const GROUPING_SEPARATOR_SYMBOL = 1; | |
98 | const PATTERN_SEPARATOR_SYMBOL = 2; | |
99 | const PERCENT_SYMBOL = 3; | |
100 | const ZERO_DIGIT_SYMBOL = 4; | |
101 | const DIGIT_SYMBOL = 5; | |
102 | const MINUS_SIGN_SYMBOL = 6; | |
103 | const PLUS_SIGN_SYMBOL = 7; | |
104 | const CURRENCY_SYMBOL = 8; | |
105 | const INTL_CURRENCY_SYMBOL = 9; | |
106 | const MONETARY_SEPARATOR_SYMBOL = 10; | |
107 | const EXPONENTIAL_SYMBOL = 11; | |
108 | const PERMILL_SYMBOL = 12; | |
109 | const PAD_ESCAPE_SYMBOL = 13; | |
110 | const INFINITY_SYMBOL = 14; | |
111 | const NAN_SYMBOL = 15; | |
112 | const SIGNIFICANT_DIGIT_SYMBOL = 16; | |
113 | const MONETARY_GROUPING_SEPARATOR_SYMBOL = 17; | |
114 | ||
115 | /* Rounding mode values used by NumberFormatter::setAttribute() with NumberFormatter::ROUNDING_MODE attribute */ | |
116 | const ROUND_CEILING = 0; | |
117 | const ROUND_FLOOR = 1; | |
118 | const ROUND_DOWN = 2; | |
119 | const ROUND_UP = 3; | |
120 | const ROUND_HALFEVEN = 4; | |
121 | const ROUND_HALFDOWN = 5; | |
122 | const ROUND_HALFUP = 6; | |
123 | ||
124 | /* Pad position values used by NumberFormatter::setAttribute() with NumberFormatter::PADDING_POSITION attribute */ | |
125 | const PAD_BEFORE_PREFIX = 0; | |
126 | const PAD_AFTER_PREFIX = 1; | |
127 | const PAD_BEFORE_SUFFIX = 2; | |
128 | const PAD_AFTER_SUFFIX = 3; | |
129 | ||
130 | /** | |
131 | * The error code from the last operation | |
132 | * | |
133 | * @var integer | |
134 | */ | |
135 | protected $errorCode = IntlGlobals::U_ZERO_ERROR; | |
136 | ||
137 | /** | |
138 | * The error message from the last operation | |
139 | * | |
140 | * @var string | |
141 | */ | |
142 | protected $errorMessage = 'U_ZERO_ERROR'; | |
143 | ||
144 | /** | |
145 | * @var int | |
146 | */ | |
147 | private $style; | |
148 | ||
149 | /** | |
150 | * Default values for the en locale | |
151 | * | |
152 | * @var array | |
153 | */ | |
154 | private $attributes = array( | |
155 | self::FRACTION_DIGITS => 0, | |
156 | self::GROUPING_USED => 1, | |
157 | self::ROUNDING_MODE => self::ROUND_HALFEVEN | |
158 | ); | |
159 | ||
160 | /** | |
161 | * Holds the initialized attributes code | |
162 | * | |
163 | * @var array | |
164 | */ | |
165 | private $initializedAttributes = array(); | |
166 | ||
167 | /** | |
168 | * The supported styles to the constructor $styles argument | |
169 | * | |
170 | * @var array | |
171 | */ | |
172 | private static $supportedStyles = array( | |
173 | 'CURRENCY' => self::CURRENCY, | |
174 | 'DECIMAL' => self::DECIMAL | |
175 | ); | |
176 | ||
177 | /** | |
178 | * Supported attributes to the setAttribute() $attr argument | |
179 | * | |
180 | * @var array | |
181 | */ | |
182 | private static $supportedAttributes = array( | |
183 | 'FRACTION_DIGITS' => self::FRACTION_DIGITS, | |
184 | 'GROUPING_USED' => self::GROUPING_USED, | |
185 | 'ROUNDING_MODE' => self::ROUNDING_MODE | |
186 | ); | |
187 | ||
188 | /** | |
189 | * The available rounding modes for setAttribute() usage with | |
190 | * NumberFormatter::ROUNDING_MODE. NumberFormatter::ROUND_DOWN | |
191 | * and NumberFormatter::ROUND_UP does not have a PHP only equivalent | |
192 | * | |
193 | * @var array | |
194 | */ | |
195 | private static $roundingModes = array( | |
196 | 'ROUND_HALFEVEN' => self::ROUND_HALFEVEN, | |
197 | 'ROUND_HALFDOWN' => self::ROUND_HALFDOWN, | |
198 | 'ROUND_HALFUP' => self::ROUND_HALFUP | |
199 | ); | |
200 | ||
201 | /** | |
202 | * The mapping between NumberFormatter rounding modes to the available | |
203 | * modes in PHP's round() function. | |
204 | * | |
205 | * @see http://www.php.net/manual/en/function.round.php | |
206 | * | |
207 | * @var array | |
208 | */ | |
209 | private static $phpRoundingMap = array( | |
210 | self::ROUND_HALFDOWN => \PHP_ROUND_HALF_DOWN, | |
211 | self::ROUND_HALFEVEN => \PHP_ROUND_HALF_EVEN, | |
212 | self::ROUND_HALFUP => \PHP_ROUND_HALF_UP | |
213 | ); | |
214 | ||
215 | /** | |
216 | * The maximum values of the integer type in 32 bit platforms. | |
217 | * | |
218 | * @var array | |
219 | */ | |
220 | private static $int32Range = array( | |
221 | 'positive' => 2147483647, | |
222 | 'negative' => -2147483648 | |
223 | ); | |
224 | ||
225 | /** | |
226 | * The maximum values of the integer type in 64 bit platforms. | |
227 | * | |
228 | * @var array | |
229 | */ | |
230 | private static $int64Range = array( | |
231 | 'positive' => 9223372036854775807, | |
232 | 'negative' => -9223372036854775808 | |
233 | ); | |
234 | ||
235 | /** | |
236 | * Constructor. | |
237 | * | |
238 | * @param string $locale The locale code. The only currently supported locale is "en". | |
239 | * @param int $style Style of the formatting, one of the format style constants. | |
240 | * The only supported styles are NumberFormatter::DECIMAL | |
241 | * and NumberFormatter::CURRENCY. | |
242 | * @param string $pattern Not supported. A pattern string in case $style is NumberFormat::PATTERN_DECIMAL or | |
243 | * NumberFormat::PATTERN_RULEBASED. It must conform to the syntax | |
244 | * described in the ICU DecimalFormat or ICU RuleBasedNumberFormat documentation | |
245 | * | |
246 | * @see http://www.php.net/manual/en/numberformatter.create.php | |
247 | * @see http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details | |
248 | * @see http://www.icu-project.org/apiref/icu4c/classRuleBasedNumberFormat.html#_details | |
249 | * | |
250 | * @throws MethodArgumentValueNotImplementedException When $locale different than "en" is passed | |
251 | * @throws MethodArgumentValueNotImplementedException When the $style is not supported | |
252 | * @throws MethodArgumentNotImplementedException When the pattern value is different than null | |
253 | */ | |
254 | public function __construct($locale = 'en', $style = null, $pattern = null) | |
255 | { | |
256 | if ('en' != $locale) { | |
257 | throw new MethodArgumentValueNotImplementedException(__METHOD__, 'locale', $locale, 'Only the locale "en" is supported'); | |
258 | } | |
259 | ||
260 | if (!in_array($style, self::$supportedStyles)) { | |
261 | $message = sprintf('The available styles are: %s.', implode(', ', array_keys(self::$supportedStyles))); | |
262 | throw new MethodArgumentValueNotImplementedException(__METHOD__, 'style', $style, $message); | |
263 | } | |
264 | ||
265 | if (null !== $pattern) { | |
266 | throw new MethodArgumentNotImplementedException(__METHOD__, 'pattern'); | |
267 | } | |
268 | ||
269 | $this->style = $style; | |
270 | } | |
271 | ||
272 | /** | |
273 | * Static constructor. | |
274 | * | |
275 | * @param string $locale The locale code. The only supported locale is "en". | |
276 | * @param int $style Style of the formatting, one of the format style constants. | |
277 | * The only currently supported styles are NumberFormatter::DECIMAL | |
278 | * and NumberFormatter::CURRENCY. | |
279 | * @param string $pattern Not supported. A pattern string in case $style is NumberFormat::PATTERN_DECIMAL or | |
280 | * NumberFormat::PATTERN_RULEBASED. It must conform to the syntax | |
281 | * described in the ICU DecimalFormat or ICU RuleBasedNumberFormat documentation | |
282 | * | |
283 | * @return NumberFormatter | |
284 | * | |
285 | * @see http://www.php.net/manual/en/numberformatter.create.php | |
286 | * @see http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details | |
287 | * @see http://www.icu-project.org/apiref/icu4c/classRuleBasedNumberFormat.html#_details | |
288 | * | |
289 | * @throws MethodArgumentValueNotImplementedException When $locale different than "en" is passed | |
290 | * @throws MethodArgumentValueNotImplementedException When the $style is not supported | |
291 | * @throws MethodArgumentNotImplementedException When the pattern value is different than null | |
292 | */ | |
293 | public static function create($locale = 'en', $style = null, $pattern = null) | |
294 | { | |
295 | return new self($locale, $style, $pattern); | |
296 | } | |
297 | ||
298 | /** | |
299 | * Format a currency value | |
300 | * | |
301 | * @param float $value The numeric currency value | |
302 | * @param string $currency The 3-letter ISO 4217 currency code indicating the currency to use | |
303 | * | |
304 | * @return string The formatted currency value | |
305 | * | |
306 | * @see http://www.php.net/manual/en/numberformatter.formatcurrency.php | |
307 | * @see http://www.iso.org/iso/support/faqs/faqs_widely_used_standards/widely_used_standards_other/currency_codes/currency_codes_list-1.htm | |
308 | */ | |
309 | public function formatCurrency($value, $currency) | |
310 | { | |
311 | if ($this->style == self::DECIMAL) { | |
312 | return $this->format($value); | |
313 | } | |
314 | ||
315 | $symbol = Intl::getCurrencyBundle()->getCurrencySymbol($currency, 'en'); | |
316 | $fractionDigits = Intl::getCurrencyBundle()->getFractionDigits($currency); | |
317 | ||
318 | $value = $this->roundCurrency($value, $currency); | |
319 | ||
320 | $negative = false; | |
321 | if (0 > $value) { | |
322 | $negative = true; | |
323 | $value *= -1; | |
324 | } | |
325 | ||
326 | $value = $this->formatNumber($value, $fractionDigits); | |
327 | ||
328 | $ret = $symbol.$value; | |
329 | ||
330 | return $negative ? '('.$ret.')' : $ret; | |
331 | } | |
332 | ||
333 | /** | |
334 | * Format a number | |
335 | * | |
336 | * @param number $value The value to format | |
337 | * @param int $type Type of the formatting, one of the format type constants. | |
338 | * Only type NumberFormatter::TYPE_DEFAULT is currently supported. | |
339 | * | |
340 | * @return Boolean|string The formatted value or false on error | |
341 | * | |
342 | * @see http://www.php.net/manual/en/numberformatter.format.php | |
343 | * | |
344 | * @throws NotImplementedException If the method is called with the class $style 'CURRENCY' | |
345 | * @throws MethodArgumentValueNotImplementedException If the $type is different than TYPE_DEFAULT | |
346 | */ | |
347 | public function format($value, $type = self::TYPE_DEFAULT) | |
348 | { | |
349 | // The original NumberFormatter does not support this format type | |
350 | if ($type == self::TYPE_CURRENCY) { | |
351 | trigger_error(__METHOD__.'(): Unsupported format type '.$type, \E_USER_WARNING); | |
352 | ||
353 | return false; | |
354 | } | |
355 | ||
356 | if ($this->style == self::CURRENCY) { | |
357 | throw new NotImplementedException(sprintf( | |
358 | '%s() method does not support the formatting of currencies (instance with CURRENCY style). %s', | |
359 | __METHOD__, NotImplementedException::INTL_INSTALL_MESSAGE | |
360 | )); | |
361 | } | |
362 | ||
363 | // Only the default type is supported. | |
364 | if ($type != self::TYPE_DEFAULT) { | |
365 | throw new MethodArgumentValueNotImplementedException(__METHOD__, 'type', $type, 'Only TYPE_DEFAULT is supported'); | |
366 | } | |
367 | ||
368 | $fractionDigits = $this->getAttribute(self::FRACTION_DIGITS); | |
369 | ||
370 | $value = $this->round($value, $fractionDigits); | |
371 | $value = $this->formatNumber($value, $fractionDigits); | |
372 | ||
373 | // behave like the intl extension | |
374 | $this->resetError(); | |
375 | ||
376 | return $value; | |
377 | } | |
378 | ||
379 | /** | |
380 | * Returns an attribute value | |
381 | * | |
382 | * @param int $attr An attribute specifier, one of the numeric attribute constants | |
383 | * | |
384 | * @return Boolean|int The attribute value on success or false on error | |
385 | * | |
386 | * @see http://www.php.net/manual/en/numberformatter.getattribute.php | |
387 | */ | |
388 | public function getAttribute($attr) | |
389 | { | |
390 | return isset($this->attributes[$attr]) ? $this->attributes[$attr] : null; | |
391 | } | |
392 | ||
393 | /** | |
394 | * Returns formatter's last error code. Always returns the U_ZERO_ERROR class constant value | |
395 | * | |
396 | * @return int The error code from last formatter call | |
397 | * | |
398 | * @see http://www.php.net/manual/en/numberformatter.geterrorcode.php | |
399 | */ | |
400 | public function getErrorCode() | |
401 | { | |
402 | return $this->errorCode; | |
403 | } | |
404 | ||
405 | /** | |
406 | * Returns formatter's last error message. Always returns the U_ZERO_ERROR_MESSAGE class constant value | |
407 | * | |
408 | * @return string The error message from last formatter call | |
409 | * | |
410 | * @see http://www.php.net/manual/en/numberformatter.geterrormessage.php | |
411 | */ | |
412 | public function getErrorMessage() | |
413 | { | |
414 | return $this->errorMessage; | |
415 | } | |
416 | ||
417 | /** | |
418 | * Returns the formatter's locale | |
419 | * | |
420 | * The parameter $type is currently ignored. | |
421 | * | |
422 | * @param int $type Not supported. The locale name type to return (Locale::VALID_LOCALE or Locale::ACTUAL_LOCALE) | |
423 | * | |
424 | * @return string The locale used to create the formatter. Currently always | |
425 | * returns "en". | |
426 | * | |
427 | * @see http://www.php.net/manual/en/numberformatter.getlocale.php | |
428 | */ | |
429 | public function getLocale($type = Locale::ACTUAL_LOCALE) | |
430 | { | |
431 | return 'en'; | |
432 | } | |
433 | ||
434 | /** | |
435 | * Not supported. Returns the formatter's pattern | |
436 | * | |
437 | * @return Boolean|string The pattern string used by the formatter or false on error | |
438 | * | |
439 | * @see http://www.php.net/manual/en/numberformatter.getpattern.php | |
440 | * | |
441 | * @throws MethodNotImplementedException | |
442 | */ | |
443 | public function getPattern() | |
444 | { | |
445 | throw new MethodNotImplementedException(__METHOD__); | |
446 | } | |
447 | ||
448 | /** | |
449 | * Not supported. Returns a formatter symbol value | |
450 | * | |
451 | * @param int $attr A symbol specifier, one of the format symbol constants | |
452 | * | |
453 | * @return Boolean|string The symbol value or false on error | |
454 | * | |
455 | * @see http://www.php.net/manual/en/numberformatter.getsymbol.php | |
456 | * | |
457 | * @throws MethodNotImplementedException | |
458 | */ | |
459 | public function getSymbol($attr) | |
460 | { | |
461 | throw new MethodNotImplementedException(__METHOD__); | |
462 | } | |
463 | ||
464 | /** | |
465 | * Not supported. Returns a formatter text attribute value | |
466 | * | |
467 | * @param int $attr An attribute specifier, one of the text attribute constants | |
468 | * | |
469 | * @return Boolean|string The attribute value or false on error | |
470 | * | |
471 | * @see http://www.php.net/manual/en/numberformatter.gettextattribute.php | |
472 | * | |
473 | * @throws MethodNotImplementedException | |
474 | */ | |
475 | public function getTextAttribute($attr) | |
476 | { | |
477 | throw new MethodNotImplementedException(__METHOD__); | |
478 | } | |
479 | ||
480 | /** | |
481 | * Not supported. Parse a currency number | |
482 | * | |
483 | * @param string $value The value to parse | |
484 | * @param string $currency Parameter to receive the currency name (reference) | |
485 | * @param int $position Offset to begin the parsing on return this value will hold the offset at which the parsing ended | |
486 | * | |
487 | * @return Boolean|string The parsed numeric value of false on error | |
488 | * | |
489 | * @see http://www.php.net/manual/en/numberformatter.parsecurrency.php | |
490 | * | |
491 | * @throws MethodNotImplementedException | |
492 | */ | |
493 | public function parseCurrency($value, &$currency, &$position = null) | |
494 | { | |
495 | throw new MethodNotImplementedException(__METHOD__); | |
496 | } | |
497 | ||
498 | /** | |
499 | * Parse a number | |
500 | * | |
501 | * @param string $value The value to parse | |
502 | * @param int $type Type of the formatting, one of the format type constants. | |
503 | * The only currently supported types are NumberFormatter::TYPE_DOUBLE, | |
504 | * NumberFormatter::TYPE_INT32 and NumberFormatter::TYPE_INT64. | |
505 | * @param int $position Not supported. Offset to begin the parsing on return this value will hold the offset at which the parsing ended | |
506 | * | |
507 | * @return Boolean|string The parsed value of false on error | |
508 | * | |
509 | * @see http://www.php.net/manual/en/numberformatter.parse.php | |
510 | * | |
511 | * @throws MethodArgumentNotImplementedException When $position different than null, behavior not implemented | |
512 | */ | |
513 | public function parse($value, $type = self::TYPE_DOUBLE, &$position = null) | |
514 | { | |
515 | if ($type == self::TYPE_DEFAULT || $type == self::TYPE_CURRENCY) { | |
516 | trigger_error(__METHOD__.'(): Unsupported format type '.$type, \E_USER_WARNING); | |
517 | ||
518 | return false; | |
519 | } | |
520 | ||
521 | // We don't calculate the position when parsing the value | |
522 | if (null !== $position) { | |
523 | throw new MethodArgumentNotImplementedException(__METHOD__, 'position'); | |
524 | } | |
525 | ||
526 | preg_match('/^([^0-9\-]{0,})(.*)/', $value, $matches); | |
527 | ||
528 | // Any string before the numeric value causes error in the parsing | |
529 | if (isset($matches[1]) && !empty($matches[1])) { | |
530 | IntlGlobals::setError(IntlGlobals::U_PARSE_ERROR, 'Number parsing failed'); | |
531 | $this->errorCode = IntlGlobals::getErrorCode(); | |
532 | $this->errorMessage = IntlGlobals::getErrorMessage(); | |
533 | ||
534 | return false; | |
535 | } | |
536 | ||
537 | // Remove everything that is not number or dot (.) | |
538 | $value = preg_replace('/[^0-9\.\-]/', '', $value); | |
539 | $value = $this->convertValueDataType($value, $type); | |
540 | ||
541 | // behave like the intl extension | |
542 | $this->resetError(); | |
543 | ||
544 | return $value; | |
545 | } | |
546 | ||
547 | /** | |
548 | * Set an attribute | |
549 | * | |
550 | * @param int $attr An attribute specifier, one of the numeric attribute constants. | |
551 | * The only currently supported attributes are NumberFormatter::FRACTION_DIGITS, | |
552 | * NumberFormatter::GROUPING_USED and NumberFormatter::ROUNDING_MODE. | |
553 | * @param int $value The attribute value. The only currently supported rounding modes are | |
554 | * NumberFormatter::ROUND_HALFEVEN, NumberFormatter::ROUND_HALFDOWN and | |
555 | * NumberFormatter::ROUND_HALFUP. | |
556 | * | |
557 | * @return Boolean true on success or false on failure | |
558 | * | |
559 | * @see http://www.php.net/manual/en/numberformatter.setattribute.php | |
560 | * | |
561 | * @throws MethodArgumentValueNotImplementedException When the $attr is not supported | |
562 | * @throws MethodArgumentValueNotImplementedException When the $value is not supported | |
563 | */ | |
564 | public function setAttribute($attr, $value) | |
565 | { | |
566 | if (!in_array($attr, self::$supportedAttributes)) { | |
567 | $message = sprintf( | |
568 | 'The available attributes are: %s', | |
569 | implode(', ', array_keys(self::$supportedAttributes)) | |
570 | ); | |
571 | ||
572 | throw new MethodArgumentValueNotImplementedException(__METHOD__, 'attr', $value, $message); | |
573 | } | |
574 | ||
575 | if (self::$supportedAttributes['ROUNDING_MODE'] == $attr && $this->isInvalidRoundingMode($value)) { | |
576 | $message = sprintf( | |
577 | 'The supported values for ROUNDING_MODE are: %s', | |
578 | implode(', ', array_keys(self::$roundingModes)) | |
579 | ); | |
580 | ||
581 | throw new MethodArgumentValueNotImplementedException(__METHOD__, 'attr', $value, $message); | |
582 | } | |
583 | ||
584 | if (self::$supportedAttributes['GROUPING_USED'] == $attr) { | |
585 | $value = $this->normalizeGroupingUsedValue($value); | |
586 | } | |
587 | ||
588 | if (self::$supportedAttributes['FRACTION_DIGITS'] == $attr) { | |
589 | $value = $this->normalizeFractionDigitsValue($value); | |
590 | } | |
591 | ||
592 | $this->attributes[$attr] = $value; | |
593 | $this->initializedAttributes[$attr] = true; | |
594 | ||
595 | return true; | |
596 | } | |
597 | ||
598 | /** | |
599 | * Not supported. Set the formatter's pattern | |
600 | * | |
601 | * @param string $pattern A pattern string in conformance with the ICU DecimalFormat documentation | |
602 | * | |
603 | * @return Boolean true on success or false on failure | |
604 | * | |
605 | * @see http://www.php.net/manual/en/numberformatter.setpattern.php | |
606 | * @see http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details | |
607 | * | |
608 | * @throws MethodNotImplementedException | |
609 | */ | |
610 | public function setPattern($pattern) | |
611 | { | |
612 | throw new MethodNotImplementedException(__METHOD__); | |
613 | } | |
614 | ||
615 | /** | |
616 | * Not supported. Set the formatter's symbol | |
617 | * | |
618 | * @param int $attr A symbol specifier, one of the format symbol constants | |
619 | * @param string $value The value for the symbol | |
620 | * | |
621 | * @return Boolean true on success or false on failure | |
622 | * | |
623 | * @see http://www.php.net/manual/en/numberformatter.setsymbol.php | |
624 | * | |
625 | * @throws MethodNotImplementedException | |
626 | */ | |
627 | public function setSymbol($attr, $value) | |
628 | { | |
629 | throw new MethodNotImplementedException(__METHOD__); | |
630 | } | |
631 | ||
632 | /** | |
633 | * Not supported. Set a text attribute | |
634 | * | |
635 | * @param int $attr An attribute specifier, one of the text attribute constants | |
636 | * @param int $value The attribute value | |
637 | * | |
638 | * @return Boolean true on success or false on failure | |
639 | * | |
640 | * @see http://www.php.net/manual/en/numberformatter.settextattribute.php | |
641 | * | |
642 | * @throws MethodNotImplementedException | |
643 | */ | |
644 | public function setTextAttribute($attr, $value) | |
645 | { | |
646 | throw new MethodNotImplementedException(__METHOD__); | |
647 | } | |
648 | ||
649 | /** | |
650 | * Set the error to the default U_ZERO_ERROR | |
651 | */ | |
652 | protected function resetError() | |
653 | { | |
654 | IntlGlobals::setError(IntlGlobals::U_ZERO_ERROR); | |
655 | $this->errorCode = IntlGlobals::getErrorCode(); | |
656 | $this->errorMessage = IntlGlobals::getErrorMessage(); | |
657 | } | |
658 | ||
659 | /** | |
660 | * Rounds a currency value, applying increment rounding if applicable | |
661 | * | |
662 | * When a currency have a rounding increment, an extra round is made after the first one. The rounding factor is | |
663 | * determined in the ICU data and is explained as of: | |
664 | * | |
665 | * "the rounding increment is given in units of 10^(-fraction_digits)" | |
666 | * | |
667 | * The only actual rounding data as of this writing, is CHF. | |
668 | * | |
669 | * @param float $value The numeric currency value | |
670 | * @param string $currency The 3-letter ISO 4217 currency code indicating the currency to use | |
671 | * | |
672 | * @return string The rounded numeric currency value | |
673 | * | |
674 | * @see http://en.wikipedia.org/wiki/Swedish_rounding | |
675 | * @see http://www.docjar.com/html/api/com/ibm/icu/util/Currency.java.html#1007 | |
676 | */ | |
677 | private function roundCurrency($value, $currency) | |
678 | { | |
679 | $fractionDigits = Intl::getCurrencyBundle()->getFractionDigits($currency); | |
680 | $roundingIncrement = Intl::getCurrencyBundle()->getRoundingIncrement($currency); | |
681 | ||
682 | // Round with the formatter rounding mode | |
683 | $value = $this->round($value, $fractionDigits); | |
684 | ||
685 | // Swiss rounding | |
686 | if (0 < $roundingIncrement && 0 < $fractionDigits) { | |
687 | $roundingFactor = $roundingIncrement / pow(10, $fractionDigits); | |
688 | $value = round($value / $roundingFactor) * $roundingFactor; | |
689 | } | |
690 | ||
691 | return $value; | |
692 | } | |
693 | ||
694 | /** | |
695 | * Rounds a value. | |
696 | * | |
697 | * @param integer|float $value The value to round | |
698 | * @param int $precision The number of decimal digits to round to | |
699 | * | |
700 | * @return integer|float The rounded value | |
701 | */ | |
702 | private function round($value, $precision) | |
703 | { | |
704 | $precision = $this->getUnitializedPrecision($value, $precision); | |
705 | ||
706 | $roundingMode = self::$phpRoundingMap[$this->getAttribute(self::ROUNDING_MODE)]; | |
707 | $value = round($value, $precision, $roundingMode); | |
708 | ||
709 | return $value; | |
710 | } | |
711 | ||
712 | /** | |
713 | * Formats a number. | |
714 | * | |
715 | * @param integer|float $value The numeric value to format | |
716 | * @param int $precision The number of decimal digits to use | |
717 | * | |
718 | * @return string The formatted number | |
719 | */ | |
720 | private function formatNumber($value, $precision) | |
721 | { | |
722 | $precision = $this->getUnitializedPrecision($value, $precision); | |
723 | ||
724 | return number_format($value, $precision, '.', $this->getAttribute(self::GROUPING_USED) ? ',' : ''); | |
725 | } | |
726 | ||
727 | /** | |
728 | * Returns the precision value if the DECIMAL style is being used and the FRACTION_DIGITS attribute is unitialized. | |
729 | * | |
730 | * @param integer|float $value The value to get the precision from if the FRACTION_DIGITS attribute is unitialized | |
731 | * @param int $precision The precision value to returns if the FRACTION_DIGITS attribute is initialized | |
732 | * | |
733 | * @return int The precision value | |
734 | */ | |
735 | private function getUnitializedPrecision($value, $precision) | |
736 | { | |
737 | if ($this->style == self::CURRENCY) { | |
738 | return $precision; | |
739 | } | |
740 | ||
741 | if (!$this->isInitializedAttribute(self::FRACTION_DIGITS)) { | |
742 | preg_match('/.*\.(.*)/', (string) $value, $digits); | |
743 | if (isset($digits[1])) { | |
744 | $precision = strlen($digits[1]); | |
745 | } | |
746 | } | |
747 | ||
748 | return $precision; | |
749 | } | |
750 | ||
751 | /** | |
752 | * Check if the attribute is initialized (value set by client code). | |
753 | * | |
754 | * @param string $attr The attribute name | |
755 | * | |
756 | * @return Boolean true if the value was set by client, false otherwise | |
757 | */ | |
758 | private function isInitializedAttribute($attr) | |
759 | { | |
760 | return isset($this->initializedAttributes[$attr]); | |
761 | } | |
762 | ||
763 | /** | |
764 | * Returns the numeric value using the $type to convert to the right data type. | |
765 | * | |
766 | * @param mixed $value The value to be converted | |
767 | * @param int $type The type to convert. Can be TYPE_DOUBLE (float) or TYPE_INT32 (int) | |
768 | * | |
769 | * @return integer|float The converted value | |
770 | */ | |
771 | private function convertValueDataType($value, $type) | |
772 | { | |
773 | if ($type == self::TYPE_DOUBLE) { | |
774 | $value = (float) $value; | |
775 | } elseif ($type == self::TYPE_INT32) { | |
776 | $value = $this->getInt32Value($value); | |
777 | } elseif ($type == self::TYPE_INT64) { | |
778 | $value = $this->getInt64Value($value); | |
779 | } | |
780 | ||
781 | return $value; | |
782 | } | |
783 | ||
784 | /** | |
785 | * Convert the value data type to int or returns false if the value is out of the integer value range. | |
786 | * | |
787 | * @param mixed $value The value to be converted | |
788 | * | |
789 | * @return int The converted value | |
790 | */ | |
791 | private function getInt32Value($value) | |
792 | { | |
793 | if ($value > self::$int32Range['positive'] || $value < self::$int32Range['negative']) { | |
794 | return false; | |
795 | } | |
796 | ||
797 | return (int) $value; | |
798 | } | |
799 | ||
800 | /** | |
801 | * Convert the value data type to int or returns false if the value is out of the integer value range. | |
802 | * | |
803 | * @param mixed $value The value to be converted | |
804 | * | |
805 | * @return int|float The converted value | |
806 | * | |
807 | * @see https://bugs.php.net/bug.php?id=59597 Bug #59597 | |
808 | */ | |
809 | private function getInt64Value($value) | |
810 | { | |
811 | if ($value > self::$int64Range['positive'] || $value < self::$int64Range['negative']) { | |
812 | return false; | |
813 | } | |
814 | ||
815 | if (PHP_INT_SIZE !== 8 && ($value > self::$int32Range['positive'] || $value <= self::$int32Range['negative'])) { | |
816 | // Bug #59597 was fixed on PHP 5.3.14 and 5.4.4 | |
817 | // The negative PHP_INT_MAX was being converted to float | |
818 | if ( | |
819 | $value == self::$int32Range['negative'] && | |
820 | ( | |
821 | (version_compare(PHP_VERSION, '5.4.0', '<') && version_compare(PHP_VERSION, '5.3.14', '>=')) || | |
822 | version_compare(PHP_VERSION, '5.4.4', '>=') | |
823 | ) | |
824 | ) { | |
825 | return (int) $value; | |
826 | } | |
827 | ||
828 | return (float) $value; | |
829 | } | |
830 | ||
831 | if (PHP_INT_SIZE === 8) { | |
832 | // Bug #59597 was fixed on PHP 5.3.14 and 5.4.4 | |
833 | // A 32 bit integer was being generated instead of a 64 bit integer | |
834 | if ( | |
835 | ($value > self::$int32Range['positive'] || $value < self::$int32Range['negative']) && | |
836 | ( | |
837 | (version_compare(PHP_VERSION, '5.3.14', '<')) || | |
838 | (version_compare(PHP_VERSION, '5.4.0', '>=') && version_compare(PHP_VERSION, '5.4.4', '<')) | |
839 | ) | |
840 | ) { | |
841 | $value = (-2147483648 - ($value % -2147483648)) * ($value / abs($value)); | |
842 | } | |
843 | } | |
844 | ||
845 | return (int) $value; | |
846 | } | |
847 | ||
848 | /** | |
849 | * Check if the rounding mode is invalid. | |
850 | * | |
851 | * @param int $value The rounding mode value to check | |
852 | * | |
853 | * @return Boolean true if the rounding mode is invalid, false otherwise | |
854 | */ | |
855 | private function isInvalidRoundingMode($value) | |
856 | { | |
857 | if (in_array($value, self::$roundingModes, true)) { | |
858 | return false; | |
859 | } | |
860 | ||
861 | return true; | |
862 | } | |
863 | ||
864 | /** | |
865 | * Returns the normalized value for the GROUPING_USED attribute. Any value that can be converted to int will be | |
866 | * cast to Boolean and then to int again. This way, negative values are converted to 1 and string values to 0. | |
867 | * | |
868 | * @param mixed $value The value to be normalized | |
869 | * | |
870 | * @return int The normalized value for the attribute (0 or 1) | |
871 | */ | |
872 | private function normalizeGroupingUsedValue($value) | |
873 | { | |
874 | return (int) (Boolean) (int) $value; | |
875 | } | |
876 | ||
877 | /** | |
878 | * Returns the normalized value for the FRACTION_DIGITS attribute. The value is converted to int and if negative, | |
879 | * the returned value will be 0. | |
880 | * | |
881 | * @param mixed $value The value to be normalized | |
882 | * | |
883 | * @return int The normalized value for the attribute | |
884 | */ | |
885 | private function normalizeFractionDigitsValue($value) | |
886 | { | |
887 | $value = (int) $value; | |
888 | ||
889 | return (0 > $value) ? 0 : $value; | |
890 | } | |
891 | } |