diff --git a/src/Symfony/Component/Locale/Locale.php b/src/Symfony/Component/Locale/Locale.php index 3b2c4ec83bbcd..a033c21b87590 100644 --- a/src/Symfony/Component/Locale/Locale.php +++ b/src/Symfony/Component/Locale/Locale.php @@ -11,8 +11,23 @@ namespace Symfony\Component\Locale; -class Locale extends \Locale +class Locale { + const DEFAULT_LOCALE = null; + + /** Locale method constants */ + const ACTUAL_LOCALE = 0; + const VALID_LOCALE = 1; + + /** Language tags constants */ + const LANG_TAG = 'language'; + const EXTLANG_TAG = 'extlang'; + const SCRIPT_TAG = 'script'; + const REGION_TAG = 'region'; + const VARIANT_TAG = 'variant'; + const GRANDFATHERED_LANG_TAG = 'grandfathered'; + const PRIVATE_TAG = 'private'; + /** * Caches the countries in different locales * @var array @@ -164,4 +179,261 @@ public static function getLocales() { return array_keys(self::getDisplayLocales(self::getDefault())); } -} \ No newline at end of file + + /** + * Returns the best available locale based on HTTP "Accept-Language" header according to RFC 2616 + * + * @param string $header The string containing the "Accept-Language" header value + * @return string The corresponding locale code + * @see http://www.php.net/manual/en/locale.acceptfromhttp.php + * @throws RuntimeException When the intl extension is not loaded + */ + public static function acceptFromHttp($header) + { + self::assertIntlExtensionAvailability(); + return \Locale::acceptFromHttp($header); + } + + /** + * Returns a correctly ordered and delimited locale code + * + * @param array $subtags A keyed array where the keys identify the particular locale code subtag + * @return string The corresponding locale code + * @see http://www.php.net/manual/en/locale.composelocale.php + * @throws RuntimeException When the intl extension is not loaded + */ + public static function composeLocale(array $subtags) + { + self::assertIntlExtensionAvailability(); + return \Locale::composeLocale($subtags); + } + + /** + * Checks if a language tag filter matches with locale + * + * @param string $langtag The language tag to check + * @param string $locale The language range to check against + * @return string The corresponding locale code + * @see http://www.php.net/manual/en/locale.filtermatches.php + * @throws RuntimeException When the intl extension is not loaded + */ + public static function filterMatches($langtag, $locale, $canonicalize = false) + { + self::assertIntlExtensionAvailability(); + return \Locale::filterMatches($langtag, $locale, $canonicalize); + } + + /** + * Returns the variants for the input locale + * + * @param string $locale The locale to extract the variants from + * @return array The locale variants + * @see http://www.php.net/manual/en/locale.getallvariants.php + * @throws RuntimeException When the intl extension is not loaded + */ + public static function getAllVariants($locale) + { + self::assertIntlExtensionAvailability(); + return \Locale::getAllVariants($locale); + } + + /** + * Returns the default locale + * + * @return string The default locale code + * @see http://www.php.net/manual/en/locale.getdefault.php + * @throws RuntimeException When the intl extension is not loaded + */ + public static function getDefault() + { + self::assertIntlExtensionAvailability(); + return \Locale::getDefault(); + } + + /** + * Returns the localized display name for the locale language + * + * @param string $locale The locale code to return the display language from + * @param string $inLocale Optional format locale code to use to display the language name + * @return string The localized language display name + * @see http://www.php.net/manual/en/locale.getdisplaylanguage.php + * @throws RuntimeException When the intl extension is not loaded + */ + public static function getDisplayLanguage($locale, $inLocale = null) + { + self::assertIntlExtensionAvailability(); + return \Locale::getDisplayLanguage($locale, $inLocale); + } + + /** + * Returns the localized display name for the locale + * + * @param string $locale The locale code to return the display locale name from + * @param string $inLocale Optional format locale code to use to display the locale name + * @return string The localized locale display name + * @see http://www.php.net/manual/en/locale.getdisplayname.php + * @throws RuntimeException When the intl extension is not loaded + */ + public static function getDisplayName($locale, $inLocale = null) + { + self::assertIntlExtensionAvailability(); + return \Locale::getDisplayName($locale, $inLocale); + } + + /** + * Returns the localized display name for the locale region + * + * @param string $locale The locale code to return the display region from + * @param string $inLocale Optional format locale code to use to display the region name + * @return string The localized region display name + * @see http://www.php.net/manual/en/locale.getdisplayregion.php + * @throws RuntimeException When the intl extension is not loaded + */ + public static function getDisplayRegion($locale, $inLocale = null) + { + self::assertIntlExtensionAvailability(); + return \Locale::getDisplayRegion($locale, $inLocale); + } + + /** + * Returns the localized display name for the locale script + * + * @param string $locale The locale code to return the display scrit from + * @param string $inLocale Optional format locale code to use to display the script name + * @return string The localized script display name + * @see http://www.php.net/manual/en/locale.getdisplayscript.php + * @throws RuntimeException When the intl extension is not loaded + */ + public static function getDisplayScript($locale, $inLocale = null) + { + self::assertIntlExtensionAvailability(); + return \Locale::getDisplayScript($locale, $inLocale); + } + + /** + * Returns the localized display name for the locale variant + * + * @param string $locale The locale code to return the display variant from + * @param string $inLocale Optional format locale code to use to display the variant name + * @return string The localized variant display name + * @see http://www.php.net/manual/en/locale.getdisplayvariant.php + * @throws RuntimeException When the intl extension is not loaded + */ + public static function getDisplayVariant($locale, $inLocale = null) + { + self::assertIntlExtensionAvailability(); + return \Locale::getDisplayVariant($locale, $inLocale); + } + + /** + * Returns the keywords for the locale + * + * @param string $locale The locale code to extract the keywords from + * @return array Associative array with the extracted variants + * @see http://www.php.net/manual/en/locale.getkeywords.php + * @throws RuntimeException When the intl extension is not loaded + */ + public static function getKeywords($locale) + { + self::assertIntlExtensionAvailability(); + return \Locale::getKeywords($locale); + } + + /** + * Returns the primary language for the locale + * + * @param string $locale The locale code to extract the language code from + * @return string|null The extracted language code or null in case of error + * @see http://www.php.net/manual/en/locale.getprimarylanguage.php + * @throws RuntimeException When the intl extension is not loaded + */ + public static function getPrimaryLanguage($locale) + { + self::assertIntlExtensionAvailability(); + return \Locale::getPrimaryLanguage($locale); + } + + /** + * Returns the region for the locale + * + * @param string $locale The locale code to extract the region code from + * @return string|null The extracted region code or null if not present + * @see http://www.php.net/manual/en/locale.getregion.php + * @throws RuntimeException When the intl extension is not loaded + */ + public static function getRegion($locale) + { + self::assertIntlExtensionAvailability(); + return \Locale::getRegion($locale); + } + + /** + * Returns the script for the locale + * + * @param string $locale The locale code to extract the script code from + * @return string|null The extracted script code or null if not present + * @see http://www.php.net/manual/en/locale.getscript.php + * @throws RuntimeException When the intl extension is not loaded + */ + public static function getScript($locale) + { + self::assertIntlExtensionAvailability(); + return \Locale::getScript($locale); + } + + /** + * Returns the closest language tag for the locale + * + * @param array $langtag A list of the language tags to compare to locale + * @param string $locale The locale to use as the language range when matching + * @param bool $canonicalize If true, the arguments will be converted to canonical form before matching + * @param string $default The locale to use if no match is found + * @see http://www.php.net/manual/en/locale.lookup.php + * @throws RuntimeException When the intl extension is not loaded + */ + public static function lookup(array $langtag, $locale, $canonicalize = false, $default = null) + { + self::assertIntlExtensionAvailability(); + return \Locale::lookup($langtag, $locale, $canonicalize, $default); + } + + /** + * Returns an associative array of locale identifier subtags + * + * @param string $locale The locale code to extract the subtag array from + * @return array Associative arrat with the extracted subtags + * @see http://www.php.net/manual/en/locale.parselocale.php + * @throws RuntimeException When the intl extension is not loaded + */ + public static function parseLocale($locale) + { + self::assertIntlExtensionAvailability(); + return \Locale::parseLocale($locale); + } + + /** + * Sets the default runtime locale + * + * @param string $locale The locale code + * @return bool true on success or false on failure + * @see http://www.php.net/manual/en/locale.parselocale.php + * @throws RuntimeException When the intl extension is not loaded + */ + public static function setDefault($locale) + { + self::assertIntlExtensionAvailability(); + return \Locale::setDefault($locale); + } + + /** + * Check if the intl extension is loaded. + * + * @throws RuntimeException When the intl extension is not loaded + */ + private static function assertIntlExtensionAvailability() + { + if (!extension_loaded('intl')) { + throw new \RuntimeException('The intl extension is not available.'); + } + } +} diff --git a/src/Symfony/Component/Locale/Stub/StubIntlDateFormatter.php b/src/Symfony/Component/Locale/Stub/StubIntlDateFormatter.php new file mode 100644 index 0000000000000..ced9126847231 --- /dev/null +++ b/src/Symfony/Component/Locale/Stub/StubIntlDateFormatter.php @@ -0,0 +1,178 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Locale\Stub; + +use Symfony\Component\Locale\Locale; + +/** + * Provides a stub IntlDateFormatter for the 'en' locale. + */ +class StubIntlDateFormatter +{ + /* formats */ + const NONE = -1; + const FULL = 0; + const LONG = 1; + const MEDIUM = 2; + const SHORT = 3; + + /* formats */ + const TRADITIONAL = 0; + const GREGORIAN = 1; + + public function __construct($locale, $datetype, $timetype, $timezone = null, $calendar = null, $pattern = null) + { + if ('en' != $locale) { + throw new \InvalidArgumentException('Unsupported $locale value. Only the \'en\' locale is supported. Install the intl extension for full localization capabilities.'); + } + + $this->setPattern($pattern); + } + + public function format($timestamp) + { + $specialChars = 'MLydGQqhDEaH'; + $specialCharsArray = str_split($specialChars); + $specialCharsMatch = implode('|', array_map(function($char) { + return $char . '+'; + }, $specialCharsArray)); + $regExp = "/('($specialCharsMatch|[^$specialChars])|$specialCharsMatch)/"; + + $callback = function($matches) use ($timestamp) { + $pattern = $matches[0]; + $length = strlen($pattern); + + if ("'" === $pattern[0]) { + return substr($pattern, 1); + } + + switch ($pattern[0]) { + case 'M': + case 'L': + $matchLengthMap = array( + 1 => 'n', + 2 => 'm', + 3 => 'M', + 4 => 'F', + ); + + if (isset($matchLengthMap[$length])) { + return gmdate($matchLengthMap[$length], $timestamp); + } else if (5 == $length) { + return substr(gmdate('M', $timestamp), 0, 1); + } else { + return str_pad(gmdate('m', $timestamp), $length, '0', STR_PAD_LEFT); + } + break; + + case 'y': + $matchLengthMap = array( + 1 => 'Y', + 2 => 'y', + 3 => 'Y', + 4 => 'Y', + ); + + if (isset($matchLengthMap[$length])) { + return gmdate($matchLengthMap[$length], $timestamp); + } else { + return str_pad(gmdate('Y', $timestamp), $length, '0', STR_PAD_LEFT); + } + break; + + case 'd': + return str_pad(gmdate('j', $timestamp), $length, '0', STR_PAD_LEFT); + break; + + case 'G': + $year = (int) gmdate('Y', $timestamp); + return $year >= 0 ? 'AD' : 'BC'; + break; + + case 'q': + case 'Q': + $month = (int) gmdate('n', $timestamp); + $quarter = (int) floor(($month - 1) / 3) + 1; + switch ($length) { + case 1: + case 2: + return str_pad($quarter, $length, '0', STR_PAD_LEFT); + break; + case 3: + return 'Q' . $quarter; + break; + default: + $map = array(1 => '1st quarter', 2 => '2nd quarter', 3 => '3rd quarter', 4 => '4th quarter'); + return $map[$quarter]; + break; + } + break; + + case 'h': + return str_pad(gmdate('g', $timestamp), $length, '0', STR_PAD_LEFT); + break; + + case 'D': + $dayOfYear = gmdate('z', $timestamp) + 1; + return str_pad($dayOfYear, $length, '0', STR_PAD_LEFT); + break; + + case 'E': + $dayOfWeek = gmdate('l', $timestamp); + switch ($length) { + case 4: + return $dayOfWeek; + break; + case 5: + return $dayOfWeek[0]; + break; + default: + return substr($dayOfWeek, 0, 3); + } + break; + + case 'a': + return gmdate('A', $timestamp); + break; + + case 'H': + return str_pad(gmdate('G', $timestamp), $length, '0', STR_PAD_LEFT); + break; + } + }; + + $formatted = preg_replace_callback($regExp, $callback, $this->getPattern()); + + return $formatted; + } + + public function getPattern() + { + return $this->pattern; + } + + public function getCalendar() + { + $this->throwMethodNotImplementException(__METHOD__); + } + + public function setPattern($pattern) + { + $this->pattern = $pattern; + } + + private function throwMethodNotImplementException($methodName) + { + $message = sprintf('The %s::%s() is not implemented. Install the intl extension for full localization capabilities.', __CLASS__, $methodName); + throw new \RuntimeException($message); + } +} diff --git a/src/Symfony/Component/Locale/Stub/StubNumberFormatter.php b/src/Symfony/Component/Locale/Stub/StubNumberFormatter.php new file mode 100644 index 0000000000000..9fd297d3e08a9 --- /dev/null +++ b/src/Symfony/Component/Locale/Stub/StubNumberFormatter.php @@ -0,0 +1,540 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Locale\Stub; + +use Symfony\Component\Locale\Locale; + +/** + * Provides a stub NumberFormatter for the 'en' locale. + */ +class StubNumberFormatter +{ + const U_ZERO_ERROR = 0; + const U_ZERO_ERROR_MESSAGE = 'U_ZERO_ERROR'; + + /** Format style constants */ + const PATTERN_DECIMAL = 0; + const DECIMAL = 1; + const CURRENCY = 2; + const PERCENT = 3; + const SCIENTIFIC = 4; + const SPELLOUT = 5; + const ORDINAL = 6; + const DURATION = 7; + const PATTERN_RULEBASED = 9; + const IGNORE = 0; + const DEFAULT_STYLE = 1; + + /** Format type constants */ + const TYPE_DEFAULT = 0; + const TYPE_INT32 = 1; + const TYPE_INT64 = 2; + const TYPE_DOUBLE = 3; + const TYPE_CURRENCY = 4; + + /** Numeric attribute constants */ + const PARSE_INT_ONLY = 0; + const GROUPING_USED = 1; + const DECIMAL_ALWAYS_SHOWN = 2; + const MAX_INTEGER_DIGITS = 3; + const MIN_INTEGER_DIGITS = 4; + const INTEGER_DIGITS = 5; + const MAX_FRACTION_DIGITS = 6; + const MIN_FRACTION_DIGITS = 7; + const FRACTION_DIGITS = 8; + const MULTIPLIER = 9; + const GROUPING_SIZE = 10; + const ROUNDING_MODE = 11; + const ROUNDING_INCREMENT = 12; + const FORMAT_WIDTH = 13; + const PADDING_POSITION = 14; + const SECONDARY_GROUPING_SIZE = 15; + const SIGNIFICANT_DIGITS_USED = 16; + const MIN_SIGNIFICANT_DIGITS = 17; + const MAX_SIGNIFICANT_DIGITS = 18; + const LENIENT_PARSE = 19; + + /** Text attribute constants */ + const POSITIVE_PREFIX = 0; + const POSITIVE_SUFFIX = 1; + const NEGATIVE_PREFIX = 2; + const NEGATIVE_SUFFIX = 3; + const PADDING_CHARACTER = 4; + const CURRENCY_CODE = 5; + const DEFAULT_RULESET = 6; + const PUBLIC_RULESETS = 7; + + /** Format symbol constants */ + const DECIMAL_SEPARATOR_SYMBOL = 0; + const GROUPING_SEPARATOR_SYMBOL = 1; + const PATTERN_SEPARATOR_SYMBOL = 2; + const PERCENT_SYMBOL = 3; + const ZERO_DIGIT_SYMBOL = 4; + const DIGIT_SYMBOL = 5; + const MINUS_SIGN_SYMBOL = 6; + const PLUS_SIGN_SYMBOL = 7; + const CURRENCY_SYMBOL = 8; + const INTL_CURRENCY_SYMBOL = 9; + const MONETARY_SEPARATOR_SYMBOL = 10; + const EXPONENTIAL_SYMBOL = 11; + const PERMILL_SYMBOL = 12; + const PAD_ESCAPE_SYMBOL = 13; + const INFINITY_SYMBOL = 14; + const NAN_SYMBOL = 15; + const SIGNIFICANT_DIGIT_SYMBOL = 16; + const MONETARY_GROUPING_SEPARATOR_SYMBOL = 17; + + /** Rounding mode values used by NumberFormatter::setAttribute() with NumberFormatter::ROUNDING_MODE attribute */ + const ROUND_CEILING = 0; + const ROUND_FLOOR = 1; + const ROUND_DOWN = 2; + const ROUND_UP = 3; + const ROUND_HALFEVEN = 4; + const ROUND_HALFDOWN = 5; + const ROUND_HALFUP = 6; + + /** Pad position values used by NumberFormatter::setAttribute() with NumberFormatter::PADDING_POSITION attribute */ + const PAD_BEFORE_PREFIX = 0; + const PAD_AFTER_PREFIX = 1; + const PAD_BEFORE_SUFFIX = 2; + const PAD_AFTER_SUFFIX = 3; + + /** + * Default values for the en locale. + */ + private $attributes = array( + self::FRACTION_DIGITS => 0, + self::GROUPING_USED => 1, + self::ROUNDING_MODE => self::ROUND_HALFEVEN + ); + + /** + * The supported styles to the constructor $styles argument. + */ + private static $supportedStyles = array( + 'CURRENCY' => self::CURRENCY, + 'DECIMAL' => self::DECIMAL + ); + + /** + * Supported attributes to the setAttribute() $attr argument. + */ + private static $supportedAttributes = array( + 'FRACTION_DIGITS' => self::FRACTION_DIGITS, + 'GROUPING_USED' => self::GROUPING_USED, + 'ROUNDING_MODE' => self::ROUNDING_MODE + ); + + /** + * The available rounding modes for setAttribute() usage with + * SimpleNumberFormatter::ROUNDING_MODE. SimpleNumberFormatter::ROUND_DOWN + * and SimpleNumberFormatter::ROUND_UP does not have a PHP only equivalent. + */ + private static $roundingModes = array( + 'ROUND_HALFEVEN' => self::ROUND_HALFEVEN, + 'ROUND_HALFDOWN' => self::ROUND_HALFDOWN, + 'ROUND_HALFUP' => self::ROUND_HALFUP + ); + + /** + * The available values for setAttribute() usage with + * SimpleNumberFormatter::GROUPING_USED. + */ + private static $groupingUsedValues = array(0, 1); + + /** + * The mapping between \NumberFormatter rounding modes to the available + * modes in PHP's round() function. + * + * @see http://www.php.net/manual/en/function.round.php + */ + private static $phpRoundingMap = array( + self::ROUND_HALFDOWN => \PHP_ROUND_HALF_DOWN, + self::ROUND_HALFEVEN => \PHP_ROUND_HALF_EVEN, + self::ROUND_HALFUP => \PHP_ROUND_HALF_UP + ); + + /** + * The currencies symbols. Each array have the symbol definition in + * hexadecimal and the decimal digits. + * + * @see http://source.icu-project.org/repos/icu/icu/trunk/source/data/curr/en.txt + * @see SimpleNumberFormatter::getCurrencySymbol() + * @see SimpleNumberFormatter::formatCurrency() + * @todo Move this to Resources/data and use \ResourceBundle to load the data. + * @todo Search in the icu data where the currency subunits (usage of cents) are defined + */ + private $currencies = array( + 'ALL' => array('0x410x4c0x4c', 0), + 'BRL' => array('0x520x24', 2), + 'CRC' => array('0xe20x820xa1', 0) + ); + + /** + * The maximum values of the integer type in 32 and 64 bit platforms. + */ + private static $intValues = array( + self::TYPE_INT32 => array( + 'positive' => 2147483647, + 'negative' => -2147483648 + ), + self::TYPE_INT64 => array( + 'positive' => 9223372036854775807, + 'negative' => -9223372036854775808 + ) + ); + + /** + * @{inheritDoc} + */ + public function __construct($locale = 'en', $style = null, $pattern = null) + { + if ('en' != $locale) { + throw new \InvalidArgumentException('Unsupported $locale value. Only the \'en\' locale is supported. Install the intl extension for full localization capabilities.'); + } + + if (!in_array($style, self::$supportedStyles)) { + throw new \InvalidArgumentException(sprintf( + 'Unsupported $style value. The available styles are: %s. Install the intl extension for full localization capabilities.', + implode(', ', array_keys(self::$supportedStyles)) + )); + } + + if (!is_null($pattern)) { + throw new \InvalidArgumentException('The $pattern value must be null. Install the intl extension for full localization capabilities.'); + } + } + + /** + * @{inheritDoc} + */ + public function formatCurrency($value, $currency) + { + $symbol = $this->getCurrencySymbol($currency); + $value = $this->round($value, $this->currencies[$currency][1]); + + $negative = false; + if (0 > $value) { + $negative = true; + $value *= -1; + } + + $value = $this->formatNumber($value, $this->currencies[$currency][1]); + + $ret = $symbol.$value; + return $negative ? '('.$ret.')' : $ret; + } + + /** + * @{inheritDoc} + */ + public function format($value, $type = self::TYPE_DEFAULT) + { + if (0 > ($fractionDigits = $this->getAttribute(self::FRACTION_DIGITS))) { + $fractionDigits = 0; + } + + $value = $this->round($value, $fractionDigits); + return $this->formatNumber($value, $fractionDigits); + } + + /** + * @{inheritDoc} + */ + public function getAttribute($attr) + { + if (isset($this->attributes[$attr])) { + return $this->attributes[$attr]; + } + } + + /** + * @{inheritDoc} + */ + public function getErrorCode() + { + return self::U_ZERO_ERROR; + } + + /** + * @{inheritDoc} + */ + public function getErrorMessage() + { + return self::U_ZERO_ERROR_MESSAGE; + } + + /** + * @{inheritDoc} + */ + public function getLocale($type = Locale::ACTUAL_LOCALE) + { + $this->throwMethodNotImplementException(__METHOD__); + } + + /** + * @{inheritDoc} + */ + public function getPattern() + { + $this->throwMethodNotImplementException(__METHOD__); + } + + /** + * @{inheritDoc} + */ + public function getSymbol($attr) + { + $this->throwMethodNotImplementException(__METHOD__); + } + + /** + * @{inheritDoc} + */ + public function getTextAttribute($attr) + { + $this->throwMethodNotImplementException(__METHOD__); + } + + /** + * @{inheritDoc} + */ + public function parseCurrency($value, &$currency, &$position = null) + { + $this->throwMethodNotImplementException(__METHOD__); + } + + /** + * @{inheritDoc} + */ + public function parse($value, $type = self::TYPE_DOUBLE, &$position = null) + { + if (!is_null($position)) { + throw new \RuntimeException('$position should be null. Processing based on the $position value is not supported. Install the intl extension for full localization capabilities.'); + } + + preg_match('/^([^0-9\-]{0,})(.*)/', $value, $matches); + + // Any string before the numeric value causes error in the parsing + if (isset($matches[1]) && !empty($matches[1])) { + return false; + } + + // Remove everything that is not number or dot (.) + $value = preg_replace('/[^0-9\.\-]/', '', $value); + + return $this->getNumericValue($value, $type); + } + + /** + * @{inheritDoc} + * @todo Decide between throwing an exception if ROUDING_MODE or GROUPING_USED are invalid. + * In \NumberFormatter, a true is returned and the format/parse() methods have undefined values + * in these cases. + * @throws InvalidArgumentException When the $attr is not supported + */ + public function setAttribute($attr, $value) + { + if (!in_array($attr, self::$supportedAttributes)) { + throw new \InvalidArgumentException(sprintf( + 'Unsupported $attr value. The available attributes are: %s. Install the intl extension for full localization capabilities.', + implode(', ', array_keys(self::$supportedAttributes)) + )); + } + + if (self::$supportedAttributes['ROUNDING_MODE'] == $attr && $this->isInvalidRoundingMode($value)) { + return false; + } + + if (self::$supportedAttributes['GROUPING_USED'] == $attr && $this->isInvalidGroupingUsedValue($value)) { + return false; + } + + $this->attributes[$attr] = $value; + return true; + } + + /** + * @{inheritDoc} + */ + public function setPattern($pattern) + { + $this->throwMethodNotImplementException(__METHOD__); + } + + /** + * @{inheritDoc} + */ + public function setSymbol($attr, $value) + { + $this->throwMethodNotImplementException(__METHOD__); + } + + /** + * @{inheritDoc} + */ + public function setTextAttribute($attr, $value) + { + $this->throwMethodNotImplementException(__METHOD__); + } + + /** + * Returns the currency symbol. + * + * @param string $currency The 3-letter ISO 4217 currency code indicating the currency to use + * @return string The currency symbol + */ + private function getCurrencySymbol($currency) + { + $symbol = ''; + $hexSymbol = $this->currencies[$currency][0]; + $hex = explode('0x', $hexSymbol); + unset($hex[0]); + + foreach ($hex as $h) { + $symbol .= chr(hexdec($h)); + } + + return $symbol; + } + + /** + * Rounds a value. + * + * @param numeric $value The value to round + * @param int $precision The number of decimal digits to round to + * @return numeric The rounded value + */ + private function round($value, $precision) + { + $roundingMode = self::$phpRoundingMap[$this->getAttribute(self::ROUNDING_MODE)]; + $value = round($value, $precision, $roundingMode); + + return $value; + } + + /** + * Formats a number. + * + * @param numeric $value The numeric value to format + * @param int $precision The number of decimal digits to use + * @return string The formatted number + */ + private function formatNumber($value, $precision) + { + return number_format($value, $precision, '.', $this->getAttribute(self::GROUPING_USED) ? ',' : ''); + } + + /** + * Check if the rounding mode is invalid. + * + * @param int $value The rounding mode value to check + * @return bool true if the rounding mode is invalid, false otherwise + */ + private function isInvalidRoundingMode($value) + { + if (in_array($value, self::$roundingModes, true)) { + return false; + } + + return true; + } + + /** + * Check if the grouping value is invalid. + * + * @param int $value The grouping value to check + * @return bool true if the grouping value is invalid, false otherwise + */ + private function isInvalidGroupingUsedValue($value) + { + if (in_array($value, self::$groupingUsedValues, true)) { + return false; + } + + return true; + } + + private function getNumericValue($value, $type) + { + $type = $this->detectNumberType($value, $type); + + if ($type == self::TYPE_DOUBLE) { + $value = (float) $value; + } + elseif ($type == self::TYPE_INT32) { + $value = $this->getIntValue($type, $value); + } + elseif ($type == self::TYPE_INT64) { + $value = $this->getIntValue($type, $value); + } + + return $value; + } + + private function detectNumberType($value, $type) + { + if ($type != self::TYPE_DEFAULT) { + return $type; + } + + if ($this->isFloat($value)) { + $type = self::TYPE_DOUBLE; + } + elseif ($this->isInt32($value)) { + $type = self::TYPE_INT32; + } + elseif ($this->isInt64($value)) { + $type = self::TYPE_INT64; + } + + return $type; + } + + private function isFloat($value) + { + return strstr($value, '.') || is_float($value + 0) || is_float($value - 0); + } + + private function isInt32($value) + { + return $this->isIntType(self::TYPE_INT32, $value); + } + + private function isInt64($value) + { + return $this->isIntType(self::TYPE_INT64, $value); + } + + private function isIntType($type, $value) + { + return $value <= self::$intValues[$type]['positive'] && $value >= self::$intValues[$type]['negative']; + } + + private function getIntValue($int, $value) + { + if ($value > self::$intValues[$int]['positive']) { + $value = self::$intValues[$int]['positive']; + } + elseif ($value < self::$intValues[$int]['negative']) { + $value = self::$intValues[$int]['negative']; + } + + return (int) $value; + } + + private function throwMethodNotImplementException($methodName) + { + $message = sprintf('The %s::%s() is not implemented. Install the intl extension for full localization capabilities.', __CLASS__, $methodName); + throw new \RuntimeException($message); + } +} diff --git a/tests/Symfony/Tests/Component/Locale/LocaleTest.php b/tests/Symfony/Tests/Component/Locale/LocaleTest.php new file mode 100644 index 0000000000000..c292bf2e38483 --- /dev/null +++ b/tests/Symfony/Tests/Component/Locale/LocaleTest.php @@ -0,0 +1,126 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LocaleTest extends \PHPUnit_Framework_TestCase +{ + public function setUp() + { + if (!extension_loaded('intl')) { + $this->markTestSkipped('The intl extension is not available.'); + } + } + + public function testAcceptFromHttp() + { + $this->assertEquals('pt_BR', Locale::acceptFromHttp('pt-br,en-us;q=0.7,en;q=0.5')); + } + + public function testComposeLocale() + { + $subtags = array( + 'language' => 'pt', + 'script' => 'Latn', + 'region' => 'BR' + ); + $this->assertEquals('pt_Latn_BR', Locale::composeLocale($subtags)); + } + + public function testFilterMatches() + { + $this->assertTrue(Locale::filterMatches('pt-BR', 'pt-BR')); + } + + public function testGetAllVariants() + { + $this->assertEquals(array('LATN'), Locale::getAllVariants('pt_BR_Latn')); + } + + /** + * @covers Symfony\Component\Locale\Locale::getDefault + * @covers Symfony\Component\Locale\Locale::setDefault + */ + public function testGetDefault() + { + Locale::setDefault('en_US'); + $this->assertEquals('en_US', Locale::getDefault()); + } + + public function testGetDisplayLanguage() + { + $this->assertEquals('Portuguese', Locale::getDisplayLanguage('pt-Latn-BR', 'en')); + } + + public function testGetDisplayName() + { + $this->assertEquals('Portuguese (Latin, Brazil)', Locale::getDisplayName('pt-Latn-BR', 'en')); + } + + public function testGetDisplayRegion() + { + $this->assertEquals('Brazil', Locale::getDisplayRegion('pt-Latn-BR', 'en')); + } + + public function testGetDisplayScript() + { + $this->assertEquals('Latin', Locale::getDisplayScript('pt-Latn-BR', 'en')); + } + + public function testGetDisplayVariant() + { + $this->assertEmpty(Locale::getDisplayVariant('pt-Latn-BR', 'en')); + } + + public function testGetKeywords() + { + $this->assertEquals( + array('currency' => 'BRL'), + Locale::getKeywords('pt-BR@currency=BRL') + ); + } + + public function testGetPrimaryLanguage() + { + $this->assertEquals('pt', Locale::getPrimaryLanguage('pt-Latn-BR')); + } + + public function testGetRegion() + { + $this->assertEquals('BR', Locale::getRegion('pt-Latn-BR')); + } + + public function testGetScript() + { + $this->assertEquals('Latn', Locale::getScript('pt-Latn-BR')); + } + + public function testLookup() + { + $langtag = array( + 'pt-Latn-BR', + 'pt-BR' + ); + $this->assertEquals('pt-BR', Locale::lookup($langtag, 'pt-BR-x-priv1')); + } + + public function testParseLocale() + { + $expected = array( + 'language' => 'pt', + 'script' => 'Latn', + 'region' => 'BR' + ); + $this->assertEquals($expected, Locale::parseLocale('pt-Latn-BR')); + } +} diff --git a/tests/Symfony/Tests/Component/Locale/Stub/Learning/NumberFormatterTest.php b/tests/Symfony/Tests/Component/Locale/Stub/Learning/NumberFormatterTest.php new file mode 100644 index 0000000000000..d0dfe40d99476 --- /dev/null +++ b/tests/Symfony/Tests/Component/Locale/Stub/Learning/NumberFormatterTest.php @@ -0,0 +1,307 @@ +formatter = $this->getDecimalFormatter(); + } + + private function getDecimalFormatter() + { + return new \NumberFormatter('en', \NumberFormatter::DECIMAL); + } + + private function getCurrencyFormatter() + { + $formatter = new \NumberFormatter('en', \NumberFormatter::CURRENCY); + $formatter->setSymbol(\NumberFormatter::CURRENCY_SYMBOL, 'SFD'); + return $formatter; + } + + /** + * @dataProvider formatCurrencyProvider + */ + public function testFormatCurrency(\NumberFormatter $formatter, $value, $currency, $expectedValue) + { + $formattedCurrency = $formatter->formatCurrency($value, $currency); + $this->assertEquals($expectedValue, $formattedCurrency); + } + + public function formatCurrencyProvider() + { + $df = $this->getDecimalFormatter(); + $cf = $this->getCurrencyFormatter(); + + return array( + array($df, 100, 'BRL', '100'), + array($df, 100.1, 'BRL', '100.1'), + array($cf, 100, 'BRL', 'R$100.00'), + array($cf, 100.1, 'BRL', 'R$100.10') + ); + } + + /** + * @dataProvider formatProvider + */ + public function testFormat($formatter, $value, $expectedValue) + { + $formattedValue = $formatter->format($value); + $this->assertEquals($expectedValue, $formattedValue); + } + + public function formatProvider() + { + $df = $this->getDecimalFormatter(); + $cf = $this->getCurrencyFormatter(); + + return array( + array($df, 1, '1'), + array($df, 1.1, '1.1'), + array($cf, 1, 'SFD1.00'), + array($cf, 1.1, 'SFD1.10') + ); + } + + /** + * @dataProvider formatTypeDefaultProvider + */ + public function testFormatTypeDefault($formatter, $value, $expectedValue) + { + $formattedValue = $formatter->format($value, \NumberFormatter::TYPE_DEFAULT); + $this->assertEquals($expectedValue, $formattedValue); + } + + public function formatTypeDefaultProvider() + { + $df = $this->getDecimalFormatter(); + $cf = $this->getCurrencyFormatter(); + + return array( + array($df, 1, '1'), + array($df, 1.1, '1.1'), + array($cf, 1, 'SFD1.00'), + array($cf, 1.1, 'SFD1.10') + ); + } + + /** + * @dataProvider formatTypeInt32Provider + */ + public function testFormatTypeInt32($formatter, $value, $expectedValue, $message = '') + { + $formattedValue = $formatter->format($value, \NumberFormatter::TYPE_INT32); + $this->assertEquals($expectedValue, $formattedValue, $message); + } + + public function formatTypeInt32Provider() + { + $df = $this->getDecimalFormatter(); + $cf = $this->getCurrencyFormatter(); + + $message = '->format() TYPE_INT32 formats incosistencily an integer if out of 32 bit range.'; + + return array( + array($df, 1, '1'), + array($df, 1.1, '1'), + array($df, 2147483648, '-2,147,483,648', $message), + array($df, -2147483649, '2,147,483,647', $message), + array($cf, 1, 'SFD1.00'), + array($cf, 1.1, 'SFD1.00'), + array($cf, 2147483648, '(SFD2,147,483,648.00)', $message), + array($cf, -2147483649, 'SFD2,147,483,647.00', $message) + ); + } + + /** + * The parse() method works differently with integer out of the 32 bit range. format() works fine. + * @dataProvider formatTypeInt64Provider + */ + public function testFormatTypeInt64($formatter, $value, $expectedValue) + { + $formattedValue = $formatter->format($value, \NumberFormatter::TYPE_INT64); + $this->assertEquals($expectedValue, $formattedValue); + } + + public function formatTypeInt64Provider() + { + $df = $this->getDecimalFormatter(); + $cf = $this->getCurrencyFormatter(); + + return array( + array($df, 1, '1'), + array($df, 1.1, '1'), + array($df, 2147483648, '2,147,483,648'), + array($df, -2147483649, '-2,147,483,649'), + array($cf, 1, 'SFD1.00'), + array($cf, 1.1, 'SFD1.00'), + array($cf, 2147483648, 'SFD2,147,483,648.00'), + array($cf, -2147483649, '(SFD2,147,483,649.00)') + ); + } + + /** + * @dataProvider formatTypeDoubleProvider + */ + public function testFormatTypeDouble($formatter, $value, $expectedValue) + { + $formattedValue = $formatter->format($value, \NumberFormatter::TYPE_DOUBLE); + $this->assertEquals($expectedValue, $formattedValue); + } + + public function formatTypeDoubleProvider() + { + $df = $this->getDecimalFormatter(); + $cf = $this->getCurrencyFormatter(); + + return array( + array($df, 1, '1'), + array($df, 1.1, '1.1'), + array($cf, 1, 'SFD1.00'), + array($cf, 1.1, 'SFD1.10'), + ); + } + + /** + * @dataProvider formatTypeCurrencyProvider + * @expectedException PHPUnit_Framework_Error_Warning + */ + public function testFormatTypeCurrency($formatter, $value) + { + $formattedValue = $formatter->format($value, \NumberFormatter::TYPE_CURRENCY); + } + + public function formatTypeCurrencyProvider() + { + $df = $this->getDecimalFormatter(); + $cf = $this->getCurrencyFormatter(); + + return array( + array($df, 1), + array($df, 1), + ); + } + + /** + * @dataProvider parseCurrencyProvider + */ + public function testParseCurrency($formatter, $value, $expectedValue, $expectedCurrency) + { + $currency = ''; + $parsedValue = $formatter->parseCurrency($value, $currency); + $this->assertEquals($expectedValue, $parsedValue); + $this->assertEquals($expectedCurrency, $currency); + } + + public function parseCurrencyProvider() + { + $df = $this->getDecimalFormatter(); + $cf = $this->getCurrencyFormatter(); + + return array( + array($df, 1, 1, ''), + array($df, 1.1, 1.1, ''), + array($cf, '$1.00', 1, 'USD'), + array($cf, '€1.00', 1, 'EUR'), + array($cf, 'R$1.00', 1, 'BRL') + ); + } + + public function testParse() + { + $parsedValue = $this->formatter->parse('1'); + $this->assertInternalType('float', $parsedValue, '->parse() as double by default.'); + $this->assertEquals(1, $parsedValue); + + $parsedValue = $this->formatter->parse('1', \NumberFormatter::TYPE_DOUBLE, $position); + $this->assertNull($position, '->parse() returns null to the $position reference if it doesn\'t had a defined value.'); + + $position = 0; + $parsedValue = $this->formatter->parse('1', \NumberFormatter::TYPE_DOUBLE, $position); + $this->assertEquals(1, $position); + + $parsedValue = $this->formatter->parse('prefix1', \NumberFormatter::TYPE_DOUBLE); + $this->assertFalse($parsedValue, '->parse() does not parse a number with a string prefix.'); + + $parsedValue = $this->formatter->parse('1suffix', \NumberFormatter::TYPE_DOUBLE); + $this->assertEquals(1, $parsedValue, '->parse() parses a number with a string suffix.'); + + $position = 0; + $parsedValue = $this->formatter->parse('1suffix', \NumberFormatter::TYPE_DOUBLE, $position); + $this->assertEquals(1, $parsedValue); + $this->assertEquals(1, $position, '->parse() ignores anything not a number before the number.'); + } + + /** + * @expectedException PHPUnit_Framework_Error_Warning + */ + public function testParseTypeDefault() + { + $this->formatter->parse('1', \NumberFormatter::TYPE_DEFAULT); + } + + public function testParseTypeInt32() + { + $parsedValue = $this->formatter->parse('1', \NumberFormatter::TYPE_INT32); + $this->assertInternalType('integer', $parsedValue); + $this->assertEquals(1, $parsedValue); + + $parsedValue = $this->formatter->parse('1.1', \NumberFormatter::TYPE_INT32); + $this->assertInternalType('integer', $parsedValue); + $this->assertEquals(1, $parsedValue, '->parse() TYPE_INT32 ignores the decimal part of a number and uses only the integer one.'); + + // int 32 out of range + $parsedValue = $this->formatter->parse('2,147,483,648', \NumberFormatter::TYPE_INT32); + $this->assertFalse($parsedValue, '->parse() TYPE_INT32 returns false if the value is out of range.'); + $parsedValue = $this->formatter->parse('-2,147,483,649', \NumberFormatter::TYPE_INT32); + $this->assertFalse($parsedValue, '->parse() TYPE_INT32 returns false if the value is out of range.'); + } + + public function testParseInt64() + { + // int 64 parsing + $parsedValue = $this->formatter->parse('2,147,483,647', \NumberFormatter::TYPE_INT64); + $this->assertInternalType('integer', $parsedValue); + $this->assertEquals(2147483647, $parsedValue); + + $parsedValue = $this->formatter->parse('-2,147,483,648', \NumberFormatter::TYPE_INT64); + $this->assertInternalType('integer', $parsedValue); + $this->assertEquals(-2147483648, $parsedValue); + + // int 64 using only 32 bit range strangeness + $parsedValue = $this->formatter->parse('2,147,483,648', \NumberFormatter::TYPE_INT64); + $this->assertInternalType('integer', $parsedValue); + $this->assertEquals(-2147483648, $parsedValue, '->parse() TYPE_INT64 does not use true 64 bit integers, using only the 32 bit range.'); + + $parsedValue = $this->formatter->parse('-2,147,483,649', \NumberFormatter::TYPE_INT64); + $this->assertInternalType('integer', $parsedValue); + $this->assertEquals(2147483647, $parsedValue, '->parse() TYPE_INT64 does not use true 64 bit integers, using only the 32 bit range.'); + } + + public function testParseTypeDouble() + { + $parsedValue = $this->formatter->parse('1', \NumberFormatter::TYPE_DOUBLE); + $this->assertInternalType('float', $parsedValue); + $this->assertEquals(1, $parsedValue); + + $parsedValue = $this->formatter->parse('1.1'); + $this->assertInternalType('float', $parsedValue); + $this->assertEquals(1.1, $parsedValue); + + $parsedValue = $this->formatter->parse('1,1'); + $this->assertInternalType('float', $parsedValue); + $this->assertEquals(11, $parsedValue); + } + + /** + * @expectedException PHPUnit_Framework_Error_Warning + */ + public function testParseTypeCurrency() + { + $this->formatter->parse('1', \NumberFormatter::TYPE_CURRENCY); + } +} diff --git a/tests/Symfony/Tests/Component/Locale/Stub/StubIntlDateFormatterTest.php b/tests/Symfony/Tests/Component/Locale/Stub/StubIntlDateFormatterTest.php new file mode 100644 index 0000000000000..fe3a776e6b318 --- /dev/null +++ b/tests/Symfony/Tests/Component/Locale/Stub/StubIntlDateFormatterTest.php @@ -0,0 +1,205 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\Locale\Stub; + +use Symfony\Component\Locale\Locale; +use Symfony\Component\Locale\Stub\StubIntlDateFormatter; + +class StubIntlDateFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function formatProvider() + { + return array( + array('y-M-d', 0, '1970-1-1'), + + /* escaping */ + array("'M", 0, 'M'), + array("'yy", 0, 'yy'), + array("'''yy", 0, "'yy"), + array("''y", 0, "'1970"), + array("''yy", 0, "'70"), + + /* month */ + array('M', 0, '1'), + array('MM', 0, '01'), + array('MMM', 0, 'Jan'), + array('MMMM', 0, 'January'), + array('MMMMM', 0, 'J'), + array('MMMMMM', 0, '000001'), + + array('L', 0, '1'), + array('LL', 0, '01'), + array('LLL', 0, 'Jan'), + array('LLLL', 0, 'January'), + array('LLLLL', 0, 'J'), + array('LLLLLL', 0, '000001'), + + /* year */ + array('y', 0, '1970'), + array('yy', 0, '70'), + array('yyy', 0, '1970'), + array('yyyy', 0, '1970'), + array('yyyyy', 0, '01970'), + array('yyyyyy', 0, '001970'), + + /* day */ + array('d', 0, '1'), + array('dd', 0, '01'), + array('ddd', 0, '001'), + + /* era */ + array('G', 0, 'AD'), + array('G', -62167222800, 'BC'), + + /* quarter */ + array('Q', 0, '1'), + array('QQ', 0, '01'), + array('QQQ', 0, 'Q1'), + array('QQQQ', 0, '1st quarter'), + array('QQQQQ', 0, '1st quarter'), + + array('q', 0, '1'), + array('qq', 0, '01'), + array('qqq', 0, 'Q1'), + array('qqqq', 0, '1st quarter'), + array('qqqqq', 0, '1st quarter'), + + // 4 months + array('Q', 7776000, '2'), + array('QQ', 7776000, '02'), + array('QQQ', 7776000, 'Q2'), + array('QQQQ', 7776000, '2nd quarter'), + + // 7 months + array('QQQQ', 15638400, '3rd quarter'), + + // 10 months + array('QQQQ', 23587200, '4th quarter'), + + /* 12-hour */ + array('h', 0, '12'), + array('hh', 0, '12'), + array('hhh', 0, '012'), + + array('h', 1, '12'), + array('h', 3600, '1'), + array('h', 43200, '12'), // 12 hours + + /* day of year */ + array('D', 0, '1'), + array('D', 86400, '2'), // 1 day + array('D', 31536000, '1'), // 1 year + array('D', 31622400, '2'), // 1 year + 1 day + + /* day of week */ + array('E', 0, 'Thu'), + array('EE', 0, 'Thu'), + array('EEE', 0, 'Thu'), + array('EEEE', 0, 'Thursday'), + array('EEEEE', 0, 'T'), + array('EEEEEE', 0, 'Thu'), + + array('E', 1296540000, 'Tue'), // 2011-02-01 + array('E', 1296950400, 'Sun'), // 2011-02-06 + + /* am/pm marker */ + array('a', 0, 'AM'), + array('aa', 0, 'AM'), + array('aaa', 0, 'AM'), + array('aaaa', 0, 'AM'), + + // 12 hours + array('a', 43200, 'PM'), + array('aa', 43200, 'PM'), + array('aaa', 43200, 'PM'), + array('aaaa', 43200, 'PM'), + + /* 24-hour */ + array('H', 0, '0'), + array('HH', 0, '00'), + array('HHH', 0, '000'), + + array('H', 1, '0'), + array('H', 3600, '1'), + array('H', 43200, '12'), + array('H', 46800, '13'), + ); + } + + /** + * provides data for cases that are broken in icu/intl + */ + public function brokenFormatProvider() + { + return array( + /* escaping */ + array("'y-'M-'d", 0, 'y-M-d'), + + /* weird bugs */ + array("WTF 'y-'M", 0, '0T1 y-M'), + array("n-'M", 0, 'n-M'), + ); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testConstructorWithUnsupportedLocale() + { + $formatter = new StubIntlDateFormatter('pt_BR', StubIntlDateFormatter::MEDIUM, StubIntlDateFormatter::SHORT); + } + + public function testConstructor() + { + $formatter = new StubIntlDateFormatter('en', StubIntlDateFormatter::MEDIUM, StubIntlDateFormatter::SHORT, 'UTC', StubIntlDateFormatter::GREGORIAN, 'Y-M-d'); + $this->assertEquals('Y-M-d', $formatter->getPattern()); + } + + /** + * @dataProvider formatProvider + */ + public function testFormat($pattern, $timestamp, $expected) + { + $formatter = new StubIntlDateFormatter('en', StubIntlDateFormatter::MEDIUM, StubIntlDateFormatter::SHORT, 'UTC', StubIntlDateFormatter::GREGORIAN, $pattern); + $this->assertSame($expected, $formatter->format($timestamp), 'Check date format with stub implementation.'); + + if (extension_loaded('intl')) { + $formatter = new \IntlDateFormatter('en', \IntlDateFormatter::MEDIUM, \IntlDateFormatter::SHORT, 'UTC', \IntlDateFormatter::GREGORIAN, $pattern); + $this->assertSame($expected, $formatter->format($timestamp), 'Check date format with intl extension.'); + } + } + + /** + * @dataProvider brokenFormatProvider + */ + public function testBrokenFormat($pattern, $timestamp, $expected) + { + $this->markTestSkipped('icu/intl has some bugs, thus skipping.'); + + $formatter = new StubIntlDateFormatter('en', StubIntlDateFormatter::MEDIUM, StubIntlDateFormatter::SHORT, 'UTC', StubIntlDateFormatter::GREGORIAN, $pattern); + $this->assertSame($expected, $formatter->format($timestamp), 'Check date format with stub implementation.'); + + if (extension_loaded('intl')) { + $formatter = new \IntlDateFormatter('en', \IntlDateFormatter::MEDIUM, \IntlDateFormatter::SHORT, 'UTC', \IntlDateFormatter::GREGORIAN, $pattern); + $this->assertSame($expected, $formatter->format($timestamp), 'Check date format with intl extension.'); + } + } + + /** + * @expectedException RuntimeException + */ + public function testGetCalendar() + { + $formatter = new StubIntlDateFormatter('en', StubIntlDateFormatter::MEDIUM, StubIntlDateFormatter::SHORT); + $formatter->getCalendar(); + } +} diff --git a/tests/Symfony/Tests/Component/Locale/Stub/StubNumberFormatterTest.php b/tests/Symfony/Tests/Component/Locale/Stub/StubNumberFormatterTest.php new file mode 100644 index 0000000000000..02f288a28c84f --- /dev/null +++ b/tests/Symfony/Tests/Component/Locale/Stub/StubNumberFormatterTest.php @@ -0,0 +1,311 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\Locale\Stub; + +use Symfony\Component\Locale\Locale; +use Symfony\Component\Locale\Stub\StubNumberFormatter; + +class StubNumberFormatterTest extends \PHPUnit_Framework_TestCase +{ + private static $int64Upper = 9223372036854775807; + + /** + * Strangely, using -9223372036854775808 directly in code make PHP type + * juggle the value to float. Then, use this value with an explicit typecast + * to int, e.g.: (int) self::$int64Lower. + */ + private static $int64Lower = -9223372036854775808; + + /** + * @expectedException InvalidArgumentException + */ + public function testConstructorWithUnsupportedLocale() + { + $formatter = new StubNumberFormatter('pt_BR'); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testConstructorWithUnsupportedStyle() + { + $formatter = new StubNumberFormatter('en', StubNumberFormatter::PATTERN_DECIMAL); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testConstructorWithPatternDifferentThanNull() + { + $formatter = new StubNumberFormatter('en', StubNumberFormatter::DECIMAL, ''); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetAttributeWithUnsupportedAttribute() + { + $formatter = new StubNumberFormatter('en', StubNumberFormatter::DECIMAL); + $formatter->setAttribute(StubNumberFormatter::LENIENT_PARSE, null); + } + + /** + * @covers Symfony\Component\Locale\StubNumberFormatter::getAttribute + */ + public function testSetAttributeInvalidRoundingMode() + { + $formatter = new StubNumberFormatter('en', StubNumberFormatter::DECIMAL); + + $ret = $formatter->setAttribute(StubNumberFormatter::ROUNDING_MODE, null); + $roundingMode = $formatter->getAttribute(StubNumberFormatter::ROUNDING_MODE); + + $this->assertFalse($ret); + $this->assertEquals(StubNumberFormatter::ROUND_HALFEVEN, $roundingMode); + } + + public function testSetAttributeInvalidGroupingUsedValue() + { + $formatter = new StubNumberFormatter('en', StubNumberFormatter::DECIMAL); + + $ret = $formatter->setAttribute(StubNumberFormatter::GROUPING_USED, null); + $groupingUsed = $formatter->getAttribute(StubNumberFormatter::GROUPING_USED); + + $this->assertFalse($ret); + $this->assertEquals(StubNumberFormatter::GROUPING_USED, $groupingUsed); + } + + /** + * @dataProvider formatCurrencyProvider + * @see StubNumberFormatter::formatCurrency() + * @todo Test with ROUND_CEILING and ROUND_FLOOR modes + */ + public function testFormatCurrency($value, $currency, $expected) + { + $formatter = new StubNumberFormatter('en', StubNumberFormatter::DECIMAL); + $this->assertEquals($expected, $formatter->formatCurrency($value, $currency)); + + if (extension_loaded('intl')) { + $numberFormatter = new \NumberFormatter('en', \NumberFormatter::CURRENCY); + + $this->assertEquals( + $numberFormatter->formatCurrency($value, $currency), + $formatter->formatCurrency($value, $currency) + ); + } + } + + public function formatCurrencyProvider() + { + return array( + array(100, 'ALL', 'ALL100'), + array(100, 'BRL', 'R$100.00'), + array(100, 'CRC', '₡100'), + array(-100, 'ALL', '(ALL100)'), + array(-100, 'BRL', '(R$100.00)'), + array(-100, 'CRC', '(₡100)'), + array(1000.12, 'ALL', 'ALL1,000'), + array(1000.12, 'BRL', 'R$1,000.12'), + array(1000.12, 'CRC', '₡1,000'), + // Test with other rounding modes + // array(1000.127, 'ALL', 'ALL1,000'), + // array(1000.127, 'BRL', 'R$1,000.12'), + // array(1000.127, 'CRC', '₡1,000'), + ); + } + + public function testFormat() + { + $formatter = new StubNumberFormatter('en', StubNumberFormatter::DECIMAL); + + // Use the defined fraction digits + $formatter->setAttribute(StubNumberFormatter::FRACTION_DIGITS, 2); + $this->assertSame('9.56', $formatter->format(9.555)); + $this->assertSame('1,000,000.12', $formatter->format(1000000.123)); + + $formatter->setAttribute(StubNumberFormatter::FRACTION_DIGITS, -1); + $this->assertSame('10', $formatter->format(9.5)); + + // Don't use number grouping + $formatter->setAttribute(StubNumberFormatter::FRACTION_DIGITS, 2); + $formatter->setAttribute(StubNumberFormatter::GROUPING_USED, 0); + $this->assertSame('1000000.12', $formatter->format(1000000.123)); + } + + public function testGetErrorCode() + { + $formatter = new StubNumberFormatter('en', StubNumberFormatter::DECIMAL); + $this->assertEquals(StubNumberFormatter::U_ZERO_ERROR, $formatter->getErrorCode()); + } + + public function testGetErrorMessage() + { + $formatter = new StubNumberFormatter('en', StubNumberFormatter::DECIMAL); + $this->assertEquals(StubNumberFormatter::U_ZERO_ERROR_MESSAGE, $formatter->getErrorMessage()); + } + + /** + * @expectedException RuntimeException + */ + public function testGetLocale() + { + $formatter = new StubNumberFormatter('en', StubNumberFormatter::DECIMAL); + $formatter->getLocale(); + } + + /** + * @expectedException RuntimeException + */ + public function testGetPattern() + { + $formatter = new StubNumberFormatter('en', StubNumberFormatter::DECIMAL); + $formatter->getPattern(); + } + + /** + * @expectedException RuntimeException + */ + public function testGetSymbol() + { + $formatter = new StubNumberFormatter('en', StubNumberFormatter::DECIMAL); + $formatter->getSymbol(null); + } + + /** + * @expectedException RuntimeException + */ + public function testGetTextAttribute() + { + $formatter = new StubNumberFormatter('en', StubNumberFormatter::DECIMAL); + $formatter->getTextAttribute(null); + } + + /** + * @expectedException RuntimeException + */ + public function testParseCurrency() + { + $formatter = new StubNumberFormatter('en', StubNumberFormatter::DECIMAL); + $formatter->parseCurrency(null, $currency); + } + + public function testParseValueWithStringInTheBeginning() + { + $formatter = new StubNumberFormatter('en', StubNumberFormatter::DECIMAL); + $value = $formatter->parse('R$1,234,567.89', StubNumberFormatter::TYPE_DOUBLE); + $this->assertFalse($value); + + $formatter = new \NumberFormatter('en', \NumberFormatter::DECIMAL); + $value = $formatter->parse('R$1,234,567.89', \NumberFormatter::TYPE_DOUBLE); + $this->assertFalse($value); + } + + public function testParseValueWithStringAtTheEnd() + { + $formatter = new StubNumberFormatter('en', StubNumberFormatter::DECIMAL); + $value = $formatter->parse('1,234,567.89', StubNumberFormatter::TYPE_DOUBLE); + $this->assertEquals(1234567.89, $value); + + $formatter = new \NumberFormatter('en', \NumberFormatter::DECIMAL); + $value = $formatter->parse('1,234,567.89R$', \NumberFormatter::TYPE_DOUBLE); + $this->assertEquals(1234567.89, $value); + } + + public function testParse() + { + $formatter = new StubNumberFormatter('en', StubNumberFormatter::DECIMAL); + + $value = $formatter->parse('9,223,372,036,854,775,808', StubNumberFormatter::TYPE_DOUBLE); + $this->assertSame(9223372036854775808, $value); + + // int 32 + $value = $formatter->parse('2,147,483,648', StubNumberFormatter::TYPE_INT32); + $this->assertSame(2147483647, $value); + + $value = $formatter->parse('-2,147,483,649', StubNumberFormatter::TYPE_INT32); + $this->assertSame(-2147483648, $value); + + // int 64 + $value = $formatter->parse('9,223,372,036,854,775,808', StubNumberFormatter::TYPE_INT64); + $this->assertSame(9223372036854775807, $value); + + $value = $formatter->parse('-9,223,372,036,854,775,809', StubNumberFormatter::TYPE_INT64); + $this->assertSame((int) self::$int64Lower, $value); + } + + /** + * @dataProvider parseDetectTypeProvider + */ + public function testParseDetectType($parseValue, $expectedType, $expectedValue) + { + $formatter = new StubNumberFormatter('en', StubNumberFormatter::DECIMAL); + $value = $formatter->parse($parseValue, StubNumberFormatter::TYPE_DEFAULT); + $this->assertInternalType($expectedType, $value); + $this->assertSame($expectedValue, $value); + } + + public function parseDetectTypeProvider() + { + return array( + array('1', 'integer', 1), + array('1.1', 'float', 1.1), + + // int 32 + array('2,147,483,647', 'integer', 2147483647), + array('-2,147,483,648', 'integer', -2147483648), + + // int 64 + array('9,223,372,036,854,775,807', 'integer', self::$int64Upper), + array('-9,223,372,036,854,775,808', 'integer', (int) self::$int64Lower), + + // int 64 overflow + array('9,223,372,036,854,775,808', 'float', 9223372036854775808), + array('-9,223,372,036,854,775,809', 'float', -9223372036854775809) + ); + } + + /** + * @expectedException RuntimeException + */ + public function testParseWithPositionValue() + { + $position = 1; + $formatter = new StubNumberFormatter('en', StubNumberFormatter::DECIMAL); + $formatter->parse('123', StubNumberFormatter::TYPE_DEFAULT, $position); + } + + /** + * @expectedException RuntimeException + */ + public function testSetPattern() + { + $formatter = new StubNumberFormatter('en', StubNumberFormatter::DECIMAL); + $formatter->setPattern(null); + } + + /** + * @expectedException RuntimeException + */ + public function testSetSymbol() + { + $formatter = new StubNumberFormatter('en', StubNumberFormatter::DECIMAL); + $formatter->setSymbol(null, null); + } + + /** + * @expectedException RuntimeException + */ + public function testSetTextAttribute() + { + $formatter = new StubNumberFormatter('en', StubNumberFormatter::DECIMAL); + $formatter->setTextAttribute(null, null); + } +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy