vendor/easycorp/easyadmin-bundle/src/Intl/IntlFormatter.php line 166

Open in your IDE?
  1. <?php
  2. namespace EasyCorp\Bundle\EasyAdminBundle\Intl;
  3. use Twig\Error\RuntimeError;
  4. /**
  5.  * Copied from https://github.com/twigphp/intl-extra/blob/2.x/src/IntlExtension.php
  6.  * (c) Fabien Potencier - MIT License.
  7.  *
  8.  * @author Fabien Potencier <fabien@symfony.com>
  9.  */
  10. final class IntlFormatter
  11. {
  12.     private const DATE_FORMATS = [
  13.         'none' => \IntlDateFormatter::NONE,
  14.         'short' => \IntlDateFormatter::SHORT,
  15.         'medium' => \IntlDateFormatter::MEDIUM,
  16.         'long' => \IntlDateFormatter::LONG,
  17.         'full' => \IntlDateFormatter::FULL,
  18.     ];
  19.     private const NUMBER_TYPES = [
  20.         'default' => \NumberFormatter::TYPE_DEFAULT,
  21.         'int32' => \NumberFormatter::TYPE_INT32,
  22.         'int64' => \NumberFormatter::TYPE_INT64,
  23.         'double' => \NumberFormatter::TYPE_DOUBLE,
  24.         'currency' => \NumberFormatter::TYPE_CURRENCY,
  25.     ];
  26.     private const NUMBER_STYLES = [
  27.         'decimal' => \NumberFormatter::DECIMAL,
  28.         'currency' => \NumberFormatter::CURRENCY,
  29.         'percent' => \NumberFormatter::PERCENT,
  30.         'scientific' => \NumberFormatter::SCIENTIFIC,
  31.         'spellout' => \NumberFormatter::SPELLOUT,
  32.         'ordinal' => \NumberFormatter::ORDINAL,
  33.         'duration' => \NumberFormatter::DURATION,
  34.     ];
  35.     private const NUMBER_ATTRIBUTES = [
  36.         'grouping_used' => \NumberFormatter::GROUPING_USED,
  37.         'decimal_always_shown' => \NumberFormatter::DECIMAL_ALWAYS_SHOWN,
  38.         'max_integer_digit' => \NumberFormatter::MAX_INTEGER_DIGITS,
  39.         'min_integer_digit' => \NumberFormatter::MIN_INTEGER_DIGITS,
  40.         'integer_digit' => \NumberFormatter::INTEGER_DIGITS,
  41.         'max_fraction_digit' => \NumberFormatter::MAX_FRACTION_DIGITS,
  42.         'min_fraction_digit' => \NumberFormatter::MIN_FRACTION_DIGITS,
  43.         'fraction_digit' => \NumberFormatter::FRACTION_DIGITS,
  44.         'multiplier' => \NumberFormatter::MULTIPLIER,
  45.         'grouping_size' => \NumberFormatter::GROUPING_SIZE,
  46.         'rounding_mode' => \NumberFormatter::ROUNDING_MODE,
  47.         'rounding_increment' => \NumberFormatter::ROUNDING_INCREMENT,
  48.         'format_width' => \NumberFormatter::FORMAT_WIDTH,
  49.         'padding_position' => \NumberFormatter::PADDING_POSITION,
  50.         'secondary_grouping_size' => \NumberFormatter::SECONDARY_GROUPING_SIZE,
  51.         'significant_digits_used' => \NumberFormatter::SIGNIFICANT_DIGITS_USED,
  52.         'min_significant_digits_used' => \NumberFormatter::MIN_SIGNIFICANT_DIGITS,
  53.         'max_significant_digits_used' => \NumberFormatter::MAX_SIGNIFICANT_DIGITS,
  54.         'lenient_parse' => \NumberFormatter::LENIENT_PARSE,
  55.     ];
  56.     private const NUMBER_ROUNDING_ATTRIBUTES = [
  57.         'ceiling' => \NumberFormatter::ROUND_CEILING,
  58.         'floor' => \NumberFormatter::ROUND_FLOOR,
  59.         'down' => \NumberFormatter::ROUND_DOWN,
  60.         'up' => \NumberFormatter::ROUND_UP,
  61.         'halfeven' => \NumberFormatter::ROUND_HALFEVEN,
  62.         'halfdown' => \NumberFormatter::ROUND_HALFDOWN,
  63.         'halfup' => \NumberFormatter::ROUND_HALFUP,
  64.     ];
  65.     private const NUMBER_PADDING_ATTRIBUTES = [
  66.         'before_prefix' => \NumberFormatter::PAD_BEFORE_PREFIX,
  67.         'after_prefix' => \NumberFormatter::PAD_AFTER_PREFIX,
  68.         'before_suffix' => \NumberFormatter::PAD_BEFORE_SUFFIX,
  69.         'after_suffix' => \NumberFormatter::PAD_AFTER_SUFFIX,
  70.     ];
  71.     private const NUMBER_TEXT_ATTRIBUTES = [
  72.         'positive_prefix' => \NumberFormatter::POSITIVE_PREFIX,
  73.         'positive_suffix' => \NumberFormatter::POSITIVE_SUFFIX,
  74.         'negative_prefix' => \NumberFormatter::NEGATIVE_PREFIX,
  75.         'negative_suffix' => \NumberFormatter::NEGATIVE_SUFFIX,
  76.         'padding_character' => \NumberFormatter::PADDING_CHARACTER,
  77.         'currency_mode' => \NumberFormatter::CURRENCY_CODE,
  78.         'default_ruleset' => \NumberFormatter::DEFAULT_RULESET,
  79.         'public_rulesets' => \NumberFormatter::PUBLIC_RULESETS,
  80.     ];
  81.     private const NUMBER_SYMBOLS = [
  82.         'decimal_separator' => \NumberFormatter::DECIMAL_SEPARATOR_SYMBOL,
  83.         'grouping_separator' => \NumberFormatter::GROUPING_SEPARATOR_SYMBOL,
  84.         'pattern_separator' => \NumberFormatter::PATTERN_SEPARATOR_SYMBOL,
  85.         'percent' => \NumberFormatter::PERCENT_SYMBOL,
  86.         'zero_digit' => \NumberFormatter::ZERO_DIGIT_SYMBOL,
  87.         'digit' => \NumberFormatter::DIGIT_SYMBOL,
  88.         'minus_sign' => \NumberFormatter::MINUS_SIGN_SYMBOL,
  89.         'plus_sign' => \NumberFormatter::PLUS_SIGN_SYMBOL,
  90.         'currency' => \NumberFormatter::CURRENCY_SYMBOL,
  91.         'intl_currency' => \NumberFormatter::INTL_CURRENCY_SYMBOL,
  92.         'monetary_separator' => \NumberFormatter::MONETARY_SEPARATOR_SYMBOL,
  93.         'exponential' => \NumberFormatter::EXPONENTIAL_SYMBOL,
  94.         'permill' => \NumberFormatter::PERMILL_SYMBOL,
  95.         'pad_escape' => \NumberFormatter::PAD_ESCAPE_SYMBOL,
  96.         'infinity' => \NumberFormatter::INFINITY_SYMBOL,
  97.         'nan' => \NumberFormatter::NAN_SYMBOL,
  98.         'significant_digit' => \NumberFormatter::SIGNIFICANT_DIGIT_SYMBOL,
  99.         'monetary_grouping_separator' => \NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL,
  100.     ];
  101.     private $dateFormatters = [];
  102.     private $numberFormatters = [];
  103.     private $numberFormatterPrototype;
  104.     public function formatCurrency($amountstring $currency, array $attrs = [], string $locale null): string
  105.     {
  106.         $formatter $this->createNumberFormatter($locale'currency'$attrs);
  107.         if (false === $formattedCurrency $formatter->formatCurrency($amount$currency)) {
  108.             throw new RuntimeError('Unable to format the given number as a currency.');
  109.         }
  110.         return $formattedCurrency;
  111.     }
  112.     public function formatNumber($number, array $attrs = [], string $style 'decimal'string $type 'default'string $locale null): string
  113.     {
  114.         if (!isset(self::NUMBER_TYPES[$type])) {
  115.             throw new RuntimeError(sprintf('The type "%s" does not exist, known types are: "%s".'$typeimplode('", "'array_keys(self::NUMBER_TYPES))));
  116.         }
  117.         $formatter $this->createNumberFormatter($locale$style$attrs);
  118.         if (false === $ret $formatter->format($numberself::NUMBER_TYPES[$type])) {
  119.             throw new RuntimeError('Unable to format the given number.');
  120.         }
  121.         return $ret;
  122.     }
  123.     /**
  124.      * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged
  125.      */
  126.     public function formatDateTime(?\DateTimeInterface $date, ?string $dateFormat 'medium', ?string $timeFormat 'medium'string $pattern ''$timezone nullstring $calendar 'gregorian'string $locale null): ?string
  127.     {
  128.         if (null === $date $this->convertDate($date$timezone)) {
  129.             return null;
  130.         }
  131.         $formatter $this->createDateFormatter($locale$dateFormat$timeFormat$pattern$date->getTimezone(), $calendar);
  132.         $formattedDateTime $formatter->format($date);
  133.         return false !== $formattedDateTime $formattedDateTime null;
  134.     }
  135.     /**
  136.      * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged
  137.      */
  138.     public function formatDate(?\DateTimeInterface $date, ?string $dateFormat 'medium'string $pattern ''$timezone nullstring $calendar 'gregorian'string $locale null): ?string
  139.     {
  140.         return $this->formatDateTime($date$dateFormat'none'$pattern$timezone$calendar$locale);
  141.     }
  142.     /**
  143.      * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged
  144.      */
  145.     public function formatTime(?\DateTimeInterface $date, ?string $timeFormat 'medium'string $pattern ''$timezone nullstring $calendar 'gregorian'string $locale null): ?string
  146.     {
  147.         return $this->formatDateTime($date'none'$timeFormat$pattern$timezone$calendar$locale);
  148.     }
  149.     private function createDateFormatter(?string $locale, ?string $dateFormat, ?string $timeFormatstring $pattern '', \DateTimeZone $timezonestring $calendar): \IntlDateFormatter
  150.     {
  151.         if (null !== $dateFormat && !isset(self::DATE_FORMATS[$dateFormat])) {
  152.             throw new RuntimeError(sprintf('The date format "%s" does not exist, known formats are: "%s".'$dateFormatimplode('", "'array_keys(self::DATE_FORMATS))));
  153.         }
  154.         if (null !== $timeFormat && !isset(self::DATE_FORMATS[$timeFormat])) {
  155.             throw new RuntimeError(sprintf('The time format "%s" does not exist, known formats are: "%s".'$timeFormatimplode('", "'array_keys(self::DATE_FORMATS))));
  156.         }
  157.         if (null === $locale) {
  158.             $locale = \Locale::getDefault();
  159.         }
  160.         $calendar 'gregorian' === $calendar ? \IntlDateFormatter::GREGORIAN : \IntlDateFormatter::TRADITIONAL;
  161.         $dateFormatValue self::DATE_FORMATS[$dateFormat] ?? null;
  162.         $timeFormatValue self::DATE_FORMATS[$timeFormat] ?? null;
  163.         $hash $locale.'|'.$dateFormatValue.'|'.$timeFormatValue.'|'.$timezone->getName().'|'.$calendar.'|'.$pattern;
  164.         if (!isset($this->dateFormatters[$hash])) {
  165.             $this->dateFormatters[$hash] = new \IntlDateFormatter($locale$dateFormatValue$timeFormatValue$timezone$calendar$pattern);
  166.         }
  167.         return $this->dateFormatters[$hash];
  168.     }
  169.     private function createNumberFormatter(?string $localestring $style, array $attrs = []): \NumberFormatter
  170.     {
  171.         if (!isset(self::NUMBER_STYLES[$style])) {
  172.             throw new RuntimeError(sprintf('The style "%s" does not exist, known styles are: "%s".'$styleimplode('", "'array_keys(self::NUMBER_STYLES))));
  173.         }
  174.         if (null === $locale) {
  175.             $locale = \Locale::getDefault();
  176.         }
  177.         // textAttrs and symbols can only be set on the prototype as there is probably no
  178.         // use case for setting it on each call.
  179.         $textAttrs = [];
  180.         $symbols = [];
  181.         if ($this->numberFormatterPrototype) {
  182.             foreach (self::NUMBER_ATTRIBUTES as $name => $const) {
  183.                 if (!isset($attrs[$name])) {
  184.                     $value $this->numberFormatterPrototype->getAttribute($const);
  185.                     if ('rounding_mode' === $name) {
  186.                         $value array_flip(self::NUMBER_ROUNDING_ATTRIBUTES)[$value];
  187.                     } elseif ('padding_position' === $name) {
  188.                         $value array_flip(self::NUMBER_PADDING_ATTRIBUTES)[$value];
  189.                     }
  190.                     $attrs[$name] = $value;
  191.                 }
  192.             }
  193.             foreach (self::NUMBER_TEXT_ATTRIBUTES as $name => $const) {
  194.                 $textAttrs[$name] = $this->numberFormatterPrototype->getTextAttribute($const);
  195.             }
  196.             foreach (self::NUMBER_SYMBOLS as $name => $const) {
  197.                 $symbols[$name] = $this->numberFormatterPrototype->getSymbol($const);
  198.             }
  199.         }
  200.         ksort($attrs);
  201.         $hash $locale.'|'.$style.'|'.json_encode($attrs).'|'.json_encode($textAttrs).'|'.json_encode($symbols);
  202.         if (!isset($this->numberFormatters[$hash])) {
  203.             $this->numberFormatters[$hash] = new \NumberFormatter($localeself::NUMBER_STYLES[$style]);
  204.         }
  205.         foreach ($attrs as $name => $value) {
  206.             if (!isset(self::NUMBER_ATTRIBUTES[$name])) {
  207.                 throw new RuntimeError(sprintf('The number formatter attribute "%s" does not exist, known attributes are: "%s".'$nameimplode('", "'array_keys(self::NUMBER_ATTRIBUTES))));
  208.             }
  209.             if ('rounding_mode' === $name) {
  210.                 if (!isset(self::NUMBER_ROUNDING_ATTRIBUTES[$value])) {
  211.                     throw new RuntimeError(sprintf('The number formatter rounding mode "%s" does not exist, known modes are: "%s".'$valueimplode('", "'array_keys(self::NUMBER_ROUNDING_ATTRIBUTES))));
  212.                 }
  213.                 $value self::NUMBER_ROUNDING_ATTRIBUTES[$value];
  214.             } elseif ('padding_position' === $name) {
  215.                 if (!isset(self::NUMBER_PADDING_ATTRIBUTES[$value])) {
  216.                     throw new RuntimeError(sprintf('The number formatter padding position "%s" does not exist, known positions are: "%s".'$valueimplode('", "'array_keys(self::NUMBER_PADDING_ATTRIBUTES))));
  217.                 }
  218.                 $value self::NUMBER_PADDING_ATTRIBUTES[$value];
  219.             }
  220.             $this->numberFormatters[$hash]->setAttribute(self::NUMBER_ATTRIBUTES[$name], $value);
  221.         }
  222.         foreach ($textAttrs as $name => $value) {
  223.             $this->numberFormatters[$hash]->setTextAttribute(self::NUMBER_TEXT_ATTRIBUTES[$name], $value);
  224.         }
  225.         foreach ($symbols as $name => $value) {
  226.             $this->numberFormatters[$hash]->setSymbol(self::NUMBER_SYMBOLS[$name], $value);
  227.         }
  228.         return $this->numberFormatters[$hash];
  229.     }
  230.     private function convertDate(?\DateTimeInterface $date$timezone null): ?\DateTimeInterface
  231.     {
  232.         if (null === $date) {
  233.             return null;
  234.         }
  235.         if (null === $timezone) {
  236.             $timezone = new \DateTimeZone(date_default_timezone_get());
  237.         } elseif (!$timezone instanceof \DateTimeZone) {
  238.             $timezone = new \DateTimeZone($timezone);
  239.         }
  240.         if ($date instanceof \DateTimeImmutable) {
  241.             return false !== $timezone $date->setTimezone($timezone) : $date;
  242.         }
  243.         $date = clone $date;
  244.         $date->setTimezone($timezone);
  245.         return $date;
  246.     }
  247. }