* @package Prado\I18N\core */ namespace Prado\I18N\core; /** * Get the NumberFormatInfo class file. */ require_once(dirname(__FILE__).'/NumberFormatInfo.php'); /** * Get the encoding utilities */ require_once(dirname(__FILE__).'/util.php'); /** * NumberFormat class. * * NumberFormat formats decimal numbers in any locale. The decimal * number is formatted according to a particular pattern. These * patterns can arise from the NumberFormatInfo object which is * culturally sensitive. The NumberFormat class can be instantiated in * many ways. E.g. * * * //create a invariant number formatter. * $formatter = new NumberFormat(); * * //create a number format for the french language locale. * $fr = new NumberFormat('fr'); * * //create a number format base on a NumberFormatInfo instance $numberInfo. * $format = new NumberFormat($numberInfo); * * * A normal decimal number can also be displayed as a currency * or as a percentage. For example * * $format->format(1234.5); //Decimal number "1234.5" * $format->format(1234.5,'c'); //Default currency "$1234.50" * $format->format(0.25, 'p') //Percent "25%" * * * Currency is formated using the localized currency pattern. For example * to format the number as Japanese Yen: * * $ja = new NumberFormat('ja_JP'); * * //Japanese currency pattern, and using Japanese Yen symbol * $ja->format(123.14,'c','JPY'); //�?123 (Yen 123) * * For each culture, the symbol for each currency may be different. * * @author Xiang Wei Zhuo * @package Prado\I18N\core */ class NumberFormat { /** * The DateTimeFormatInfo, containing culture specific patterns and names. * @var DateTimeFormatInfo */ protected $formatInfo; /** * Create a new number format instance. The constructor can be instantiated * with a string that represent a culture/locale. Similarly, passing * a CultureInfo or NumberFormatInfo instance will instantiated a instance * for that particular culture. * @param mixed either null, a CultureInfo, a NumberFormatInfo, or string * @return NumberFormat */ function __construct($formatInfo=null) { if($formatInfo === null) $this->formatInfo = NumberFormatInfo::getInvariantInfo(); else if($formatInfo instanceof CultureInfo) $this->formatInfo = $formatInfo->NumberFormat; else if($formatInfo instanceof NumberFormatInfo) $this->formatInfo = $formatInfo; else $this->formatInfo = NumberFormatInfo::getInstance($formatInfo); } /** * For the number for a certain pattern. The valid patterns are * 'c', 'd', 'e', 'p' or a custom pattern, such as "#.000" for * 3 decimal places. * @param mixed the number to format. * @param string the format pattern, either, 'c', 'd', 'e', 'p' * or a custom pattern. E.g. "#.000" will format the number to * 3 decimal places. * @param string 3-letter ISO 4217 code. For example, the code * "USD" represents the US Dollar and "EUR" represents the Euro currency. * @return string formatted number string */ function format($number, $pattern='d', $currency='USD', $charset='UTF-8') { $oldLocale=setLocale(LC_NUMERIC, '0'); setlocale(LC_NUMERIC, 'C'); $this->setPattern($pattern); if(strtolower($pattern) == 'p') $number = $number * 100; $string = (string)$number; $decimal = $this->formatDecimal($string); $integer = $this->formatInteger(abs($number)); if(strlen($decimal)>0) $result = $integer.$decimal; else $result = $integer; //get the suffix if($number >= 0) $suffix = $this->formatInfo->PositivePattern; else if($number < 0) $suffix = $this->formatInfo->NegativePattern; else $suffix = array("",""); //append and prepend suffix $result = $suffix[0].$result.$suffix[1]; //replace currency sign $symbol = @$this->formatInfo->getCurrencySymbol($currency); if($symbol === null) { $symbol = $currency; } $result = str_replace('¤',$symbol, $result); setlocale(LC_NUMERIC, $oldLocale); return I18N_toEncoding($result, $charset); } /** * For the integer, perform groupings and string padding. * @param string the decimal number in string form. * @return string formatted integer string with grouping */ protected function formatInteger($string) { $string = (string)$string; $decimalDigits = $this->formatInfo->DecimalDigits; //if not decimal digits, assume 0 decimal points. if(is_int($decimalDigits) && $decimalDigits > 0) $string = (string)round(floatval($string),$decimalDigits); $dp = strpos($string, '.'); if(is_int($dp)) $string = substr($string, 0, $dp); $integer = ''; $digitSize = $this->formatInfo->getDigitSize(); $string = str_pad($string, $digitSize, '0',STR_PAD_LEFT); $len = strlen($string); $groupSeparator = $this->formatInfo->GroupSeparator; $groupSize = $this->formatInfo->GroupSizes; $firstGroup = true; $multiGroup = is_int($groupSize[1]); $count = 0; if(is_int($groupSize[0])) { //now for the integer groupings for($i=0; $i<$len; $i++) { $char = $string{$len-$i-1}; if($multiGroup && $count == 0) { if($i != 0 && $i%$groupSize[0] == 0) { $integer = $groupSeparator . $integer; $count++; } } else if($multiGroup && $count >= 1) { if($i != 0 && ($i-$groupSize[0])%$groupSize[1] == 0) { $integer = $groupSeparator . $integer; $count++; } } else { if($i != 0 && $i%$groupSize[0] == 0) { $integer = $groupSeparator . $integer; $count++; } } $integer = $char . $integer; } } else $integer = $string; return $integer; } /** * Format the decimal places. * @param string the decimal number in string form. * @return string formatted decimal places. */ protected function formatDecimal($string) { $dp = strpos($string, '.'); $decimal = ''; $decimalDigits = $this->formatInfo->DecimalDigits; $decimalSeparator = $this->formatInfo->DecimalSeparator; //do the correct rounding here //$string = round(floatval($string), $decimalDigits); if(is_int($dp)) { if($decimalDigits == -1) { $decimal = substr($string, $dp+1); } else if(is_int($decimalDigits)) { $float = round((float)$string, $decimalDigits); if(strpos((string)$float, '.') === false) { $decimal = str_pad($decimal,$decimalDigits,'0'); } else { $decimal = substr($float, strpos($float,'.')+1); if(strlen($decimal)<$decimalDigits) $decimal = str_pad($decimal,$decimalDigits,'0'); } } else return $decimal; return $decimalSeparator.$decimal; } else if ($decimalDigits > 0) return $decimalSeparator.str_pad($decimal,$decimalDigits,'0'); return $decimal; } /** * Set the pattern to format against. The default patterns * are retrieved from the NumberFormatInfo instance. * @param string the requested patterns. * @return string a number format pattern. */ protected function setPattern($pattern) { switch($pattern) { case 'c': case 'C': $this->formatInfo->setPattern(NumberFormatInfo::CURRENCY); break; case 'd': case 'D': $this->formatInfo->setPattern(NumberFormatInfo::DECIMAL); break; case 'e': case 'E': $this->formatInfo->setPattern(NumberFormatInfo::SCIENTIFIC); break; case 'p': case 'P': $this->formatInfo->setPattern(NumberFormatInfo::PERCENTAGE); break; default: $this->formatInfo->setPattern($pattern); break; } } }