diff --git a/.editorconfig b/.editorconfig index 8a1a578..2cb9bef 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,13 +5,11 @@ end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true -indent_style = tab +indent_style = space indent_size = 4 [{*.json, *.yml, *.yaml, *.neon}] -indent_style = space indent_size = 2 [*.md] -indent_style = space max_line_length = 100 diff --git a/.github/workflows/code-style.yml b/.github/workflows/code-style.yml new file mode 100644 index 0000000..33888b6 --- /dev/null +++ b/.github/workflows/code-style.yml @@ -0,0 +1,21 @@ +name: code-style + +on: +- push +- pull_request + +jobs: + phpcs: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + coverage: none + php-version: "8.1" + ini-values: memory_limit=-1 + tools: phpcs, cs2pr + - name: Run PHP Code Sniffer + run: phpcs -q --report=checkstyle | cs2pr diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 449e4b5..37f6f81 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Install PHP uses: shivammathur/setup-php@v2 with: @@ -18,7 +18,7 @@ jobs: ini-values: memory_limit=-1 tools: composer:v2 - name: Cache dependencies - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: | ~/.composer/cache diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bd3a7e1..23b7516 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,7 +28,7 @@ jobs: - "8.2" steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Install PHP uses: shivammathur/setup-php@v2 with: @@ -38,7 +38,7 @@ jobs: tools: composer:v2 extensions: redis - name: Cache dependencies - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: | ~/.composer/cache diff --git a/Makefile b/Makefile index 01f0abf..8cb48f0 100644 --- a/Makefile +++ b/Makefile @@ -47,6 +47,7 @@ test-container-82: .PHONY: lint lint: + @XDEBUG_MODE=off phpcs -s @XDEBUG_MODE=off vendor/bin/phpstan --memory-limit=-1 # diff --git a/generator/src/Command/GenerateCurrency.php b/generator/src/Command/GenerateCurrency.php index 0f5bb45..f071f42 100644 --- a/generator/src/Command/GenerateCurrency.php +++ b/generator/src/Command/GenerateCurrency.php @@ -8,195 +8,195 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\VarExporter\VarExporter; + use function ICanBoogie\CLDR\Generator\indent; #[AsCommand('lib/Currency.php')] final class GenerateCurrency extends Command { - private const GENERATED_FILE = 'lib/Currency.php'; - - public function __construct( - private readonly Repository $repository - ) { - parent::__construct(); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - /** - * @var string[] $codes - * - * @link https://github.com/unicode-org/cldr-json/blob/45.0.0/cldr-json/cldr-numbers-full/main/en-001/currencies.json - * @phpstan-ignore-next-line - */ - $codes = array_keys($this->repository->locale_for('en-001')['currencies']); - - /** - * @var array $fractions - * - * @link https://github.com/unicode-org/cldr-json/blob/45.0.0/cldr-json/cldr-core/supplemental/currencyData.json - * @phpstan-ignore-next-line - */ - $fractions = $this->repository->supplemental['currencyData']['fractions']; - - $contents = $this->render( - codes: indent(VarExporter::export($codes), 1), - fractions: indent(VarExporter::export($fractions), 1), - ); - - file_put_contents(self::GENERATED_FILE, $contents); - - return self::SUCCESS; - } - - private function render( - string $codes, - string $fractions, - ): string { - $class = __CLASS__; - - return << - */ - final class Currency implements Localizable - { - /** - * @link https://github.com/unicode-org/cldr-json/blob/45.0.0/cldr-json/cldr-numbers-modern/main/en-001/currencies.json - */ - public const CODES = - $codes; - - /** - * @link https://github.com/unicode-org/cldr-json/blob/45.0.0/cldr-json/cldr-core/supplemental/currencyData.json - */ - private const FRACTIONS = - $fractions; - - private const FRACTIONS_FALLBACK = 'DEFAULT'; - - /** - * Whether a currency code is defined. - * - * @param string \$code - * A currency code; for example, EUR. - */ - public static function is_defined(string \$code): bool - { - return in_array(\$code, self::CODES); - } - - /** - * @param string \$code - * A currency code; for example, EUR. - * - * @throws CurrencyNotDefined - */ - public static function assert_is_defined(string \$code): void - { - self::is_defined(\$code) - or throw new CurrencyNotDefined(\$code); - } - - /** - * Returns a {@see CurrencyCode} of the specified code. - * - * @param string \$code - * A currency code; for example, EUR. - * - * @throws CurrencyNotDefined - */ - public static function of(string \$code): self - { - static \$instances; - - self::assert_is_defined(\$code); - - return \$instances[\$code] ??= new self(\$code, self::fraction_for(\$code)); - } - - /** - * Returns the {@see Fraction} for the specified currency code. - * - * @param string \$code - * * A currency code; for example, EUR. - */ - private static function fraction_for(string \$code): Fraction - { - static \$default_fraction; - - \$data = self::FRACTIONS[\$code] ?? null; - - if (!\$data) - { - return \$default_fraction ??= self::fraction_for(self::FRACTIONS_FALLBACK); - } - - return Fraction::from(\$data); - } - - /** - * @param string \$code - * A currency code; for example, EUR. - */ - private function __construct( - public readonly string \$code, - public readonly Fraction \$fraction, - ) { - } - - /** - * Returns the {@see \$code} of the currency. - */ - public function __toString() : string - { - return \$this->code; - } - - public function __serialize(): array - { - return [ 'code' => \$this->code ]; - } - - /** - * @param array{ code: string } \$data - */ - public function __unserialize(array \$data): void - { - \$this->code = \$data['code']; - \$this->fraction = self::fraction_for(\$this->code); - } - - /** - * Returns a localized currency. - */ - public function localized(Locale \$locale): LocalizedCurrency - { - return new LocalizedCurrency(\$this, \$locale); - } - } - - PHP; - } + private const GENERATED_FILE = 'lib/Currency.php'; + + public function __construct( + private readonly Repository $repository + ) { + parent::__construct(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + /** + * @var string[] $codes + * + * @link https://github.com/unicode-org/cldr-json/blob/45.0.0/cldr-json/cldr-numbers-full/main/en-001/currencies.json + * @phpstan-ignore-next-line + */ + $codes = array_keys($this->repository->locale_for('en-001')['currencies']); + + /** + * @var array $fractions + * + * @link https://github.com/unicode-org/cldr-json/blob/45.0.0/cldr-json/cldr-core/supplemental/currencyData.json + * @phpstan-ignore-next-line + */ + $fractions = $this->repository->supplemental['currencyData']['fractions']; + + $contents = $this->render( + codes: indent(VarExporter::export($codes), 2), + fractions: indent(VarExporter::export($fractions), 2), + ); + + file_put_contents(self::GENERATED_FILE, $contents); + + return self::SUCCESS; + } + + private function render( + string $codes, + string $fractions, + ): string { + $class = __CLASS__; + + return << + */ + final class Currency implements Localizable + { + /** + * @link https://github.com/unicode-org/cldr-json/blob/45.0.0/cldr-json/cldr-numbers-modern/main/en-001/currencies.json + */ + public const CODES = + $codes; + + /** + * @link https://github.com/unicode-org/cldr-json/blob/45.0.0/cldr-json/cldr-core/supplemental/currencyData.json + */ + private const FRACTIONS = + $fractions; + + private const FRACTIONS_FALLBACK = 'DEFAULT'; + + /** + * Whether a currency code is defined. + * + * @param string \$code + * A currency code; for example, EUR. + */ + public static function is_defined(string \$code): bool + { + return in_array(\$code, self::CODES); + } + + /** + * @param string \$code + * A currency code; for example, EUR. + * + * @throws CurrencyNotDefined + */ + public static function assert_is_defined(string \$code): void + { + self::is_defined(\$code) + or throw new CurrencyNotDefined(\$code); + } + + /** + * Returns a {@see CurrencyCode} of the specified code. + * + * @param string \$code + * A currency code; for example, EUR. + * + * @throws CurrencyNotDefined + */ + public static function of(string \$code): self + { + static \$instances; + + self::assert_is_defined(\$code); + + return \$instances[\$code] ??= new self(\$code, self::fraction_for(\$code)); + } + + /** + * Returns the {@see Fraction} for the specified currency code. + * + * @param string \$code + * * A currency code; for example, EUR. + */ + private static function fraction_for(string \$code): Fraction + { + static \$default_fraction; + + \$data = self::FRACTIONS[\$code] ?? null; + + if (!\$data) { + return \$default_fraction ??= self::fraction_for(self::FRACTIONS_FALLBACK); + } + + return Fraction::from(\$data); + } + + /** + * @param string \$code + * A currency code; for example, EUR. + */ + private function __construct( + public readonly string \$code, + public readonly Fraction \$fraction, + ) { + } + + /** + * Returns the {@see \$code} of the currency. + */ + public function __toString(): string + { + return \$this->code; + } + + public function __serialize(): array + { + return [ 'code' => \$this->code ]; + } + + /** + * @param array{ code: string } \$data + */ + public function __unserialize(array \$data): void + { + \$this->code = \$data['code']; + \$this->fraction = self::fraction_for(\$this->code); + } + + /** + * Returns a localized currency. + */ + public function localized(Locale \$locale): LocalizedCurrency + { + return new LocalizedCurrency(\$this, \$locale); + } + } + + PHP; + } } diff --git a/generator/src/Command/GenerateHasContextTransforms.php b/generator/src/Command/GenerateHasContextTransforms.php index ad35702..e4a4789 100644 --- a/generator/src/Command/GenerateHasContextTransforms.php +++ b/generator/src/Command/GenerateHasContextTransforms.php @@ -31,13 +31,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int foreach ($this->repository->available_locales as $locale_id) { $has = true; - try - { + try { $w("Checking $locale_id"); $this->repository->fetch("misc/$locale_id/contextTransforms"); - } - catch (ResourceNotFound) - { + } catch (ResourceNotFound) { $has = false; } @@ -45,7 +42,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $contents = $this->render( - has_by_locale: indent(VarExporter::export($has_by_locale), 1), + has_by_locale: indent(VarExporter::export($has_by_locale), 2), ); file_put_contents(self::GENERATED_FILE, $contents); @@ -55,7 +52,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int private function render(string $has_by_locale): string { - $class = __CLASS__; + $class = __CLASS__; return <<value]; } @@ -86,7 +83,9 @@ static public function for_locale(LocaleId \$locale_id): bool /** * @codeCoverageIgnore */ - private function __construct() {} + private function __construct() + { + } } PHP; diff --git a/generator/src/Command/GenerateLocaleId.php b/generator/src/Command/GenerateLocaleId.php index 3795520..287149f 100644 --- a/generator/src/Command/GenerateLocaleId.php +++ b/generator/src/Command/GenerateLocaleId.php @@ -2,13 +2,11 @@ namespace ICanBoogie\CLDR\Generator\Command; -use ICanBoogie\CLDR\LocaleNotAvailable; use ICanBoogie\CLDR\Repository; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; - use Symfony\Component\VarExporter\VarExporter; use function ICanBoogie\CLDR\Generator\indent; @@ -30,8 +28,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $available_locales = $this->repository->available_locales; $contents = $this->render( - parent_locales: indent(VarExporter::export($parent_locales), 1), - available_locales: indent(VarExporter::export($available_locales), 1), + parent_locales: indent(VarExporter::export($parent_locales), 2), + available_locales: indent(VarExporter::export($available_locales), 2), ); file_put_contents(self::GENERATED_FILE, $contents); @@ -43,7 +41,7 @@ public function render( string $parent_locales, string $available_locales, ): string { - $class = __CLASS__; + $class = __CLASS__; return <<repository->locale_for('en-001')['territories']); - $codes = array_values(array_filter($codes, fn($code) => !str_contains($code, '-alt'))); - - $contents = $this->render( - codes: indent(VarExporter::export($codes), 1), - ); - - file_put_contents(self::GENERATED_FILE, $contents); - - return self::SUCCESS; - } - - private function render( - string $codes, - ): string { - $class = __CLASS__; - - return <<value; - } - - public function __serialize(): array - { - return [ 'value' => \$this->value ]; - } - - /** - * @param array{ value: string } \$data - */ - public function __unserialize(array \$data): void - { - \$this->value = \$data['value']; - } - } - - PHP; - } + private const GENERATED_FILE = 'lib/TerritoryCode.php'; + + public function __construct( + private readonly Repository $repository + ) { + parent::__construct(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + /** + * @var string[] $codes + * + * @link https://github.com/unicode-org/cldr-json/blob/45.0.0/cldr-json/cldr-localenames-full/main/en-001/territories.json + * @phpstan-ignore-next-line + */ + $codes = array_keys($this->repository->locale_for('en-001')['territories']); + $codes = array_values(array_filter($codes, fn($code) => !str_contains($code, '-alt'))); + + $contents = $this->render( + codes: indent(VarExporter::export($codes), 2), + ); + + file_put_contents(self::GENERATED_FILE, $contents); + + return self::SUCCESS; + } + + private function render( + string $codes, + ): string { + $class = __CLASS__; + + return <<value; + } + + public function __serialize(): array + { + return [ 'value' => \$this->value ]; + } + + /** + * @param array{ value: string } \$data + */ + public function __unserialize(array \$data): void + { + \$this->value = \$data['value']; + } + } + + PHP; + } } diff --git a/generator/src/Command/GenerateUnitsCompanion.php b/generator/src/Command/GenerateUnitsCompanion.php index 3100b18..ca9e33d 100644 --- a/generator/src/Command/GenerateUnitsCompanion.php +++ b/generator/src/Command/GenerateUnitsCompanion.php @@ -62,7 +62,7 @@ private function render( string $properties, string $methods, ): string { - $class = __CLASS__; + $class = __CLASS__; return <<addCompilerPass(new AddConsoleCommandPass()); - - $container->register(Repository::class) - ->setFactory([ self::class, 'repository_factory' ]); - - foreach (self::COMMANDS as $command) { - $container - ->register( $command) - ->addTag('console.command') - ->setAutowired(true); - } - - $container->compile(); - - return $container; - } - - public static function repository_factory(): Repository - { - $provider = new Provider\CachedProvider( - new Provider\WebProvider(), - new CacheCollection([ - new RuntimeCache(), - new FileCache(CACHE) - ]) - ); - - return new Repository($provider); - } + ]; + + public static function provide_container(): ContainerInterface + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new AddConsoleCommandPass()); + + $container->register(Repository::class) + ->setFactory([ self::class, 'repository_factory' ]); + + foreach (self::COMMANDS as $command) { + $container + ->register($command) + ->addTag('console.command') + ->setAutowired(true); + } + + $container->compile(); + + return $container; + } + + public static function repository_factory(): Repository + { + $provider = new Provider\CachedProvider( + new Provider\WebProvider(), + new CacheCollection([ + new RuntimeCache(), + new FileCache(CACHE) + ]) + ); + + return new Repository($provider); + } } diff --git a/generator/src/helpers.php b/generator/src/helpers.php index 3040d65..825f9d1 100644 --- a/generator/src/helpers.php +++ b/generator/src/helpers.php @@ -11,7 +11,7 @@ function indent(string $str, int $level = 0): string $indent = str_repeat(' ', $level * 4); $parts = array_map( - fn($part) => $indent . $part, + fn($part) => $indent . $part, $parts ); diff --git a/lib/AbstractCollection.php b/lib/AbstractCollection.php index 969ee66..a396a85 100644 --- a/lib/AbstractCollection.php +++ b/lib/AbstractCollection.php @@ -14,39 +14,39 @@ */ abstract class AbstractCollection implements ArrayAccess { - use CollectionTrait; + use CollectionTrait; - /** - * @var array - */ - private array $collection = []; + /** + * @var array + */ + private array $collection = []; - /** - * @param Closure(string):T $create_instance - */ - public function __construct( - private readonly Closure $create_instance - ) { - } + /** + * @param Closure(string):T $create_instance + */ + public function __construct( + private readonly Closure $create_instance + ) { + } - /** - * @param string $offset - * - * @throws BadMethodCallException - */ - public function offsetExists($offset): bool - { - throw new BadMethodCallException("The method is not implemented"); - } + /** + * @param string $offset + * + * @throws BadMethodCallException + */ + public function offsetExists($offset): bool + { + throw new BadMethodCallException("The method is not implemented"); + } - /** - * @param string $offset - * - * @return T - */ - #[\ReturnTypeWillChange] - public function offsetGet($offset) - { - return $this->collection[$offset] ??= ($this->create_instance)($offset); - } + /** + * @param string $offset + * + * @return T + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return $this->collection[$offset] ??= ($this->create_instance)($offset); + } } diff --git a/lib/AbstractSectionCollection.php b/lib/AbstractSectionCollection.php index ba9e627..e2b25cb 100644 --- a/lib/AbstractSectionCollection.php +++ b/lib/AbstractSectionCollection.php @@ -10,50 +10,50 @@ */ abstract class AbstractSectionCollection implements ArrayAccess { - use CollectionTrait; - - public function __construct( - public readonly Repository $repository - ) { - } - - abstract public function offsetExists($offset): bool; - - /** - * @var array - * Loaded sections, where _key_ is a section name and _value_ its data. - * - * @phpstan-ignore-next-line - */ - private array $sections = []; - - /** - * @param string $offset - * - * @throws OffsetNotDefined - * @throws ResourceNotFound - */ - #[\ReturnTypeWillChange] // @phpstan-ignore-line - public function offsetGet($offset) - { - if (!$this->offsetExists($offset)) - { - throw new OffsetNotDefined(offset: $offset, container: $this); - } - - return $this->sections[$offset] ??= $this->repository->fetch( - $this->path_for($offset), - $this->data_path_for($offset) - ); - } - - /** - * Returns the CLDR path for the offset. - */ - abstract protected function path_for(string $offset): string; - - /** - * Returns the data path for the offset. - */ - abstract protected function data_path_for(string $offset): string; + use CollectionTrait; + + public function __construct( + public readonly Repository $repository + ) { + } + + abstract public function offsetExists($offset): bool; + + /** + * @var array + * Loaded sections, where _key_ is a section name and _value_ its data. + * + * @phpstan-ignore-next-line + */ + private array $sections = []; + + /** + * @param string $offset + * + * @throws OffsetNotDefined + * @throws ResourceNotFound + */ + #[\ReturnTypeWillChange] // @phpstan-ignore-line + public function offsetGet( + $offset + ) { + if (!$this->offsetExists($offset)) { + throw new OffsetNotDefined(offset: $offset, container: $this); + } + + return $this->sections[$offset] ??= $this->repository->fetch( + $this->path_for($offset), + $this->data_path_for($offset) + ); + } + + /** + * Returns the CLDR path for the offset. + */ + abstract protected function path_for(string $offset): string; + + /** + * Returns the data path for the offset. + */ + abstract protected function data_path_for(string $offset): string; } diff --git a/lib/Cache.php b/lib/Cache.php index cb38c25..d23e35e 100644 --- a/lib/Cache.php +++ b/lib/Cache.php @@ -4,13 +4,13 @@ interface Cache { - /** - * @return array|null - */ - public function get(string $path): ?array; + /** + * @return array|null + */ + public function get(string $path): ?array; - /** - * @param array $data - */ - public function set(string $path, array $data): void; + /** + * @param array $data + */ + public function set(string $path, array $data): void; } diff --git a/lib/Cache/CacheCollection.php b/lib/Cache/CacheCollection.php index a7ab692..d1c351d 100644 --- a/lib/Cache/CacheCollection.php +++ b/lib/Cache/CacheCollection.php @@ -9,33 +9,31 @@ */ final class CacheCollection implements Cache { - /** - * @param Cache[] $collection - */ - public function __construct( - private readonly array $collection - ) { - } + /** + * @param Cache[] $collection + */ + public function __construct( + private readonly array $collection + ) { + } - public function get(string $path): ?array - { - foreach ($this->collection as $cache) - { - $data = $cache->get($path); + public function get(string $path): ?array + { + foreach ($this->collection as $cache) { + $data = $cache->get($path); - if ($data !== null) { - return $data; - } - } + if ($data !== null) { + return $data; + } + } - return null; - } + return null; + } - public function set(string $path, array $data): void - { - foreach ($this->collection as $cache) - { - $cache->set($path, $data); - } - } + public function set(string $path, array $data): void + { + foreach ($this->collection as $cache) { + $cache->set($path, $data); + } + } } diff --git a/lib/Cache/FileCache.php b/lib/Cache/FileCache.php index 3e277ed..c5b9d4b 100644 --- a/lib/Cache/FileCache.php +++ b/lib/Cache/FileCache.php @@ -4,7 +4,6 @@ use Exception; use ICanBoogie\CLDR\Cache; -use Symfony\Component\VarExporter\VarExporter; use Throwable; use function dirname; @@ -28,166 +27,154 @@ */ final class FileCache implements Cache { - /** - * Recommended name for the cache directory. - */ - public const RECOMMENDED_DIR = '.cldr-cache'; - - static private bool $release_after; - - /** - * Absolute path to the storage directory. - */ - private string $path; - - /** - * @param string $path Absolute path to the storage directory. - */ - public function __construct(string $path) - { - $this->path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; - - self::$release_after ??= !(str_starts_with(PHP_OS, 'WIN')); - } - - public function get(string $path): ?array - { - $pathname = $this->format_absolute_pathname($path); - - if (!file_exists($pathname)) - { - return null; - } - - return $this->read($pathname); - } - - /** - * @throws Throwable - */ - public function set(string $path, array $data): void - { - $this->assert_writable(); - - $pathname = $this->format_absolute_pathname($path); - - // @phpstan-ignore-next-line - set_error_handler(fn() => null); - - try - { - $this->safe_set($pathname, $data); - } - finally - { - restore_error_handler(); - } - } - - private function format_absolute_pathname(string $path): string - { - return $this->path . str_replace('/', '--', $path) . '.php'; - } - - /** - * @phpstan-ignore-next-line - */ - private function read(string $pathname): array - { - return require $pathname; - } - - /** - * @phpstan-ignore-next-line - */ - private function write(string $pathname, array $data): void - { - $var = var_export($data, true); - $content = <<write($tmp_pathname, $data); - - # - # Windows, this is for you - # - if (!self::$release_after) - { - fclose($fh); - } - - if (!rename($pathname, $garbage_pathname)) - { - throw new Exception("Unable to rename $pathname as $garbage_pathname."); - } - - if (!rename($tmp_pathname, $pathname)) - { - throw new Exception("Unable to rename $tmp_pathname as $pathname."); - } - - if (!unlink($garbage_pathname)) - { - throw new Exception("Unable to delete $garbage_pathname."); - } - - # - # Unix, this is for you - # - if (self::$release_after) - { - flock($fh, LOCK_UN); - fclose($fh); - } - } - - /** - * Checks whether the storage directory is writable. - * - * @throws Exception when the storage directory is not writable. - */ - private function assert_writable(): void - { - $path = $this->path; - - if (!is_writable($path)) - { - throw new Exception("The directory $path is not writable."); - } - } + /** + * Recommended name for the cache directory. + */ + public const RECOMMENDED_DIR = '.cldr-cache'; + + private static bool $release_after; + + /** + * Absolute path to the storage directory. + */ + private string $path; + + /** + * @param string $path Absolute path to the storage directory. + */ + public function __construct(string $path) + { + $this->path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + + self::$release_after ??= !(str_starts_with(PHP_OS, 'WIN')); + } + + public function get(string $path): ?array + { + $pathname = $this->format_absolute_pathname($path); + + if (!file_exists($pathname)) { + return null; + } + + return $this->read($pathname); + } + + /** + * @throws Throwable + */ + public function set(string $path, array $data): void + { + $this->assert_writable(); + + $pathname = $this->format_absolute_pathname($path); + + // @phpstan-ignore-next-line + set_error_handler(fn() => null); + + try { + $this->safe_set($pathname, $data); + } finally { + restore_error_handler(); + } + } + + private function format_absolute_pathname(string $path): string + { + return $this->path . str_replace('/', '--', $path) . '.php'; + } + + /** + * @phpstan-ignore-next-line + */ + private function read(string $pathname): array + { + return require $pathname; + } + + /** + * @phpstan-ignore-next-line + */ + private function write(string $pathname, array $data): void + { + $var = var_export($data, true); + $content = <<write($tmp_pathname, $data); + + # + # Windows, this is for you + # + if (!self::$release_after) { + fclose($fh); + } + + if (!rename($pathname, $garbage_pathname)) { + throw new Exception("Unable to rename $pathname as $garbage_pathname."); + } + + if (!rename($tmp_pathname, $pathname)) { + throw new Exception("Unable to rename $tmp_pathname as $pathname."); + } + + if (!unlink($garbage_pathname)) { + throw new Exception("Unable to delete $garbage_pathname."); + } + + # + # Unix, this is for you + # + if (self::$release_after) { + flock($fh, LOCK_UN); + fclose($fh); + } + } + + /** + * Checks whether the storage directory is writable. + * + * @throws Exception when the storage directory is not writable. + */ + private function assert_writable(): void + { + $path = $this->path; + + if (!is_writable($path)) { + throw new Exception("The directory $path is not writable."); + } + } } diff --git a/lib/Cache/RedisCache.php b/lib/Cache/RedisCache.php index b127510..d17bb01 100644 --- a/lib/Cache/RedisCache.php +++ b/lib/Cache/RedisCache.php @@ -14,27 +14,27 @@ */ final class RedisCache implements Cache { - public const DEFAULT_PREFIX = 'icanboogie-cldr-'; + public const DEFAULT_PREFIX = 'icanboogie-cldr-'; - public function __construct( - private readonly RedisCluster|Redis $redis, - private readonly string $prefix = self::DEFAULT_PREFIX - ) { - } + public function __construct( + private readonly RedisCluster|Redis $redis, + private readonly string $prefix = self::DEFAULT_PREFIX + ) { + } - public function get(string $path): ?array - { - $data = $this->redis->get($this->prefix . $path); + public function get(string $path): ?array + { + $data = $this->redis->get($this->prefix . $path); - if (!$data) { - return null; - } + if (!$data) { + return null; + } - return unserialize($data); - } + return unserialize($data); + } - public function set(string $path, array $data): void - { - $this->redis->set($this->prefix . $path, serialize($data)); - } + public function set(string $path, array $data): void + { + $this->redis->set($this->prefix . $path, serialize($data)); + } } diff --git a/lib/Cache/RuntimeCache.php b/lib/Cache/RuntimeCache.php index 255ea35..c56d97e 100644 --- a/lib/Cache/RuntimeCache.php +++ b/lib/Cache/RuntimeCache.php @@ -6,24 +6,24 @@ final class RuntimeCache implements Cache { - /** - * @var array - */ - private array $cache = []; + /** + * @var array + */ + private array $cache = []; - /** - * @inheritDoc - */ - public function get(string $path): ?array - { - return $this->cache[$path] ?? null; - } + /** + * @inheritDoc + */ + public function get(string $path): ?array + { + return $this->cache[$path] ?? null; + } - /** - * @inheritDoc - */ - public function set(string $path, array $data): void - { - $this->cache[$path] = $data; - } + /** + * @inheritDoc + */ + public function set(string $path, array $data): void + { + $this->cache[$path] = $data; + } } diff --git a/lib/Calendar.php b/lib/Calendar.php index 3db4a64..ab153fc 100644 --- a/lib/Calendar.php +++ b/lib/Calendar.php @@ -50,136 +50,133 @@ */ final class Calendar extends ArrayObject { - public const SHORTHANDS_REGEX = '#^(standalone_)?(abbreviated|narrow|short|wide)_(days|eras|months|quarters)$#'; + /** + * @uses get_datetime_formatter + * @uses get_date_formatter + * @uses get_time_formatter + */ + use AccessorTrait; - public const WIDTH_ABBR = 'abbreviated'; - public const WIDTH_NARROW = 'narrow'; - public const WIDTH_SHORT = 'short'; - public const WIDTH_WIDE = 'wide'; + public const SHORTHANDS_REGEX = '#^(standalone_)?(abbreviated|narrow|short|wide)_(days|eras|months|quarters)$#'; - public const ERA_NAMES = 'eraNames'; - public const ERA_ABBR = 'eraAbbr'; - public const ERA_NARROW = 'eraNarrow'; + public const WIDTH_ABBR = 'abbreviated'; + public const WIDTH_NARROW = 'narrow'; + public const WIDTH_SHORT = 'short'; + public const WIDTH_WIDE = 'wide'; - public const CALENDAR_MONTHS = 'months'; - public const CALENDAR_DAYS = 'days'; - public const CALENDAR_QUARTERS = 'quarters'; - public const CALENDAR_ERAS = 'eras'; + public const ERA_NAMES = 'eraNames'; + public const ERA_ABBR = 'eraAbbr'; + public const ERA_NARROW = 'eraNarrow'; - public const CONTEXT_FORMAT = 'format'; - public const CONTEXT_STAND_ALONE = 'stand-alone'; + public const CALENDAR_MONTHS = 'months'; + public const CALENDAR_DAYS = 'days'; + public const CALENDAR_QUARTERS = 'quarters'; + public const CALENDAR_ERAS = 'eras'; - /** - * @uses get_datetime_formatter - * @uses get_date_formatter - * @uses get_time_formatter - */ - use AccessorTrait; + public const CONTEXT_FORMAT = 'format'; + public const CONTEXT_STAND_ALONE = 'stand-alone'; - /** - * @var array - */ - static private array $era_widths_mapping = [ + /** + * @var array + */ + private static array $era_widths_mapping = [ - self::WIDTH_ABBR => self::ERA_ABBR, - self::WIDTH_NARROW => self::ERA_NARROW, - self::WIDTH_SHORT => self::ERA_ABBR, - self::WIDTH_WIDE => self::ERA_NAMES + self::WIDTH_ABBR => self::ERA_ABBR, + self::WIDTH_NARROW => self::ERA_NARROW, + self::WIDTH_SHORT => self::ERA_ABBR, + self::WIDTH_WIDE => self::ERA_NAMES - ]; + ]; - private DateTimeFormatter $datetime_formatter; + private DateTimeFormatter $datetime_formatter; - private function get_datetime_formatter(): DateTimeFormatter - { - return $this->datetime_formatter ??= new DateTimeFormatter($this); - } + private function get_datetime_formatter(): DateTimeFormatter + { + return $this->datetime_formatter ??= new DateTimeFormatter($this); + } - private DateFormatter $date_formatter; + private DateFormatter $date_formatter; - private function get_date_formatter(): DateFormatter - { - return $this->date_formatter ??= new DateFormatter($this); - } + private function get_date_formatter(): DateFormatter + { + return $this->date_formatter ??= new DateFormatter($this); + } - private TimeFormatter $time_formatter; + private TimeFormatter $time_formatter; - private function get_time_formatter(): TimeFormatter - { - return $this->time_formatter ??= new TimeFormatter($this); - } + private function get_time_formatter(): TimeFormatter + { + return $this->time_formatter ??= new TimeFormatter($this); + } - private readonly ContextTransforms $context_transforms; + private readonly ContextTransforms $context_transforms; - /** - * @param array $data - */ - public function __construct( - public readonly Locale $locale, - array $data - ) { - $this->context_transforms = $locale->context_transforms; + /** + * @param array $data + */ + public function __construct( + public readonly Locale $locale, + array $data + ) { + $this->context_transforms = $locale->context_transforms; - $data = $this->transform_data($data); + $data = $this->transform_data($data); - parent::__construct($data); - } + parent::__construct($data); + } - /** - * @phpstan-ignore-next-line - */ - private array $shortcuts = []; + /** + * @phpstan-ignore-next-line + */ + private array $shortcuts = []; - /** - * @return mixed - */ - public function __get(string $property) - { - if (!preg_match(self::SHORTHANDS_REGEX, $property, $matches)) - { - return $this->accessor_get($property); - } + /** + * @return mixed + */ + public function __get(string $property) + { + if (!preg_match(self::SHORTHANDS_REGEX, $property, $matches)) { + return $this->accessor_get($property); + } - $make = function () use ($matches) { - [ , $standalone, $width, $type ] = $matches; + $make = function () use ($matches) { + [ , $standalone, $width, $type ] = $matches; - $data = $this[$type]; + $data = $this[$type]; - if ($type === self::CALENDAR_ERAS) - { - return $data[self::$era_widths_mapping[$width]]; - } + if ($type === self::CALENDAR_ERAS) { + return $data[self::$era_widths_mapping[$width]]; + } - $data = $data[$standalone ? self::CONTEXT_STAND_ALONE : self::CONTEXT_FORMAT]; + $data = $data[$standalone ? self::CONTEXT_STAND_ALONE : self::CONTEXT_FORMAT]; - if ($width === self::WIDTH_SHORT && empty($data[$width])) - { - $width = self::WIDTH_ABBR; - } + if ($width === self::WIDTH_SHORT && empty($data[$width])) { + $width = self::WIDTH_ABBR; + } - return $data[$width]; - }; + return $data[$width]; + }; - return $this->shortcuts[$property] ??= $make(); - } + return $this->shortcuts[$property] ??= $make(); + } /** * @see DateTimeFormatter::format */ - public function format_datetime( - DateTimeInterface|int|string $datetime, - string|DateTimeFormatLength $pattern_or_length_or_skeleton - ): string { + public function format_datetime( + DateTimeInterface|int|string $datetime, + string|DateTimeFormatLength $pattern_or_length_or_skeleton + ): string { return $this->get_datetime_formatter()->format($datetime, $pattern_or_length_or_skeleton); } /** * @see DateFormatter::format */ - public function format_date( - DateTimeInterface|int|string $datetime, - string|DateTimeFormatLength $pattern_or_length_or_skeleton - ): string { + public function format_date( + DateTimeInterface|int|string $datetime, + string|DateTimeFormatLength $pattern_or_length_or_skeleton + ): string { return $this->get_date_formatter()->format($datetime, $pattern_or_length_or_skeleton); } @@ -187,213 +184,191 @@ public function format_date( * @see TimeFormatter::format */ public function format_time( - DateTimeInterface|int|string $datetime, - string|DateTimeFormatLength $pattern_or_length_or_skeleton - ): string { + DateTimeInterface|int|string $datetime, + string|DateTimeFormatLength $pattern_or_length_or_skeleton + ): string { return $this->get_time_formatter()->format($datetime, $pattern_or_length_or_skeleton); } - /** - * Transforms calendar data according to context transforms rules. - * - * @param array $data - * - * @return array - * - * @uses transform_months - * @uses transform_days - * @uses transform_quarters - */ - private function transform_data(array $data): array - { - static $transformable = [ - - self::CALENDAR_MONTHS, - self::CALENDAR_DAYS, - self::CALENDAR_QUARTERS - - ]; - - foreach ($transformable as $name) - { - array_walk($data[$name], function(array &$data, string $context) use ($name): void { - - $is_stand_alone = self::CONTEXT_STAND_ALONE === $context; - - array_walk($data, function (array &$names, string $width) use ($name, $is_stand_alone): void { - - $names = $this->{ 'transform_' . $name }($names, $width, $is_stand_alone); - - }); - - }); - - } - - if (isset($data[self::CALENDAR_ERAS])) - { - array_walk($data[self::CALENDAR_ERAS], function(array &$names, string $width): void { - - $names = $this->transform_eras($names, $width); - - }); - } - - return $data; - } - - /** - * Transforms month names according to context transforms rules. - * - * @param string[] $names - * - * @return string[] - */ - private function transform_months(array $names, string $width, bool $standalone): array - { - return $this->transform_months_or_days( - $names, - $width, - $standalone, - ContextTransforms::USAGE_MONTH_STANDALONE_EXCEPT_NARROW - ); - } - - /** - * Transforms day names according to context transforms rules. - * - * @param string[] $names - * - * @return string[] - */ - private function transform_days(array $names, string $width, bool $standalone): array - { - return $this->transform_months_or_days( - $names, - $width, - $standalone, - ContextTransforms::USAGE_DAY_STANDALONE_EXCEPT_NARROW - ); - } - - /** - * Transforms day names according to context transforms rules. - * - * @param string[] $names - * - * @return string[] - */ - private function transform_months_or_days(array $names, string $width, bool $standalone, string $usage): array - { - if ($width === self::WIDTH_NARROW || !$standalone) - { - return $names; - } - - return $this->apply_transform( - $names, - $usage, - ContextTransforms::TYPE_STAND_ALONE - ); - } - - /** - * Transforms era names according to context transforms rules. - * - * @param string[] $names - * - * @return string[] - */ - private function transform_eras(array $names, string $width): array - { - switch ($width) - { - case self::ERA_ABBR: - - return $this->apply_transform( - $names, - ContextTransforms::USAGE_ERA_ABBR, - ContextTransforms::TYPE_STAND_ALONE - ); - - case self::ERA_NAMES: - - return $this->apply_transform( - $names, - ContextTransforms::USAGE_ERA_NAME, - ContextTransforms::TYPE_STAND_ALONE - ); - - case self::ERA_NARROW: - - return $this->apply_transform( - $names, - ContextTransforms::USAGE_ERA_NARROW, - ContextTransforms::TYPE_STAND_ALONE - ); - } - - return $names; // @codeCoverageIgnore - } - - /** - * Transforms quarters names according to context transforms rules. - * - * @param string[] $names - * - * @return string[] - */ - private function transform_quarters(array $names, string $width, bool $standalone): array - { - if ($standalone) - { - if ($width !== self::WIDTH_WIDE) - { - return $names; - } - - return $this->apply_transform( - $names, - ContextTransforms::USAGE_QUARTER_STANDALONE_WIDE, - ContextTransforms::TYPE_STAND_ALONE - ); - } - - return match ($width) { - self::WIDTH_ABBR => $this->apply_transform( - $names, - ContextTransforms::USAGE_QUARTER_ABBREVIATED, - ContextTransforms::TYPE_STAND_ALONE - ), - self::WIDTH_WIDE => $this->apply_transform( - $names, - ContextTransforms::USAGE_QUARTER_FORMAT_WIDE, - ContextTransforms::TYPE_STAND_ALONE - ), - self::WIDTH_NARROW => $this->apply_transform( - $names, - ContextTransforms::USAGE_QUARTER_NARROW, - ContextTransforms::TYPE_STAND_ALONE - ), - default => $names, - }; - // @codeCoverageIgnore - } - - /** - * Applies transformation to names. - * - * @param string[] $names - * - * @return string[] - */ - private function apply_transform(array $names, string $usage, string $type): array - { - $context_transforms = $this->context_transforms; - - return array_map( - static fn(string $str): string => $context_transforms->transform($str, $usage, $type), - $names - ); - } + /** + * Transforms calendar data according to context transforms rules. + * + * @param array $data + * + * @return array + * + * @uses transform_months + * @uses transform_days + * @uses transform_quarters + */ + private function transform_data(array $data): array + { + static $transformable = [ + + self::CALENDAR_MONTHS, + self::CALENDAR_DAYS, + self::CALENDAR_QUARTERS + + ]; + + foreach ($transformable as $name) { + array_walk($data[$name], function (array &$data, string $context) use ($name): void { + $is_stand_alone = self::CONTEXT_STAND_ALONE === $context; + + array_walk($data, function (array &$names, string $width) use ($name, $is_stand_alone): void { + $names = $this->{'transform_' . $name}($names, $width, $is_stand_alone); + }); + }); + } + + if (isset($data[self::CALENDAR_ERAS])) { + array_walk($data[self::CALENDAR_ERAS], function (array &$names, string $width): void { + $names = $this->transform_eras($names, $width); + }); + } + + return $data; + } + + /** + * Transforms month names according to context transforms rules. + * + * @param string[] $names + * + * @return string[] + */ + private function transform_months(array $names, string $width, bool $standalone): array + { + return $this->transform_months_or_days( + $names, + $width, + $standalone, + ContextTransforms::USAGE_MONTH_STANDALONE_EXCEPT_NARROW + ); + } + + /** + * Transforms day names according to context transforms rules. + * + * @param string[] $names + * + * @return string[] + */ + private function transform_days(array $names, string $width, bool $standalone): array + { + return $this->transform_months_or_days( + $names, + $width, + $standalone, + ContextTransforms::USAGE_DAY_STANDALONE_EXCEPT_NARROW + ); + } + + /** + * Transforms day names according to context transforms rules. + * + * @param string[] $names + * + * @return string[] + */ + private function transform_months_or_days(array $names, string $width, bool $standalone, string $usage): array + { + if ($width === self::WIDTH_NARROW || !$standalone) { + return $names; + } + + return $this->apply_transform( + $names, + $usage, + ContextTransforms::TYPE_STAND_ALONE + ); + } + + /** + * Transforms era names according to context transforms rules. + * + * @param string[] $names + * + * @return string[] + */ + private function transform_eras(array $names, string $width): array + { + return match ($width) { + self::ERA_ABBR => $this->apply_transform( + $names, + ContextTransforms::USAGE_ERA_ABBR, + ContextTransforms::TYPE_STAND_ALONE + ), + self::ERA_NAMES => $this->apply_transform( + $names, + ContextTransforms::USAGE_ERA_NAME, + ContextTransforms::TYPE_STAND_ALONE + ), + self::ERA_NARROW => $this->apply_transform( + $names, + ContextTransforms::USAGE_ERA_NARROW, + ContextTransforms::TYPE_STAND_ALONE + ), + default => $names, + }; + } + + /** + * Transforms quarters names according to context transforms rules. + * + * @param string[] $names + * + * @return string[] + */ + private function transform_quarters(array $names, string $width, bool $standalone): array + { + if ($standalone) { + if ($width !== self::WIDTH_WIDE) { + return $names; + } + + return $this->apply_transform( + $names, + ContextTransforms::USAGE_QUARTER_STANDALONE_WIDE, + ContextTransforms::TYPE_STAND_ALONE + ); + } + + return match ($width) { + self::WIDTH_ABBR => $this->apply_transform( + $names, + ContextTransforms::USAGE_QUARTER_ABBREVIATED, + ContextTransforms::TYPE_STAND_ALONE + ), + self::WIDTH_WIDE => $this->apply_transform( + $names, + ContextTransforms::USAGE_QUARTER_FORMAT_WIDE, + ContextTransforms::TYPE_STAND_ALONE + ), + self::WIDTH_NARROW => $this->apply_transform( + $names, + ContextTransforms::USAGE_QUARTER_NARROW, + ContextTransforms::TYPE_STAND_ALONE + ), + default => $names, + }; + // @codeCoverageIgnore + } + + /** + * Applies transformation to names. + * + * @param string[] $names + * + * @return string[] + */ + private function apply_transform(array $names, string $usage, string $type): array + { + $context_transforms = $this->context_transforms; + + return array_map( + static fn(string $str): string => $context_transforms->transform($str, $usage, $type), + $names + ); + } } diff --git a/lib/CalendarCollection.php b/lib/CalendarCollection.php index e4a34d5..5f2f611 100644 --- a/lib/CalendarCollection.php +++ b/lib/CalendarCollection.php @@ -18,18 +18,18 @@ */ final class CalendarCollection extends AbstractCollection { - public function __construct( - public readonly Locale $locale - ) { - parent::__construct($this->new(...)); - } + public function __construct( + public readonly Locale $locale + ) { + parent::__construct($this->new(...)); + } - private function new(string $id): Calendar - { - $data = $this->locale["ca-$id"]; + private function new(string $id): Calendar + { + $data = $this->locale["ca-$id"]; - assert(is_array($data)); + assert(is_array($data)); - return new Calendar($this->locale, $data); - } + return new Calendar($this->locale, $data); + } } diff --git a/lib/CollectionTrait.php b/lib/CollectionTrait.php index 72733f5..9c4d9e5 100644 --- a/lib/CollectionTrait.php +++ b/lib/CollectionTrait.php @@ -9,24 +9,24 @@ */ trait CollectionTrait { - /** - * @param string $offset - * @param mixed $value - * - * @throw OffsetNotWritable in attempt to set the offset. - */ - public function offsetSet($offset, $value): void - { - throw new OffsetNotWritable(offset: $offset, container: $this); - } + /** + * @param string $offset + * @param mixed $value + * + * @throw OffsetNotWritable in attempt to set the offset. + */ + public function offsetSet($offset, $value): void + { + throw new OffsetNotWritable(offset: $offset, container: $this); + } - /** - * @param string $offset - * - * @throw OffsetNotWritable in attempt to unset the offset. - */ - public function offsetUnset($offset): void - { - throw new OffsetNotWritable(offset: $offset, container: $this); - } + /** + * @param string $offset + * + * @throw OffsetNotWritable in attempt to unset the offset. + */ + public function offsetUnset($offset): void + { + throw new OffsetNotWritable(offset: $offset, container: $this); + } } diff --git a/lib/ContextTransforms.php b/lib/ContextTransforms.php index 8eacf87..7677131 100644 --- a/lib/ContextTransforms.php +++ b/lib/ContextTransforms.php @@ -12,82 +12,80 @@ */ final class ContextTransforms { - public const USAGE_ALL = 'all'; - public const USAGE_LANGUAGE = 'language'; - public const USAGE_SCRIPT = 'script'; - public const USAGE_TERRITORY = 'territory'; - public const USAGE_VARIANT = 'variant'; - public const USAGE_KEY = 'key'; - public const USAGE_KEYVALUE = 'keyValue'; - public const USAGE_MONTH_FORMAT_EXCEPT_NARROW = 'month-format-except-narrow'; - public const USAGE_MONTH_STANDALONE_EXCEPT_NARROW = 'month-standalone-except-narrow'; - public const USAGE_MONTH_NARROW = 'month-narrow'; - public const USAGE_DAY_FORMAT_EXCEPT_NARROW = 'day-format-except-narrow'; - public const USAGE_DAY_STANDALONE_EXCEPT_NARROW = 'day-standalone-except-narrow'; - public const USAGE_DAY_NARROW = 'day-narrow'; - public const USAGE_ERA_NAME = 'era-name'; - public const USAGE_ERA_ABBR = 'era-abbr'; - public const USAGE_ERA_NARROW = 'era-narrow'; - public const USAGE_QUARTER_FORMAT_WIDE = 'quarter-format-wide'; - public const USAGE_QUARTER_STANDALONE_WIDE = 'quarter-standalone-wide'; - public const USAGE_QUARTER_ABBREVIATED = 'quarter-abbreviated'; - public const USAGE_QUARTER_NARROW = 'quarter-narrow'; - public const USAGE_CALENDAR_FIELD = 'calendar-field'; - public const USAGE_ZONE_EXEMPLARCITY = 'zone-exemplarCity'; - public const USAGE_ZONE_LONG = 'zone-long'; - public const USAGE_ZONE_SHORT = 'zone-short'; - public const USAGE_METAZONE_LONG = 'metazone-long'; - public const USAGE_METAZONE_SHORT = 'metazone-short'; - public const USAGE_SYMBOL = 'symbol'; - public const USAGE_CURRENCYNAME = 'currencyName'; - public const USAGE_CURRENCYNAME_COUNT = 'currencyName-count'; - public const USAGE_RELATIVE = 'relative'; - public const USAGE_UNIT_PATTERN = 'unit-pattern'; - public const USAGE_NUMBER_SPELLOUT = 'number-spellout'; + public const USAGE_ALL = 'all'; + public const USAGE_LANGUAGE = 'language'; + public const USAGE_SCRIPT = 'script'; + public const USAGE_TERRITORY = 'territory'; + public const USAGE_VARIANT = 'variant'; + public const USAGE_KEY = 'key'; + public const USAGE_KEYVALUE = 'keyValue'; + public const USAGE_MONTH_FORMAT_EXCEPT_NARROW = 'month-format-except-narrow'; + public const USAGE_MONTH_STANDALONE_EXCEPT_NARROW = 'month-standalone-except-narrow'; + public const USAGE_MONTH_NARROW = 'month-narrow'; + public const USAGE_DAY_FORMAT_EXCEPT_NARROW = 'day-format-except-narrow'; + public const USAGE_DAY_STANDALONE_EXCEPT_NARROW = 'day-standalone-except-narrow'; + public const USAGE_DAY_NARROW = 'day-narrow'; + public const USAGE_ERA_NAME = 'era-name'; + public const USAGE_ERA_ABBR = 'era-abbr'; + public const USAGE_ERA_NARROW = 'era-narrow'; + public const USAGE_QUARTER_FORMAT_WIDE = 'quarter-format-wide'; + public const USAGE_QUARTER_STANDALONE_WIDE = 'quarter-standalone-wide'; + public const USAGE_QUARTER_ABBREVIATED = 'quarter-abbreviated'; + public const USAGE_QUARTER_NARROW = 'quarter-narrow'; + public const USAGE_CALENDAR_FIELD = 'calendar-field'; + public const USAGE_ZONE_EXEMPLARCITY = 'zone-exemplarCity'; + public const USAGE_ZONE_LONG = 'zone-long'; + public const USAGE_ZONE_SHORT = 'zone-short'; + public const USAGE_METAZONE_LONG = 'metazone-long'; + public const USAGE_METAZONE_SHORT = 'metazone-short'; + public const USAGE_SYMBOL = 'symbol'; + public const USAGE_CURRENCYNAME = 'currencyName'; + public const USAGE_CURRENCYNAME_COUNT = 'currencyName-count'; + public const USAGE_RELATIVE = 'relative'; + public const USAGE_UNIT_PATTERN = 'unit-pattern'; + public const USAGE_NUMBER_SPELLOUT = 'number-spellout'; - public const TYPE_UILIST_OR_MENU = 'uiListOrMenu'; - public const TYPE_STAND_ALONE = 'stand-alone'; + public const TYPE_UILIST_OR_MENU = 'uiListOrMenu'; + public const TYPE_STAND_ALONE = 'stand-alone'; - public const TRANSFORM_TITLECASE_FIRSTWORD = 'titlecase-firstword'; - public const TRANSFORM_NO_CHANGE = 'no-change'; + public const TRANSFORM_TITLECASE_FIRSTWORD = 'titlecase-firstword'; + public const TRANSFORM_NO_CHANGE = 'no-change'; - /** - * @param array> $rules - */ - public function __construct( - private readonly array $rules - ) { - } + /** + * @param array> $rules + */ + public function __construct( + private readonly array $rules + ) { + } - /** - * @param string $usage One of `USAGE_*`. - * @param string $type One of `TYPE_*`. - */ - public function transform(string $str, string $usage, string $type): string - { - $rules = $this->rules; + /** + * @param string $usage One of `USAGE_*`. + * @param string $type One of `TYPE_*`. + */ + public function transform(string $str, string $usage, string $type): string + { + $rules = $this->rules; - if (empty($rules[$usage])) - { - $usage = self::USAGE_ALL; - } + if (empty($rules[$usage])) { + $usage = self::USAGE_ALL; + } - if (empty($rules[$usage][$type])) - { - return $str; - } + if (empty($rules[$usage][$type])) { + return $str; + } - $transform = $rules[$usage][$type]; + $transform = $rules[$usage][$type]; - return match ($transform) { - self::TRANSFORM_TITLECASE_FIRSTWORD => $this->titlecase_firstword($str), - self::TRANSFORM_NO_CHANGE => $str, - default => throw new LogicException("Don't know how to apply transform: $transform"), - }; - } + return match ($transform) { + self::TRANSFORM_TITLECASE_FIRSTWORD => $this->titlecase_firstword($str), + self::TRANSFORM_NO_CHANGE => $str, + default => throw new LogicException("Don't know how to apply transform: $transform"), + }; + } - private function titlecase_firstword(string $str): string - { - return mb_strtoupper(mb_substr($str, 0, 1)) . mb_substr($str, 1); - } + private function titlecase_firstword(string $str): string + { + return mb_strtoupper(mb_substr($str, 0, 1)) . mb_substr($str, 1); + } } diff --git a/lib/Currency.php b/lib/Currency.php index 96e9837..466a5f1 100644 --- a/lib/Currency.php +++ b/lib/Currency.php @@ -19,752 +19,751 @@ */ final class Currency implements Localizable { - /** - * @link https://github.com/unicode-org/cldr-json/blob/45.0.0/cldr-json/cldr-numbers-modern/main/en-001/currencies.json - */ - public const CODES = - [ - 'ADP', - 'AED', - 'AFA', - 'AFN', - 'ALK', - 'ALL', - 'AMD', - 'ANG', - 'AOA', - 'AOK', - 'AON', - 'AOR', - 'ARA', - 'ARL', - 'ARM', - 'ARP', - 'ARS', - 'ATS', - 'AUD', - 'AWG', - 'AZM', - 'AZN', - 'BAD', - 'BAM', - 'BAN', - 'BBD', - 'BDT', - 'BEC', - 'BEF', - 'BEL', - 'BGL', - 'BGM', - 'BGN', - 'BGO', - 'BHD', - 'BIF', - 'BMD', - 'BND', - 'BOB', - 'BOL', - 'BOP', - 'BOV', - 'BRB', - 'BRC', - 'BRE', - 'BRL', - 'BRN', - 'BRR', - 'BRZ', - 'BSD', - 'BTN', - 'BUK', - 'BWP', - 'BYB', - 'BYN', - 'BYR', - 'BZD', - 'CAD', - 'CDF', - 'CHE', - 'CHF', - 'CHW', - 'CLE', - 'CLF', - 'CLP', - 'CNH', - 'CNX', - 'CNY', - 'COP', - 'COU', - 'CRC', - 'CSD', - 'CSK', - 'CUC', - 'CUP', - 'CVE', - 'CYP', - 'CZK', - 'DDM', - 'DEM', - 'DJF', - 'DKK', - 'DOP', - 'DZD', - 'ECS', - 'ECV', - 'EEK', - 'EGP', - 'ERN', - 'ESA', - 'ESB', - 'ESP', - 'ETB', - 'EUR', - 'FIM', - 'FJD', - 'FKP', - 'FRF', - 'GBP', - 'GEK', - 'GEL', - 'GHC', - 'GHS', - 'GIP', - 'GMD', - 'GNF', - 'GNS', - 'GQE', - 'GRD', - 'GTQ', - 'GWE', - 'GWP', - 'GYD', - 'HKD', - 'HNL', - 'HRD', - 'HRK', - 'HTG', - 'HUF', - 'IDR', - 'IEP', - 'ILP', - 'ILR', - 'ILS', - 'INR', - 'IQD', - 'IRR', - 'ISJ', - 'ISK', - 'ITL', - 'JMD', - 'JOD', - 'JPY', - 'KES', - 'KGS', - 'KHR', - 'KMF', - 'KPW', - 'KRH', - 'KRO', - 'KRW', - 'KWD', - 'KYD', - 'KZT', - 'LAK', - 'LBP', - 'LKR', - 'LRD', - 'LSL', - 'LTL', - 'LTT', - 'LUC', - 'LUF', - 'LUL', - 'LVL', - 'LVR', - 'LYD', - 'MAD', - 'MAF', - 'MCF', - 'MDC', - 'MDL', - 'MGA', - 'MGF', - 'MKD', - 'MKN', - 'MLF', - 'MMK', - 'MNT', - 'MOP', - 'MRO', - 'MRU', - 'MTL', - 'MTP', - 'MUR', - 'MVP', - 'MVR', - 'MWK', - 'MXN', - 'MXP', - 'MXV', - 'MYR', - 'MZE', - 'MZM', - 'MZN', - 'NAD', - 'NGN', - 'NIC', - 'NIO', - 'NLG', - 'NOK', - 'NPR', - 'NZD', - 'OMR', - 'PAB', - 'PEI', - 'PEN', - 'PES', - 'PGK', - 'PHP', - 'PKR', - 'PLN', - 'PLZ', - 'PTE', - 'PYG', - 'QAR', - 'RHD', - 'ROL', - 'RON', - 'RSD', - 'RUB', - 'RUR', - 'RWF', - 'SAR', - 'SBD', - 'SCR', - 'SDD', - 'SDG', - 'SDP', - 'SEK', - 'SGD', - 'SHP', - 'SIT', - 'SKK', - 'SLE', - 'SLL', - 'SOS', - 'SRD', - 'SRG', - 'SSP', - 'STD', - 'STN', - 'SUR', - 'SVC', - 'SYP', - 'SZL', - 'THB', - 'TJR', - 'TJS', - 'TMM', - 'TMT', - 'TND', - 'TOP', - 'TPE', - 'TRL', - 'TRY', - 'TTD', - 'TWD', - 'TZS', - 'UAH', - 'UAK', - 'UGS', - 'UGX', - 'USD', - 'USN', - 'USS', - 'UYI', - 'UYP', - 'UYU', - 'UYW', - 'UZS', - 'VEB', - 'VED', - 'VEF', - 'VES', - 'VND', - 'VNN', - 'VUV', - 'WST', - 'XAF', - 'XAG', - 'XAU', - 'XBA', - 'XBB', - 'XBC', - 'XBD', - 'XCD', - 'XCG', - 'XDR', - 'XEU', - 'XFO', - 'XFU', - 'XOF', - 'XPD', - 'XPF', - 'XPT', - 'XRE', - 'XSU', - 'XTS', - 'XUA', - 'XXX', - 'YDD', - 'YER', - 'YUD', - 'YUM', - 'YUN', - 'YUR', - 'ZAL', - 'ZAR', - 'ZMK', - 'ZMW', - 'ZRN', - 'ZRZ', - 'ZWD', - 'ZWL', - 'ZWR', - ]; + /** + * @link https://github.com/unicode-org/cldr-json/blob/45.0.0/cldr-json/cldr-numbers-modern/main/en-001/currencies.json + */ + public const CODES = + [ + 'ADP', + 'AED', + 'AFA', + 'AFN', + 'ALK', + 'ALL', + 'AMD', + 'ANG', + 'AOA', + 'AOK', + 'AON', + 'AOR', + 'ARA', + 'ARL', + 'ARM', + 'ARP', + 'ARS', + 'ATS', + 'AUD', + 'AWG', + 'AZM', + 'AZN', + 'BAD', + 'BAM', + 'BAN', + 'BBD', + 'BDT', + 'BEC', + 'BEF', + 'BEL', + 'BGL', + 'BGM', + 'BGN', + 'BGO', + 'BHD', + 'BIF', + 'BMD', + 'BND', + 'BOB', + 'BOL', + 'BOP', + 'BOV', + 'BRB', + 'BRC', + 'BRE', + 'BRL', + 'BRN', + 'BRR', + 'BRZ', + 'BSD', + 'BTN', + 'BUK', + 'BWP', + 'BYB', + 'BYN', + 'BYR', + 'BZD', + 'CAD', + 'CDF', + 'CHE', + 'CHF', + 'CHW', + 'CLE', + 'CLF', + 'CLP', + 'CNH', + 'CNX', + 'CNY', + 'COP', + 'COU', + 'CRC', + 'CSD', + 'CSK', + 'CUC', + 'CUP', + 'CVE', + 'CYP', + 'CZK', + 'DDM', + 'DEM', + 'DJF', + 'DKK', + 'DOP', + 'DZD', + 'ECS', + 'ECV', + 'EEK', + 'EGP', + 'ERN', + 'ESA', + 'ESB', + 'ESP', + 'ETB', + 'EUR', + 'FIM', + 'FJD', + 'FKP', + 'FRF', + 'GBP', + 'GEK', + 'GEL', + 'GHC', + 'GHS', + 'GIP', + 'GMD', + 'GNF', + 'GNS', + 'GQE', + 'GRD', + 'GTQ', + 'GWE', + 'GWP', + 'GYD', + 'HKD', + 'HNL', + 'HRD', + 'HRK', + 'HTG', + 'HUF', + 'IDR', + 'IEP', + 'ILP', + 'ILR', + 'ILS', + 'INR', + 'IQD', + 'IRR', + 'ISJ', + 'ISK', + 'ITL', + 'JMD', + 'JOD', + 'JPY', + 'KES', + 'KGS', + 'KHR', + 'KMF', + 'KPW', + 'KRH', + 'KRO', + 'KRW', + 'KWD', + 'KYD', + 'KZT', + 'LAK', + 'LBP', + 'LKR', + 'LRD', + 'LSL', + 'LTL', + 'LTT', + 'LUC', + 'LUF', + 'LUL', + 'LVL', + 'LVR', + 'LYD', + 'MAD', + 'MAF', + 'MCF', + 'MDC', + 'MDL', + 'MGA', + 'MGF', + 'MKD', + 'MKN', + 'MLF', + 'MMK', + 'MNT', + 'MOP', + 'MRO', + 'MRU', + 'MTL', + 'MTP', + 'MUR', + 'MVP', + 'MVR', + 'MWK', + 'MXN', + 'MXP', + 'MXV', + 'MYR', + 'MZE', + 'MZM', + 'MZN', + 'NAD', + 'NGN', + 'NIC', + 'NIO', + 'NLG', + 'NOK', + 'NPR', + 'NZD', + 'OMR', + 'PAB', + 'PEI', + 'PEN', + 'PES', + 'PGK', + 'PHP', + 'PKR', + 'PLN', + 'PLZ', + 'PTE', + 'PYG', + 'QAR', + 'RHD', + 'ROL', + 'RON', + 'RSD', + 'RUB', + 'RUR', + 'RWF', + 'SAR', + 'SBD', + 'SCR', + 'SDD', + 'SDG', + 'SDP', + 'SEK', + 'SGD', + 'SHP', + 'SIT', + 'SKK', + 'SLE', + 'SLL', + 'SOS', + 'SRD', + 'SRG', + 'SSP', + 'STD', + 'STN', + 'SUR', + 'SVC', + 'SYP', + 'SZL', + 'THB', + 'TJR', + 'TJS', + 'TMM', + 'TMT', + 'TND', + 'TOP', + 'TPE', + 'TRL', + 'TRY', + 'TTD', + 'TWD', + 'TZS', + 'UAH', + 'UAK', + 'UGS', + 'UGX', + 'USD', + 'USN', + 'USS', + 'UYI', + 'UYP', + 'UYU', + 'UYW', + 'UZS', + 'VEB', + 'VED', + 'VEF', + 'VES', + 'VND', + 'VNN', + 'VUV', + 'WST', + 'XAF', + 'XAG', + 'XAU', + 'XBA', + 'XBB', + 'XBC', + 'XBD', + 'XCD', + 'XCG', + 'XDR', + 'XEU', + 'XFO', + 'XFU', + 'XOF', + 'XPD', + 'XPF', + 'XPT', + 'XRE', + 'XSU', + 'XTS', + 'XUA', + 'XXX', + 'YDD', + 'YER', + 'YUD', + 'YUM', + 'YUN', + 'YUR', + 'ZAL', + 'ZAR', + 'ZMK', + 'ZMW', + 'ZRN', + 'ZRZ', + 'ZWD', + 'ZWL', + 'ZWR', + ]; - /** - * @link https://github.com/unicode-org/cldr-json/blob/45.0.0/cldr-json/cldr-core/supplemental/currencyData.json - */ - private const FRACTIONS = - [ - 'ADP' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'AFN' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'ALL' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'AMD' => [ - '_rounding' => '0', - '_digits' => '2', - '_cashRounding' => '0', - '_cashDigits' => '0', - ], - 'BHD' => [ - '_rounding' => '0', - '_digits' => '3', - ], - 'BIF' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'BYN' => [ - '_rounding' => '0', - '_digits' => '2', - ], - 'BYR' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'CAD' => [ - '_rounding' => '0', - '_digits' => '2', - '_cashRounding' => '5', - ], - 'CHF' => [ - '_rounding' => '0', - '_digits' => '2', - '_cashRounding' => '5', - ], - 'CLF' => [ - '_rounding' => '0', - '_digits' => '4', - ], - 'CLP' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'COP' => [ - '_rounding' => '0', - '_digits' => '2', - '_cashRounding' => '0', - '_cashDigits' => '0', - ], - 'CRC' => [ - '_rounding' => '0', - '_digits' => '2', - '_cashRounding' => '0', - '_cashDigits' => '0', - ], - 'CZK' => [ - '_rounding' => '0', - '_digits' => '2', - '_cashRounding' => '0', - '_cashDigits' => '0', - ], - 'DEFAULT' => [ - '_rounding' => '0', - '_digits' => '2', - ], - 'DJF' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'DKK' => [ - '_rounding' => '0', - '_digits' => '2', - '_cashRounding' => '50', - ], - 'ESP' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'GNF' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'GYD' => [ - '_rounding' => '0', - '_digits' => '2', - '_cashRounding' => '0', - '_cashDigits' => '0', - ], - 'HUF' => [ - '_rounding' => '0', - '_digits' => '2', - '_cashRounding' => '0', - '_cashDigits' => '0', - ], - 'IDR' => [ - '_rounding' => '0', - '_digits' => '2', - '_cashRounding' => '0', - '_cashDigits' => '0', - ], - 'IQD' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'IRR' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'ISK' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'ITL' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'JOD' => [ - '_rounding' => '0', - '_digits' => '3', - ], - 'JPY' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'KMF' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'KPW' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'KRW' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'KWD' => [ - '_rounding' => '0', - '_digits' => '3', - ], - 'LAK' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'LBP' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'LUF' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'LYD' => [ - '_rounding' => '0', - '_digits' => '3', - ], - 'MGA' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'MGF' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'MMK' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'MNT' => [ - '_rounding' => '0', - '_digits' => '2', - '_cashRounding' => '0', - '_cashDigits' => '0', - ], - 'MRO' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'MUR' => [ - '_rounding' => '0', - '_digits' => '2', - '_cashRounding' => '0', - '_cashDigits' => '0', - ], - 'NOK' => [ - '_rounding' => '0', - '_digits' => '2', - '_cashRounding' => '0', - '_cashDigits' => '0', - ], - 'OMR' => [ - '_rounding' => '0', - '_digits' => '3', - ], - 'PKR' => [ - '_rounding' => '0', - '_digits' => '2', - '_cashRounding' => '0', - '_cashDigits' => '0', - ], - 'PYG' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'RSD' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'RWF' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'SEK' => [ - '_rounding' => '0', - '_digits' => '2', - '_cashRounding' => '0', - '_cashDigits' => '0', - ], - 'SLE' => [ - '_rounding' => '0', - '_digits' => '2', - ], - 'SLL' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'SOS' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'STD' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'SYP' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'TMM' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'TND' => [ - '_rounding' => '0', - '_digits' => '3', - ], - 'TRL' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'TWD' => [ - '_rounding' => '0', - '_digits' => '2', - '_cashRounding' => '0', - '_cashDigits' => '0', - ], - 'TZS' => [ - '_rounding' => '0', - '_digits' => '2', - '_cashRounding' => '0', - '_cashDigits' => '0', - ], - 'UGX' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'UYI' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'UYW' => [ - '_rounding' => '0', - '_digits' => '4', - ], - 'UZS' => [ - '_rounding' => '0', - '_digits' => '2', - '_cashRounding' => '0', - '_cashDigits' => '0', - ], - 'VEF' => [ - '_rounding' => '0', - '_digits' => '2', - '_cashRounding' => '0', - '_cashDigits' => '0', - ], - 'VND' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'VUV' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'XAF' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'XOF' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'XPF' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'YER' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'ZMK' => [ - '_rounding' => '0', - '_digits' => '0', - ], - 'ZWD' => [ - '_rounding' => '0', - '_digits' => '0', - ], - ]; + /** + * @link https://github.com/unicode-org/cldr-json/blob/45.0.0/cldr-json/cldr-core/supplemental/currencyData.json + */ + private const FRACTIONS = + [ + 'ADP' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'AFN' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'ALL' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'AMD' => [ + '_rounding' => '0', + '_digits' => '2', + '_cashRounding' => '0', + '_cashDigits' => '0', + ], + 'BHD' => [ + '_rounding' => '0', + '_digits' => '3', + ], + 'BIF' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'BYN' => [ + '_rounding' => '0', + '_digits' => '2', + ], + 'BYR' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'CAD' => [ + '_rounding' => '0', + '_digits' => '2', + '_cashRounding' => '5', + ], + 'CHF' => [ + '_rounding' => '0', + '_digits' => '2', + '_cashRounding' => '5', + ], + 'CLF' => [ + '_rounding' => '0', + '_digits' => '4', + ], + 'CLP' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'COP' => [ + '_rounding' => '0', + '_digits' => '2', + '_cashRounding' => '0', + '_cashDigits' => '0', + ], + 'CRC' => [ + '_rounding' => '0', + '_digits' => '2', + '_cashRounding' => '0', + '_cashDigits' => '0', + ], + 'CZK' => [ + '_rounding' => '0', + '_digits' => '2', + '_cashRounding' => '0', + '_cashDigits' => '0', + ], + 'DEFAULT' => [ + '_rounding' => '0', + '_digits' => '2', + ], + 'DJF' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'DKK' => [ + '_rounding' => '0', + '_digits' => '2', + '_cashRounding' => '50', + ], + 'ESP' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'GNF' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'GYD' => [ + '_rounding' => '0', + '_digits' => '2', + '_cashRounding' => '0', + '_cashDigits' => '0', + ], + 'HUF' => [ + '_rounding' => '0', + '_digits' => '2', + '_cashRounding' => '0', + '_cashDigits' => '0', + ], + 'IDR' => [ + '_rounding' => '0', + '_digits' => '2', + '_cashRounding' => '0', + '_cashDigits' => '0', + ], + 'IQD' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'IRR' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'ISK' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'ITL' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'JOD' => [ + '_rounding' => '0', + '_digits' => '3', + ], + 'JPY' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'KMF' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'KPW' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'KRW' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'KWD' => [ + '_rounding' => '0', + '_digits' => '3', + ], + 'LAK' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'LBP' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'LUF' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'LYD' => [ + '_rounding' => '0', + '_digits' => '3', + ], + 'MGA' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'MGF' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'MMK' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'MNT' => [ + '_rounding' => '0', + '_digits' => '2', + '_cashRounding' => '0', + '_cashDigits' => '0', + ], + 'MRO' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'MUR' => [ + '_rounding' => '0', + '_digits' => '2', + '_cashRounding' => '0', + '_cashDigits' => '0', + ], + 'NOK' => [ + '_rounding' => '0', + '_digits' => '2', + '_cashRounding' => '0', + '_cashDigits' => '0', + ], + 'OMR' => [ + '_rounding' => '0', + '_digits' => '3', + ], + 'PKR' => [ + '_rounding' => '0', + '_digits' => '2', + '_cashRounding' => '0', + '_cashDigits' => '0', + ], + 'PYG' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'RSD' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'RWF' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'SEK' => [ + '_rounding' => '0', + '_digits' => '2', + '_cashRounding' => '0', + '_cashDigits' => '0', + ], + 'SLE' => [ + '_rounding' => '0', + '_digits' => '2', + ], + 'SLL' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'SOS' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'STD' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'SYP' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'TMM' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'TND' => [ + '_rounding' => '0', + '_digits' => '3', + ], + 'TRL' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'TWD' => [ + '_rounding' => '0', + '_digits' => '2', + '_cashRounding' => '0', + '_cashDigits' => '0', + ], + 'TZS' => [ + '_rounding' => '0', + '_digits' => '2', + '_cashRounding' => '0', + '_cashDigits' => '0', + ], + 'UGX' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'UYI' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'UYW' => [ + '_rounding' => '0', + '_digits' => '4', + ], + 'UZS' => [ + '_rounding' => '0', + '_digits' => '2', + '_cashRounding' => '0', + '_cashDigits' => '0', + ], + 'VEF' => [ + '_rounding' => '0', + '_digits' => '2', + '_cashRounding' => '0', + '_cashDigits' => '0', + ], + 'VND' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'VUV' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'XAF' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'XOF' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'XPF' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'YER' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'ZMK' => [ + '_rounding' => '0', + '_digits' => '0', + ], + 'ZWD' => [ + '_rounding' => '0', + '_digits' => '0', + ], + ]; - private const FRACTIONS_FALLBACK = 'DEFAULT'; + private const FRACTIONS_FALLBACK = 'DEFAULT'; - /** - * Whether a currency code is defined. - * - * @param string $code - * A currency code; for example, EUR. - */ - public static function is_defined(string $code): bool - { - return in_array($code, self::CODES); - } + /** + * Whether a currency code is defined. + * + * @param string $code + * A currency code; for example, EUR. + */ + public static function is_defined(string $code): bool + { + return in_array($code, self::CODES); + } - /** - * @param string $code - * A currency code; for example, EUR. - * - * @throws CurrencyNotDefined - */ - public static function assert_is_defined(string $code): void - { - self::is_defined($code) - or throw new CurrencyNotDefined($code); - } + /** + * @param string $code + * A currency code; for example, EUR. + * + * @throws CurrencyNotDefined + */ + public static function assert_is_defined(string $code): void + { + self::is_defined($code) + or throw new CurrencyNotDefined($code); + } - /** - * Returns a {@see CurrencyCode} of the specified code. - * - * @param string $code - * A currency code; for example, EUR. - * - * @throws CurrencyNotDefined - */ - public static function of(string $code): self - { - static $instances; + /** + * Returns a {@see CurrencyCode} of the specified code. + * + * @param string $code + * A currency code; for example, EUR. + * + * @throws CurrencyNotDefined + */ + public static function of(string $code): self + { + static $instances; - self::assert_is_defined($code); + self::assert_is_defined($code); - return $instances[$code] ??= new self($code, self::fraction_for($code)); - } + return $instances[$code] ??= new self($code, self::fraction_for($code)); + } - /** - * Returns the {@see Fraction} for the specified currency code. - * - * @param string $code - * * A currency code; for example, EUR. - */ - private static function fraction_for(string $code): Fraction - { - static $default_fraction; + /** + * Returns the {@see Fraction} for the specified currency code. + * + * @param string $code + * * A currency code; for example, EUR. + */ + private static function fraction_for(string $code): Fraction + { + static $default_fraction; - $data = self::FRACTIONS[$code] ?? null; + $data = self::FRACTIONS[$code] ?? null; - if (!$data) - { - return $default_fraction ??= self::fraction_for(self::FRACTIONS_FALLBACK); - } + if (!$data) { + return $default_fraction ??= self::fraction_for(self::FRACTIONS_FALLBACK); + } - return Fraction::from($data); - } + return Fraction::from($data); + } - /** - * @param string $code - * A currency code; for example, EUR. - */ - private function __construct( - public readonly string $code, - public readonly Fraction $fraction, - ) { - } + /** + * @param string $code + * A currency code; for example, EUR. + */ + private function __construct( + public readonly string $code, + public readonly Fraction $fraction, + ) { + } - /** - * Returns the {@see $code} of the currency. - */ - public function __toString() : string - { - return $this->code; - } + /** + * Returns the {@see $code} of the currency. + */ + public function __toString(): string + { + return $this->code; + } - public function __serialize(): array - { - return [ 'code' => $this->code ]; - } + public function __serialize(): array + { + return [ 'code' => $this->code ]; + } - /** - * @param array{ code: string } $data - */ - public function __unserialize(array $data): void - { - $this->code = $data['code']; - $this->fraction = self::fraction_for($this->code); - } + /** + * @param array{ code: string } $data + */ + public function __unserialize(array $data): void + { + $this->code = $data['code']; + $this->fraction = self::fraction_for($this->code); + } - /** - * Returns a localized currency. - */ - public function localized(Locale $locale): LocalizedCurrency - { - return new LocalizedCurrency($this, $locale); - } + /** + * Returns a localized currency. + */ + public function localized(Locale $locale): LocalizedCurrency + { + return new LocalizedCurrency($this, $locale); + } } diff --git a/lib/CurrencyFormatter.php b/lib/CurrencyFormatter.php index 865f9d8..b84424a 100644 --- a/lib/CurrencyFormatter.php +++ b/lib/CurrencyFormatter.php @@ -3,6 +3,7 @@ namespace ICanBoogie\CLDR; use ICanBoogie\CLDR\Numbers\Symbols; + use function str_replace; /** @@ -12,36 +13,36 @@ */ final class CurrencyFormatter implements Formatter, Localizable { - public const DEFAULT_CURRENCY_SYMBOL = '¤'; + public const DEFAULT_CURRENCY_SYMBOL = '¤'; - public function __construct( - private readonly NumberFormatter $number_formatter, - ) { - } + public function __construct( + private readonly NumberFormatter $number_formatter, + ) { + } - /** - * Formats a number with the specified pattern. - * - * @param float|int|numeric-string $number - * The number to format. - * @param string|NumberPattern $pattern - * The pattern used to format the number. - */ - public function format( - float|int|string $number, - NumberPattern|string $pattern, - Symbols $symbols = null, - string $currencySymbol = self::DEFAULT_CURRENCY_SYMBOL - ): string { - return str_replace( - self::DEFAULT_CURRENCY_SYMBOL, - $currencySymbol, - $this->number_formatter->format($number, $pattern, $symbols) - ); - } + /** + * Formats a number with the specified pattern. + * + * @param float|int|numeric-string $number + * The number to format. + * @param string|NumberPattern $pattern + * The pattern used to format the number. + */ + public function format( + float|int|string $number, + NumberPattern|string $pattern, + Symbols $symbols = null, + string $currencySymbol = self::DEFAULT_CURRENCY_SYMBOL + ): string { + return str_replace( + self::DEFAULT_CURRENCY_SYMBOL, + $currencySymbol, + $this->number_formatter->format($number, $pattern, $symbols) + ); + } - public function localized(Locale $locale): LocalizedCurrencyFormatter - { - return new LocalizedCurrencyFormatter($this, $locale); - } + public function localized(Locale $locale): LocalizedCurrencyFormatter + { + return new LocalizedCurrencyFormatter($this, $locale); + } } diff --git a/lib/CurrencyNotDefined.php b/lib/CurrencyNotDefined.php index 03c396c..2e09e0c 100644 --- a/lib/CurrencyNotDefined.php +++ b/lib/CurrencyNotDefined.php @@ -10,17 +10,17 @@ */ class CurrencyNotDefined extends InvalidArgumentException implements Exception { - /** - * @param string $currency_code - * A currency code; for example, EUR. - */ - public function __construct( - public readonly string $currency_code, - string $message = null, - Throwable $previous = null - ) { - $message ??= "Currency code is not defined: $currency_code"; + /** + * @param string $currency_code + * A currency code; for example, EUR. + */ + public function __construct( + public readonly string $currency_code, + string $message = null, + Throwable $previous = null + ) { + $message ??= "Currency code is not defined: $currency_code"; - parent::__construct($message, previous: $previous); - } + parent::__construct($message, previous: $previous); + } } diff --git a/lib/DateFormatter.php b/lib/DateFormatter.php index 9a8a07e..93ca539 100644 --- a/lib/DateFormatter.php +++ b/lib/DateFormatter.php @@ -29,16 +29,16 @@ */ final class DateFormatter extends DateTimeFormatter { - /** - * Resolves length defined in `dateFormats` into a pattern. - */ - protected function resolve_pattern( - string|DateTimeFormatLength|DateTimeFormatId $pattern_or_length_or_id - ): string { - if ($pattern_or_length_or_id instanceof DateTimeFormatLength) { - return $this->calendar['dateFormats'][$pattern_or_length_or_id->value]; - } + /** + * Resolves length defined in `dateFormats` into a pattern. + */ + protected function resolve_pattern( + string|DateTimeFormatLength|DateTimeFormatId $pattern_or_length_or_id + ): string { + if ($pattern_or_length_or_id instanceof DateTimeFormatLength) { + return $this->calendar['dateFormats'][$pattern_or_length_or_id->value]; + } - return parent::resolve_pattern($pattern_or_length_or_id); - } + return parent::resolve_pattern($pattern_or_length_or_id); + } } diff --git a/lib/DateTimeAccessor.php b/lib/DateTimeAccessor.php index 21c5221..35a7e0d 100644 --- a/lib/DateTimeAccessor.php +++ b/lib/DateTimeAccessor.php @@ -22,40 +22,40 @@ */ class DateTimeAccessor { - public function __construct( - private readonly DateTimeInterface $datetime - ) { - } + public function __construct( + private readonly DateTimeInterface $datetime + ) { + } - /** - * @return mixed - */ - public function __get(string $property) - { - $dt = $this->datetime; + /** + * @return mixed + */ + public function __get(string $property) + { + $dt = $this->datetime; - return match ($property) { - 'year' => (int)$dt->format('Y'), - 'month' => (int)$dt->format('m'), - 'day' => (int)$dt->format('d'), - 'hour' => (int)$dt->format('H'), - 'minute' => (int)$dt->format('i'), - 'second' => (int)$dt->format('s'), - 'quarter' => (int)floor(($this->month - 1) / 3) + 1, - 'week' => (int)$dt->format('W'), - 'year_day' => (int)$dt->format('z') + 1, - 'weekday' => (int)$dt->format('w') ?: 7, - default => throw new LogicException("Undefined property: $property"), - }; - } + return match ($property) { + 'year' => (int)$dt->format('Y'), + 'month' => (int)$dt->format('m'), + 'day' => (int)$dt->format('d'), + 'hour' => (int)$dt->format('H'), + 'minute' => (int)$dt->format('i'), + 'second' => (int)$dt->format('s'), + 'quarter' => (int)floor(($this->month - 1) / 3) + 1, + 'week' => (int)$dt->format('W'), + 'year_day' => (int)$dt->format('z') + 1, + 'weekday' => (int)$dt->format('w') ?: 7, + default => throw new LogicException("Undefined property: $property"), + }; + } - /** - * @param mixed[] $params - * - * @return mixed - */ - public function __call(string $name, array $params) - { - return $this->datetime->$name(...$params); - } + /** + * @param mixed[] $params + * + * @return mixed + */ + public function __call(string $name, array $params) + { + return $this->datetime->$name(...$params); + } } diff --git a/lib/DateTimeFormatId.php b/lib/DateTimeFormatId.php index 6b11f4d..4018d9a 100644 --- a/lib/DateTimeFormatId.php +++ b/lib/DateTimeFormatId.php @@ -8,13 +8,13 @@ */ final class DateTimeFormatId { - public static function from(string $id): self - { - return new self($id); - } + public static function from(string $id): self + { + return new self($id); + } - private function __construct( - public readonly string $id, - ) { - } + private function __construct( + public readonly string $id, + ) { + } } diff --git a/lib/DateTimeFormatLength.php b/lib/DateTimeFormatLength.php index 4913190..ca7be1a 100644 --- a/lib/DateTimeFormatLength.php +++ b/lib/DateTimeFormatLength.php @@ -7,8 +7,8 @@ */ enum DateTimeFormatLength: string { - case FULL = 'full'; - case LONG = 'long'; - case MEDIUM = 'medium'; - case SHORT = 'short'; + case FULL = 'full'; + case LONG = 'long'; + case MEDIUM = 'medium'; + case SHORT = 'short'; } diff --git a/lib/DateTimeFormatter.php b/lib/DateTimeFormatter.php index c7db787..e80b4ee 100644 --- a/lib/DateTimeFormatter.php +++ b/lib/DateTimeFormatter.php @@ -27,719 +27,718 @@ */ class DateTimeFormatter implements Formatter { - /** - * Pattern characters mapping to the corresponding translator methods. - * - * @var array - * Where _key_ is a pattern character and _value_ its formatter. - */ - private static array $formatters = [ - - 'G' => 'format_era', - 'y' => 'format_year', -// 'Y' => Year (in "Week of Year" based calendars). -// 'u' => Extended year. - 'Q' => 'format_quarter', - 'q' => 'format_standalone_quarter', - 'M' => 'format_month', - 'L' => 'format_standalone_month', -// 'l' => Special symbol for Chinese leap month, used in combination with M. Only used with the Chinese calendar. - 'w' => 'format_week_of_year', - 'W' => 'format_week_of_month', - 'd' => 'format_day_of_month', - 'D' => 'format_day_of_year', - 'F' => 'format_day_of_week_in_month', - - 'h' => 'format_hour12', - 'H' => 'format_hour24', - 'm' => 'format_minutes', - 's' => 'format_seconds', - 'E' => 'format_day_in_week', - 'c' => 'format_day_in_week_stand_alone', - 'e' => 'format_day_in_week_local', - 'a' => 'format_period', - 'k' => 'format_hour_in_day', - 'K' => 'format_hour_in_period', - 'z' => 'format_timezone_non_location', - 'Z' => 'format_timezone_basic', - 'v' => 'format_timezone_non_location' - - ]; - - /** - * Parses the datetime format pattern. - * - * @return array - * Where _value_ is either a literal or an array where `0` is a formatter method and `1` a length. - */ - private static function tokenize(string $pattern): array - { - static $formats = []; - - if (isset($formats[$pattern])) { - return $formats[$pattern]; - } - - $tokens = []; - $is_literal = false; - $literal = ''; - - for ($i = 0, $n = strlen($pattern); $i < $n; ++$i) { - $c = $pattern[$i]; - - if ($c === "'") { - if ($i < $n - 1 && $pattern[$i + 1] === "'") { - $tokens[] = "'"; - $i++; - } else { - if ($is_literal) { - $tokens[] = $literal; - $literal = ''; - $is_literal = false; - } else { - $is_literal = true; - $literal = ''; - } - } - } else { - if ($is_literal) { - $literal .= $c; - } else { - for ($j = $i + 1; $j < $n; ++$j) { - if ($pattern[$j] !== $c) { - break; - } - } - - $l = $j - $i; - $p = str_repeat($c, $l); - - $tokens[] = isset(self::$formatters[$c]) ? [ self::$formatters[$c], $l ] : $p; - - $i = $j - 1; - } - } - } - - if ($literal) { - $tokens[] = $literal; - } - - return $formats[$pattern] = $tokens; - } - - /** - * Pad a numeric value with zero on its left. - */ - static private function numeric_pad(int $value, int $length = 2): string - { - return str_pad((string)$value, $length, '0', STR_PAD_LEFT); - } - - public function __construct( - public readonly Calendar $calendar - ) { - } - - /** - * Formats a date according to a pattern. - * - * @param DateTimeInterface|string|int $datetime - * The datetime to format. - * - * @return string - * The formatted date time. - * - * @throws \Exception - * - * @see https://www.unicode.org/reports/tr35/tr35-72/tr35-dates.html#26-element-datetimeformats - * - * @uses format_era - * @uses format_year - * @uses format_standalone_quarter - * @uses format_standalone_month - * @uses format_week_of_year - * @uses format_week_of_month - * @uses format_day_of_month - * @uses format_day_of_year - * @uses format_day_of_week_in_month - * @uses format_day_in_week - * @uses format_day_in_week_stand_alone - * @uses format_day_in_week_local - * @uses format_period - * @uses format_hour12 - * @uses format_hour24 - * @uses format_hour_in_period - * @uses format_hour_in_day - * @uses format_minutes - * @uses format_seconds - * @uses format_timezone_basic - * @uses format_timezone_non_location - * - */ - public function format( - $datetime, - string|DateTimeFormatLength|DateTimeFormatId $pattern_or_length_or_id - ): string { - $datetime = $this->ensure_datetime($datetime); - $datetime = new DateTimeAccessor($datetime); - $pattern = $this->resolve_pattern($pattern_or_length_or_id); - $tokens = self::tokenize($pattern); - - $rc = ''; - - foreach ($tokens as $token) { - if (is_array($token)) // a callback: method name, repeating chars - { - $token = $this->{$token[0]}($datetime, $token[1]); - } - - $rc .= $token; - } - - return $rc; - } - - /** - * Resolves the specified pattern, which can be a width, a skeleton or an actual pattern. - */ - protected function resolve_pattern( - string|DateTimeFormatLength|DateTimeFormatId $pattern_or_length_or_id - ): string { - if (is_string($pattern_or_length_or_id) && $pattern_or_length_or_id[0] === ':') { - trigger_error( - "Prefixing date time format ids with ':' is no longer supported, use DateTimeFormatId instead", - E_USER_DEPRECATED - ); - - $pattern_or_length_or_id = DateTimeFormatId::from(substr($pattern_or_length_or_id, 1)); - } - - if ($pattern_or_length_or_id instanceof DateTimeFormatLength) { - $length = $pattern_or_length_or_id->value; - $calendar = $this->calendar; - $datetime_pattern = $calendar['dateTimeFormats-atTime']['standard'][$length] - ?? $calendar['dateTimeFormats'][$length]; - $date_pattern = $calendar['dateFormats'][$length]; - $time_pattern = $calendar['timeFormats'][$length]; - - return strtr($datetime_pattern, [ - '{1}' => $date_pattern, - '{0}' => $time_pattern - ]); - } elseif ($pattern_or_length_or_id instanceof DateTimeFormatId) { - $id = $pattern_or_length_or_id->id; - - return $this->calendar['dateTimeFormats']['availableFormats'][$id] - ?? throw new RuntimeException("Unknown DateTime format id: $id"); - } - - return $pattern_or_length_or_id; - } - - /* - * era (G) - */ - - /** - * Era - Replaced with the Era string for the current date. One to three letters for the - * abbreviated form, four letters for the long form, five for the narrow form. [1..3,4,5] - * @todo How to support multiple Eras?, e.g. Japanese. - */ - private function format_era(DateTimeAccessor $datetime, int $length): string - { - if ($length > 5) { - return ''; - } - - $era = ($datetime->year > 0) ? 1 : 0; - - return match ($length) { - 1, 2, 3 => $this->calendar->abbreviated_eras[$era], - 4 => $this->calendar->wide_eras[$era], - 5 => $this->calendar->narrow_eras[$era], - default => '', - }; - } - - /* - * year (y) - */ - - /** - * Year. Normally the length specifies the padding, but for two letters it also specifies the - * maximum length. [1..n] - */ - private function format_year(DateTimeAccessor $datetime, int $length): string - { - $year = $datetime->year; - - if ($length == 2) { - $year = $year % 100; - } - - return self::numeric_pad($year, $length); - } - - /* - * quarter (Q,q) - */ - - /** - * Quarter - Use one or two "Q" for the numerical quarter, three for the abbreviation, or four - * for the full (wide) name. [1..2,3,4] - * - * @uses Calendar::$abbreviated_quarters - * @uses Calendar::$wide_quarters - */ - private function format_quarter( - DateTimeAccessor $datetime, - int $length, - string $abbreviated = 'abbreviated_quarters', - string $wide = 'wide_quarters' - ): string { - if ($length > 4) { - return ''; - } - - $quarter = $datetime->quarter; - - return match ($length) { - 1 => (string)$quarter, - 2 => self::numeric_pad($quarter), - 3 => $this->calendar->$abbreviated[$quarter], - 4 => $this->calendar->$wide[$quarter], - default => '', - }; - } - - /** - * Stand-Alone Quarter - Use one or two "q" for the numerical quarter, three for the - * abbreviation, or four for the full (wide) name. [1..2,3,4] - * - * @uses Calendar::$standalone_abbreviated_quarters - * @uses Calendar::$standalone_wide_quarters - */ - private function format_standalone_quarter(DateTimeAccessor $datetime, int $length): string - { - return $this->format_quarter( - datetime: $datetime, - length: $length, - abbreviated: 'standalone_abbreviated_quarters', - wide: 'standalone_wide_quarters', - ); - } - - /* - * month (M|L) - */ - - /** - * Month - Use one or two "M" for the numerical month, three for the abbreviation, four for - * the full name, or five for the narrow name. [1..2,3,4,5] - * - * @uses Calendar::$abbreviated_months - * @uses Calendar::$wide_months - * @uses Calendar::$narrow_months - */ - private function format_month( - DateTimeAccessor $datetime, - int $length, - string $abbreviated = 'abbreviated_months', - string $wide = 'wide_months', - string $narrow = 'narrow_months' - ): string { - if ($length > 5) { - return ''; - } - - $month = $datetime->month; - - switch ($length) { - case 1: - return (string)$month; - case 2: - return self::numeric_pad($month); - case 3: - $names = $this->calendar->$abbreviated; - return $names[$month]; - case 4: - $names = $this->calendar->$wide; - return $names[$month]; - case 5: - $names = $this->calendar->$narrow; - return $names[$month]; - } - - return ''; // @codeCoverageIgnore - } - - /** - * Stand-Alone Month - Use one or two "L" for the numerical month, three for the abbreviation, - * or four for the full (wide) name, or 5 for the narrow name. [1..2,3,4,5] - * - * @uses Calendar::$standalone_abbreviated_months - * @uses Calendar::$standalone_wide_months - * @uses Calendar::$standalone_narrow_months - */ - private function format_standalone_month(DateTimeAccessor $datetime, int $length): string - { - return $this->format_month( - datetime: $datetime, - length: $length, - abbreviated: 'standalone_abbreviated_months', - wide: 'standalone_wide_months', - narrow: 'standalone_narrow_months' - ); - } - - /* - * week (w|W) - */ - - /** - * Week of Year. [1..2] - */ - private function format_week_of_year(DateTimeAccessor $datetime, int $length): string - { - if ($length > 2) { - return ''; - } - - $week = $datetime->week; - - return $length == 1 ? (string)$week : self::numeric_pad($week); - } - - /** - * Week of Month. [1] - */ - private function format_week_of_month(DateTimeAccessor $datetime, int $length): string - { - if ($length > 1) { - return ''; - } - - return (string)ceil($datetime->day / 7) ?: "0"; - } - - /* - * day (d,D,F) - */ - - /** - * Date - Day of the month. [1..2] - */ - private function format_day_of_month(DateTimeAccessor $datetime, int $length): string - { - if ($length > 2) { - return ''; - } - - $day = $datetime->day; - - if ($length == 1) { - return (string)$day; - } - - return self::numeric_pad($day); - } - - /** - * Day of year. [1..3] - */ - private function format_day_of_year(DateTimeAccessor $datetime, int $length): string - { - $day = $datetime->year_day; - - if ($length > 3) { - return ''; - } - - return self::numeric_pad($day, $length); - } - - /** - * Day of Week in Month. The example is for the 2nd Wed in July. [1] - */ - private function format_day_of_week_in_month(DateTimeAccessor $datetime, int $length): string - { - if ($length > 1) { - return ''; - } - - return (string)floor(($datetime->day + 6) / 7); - } - - /* - * weekday (E,e,c) - */ - - /** - * Day of week - Use one through three letters for the short day, or four for the full name, - * five for the narrow name, or six for the short name. [1..3,4,5,6] - */ - private function format_day_in_week(DateTimeAccessor $datetime, int $length): string - { - if ($length > 6) { - return ''; - } - - $day = $datetime->weekday; - $code = $this->resolve_day_code($day); - $calendar = $this->calendar; - - return match ($length) { - 1, 2, 3 => $calendar->abbreviated_days[$code], - 4 => $calendar->wide_days[$code], - 5 => $calendar->narrow_days[$code], - 6 => $calendar->short_days[$code], - default => '', - }; - // @codeCoverageIgnore - } - - /** - * Stand-Alone local day of week - Use one letter for the local numeric value (same as 'e'), - * three for the abbreviated day name, four for the full (wide) name, five for the narrow name, - * or six for the short name. - * - * @uses Calendar::$standalone_abbreviated_days - * @uses Calendar::$standalone_wide_days - * @uses Calendar::$standalone_narrow_days - * @uses Calendar::$standalone_short_days - */ - private function format_day_in_week_stand_alone(DateTimeAccessor $datetime, int $length): string - { - static $mapping = [ - - 3 => 'abbreviated', - 4 => 'wide', - 5 => 'narrow', - 6 => 'short', - - ]; - - if ($length == 2 || $length > 6) { - return ''; - } - - $day = $datetime->weekday; - - if ($length == 1) { - return (string)$day; - } - - $code = $this->resolve_day_code($day); - - return $this->calendar->{'standalone_' . $mapping[$length] . '_days'}[$code]; - } - - /** - * Local day of week. Same as E except adds a numeric value that will depend on the local - * starting day of the week, using one or two letters. For this example, Monday is the - * first day of the week. - */ - private function format_day_in_week_local(DateTimeAccessor $datetime, int $length): string - { - if ($length < 3) { - return (string)$datetime->weekday; - } - - return $this->format_day_in_week($datetime, $length); - } - - /* - * period (a) - */ - - /** - * AM or PM. [1] - * - * @return string AM or PM designator - */ - private function format_period(DateTimeAccessor $datetime): string - { - return $this->calendar['dayPeriods']['format']['abbreviated'][$datetime->hour < 12 ? 'am' : 'pm']; - } - - /* - * hour (h,H,K,k) - */ - - /** - * Hour [1-12]. When used in skeleton data or in a skeleton passed in an API for flexible data - * pattern generation, it should match the 12-hour-cycle format preferred by the locale - * (h or K); it should not match a 24-hour-cycle format (H or k). Use hh for zero - * padding. [1..2] - */ - private function format_hour12(DateTimeAccessor $datetime, int $length): string - { - if ($length > 2) { - return ''; - } - - $hour = $datetime->hour; - $hour = ($hour == 12) ? 12 : $hour % 12; - - if ($length == 1) { - return (string)$hour; - } - - return self::numeric_pad($hour); - } - - /** - * Hour [0-23]. When used in skeleton data or in a skeleton passed in an API for flexible - * data pattern generation, it should match the 24-hour-cycle format preferred by the - * locale (H or k); it should not match a 12-hour-cycle format (h or K). Use HH for zero - * padding. [1..2] - */ - private function format_hour24(DateTimeAccessor $datetime, int $length): string - { - if ($length > 2) { - return ''; - } - - $hour = $datetime->hour; - - if ($length == 1) { - return (string)$hour; - } - - return self::numeric_pad($hour); - } - - /** - * Hour [0-11]. When used in a skeleton, only matches K or h, see above. Use KK for zero - * padding. [1..2] - */ - private function format_hour_in_period(DateTimeAccessor $datetime, int $length): string - { - if ($length > 2) { - return ''; - } - - $hour = $datetime->hour % 12; - - if ($length == 1) { - return (string)$hour; - } - - return self::numeric_pad($hour); - } - - /** - * Hour [1-24]. When used in a skeleton, only matches k or H, see above. Use kk for zero - * padding. [1..2] - */ - private function format_hour_in_day(DateTimeAccessor $datetime, int $length): string - { - if ($length > 2) { - return ''; - } - - $hour = $datetime->hour ?: 24; - - if ($length == 1) { - return (string)$hour; - } - - return self::numeric_pad($hour); - } - - /* - * minute (m) - */ - - /** - * Minute. Use one or two "m" for zero padding. - */ - private function format_minutes(DateTimeAccessor $datetime, int $length): string - { - return $this->format_minutes_or_seconds($datetime, $length, 'minute'); - } - - /* - * second - */ - - /** - * Second. Use one or two "s" for zero padding. - */ - private function format_seconds(DateTimeAccessor $datetime, int $length): string - { - return $this->format_minutes_or_seconds($datetime, $length, 'second'); - } - - /** - * Minute. Use one or two "m" for zero padding. - */ - private function format_minutes_or_seconds(DateTimeAccessor $datetime, int $length, string $which): string - { - if ($length > 2) { - return ''; - } - - $value = $datetime->$which; - - if ($length == 1) { - return $value; - } - - return self::numeric_pad($value); - } - - /* - * zone (z,Z,v) - */ - - /** - * The ISO8601 basic format. - */ - private function format_timezone_basic(DateTimeAccessor $datetime): string - { - return $datetime->format('O'); - } - - /** - * The specific non-location format. - */ - private function format_timezone_non_location(DateTimeAccessor $datetime): string - { - $str = $datetime->format('T'); - - return $str === 'Z' ? 'UTC' : $str; - } - - /** - * @param DateTimeInterface|string|int $datetime - * - * @throws \Exception - */ - private function ensure_datetime($datetime): DateTimeInterface - { - if ($datetime instanceof DateTimeInterface) { - return $datetime; - } - - return new DateTimeImmutable(is_numeric($datetime) ? "@$datetime" : (string)$datetime); - } - - private function resolve_day_code(int $day): string - { - static $translate = [ - - 1 => 'mon', - 2 => 'tue', - 3 => 'wed', - 4 => 'thu', - 5 => 'fri', - 6 => 'sat', - 7 => 'sun' - - ]; - - return $translate[$day]; - } + /** + * Pattern characters mapping to the corresponding translator methods. + * + * @var array + * Where _key_ is a pattern character and _value_ its formatter. + */ + private static array $formatters = [ + + 'G' => 'format_era', + 'y' => 'format_year', +// 'Y' => Year (in "Week of Year" based calendars). +// 'u' => Extended year. + 'Q' => 'format_quarter', + 'q' => 'format_standalone_quarter', + 'M' => 'format_month', + 'L' => 'format_standalone_month', +// 'l' => Special symbol for Chinese leap month, used in combination with M. Only used with the Chinese calendar. + 'w' => 'format_week_of_year', + 'W' => 'format_week_of_month', + 'd' => 'format_day_of_month', + 'D' => 'format_day_of_year', + 'F' => 'format_day_of_week_in_month', + + 'h' => 'format_hour12', + 'H' => 'format_hour24', + 'm' => 'format_minutes', + 's' => 'format_seconds', + 'E' => 'format_day_in_week', + 'c' => 'format_day_in_week_stand_alone', + 'e' => 'format_day_in_week_local', + 'a' => 'format_period', + 'k' => 'format_hour_in_day', + 'K' => 'format_hour_in_period', + 'z' => 'format_timezone_non_location', + 'Z' => 'format_timezone_basic', + 'v' => 'format_timezone_non_location' + + ]; + + /** + * Parses the datetime format pattern. + * + * @return array + * Where _value_ is either a literal or an array where `0` is a formatter method and `1` a length. + */ + private static function tokenize(string $pattern): array + { + static $formats = []; + + if (isset($formats[$pattern])) { + return $formats[$pattern]; + } + + $tokens = []; + $is_literal = false; + $literal = ''; + + for ($i = 0, $n = strlen($pattern); $i < $n; ++$i) { + $c = $pattern[$i]; + + if ($c === "'") { + if ($i < $n - 1 && $pattern[$i + 1] === "'") { + $tokens[] = "'"; + $i++; + } else { + if ($is_literal) { + $tokens[] = $literal; + $literal = ''; + $is_literal = false; + } else { + $is_literal = true; + $literal = ''; + } + } + } else { + if ($is_literal) { + $literal .= $c; + } else { + for ($j = $i + 1; $j < $n; ++$j) { + if ($pattern[$j] !== $c) { + break; + } + } + + $l = $j - $i; + $p = str_repeat($c, $l); + + $tokens[] = isset(self::$formatters[$c]) ? [ self::$formatters[$c], $l ] : $p; + + $i = $j - 1; + } + } + } + + if ($literal) { + $tokens[] = $literal; + } + + return $formats[$pattern] = $tokens; + } + + /** + * Pad a numeric value with zero on its left. + */ + private static function numeric_pad(int $value, int $length = 2): string + { + return str_pad((string)$value, $length, '0', STR_PAD_LEFT); + } + + public function __construct( + public readonly Calendar $calendar + ) { + } + + /** + * Formats a date according to a pattern. + * + * @param DateTimeInterface|string|int $datetime + * The datetime to format. + * + * @return string + * The formatted date time. + * + * @throws \Exception + * + * @see https://www.unicode.org/reports/tr35/tr35-72/tr35-dates.html#26-element-datetimeformats + * + * @uses format_era + * @uses format_year + * @uses format_standalone_quarter + * @uses format_standalone_month + * @uses format_week_of_year + * @uses format_week_of_month + * @uses format_day_of_month + * @uses format_day_of_year + * @uses format_day_of_week_in_month + * @uses format_day_in_week + * @uses format_day_in_week_stand_alone + * @uses format_day_in_week_local + * @uses format_period + * @uses format_hour12 + * @uses format_hour24 + * @uses format_hour_in_period + * @uses format_hour_in_day + * @uses format_minutes + * @uses format_seconds + * @uses format_timezone_basic + * @uses format_timezone_non_location + * + */ + public function format( + $datetime, + string|DateTimeFormatLength|DateTimeFormatId $pattern_or_length_or_id + ): string { + $datetime = $this->ensure_datetime($datetime); + $datetime = new DateTimeAccessor($datetime); + $pattern = $this->resolve_pattern($pattern_or_length_or_id); + $tokens = self::tokenize($pattern); + + $rc = ''; + + foreach ($tokens as $token) { + if (is_array($token)) { // a callback: method name, repeating chars + $token = $this->{$token[0]}($datetime, $token[1]); + } + + $rc .= $token; + } + + return $rc; + } + + /** + * Resolves the specified pattern, which can be a width, a skeleton or an actual pattern. + */ + protected function resolve_pattern( + string|DateTimeFormatLength|DateTimeFormatId $pattern_or_length_or_id + ): string { + if (is_string($pattern_or_length_or_id) && $pattern_or_length_or_id[0] === ':') { + trigger_error( + "Prefixing date time format ids with ':' is no longer supported, use DateTimeFormatId instead", + E_USER_DEPRECATED + ); + + $pattern_or_length_or_id = DateTimeFormatId::from(substr($pattern_or_length_or_id, 1)); + } + + if ($pattern_or_length_or_id instanceof DateTimeFormatLength) { + $length = $pattern_or_length_or_id->value; + $calendar = $this->calendar; + $datetime_pattern = $calendar['dateTimeFormats-atTime']['standard'][$length] + ?? $calendar['dateTimeFormats'][$length]; + $date_pattern = $calendar['dateFormats'][$length]; + $time_pattern = $calendar['timeFormats'][$length]; + + return strtr($datetime_pattern, [ + '{1}' => $date_pattern, + '{0}' => $time_pattern + ]); + } elseif ($pattern_or_length_or_id instanceof DateTimeFormatId) { + $id = $pattern_or_length_or_id->id; + + return $this->calendar['dateTimeFormats']['availableFormats'][$id] + ?? throw new RuntimeException("Unknown DateTime format id: $id"); + } + + return $pattern_or_length_or_id; + } + + /* + * era (G) + */ + + /** + * Era - Replaced with the Era string for the current date. One to three letters for the + * abbreviated form, four letters for the long form, five for the narrow form. [1..3,4,5] + * @todo How to support multiple Eras?, e.g. Japanese. + */ + private function format_era(DateTimeAccessor $datetime, int $length): string + { + if ($length > 5) { + return ''; + } + + $era = ($datetime->year > 0) ? 1 : 0; + + return match ($length) { + 1, 2, 3 => $this->calendar->abbreviated_eras[$era], + 4 => $this->calendar->wide_eras[$era], + 5 => $this->calendar->narrow_eras[$era], + default => '', + }; + } + + /* + * year (y) + */ + + /** + * Year. Normally the length specifies the padding, but for two letters it also specifies the + * maximum length. [1..n] + */ + private function format_year(DateTimeAccessor $datetime, int $length): string + { + $year = $datetime->year; + + if ($length == 2) { + $year = $year % 100; + } + + return self::numeric_pad($year, $length); + } + + /* + * quarter (Q,q) + */ + + /** + * Quarter - Use one or two "Q" for the numerical quarter, three for the abbreviation, or four + * for the full (wide) name. [1..2,3,4] + * + * @uses Calendar::$abbreviated_quarters + * @uses Calendar::$wide_quarters + */ + private function format_quarter( + DateTimeAccessor $datetime, + int $length, + string $abbreviated = 'abbreviated_quarters', + string $wide = 'wide_quarters' + ): string { + if ($length > 4) { + return ''; + } + + $quarter = $datetime->quarter; + + return match ($length) { + 1 => (string)$quarter, + 2 => self::numeric_pad($quarter), + 3 => $this->calendar->$abbreviated[$quarter], + 4 => $this->calendar->$wide[$quarter], + default => '', + }; + } + + /** + * Stand-Alone Quarter - Use one or two "q" for the numerical quarter, three for the + * abbreviation, or four for the full (wide) name. [1..2,3,4] + * + * @uses Calendar::$standalone_abbreviated_quarters + * @uses Calendar::$standalone_wide_quarters + */ + private function format_standalone_quarter(DateTimeAccessor $datetime, int $length): string + { + return $this->format_quarter( + datetime: $datetime, + length: $length, + abbreviated: 'standalone_abbreviated_quarters', + wide: 'standalone_wide_quarters', + ); + } + + /* + * month (M|L) + */ + + /** + * Month - Use one or two "M" for the numerical month, three for the abbreviation, four for + * the full name, or five for the narrow name. [1..2,3,4,5] + * + * @uses Calendar::$abbreviated_months + * @uses Calendar::$wide_months + * @uses Calendar::$narrow_months + */ + private function format_month( + DateTimeAccessor $datetime, + int $length, + string $abbreviated = 'abbreviated_months', + string $wide = 'wide_months', + string $narrow = 'narrow_months' + ): string { + if ($length > 5) { + return ''; + } + + $month = $datetime->month; + + switch ($length) { + case 1: + return (string)$month; + case 2: + return self::numeric_pad($month); + case 3: + $names = $this->calendar->$abbreviated; + return $names[$month]; + case 4: + $names = $this->calendar->$wide; + return $names[$month]; + case 5: + $names = $this->calendar->$narrow; + return $names[$month]; + } + + return ''; // @codeCoverageIgnore + } + + /** + * Stand-Alone Month - Use one or two "L" for the numerical month, three for the abbreviation, + * or four for the full (wide) name, or 5 for the narrow name. [1..2,3,4,5] + * + * @uses Calendar::$standalone_abbreviated_months + * @uses Calendar::$standalone_wide_months + * @uses Calendar::$standalone_narrow_months + */ + private function format_standalone_month(DateTimeAccessor $datetime, int $length): string + { + return $this->format_month( + datetime: $datetime, + length: $length, + abbreviated: 'standalone_abbreviated_months', + wide: 'standalone_wide_months', + narrow: 'standalone_narrow_months' + ); + } + + /* + * week (w|W) + */ + + /** + * Week of Year. [1..2] + */ + private function format_week_of_year(DateTimeAccessor $datetime, int $length): string + { + if ($length > 2) { + return ''; + } + + $week = $datetime->week; + + return $length == 1 ? (string)$week : self::numeric_pad($week); + } + + /** + * Week of Month. [1] + */ + private function format_week_of_month(DateTimeAccessor $datetime, int $length): string + { + if ($length > 1) { + return ''; + } + + return (string)ceil($datetime->day / 7) ?: "0"; + } + + /* + * day (d,D,F) + */ + + /** + * Date - Day of the month. [1..2] + */ + private function format_day_of_month(DateTimeAccessor $datetime, int $length): string + { + if ($length > 2) { + return ''; + } + + $day = $datetime->day; + + if ($length == 1) { + return (string)$day; + } + + return self::numeric_pad($day); + } + + /** + * Day of year. [1..3] + */ + private function format_day_of_year(DateTimeAccessor $datetime, int $length): string + { + $day = $datetime->year_day; + + if ($length > 3) { + return ''; + } + + return self::numeric_pad($day, $length); + } + + /** + * Day of Week in Month. The example is for the 2nd Wed in July. [1] + */ + private function format_day_of_week_in_month(DateTimeAccessor $datetime, int $length): string + { + if ($length > 1) { + return ''; + } + + return (string)floor(($datetime->day + 6) / 7); + } + + /* + * weekday (E,e,c) + */ + + /** + * Day of week - Use one through three letters for the short day, or four for the full name, + * five for the narrow name, or six for the short name. [1..3,4,5,6] + */ + private function format_day_in_week(DateTimeAccessor $datetime, int $length): string + { + if ($length > 6) { + return ''; + } + + $day = $datetime->weekday; + $code = $this->resolve_day_code($day); + $calendar = $this->calendar; + + return match ($length) { + 1, 2, 3 => $calendar->abbreviated_days[$code], + 4 => $calendar->wide_days[$code], + 5 => $calendar->narrow_days[$code], + 6 => $calendar->short_days[$code], + default => '', + }; + // @codeCoverageIgnore + } + + /** + * Stand-Alone local day of week - Use one letter for the local numeric value (same as 'e'), + * three for the abbreviated day name, four for the full (wide) name, five for the narrow name, + * or six for the short name. + * + * @uses Calendar::$standalone_abbreviated_days + * @uses Calendar::$standalone_wide_days + * @uses Calendar::$standalone_narrow_days + * @uses Calendar::$standalone_short_days + */ + private function format_day_in_week_stand_alone(DateTimeAccessor $datetime, int $length): string + { + static $mapping = [ + + 3 => 'abbreviated', + 4 => 'wide', + 5 => 'narrow', + 6 => 'short', + + ]; + + if ($length == 2 || $length > 6) { + return ''; + } + + $day = $datetime->weekday; + + if ($length == 1) { + return (string)$day; + } + + $code = $this->resolve_day_code($day); + + return $this->calendar->{'standalone_' . $mapping[$length] . '_days'}[$code]; + } + + /** + * Local day of week. Same as E except adds a numeric value that will depend on the local + * starting day of the week, using one or two letters. For this example, Monday is the + * first day of the week. + */ + private function format_day_in_week_local(DateTimeAccessor $datetime, int $length): string + { + if ($length < 3) { + return (string)$datetime->weekday; + } + + return $this->format_day_in_week($datetime, $length); + } + + /* + * period (a) + */ + + /** + * AM or PM. [1] + * + * @return string AM or PM designator + */ + private function format_period(DateTimeAccessor $datetime): string + { + return $this->calendar['dayPeriods']['format']['abbreviated'][$datetime->hour < 12 ? 'am' : 'pm']; + } + + /* + * hour (h,H,K,k) + */ + + /** + * Hour [1-12]. When used in skeleton data or in a skeleton passed in an API for flexible data + * pattern generation, it should match the 12-hour-cycle format preferred by the locale + * (h or K); it should not match a 24-hour-cycle format (H or k). Use hh for zero + * padding. [1..2] + */ + private function format_hour12(DateTimeAccessor $datetime, int $length): string + { + if ($length > 2) { + return ''; + } + + $hour = $datetime->hour; + $hour = ($hour == 12) ? 12 : $hour % 12; + + if ($length == 1) { + return (string)$hour; + } + + return self::numeric_pad($hour); + } + + /** + * Hour [0-23]. When used in skeleton data or in a skeleton passed in an API for flexible + * data pattern generation, it should match the 24-hour-cycle format preferred by the + * locale (H or k); it should not match a 12-hour-cycle format (h or K). Use HH for zero + * padding. [1..2] + */ + private function format_hour24(DateTimeAccessor $datetime, int $length): string + { + if ($length > 2) { + return ''; + } + + $hour = $datetime->hour; + + if ($length == 1) { + return (string)$hour; + } + + return self::numeric_pad($hour); + } + + /** + * Hour [0-11]. When used in a skeleton, only matches K or h, see above. Use KK for zero + * padding. [1..2] + */ + private function format_hour_in_period(DateTimeAccessor $datetime, int $length): string + { + if ($length > 2) { + return ''; + } + + $hour = $datetime->hour % 12; + + if ($length == 1) { + return (string)$hour; + } + + return self::numeric_pad($hour); + } + + /** + * Hour [1-24]. When used in a skeleton, only matches k or H, see above. Use kk for zero + * padding. [1..2] + */ + private function format_hour_in_day(DateTimeAccessor $datetime, int $length): string + { + if ($length > 2) { + return ''; + } + + $hour = $datetime->hour ?: 24; + + if ($length == 1) { + return (string)$hour; + } + + return self::numeric_pad($hour); + } + + /* + * minute (m) + */ + + /** + * Minute. Use one or two "m" for zero padding. + */ + private function format_minutes(DateTimeAccessor $datetime, int $length): string + { + return $this->format_minutes_or_seconds($datetime, $length, 'minute'); + } + + /* + * second + */ + + /** + * Second. Use one or two "s" for zero padding. + */ + private function format_seconds(DateTimeAccessor $datetime, int $length): string + { + return $this->format_minutes_or_seconds($datetime, $length, 'second'); + } + + /** + * Minute. Use one or two "m" for zero padding. + */ + private function format_minutes_or_seconds(DateTimeAccessor $datetime, int $length, string $which): string + { + if ($length > 2) { + return ''; + } + + $value = $datetime->$which; + + if ($length == 1) { + return $value; + } + + return self::numeric_pad($value); + } + + /* + * zone (z,Z,v) + */ + + /** + * The ISO8601 basic format. + */ + private function format_timezone_basic(DateTimeAccessor $datetime): string + { + return $datetime->format('O'); + } + + /** + * The specific non-location format. + */ + private function format_timezone_non_location(DateTimeAccessor $datetime): string + { + $str = $datetime->format('T'); + + return $str === 'Z' ? 'UTC' : $str; + } + + /** + * @param DateTimeInterface|string|int $datetime + * + * @throws \Exception + */ + private function ensure_datetime($datetime): DateTimeInterface + { + if ($datetime instanceof DateTimeInterface) { + return $datetime; + } + + return new DateTimeImmutable(is_numeric($datetime) ? "@$datetime" : (string)$datetime); + } + + private function resolve_day_code(int $day): string + { + static $translate = [ + + 1 => 'mon', + 2 => 'tue', + 3 => 'wed', + 4 => 'thu', + 5 => 'fri', + 6 => 'sat', + 7 => 'sun' + + ]; + + return $translate[$day]; + } } diff --git a/lib/Exception.php b/lib/Exception.php index ca8854b..28649d0 100644 --- a/lib/Exception.php +++ b/lib/Exception.php @@ -24,5 +24,4 @@ */ interface Exception extends Throwable { - } diff --git a/lib/Formatter.php b/lib/Formatter.php index 42302a0..31f0f65 100644 --- a/lib/Formatter.php +++ b/lib/Formatter.php @@ -8,5 +8,4 @@ */ interface Formatter { - } diff --git a/lib/GitHub/UrlResolver.php b/lib/GitHub/UrlResolver.php index 8f99a01..a0fc1a7 100644 --- a/lib/GitHub/UrlResolver.php +++ b/lib/GitHub/UrlResolver.php @@ -10,57 +10,55 @@ */ class UrlResolver { - public const DEFAULT_ORIGIN = "https://raw.githubusercontent.com/unicode-org/cldr-json/"; - public const DEFAULT_VERSION = "45.0.0"; - public const DEFAULT_VARIATION = self::VARIATION_FULL; + public const DEFAULT_ORIGIN = "https://raw.githubusercontent.com/unicode-org/cldr-json/"; + public const DEFAULT_VERSION = "45.0.0"; + public const DEFAULT_VARIATION = self::VARIATION_FULL; - public const VARIATION_FULL = 'full'; + public const VARIATION_FULL = 'full'; - /** - * List of sections that don't have variations, that is no '-full' or '-modern' suffix. - */ - private const INVARIANT = [ 'bcp47', 'core', 'rbnf' ]; + /** + * List of sections that don't have variations, that is no '-full' or '-modern' suffix. + */ + private const INVARIANT = [ 'bcp47', 'core', 'rbnf' ]; - private const MAIN_OVERRIDE = [ + private const MAIN_OVERRIDE = [ - "annotations-derived" => "annotationsDerived", - "annotations" => "annotations", - "bcp47" => "bcp47", - "core" => "", // no _main_, files are available at the base - "rbnf" => "rbnf", - "segments" => "segments", + "annotations-derived" => "annotationsDerived", + "annotations" => "annotations", + "bcp47" => "bcp47", + "core" => "", // no _main_, files are available at the base + "rbnf" => "rbnf", + "segments" => "segments", - ]; + ]; - public function __construct( - private readonly string $origin = self::DEFAULT_ORIGIN, - private readonly string $version = self::DEFAULT_VERSION, - private readonly string $variation = self::DEFAULT_VARIATION - ) { - } + public function __construct( + private readonly string $origin = self::DEFAULT_ORIGIN, + private readonly string $version = self::DEFAULT_VERSION, + private readonly string $variation = self::DEFAULT_VARIATION + ) { + } - /** - * @param string $path A relative path e.g. 'numbers/en-150/currencies' - * - * @return string Resolved URL. - */ - public function resolve(string $path): string - { - $parts = explode("/", $path, 2); - $p0 = array_shift($parts); - $p9 = array_shift($parts); - $main = self::MAIN_OVERRIDE[$p0] ?? "main"; + /** + * @param string $path A relative path e.g. 'numbers/en-150/currencies' + * + * @return string Resolved URL. + */ + public function resolve(string $path): string + { + $parts = explode("/", $path, 2); + $p0 = array_shift($parts); + $p9 = array_shift($parts); + $main = self::MAIN_OVERRIDE[$p0] ?? "main"; - if ($main) - { - $main .= "/"; - } + if ($main) { + $main .= "/"; + } - if (!in_array($p0, self::INVARIANT)) - { - $p0 = "$p0-$this->variation"; - } + if (!in_array($p0, self::INVARIANT)) { + $p0 = "$p0-$this->variation"; + } - return "$this->origin$this->version/cldr-json/cldr-$p0/$main$p9.json"; - } + return "$this->origin$this->version/cldr-json/cldr-$p0/$main$p9.json"; + } } diff --git a/lib/ListFormatter.php b/lib/ListFormatter.php index c839f04..e8cdc16 100644 --- a/lib/ListFormatter.php +++ b/lib/ListFormatter.php @@ -13,66 +13,61 @@ */ class ListFormatter implements Formatter, Localizable { - /** - * Formats variable-length lists of scalars. - * - * @param scalar[] $list - */ - public function format(array $list, ListPattern $list_pattern): string - { - $list = array_values($list); + /** + * Formats variable-length lists of scalars. + * + * @param scalar[] $list + */ + public function format(array $list, ListPattern $list_pattern): string + { + $list = array_values($list); - switch (count($list)) - { - case 0: - return ""; + switch (count($list)) { + case 0: + return ""; - case 1: - return (string) current($list); + case 1: + return (string)current($list); - case 2: - return $this->format_pattern($list_pattern->two, (string) $list[0], (string) $list[1]); + case 2: + return $this->format_pattern($list_pattern->two, (string)$list[0], (string)$list[1]); - default: - $n = count($list) - 1; - $v1 = $list[$n]; + default: + $n = count($list) - 1; + $v1 = $list[$n]; - for ($i = $n - 1 ; $i > -1 ; $i--) - { - $v0 = $list[$i]; + for ($i = $n - 1; $i > -1; $i--) { + $v0 = $list[$i]; - if ($i === 0) - { - $pattern = $list_pattern->start; - } - else if ($i + 1 === $n) - { - $pattern = $list_pattern->end; - } - else - { - $pattern = $list_pattern->middle; - } + if ($i === 0) { + $pattern = $list_pattern->start; + } else { + if ($i + 1 === $n) { + $pattern = $list_pattern->end; + } else { + $pattern = $list_pattern->middle; + } + } - $v1 = $this->format_pattern($pattern, (string) $v0, (string) $v1); - } + $v1 = $this->format_pattern($pattern, (string)$v0, (string)$v1); + } - return $v1; - } - } + return $v1; + } + } - private function format_pattern(string $pattern, string $v0, string $v1): string - { - return strtr($pattern, [ + private function format_pattern(string $pattern, string $v0, string $v1): string + { + return strtr($pattern, [ - '{0}' => $v0, - '{1}' => $v1 + '{0}' => $v0, + '{1}' => $v1 - ]); - } + ]); + } - public function localized(Locale $locale): LocalizedListFormatter - { - return new LocalizedListFormatter($this, $locale); - } + public function localized(Locale $locale): LocalizedListFormatter + { + return new LocalizedListFormatter($this, $locale); + } } diff --git a/lib/ListType.php b/lib/ListType.php index 3293a68..abcd651 100644 --- a/lib/ListType.php +++ b/lib/ListType.php @@ -7,13 +7,13 @@ */ enum ListType: string { - case STANDARD = 'standard'; - case STANDARD_SHORT = 'standard-short'; - case STANDARD_NARROW = 'standard-narrow'; - case OR = 'or'; - case OR_SHORT = 'or-short'; - case OR_NARROW = 'or-narrow'; - case UNIT = 'unit'; - case UNIT_SHORT = 'unit-short'; - case UNIT_NARROW = 'unit-narrow'; + case STANDARD = 'standard'; + case STANDARD_SHORT = 'standard-short'; + case STANDARD_NARROW = 'standard-narrow'; + case OR = 'or'; + case OR_SHORT = 'or-short'; + case OR_NARROW = 'or-narrow'; + case UNIT = 'unit'; + case UNIT_SHORT = 'unit-short'; + case UNIT_NARROW = 'unit-narrow'; } diff --git a/lib/Locale.php b/lib/Locale.php index e691fd3..813ee12 100644 --- a/lib/Locale.php +++ b/lib/Locale.php @@ -5,10 +5,8 @@ use Closure; use ICanBoogie\Accessor\AccessorTrait; use ICanBoogie\CLDR\Locale\HasContextTransforms; -use LogicException; use function str_replace; -use function strtr; /** * Representation of a locale. @@ -36,224 +34,227 @@ */ class Locale extends AbstractSectionCollection implements Localizable, Warmable { - use AccessorTrait; - - /** - * Where _key_ is an offset and _value_ and array where `0` is a pattern for the path and `1` the data path. - */ - private const OFFSET_MAPPING = [ - - 'ca-buddhist' => [ 'cal-buddhist/{locale}/ca-buddhist', 'dates/calendars/buddhist' ], - 'ca-chinese' => [ 'cal-chinese/{locale}/ca-chinese', 'dates/calendars/chinese' ], - 'ca-coptic' => [ 'cal-coptic/{locale}/ca-coptic', 'dates/calendars/coptic' ], - 'ca-dangi' => [ 'cal-dangi/{locale}/ca-dangi', 'dates/calendars/dangi' ], - 'ca-ethiopic' => [ 'cal-ethiopic/{locale}/ca-ethiopic', 'dates/calendars/ethiopic' ], - 'ca-hebrew' => [ 'cal-hebrew/{locale}/ca-hebrew', 'dates/calendars/hebrew' ], - 'ca-indian' => [ 'cal-indian/{locale}/ca-indian', 'dates/calendars/indian' ], - 'ca-islamic' => [ 'cal-islamic/{locale}/ca-islamic', 'dates/calendars/islamic' ], - 'ca-japanese' => [ 'cal-japanese/{locale}/ca-japanese', 'dates/calendars/japanese' ], - 'ca-persian' => [ 'cal-persian/{locale}/ca-persian', 'dates/calendars/persian' ], - 'ca-roc' => [ 'cal-roc/{locale}/ca-roc', 'dates/calendars/roc' ], - 'ca-generic' => [ 'dates/{locale}/ca-generic', 'dates/calendars/generic' ], - 'ca-gregorian' => [ 'dates/{locale}/ca-gregorian', 'dates/calendars/gregorian' ], - 'dateFields' => [ 'dates/{locale}/dateFields', 'dates/fields' ], - 'timeZoneNames' => [ 'dates/{locale}/timeZoneNames', 'dates/timeZoneNames' ], - 'languages' => [ 'localenames/{locale}/languages', 'localeDisplayNames/languages' ], - 'localeDisplayNames' => [ 'localenames/{locale}/localeDisplayNames', 'localeDisplayNames' ], - 'scripts' => [ 'localenames/{locale}/scripts', 'localeDisplayNames/scripts' ], - 'territories' => [ 'localenames/{locale}/territories', 'localeDisplayNames/territories' ], - 'variants' => [ 'localenames/{locale}/variants', 'localeDisplayNames/variants' ], - 'characters' => [ 'misc/{locale}/characters', 'characters' ], - 'contextTransforms' => [ 'misc/{locale}/contextTransforms', 'contextTransforms' ], - 'delimiters' => [ 'misc/{locale}/delimiters', 'delimiters' ], - 'layout' => [ 'misc/{locale}/layout', 'layout' ], - 'listPatterns' => [ 'misc/{locale}/listPatterns', 'listPatterns' ], - 'posix' => [ 'misc/{locale}/posix', 'posix' ], - 'currencies' => [ 'numbers/{locale}/currencies', 'numbers/currencies' ], - 'numbers' => [ 'numbers/{locale}/numbers', 'numbers' ], - 'measurementSystemNames' => [ 'units/{locale}/measurementSystemNames', 'localeDisplayNames/measurementSystemNames' ], - 'units' => [ 'units/{locale}/units', 'units' ], - - ]; - - public function __construct( - Repository $repository, - public readonly LocaleId $id - ) { - parent::__construct($repository); - } - - // @phpstan-ignore-next-line - public function offsetGet($offset) - { - // Not all locales have context transforms - if ($offset === 'contextTransforms' && !HasContextTransforms::for_locale($this->id)) { - return []; - } - - return parent::offsetGet($offset); - } - - public function offsetExists($offset): bool - { - return isset(self::OFFSET_MAPPING[$offset]); - } - - /** - * Warm up with locale relevant data. - */ - public function warm_up(Closure $progress): void - { + use AccessorTrait; + + /** + * Where _key_ is an offset and _value_ and array where `0` is a pattern for the path and `1` the data path. + */ + private const OFFSET_MAPPING = [ + + 'ca-buddhist' => [ 'cal-buddhist/{locale}/ca-buddhist', 'dates/calendars/buddhist' ], + 'ca-chinese' => [ 'cal-chinese/{locale}/ca-chinese', 'dates/calendars/chinese' ], + 'ca-coptic' => [ 'cal-coptic/{locale}/ca-coptic', 'dates/calendars/coptic' ], + 'ca-dangi' => [ 'cal-dangi/{locale}/ca-dangi', 'dates/calendars/dangi' ], + 'ca-ethiopic' => [ 'cal-ethiopic/{locale}/ca-ethiopic', 'dates/calendars/ethiopic' ], + 'ca-hebrew' => [ 'cal-hebrew/{locale}/ca-hebrew', 'dates/calendars/hebrew' ], + 'ca-indian' => [ 'cal-indian/{locale}/ca-indian', 'dates/calendars/indian' ], + 'ca-islamic' => [ 'cal-islamic/{locale}/ca-islamic', 'dates/calendars/islamic' ], + 'ca-japanese' => [ 'cal-japanese/{locale}/ca-japanese', 'dates/calendars/japanese' ], + 'ca-persian' => [ 'cal-persian/{locale}/ca-persian', 'dates/calendars/persian' ], + 'ca-roc' => [ 'cal-roc/{locale}/ca-roc', 'dates/calendars/roc' ], + 'ca-generic' => [ 'dates/{locale}/ca-generic', 'dates/calendars/generic' ], + 'ca-gregorian' => [ 'dates/{locale}/ca-gregorian', 'dates/calendars/gregorian' ], + 'dateFields' => [ 'dates/{locale}/dateFields', 'dates/fields' ], + 'timeZoneNames' => [ 'dates/{locale}/timeZoneNames', 'dates/timeZoneNames' ], + 'languages' => [ 'localenames/{locale}/languages', 'localeDisplayNames/languages' ], + 'localeDisplayNames' => [ 'localenames/{locale}/localeDisplayNames', 'localeDisplayNames' ], + 'scripts' => [ 'localenames/{locale}/scripts', 'localeDisplayNames/scripts' ], + 'territories' => [ 'localenames/{locale}/territories', 'localeDisplayNames/territories' ], + 'variants' => [ 'localenames/{locale}/variants', 'localeDisplayNames/variants' ], + 'characters' => [ 'misc/{locale}/characters', 'characters' ], + 'contextTransforms' => [ 'misc/{locale}/contextTransforms', 'contextTransforms' ], + 'delimiters' => [ 'misc/{locale}/delimiters', 'delimiters' ], + 'layout' => [ 'misc/{locale}/layout', 'layout' ], + 'listPatterns' => [ 'misc/{locale}/listPatterns', 'listPatterns' ], + 'posix' => [ 'misc/{locale}/posix', 'posix' ], + 'currencies' => [ 'numbers/{locale}/currencies', 'numbers/currencies' ], + 'numbers' => [ 'numbers/{locale}/numbers', 'numbers' ], + 'measurementSystemNames' => [ + 'units/{locale}/measurementSystemNames', + 'localeDisplayNames/measurementSystemNames' + ], + 'units' => [ 'units/{locale}/units', 'units' ], + + ]; + + public function __construct( + Repository $repository, + public readonly LocaleId $id + ) { + parent::__construct($repository); + } + + // @phpstan-ignore-next-line + public function offsetGet($offset) + { + // Not all locales have context transforms + if ($offset === 'contextTransforms' && !HasContextTransforms::for_locale($this->id)) { + return []; + } + + return parent::offsetGet($offset); + } + + public function offsetExists($offset): bool + { + return isset(self::OFFSET_MAPPING[$offset]); + } + + /** + * Warm up with locale relevant data. + */ + public function warm_up(Closure $progress): void + { $progress("Warming up locale '{$this->id->value}':"); - foreach (array_keys(self::OFFSET_MAPPING) as $offset) { + foreach (array_keys(self::OFFSET_MAPPING) as $offset) { $progress("- $offset"); - $this->offsetGet($offset); - } - } - - protected function path_for(string $offset): string - { - return str_replace('{locale}', $this->id->value, self::OFFSET_MAPPING[$offset][0]); - } - - protected function data_path_for(string $offset): string - { - return "main/{$this->id->value}/" . self::OFFSET_MAPPING[$offset][1]; - } - - private function get_language(): string - { - [ $language ] = explode('-', $this->id->value, 2); - - return $language; - } - - private CalendarCollection $calendars; - - private function get_calendars(): CalendarCollection - { - return $this->calendars ??= new CalendarCollection($this); - } - - private Calendar $calendar; - - private function get_calendar(): Calendar - { - return $this->calendar ??= $this->get_calendars()['gregorian']; // TODO-20131101: use preferred data - } - - private Numbers $numbers; - - private function get_numbers(): Numbers - { - /** @phpstan-ignore-next-line */ - return $this->numbers ??= new Numbers($this, $this['numbers']); - } - - private LocalizedNumberFormatter $number_formatter; - - private function get_number_formatter(): LocalizedNumberFormatter - { - return $this->number_formatter ??= $this->repository->number_formatter->localized($this); - } - - private LocalizedCurrencyFormatter $currency_formatter; - - private function get_currency_formatter(): LocalizedCurrencyFormatter - { - return $this->currency_formatter ??= $this->repository->currency_formatter->localized($this); - } - - private LocalizedListFormatter $list_formatter; - - private function get_list_formatter(): LocalizedListFormatter - { - return $this->list_formatter ??= $this->repository->list_formatter->localized($this); - } - - private ContextTransforms $context_transforms; - - private function get_context_transforms(): ContextTransforms - { - /** @phpstan-ignore-next-line */ - return $this->context_transforms ??= new ContextTransforms($this['contextTransforms']); - } - - private Units $units; - - private function get_units(): Units - { - return $this->units ??= new Units($this); - } - - public function localized(Locale|LocaleId|string $locale): LocalizedLocale - { - if (!$locale instanceof self) { - $locale = $this->repository->locale_for($locale); - } - - return new LocalizedLocale($this, $locale); - } - - /** - * Formats a number using {@link $number_formatter}. - * - * @param float|int $number - * - * @see LocalizedNumberFormatter::format - */ - public function format_number($number, string $pattern = null): string - { - return $this->get_number_formatter()->format($number, $pattern); - } - - /** - * @param float|int|numeric-string $number - * - * @see LocalizedNumberFormatter::format - */ - public function format_percent(float|int|string $number, string $pattern = null): string - { - return $this->get_number_formatter()->format( - $number, - $pattern ?? $this->get_numbers()->percent_formats['standard'] - ); - } - - /** - * Formats currency using localized conventions. - * - * @param float|int|numeric-string $number - */ - public function format_currency( - float|int|string $number, - Currency|string $currency, - string $pattern = LocalizedCurrencyFormatter::PATTERN_STANDARD - ): string { - return $this->get_currency_formatter()->format($number, $currency, $pattern); - } - - /** - * Formats variable-length lists of scalars. - * - * @param scalar[] $list - * - * @see LocalizedListFormatter::format() - */ - public function format_list(array $list, ListType $type = ListType::STANDARD): string - { - return $this->get_list_formatter()->format($list, $type); - } - - /** - * Transforms a string depending on the context and the locale rules. - * - * @param ContextTransforms::USAGE_* $usage - * @param ContextTransforms::TYPE_* $type - */ - public function context_transform(string $str, string $usage, string $type): string - { - return $this->get_context_transforms()->transform($str, $usage, $type); - } + $this->offsetGet($offset); + } + } + + protected function path_for(string $offset): string + { + return str_replace('{locale}', $this->id->value, self::OFFSET_MAPPING[$offset][0]); + } + + protected function data_path_for(string $offset): string + { + return "main/{$this->id->value}/" . self::OFFSET_MAPPING[$offset][1]; + } + + private function get_language(): string + { + [ $language ] = explode('-', $this->id->value, 2); + + return $language; + } + + private CalendarCollection $calendars; + + private function get_calendars(): CalendarCollection + { + return $this->calendars ??= new CalendarCollection($this); + } + + private Calendar $calendar; + + private function get_calendar(): Calendar + { + return $this->calendar ??= $this->get_calendars()['gregorian']; // TODO-20131101: use preferred data + } + + private Numbers $numbers; + + private function get_numbers(): Numbers + { + /** @phpstan-ignore-next-line */ + return $this->numbers ??= new Numbers($this, $this['numbers']); + } + + private LocalizedNumberFormatter $number_formatter; + + private function get_number_formatter(): LocalizedNumberFormatter + { + return $this->number_formatter ??= $this->repository->number_formatter->localized($this); + } + + private LocalizedCurrencyFormatter $currency_formatter; + + private function get_currency_formatter(): LocalizedCurrencyFormatter + { + return $this->currency_formatter ??= $this->repository->currency_formatter->localized($this); + } + + private LocalizedListFormatter $list_formatter; + + private function get_list_formatter(): LocalizedListFormatter + { + return $this->list_formatter ??= $this->repository->list_formatter->localized($this); + } + + private ContextTransforms $context_transforms; + + private function get_context_transforms(): ContextTransforms + { + /** @phpstan-ignore-next-line */ + return $this->context_transforms ??= new ContextTransforms($this['contextTransforms']); + } + + private Units $units; + + private function get_units(): Units + { + return $this->units ??= new Units($this); + } + + public function localized(Locale|LocaleId|string $locale): LocalizedLocale + { + if (!$locale instanceof self) { + $locale = $this->repository->locale_for($locale); + } + + return new LocalizedLocale($this, $locale); + } + + /** + * Formats a number using {@link $number_formatter}. + * + * @param float|int $number + * + * @see LocalizedNumberFormatter::format + */ + public function format_number($number, string $pattern = null): string + { + return $this->get_number_formatter()->format($number, $pattern); + } + + /** + * @param float|int|numeric-string $number + * + * @see LocalizedNumberFormatter::format + */ + public function format_percent(float|int|string $number, string $pattern = null): string + { + return $this->get_number_formatter()->format( + $number, + $pattern ?? $this->get_numbers()->percent_formats['standard'] + ); + } + + /** + * Formats currency using localized conventions. + * + * @param float|int|numeric-string $number + */ + public function format_currency( + float|int|string $number, + Currency|string $currency, + string $pattern = LocalizedCurrencyFormatter::PATTERN_STANDARD + ): string { + return $this->get_currency_formatter()->format($number, $currency, $pattern); + } + + /** + * Formats variable-length lists of scalars. + * + * @param scalar[] $list + * + * @see LocalizedListFormatter::format() + */ + public function format_list(array $list, ListType $type = ListType::STANDARD): string + { + return $this->get_list_formatter()->format($list, $type); + } + + /** + * Transforms a string depending on the context and the locale rules. + * + * @param ContextTransforms::USAGE_* $usage + * @param ContextTransforms::TYPE_* $type + */ + public function context_transform(string $str, string $usage, string $type): string + { + return $this->get_context_transforms()->transform($str, $usage, $type); + } } diff --git a/lib/Locale/HasContextTransforms.php b/lib/Locale/HasContextTransforms.php index 5ac20aa..f4aad30 100644 --- a/lib/Locale/HasContextTransforms.php +++ b/lib/Locale/HasContextTransforms.php @@ -13,405 +13,405 @@ final class HasContextTransforms { private const HAS_CONTEXT_TRANSFORMS = - [ - 'af' => false, - 'af-NA' => false, - 'am' => false, - 'ar' => false, - 'ar-AE' => false, - 'ar-BH' => false, - 'ar-DJ' => false, - 'ar-DZ' => false, - 'ar-EG' => false, - 'ar-EH' => false, - 'ar-ER' => false, - 'ar-IL' => false, - 'ar-IQ' => false, - 'ar-JO' => false, - 'ar-KM' => false, - 'ar-KW' => false, - 'ar-LB' => false, - 'ar-LY' => false, - 'ar-MA' => false, - 'ar-MR' => false, - 'ar-OM' => false, - 'ar-PS' => false, - 'ar-QA' => false, - 'ar-SA' => false, - 'ar-SD' => false, - 'ar-SO' => false, - 'ar-SS' => false, - 'ar-SY' => false, - 'ar-TD' => false, - 'ar-TN' => false, - 'ar-YE' => false, - 'as' => false, - 'az' => false, - 'az-Latn' => false, - 'be' => false, - 'be-tarask' => false, - 'bg' => false, - 'bn' => false, - 'bn-IN' => false, - 'bs' => false, - 'bs-Latn' => false, - 'ca' => true, - 'ca-AD' => true, - 'ca-ES-valencia' => true, - 'ca-FR' => true, - 'ca-IT' => true, - 'chr' => false, - 'cs' => true, - 'cy' => false, - 'da' => true, - 'da-GL' => true, - 'de' => true, - 'de-AT' => true, - 'de-BE' => true, - 'de-CH' => true, - 'de-IT' => true, - 'de-LI' => true, - 'de-LU' => true, - 'dsb' => false, - 'el' => true, - 'el-CY' => true, - 'el-polyton' => true, - 'en' => true, - 'en-001' => true, - 'en-150' => true, - 'en-AE' => true, - 'en-AG' => true, - 'en-AI' => true, - 'en-AS' => true, - 'en-AT' => true, - 'en-AU' => true, - 'en-BB' => true, - 'en-BE' => true, - 'en-BI' => true, - 'en-BM' => true, - 'en-BS' => true, - 'en-BW' => true, - 'en-BZ' => true, - 'en-CA' => true, - 'en-CC' => true, - 'en-CH' => true, - 'en-CK' => true, - 'en-CM' => true, - 'en-CX' => true, - 'en-CY' => true, - 'en-DE' => true, - 'en-DG' => true, - 'en-DK' => true, - 'en-DM' => true, - 'en-ER' => true, - 'en-FI' => true, - 'en-FJ' => true, - 'en-FK' => true, - 'en-FM' => true, - 'en-GB' => true, - 'en-GD' => true, - 'en-GG' => true, - 'en-GH' => true, - 'en-GI' => true, - 'en-GM' => true, - 'en-GU' => true, - 'en-GY' => true, - 'en-HK' => true, - 'en-ID' => true, - 'en-IE' => true, - 'en-IL' => true, - 'en-IM' => true, - 'en-IN' => true, - 'en-IO' => true, - 'en-JE' => true, - 'en-JM' => true, - 'en-KE' => true, - 'en-KI' => true, - 'en-KN' => true, - 'en-KY' => true, - 'en-LC' => true, - 'en-LR' => true, - 'en-LS' => true, - 'en-MG' => true, - 'en-MH' => true, - 'en-MO' => true, - 'en-MP' => true, - 'en-MS' => true, - 'en-MT' => true, - 'en-MU' => true, - 'en-MV' => true, - 'en-MW' => true, - 'en-MY' => true, - 'en-NA' => true, - 'en-NF' => true, - 'en-NG' => true, - 'en-NL' => true, - 'en-NR' => true, - 'en-NU' => true, - 'en-NZ' => true, - 'en-PG' => true, - 'en-PH' => true, - 'en-PK' => true, - 'en-PN' => true, - 'en-PR' => true, - 'en-PW' => true, - 'en-RW' => true, - 'en-SB' => true, - 'en-SC' => true, - 'en-SD' => true, - 'en-SE' => true, - 'en-SG' => true, - 'en-SH' => true, - 'en-SI' => true, - 'en-SL' => true, - 'en-SS' => true, - 'en-SX' => true, - 'en-SZ' => true, - 'en-TC' => true, - 'en-TK' => true, - 'en-TO' => true, - 'en-TT' => true, - 'en-TV' => true, - 'en-TZ' => true, - 'en-UG' => true, - 'en-UM' => true, - 'en-VC' => true, - 'en-VG' => true, - 'en-VI' => true, - 'en-VU' => true, - 'en-WS' => true, - 'en-ZA' => true, - 'en-ZM' => true, - 'en-ZW' => true, - 'es' => true, - 'es-419' => true, - 'es-AR' => true, - 'es-BO' => true, - 'es-BR' => true, - 'es-BZ' => true, - 'es-CL' => true, - 'es-CO' => true, - 'es-CR' => true, - 'es-CU' => true, - 'es-DO' => true, - 'es-EA' => true, - 'es-EC' => true, - 'es-GQ' => true, - 'es-GT' => true, - 'es-HN' => true, - 'es-IC' => true, - 'es-MX' => true, - 'es-NI' => true, - 'es-PA' => true, - 'es-PE' => true, - 'es-PH' => true, - 'es-PR' => true, - 'es-PY' => true, - 'es-SV' => true, - 'es-US' => true, - 'es-UY' => true, - 'es-VE' => true, - 'et' => false, - 'eu' => false, - 'fa' => false, - 'fa-AF' => false, - 'fi' => true, - 'fil' => false, - 'fr' => true, - 'fr-BE' => true, - 'fr-BF' => true, - 'fr-BI' => true, - 'fr-BJ' => true, - 'fr-BL' => true, - 'fr-CA' => true, - 'fr-CD' => true, - 'fr-CF' => true, - 'fr-CG' => true, - 'fr-CH' => true, - 'fr-CI' => true, - 'fr-CM' => true, - 'fr-DJ' => true, - 'fr-DZ' => true, - 'fr-GA' => true, - 'fr-GF' => true, - 'fr-GN' => true, - 'fr-GP' => true, - 'fr-GQ' => true, - 'fr-HT' => true, - 'fr-KM' => true, - 'fr-LU' => true, - 'fr-MA' => true, - 'fr-MC' => true, - 'fr-MF' => true, - 'fr-MG' => true, - 'fr-ML' => true, - 'fr-MQ' => true, - 'fr-MR' => true, - 'fr-MU' => true, - 'fr-NC' => true, - 'fr-NE' => true, - 'fr-PF' => true, - 'fr-PM' => true, - 'fr-RE' => true, - 'fr-RW' => true, - 'fr-SC' => true, - 'fr-SN' => true, - 'fr-SY' => true, - 'fr-TD' => true, - 'fr-TG' => true, - 'fr-TN' => true, - 'fr-VU' => true, - 'fr-WF' => true, - 'fr-YT' => true, - 'ga' => false, - 'ga-GB' => false, - 'gd' => false, - 'gl' => false, - 'gu' => false, - 'ha' => false, - 'ha-GH' => false, - 'ha-NE' => false, - 'he' => false, - 'hi' => false, - 'hi-Latn' => true, - 'hr' => true, - 'hr-BA' => true, - 'hsb' => false, - 'hu' => true, - 'hy' => false, - 'id' => true, - 'ig' => false, - 'is' => false, - 'it' => true, - 'it-CH' => true, - 'it-SM' => true, - 'it-VA' => true, - 'ja' => false, - 'jv' => false, - 'ka' => false, - 'kk' => false, - 'km' => false, - 'kn' => false, - 'ko' => false, - 'ko-CN' => false, - 'ko-KP' => false, - 'kok' => false, - 'ky' => false, - 'lo' => false, - 'lt' => false, - 'lv' => false, - 'mk' => false, - 'ml' => false, - 'mn' => false, - 'mr' => false, - 'ms' => false, - 'ms-BN' => false, - 'ms-ID' => false, - 'ms-SG' => false, - 'my' => false, - 'nb' => true, - 'nb-SJ' => true, - 'ne' => false, - 'ne-IN' => false, - 'nl' => true, - 'nl-AW' => true, - 'nl-BE' => true, - 'nl-BQ' => true, - 'nl-CW' => true, - 'nl-SR' => true, - 'nl-SX' => true, - 'nn' => true, - 'no' => true, - 'or' => false, - 'pa' => false, - 'pa-Guru' => false, - 'pl' => true, - 'ps' => false, - 'ps-PK' => false, - 'pt' => true, - 'pt-AO' => true, - 'pt-CH' => true, - 'pt-CV' => true, - 'pt-GQ' => true, - 'pt-GW' => true, - 'pt-LU' => true, - 'pt-MO' => true, - 'pt-MZ' => true, - 'pt-PT' => true, - 'pt-ST' => true, - 'pt-TL' => true, - 'ro' => true, - 'ro-MD' => true, - 'ru' => true, - 'ru-BY' => true, - 'ru-KG' => true, - 'ru-KZ' => true, - 'ru-MD' => true, - 'ru-UA' => true, - 'sd' => false, - 'sd-Arab' => false, - 'si' => false, - 'sk' => true, - 'sl' => false, - 'so' => false, - 'so-DJ' => false, - 'so-ET' => false, - 'so-KE' => false, - 'sq' => false, - 'sq-MK' => false, - 'sq-XK' => false, - 'sr' => false, - 'sr-Cyrl' => false, - 'sr-Cyrl-BA' => false, - 'sr-Cyrl-ME' => false, - 'sr-Cyrl-XK' => false, - 'sr-Latn' => false, - 'sr-Latn-BA' => false, - 'sr-Latn-ME' => false, - 'sr-Latn-XK' => false, - 'sv' => true, - 'sv-AX' => true, - 'sv-FI' => true, - 'sw' => false, - 'sw-CD' => false, - 'sw-KE' => false, - 'sw-UG' => false, - 'ta' => false, - 'ta-LK' => false, - 'ta-MY' => false, - 'ta-SG' => false, - 'te' => false, - 'th' => false, - 'tk' => true, - 'tr' => true, - 'tr-CY' => true, - 'uk' => true, - 'und' => false, - 'ur' => false, - 'ur-IN' => false, - 'uz' => false, - 'uz-Latn' => false, - 'vi' => true, - 'yo' => false, - 'yo-BJ' => false, - 'yue' => false, - 'yue-Hans' => false, - 'yue-Hant' => false, - 'zh' => false, - 'zh-Hans' => false, - 'zh-Hans-HK' => false, - 'zh-Hans-MO' => false, - 'zh-Hans-SG' => false, - 'zh-Hant' => false, - 'zh-Hant-HK' => false, - 'zh-Hant-MO' => false, - 'zu' => false, - ]; + [ + 'af' => false, + 'af-NA' => false, + 'am' => false, + 'ar' => false, + 'ar-AE' => false, + 'ar-BH' => false, + 'ar-DJ' => false, + 'ar-DZ' => false, + 'ar-EG' => false, + 'ar-EH' => false, + 'ar-ER' => false, + 'ar-IL' => false, + 'ar-IQ' => false, + 'ar-JO' => false, + 'ar-KM' => false, + 'ar-KW' => false, + 'ar-LB' => false, + 'ar-LY' => false, + 'ar-MA' => false, + 'ar-MR' => false, + 'ar-OM' => false, + 'ar-PS' => false, + 'ar-QA' => false, + 'ar-SA' => false, + 'ar-SD' => false, + 'ar-SO' => false, + 'ar-SS' => false, + 'ar-SY' => false, + 'ar-TD' => false, + 'ar-TN' => false, + 'ar-YE' => false, + 'as' => false, + 'az' => false, + 'az-Latn' => false, + 'be' => false, + 'be-tarask' => false, + 'bg' => false, + 'bn' => false, + 'bn-IN' => false, + 'bs' => false, + 'bs-Latn' => false, + 'ca' => true, + 'ca-AD' => true, + 'ca-ES-valencia' => true, + 'ca-FR' => true, + 'ca-IT' => true, + 'chr' => false, + 'cs' => true, + 'cy' => false, + 'da' => true, + 'da-GL' => true, + 'de' => true, + 'de-AT' => true, + 'de-BE' => true, + 'de-CH' => true, + 'de-IT' => true, + 'de-LI' => true, + 'de-LU' => true, + 'dsb' => false, + 'el' => true, + 'el-CY' => true, + 'el-polyton' => true, + 'en' => true, + 'en-001' => true, + 'en-150' => true, + 'en-AE' => true, + 'en-AG' => true, + 'en-AI' => true, + 'en-AS' => true, + 'en-AT' => true, + 'en-AU' => true, + 'en-BB' => true, + 'en-BE' => true, + 'en-BI' => true, + 'en-BM' => true, + 'en-BS' => true, + 'en-BW' => true, + 'en-BZ' => true, + 'en-CA' => true, + 'en-CC' => true, + 'en-CH' => true, + 'en-CK' => true, + 'en-CM' => true, + 'en-CX' => true, + 'en-CY' => true, + 'en-DE' => true, + 'en-DG' => true, + 'en-DK' => true, + 'en-DM' => true, + 'en-ER' => true, + 'en-FI' => true, + 'en-FJ' => true, + 'en-FK' => true, + 'en-FM' => true, + 'en-GB' => true, + 'en-GD' => true, + 'en-GG' => true, + 'en-GH' => true, + 'en-GI' => true, + 'en-GM' => true, + 'en-GU' => true, + 'en-GY' => true, + 'en-HK' => true, + 'en-ID' => true, + 'en-IE' => true, + 'en-IL' => true, + 'en-IM' => true, + 'en-IN' => true, + 'en-IO' => true, + 'en-JE' => true, + 'en-JM' => true, + 'en-KE' => true, + 'en-KI' => true, + 'en-KN' => true, + 'en-KY' => true, + 'en-LC' => true, + 'en-LR' => true, + 'en-LS' => true, + 'en-MG' => true, + 'en-MH' => true, + 'en-MO' => true, + 'en-MP' => true, + 'en-MS' => true, + 'en-MT' => true, + 'en-MU' => true, + 'en-MV' => true, + 'en-MW' => true, + 'en-MY' => true, + 'en-NA' => true, + 'en-NF' => true, + 'en-NG' => true, + 'en-NL' => true, + 'en-NR' => true, + 'en-NU' => true, + 'en-NZ' => true, + 'en-PG' => true, + 'en-PH' => true, + 'en-PK' => true, + 'en-PN' => true, + 'en-PR' => true, + 'en-PW' => true, + 'en-RW' => true, + 'en-SB' => true, + 'en-SC' => true, + 'en-SD' => true, + 'en-SE' => true, + 'en-SG' => true, + 'en-SH' => true, + 'en-SI' => true, + 'en-SL' => true, + 'en-SS' => true, + 'en-SX' => true, + 'en-SZ' => true, + 'en-TC' => true, + 'en-TK' => true, + 'en-TO' => true, + 'en-TT' => true, + 'en-TV' => true, + 'en-TZ' => true, + 'en-UG' => true, + 'en-UM' => true, + 'en-VC' => true, + 'en-VG' => true, + 'en-VI' => true, + 'en-VU' => true, + 'en-WS' => true, + 'en-ZA' => true, + 'en-ZM' => true, + 'en-ZW' => true, + 'es' => true, + 'es-419' => true, + 'es-AR' => true, + 'es-BO' => true, + 'es-BR' => true, + 'es-BZ' => true, + 'es-CL' => true, + 'es-CO' => true, + 'es-CR' => true, + 'es-CU' => true, + 'es-DO' => true, + 'es-EA' => true, + 'es-EC' => true, + 'es-GQ' => true, + 'es-GT' => true, + 'es-HN' => true, + 'es-IC' => true, + 'es-MX' => true, + 'es-NI' => true, + 'es-PA' => true, + 'es-PE' => true, + 'es-PH' => true, + 'es-PR' => true, + 'es-PY' => true, + 'es-SV' => true, + 'es-US' => true, + 'es-UY' => true, + 'es-VE' => true, + 'et' => false, + 'eu' => false, + 'fa' => false, + 'fa-AF' => false, + 'fi' => true, + 'fil' => false, + 'fr' => true, + 'fr-BE' => true, + 'fr-BF' => true, + 'fr-BI' => true, + 'fr-BJ' => true, + 'fr-BL' => true, + 'fr-CA' => true, + 'fr-CD' => true, + 'fr-CF' => true, + 'fr-CG' => true, + 'fr-CH' => true, + 'fr-CI' => true, + 'fr-CM' => true, + 'fr-DJ' => true, + 'fr-DZ' => true, + 'fr-GA' => true, + 'fr-GF' => true, + 'fr-GN' => true, + 'fr-GP' => true, + 'fr-GQ' => true, + 'fr-HT' => true, + 'fr-KM' => true, + 'fr-LU' => true, + 'fr-MA' => true, + 'fr-MC' => true, + 'fr-MF' => true, + 'fr-MG' => true, + 'fr-ML' => true, + 'fr-MQ' => true, + 'fr-MR' => true, + 'fr-MU' => true, + 'fr-NC' => true, + 'fr-NE' => true, + 'fr-PF' => true, + 'fr-PM' => true, + 'fr-RE' => true, + 'fr-RW' => true, + 'fr-SC' => true, + 'fr-SN' => true, + 'fr-SY' => true, + 'fr-TD' => true, + 'fr-TG' => true, + 'fr-TN' => true, + 'fr-VU' => true, + 'fr-WF' => true, + 'fr-YT' => true, + 'ga' => false, + 'ga-GB' => false, + 'gd' => false, + 'gl' => false, + 'gu' => false, + 'ha' => false, + 'ha-GH' => false, + 'ha-NE' => false, + 'he' => false, + 'hi' => false, + 'hi-Latn' => true, + 'hr' => true, + 'hr-BA' => true, + 'hsb' => false, + 'hu' => true, + 'hy' => false, + 'id' => true, + 'ig' => false, + 'is' => false, + 'it' => true, + 'it-CH' => true, + 'it-SM' => true, + 'it-VA' => true, + 'ja' => false, + 'jv' => false, + 'ka' => false, + 'kk' => false, + 'km' => false, + 'kn' => false, + 'ko' => false, + 'ko-CN' => false, + 'ko-KP' => false, + 'kok' => false, + 'ky' => false, + 'lo' => false, + 'lt' => false, + 'lv' => false, + 'mk' => false, + 'ml' => false, + 'mn' => false, + 'mr' => false, + 'ms' => false, + 'ms-BN' => false, + 'ms-ID' => false, + 'ms-SG' => false, + 'my' => false, + 'nb' => true, + 'nb-SJ' => true, + 'ne' => false, + 'ne-IN' => false, + 'nl' => true, + 'nl-AW' => true, + 'nl-BE' => true, + 'nl-BQ' => true, + 'nl-CW' => true, + 'nl-SR' => true, + 'nl-SX' => true, + 'nn' => true, + 'no' => true, + 'or' => false, + 'pa' => false, + 'pa-Guru' => false, + 'pl' => true, + 'ps' => false, + 'ps-PK' => false, + 'pt' => true, + 'pt-AO' => true, + 'pt-CH' => true, + 'pt-CV' => true, + 'pt-GQ' => true, + 'pt-GW' => true, + 'pt-LU' => true, + 'pt-MO' => true, + 'pt-MZ' => true, + 'pt-PT' => true, + 'pt-ST' => true, + 'pt-TL' => true, + 'ro' => true, + 'ro-MD' => true, + 'ru' => true, + 'ru-BY' => true, + 'ru-KG' => true, + 'ru-KZ' => true, + 'ru-MD' => true, + 'ru-UA' => true, + 'sd' => false, + 'sd-Arab' => false, + 'si' => false, + 'sk' => true, + 'sl' => false, + 'so' => false, + 'so-DJ' => false, + 'so-ET' => false, + 'so-KE' => false, + 'sq' => false, + 'sq-MK' => false, + 'sq-XK' => false, + 'sr' => false, + 'sr-Cyrl' => false, + 'sr-Cyrl-BA' => false, + 'sr-Cyrl-ME' => false, + 'sr-Cyrl-XK' => false, + 'sr-Latn' => false, + 'sr-Latn-BA' => false, + 'sr-Latn-ME' => false, + 'sr-Latn-XK' => false, + 'sv' => true, + 'sv-AX' => true, + 'sv-FI' => true, + 'sw' => false, + 'sw-CD' => false, + 'sw-KE' => false, + 'sw-UG' => false, + 'ta' => false, + 'ta-LK' => false, + 'ta-MY' => false, + 'ta-SG' => false, + 'te' => false, + 'th' => false, + 'tk' => true, + 'tr' => true, + 'tr-CY' => true, + 'uk' => true, + 'und' => false, + 'ur' => false, + 'ur-IN' => false, + 'uz' => false, + 'uz-Latn' => false, + 'vi' => true, + 'yo' => false, + 'yo-BJ' => false, + 'yue' => false, + 'yue-Hans' => false, + 'yue-Hant' => false, + 'zh' => false, + 'zh-Hans' => false, + 'zh-Hans-HK' => false, + 'zh-Hans-MO' => false, + 'zh-Hans-SG' => false, + 'zh-Hant' => false, + 'zh-Hant-HK' => false, + 'zh-Hant-MO' => false, + 'zu' => false, + ]; /** * Whether a locale has context transforms. */ - static public function for_locale(LocaleId $locale_id): bool + public static function for_locale(LocaleId $locale_id): bool { return self::HAS_CONTEXT_TRANSFORMS[$locale_id->value]; } @@ -419,5 +419,7 @@ static public function for_locale(LocaleId $locale_id): bool /** * @codeCoverageIgnore */ - private function __construct() {} + private function __construct() + { + } } diff --git a/lib/Locale/ListPattern.php b/lib/Locale/ListPattern.php index f61b3e1..64f727f 100644 --- a/lib/Locale/ListPattern.php +++ b/lib/Locale/ListPattern.php @@ -7,29 +7,29 @@ */ final class ListPattern { - /** - * @param array{ - * 2: string, - * start: string, - * middle: string, - * end: string, - * } $list_pattern - */ - static public function from(array $list_pattern): self - { - return new self( - $list_pattern['2'], - $list_pattern['start'], - $list_pattern['middle'], - $list_pattern['end'] - ); - } + /** + * @param array{ + * 2: string, + * start: string, + * middle: string, + * end: string, + * } $list_pattern + */ + public static function from(array $list_pattern): self + { + return new self( + $list_pattern[2], + $list_pattern['start'], + $list_pattern['middle'], + $list_pattern['end'] + ); + } - private function __construct( - public readonly string $two, - public readonly string $start, - public readonly string $middle, - public readonly string $end - ) { - } + private function __construct( + public readonly string $two, + public readonly string $start, + public readonly string $middle, + public readonly string $end + ) { + } } diff --git a/lib/LocaleId.php b/lib/LocaleId.php index b6669e5..12ad1f0 100644 --- a/lib/LocaleId.php +++ b/lib/LocaleId.php @@ -49,7 +49,7 @@ public static function assert_is_available(string $value): void */ public static function of(string $value): self { - static $instances; + static $instances; self::assert_is_available($value); @@ -69,585 +69,585 @@ private function __construct( * @link https://github.com/unicode-org/cldr-json/blob/45.0.0/cldr-json/cldr-core/supplemental/parentLocales.json */ public const PARENT_LOCALES = - [ - 'en-150' => 'en-001', - 'en-AG' => 'en-001', - 'en-AI' => 'en-001', - 'en-AU' => 'en-001', - 'en-BB' => 'en-001', - 'en-BM' => 'en-001', - 'en-BS' => 'en-001', - 'en-BW' => 'en-001', - 'en-BZ' => 'en-001', - 'en-CC' => 'en-001', - 'en-CK' => 'en-001', - 'en-CM' => 'en-001', - 'en-CX' => 'en-001', - 'en-CY' => 'en-001', - 'en-DG' => 'en-001', - 'en-DM' => 'en-001', - 'en-ER' => 'en-001', - 'en-FJ' => 'en-001', - 'en-FK' => 'en-001', - 'en-FM' => 'en-001', - 'en-GB' => 'en-001', - 'en-GD' => 'en-001', - 'en-GG' => 'en-001', - 'en-GH' => 'en-001', - 'en-GI' => 'en-001', - 'en-GM' => 'en-001', - 'en-GY' => 'en-001', - 'en-HK' => 'en-001', - 'en-ID' => 'en-001', - 'en-IE' => 'en-001', - 'en-IL' => 'en-001', - 'en-IM' => 'en-001', - 'en-IN' => 'en-001', - 'en-IO' => 'en-001', - 'en-JE' => 'en-001', - 'en-JM' => 'en-001', - 'en-KE' => 'en-001', - 'en-KI' => 'en-001', - 'en-KN' => 'en-001', - 'en-KY' => 'en-001', - 'en-LC' => 'en-001', - 'en-LR' => 'en-001', - 'en-LS' => 'en-001', - 'en-MG' => 'en-001', - 'en-MO' => 'en-001', - 'en-MS' => 'en-001', - 'en-MT' => 'en-001', - 'en-MU' => 'en-001', - 'en-MV' => 'en-001', - 'en-MW' => 'en-001', - 'en-MY' => 'en-001', - 'en-NA' => 'en-001', - 'en-NF' => 'en-001', - 'en-NG' => 'en-001', - 'en-NR' => 'en-001', - 'en-NU' => 'en-001', - 'en-NZ' => 'en-001', - 'en-PG' => 'en-001', - 'en-PK' => 'en-001', - 'en-PN' => 'en-001', - 'en-PW' => 'en-001', - 'en-RW' => 'en-001', - 'en-SB' => 'en-001', - 'en-SC' => 'en-001', - 'en-SD' => 'en-001', - 'en-SG' => 'en-001', - 'en-SH' => 'en-001', - 'en-SL' => 'en-001', - 'en-SS' => 'en-001', - 'en-SX' => 'en-001', - 'en-SZ' => 'en-001', - 'en-TC' => 'en-001', - 'en-TK' => 'en-001', - 'en-TO' => 'en-001', - 'en-TT' => 'en-001', - 'en-TV' => 'en-001', - 'en-TZ' => 'en-001', - 'en-UG' => 'en-001', - 'en-VC' => 'en-001', - 'en-VG' => 'en-001', - 'en-VU' => 'en-001', - 'en-WS' => 'en-001', - 'en-ZA' => 'en-001', - 'en-ZM' => 'en-001', - 'en-ZW' => 'en-001', - 'en-AT' => 'en-150', - 'en-BE' => 'en-150', - 'en-CH' => 'en-150', - 'en-DE' => 'en-150', - 'en-DK' => 'en-150', - 'en-FI' => 'en-150', - 'en-NL' => 'en-150', - 'en-SE' => 'en-150', - 'en-SI' => 'en-150', - 'hi-Latn' => 'en-IN', - 'es-AR' => 'es-419', - 'es-BO' => 'es-419', - 'es-BR' => 'es-419', - 'es-BZ' => 'es-419', - 'es-CL' => 'es-419', - 'es-CO' => 'es-419', - 'es-CR' => 'es-419', - 'es-CU' => 'es-419', - 'es-DO' => 'es-419', - 'es-EC' => 'es-419', - 'es-GT' => 'es-419', - 'es-HN' => 'es-419', - 'es-JP' => 'es-419', - 'es-MX' => 'es-419', - 'es-NI' => 'es-419', - 'es-PA' => 'es-419', - 'es-PE' => 'es-419', - 'es-PR' => 'es-419', - 'es-PY' => 'es-419', - 'es-SV' => 'es-419', - 'es-US' => 'es-419', - 'es-UY' => 'es-419', - 'es-VE' => 'es-419', - 'ht' => 'fr-HT', - 'nb' => 'no', - 'nn' => 'no', - 'no-NO' => 'no', - 'pt-AO' => 'pt-PT', - 'pt-CH' => 'pt-PT', - 'pt-CV' => 'pt-PT', - 'pt-FR' => 'pt-PT', - 'pt-GQ' => 'pt-PT', - 'pt-GW' => 'pt-PT', - 'pt-LU' => 'pt-PT', - 'pt-MO' => 'pt-PT', - 'pt-MZ' => 'pt-PT', - 'pt-ST' => 'pt-PT', - 'pt-TL' => 'pt-PT', - 'az-Arab' => 'und', - 'az-Cyrl' => 'und', - 'bal-Latn' => 'und', - 'blt-Latn' => 'und', - 'bm-Nkoo' => 'und', - 'bs-Cyrl' => 'und', - 'byn-Latn' => 'und', - 'cu-Glag' => 'und', - 'dje-Arab' => 'und', - 'dyo-Arab' => 'und', - 'en-Dsrt' => 'und', - 'en-Shaw' => 'und', - 'ff-Adlm' => 'und', - 'ff-Arab' => 'und', - 'ha-Arab' => 'und', - 'iu-Latn' => 'und', - 'kk-Arab' => 'und', - 'ks-Deva' => 'und', - 'ku-Arab' => 'und', - 'kxv-Deva' => 'und', - 'kxv-Orya' => 'und', - 'kxv-Telu' => 'und', - 'ky-Arab' => 'und', - 'ky-Latn' => 'und', - 'ml-Arab' => 'und', - 'mn-Mong' => 'und', - 'mni-Mtei' => 'und', - 'ms-Arab' => 'und', - 'pa-Arab' => 'und', - 'sat-Deva' => 'und', - 'sd-Deva' => 'und', - 'sd-Khoj' => 'und', - 'sd-Sind' => 'und', - 'shi-Latn' => 'und', - 'so-Arab' => 'und', - 'sr-Latn' => 'und', - 'sw-Arab' => 'und', - 'tg-Arab' => 'und', - 'ug-Cyrl' => 'und', - 'uz-Arab' => 'und', - 'uz-Cyrl' => 'und', - 'vai-Latn' => 'und', - 'wo-Arab' => 'und', - 'yo-Arab' => 'und', - 'yue-Hans' => 'und', - 'zh-Hant' => 'und', - 'zh-Hant-MO' => 'zh-Hant-HK', - ]; + [ + 'en-150' => 'en-001', + 'en-AG' => 'en-001', + 'en-AI' => 'en-001', + 'en-AU' => 'en-001', + 'en-BB' => 'en-001', + 'en-BM' => 'en-001', + 'en-BS' => 'en-001', + 'en-BW' => 'en-001', + 'en-BZ' => 'en-001', + 'en-CC' => 'en-001', + 'en-CK' => 'en-001', + 'en-CM' => 'en-001', + 'en-CX' => 'en-001', + 'en-CY' => 'en-001', + 'en-DG' => 'en-001', + 'en-DM' => 'en-001', + 'en-ER' => 'en-001', + 'en-FJ' => 'en-001', + 'en-FK' => 'en-001', + 'en-FM' => 'en-001', + 'en-GB' => 'en-001', + 'en-GD' => 'en-001', + 'en-GG' => 'en-001', + 'en-GH' => 'en-001', + 'en-GI' => 'en-001', + 'en-GM' => 'en-001', + 'en-GY' => 'en-001', + 'en-HK' => 'en-001', + 'en-ID' => 'en-001', + 'en-IE' => 'en-001', + 'en-IL' => 'en-001', + 'en-IM' => 'en-001', + 'en-IN' => 'en-001', + 'en-IO' => 'en-001', + 'en-JE' => 'en-001', + 'en-JM' => 'en-001', + 'en-KE' => 'en-001', + 'en-KI' => 'en-001', + 'en-KN' => 'en-001', + 'en-KY' => 'en-001', + 'en-LC' => 'en-001', + 'en-LR' => 'en-001', + 'en-LS' => 'en-001', + 'en-MG' => 'en-001', + 'en-MO' => 'en-001', + 'en-MS' => 'en-001', + 'en-MT' => 'en-001', + 'en-MU' => 'en-001', + 'en-MV' => 'en-001', + 'en-MW' => 'en-001', + 'en-MY' => 'en-001', + 'en-NA' => 'en-001', + 'en-NF' => 'en-001', + 'en-NG' => 'en-001', + 'en-NR' => 'en-001', + 'en-NU' => 'en-001', + 'en-NZ' => 'en-001', + 'en-PG' => 'en-001', + 'en-PK' => 'en-001', + 'en-PN' => 'en-001', + 'en-PW' => 'en-001', + 'en-RW' => 'en-001', + 'en-SB' => 'en-001', + 'en-SC' => 'en-001', + 'en-SD' => 'en-001', + 'en-SG' => 'en-001', + 'en-SH' => 'en-001', + 'en-SL' => 'en-001', + 'en-SS' => 'en-001', + 'en-SX' => 'en-001', + 'en-SZ' => 'en-001', + 'en-TC' => 'en-001', + 'en-TK' => 'en-001', + 'en-TO' => 'en-001', + 'en-TT' => 'en-001', + 'en-TV' => 'en-001', + 'en-TZ' => 'en-001', + 'en-UG' => 'en-001', + 'en-VC' => 'en-001', + 'en-VG' => 'en-001', + 'en-VU' => 'en-001', + 'en-WS' => 'en-001', + 'en-ZA' => 'en-001', + 'en-ZM' => 'en-001', + 'en-ZW' => 'en-001', + 'en-AT' => 'en-150', + 'en-BE' => 'en-150', + 'en-CH' => 'en-150', + 'en-DE' => 'en-150', + 'en-DK' => 'en-150', + 'en-FI' => 'en-150', + 'en-NL' => 'en-150', + 'en-SE' => 'en-150', + 'en-SI' => 'en-150', + 'hi-Latn' => 'en-IN', + 'es-AR' => 'es-419', + 'es-BO' => 'es-419', + 'es-BR' => 'es-419', + 'es-BZ' => 'es-419', + 'es-CL' => 'es-419', + 'es-CO' => 'es-419', + 'es-CR' => 'es-419', + 'es-CU' => 'es-419', + 'es-DO' => 'es-419', + 'es-EC' => 'es-419', + 'es-GT' => 'es-419', + 'es-HN' => 'es-419', + 'es-JP' => 'es-419', + 'es-MX' => 'es-419', + 'es-NI' => 'es-419', + 'es-PA' => 'es-419', + 'es-PE' => 'es-419', + 'es-PR' => 'es-419', + 'es-PY' => 'es-419', + 'es-SV' => 'es-419', + 'es-US' => 'es-419', + 'es-UY' => 'es-419', + 'es-VE' => 'es-419', + 'ht' => 'fr-HT', + 'nb' => 'no', + 'nn' => 'no', + 'no-NO' => 'no', + 'pt-AO' => 'pt-PT', + 'pt-CH' => 'pt-PT', + 'pt-CV' => 'pt-PT', + 'pt-FR' => 'pt-PT', + 'pt-GQ' => 'pt-PT', + 'pt-GW' => 'pt-PT', + 'pt-LU' => 'pt-PT', + 'pt-MO' => 'pt-PT', + 'pt-MZ' => 'pt-PT', + 'pt-ST' => 'pt-PT', + 'pt-TL' => 'pt-PT', + 'az-Arab' => 'und', + 'az-Cyrl' => 'und', + 'bal-Latn' => 'und', + 'blt-Latn' => 'und', + 'bm-Nkoo' => 'und', + 'bs-Cyrl' => 'und', + 'byn-Latn' => 'und', + 'cu-Glag' => 'und', + 'dje-Arab' => 'und', + 'dyo-Arab' => 'und', + 'en-Dsrt' => 'und', + 'en-Shaw' => 'und', + 'ff-Adlm' => 'und', + 'ff-Arab' => 'und', + 'ha-Arab' => 'und', + 'iu-Latn' => 'und', + 'kk-Arab' => 'und', + 'ks-Deva' => 'und', + 'ku-Arab' => 'und', + 'kxv-Deva' => 'und', + 'kxv-Orya' => 'und', + 'kxv-Telu' => 'und', + 'ky-Arab' => 'und', + 'ky-Latn' => 'und', + 'ml-Arab' => 'und', + 'mn-Mong' => 'und', + 'mni-Mtei' => 'und', + 'ms-Arab' => 'und', + 'pa-Arab' => 'und', + 'sat-Deva' => 'und', + 'sd-Deva' => 'und', + 'sd-Khoj' => 'und', + 'sd-Sind' => 'und', + 'shi-Latn' => 'und', + 'so-Arab' => 'und', + 'sr-Latn' => 'und', + 'sw-Arab' => 'und', + 'tg-Arab' => 'und', + 'ug-Cyrl' => 'und', + 'uz-Arab' => 'und', + 'uz-Cyrl' => 'und', + 'vai-Latn' => 'und', + 'wo-Arab' => 'und', + 'yo-Arab' => 'und', + 'yue-Hans' => 'und', + 'zh-Hant' => 'und', + 'zh-Hant-MO' => 'zh-Hant-HK', + ]; /** * @link https://github.com/unicode-org/cldr-json/blob/45.0.0/cldr-json/cldr-core/availableLocales.json */ public const AVAILABLE_LOCALES = - [ - 'af', - 'af-NA', - 'am', - 'ar', - 'ar-AE', - 'ar-BH', - 'ar-DJ', - 'ar-DZ', - 'ar-EG', - 'ar-EH', - 'ar-ER', - 'ar-IL', - 'ar-IQ', - 'ar-JO', - 'ar-KM', - 'ar-KW', - 'ar-LB', - 'ar-LY', - 'ar-MA', - 'ar-MR', - 'ar-OM', - 'ar-PS', - 'ar-QA', - 'ar-SA', - 'ar-SD', - 'ar-SO', - 'ar-SS', - 'ar-SY', - 'ar-TD', - 'ar-TN', - 'ar-YE', - 'as', - 'az', - 'az-Latn', - 'be', - 'be-tarask', - 'bg', - 'bn', - 'bn-IN', - 'bs', - 'bs-Latn', - 'ca', - 'ca-AD', - 'ca-ES-valencia', - 'ca-FR', - 'ca-IT', - 'chr', - 'cs', - 'cy', - 'da', - 'da-GL', - 'de', - 'de-AT', - 'de-BE', - 'de-CH', - 'de-IT', - 'de-LI', - 'de-LU', - 'dsb', - 'el', - 'el-CY', - 'el-polyton', - 'en', - 'en-001', - 'en-150', - 'en-AE', - 'en-AG', - 'en-AI', - 'en-AS', - 'en-AT', - 'en-AU', - 'en-BB', - 'en-BE', - 'en-BI', - 'en-BM', - 'en-BS', - 'en-BW', - 'en-BZ', - 'en-CA', - 'en-CC', - 'en-CH', - 'en-CK', - 'en-CM', - 'en-CX', - 'en-CY', - 'en-DE', - 'en-DG', - 'en-DK', - 'en-DM', - 'en-ER', - 'en-FI', - 'en-FJ', - 'en-FK', - 'en-FM', - 'en-GB', - 'en-GD', - 'en-GG', - 'en-GH', - 'en-GI', - 'en-GM', - 'en-GU', - 'en-GY', - 'en-HK', - 'en-ID', - 'en-IE', - 'en-IL', - 'en-IM', - 'en-IN', - 'en-IO', - 'en-JE', - 'en-JM', - 'en-KE', - 'en-KI', - 'en-KN', - 'en-KY', - 'en-LC', - 'en-LR', - 'en-LS', - 'en-MG', - 'en-MH', - 'en-MO', - 'en-MP', - 'en-MS', - 'en-MT', - 'en-MU', - 'en-MV', - 'en-MW', - 'en-MY', - 'en-NA', - 'en-NF', - 'en-NG', - 'en-NL', - 'en-NR', - 'en-NU', - 'en-NZ', - 'en-PG', - 'en-PH', - 'en-PK', - 'en-PN', - 'en-PR', - 'en-PW', - 'en-RW', - 'en-SB', - 'en-SC', - 'en-SD', - 'en-SE', - 'en-SG', - 'en-SH', - 'en-SI', - 'en-SL', - 'en-SS', - 'en-SX', - 'en-SZ', - 'en-TC', - 'en-TK', - 'en-TO', - 'en-TT', - 'en-TV', - 'en-TZ', - 'en-UG', - 'en-UM', - 'en-VC', - 'en-VG', - 'en-VI', - 'en-VU', - 'en-WS', - 'en-ZA', - 'en-ZM', - 'en-ZW', - 'es', - 'es-419', - 'es-AR', - 'es-BO', - 'es-BR', - 'es-BZ', - 'es-CL', - 'es-CO', - 'es-CR', - 'es-CU', - 'es-DO', - 'es-EA', - 'es-EC', - 'es-GQ', - 'es-GT', - 'es-HN', - 'es-IC', - 'es-MX', - 'es-NI', - 'es-PA', - 'es-PE', - 'es-PH', - 'es-PR', - 'es-PY', - 'es-SV', - 'es-US', - 'es-UY', - 'es-VE', - 'et', - 'eu', - 'fa', - 'fa-AF', - 'fi', - 'fil', - 'fr', - 'fr-BE', - 'fr-BF', - 'fr-BI', - 'fr-BJ', - 'fr-BL', - 'fr-CA', - 'fr-CD', - 'fr-CF', - 'fr-CG', - 'fr-CH', - 'fr-CI', - 'fr-CM', - 'fr-DJ', - 'fr-DZ', - 'fr-GA', - 'fr-GF', - 'fr-GN', - 'fr-GP', - 'fr-GQ', - 'fr-HT', - 'fr-KM', - 'fr-LU', - 'fr-MA', - 'fr-MC', - 'fr-MF', - 'fr-MG', - 'fr-ML', - 'fr-MQ', - 'fr-MR', - 'fr-MU', - 'fr-NC', - 'fr-NE', - 'fr-PF', - 'fr-PM', - 'fr-RE', - 'fr-RW', - 'fr-SC', - 'fr-SN', - 'fr-SY', - 'fr-TD', - 'fr-TG', - 'fr-TN', - 'fr-VU', - 'fr-WF', - 'fr-YT', - 'ga', - 'ga-GB', - 'gd', - 'gl', - 'gu', - 'ha', - 'ha-GH', - 'ha-NE', - 'he', - 'hi', - 'hi-Latn', - 'hr', - 'hr-BA', - 'hsb', - 'hu', - 'hy', - 'id', - 'ig', - 'is', - 'it', - 'it-CH', - 'it-SM', - 'it-VA', - 'ja', - 'jv', - 'ka', - 'kk', - 'km', - 'kn', - 'ko', - 'ko-CN', - 'ko-KP', - 'kok', - 'ky', - 'lo', - 'lt', - 'lv', - 'mk', - 'ml', - 'mn', - 'mr', - 'ms', - 'ms-BN', - 'ms-ID', - 'ms-SG', - 'my', - 'nb', - 'nb-SJ', - 'ne', - 'ne-IN', - 'nl', - 'nl-AW', - 'nl-BE', - 'nl-BQ', - 'nl-CW', - 'nl-SR', - 'nl-SX', - 'nn', - 'no', - 'or', - 'pa', - 'pa-Guru', - 'pl', - 'ps', - 'ps-PK', - 'pt', - 'pt-AO', - 'pt-CH', - 'pt-CV', - 'pt-GQ', - 'pt-GW', - 'pt-LU', - 'pt-MO', - 'pt-MZ', - 'pt-PT', - 'pt-ST', - 'pt-TL', - 'ro', - 'ro-MD', - 'ru', - 'ru-BY', - 'ru-KG', - 'ru-KZ', - 'ru-MD', - 'ru-UA', - 'sd', - 'sd-Arab', - 'si', - 'sk', - 'sl', - 'so', - 'so-DJ', - 'so-ET', - 'so-KE', - 'sq', - 'sq-MK', - 'sq-XK', - 'sr', - 'sr-Cyrl', - 'sr-Cyrl-BA', - 'sr-Cyrl-ME', - 'sr-Cyrl-XK', - 'sr-Latn', - 'sr-Latn-BA', - 'sr-Latn-ME', - 'sr-Latn-XK', - 'sv', - 'sv-AX', - 'sv-FI', - 'sw', - 'sw-CD', - 'sw-KE', - 'sw-UG', - 'ta', - 'ta-LK', - 'ta-MY', - 'ta-SG', - 'te', - 'th', - 'tk', - 'tr', - 'tr-CY', - 'uk', - 'und', - 'ur', - 'ur-IN', - 'uz', - 'uz-Latn', - 'vi', - 'yo', - 'yo-BJ', - 'yue', - 'yue-Hans', - 'yue-Hant', - 'zh', - 'zh-Hans', - 'zh-Hans-HK', - 'zh-Hans-MO', - 'zh-Hans-SG', - 'zh-Hant', - 'zh-Hant-HK', - 'zh-Hant-MO', - 'zu', - ]; + [ + 'af', + 'af-NA', + 'am', + 'ar', + 'ar-AE', + 'ar-BH', + 'ar-DJ', + 'ar-DZ', + 'ar-EG', + 'ar-EH', + 'ar-ER', + 'ar-IL', + 'ar-IQ', + 'ar-JO', + 'ar-KM', + 'ar-KW', + 'ar-LB', + 'ar-LY', + 'ar-MA', + 'ar-MR', + 'ar-OM', + 'ar-PS', + 'ar-QA', + 'ar-SA', + 'ar-SD', + 'ar-SO', + 'ar-SS', + 'ar-SY', + 'ar-TD', + 'ar-TN', + 'ar-YE', + 'as', + 'az', + 'az-Latn', + 'be', + 'be-tarask', + 'bg', + 'bn', + 'bn-IN', + 'bs', + 'bs-Latn', + 'ca', + 'ca-AD', + 'ca-ES-valencia', + 'ca-FR', + 'ca-IT', + 'chr', + 'cs', + 'cy', + 'da', + 'da-GL', + 'de', + 'de-AT', + 'de-BE', + 'de-CH', + 'de-IT', + 'de-LI', + 'de-LU', + 'dsb', + 'el', + 'el-CY', + 'el-polyton', + 'en', + 'en-001', + 'en-150', + 'en-AE', + 'en-AG', + 'en-AI', + 'en-AS', + 'en-AT', + 'en-AU', + 'en-BB', + 'en-BE', + 'en-BI', + 'en-BM', + 'en-BS', + 'en-BW', + 'en-BZ', + 'en-CA', + 'en-CC', + 'en-CH', + 'en-CK', + 'en-CM', + 'en-CX', + 'en-CY', + 'en-DE', + 'en-DG', + 'en-DK', + 'en-DM', + 'en-ER', + 'en-FI', + 'en-FJ', + 'en-FK', + 'en-FM', + 'en-GB', + 'en-GD', + 'en-GG', + 'en-GH', + 'en-GI', + 'en-GM', + 'en-GU', + 'en-GY', + 'en-HK', + 'en-ID', + 'en-IE', + 'en-IL', + 'en-IM', + 'en-IN', + 'en-IO', + 'en-JE', + 'en-JM', + 'en-KE', + 'en-KI', + 'en-KN', + 'en-KY', + 'en-LC', + 'en-LR', + 'en-LS', + 'en-MG', + 'en-MH', + 'en-MO', + 'en-MP', + 'en-MS', + 'en-MT', + 'en-MU', + 'en-MV', + 'en-MW', + 'en-MY', + 'en-NA', + 'en-NF', + 'en-NG', + 'en-NL', + 'en-NR', + 'en-NU', + 'en-NZ', + 'en-PG', + 'en-PH', + 'en-PK', + 'en-PN', + 'en-PR', + 'en-PW', + 'en-RW', + 'en-SB', + 'en-SC', + 'en-SD', + 'en-SE', + 'en-SG', + 'en-SH', + 'en-SI', + 'en-SL', + 'en-SS', + 'en-SX', + 'en-SZ', + 'en-TC', + 'en-TK', + 'en-TO', + 'en-TT', + 'en-TV', + 'en-TZ', + 'en-UG', + 'en-UM', + 'en-VC', + 'en-VG', + 'en-VI', + 'en-VU', + 'en-WS', + 'en-ZA', + 'en-ZM', + 'en-ZW', + 'es', + 'es-419', + 'es-AR', + 'es-BO', + 'es-BR', + 'es-BZ', + 'es-CL', + 'es-CO', + 'es-CR', + 'es-CU', + 'es-DO', + 'es-EA', + 'es-EC', + 'es-GQ', + 'es-GT', + 'es-HN', + 'es-IC', + 'es-MX', + 'es-NI', + 'es-PA', + 'es-PE', + 'es-PH', + 'es-PR', + 'es-PY', + 'es-SV', + 'es-US', + 'es-UY', + 'es-VE', + 'et', + 'eu', + 'fa', + 'fa-AF', + 'fi', + 'fil', + 'fr', + 'fr-BE', + 'fr-BF', + 'fr-BI', + 'fr-BJ', + 'fr-BL', + 'fr-CA', + 'fr-CD', + 'fr-CF', + 'fr-CG', + 'fr-CH', + 'fr-CI', + 'fr-CM', + 'fr-DJ', + 'fr-DZ', + 'fr-GA', + 'fr-GF', + 'fr-GN', + 'fr-GP', + 'fr-GQ', + 'fr-HT', + 'fr-KM', + 'fr-LU', + 'fr-MA', + 'fr-MC', + 'fr-MF', + 'fr-MG', + 'fr-ML', + 'fr-MQ', + 'fr-MR', + 'fr-MU', + 'fr-NC', + 'fr-NE', + 'fr-PF', + 'fr-PM', + 'fr-RE', + 'fr-RW', + 'fr-SC', + 'fr-SN', + 'fr-SY', + 'fr-TD', + 'fr-TG', + 'fr-TN', + 'fr-VU', + 'fr-WF', + 'fr-YT', + 'ga', + 'ga-GB', + 'gd', + 'gl', + 'gu', + 'ha', + 'ha-GH', + 'ha-NE', + 'he', + 'hi', + 'hi-Latn', + 'hr', + 'hr-BA', + 'hsb', + 'hu', + 'hy', + 'id', + 'ig', + 'is', + 'it', + 'it-CH', + 'it-SM', + 'it-VA', + 'ja', + 'jv', + 'ka', + 'kk', + 'km', + 'kn', + 'ko', + 'ko-CN', + 'ko-KP', + 'kok', + 'ky', + 'lo', + 'lt', + 'lv', + 'mk', + 'ml', + 'mn', + 'mr', + 'ms', + 'ms-BN', + 'ms-ID', + 'ms-SG', + 'my', + 'nb', + 'nb-SJ', + 'ne', + 'ne-IN', + 'nl', + 'nl-AW', + 'nl-BE', + 'nl-BQ', + 'nl-CW', + 'nl-SR', + 'nl-SX', + 'nn', + 'no', + 'or', + 'pa', + 'pa-Guru', + 'pl', + 'ps', + 'ps-PK', + 'pt', + 'pt-AO', + 'pt-CH', + 'pt-CV', + 'pt-GQ', + 'pt-GW', + 'pt-LU', + 'pt-MO', + 'pt-MZ', + 'pt-PT', + 'pt-ST', + 'pt-TL', + 'ro', + 'ro-MD', + 'ru', + 'ru-BY', + 'ru-KG', + 'ru-KZ', + 'ru-MD', + 'ru-UA', + 'sd', + 'sd-Arab', + 'si', + 'sk', + 'sl', + 'so', + 'so-DJ', + 'so-ET', + 'so-KE', + 'sq', + 'sq-MK', + 'sq-XK', + 'sr', + 'sr-Cyrl', + 'sr-Cyrl-BA', + 'sr-Cyrl-ME', + 'sr-Cyrl-XK', + 'sr-Latn', + 'sr-Latn-BA', + 'sr-Latn-ME', + 'sr-Latn-XK', + 'sv', + 'sv-AX', + 'sv-FI', + 'sw', + 'sw-CD', + 'sw-KE', + 'sw-UG', + 'ta', + 'ta-LK', + 'ta-MY', + 'ta-SG', + 'te', + 'th', + 'tk', + 'tr', + 'tr-CY', + 'uk', + 'und', + 'ur', + 'ur-IN', + 'uz', + 'uz-Latn', + 'vi', + 'yo', + 'yo-BJ', + 'yue', + 'yue-Hans', + 'yue-Hant', + 'zh', + 'zh-Hans', + 'zh-Hans-HK', + 'zh-Hans-MO', + 'zh-Hans-SG', + 'zh-Hant', + 'zh-Hant-HK', + 'zh-Hant-MO', + 'zu', + ]; } diff --git a/lib/LocaleNotAvailable.php b/lib/LocaleNotAvailable.php index 13c8f5b..eccaafd 100644 --- a/lib/LocaleNotAvailable.php +++ b/lib/LocaleNotAvailable.php @@ -12,16 +12,16 @@ */ final class LocaleNotAvailable extends InvalidArgumentException implements Exception { - /** - * @param string $locale_id - * A locale ID. - */ + /** + * @param string $locale_id + * A locale ID. + */ public function __construct( - public readonly string $locale_id, - string $message = null, - Throwable $previous = null - ) { - $message ??= "Locale ID is not available: $locale_id"; + public readonly string $locale_id, + string $message = null, + Throwable $previous = null + ) { + $message ??= "Locale ID is not available: $locale_id"; parent::__construct($message, previous: $previous); } diff --git a/lib/Localizable.php b/lib/Localizable.php index 65af458..9dfb04f 100644 --- a/lib/Localizable.php +++ b/lib/Localizable.php @@ -10,10 +10,10 @@ */ interface Localizable { - /** - * Localize the instance. - * - * @return TLocalized&LocalizedObject - */ - public function localized(Locale $locale): LocalizedObject; + /** + * Localize the instance. + * + * @return TLocalized&LocalizedObject + */ + public function localized(Locale $locale): LocalizedObject; } diff --git a/lib/LocalizedCurrency.php b/lib/LocalizedCurrency.php index e8dfcdd..dd2b537 100644 --- a/lib/LocalizedCurrency.php +++ b/lib/LocalizedCurrency.php @@ -12,65 +12,64 @@ */ class LocalizedCurrency extends LocalizedObjectWithFormatter { - /** - * @return LocalizedCurrencyFormatter - */ - protected function lazy_get_formatter(): Formatter - { - return $this->locale->currency_formatter; - } + /** + * @return LocalizedCurrencyFormatter + */ + protected function lazy_get_formatter(): Formatter + { + return $this->locale->currency_formatter; + } - /** - * @uses get_name - */ - protected function get_name(): string - { - return $this->name_for(); - } + /** + * @uses get_name + */ + protected function get_name(): string + { + return $this->name_for(); + } - /** - * Returns the localized name of the currency. - * - * @param int|null $count Used for pluralization. - */ - public function name_for(int $count = null): string - { - $offset = 'displayName'; + /** + * Returns the localized name of the currency. + * + * @param int|null $count Used for pluralization. + */ + public function name_for(int $count = null): string + { + $offset = 'displayName'; - if ($count == 1) - { - $offset .= '-count-one'; - } - else if ($count) - { - $offset .= '-count-other'; - } + if ($count == 1) { + $offset .= '-count-one'; + } else { + if ($count) { + $offset .= '-count-other'; + } + } - /** @phpstan-ignore-next-line */ - return $this->locale['currencies'][$this->target->code][$offset]; - } + /** @phpstan-ignore-next-line */ + return $this->locale['currencies'][$this->target->code][$offset]; + } - private string $symbol; + private string $symbol; - /** - * Returns the localized symbol of the currency. - * - * @uses get_symbol - */ - protected function get_symbol(): string - { - return $this->symbol ??= $this->locale['currencies'][$this->target->code]['symbol']; // @phpstan-ignore-line - } + /** + * Returns the localized symbol of the currency. + * + * @uses get_symbol + */ + protected function get_symbol(): string + { + return $this->symbol ??= $this->locale['currencies'][$this->target->code]['symbol']; // @phpstan-ignore-line + } - /** - * Formats currency using localized conventions. - * - * @param float|int|numeric-string $number - */ - public function format( - float|int|string $number, - string $pattern = LocalizedCurrencyFormatter::PATTERN_STANDARD - ): string { - return $this->formatter->format($number, $this->target, $pattern); - } + /** + * Formats currency using localized conventions. + * + * @param float|int|numeric-string $number + */ + public function format( + float|int|string $number, + string $pattern = LocalizedCurrencyFormatter::PATTERN_STANDARD + ): string { + return $this->formatter->format($number, $this->target, $pattern); + } } diff --git a/lib/LocalizedCurrencyFormatter.php b/lib/LocalizedCurrencyFormatter.php index b633855..dbe0a31 100644 --- a/lib/LocalizedCurrencyFormatter.php +++ b/lib/LocalizedCurrencyFormatter.php @@ -9,45 +9,45 @@ */ class LocalizedCurrencyFormatter extends LocalizedObject implements Formatter { - public const PATTERN_STANDARD = 'standard'; - public const PATTERN_ACCOUNTING = 'accounting'; + public const PATTERN_STANDARD = 'standard'; + public const PATTERN_ACCOUNTING = 'accounting'; - /** - * Formats currency using localized conventions. - * - * @param float|int|numeric-string $number - * @param string|Currency $currency A {@see Currency} or currency code. - */ - public function format( - float|int|string $number, - Currency|string $currency, - string $pattern = self::PATTERN_STANDARD - ): string { - return $this->target->format( - $number, - $this->resolve_pattern($pattern), - $this->locale->numbers->symbols, - $this->resolve_currency_symbol($currency) - ); - } + /** + * Formats currency using localized conventions. + * + * @param float|int|numeric-string $number + * @param string|Currency $currency A {@see Currency} or currency code. + */ + public function format( + float|int|string $number, + Currency|string $currency, + string $pattern = self::PATTERN_STANDARD + ): string { + return $this->target->format( + $number, + $this->resolve_pattern($pattern), + $this->locale->numbers->symbols, + $this->resolve_currency_symbol($currency) + ); + } - private function resolve_currency_symbol(string $currency): string - { - return $this->locale['currencies'][$currency]['symbol'] ?? $currency; - } + private function resolve_currency_symbol(string $currency): string + { + return $this->locale['currencies'][$currency]['symbol'] ?? $currency; + } - /** - * Resolves a pattern. - * - * The special patterns {@link PATTERN_STANDARD} and {@link PATTERN_ACCOUNTING} are resolved - * from the currency formats. - */ - private function resolve_pattern(string $pattern): string - { - return match ($pattern) { - self::PATTERN_STANDARD => $this->locale->numbers->currency_formats['standard'], - self::PATTERN_ACCOUNTING => $this->locale->numbers->currency_formats['accounting'], - default => $pattern, - }; - } + /** + * Resolves a pattern. + * + * The special patterns {@link PATTERN_STANDARD} and {@link PATTERN_ACCOUNTING} are resolved + * from the currency formats. + */ + private function resolve_pattern(string $pattern): string + { + return match ($pattern) { + self::PATTERN_STANDARD => $this->locale->numbers->currency_formats['standard'], + self::PATTERN_ACCOUNTING => $this->locale->numbers->currency_formats['accounting'], + default => $pattern, + }; + } } diff --git a/lib/LocalizedDateTime.php b/lib/LocalizedDateTime.php index 4397795..04d3e0f 100644 --- a/lib/LocalizedDateTime.php +++ b/lib/LocalizedDateTime.php @@ -34,114 +34,109 @@ */ final class LocalizedDateTime extends LocalizedObjectWithFormatter { - /** - * @inheritDoc - * - * @return DateTimeFormatter - */ - protected function lazy_get_formatter(): Formatter - { - return $this->locale->calendar->datetime_formatter; - } - - /** - * @param string $property - * - * @return mixed - */ - public function __get($property) - { - if (str_starts_with($property, 'as_')) - { - return $this->{ 'format_' . $property }(); - } - - try - { - return parent::__get($property); - } - catch (PropertyNotDefined) - { - return $this->target->$property; - } - } - - /** - * @param string $property - * @param mixed $value - */ - public function __set($property, $value): void - { - $this->target->$property = $value; - } - - /** - * @param string $method - * @param array $arguments - * - * @return mixed - * - * @throws \Exception - */ - public function __call($method, $arguments) - { - return $this->target->$method(...$arguments); - } - - public function __toString(): string - { - $target = $this->target; - - if (method_exists($target, __FUNCTION__)) - { - return (string) $target; - } - - // `ATOM` is used instead of `ISO8601` because of a bug in the pattern - // @see http://php.net/manual/en/class.datetime.php#datetime.constants.iso8601 - - return $this->target->format(DateTime::ATOM); - } - - /** - * @inheritDoc - * - * @throws \Exception - */ - public function format(string|DateTimeFormatLength|DateTimeFormatId $pattern): string - { - return $this->formatter->format($this->target, $pattern); - } - - /** - * Formats the instance according to the {@link DateTimeFormatLength::FULL} length. - */ - public function format_as_full(): string - { - return $this->format(DateTimeFormatLength::FULL); - } - - /** - * Formats the instance according to the {@link DateTimeFormatLength::LONG} length. - */ - public function format_as_long(): string - { - return $this->format(DateTimeFormatLength::LONG); - } - - /** - * Formats the instance according to the {@link DateTimeFormatLength::MEDIUM} length. - */ - public function format_as_medium(): string - { - return $this->format(DateTimeFormatLength::MEDIUM); - } - - /** - * Formats the instance according to the {@link DateTimeFormatLength::SHORT} length. - */ - public function format_as_short(): string - { - return $this->format(DateTimeFormatLength::SHORT); - } + /** + * @inheritDoc + * + * @return DateTimeFormatter + */ + protected function lazy_get_formatter(): Formatter + { + return $this->locale->calendar->datetime_formatter; + } + + /** + * @param string $property + * + * @return mixed + */ + public function __get($property) + { + if (str_starts_with($property, 'as_')) { + return $this->{'format_' . $property}(); + } + + try { + return parent::__get($property); + } catch (PropertyNotDefined) { + return $this->target->$property; + } + } + + /** + * @param string $property + * @param mixed $value + */ + public function __set($property, $value): void + { + $this->target->$property = $value; + } + + /** + * @param string $method + * @param array $arguments + * + * @return mixed + * + * @throws \Exception + */ + public function __call($method, $arguments) + { + return $this->target->$method(...$arguments); + } + + public function __toString(): string + { + $target = $this->target; + + if (method_exists($target, __FUNCTION__)) { + return (string)$target; + } + + // `ATOM` is used instead of `ISO8601` because of a bug in the pattern + // @see http://php.net/manual/en/class.datetime.php#datetime.constants.iso8601 + + return $this->target->format(DateTime::ATOM); + } + + /** + * @inheritDoc + * + * @throws \Exception + */ + public function format(string|DateTimeFormatLength|DateTimeFormatId $pattern): string + { + return $this->formatter->format($this->target, $pattern); + } + + /** + * Formats the instance according to the {@link DateTimeFormatLength::FULL} length. + */ + public function format_as_full(): string + { + return $this->format(DateTimeFormatLength::FULL); + } + + /** + * Formats the instance according to the {@link DateTimeFormatLength::LONG} length. + */ + public function format_as_long(): string + { + return $this->format(DateTimeFormatLength::LONG); + } + + /** + * Formats the instance according to the {@link DateTimeFormatLength::MEDIUM} length. + */ + public function format_as_medium(): string + { + return $this->format(DateTimeFormatLength::MEDIUM); + } + + /** + * Formats the instance according to the {@link DateTimeFormatLength::SHORT} length. + */ + public function format_as_short(): string + { + return $this->format(DateTimeFormatLength::SHORT); + } } diff --git a/lib/LocalizedListFormatter.php b/lib/LocalizedListFormatter.php index e1d6f3d..0e8b842 100644 --- a/lib/LocalizedListFormatter.php +++ b/lib/LocalizedListFormatter.php @@ -13,16 +13,16 @@ */ class LocalizedListFormatter extends LocalizedObject implements Formatter { - /** - * Formats variable-length lists of scalars. - * - * @param scalar[] $list - */ - public function format(array $list, ListType $type = ListType::STANDARD): string - { - /** @phpstan-ignore-next-line */ - $list_pattern = ListPattern::from($this->locale['listPatterns']["listPattern-type-$type->value"]); + /** + * Formats variable-length lists of scalars. + * + * @param scalar[] $list + */ + public function format(array $list, ListType $type = ListType::STANDARD): string + { + /** @phpstan-ignore-next-line */ + $list_pattern = ListPattern::from($this->locale['listPatterns']["listPattern-type-$type->value"]); - return $this->target->format($list, $list_pattern); - } + return $this->target->format($list, $list_pattern); + } } diff --git a/lib/LocalizedLocale.php b/lib/LocalizedLocale.php index a2d3143..895ff0a 100644 --- a/lib/LocalizedLocale.php +++ b/lib/LocalizedLocale.php @@ -12,12 +12,12 @@ */ class LocalizedLocale extends LocalizedObject { - /** - * @uses get_name - */ - protected function get_name(): string - { - /** @phpstan-ignore-next-line */ - return $this->locale['languages'][$this->target->id->value]; - } + /** + * @uses get_name + */ + protected function get_name(): string + { + /** @phpstan-ignore-next-line */ + return $this->locale['languages'][$this->target->id->value]; + } } diff --git a/lib/LocalizedNumberFormatter.php b/lib/LocalizedNumberFormatter.php index 4af87f3..b853b20 100644 --- a/lib/LocalizedNumberFormatter.php +++ b/lib/LocalizedNumberFormatter.php @@ -9,15 +9,15 @@ */ class LocalizedNumberFormatter extends LocalizedObject implements Formatter { - /** - * Formats a number. - * - * @param float|int|numeric-string $number - */ - public function format(float|int|string $number, string $pattern = null): string - { - $numbers = $this->locale->numbers; + /** + * Formats a number. + * + * @param float|int|numeric-string $number + */ + public function format(float|int|string $number, string $pattern = null): string + { + $numbers = $this->locale->numbers; - return $this->target->format($number, $pattern ?? $numbers->decimal_format, $numbers->symbols); - } + return $this->target->format($number, $pattern ?? $numbers->decimal_format, $numbers->symbols); + } } diff --git a/lib/LocalizedObject.php b/lib/LocalizedObject.php index 0ff8fa4..b8a9a71 100644 --- a/lib/LocalizedObject.php +++ b/lib/LocalizedObject.php @@ -11,18 +11,18 @@ */ abstract class LocalizedObject { - /** - * @uses get_target - * @uses get_locale - */ - use AccessorTrait; + /** + * @uses get_target + * @uses get_locale + */ + use AccessorTrait; - /** - * @phpstan-param T $target The object to localize. - */ - public function __construct( - public readonly object $target, - public readonly Locale $locale, - ) { - } + /** + * @phpstan-param T $target The object to localize. + */ + public function __construct( + public readonly object $target, + public readonly Locale $locale, + ) { + } } diff --git a/lib/LocalizedObjectWithFormatter.php b/lib/LocalizedObjectWithFormatter.php index b5346aa..79be713 100644 --- a/lib/LocalizedObjectWithFormatter.php +++ b/lib/LocalizedObjectWithFormatter.php @@ -17,29 +17,29 @@ */ abstract class LocalizedObjectWithFormatter extends LocalizedObject { - /** - * @var TFormatter|null - */ - private $formatter; + /** + * @var TFormatter|null + */ + private $formatter; - /** - * @param string $property - * - * @return mixed - */ - public function __get($property) - { - if ($property === 'formatter') { - return $this->formatter ??= $this->lazy_get_formatter(); - } + /** + * @param string $property + * + * @return mixed + */ + public function __get($property) + { + if ($property === 'formatter') { + return $this->formatter ??= $this->lazy_get_formatter(); + } - return parent::__get($property); - } + return parent::__get($property); + } - /** - * Returns the formatter used to format the target object. - * - * @return TFormatter - */ - abstract protected function lazy_get_formatter(): Formatter; + /** + * Returns the formatter used to format the target object. + * + * @return TFormatter + */ + abstract protected function lazy_get_formatter(): Formatter; } diff --git a/lib/LocalizedTerritory.php b/lib/LocalizedTerritory.php index cfba879..28488ba 100644 --- a/lib/LocalizedTerritory.php +++ b/lib/LocalizedTerritory.php @@ -12,12 +12,12 @@ */ class LocalizedTerritory extends LocalizedObject { - /** - * @uses get_name - */ - protected function get_name(): string - { - /** @phpstan-ignore-next-line */ - return $this->locale['territories'][$this->target->code->value]; - } + /** + * @uses get_name + */ + protected function get_name(): string + { + /** @phpstan-ignore-next-line */ + return $this->locale['territories'][$this->target->code->value]; + } } diff --git a/lib/Number.php b/lib/Number.php index fb871dd..260861c 100644 --- a/lib/Number.php +++ b/lib/Number.php @@ -14,92 +14,88 @@ final class Number { - /** - * Returns the precision of a number. - * - * @param float|int|numeric-string $number - */ - static public function precision_from(float|int|string $number): int - { - $number = (string) $number; - $pos = strrpos($number, '.'); - - if (!$pos) - { - return 0; - } - - return strlen($number) - $pos - 1; - } - - /** - * Returns a number rounded to the specified precision. - * - * @param float|int|numeric-string $number - */ - static public function round_to(float|int|string $number, int $precision): float - { - return round($number+0, $precision); - } - - /** - * Parses a number. - * - * @param float|int|numeric-string $number - * - * @return array{ 0: int, 1: string|null} - * Where `0` is the integer part and `1` the fractional part. The fractional part is `null` if - * `$number` has no decimal separator. The fractional part is returned as a string to preserve '03' from - * '1.03'. - */ - static public function parse(float|int|string $number, int $precision = null): array - { - if ($precision === null) - { - $precision = self::precision_from($number); - } - - $number = self::round_to($number+0, $precision); - $number = abs($number); - $number = number_format($number, $precision, '.', ''); - - [ $integer, $fractional ] = explode('.', (string) $number) + [ 1 => null ]; - - return [ (int) $integer, $fractional ]; - } - - /** - * @param float|int|numeric-string $number - * - * @return float|int|numeric-string - */ - static public function expand_compact_decimal_exponent(float|int|string $number, int &$c = 0): float|int|string - { - $c = 0; - - if (!is_string($number)) - { - return $number; - } - - $c_pos = strpos($number, 'c'); - - if ($c_pos === false) - { - return $number; - } - - $c = (int) substr($number, $c_pos + 1); - $number = substr($number, 0, $c_pos); - preg_match('/0+$/', $number, $match); - assert(is_numeric($number)); - $multiplier = (int) ('1' . str_repeat('0', $c)); - $number *= $multiplier; - - if ($match) { - return $number . $match[0]; // @phpstan-ignore-line - } - - return $number; - } + /** + * Returns the precision of a number. + * + * @param float|int|numeric-string $number + */ + public static function precision_from(float|int|string $number): int + { + $number = (string)$number; + $pos = strrpos($number, '.'); + + if (!$pos) { + return 0; + } + + return strlen($number) - $pos - 1; + } + + /** + * Returns a number rounded to the specified precision. + * + * @param float|int|numeric-string $number + */ + public static function round_to(float|int|string $number, int $precision): float + { + return round($number + 0, $precision); + } + + /** + * Parses a number. + * + * @param float|int|numeric-string $number + * + * @return array{ 0: int, 1: string|null} + * Where `0` is the integer part and `1` the fractional part. The fractional part is `null` if + * `$number` has no decimal separator. The fractional part is returned as a string to preserve '03' from + * '1.03'. + */ + public static function parse(float|int|string $number, int $precision = null): array + { + if ($precision === null) { + $precision = self::precision_from($number); + } + + $number = self::round_to($number + 0, $precision); + $number = abs($number); + $number = number_format($number, $precision, '.', ''); + + [ $integer, $fractional ] = explode('.', (string)$number) + [ 1 => null ]; + + return [ (int)$integer, $fractional ]; + } + + /** + * @param float|int|numeric-string $number + * + * @return float|int|numeric-string + */ + public static function expand_compact_decimal_exponent(float|int|string $number, int &$c = 0): float|int|string + { + $c = 0; + + if (!is_string($number)) { + return $number; + } + + $c_pos = strpos($number, 'c'); + + if ($c_pos === false) { + return $number; + } + + $c = (int)substr($number, $c_pos + 1); + $number = substr($number, 0, $c_pos); + preg_match('/0+$/', $number, $match); + assert(is_numeric($number)); + $multiplier = (int)('1' . str_repeat('0', $c)); + $number *= $multiplier; + + if ($match) { + return $number . $match[0]; // @phpstan-ignore-line + } + + return $number; + } } diff --git a/lib/NumberFormatter.php b/lib/NumberFormatter.php index ee94f94..42880c4 100644 --- a/lib/NumberFormatter.php +++ b/lib/NumberFormatter.php @@ -11,53 +11,49 @@ */ final class NumberFormatter implements Formatter, Localizable { - /** - * Formats a number with the specified pattern. - * - * Note, if the pattern contains '%', the number will be multiplied by 100 first. If the - * pattern contains '‰', the number will be multiplied by 1000. - * - * @param float|int|numeric-string $number - * The number to format. - * @param string|NumberPattern $pattern - * The pattern used to format the number. - */ - public function format( - float|int|string $number, - NumberPattern|string $pattern, - Symbols $symbols = null, - ): string { - if (!$pattern instanceof NumberPattern) - { - $pattern = NumberPattern::from($pattern); - } - - $symbols = $symbols ?? Symbols::defaults(); - - [ $integer, $decimal ] = $pattern->parse_number($number); - - $formatted_integer = $pattern->format_integer_with_group($integer, $symbols->group); - $formatted_number = $pattern->format_integer_with_decimal($formatted_integer, $decimal, $symbols->decimal); - - if ($number < 0) - { - $number = $pattern->negative_prefix . $formatted_number . $pattern->negative_suffix; - } - else - { - $number = $pattern->positive_prefix . $formatted_number . $pattern->positive_suffix; - } - - return strtr($number, [ - - '%' => $symbols->percentSign, - '‰' => $symbols->perMille, - - ]); - } - - public function localized(Locale $locale): LocalizedNumberFormatter - { - return new LocalizedNumberFormatter($this, $locale); - } + /** + * Formats a number with the specified pattern. + * + * Note, if the pattern contains '%', the number will be multiplied by 100 first. If the + * pattern contains '‰', the number will be multiplied by 1000. + * + * @param float|int|numeric-string $number + * The number to format. + * @param string|NumberPattern $pattern + * The pattern used to format the number. + */ + public function format( + float|int|string $number, + NumberPattern|string $pattern, + Symbols $symbols = null, + ): string { + if (!$pattern instanceof NumberPattern) { + $pattern = NumberPattern::from($pattern); + } + + $symbols = $symbols ?? Symbols::defaults(); + + [ $integer, $decimal ] = $pattern->parse_number($number); + + $formatted_integer = $pattern->format_integer_with_group($integer, $symbols->group); + $formatted_number = $pattern->format_integer_with_decimal($formatted_integer, $decimal, $symbols->decimal); + + if ($number < 0) { + $number = $pattern->negative_prefix . $formatted_number . $pattern->negative_suffix; + } else { + $number = $pattern->positive_prefix . $formatted_number . $pattern->positive_suffix; + } + + return strtr($number, [ + + '%' => $symbols->percentSign, + '‰' => $symbols->perMille, + + ]); + } + + public function localized(Locale $locale): LocalizedNumberFormatter + { + return new LocalizedNumberFormatter($this, $locale); + } } diff --git a/lib/NumberPattern.php b/lib/NumberPattern.php index c017066..94b7473 100644 --- a/lib/NumberPattern.php +++ b/lib/NumberPattern.php @@ -19,152 +19,148 @@ */ final class NumberPattern { - /** - * @var NumberPattern[] - */ - static private array $instances = []; - - static public function from(string $pattern): NumberPattern - { - if (isset(self::$instances[$pattern])) - { - return self::$instances[$pattern]; - } - - $parsed_pattern = NumberPatternParser::parse($pattern); - - return self::$instances[$pattern] = new self( - $pattern, - $parsed_pattern['positive_prefix'], - $parsed_pattern['positive_suffix'], - $parsed_pattern['negative_prefix'], - $parsed_pattern['negative_suffix'], - $parsed_pattern['multiplier'], - $parsed_pattern['decimal_digits'], - $parsed_pattern['max_decimal_digits'], - $parsed_pattern['integer_digits'], - $parsed_pattern['group_size1'], - $parsed_pattern['group_size2'] - ); - } - - /** - * @param string $pattern - * @param string $positive_prefix - * Prefix to positive number. - * @param string $positive_suffix - * Suffix to positive number. - * @param string $negative_prefix - * Prefix to negative number. - * @param string $negative_suffix - * Suffix to negative number. - * @param int $multiplier - * 100 for percent, 1000 for per mille. - * @param int $decimal_digits - * The number of required digits after decimal point. The string is padded with zeros if there is not enough digits. - * `-1` means the decimal point should be dropped. - * @param int $max_decimal_digits - * The maximum number of digits after decimal point. Additional digits will be truncated. - * @param int $integer_digits - * The number of required digits before decimal point. The string is padded with zeros if there is not enough digits. - * @param int $group_size1 - * The primary grouping size. `0` means no grouping. - * @param int $group_size2 - * The secondary grouping size. `0` means no secondary grouping. - */ - private function __construct( - public readonly string $pattern, - public readonly string $positive_prefix, - public readonly string $positive_suffix, - public readonly string $negative_prefix, - public readonly string $negative_suffix, - public readonly int $multiplier, - public readonly int $decimal_digits, - public readonly int $max_decimal_digits, - public readonly int $integer_digits, - public readonly int $group_size1, - public readonly int $group_size2 - ) { - } - - public function __toString(): string - { - return $this->pattern; - } - - /** - * Parse a number according to the pattern and return its integer and decimal parts. - * - * @param float|int|numeric-string $number - * - * @return array{ 0: int, 1: string} - * Where `0` is the integer part and `1` the decimal part. - */ - public function parse_number(float|int|string $number): array - { - $number = abs($number * $this->multiplier); - - if ($this->max_decimal_digits >= 0) - { - $number = round($number, $this->max_decimal_digits); - } - - $number = "$number"; - $pos = strpos($number, '.'); - - if ($pos !== false) - { - return [ (int) substr($number, 0, $pos), substr($number, $pos + 1) ]; - } - - return [ (int) $number, '' ]; - } - - /** - * Formats integer according to group pattern. - */ - public function format_integer_with_group(int $integer, string $group_symbol): string - { - $integer = str_pad((string) $integer, $this->integer_digits, '0', STR_PAD_LEFT); - $group_size1 = $this->group_size1; - - if ($group_size1 < 1 || strlen($integer) <= $this->group_size1) - { - return $integer; - } - - $group_size2 = $this->group_size2; - - $str1 = substr($integer, 0, -$group_size1); - $str2 = substr($integer, -$group_size1); - $size = $group_size2 > 0 ? $group_size2 : $group_size1; - $str1 = str_pad($str1, (int) ((strlen($str1) + $size - 1) / $size) * $size, ' ', STR_PAD_LEFT); - - return ltrim(implode($group_symbol, (array) str_split($str1, $size))) . $group_symbol . $str2; - } - - /** - * Formats an integer with a decimal. - * - * @param int|string $integer - * An integer, or a formatted integer as returned by {@link format_integer_with_group}. - */ - public function format_integer_with_decimal(int|string $integer, string $decimal, string $decimal_symbol): string - { - if ($decimal === '0') { - $decimal = ''; - } - - if ($this->decimal_digits > strlen($decimal)) - { - $decimal = str_pad($decimal, $this->decimal_digits, '0'); - } - - if (strlen($decimal)) - { - $decimal = $decimal_symbol . $decimal; - } - - return "$integer" . $decimal; - } + /** + * @var NumberPattern[] + */ + private static array $instances = []; + + public static function from(string $pattern): NumberPattern + { + if (isset(self::$instances[$pattern])) { + return self::$instances[$pattern]; + } + + $parsed_pattern = NumberPatternParser::parse($pattern); + + return self::$instances[$pattern] = new self( + $pattern, + $parsed_pattern['positive_prefix'], + $parsed_pattern['positive_suffix'], + $parsed_pattern['negative_prefix'], + $parsed_pattern['negative_suffix'], + $parsed_pattern['multiplier'], + $parsed_pattern['decimal_digits'], + $parsed_pattern['max_decimal_digits'], + $parsed_pattern['integer_digits'], + $parsed_pattern['group_size1'], + $parsed_pattern['group_size2'] + ); + } + + /** + * @param string $pattern + * @param string $positive_prefix + * Prefix to positive number. + * @param string $positive_suffix + * Suffix to positive number. + * @param string $negative_prefix + * Prefix to negative number. + * @param string $negative_suffix + * Suffix to negative number. + * @param int $multiplier + * 100 for percent, 1000 for per mille. + * @param int $decimal_digits + * The number of required digits after decimal point. The string is padded with zeros if there is not enough + * digits. + * `-1` means the decimal point should be dropped. + * @param int $max_decimal_digits + * The maximum number of digits after decimal point. Additional digits will be truncated. + * @param int $integer_digits + * The number of required digits before decimal point. The string is padded with zeros if there is not enough + * digits. + * @param int $group_size1 + * The primary grouping size. `0` means no grouping. + * @param int $group_size2 + * The secondary grouping size. `0` means no secondary grouping. + */ + private function __construct( + public readonly string $pattern, + public readonly string $positive_prefix, + public readonly string $positive_suffix, + public readonly string $negative_prefix, + public readonly string $negative_suffix, + public readonly int $multiplier, + public readonly int $decimal_digits, + public readonly int $max_decimal_digits, + public readonly int $integer_digits, + public readonly int $group_size1, + public readonly int $group_size2 + ) { + } + + public function __toString(): string + { + return $this->pattern; + } + + /** + * Parse a number according to the pattern and return its integer and decimal parts. + * + * @param float|int|numeric-string $number + * + * @return array{ 0: int, 1: string} + * Where `0` is the integer part and `1` the decimal part. + */ + public function parse_number(float|int|string $number): array + { + $number = abs($number * $this->multiplier); + + if ($this->max_decimal_digits >= 0) { + $number = round($number, $this->max_decimal_digits); + } + + $number = "$number"; + $pos = strpos($number, '.'); + + if ($pos !== false) { + return [ (int)substr($number, 0, $pos), substr($number, $pos + 1) ]; + } + + return [ (int)$number, '' ]; + } + + /** + * Formats integer according to group pattern. + */ + public function format_integer_with_group(int $integer, string $group_symbol): string + { + $integer = str_pad((string)$integer, $this->integer_digits, '0', STR_PAD_LEFT); + $group_size1 = $this->group_size1; + + if ($group_size1 < 1 || strlen($integer) <= $this->group_size1) { + return $integer; + } + + $group_size2 = $this->group_size2; + + $str1 = substr($integer, 0, -$group_size1); + $str2 = substr($integer, -$group_size1); + $size = $group_size2 > 0 ? $group_size2 : $group_size1; + $str1 = str_pad($str1, (int)((strlen($str1) + $size - 1) / $size) * $size, ' ', STR_PAD_LEFT); + + return ltrim(implode($group_symbol, (array)str_split($str1, $size))) . $group_symbol . $str2; + } + + /** + * Formats an integer with a decimal. + * + * @param int|string $integer + * An integer, or a formatted integer as returned by {@link format_integer_with_group}. + */ + public function format_integer_with_decimal(int|string $integer, string $decimal, string $decimal_symbol): string + { + if ($decimal === '0') { + $decimal = ''; + } + + if ($this->decimal_digits > strlen($decimal)) { + $decimal = str_pad($decimal, $this->decimal_digits, '0'); + } + + if (strlen($decimal)) { + $decimal = $decimal_symbol . $decimal; + } + + return "$integer" . $decimal; + } } diff --git a/lib/NumberPatternParser.php b/lib/NumberPatternParser.php index 6f98222..88f5ce4 100644 --- a/lib/NumberPatternParser.php +++ b/lib/NumberPatternParser.php @@ -14,202 +14,192 @@ */ final class NumberPatternParser { - public const PATTERN_REGEX = '/^(.*?)[#,\.0]+(.*?)$/'; - - /** - * @var array - */ - static private array $initial_format = [ - - 'positive_prefix' => '', - 'positive_suffix' => '', - 'negative_prefix' => '', - 'negative_suffix' => '', - 'multiplier' => 1, - 'decimal_digits' => 0, - 'max_decimal_digits' => 0, - 'integer_digits' => 0, - 'group_size1' => 0, - 'group_size2' => 0 - - ]; - - /** - * Parses a given string pattern. - * - * @return array{ - * positive_prefix: string, - * positive_suffix: string, - * negative_prefix: string, - * negative_suffix: string, - * multiplier: int, - * decimal_digits: int, - * max_decimal_digits: int, - * integer_digits: int, - * group_size1: int, - * group_size2: int - * } - */ - static public function parse(string $pattern): array - { - $format = self::$initial_format; - - self::parse_multiple_patterns($pattern, $format); - self::parse_multiplier($pattern, $format); - self::parse_decimal_part($pattern, $format); - self::parse_integer_part($pattern, $format); - self::parse_group_sizes($pattern, $format); - - return $format; - } - - /** - * @param array{ - * positive_prefix: string, - * positive_suffix: string, - * negative_prefix: string, - * negative_suffix: string, - * multiplier: int, - * decimal_digits: int, - * max_decimal_digits: int, - * integer_digits: int, - * group_size1: int, - * group_size2: int - * } $format - */ - static private function parse_multiple_patterns(string &$pattern, array &$format): void - { - $patterns = explode(';', $pattern); - - if (preg_match(self::PATTERN_REGEX, $patterns[0], $matches)) - { - $format['positive_prefix'] = $matches[1]; - $format['positive_suffix'] = $matches[2]; - } - - if (isset($patterns[1]) && preg_match(self::PATTERN_REGEX, $patterns[1], $matches)) - { - $format['negative_prefix'] = $matches[1]; - $format['negative_suffix'] = $matches[2]; - } - else - { - $format['negative_prefix'] = '-' . $format['positive_prefix']; - $format['negative_suffix'] = $format['positive_suffix']; - } - - $pattern = $patterns[0]; - } - - /** - * @param array{ - * positive_prefix: string, - * positive_suffix: string, - * negative_prefix: string, - * negative_suffix: string, - * multiplier: int, - * decimal_digits: int, - * max_decimal_digits: int, - * integer_digits: int, - * group_size1: int, - * group_size2: int - * } $format - */ - static private function parse_multiplier(string $pattern, array &$format): void - { - if (str_contains($pattern, '%')) - { - $format['multiplier'] = 100; - } - elseif (str_contains($pattern, '‰')) - { - $format['multiplier'] = 1000; - } - } - - /** - * @param array{ - * positive_prefix: string, - * positive_suffix: string, - * negative_prefix: string, - * negative_suffix: string, - * multiplier: int, - * decimal_digits: int, - * max_decimal_digits: int, - * integer_digits: int, - * group_size1: int, - * group_size2: int - * } $format - */ - static private function parse_decimal_part(string &$pattern, array &$format): void - { - $pos = strpos($pattern, '.'); - - if ($pos !== false) - { - $pos2 = strrpos($pattern, '0'); - $format['decimal_digits'] = $pos2 > $pos - ? $pos2 - $pos - : 0; - - $pos3 = strrpos($pattern, '#'); - $format['max_decimal_digits'] = $pos3 >= $pos2 - ? $pos3 - $pos - : $format['decimal_digits']; - - $pattern = substr($pattern, 0, $pos); - } - - } - - /** - * @param array{ - * positive_prefix: string, - * positive_suffix: string, - * negative_prefix: string, - * negative_suffix: string, - * multiplier: int, - * decimal_digits: int, - * max_decimal_digits: int, - * integer_digits: int, - * group_size1: int, - * group_size2: int - * } $format - */ - static private function parse_integer_part(string $pattern, array &$format): void - { - $p = str_replace(',', '', $pattern); - $pos = strpos($p, '0'); - - $format['integer_digits'] = $pos !== false - ? strrpos($p, '0') - $pos + 1 - : 0; - } - - /** - * @param array{ - * positive_prefix: string, - * positive_suffix: string, - * negative_prefix: string, - * negative_suffix: string, - * multiplier: int, - * decimal_digits: int, - * max_decimal_digits: int, - * integer_digits: int, - * group_size1: int, - * group_size2: int - * } $format - */ - static private function parse_group_sizes(string $pattern, array &$format): void - { - $p = str_replace('#', '0', $pattern); - $pos = strrpos($pattern, ','); - - if ($pos !== false) - { - $pos2 = strrpos(substr($p, 0, $pos), ','); - $format['group_size1'] = strrpos($p, '0') - $pos; - $format['group_size2'] = $pos2 !== false ? $pos - $pos2 - 1 : 0; - } - } + public const PATTERN_REGEX = '/^(.*?)[#,\.0]+(.*?)$/'; + + /** + * @var array + */ + private static array $initial_format = [ + + 'positive_prefix' => '', + 'positive_suffix' => '', + 'negative_prefix' => '', + 'negative_suffix' => '', + 'multiplier' => 1, + 'decimal_digits' => 0, + 'max_decimal_digits' => 0, + 'integer_digits' => 0, + 'group_size1' => 0, + 'group_size2' => 0 + + ]; + + /** + * Parses a given string pattern. + * + * @return array{ + * positive_prefix: string, + * positive_suffix: string, + * negative_prefix: string, + * negative_suffix: string, + * multiplier: int, + * decimal_digits: int, + * max_decimal_digits: int, + * integer_digits: int, + * group_size1: int, + * group_size2: int + * } + */ + public static function parse(string $pattern): array + { + $format = self::$initial_format; + + self::parse_multiple_patterns($pattern, $format); + self::parse_multiplier($pattern, $format); + self::parse_decimal_part($pattern, $format); + self::parse_integer_part($pattern, $format); + self::parse_group_sizes($pattern, $format); + + return $format; + } + + /** + * @param array{ + * positive_prefix: string, + * positive_suffix: string, + * negative_prefix: string, + * negative_suffix: string, + * multiplier: int, + * decimal_digits: int, + * max_decimal_digits: int, + * integer_digits: int, + * group_size1: int, + * group_size2: int + * } $format + */ + private static function parse_multiple_patterns(string &$pattern, array &$format): void + { + $patterns = explode(';', $pattern); + + if (preg_match(self::PATTERN_REGEX, $patterns[0], $matches)) { + $format['positive_prefix'] = $matches[1]; + $format['positive_suffix'] = $matches[2]; + } + + if (isset($patterns[1]) && preg_match(self::PATTERN_REGEX, $patterns[1], $matches)) { + $format['negative_prefix'] = $matches[1]; + $format['negative_suffix'] = $matches[2]; + } else { + $format['negative_prefix'] = '-' . $format['positive_prefix']; + $format['negative_suffix'] = $format['positive_suffix']; + } + + $pattern = $patterns[0]; + } + + /** + * @param array{ + * positive_prefix: string, + * positive_suffix: string, + * negative_prefix: string, + * negative_suffix: string, + * multiplier: int, + * decimal_digits: int, + * max_decimal_digits: int, + * integer_digits: int, + * group_size1: int, + * group_size2: int + * } $format + */ + private static function parse_multiplier(string $pattern, array &$format): void + { + if (str_contains($pattern, '%')) { + $format['multiplier'] = 100; + } elseif (str_contains($pattern, '‰')) { + $format['multiplier'] = 1000; + } + } + + /** + * @param array{ + * positive_prefix: string, + * positive_suffix: string, + * negative_prefix: string, + * negative_suffix: string, + * multiplier: int, + * decimal_digits: int, + * max_decimal_digits: int, + * integer_digits: int, + * group_size1: int, + * group_size2: int + * } $format + */ + private static function parse_decimal_part(string &$pattern, array &$format): void + { + $pos = strpos($pattern, '.'); + + if ($pos !== false) { + $pos2 = strrpos($pattern, '0'); + $format['decimal_digits'] = $pos2 > $pos + ? $pos2 - $pos + : 0; + + $pos3 = strrpos($pattern, '#'); + $format['max_decimal_digits'] = $pos3 >= $pos2 + ? $pos3 - $pos + : $format['decimal_digits']; + + $pattern = substr($pattern, 0, $pos); + } + } + + /** + * @param array{ + * positive_prefix: string, + * positive_suffix: string, + * negative_prefix: string, + * negative_suffix: string, + * multiplier: int, + * decimal_digits: int, + * max_decimal_digits: int, + * integer_digits: int, + * group_size1: int, + * group_size2: int + * } $format + */ + private static function parse_integer_part(string $pattern, array &$format): void + { + $p = str_replace(',', '', $pattern); + $pos = strpos($p, '0'); + + $format['integer_digits'] = $pos !== false + ? strrpos($p, '0') - $pos + 1 + : 0; + } + + /** + * @param array{ + * positive_prefix: string, + * positive_suffix: string, + * negative_prefix: string, + * negative_suffix: string, + * multiplier: int, + * decimal_digits: int, + * max_decimal_digits: int, + * integer_digits: int, + * group_size1: int, + * group_size2: int + * } $format + */ + private static function parse_group_sizes(string $pattern, array &$format): void + { + $p = str_replace('#', '0', $pattern); + $pos = strrpos($pattern, ','); + + if ($pos !== false) { + $pos2 = strrpos(substr($p, 0, $pos), ','); + $format['group_size1'] = strrpos($p, '0') - $pos; + $format['group_size2'] = $pos2 !== false ? $pos - $pos2 - 1 : 0; + } + } } diff --git a/lib/Numbers.php b/lib/Numbers.php index 3c44aca..831b972 100644 --- a/lib/Numbers.php +++ b/lib/Numbers.php @@ -14,83 +14,83 @@ */ final class Numbers extends ArrayObject { - public readonly Symbols $symbols; + public readonly Symbols $symbols; - /** - * Indicates which numbering system should be used for presentation of numeric quantities in the given locale. - */ - public readonly string $default_numbering_system; + /** + * Indicates which numbering system should be used for presentation of numeric quantities in the given locale. + */ + public readonly string $default_numbering_system; - /** - * @phpstan-ignore-next-line - */ - public readonly array $decimal_formats; + /** + * @phpstan-ignore-next-line + */ + public readonly array $decimal_formats; - /** - * Shortcut to the `decimalFormats-numberSystem-$default_numbering_system/standard`. - */ - public readonly string $decimal_format; + /** + * Shortcut to the `decimalFormats-numberSystem-$default_numbering_system/standard`. + */ + public readonly string $decimal_format; - /** - * Shortcut to the `decimalFormats-numberSystem-$default_numbering_system/short/decimalFormats`. - * - * @phpstan-ignore-next-line - */ - public readonly array $short_decimal_formats; + /** + * Shortcut to the `decimalFormats-numberSystem-$default_numbering_system/short/decimalFormats`. + * + * @phpstan-ignore-next-line + */ + public readonly array $short_decimal_formats; - /** - * Shortcut to the `decimalFormats-numberSystem-$default_numbering_system/long/decimalFormats`. - * - * @phpstan-ignore-next-line - */ - public readonly array $long_decimal_formats; + /** + * Shortcut to the `decimalFormats-numberSystem-$default_numbering_system/long/decimalFormats`. + * + * @phpstan-ignore-next-line + */ + public readonly array $long_decimal_formats; - /** - * Shortcut to the `scientificFormats-numberSystem-$default_numbering_system`. - * - * @phpstan-ignore-next-line - */ - public readonly array $scientific_formats; + /** + * Shortcut to the `scientificFormats-numberSystem-$default_numbering_system`. + * + * @phpstan-ignore-next-line + */ + public readonly array $scientific_formats; - /** - * Shortcut to the `percentFormats-numberSystem-$default_numbering_system`. - * - * @phpstan-ignore-next-line - */ - public readonly array $percent_formats; + /** + * Shortcut to the `percentFormats-numberSystem-$default_numbering_system`. + * + * @phpstan-ignore-next-line + */ + public readonly array $percent_formats; - /** - * Shortcut to the `currencyFormats-numberSystem-$default_numbering_system`. - * - * @phpstan-ignore-next-line - */ - public readonly array $currency_formats; + /** + * Shortcut to the `currencyFormats-numberSystem-$default_numbering_system`. + * + * @phpstan-ignore-next-line + */ + public readonly array $currency_formats; - /** - * Shortcut to the `miscPatterns-numberSystem-$default_numbering_system`. - * - * @phpstan-ignore-next-line - */ - public readonly array $misc_patterns; + /** + * Shortcut to the `miscPatterns-numberSystem-$default_numbering_system`. + * + * @phpstan-ignore-next-line + */ + public readonly array $misc_patterns; - /** - * @param array $data - */ - public function __construct( - public readonly Locale $locale, - array $data - ) { - parent::__construct($data); + /** + * @param array $data + */ + public function __construct( + public readonly Locale $locale, + array $data + ) { + parent::__construct($data); - $this->default_numbering_system = $dns = $data['defaultNumberingSystem']; - $this->decimal_formats = $data["decimalFormats-numberSystem-$dns"]; - $this->decimal_format = $this->decimal_formats['standard']; - $this->short_decimal_formats = $this->decimal_formats['short']['decimalFormat']; - $this->long_decimal_formats = $this->decimal_formats['long']['decimalFormat']; - $this->scientific_formats = $data["scientificFormats-numberSystem-$dns"]; - $this->percent_formats = $data["percentFormats-numberSystem-$dns"]; - $this->currency_formats = $data["currencyFormats-numberSystem-$dns"]; - $this->misc_patterns = $data["miscPatterns-numberSystem-$dns"]; - $this->symbols = Symbols::from($data["symbols-numberSystem-$dns"]); - } + $this->default_numbering_system = $dns = $data['defaultNumberingSystem']; + $this->decimal_formats = $data["decimalFormats-numberSystem-$dns"]; + $this->decimal_format = $this->decimal_formats['standard']; + $this->short_decimal_formats = $this->decimal_formats['short']['decimalFormat']; + $this->long_decimal_formats = $this->decimal_formats['long']['decimalFormat']; + $this->scientific_formats = $data["scientificFormats-numberSystem-$dns"]; + $this->percent_formats = $data["percentFormats-numberSystem-$dns"]; + $this->currency_formats = $data["currencyFormats-numberSystem-$dns"]; + $this->misc_patterns = $data["miscPatterns-numberSystem-$dns"]; + $this->symbols = Symbols::from($data["symbols-numberSystem-$dns"]); + } } diff --git a/lib/Numbers/Symbols.php b/lib/Numbers/Symbols.php index 9fc4898..fcf9501 100644 --- a/lib/Numbers/Symbols.php +++ b/lib/Numbers/Symbols.php @@ -9,75 +9,75 @@ */ final class Symbols { - private const DEFAULTS = [ + private const DEFAULTS = [ - 'decimal' => '.', - 'group' => ',', - 'list' => ';', - 'percentSign' => '%', - 'minusSign' => '-', - 'plusSign' => '+', - 'approximatelySign' => '~', - 'exponential' => 'E', - 'superscriptingExponent' => '×', - 'perMille' => '‰', - 'infinity' => '∞', - 'nan' => '☹', - 'currencyDecimal' => '.', - 'currencyGroup' => ',', - 'timeSeparator' => ':', + 'decimal' => '.', + 'group' => ',', + 'list' => ';', + 'percentSign' => '%', + 'minusSign' => '-', + 'plusSign' => '+', + 'approximatelySign' => '~', + 'exponential' => 'E', + 'superscriptingExponent' => '×', + 'perMille' => '‰', + 'infinity' => '∞', + 'nan' => '☹', + 'currencyDecimal' => '.', + 'currencyGroup' => ',', + 'timeSeparator' => ':', - ]; + ]; - /** - * @param array $symbols - */ - public static function from(array $symbols): self - { - $symbols += self::DEFAULTS; + /** + * @param array $symbols + */ + public static function from(array $symbols): self + { + $symbols += self::DEFAULTS; - return new self( - $symbols['decimal'], - $symbols['group'], - $symbols['list'], - $symbols['percentSign'], - $symbols['minusSign'], - $symbols['plusSign'], - $symbols['approximatelySign'], - $symbols['exponential'], - $symbols['superscriptingExponent'], - $symbols['perMille'], - $symbols['infinity'], - $symbols['nan'], - $symbols['currencyDecimal'], - $symbols['currencyGroup'], - $symbols['timeSeparator'] - ); - } + return new self( + $symbols['decimal'], + $symbols['group'], + $symbols['list'], + $symbols['percentSign'], + $symbols['minusSign'], + $symbols['plusSign'], + $symbols['approximatelySign'], + $symbols['exponential'], + $symbols['superscriptingExponent'], + $symbols['perMille'], + $symbols['infinity'], + $symbols['nan'], + $symbols['currencyDecimal'], + $symbols['currencyGroup'], + $symbols['timeSeparator'] + ); + } - public static function defaults(): self - { - static $defaults; + public static function defaults(): self + { + static $defaults; - return $defaults ??= self::from(self::DEFAULTS); - } + return $defaults ??= self::from(self::DEFAULTS); + } - public function __construct( - public readonly string $decimal = '.', - public readonly string $group = ',', - public readonly string $list = ';', - public readonly string $percentSign = '%', - public readonly string $minusSign = '-', - public readonly string $plusSign = '+', - public readonly string $approximatelySign = '~', - public readonly string $exponential = 'E', - public readonly string $superscriptingExponent = '×', - public readonly string $perMille = '‰', - public readonly string $infinity = '∞', - public readonly string $nan = '☹', - public readonly string $currencyDecimal = '.', - public readonly string $currencyGroup = ',', - public readonly string $timeSeparator = ':' - ) { - } + public function __construct( + public readonly string $decimal = '.', + public readonly string $group = ',', + public readonly string $list = ';', + public readonly string $percentSign = '%', + public readonly string $minusSign = '-', + public readonly string $plusSign = '+', + public readonly string $approximatelySign = '~', + public readonly string $exponential = 'E', + public readonly string $superscriptingExponent = '×', + public readonly string $perMille = '‰', + public readonly string $infinity = '∞', + public readonly string $nan = '☹', + public readonly string $currencyDecimal = '.', + public readonly string $currencyGroup = ',', + public readonly string $timeSeparator = ':' + ) { + } } diff --git a/lib/Plurals.php b/lib/Plurals.php index 5de8e70..abf620b 100644 --- a/lib/Plurals.php +++ b/lib/Plurals.php @@ -23,126 +23,126 @@ */ final class Plurals extends ArrayObject { - public const COUNT_ZERO = 'zero'; - public const COUNT_ONE = 'one'; - public const COUNT_TWO = 'two'; - public const COUNT_FEW = 'few'; - public const COUNT_MANY = 'many'; - public const COUNT_OTHER = 'other'; - - /** - * @private - */ - public const RULE_COUNT_PREFIX = 'pluralRule-count-'; - - /** - * @var array> - * Where _key_ is a locale code and _value_ an array where _key_ is a rule count. - */ - private array $rules = []; - - /** - * @var array> - * Where _key_ is a locale code and _value_ an array where _key_ is a rule count. - */ - private array $samples = []; - - /** - * @param float|int|numeric-string $number - * - * @return self::COUNT_* - */ - public function rule_for(float|int|string $number, string $language): string - { - $rules = $this->rule_instances_for($language); - - foreach ($rules as $count => $rule) { - if ($rule->validate($number)) { - return $count; - } - } - - return self::COUNT_OTHER; - } - - /** - * @return array - */ - public function rules_for(string $locale): array - { - return array_keys($this->rule_instances_for($locale)); - } - - /** - * @return array - */ - public function samples_for(string $locale): array - { - return $this->samples[$locale] ??= $this->create_samples_for($locale); - } - - /** - * @return array - */ - private function rule_instances_for(string $language): array - { - return $this->rules[$language] ??= $this->create_rules_for($language); - } - - /** - * @return array - */ - private function create_rules_for(string $language): array - { - $rules = []; - $prefix_length = strlen(self::RULE_COUNT_PREFIX); - - /** @phpstan-ignore-next-line */ - foreach ($this[$language] as $count => $rule_string) { - $count = substr($count, $prefix_length); - $rules[$count] = Rule::from($this->extract_rule($rule_string)); - } - - /** @var array */ - return $rules; - } - - private function extract_rule(string $rule_string): string - { - $rule = explode('@', $rule_string, 2); - $rule = array_shift($rule); - - return trim($rule); - } - - /** - * @return array - */ - private function create_samples_for(string $locale): array - { - $samples = []; - $prefix_length = strlen(self::RULE_COUNT_PREFIX); - $rules = $this[$locale]; - - assert(!is_null($rules)); - - foreach ($rules as $count => $rule_string) { - $count = substr($count, $prefix_length); - $samples[$count] = Samples::from($this->extract_samples($rule_string)); - } - - /** @var array */ - return $samples; - } - - private function extract_samples(string $rule_string): string - { - $pos = strpos($rule_string, '@'); - - if ($pos === false) { - return ""; - } - - return substr($rule_string, $pos); - } + public const COUNT_ZERO = 'zero'; + public const COUNT_ONE = 'one'; + public const COUNT_TWO = 'two'; + public const COUNT_FEW = 'few'; + public const COUNT_MANY = 'many'; + public const COUNT_OTHER = 'other'; + + /** + * @private + */ + public const RULE_COUNT_PREFIX = 'pluralRule-count-'; + + /** + * @var array> + * Where _key_ is a locale code and _value_ an array where _key_ is a rule count. + */ + private array $rules = []; + + /** + * @var array> + * Where _key_ is a locale code and _value_ an array where _key_ is a rule count. + */ + private array $samples = []; + + /** + * @param float|int|numeric-string $number + * + * @return self::COUNT_* + */ + public function rule_for(float|int|string $number, string $language): string + { + $rules = $this->rule_instances_for($language); + + foreach ($rules as $count => $rule) { + if ($rule->validate($number)) { + return $count; + } + } + + return self::COUNT_OTHER; + } + + /** + * @return array + */ + public function rules_for(string $locale): array + { + return array_keys($this->rule_instances_for($locale)); + } + + /** + * @return array + */ + public function samples_for(string $locale): array + { + return $this->samples[$locale] ??= $this->create_samples_for($locale); + } + + /** + * @return array + */ + private function rule_instances_for(string $language): array + { + return $this->rules[$language] ??= $this->create_rules_for($language); + } + + /** + * @return array + */ + private function create_rules_for(string $language): array + { + $rules = []; + $prefix_length = strlen(self::RULE_COUNT_PREFIX); + + /** @phpstan-ignore-next-line */ + foreach ($this[$language] as $count => $rule_string) { + $count = substr($count, $prefix_length); + $rules[$count] = Rule::from($this->extract_rule($rule_string)); + } + + /** @var array */ + return $rules; + } + + private function extract_rule(string $rule_string): string + { + $rule = explode('@', $rule_string, 2); + $rule = array_shift($rule); + + return trim($rule); + } + + /** + * @return array + */ + private function create_samples_for(string $locale): array + { + $samples = []; + $prefix_length = strlen(self::RULE_COUNT_PREFIX); + $rules = $this[$locale]; + + assert(!is_null($rules)); + + foreach ($rules as $count => $rule_string) { + $count = substr($count, $prefix_length); + $samples[$count] = Samples::from($this->extract_samples($rule_string)); + } + + /** @var array */ + return $samples; + } + + private function extract_samples(string $rule_string): string + { + $pos = strpos($rule_string, '@'); + + if ($pos === false) { + return ""; + } + + return substr($rule_string, $pos); + } } diff --git a/lib/Plurals/Operands.php b/lib/Plurals/Operands.php index ed6eb87..61a813b 100644 --- a/lib/Plurals/Operands.php +++ b/lib/Plurals/Operands.php @@ -2,7 +2,6 @@ namespace ICanBoogie\CLDR\Plurals; -use ICanBoogie\Accessor\AccessorTrait; use ICanBoogie\CLDR\Number; use function abs; @@ -16,74 +15,70 @@ */ final class Operands { - /** - * @param float|int|numeric-string $number - */ - static public function from(float|int|string $number): self - { - return OperandsCache::get($number, static fn(): self => new self($number)); - } + /** + * @param float|int|numeric-string $number + */ + public static function from(float|int|string $number): self + { + return OperandsCache::get($number, static fn(): self => new self($number)); + } - public readonly int|float $n; - public readonly int $i; - public readonly int $v; - public readonly int $w; - public readonly int $f; - public readonly int $t; - public readonly int $e; + public readonly int|float $n; + public readonly int $i; + public readonly int $v; + public readonly int $w; + public readonly int $f; + public readonly int $t; + public readonly int $e; - /** - * @param float|int|numeric-string $number - */ - private function __construct(float|int|string $number) - { - $e = 0; - $number = Number::expand_compact_decimal_exponent($number, $e); + /** + * @param float|int|numeric-string $number + */ + private function __construct(float|int|string $number) + { + $e = 0; + $number = Number::expand_compact_decimal_exponent($number, $e); - [ $integer, $fractional ] = Number::parse($number); + [ $integer, $fractional ] = Number::parse($number); - $n = abs($number); + $n = abs($number); - if ($fractional === null || (int) $fractional === 0) - { - $n = (int) $n; - } + if ($fractional === null || (int)$fractional === 0) { + $n = (int)$n; + } - $this->n = $n; - $this->i = $integer; - $this->e = $e; + $this->n = $n; + $this->i = $integer; + $this->e = $e; - if ($fractional === null) - { - $this->v = 0; - $this->w = 0; - $this->f = 0; - $this->t = 0; - } - else - { - $this->v = strlen($fractional); - $this->w = strlen(rtrim($fractional, '0')); - $this->f = (int) ltrim($fractional, '0'); - $this->t = (int) trim($fractional, '0'); - } - } + if ($fractional === null) { + $this->v = 0; + $this->w = 0; + $this->f = 0; + $this->t = 0; + } else { + $this->v = strlen($fractional); + $this->w = strlen(rtrim($fractional, '0')); + $this->f = (int)ltrim($fractional, '0'); + $this->t = (int)trim($fractional, '0'); + } + } - /** - * @return array{ n: float|int, i: int, v: int, w: int, f: int, t: int, e: int } - */ - public function to_array(): array - { - return [ + /** + * @return array{ n: float|int, i: int, v: int, w: int, f: int, t: int, e: int } + */ + public function to_array(): array + { + return [ - 'n' => $this->n, - 'i' => $this->i, - 'v' => $this->v, - 'w' => $this->w, - 'f' => $this->f, - 't' => $this->t, - 'e' => $this->e, + 'n' => $this->n, + 'i' => $this->i, + 'v' => $this->v, + 'w' => $this->w, + 'f' => $this->f, + 't' => $this->t, + 'e' => $this->e, - ]; - } + ]; + } } diff --git a/lib/Plurals/OperandsCache.php b/lib/Plurals/OperandsCache.php index d70f2b0..7210d47 100644 --- a/lib/Plurals/OperandsCache.php +++ b/lib/Plurals/OperandsCache.php @@ -7,19 +7,16 @@ */ final class OperandsCache { - /** - * @var array - */ - static private array $instances = []; + /** + * @param float|int|numeric-string $number + * @param callable():Operands $new + */ + public static function get(float|int|string $number, callable $new): Operands + { + static $instances; - /** - * @param float|int|numeric-string $number - * @param callable():Operands $new - */ - static public function get(float|int|string $number, callable $new): Operands - { - $key = "number-$number"; + $key = "number-$number"; - return self::$instances[$key] ??= $new(); - } + return $instances[$key] ??= $new(); + } } diff --git a/lib/Plurals/Relation.php b/lib/Plurals/Relation.php index 74b706c..dd35b02 100644 --- a/lib/Plurals/Relation.php +++ b/lib/Plurals/Relation.php @@ -26,169 +26,160 @@ */ final class Relation { - public const RANGE_SEPARATOR = '..'; - public const MODULUS_SIGN = '%'; - - static public function from(string $relation): Relation - { - return RelationCache::get( - $relation, - static fn(): Relation => new self(...self::parse_relation($relation)) - ); - } - - /** - * @return array{ 0: string|null, 1: string } - * Where `0` is the x_expressions and `1` the range. - */ - static private function parse_relation(string $relation): array - { - [ $x_expression, $range_list ] = explode('= ', $relation) + [ 1 => null ]; - assert(is_string($x_expression)); - [ $x_expression, $negative ] = self::parse_x_expression($x_expression); - - $range = $range_list ? self::parse_range_list($range_list) : '($x == 0)'; - - if ($negative) - { - $range = "!($range)"; - } - - return [ $x_expression, $range ]; - } - - /** - * @return array{ 0: string|null, 1: bool} - * Where `0` is the x_expressions and `1` whether it's negative. - */ - static private function parse_x_expression(string $x_expression): array - { - if (!$x_expression) - { - return [ null, false ]; - } - - $negative = false; - - if ($x_expression[strlen($x_expression) - 1] === '!') - { - $negative = true; - - $x_expression = substr($x_expression, 0, -1); - } - - $x_expression = '$' . rtrim($x_expression); - - if (str_contains($x_expression, self::MODULUS_SIGN)) - { - [ $operand, $modulus ] = explode(self::MODULUS_SIGN, $x_expression); - - $operand = trim($operand); - - $x_expression = "fmod($operand, $modulus)"; - } - - return [ $x_expression, $negative ]; - } - - /** - * @return string A PHP statement. - */ - static private function parse_range_list(string $range_list): string - { - $ranges = []; - - foreach (explode(',', $range_list) as $range) - { - if (strpos($range, self::RANGE_SEPARATOR)) - { - $ranges = array_merge($ranges, self::unwind_range($range)); - - continue; - } - - $ranges[] = "(\$x == $range)"; - } - - return implode(' || ', $ranges); - } - - /** - * @return string[] - * Where _value_ is PHP code to evaluate. - */ - static private function unwind_range(string $range): array - { - [ $start, $end ] = explode(self::RANGE_SEPARATOR, $range); - - assert(is_numeric($start)); - assert(is_numeric($end)); - - $precision = self::precision_from($start) ?: self::precision_from($end); - $step = 1 / (int) ('1' . str_repeat('0', $precision)); - $end = (int) $end + $step; - - $ranges = []; - - for (; $start < $end; $start += $step) - { - $ranges[] = "(\$x == $start)"; - } - - return $ranges; - } - - /** - * @param float|int|numeric-string $number - */ - static private function precision_from(float|int|string $number): int - { - return Number::precision_from($number); - } - - private function __construct( - private readonly ?string $x_expression, - private readonly string $conditions - ) { - } - - public function resolve_x(Operands $operands): float|int|null - { - if ($this->x_expression === null) - { - return null; - } - - $operands = $operands->to_array(); - - extract($operands); - - /** - * @var float|int $n - * @var int $i - * @var int $v - * @var int $w - * @var int $f - * @var int $t - * @var int $e - */ - - return eval("return ($this->x_expression);"); - } - - /** - * Evaluate operands - */ - public function evaluate(Operands $operands): bool - { - if ($this->x_expression === null) - { - return true; - } - - // $x is typecasted as a string because `fmod(4.3, 3) != 1.3` BUT `(string) fmod(4.3, 3) == 1.3 - $x = (string) $this->resolve_x($operands); - - return eval("return $this->conditions;"); - } + public const RANGE_SEPARATOR = '..'; + public const MODULUS_SIGN = '%'; + + public static function from(string $relation): Relation + { + return RelationCache::get( + $relation, + static fn(): Relation => new self(...self::parse_relation($relation)) + ); + } + + /** + * @return array{ 0: string|null, 1: string } + * Where `0` is the x_expressions and `1` the range. + */ + private static function parse_relation(string $relation): array + { + [ $x_expression, $range_list ] = explode('= ', $relation) + [ 1 => null ]; + assert(is_string($x_expression)); + [ $x_expression, $negative ] = self::parse_x_expression($x_expression); + + $range = $range_list ? self::parse_range_list($range_list) : '($x == 0)'; + + if ($negative) { + $range = "!($range)"; + } + + return [ $x_expression, $range ]; + } + + /** + * @return array{ 0: string|null, 1: bool} + * Where `0` is the x_expressions and `1` whether it's negative. + */ + private static function parse_x_expression(string $x_expression): array + { + if (!$x_expression) { + return [ null, false ]; + } + + $negative = false; + + if ($x_expression[strlen($x_expression) - 1] === '!') { + $negative = true; + + $x_expression = substr($x_expression, 0, -1); + } + + $x_expression = '$' . rtrim($x_expression); + + if (str_contains($x_expression, self::MODULUS_SIGN)) { + [ $operand, $modulus ] = explode(self::MODULUS_SIGN, $x_expression); + + $operand = trim($operand); + + $x_expression = "fmod($operand, $modulus)"; + } + + return [ $x_expression, $negative ]; + } + + /** + * @return string A PHP statement. + */ + private static function parse_range_list(string $range_list): string + { + $ranges = []; + + foreach (explode(',', $range_list) as $range) { + if (strpos($range, self::RANGE_SEPARATOR)) { + $ranges = array_merge($ranges, self::unwind_range($range)); + + continue; + } + + $ranges[] = "(\$x == $range)"; + } + + return implode(' || ', $ranges); + } + + /** + * @return string[] + * Where _value_ is PHP code to evaluate. + */ + private static function unwind_range(string $range): array + { + [ $start, $end ] = explode(self::RANGE_SEPARATOR, $range); + + assert(is_numeric($start)); + assert(is_numeric($end)); + + $precision = self::precision_from($start) ?: self::precision_from($end); + $step = 1 / (int)('1' . str_repeat('0', $precision)); + $end = (int)$end + $step; + + $ranges = []; + + for (; $start < $end; $start += $step) { + $ranges[] = "(\$x == $start)"; + } + + return $ranges; + } + + /** + * @param float|int|numeric-string $number + */ + private static function precision_from(float|int|string $number): int + { + return Number::precision_from($number); + } + + private function __construct( + private readonly ?string $x_expression, + private readonly string $conditions + ) { + } + + public function resolve_x(Operands $operands): float|int|null + { + if ($this->x_expression === null) { + return null; + } + + $operands = $operands->to_array(); + + extract($operands); + + /** + * @var float|int $n + * @var int $i + * @var int $v + * @var int $w + * @var int $f + * @var int $t + * @var int $e + */ + + return eval("return ($this->x_expression);"); + } + + /** + * Evaluate operands + */ + public function evaluate(Operands $operands): bool + { + if ($this->x_expression === null) { + return true; + } + + // $x is typecasted as a string because `fmod(4.3, 3) != 1.3` BUT `(string) fmod(4.3, 3) == 1.3 + $x = (string)$this->resolve_x($operands); + + return eval("return $this->conditions;"); + } } diff --git a/lib/Plurals/RelationCache.php b/lib/Plurals/RelationCache.php index bd761a1..32287ce 100644 --- a/lib/Plurals/RelationCache.php +++ b/lib/Plurals/RelationCache.php @@ -7,17 +7,13 @@ */ final class RelationCache { - /** - * @var array - * Where _key_ is a relation statement and _value_ a {@link Relation}. - */ - static private array $instances = []; + /** + * @param callable():Relation $new + */ + public static function get(string $relation, callable $new): Relation + { + static $instances; - /** - * @param callable():Relation $new - */ - static public function get(string $relation, callable $new): Relation - { - return self::$instances[$relation] ??= $new(); - } + return $instances[$relation] ??= $new(); + } } diff --git a/lib/Plurals/Rule.php b/lib/Plurals/Rule.php index e81b18c..a284852 100644 --- a/lib/Plurals/Rule.php +++ b/lib/Plurals/Rule.php @@ -11,84 +11,84 @@ /** * Representation of plural samples. * - * @see http://unicode.org/reports/tr35/tr35-25-numbers.html#Language_Plural_Rules + * @see http://unicode.org/reports/tr35/tr35-72-numbers.html#Language_Plural_Rules */ final class Rule { - static public function from(string $rule): Rule - { - return RuleCache::get( - $rule, - static fn(): Rule => new self(self::parse_rule($rule)) - ); - } + public static function from(string $rule): Rule + { + return RuleCache::get( + $rule, + static fn(): Rule => new self(self::parse_rule($rule)) + ); + } - /** - * @return array - * An array of 'OR' relations, where _value_ is an array of 'AND' relations. - */ - static private function parse_rule(string $rules): array - { - $relations = self::extract_relations($rules); - array_walk_recursive($relations, function (string &$relation): void { - $relation = Relation::from($relation); - }); + /** + * @return array + * An array of 'OR' relations, where _value_ is an array of 'AND' relations. + */ + private static function parse_rule(string $rules): array + { + $relations = self::extract_relations($rules); + array_walk_recursive($relations, function (string &$relation): void { + $relation = Relation::from($relation); + }); - /** @var array */ - return $relations; - } + /** @var array */ + return $relations; + } - /** - * @return array - * An array of 'OR' relations, where _value_ is an array of 'AND' relations. - */ - static private function extract_relations(string $rule): array - { - return array_map( - static fn(string $rule): array => explode(' and ', $rule), - explode(' or ', $rule) - ); - } + /** + * @return array + * An array of 'OR' relations, where _value_ is an array of 'AND' relations. + */ + private static function extract_relations(string $rule): array + { + return array_map( + static fn(string $rule): array => explode(' and ', $rule), + explode(' or ', $rule) + ); + } - /** - * @param Relation[][] $relations - */ - private function __construct( - private readonly array $relations - ) { - } + /** + * @param Relation[][] $relations + */ + private function __construct( + private readonly array $relations + ) { + } - /** - * Whether a number matches the rule. - * - * @param float|int|numeric-string $number - */ - public function validate(float|int|string $number): bool - { - $operands = Operands::from($number); + /** + * Whether a number matches the rule. + * + * @param float|int|numeric-string $number + */ + public function validate(float|int|string $number): bool + { + $operands = Operands::from($number); - return $this->validate_or($operands, $this->relations); - } + return $this->validate_or($operands, $this->relations); + } - /** - * @param Relation[][] $or_relations - */ - public function validate_or(Operands $operands, iterable $or_relations): bool - { - return iterable_some( - $or_relations, - fn($and_relations) => $this->validate_and($operands, $and_relations) - ); - } + /** + * @param Relation[][] $or_relations + */ + public function validate_or(Operands $operands, iterable $or_relations): bool + { + return iterable_some( + $or_relations, + fn($and_relations) => $this->validate_and($operands, $and_relations) + ); + } - /** - * @param Relation[] $and_relations - */ - public function validate_and(Operands $operands, iterable $and_relations): bool - { - return iterable_every( - $and_relations, - fn(Relation $relation) => $relation->evaluate($operands) - ); - } + /** + * @param Relation[] $and_relations + */ + public function validate_and(Operands $operands, iterable $and_relations): bool + { + return iterable_every( + $and_relations, + fn(Relation $relation) => $relation->evaluate($operands) + ); + } } diff --git a/lib/Plurals/RuleCache.php b/lib/Plurals/RuleCache.php index 42b4593..81e03a8 100644 --- a/lib/Plurals/RuleCache.php +++ b/lib/Plurals/RuleCache.php @@ -7,17 +7,13 @@ */ final class RuleCache { - /** - * @var array - * Where _key_ is a rule statement and _value_ a {@link Rule}. - */ - static private array $instances = []; + /** + * @param callable():Rule $new + */ + public static function get(string $rule, callable $new): Rule + { + static $instances; - /** - * @param callable():Rule $new - */ - static public function get(string $rule, callable $new): Rule - { - return self::$instances[$rule] ??= $new(); - } + return $instances[$rule] ??= $new(); + } } diff --git a/lib/Plurals/Samples.php b/lib/Plurals/Samples.php index 899b029..fe960dc 100644 --- a/lib/Plurals/Samples.php +++ b/lib/Plurals/Samples.php @@ -12,7 +12,6 @@ use function explode; use function is_array; use function str_repeat; -use function strpos; use function trim; /** @@ -26,124 +25,117 @@ */ final class Samples implements IteratorAggregate { - /** - * @private - */ - public const INFINITY = '…'; - - /** - * @private - */ - public const SAMPLE_RANGE_SEPARATOR = '~'; - - static public function from(string $samples): Samples - { - return SamplesCache::get( - $samples, - static fn(): Samples => new self(self::parse_rules($samples)) - ); - } - - /** - * @return array - */ - static private function parse_rules(string $sample_string): array - { - $samples = []; - $type_and_samples_string_list = array_slice(explode('@', $sample_string), 1); - - foreach ($type_and_samples_string_list as $type_and_samples_string) - { - [ $type, $samples_string ] = explode(' ', trim($type_and_samples_string), 2); - - $samples[$type] = self::parse_samples($samples_string); - } - - return array_merge(...array_values($samples)); - } - - /** - * Parse a samples string. - * - * - * @return array - */ - static private function parse_samples(string $samples_string): array - { - $samples = []; - - foreach (explode(', ', $samples_string) as $sample) - { - if ($sample === self::INFINITY) - { - continue; - } - - if (!str_contains($sample, self::SAMPLE_RANGE_SEPARATOR)) - { - $samples[] = $sample; - - continue; - } - - [ $start, $end ] = explode(self::SAMPLE_RANGE_SEPARATOR, $sample); - - $samples[] = [ $start, $end ]; - } - - return $samples; - } - - /** - * @param float|int|numeric-string $number - */ - static private function precision_from(float|int|string $number): int - { - return Number::precision_from($number); - } - - /** - * @param array $samples - */ - private function __construct( - private readonly array $samples - ) { - } - - /** - * Note: The iterator yields numeric strings to avoid '0.30000000000000004' when '0.3' is correct, - * and to avoid removing trailing zeros e.g. '1.0' or '1.00'. - */ - public function getIterator(): Traversable - { - foreach ($this->samples as $sample) - { - if (!is_array($sample)) - { - yield $sample; - - continue; - } - - /** - * @var numeric-string $start - * @var numeric-string $end - */ - - [ $start, $end ] = $sample; - - $precision = self::precision_from($start) ?: self::precision_from($end); - $step = 1 / (int) ('1' . str_repeat('0', $precision)); - $start += 0; - $end += 0; - - // we use a for/times, so we don't lose quantities, compared to a $start += $step - $times = ($end - $start) / $step; - - for ($i = 0 ; $i < $times + 1 ; $i++) - { - yield (string) ($start + $step * $i); - } - } - } + /** + * @private + */ + public const INFINITY = '…'; + + /** + * @private + */ + public const SAMPLE_RANGE_SEPARATOR = '~'; + + public static function from(string $samples): Samples + { + return SamplesCache::get( + $samples, + static fn(): Samples => new self(self::parse_rules($samples)) + ); + } + + /** + * @return array + */ + private static function parse_rules(string $sample_string): array + { + $samples = []; + $type_and_samples_string_list = array_slice(explode('@', $sample_string), 1); + + foreach ($type_and_samples_string_list as $type_and_samples_string) { + [ $type, $samples_string ] = explode(' ', trim($type_and_samples_string), 2); + + $samples[$type] = self::parse_samples($samples_string); + } + + return array_merge(...array_values($samples)); + } + + /** + * Parse a samples string. + * + * + * @return array + */ + private static function parse_samples(string $samples_string): array + { + $samples = []; + + foreach (explode(', ', $samples_string) as $sample) { + if ($sample === self::INFINITY) { + continue; + } + + if (!str_contains($sample, self::SAMPLE_RANGE_SEPARATOR)) { + $samples[] = $sample; + + continue; + } + + [ $start, $end ] = explode(self::SAMPLE_RANGE_SEPARATOR, $sample); + + $samples[] = [ $start, $end ]; + } + + return $samples; + } + + /** + * @param float|int|numeric-string $number + */ + private static function precision_from(float|int|string $number): int + { + return Number::precision_from($number); + } + + /** + * @param array $samples + */ + private function __construct( + private readonly array $samples + ) { + } + + /** + * Note: The iterator yields numeric strings to avoid '0.30000000000000004' when '0.3' is correct, + * and to avoid removing trailing zeros e.g. '1.0' or '1.00'. + */ + public function getIterator(): Traversable + { + foreach ($this->samples as $sample) { + if (!is_array($sample)) { + yield $sample; + + continue; + } + + /** + * @var numeric-string $start + * @var numeric-string $end + */ + + [ $start, $end ] = $sample; + + $precision = self::precision_from($start) ?: self::precision_from($end); + $step = 1 / (int)('1' . str_repeat('0', $precision)); + $start += 0; + $end += 0; + + // we use a for/times, so we don't lose quantities, compared to a $start += $step + $times = ($end - $start) / $step; + + for ($i = 0; $i < $times + 1; $i++) { + yield (string)($start + $step * $i); + } + } + } } diff --git a/lib/Plurals/SamplesCache.php b/lib/Plurals/SamplesCache.php index 387b151..4e4ffd5 100644 --- a/lib/Plurals/SamplesCache.php +++ b/lib/Plurals/SamplesCache.php @@ -7,17 +7,17 @@ */ final class SamplesCache { - /** - * @var array - * Where _key_ is a rule statement and _value_ a {@link Samples}. - */ - static private array $instances = []; + /** + * @var array + * Where _key_ is a rule statement and _value_ a {@link Samples}. + */ + private static array $instances = []; - /** - * @param callable():Samples $new - */ - static public function get(string $samples, callable $new): Samples - { - return self::$instances[$samples] ??= $new(); - } + /** + * @param callable():Samples $new + */ + public static function get(string $samples, callable $new): Samples + { + return self::$instances[$samples] ??= $new(); + } } diff --git a/lib/Provider.php b/lib/Provider.php index 6d5ad0f..c7de1a2 100644 --- a/lib/Provider.php +++ b/lib/Provider.php @@ -7,12 +7,12 @@ */ interface Provider { - /** - * The section path, following the pattern "/
". - * - * @throws ResourceNotFound when the specified path does not exist on the CLDR source. - * - * @return array - */ - public function provide(string $path): array; + /** + * The section path, following the pattern "/
". + * + * @return array + * @throws ResourceNotFound when the specified path does not exist on the CLDR source. + * + */ + public function provide(string $path): array; } diff --git a/lib/Provider/CachedProvider.php b/lib/Provider/CachedProvider.php index d6b11a0..2af78e7 100644 --- a/lib/Provider/CachedProvider.php +++ b/lib/Provider/CachedProvider.php @@ -10,28 +10,27 @@ */ final class CachedProvider implements Provider { - public function __construct( - private readonly Provider $provider, - private readonly Cache $cache - ) { - } - - /** - * @inheritDoc - */ - public function provide(string $path): array - { - $data = $this->cache->get($path); - - if ($data !== null) - { - return $data; - } - - $data = $this->provider->provide($path); - - $this->cache->set($path, $data); - - return $data; - } + public function __construct( + private readonly Provider $provider, + private readonly Cache $cache + ) { + } + + /** + * @inheritDoc + */ + public function provide(string $path): array + { + $data = $this->cache->get($path); + + if ($data !== null) { + return $data; + } + + $data = $this->provider->provide($path); + + $this->cache->set($path, $data); + + return $data; + } } diff --git a/lib/Provider/FailingProvider.php b/lib/Provider/FailingProvider.php index c110b0b..9897621 100644 --- a/lib/Provider/FailingProvider.php +++ b/lib/Provider/FailingProvider.php @@ -12,8 +12,8 @@ */ final class FailingProvider implements Provider { - public function provide(string $path): array - { - throw new ResourceNotFound("Only warmed-up data is available, tried to read from: $path"); - } + public function provide(string $path): array + { + throw new ResourceNotFound("Only warmed-up data is available, tried to read from: $path"); + } } diff --git a/lib/Provider/WebProvider.php b/lib/Provider/WebProvider.php index f163d79..145074f 100644 --- a/lib/Provider/WebProvider.php +++ b/lib/Provider/WebProvider.php @@ -25,56 +25,55 @@ */ final class WebProvider implements Provider { - private CurlHandle $connection; + private CurlHandle $connection; - public function __construct( - private readonly UrlResolver $url_resolver = new UrlResolver() - ) { - } + public function __construct( + private readonly UrlResolver $url_resolver = new UrlResolver() + ) { + } - /** - * @inheritDoc - */ - public function provide(string $path): array - { - $connection = $this->obtain_connection(); - $url = $this->url_resolver->resolve($path); + /** + * @inheritDoc + */ + public function provide(string $path): array + { + $connection = $this->obtain_connection(); + $url = $this->url_resolver->resolve($path); - curl_setopt($connection, CURLOPT_URL, $url); + curl_setopt($connection, CURLOPT_URL, $url); - $rc = curl_exec($connection); + $rc = curl_exec($connection); - $http_code = curl_getinfo($connection, CURLINFO_HTTP_CODE); + $http_code = curl_getinfo($connection, CURLINFO_HTTP_CODE); - if ($http_code != 200) - { - throw new ResourceNotFound("Unable to fetch '$path', 'GET $url' responds with $http_code"); - } + if ($http_code != 200) { + throw new ResourceNotFound("Unable to fetch '$path', 'GET $url' responds with $http_code"); + } - assert(is_string($rc)); + assert(is_string($rc)); - return json_decode($rc, true); - } + return json_decode($rc, true); + } - /** - * Returns a reusable cURL connection. - */ - private function obtain_connection(): CurlHandle - { - return $this->connection ??= $this->create_connection(); - } + /** + * Returns a reusable cURL connection. + */ + private function obtain_connection(): CurlHandle + { + return $this->connection ??= $this->create_connection(); + } - private function create_connection(): CurlHandle - { - $connection = curl_init(); + private function create_connection(): CurlHandle + { + $connection = curl_init(); - curl_setopt_array($connection, [ + curl_setopt_array($connection, [ - CURLOPT_FAILONERROR => true, - CURLOPT_RETURNTRANSFER => true + CURLOPT_FAILONERROR => true, + CURLOPT_RETURNTRANSFER => true - ]); + ]); - return $connection; - } + return $connection; + } } diff --git a/lib/Repository.php b/lib/Repository.php index 16efa4c..5e34386 100644 --- a/lib/Repository.php +++ b/lib/Repository.php @@ -29,171 +29,169 @@ */ final class Repository { - /** - * @uses get_supplemental - * @uses get_number_formatter - * @uses get_currency_formatter - * @uses get_list_formatter - * @uses get_list_formatter - * @uses get_plurals - * @uses get_available_locales - */ - use AccessorTrait; - - public function __construct( - public readonly Provider $provider - ) { - } - - private Supplemental $supplemental; - - private function get_supplemental(): Supplemental - { - return $this->supplemental ??= new Supplemental($this); - } - - private NumberFormatter $number_formatter; - - private function get_number_formatter(): NumberFormatter - { - return $this->number_formatter ??= new NumberFormatter(); - } - - private CurrencyFormatter $currency_formatter; - - private function get_currency_formatter(): CurrencyFormatter - { - return $this->currency_formatter ??= new CurrencyFormatter($this->get_number_formatter()); - } - - private ListFormatter $list_formatter; - - private function get_list_formatter(): ListFormatter - { - return $this->list_formatter ??= new ListFormatter(); - } - - private Plurals $plurals; - - private function get_plurals(): Plurals - { - /** @phpstan-ignore-next-line */ - return $this->plurals ??= new Plurals($this->get_supplemental()['plurals']); - } - - /** - * @var array - */ - private array $available_locales; - - /** - * @return array - * - * @throws ResourceNotFound - */ - private function get_available_locales(): array - { - return $this->available_locales ??= $this->fetch('core/availableLocales', 'availableLocales/modern'); - } - - /** - * Fetches the data available at the specified path. - * - * @param string|null $data_path Path to the data to extract. - * - * @throws ResourceNotFound - * - * @phpstan-ignore-next-line - */ - public function fetch(string $path, string $data_path = null): array - { - $data = $this->provider->provide($path); - - if ($data_path) { - $data_path = explode('/', $data_path); - - while ($data_path) { - $p = array_shift($data_path); - $data = $data[$p]; - } - } - - return $data; - } - - /** - * Format a number with the specified pattern. - * - * Note, if the pattern contains '%', the number will be multiplied by 100 first. If the - * pattern contains '‰', the number will be multiplied by 1000. - * - * @param float|int|numeric-string $number - * The number to format. - * @param string|NumberPattern $pattern - * The pattern used to format the number. - */ - public function format_number( - float|int|string $number, - NumberPattern|string $pattern, - Symbols $symbols = null, - ): string { - return $this->number_formatter->format($number, $pattern, $symbols); - } - - /** - * Format a number with the specified pattern. - * - * @param float|int|numeric-string $number - * The number to format. - * - * @see CurrencyFormatter::format() - */ - public function format_currency( - float|int|string $number, - NumberPattern|string $pattern, - Symbols $symbols = null, - string $currencySymbol = CurrencyFormatter::DEFAULT_CURRENCY_SYMBOL - ): string { - return $this->currency_formatter->format($number, $pattern, $symbols, $currencySymbol); - } - - /** - * Formats variable-length lists of scalars. - * - * @param scalar[] $list - * - * @see ListFormatter::format() - */ - public function format_list(array $list, ListPattern $list_pattern): string - { - return $this->list_formatter->format($list, $list_pattern); - } - - /** - * @param string|LocaleId $id - * A locale ID; for example, fr-BE. - */ - public function locale_for(string|LocaleId $id): Locale - { - if (!$id instanceof LocaleId) - { - $id = LocaleId::of($id); - } - - return new Locale($this, $id); - } - - /** - * @param string|TerritoryCode $code - * A territory code; for example, CA. - */ - public function territory_for(string|TerritoryCode $code): Territory - { - if (!$code instanceof TerritoryCode) - { - $code = TerritoryCode::of($code); - } - - return new Territory($this, $code); - } + /** + * @uses get_supplemental + * @uses get_number_formatter + * @uses get_currency_formatter + * @uses get_list_formatter + * @uses get_list_formatter + * @uses get_plurals + * @uses get_available_locales + */ + use AccessorTrait; + + public function __construct( + public readonly Provider $provider + ) { + } + + private Supplemental $supplemental; + + private function get_supplemental(): Supplemental + { + return $this->supplemental ??= new Supplemental($this); + } + + private NumberFormatter $number_formatter; + + private function get_number_formatter(): NumberFormatter + { + return $this->number_formatter ??= new NumberFormatter(); + } + + private CurrencyFormatter $currency_formatter; + + private function get_currency_formatter(): CurrencyFormatter + { + return $this->currency_formatter ??= new CurrencyFormatter($this->get_number_formatter()); + } + + private ListFormatter $list_formatter; + + private function get_list_formatter(): ListFormatter + { + return $this->list_formatter ??= new ListFormatter(); + } + + private Plurals $plurals; + + private function get_plurals(): Plurals + { + /** @phpstan-ignore-next-line */ + return $this->plurals ??= new Plurals($this->get_supplemental()['plurals']); + } + + /** + * @var array + */ + private array $available_locales; + + /** + * @return array + * + * @throws ResourceNotFound + */ + private function get_available_locales(): array + { + return $this->available_locales ??= $this->fetch('core/availableLocales', 'availableLocales/modern'); + } + + /** + * Fetches the data available at the specified path. + * + * @param string|null $data_path Path to the data to extract. + * + * @throws ResourceNotFound + * + * @phpstan-ignore-next-line + */ + public function fetch(string $path, string $data_path = null): array + { + $data = $this->provider->provide($path); + + if ($data_path) { + $data_path = explode('/', $data_path); + + while ($data_path) { + $p = array_shift($data_path); + $data = $data[$p]; + } + } + + return $data; + } + + /** + * Format a number with the specified pattern. + * + * Note, if the pattern contains '%', the number will be multiplied by 100 first. If the + * pattern contains '‰', the number will be multiplied by 1000. + * + * @param float|int|numeric-string $number + * The number to format. + * @param string|NumberPattern $pattern + * The pattern used to format the number. + */ + public function format_number( + float|int|string $number, + NumberPattern|string $pattern, + Symbols $symbols = null, + ): string { + return $this->number_formatter->format($number, $pattern, $symbols); + } + + /** + * Format a number with the specified pattern. + * + * @param float|int|numeric-string $number + * The number to format. + * + * @see CurrencyFormatter::format() + */ + public function format_currency( + float|int|string $number, + NumberPattern|string $pattern, + Symbols $symbols = null, + string $currencySymbol = CurrencyFormatter::DEFAULT_CURRENCY_SYMBOL + ): string { + return $this->currency_formatter->format($number, $pattern, $symbols, $currencySymbol); + } + + /** + * Formats variable-length lists of scalars. + * + * @param scalar[] $list + * + * @see ListFormatter::format() + */ + public function format_list(array $list, ListPattern $list_pattern): string + { + return $this->list_formatter->format($list, $list_pattern); + } + + /** + * @param string|LocaleId $id + * A locale ID; for example, fr-BE. + */ + public function locale_for(string|LocaleId $id): Locale + { + if (!$id instanceof LocaleId) { + $id = LocaleId::of($id); + } + + return new Locale($this, $id); + } + + /** + * @param string|TerritoryCode $code + * A territory code; for example, CA. + */ + public function territory_for(string|TerritoryCode $code): Territory + { + if (!$code instanceof TerritoryCode) { + $code = TerritoryCode::of($code); + } + + return new Territory($this, $code); + } } diff --git a/lib/Spaces.php b/lib/Spaces.php index 93669e1..0421ad1 100644 --- a/lib/Spaces.php +++ b/lib/Spaces.php @@ -9,21 +9,21 @@ */ interface Spaces { - public const SPACE = "\x20"; - public const NO_BREAK_SPACE = "\xc2\xa0"; - public const OGHAM_SPACE_MARK = "\xE1\x9A\x80"; - public const EN_QUAD = "\xE2\x80\x80"; - public const EM_QUAD = "\xE2\x80\x81"; - public const EN_SPACE = "\xE2\x80\x82"; - public const EM_SPACE = "\xE2\x80\x83"; - public const THREE_PER_EM_SPACE = "\xE2\x80\x84"; - public const FOUR_PER_EM_SPACE = "\xE2\x80\x85"; - public const SIX_PER_EM_SPACE = "\xE2\x80\x86"; - public const FIGURE_SPACE = "\xE2\x80\x87"; - public const PUNCTUATION_SPACE = "\xE2\x80\x88"; - public const THIN_SPACE = "\xE2\x80\x89"; - public const HAIR_SPACE = "\xE2\x80\x8A"; - public const NARROW_NO_BREAK_SPACE = "\xE2\x80\xAF"; + public const SPACE = "\x20"; + public const NO_BREAK_SPACE = "\xc2\xa0"; + public const OGHAM_SPACE_MARK = "\xE1\x9A\x80"; + public const EN_QUAD = "\xE2\x80\x80"; + public const EM_QUAD = "\xE2\x80\x81"; + public const EN_SPACE = "\xE2\x80\x82"; + public const EM_SPACE = "\xE2\x80\x83"; + public const THREE_PER_EM_SPACE = "\xE2\x80\x84"; + public const FOUR_PER_EM_SPACE = "\xE2\x80\x85"; + public const SIX_PER_EM_SPACE = "\xE2\x80\x86"; + public const FIGURE_SPACE = "\xE2\x80\x87"; + public const PUNCTUATION_SPACE = "\xE2\x80\x88"; + public const THIN_SPACE = "\xE2\x80\x89"; + public const HAIR_SPACE = "\xE2\x80\x8A"; + public const NARROW_NO_BREAK_SPACE = "\xE2\x80\xAF"; public const MEDIUM_MATHEMATICAL_SPACE = "\xE2\x81\x9F"; - public const IDEOGRAPHIC_SPACE = "\xE3\x80\x80"; + public const IDEOGRAPHIC_SPACE = "\xE3\x80\x80"; } diff --git a/lib/Supplemental.php b/lib/Supplemental.php index 51dcbd8..910d3b5 100644 --- a/lib/Supplemental.php +++ b/lib/Supplemental.php @@ -19,64 +19,64 @@ */ final class Supplemental extends AbstractSectionCollection implements Warmable { - /** - * Where _key_ is a property, matching a CLDR filename, and _value_ is an array path under "supplemental". - */ - private const OFFSET_MAPPING = [ + /** + * Where _key_ is a property, matching a CLDR filename, and _value_ is an array path under "supplemental". + */ + private const OFFSET_MAPPING = [ - 'aliases' => 'metadata/alias', - 'calendarData' => 'calendarData', - 'calendarPreferenceData' => 'calendarPreferenceData', - 'characterFallbacks' => 'characters/character-fallback', - 'codeMappings' => 'codeMappings', - 'currencyData' => 'currencyData', - 'dayPeriods' => 'dayPeriodRuleSet', - 'gender' => 'gender', - 'grammaticalFeatures' => 'grammaticalData', - 'languageData' => 'languageData', - 'languageGroups' => 'languageGroups', - 'languageMatching' => 'languageMatching', - 'likelySubtags' => 'likelySubtags', - 'measurementData' => 'measurementData', - 'metaZones' => 'metaZones', - 'numberingSystems' => 'numberingSystems', - 'ordinals' => 'plurals-type-ordinal', - 'parentLocales' => 'parentLocales/parentLocale', - 'pluralRanges' => 'plurals', - 'plurals' => 'plurals-type-cardinal', - 'primaryZones' => 'primaryZones', - 'references' => 'references', - 'territoryContainment' => 'territoryContainment', - 'territoryInfo' => 'territoryInfo', - 'timeData' => 'timeData', - 'unitPreferenceData' => 'unitPreferenceData', - 'weekData' => 'weekData', - 'windowsZones' => 'windowsZones', + 'aliases' => 'metadata/alias', + 'calendarData' => 'calendarData', + 'calendarPreferenceData' => 'calendarPreferenceData', + 'characterFallbacks' => 'characters/character-fallback', + 'codeMappings' => 'codeMappings', + 'currencyData' => 'currencyData', + 'dayPeriods' => 'dayPeriodRuleSet', + 'gender' => 'gender', + 'grammaticalFeatures' => 'grammaticalData', + 'languageData' => 'languageData', + 'languageGroups' => 'languageGroups', + 'languageMatching' => 'languageMatching', + 'likelySubtags' => 'likelySubtags', + 'measurementData' => 'measurementData', + 'metaZones' => 'metaZones', + 'numberingSystems' => 'numberingSystems', + 'ordinals' => 'plurals-type-ordinal', + 'parentLocales' => 'parentLocales/parentLocale', + 'pluralRanges' => 'plurals', + 'plurals' => 'plurals-type-cardinal', + 'primaryZones' => 'primaryZones', + 'references' => 'references', + 'territoryContainment' => 'territoryContainment', + 'territoryInfo' => 'territoryInfo', + 'timeData' => 'timeData', + 'unitPreferenceData' => 'unitPreferenceData', + 'weekData' => 'weekData', + 'windowsZones' => 'windowsZones', - ]; + ]; - public function offsetExists($offset): bool - { - return isset(self::OFFSET_MAPPING[$offset]); - } + public function offsetExists($offset): bool + { + return isset(self::OFFSET_MAPPING[$offset]); + } - public function warm_up(Closure $progress): void - { + public function warm_up(Closure $progress): void + { $progress("Warming up supplemental:"); - foreach (array_keys(self::OFFSET_MAPPING) as $offset) { - $progress("- $offset"); - $this->offsetGet($offset); - } - } + foreach (array_keys(self::OFFSET_MAPPING) as $offset) { + $progress("- $offset"); + $this->offsetGet($offset); + } + } - protected function path_for(string $offset): string - { - return "core/supplemental/$offset"; - } + protected function path_for(string $offset): string + { + return "core/supplemental/$offset"; + } - protected function data_path_for(string $offset): string - { - return "supplemental/" . self::OFFSET_MAPPING[$offset]; - } + protected function data_path_for(string $offset): string + { + return "supplemental/" . self::OFFSET_MAPPING[$offset]; + } } diff --git a/lib/Supplemental/Fraction.php b/lib/Supplemental/Fraction.php index 98dbbb2..3848f2a 100644 --- a/lib/Supplemental/Fraction.php +++ b/lib/Supplemental/Fraction.php @@ -9,37 +9,37 @@ */ final class Fraction { - /** - * @param array{ _digits?: string, _rounding?: string, _cashDigits?: string, _cashRounding?: string } $data - */ - static public function from(array $data): self - { - $digits = (int) ($data['_digits'] ?? 2); - $rounding = (int) ($data['_rounding'] ?? 0); + /** + * @param array{ _digits?: string, _rounding?: string, _cashDigits?: string, _cashRounding?: string } $data + */ + public static function from(array $data): self + { + $digits = (int)($data['_digits'] ?? 2); + $rounding = (int)($data['_rounding'] ?? 0); - return new self( - $digits, - $rounding, - (int) ($data['_cashDigits'] ?? $digits), - (int) ($data['_cashRounding'] ?? $rounding) - ); - } + return new self( + $digits, + $rounding, + (int)($data['_cashDigits'] ?? $digits), + (int)($data['_cashRounding'] ?? $rounding) + ); + } - /** - * @param int $digits - * The minimum and maximum number of decimal digits normally formatted. - * @param int $rounding - * The rounding increment, in units of 10^-digits. - * @param int $cash_digits - * The number of decimal digits to be used when formatting quantities used in cash transactions. - * @param int $cash_rounding - * The cash rounding increment, in units of 10^cashDigits. - */ - private function __construct( - public readonly int $digits, - public readonly int $rounding, - public readonly int $cash_digits, - public readonly int $cash_rounding - ) { - } + /** + * @param int $digits + * The minimum and maximum number of decimal digits normally formatted. + * @param int $rounding + * The rounding increment, in units of 10^-digits. + * @param int $cash_digits + * The number of decimal digits to be used when formatting quantities used in cash transactions. + * @param int $cash_rounding + * The cash rounding increment, in units of 10^cashDigits. + */ + private function __construct( + public readonly int $digits, + public readonly int $rounding, + public readonly int $cash_digits, + public readonly int $cash_rounding + ) { + } } diff --git a/lib/Territory.php b/lib/Territory.php index 9737d8d..3b94f11 100644 --- a/lib/Territory.php +++ b/lib/Territory.php @@ -7,6 +7,7 @@ use ICanBoogie\Accessor\AccessorTrait; use ICanBoogie\CLDR\Territory\RegionCurrencies; use Throwable; + use function ICanBoogie\trim_prefix; use function in_array; @@ -31,215 +32,218 @@ */ final class Territory implements Localizable { - /** - * @uses get_containment - * @uses get_currencies - * @uses get_currency - * @uses get_info - * @uses get_language - * @uses get_first_day - * @uses get_weekend_start - * @uses get_weekend_end - * @uses get_population - */ - use AccessorTrait; - - /** - * @phpstan-ignore-next-line - */ - private array $containment; - - /** - * @return array - */ - private function get_containment(): array - { - return $this->containment ??= $this->retrieve_from_supplemental('territoryContainment'); - } - - private RegionCurrencies $currencies; - - private function get_currencies(): RegionCurrencies - { - return $this->currencies ??= RegionCurrencies::from( - /** @phpstan-ignore-next-line */ - $this->repository->supplemental['currencyData']['region'][$this->code->value] - ); - } - - private ?Currency $currency; - - /** - * @throws Throwable - */ - private function get_currency(): ?Currency - { - return $this->currency ??= $this->currency_at(); - } - - /** - * @phpstan-ignore-next-line - */ - private array $info; - - /** - * @return array - */ - private function get_info(): array - { - return $this->info ??= $this->retrieve_from_supplemental('territoryInfo'); - } - - private string|false $language; - - /** - * Return the ISO code of the official language of the territory. - * - * @return string|false - * The ISO code of the official language, or `false` if it cannot be determined. - */ - private function get_language(): string|false - { - $make = function () { - $info = $this->get_info(); - - foreach ($info['languagePopulation'] as $language => $lp) { - if (empty($lp['_officialStatus']) || ($lp['_officialStatus'] != "official" && $lp['_officialStatus'] != "de_facto_official")) { - continue; - } - - return $language; - } - - return false; - }; - - return $this->language ??= $make(); - } - - private function get_first_day(): string - { - return $this->resolve_week_data('firstDay'); - } - - private function get_weekend_start(): string - { - return $this->resolve_week_data('weekendStart'); - } - - private function get_weekend_end(): string - { - return $this->resolve_week_data('weekendEnd'); - } - - private function get_population(): int - { - $info = $this->get_info(); - - return (int)$info['_population']; - } - - public function __construct( - public readonly Repository $repository, - public readonly TerritoryCode $code, - ) { - } - - public function __toString(): string - { - return $this->code->value; - } - - /** - * @return mixed - */ - public function __get(string $property) - { - if (str_starts_with($property, 'name_as_')) { - $locale_id = trim_prefix($property, 'name_as_'); - $locale_id = strtr($locale_id, '_', '-'); - - return $this->name_as($locale_id); - } - - return $this->accessor_get($property); - } - - /** - * @return array - */ - private function retrieve_from_supplemental(string $section): array - { - /** @phpstan-ignore-next-line */ - return $this->repository->supplemental[$section][$this->code->value]; - } - - /** - * Return the currency used in the territory at a point in time. - * - * @param DateTimeInterface|string|null $date - * - * @throws Throwable - */ - public function currency_at(DateTimeInterface|string $date = null): ?Currency - { - $date = $this->ensure_is_datetime($date)->format('Y-m-d'); - - $code = $this->get_currencies()->at($date)?->code; - - if (!$code) { - return null; - } - - return Currency::of($code); - } - - /** - * Whether the territory contains the specified territory. - */ - public function is_containing(string $code): bool - { - $containment = $this->get_containment(); - - return in_array($code, $containment['_contains']); - } - - /** - * Return the name of the territory localized according to the specified locale code. - */ - public function name_as(string|LocaleId $locale_id): string - { - return $this->localized($locale_id)->name; - } - - public function localized(Locale|LocaleId|string $locale): LocalizedTerritory - { - if (!$locale instanceof Locale) { - $locale = $this->repository->locale_for($locale); - } - - return new LocalizedTerritory($this, $locale); - } - - private function resolve_week_data(string $which): string - { - $code = $this->code; - /** @phpstan-ignore-next-line */ - $data = $this->repository->supplemental['weekData'][$which]; - - return $data[$code->value] ?? $data['001']; - } - - private function ensure_is_datetime(DateTimeInterface|string|null $datetime): DateTimeInterface - { - if ($datetime === null) { - return new DateTimeImmutable(); - } - - if ($datetime instanceof DateTimeInterface) { - return $datetime; - } - - return new DateTimeImmutable($datetime); - } + /** + * @uses get_containment + * @uses get_currencies + * @uses get_currency + * @uses get_info + * @uses get_language + * @uses get_first_day + * @uses get_weekend_start + * @uses get_weekend_end + * @uses get_population + */ + use AccessorTrait; + + /** + * @phpstan-ignore-next-line + */ + private array $containment; + + /** + * @return array + */ + private function get_containment(): array + { + return $this->containment ??= $this->retrieve_from_supplemental('territoryContainment'); + } + + private RegionCurrencies $currencies; + + private function get_currencies(): RegionCurrencies + { + return $this->currencies ??= RegionCurrencies::from( + /** @phpstan-ignore-next-line */ + $this->repository->supplemental['currencyData']['region'][$this->code->value] + ); + } + + private ?Currency $currency; + + /** + * @throws Throwable + */ + private function get_currency(): ?Currency + { + return $this->currency ??= $this->currency_at(); + } + + /** + * @phpstan-ignore-next-line + */ + private array $info; + + /** + * @return array + */ + private function get_info(): array + { + return $this->info ??= $this->retrieve_from_supplemental('territoryInfo'); + } + + private string|false $language; + + /** + * Return the ISO code of the official language of the territory. + * + * @return string|false + * The ISO code of the official language, or `false` if it cannot be determined. + */ + private function get_language(): string|false + { + $make = function () { + $info = $this->get_info(); + + foreach ($info['languagePopulation'] as $language => $lp) { + if ( + empty($lp['_officialStatus']) + || ($lp['_officialStatus'] != "official" && $lp['_officialStatus'] != "de_facto_official") + ) { + continue; + } + + return $language; + } + + return false; + }; + + return $this->language ??= $make(); + } + + private function get_first_day(): string + { + return $this->resolve_week_data('firstDay'); + } + + private function get_weekend_start(): string + { + return $this->resolve_week_data('weekendStart'); + } + + private function get_weekend_end(): string + { + return $this->resolve_week_data('weekendEnd'); + } + + private function get_population(): int + { + $info = $this->get_info(); + + return (int)$info['_population']; + } + + public function __construct( + public readonly Repository $repository, + public readonly TerritoryCode $code, + ) { + } + + public function __toString(): string + { + return $this->code->value; + } + + /** + * @return mixed + */ + public function __get(string $property) + { + if (str_starts_with($property, 'name_as_')) { + $locale_id = trim_prefix($property, 'name_as_'); + $locale_id = strtr($locale_id, '_', '-'); + + return $this->name_as($locale_id); + } + + return $this->accessor_get($property); + } + + /** + * @return array + */ + private function retrieve_from_supplemental(string $section): array + { + /** @phpstan-ignore-next-line */ + return $this->repository->supplemental[$section][$this->code->value]; + } + + /** + * Return the currency used in the territory at a point in time. + * + * @param DateTimeInterface|string|null $date + * + * @throws Throwable + */ + public function currency_at(DateTimeInterface|string $date = null): ?Currency + { + $date = $this->ensure_is_datetime($date)->format('Y-m-d'); + + $code = $this->get_currencies()->at($date)?->code; + + if (!$code) { + return null; + } + + return Currency::of($code); + } + + /** + * Whether the territory contains the specified territory. + */ + public function is_containing(string $code): bool + { + $containment = $this->get_containment(); + + return in_array($code, $containment['_contains']); + } + + /** + * Return the name of the territory localized according to the specified locale code. + */ + public function name_as(string|LocaleId $locale_id): string + { + return $this->localized($locale_id)->name; + } + + public function localized(Locale|LocaleId|string $locale): LocalizedTerritory + { + if (!$locale instanceof Locale) { + $locale = $this->repository->locale_for($locale); + } + + return new LocalizedTerritory($this, $locale); + } + + private function resolve_week_data(string $which): string + { + $code = $this->code; + /** @phpstan-ignore-next-line */ + $data = $this->repository->supplemental['weekData'][$which]; + + return $data[$code->value] ?? $data['001']; + } + + private function ensure_is_datetime(DateTimeInterface|string|null $datetime): DateTimeInterface + { + if ($datetime === null) { + return new DateTimeImmutable(); + } + + if ($datetime instanceof DateTimeInterface) { + return $datetime; + } + + return new DateTimeImmutable($datetime); + } } diff --git a/lib/Territory/RegionCurrencies.php b/lib/Territory/RegionCurrencies.php index 9b771ee..4321970 100644 --- a/lib/Territory/RegionCurrencies.php +++ b/lib/Territory/RegionCurrencies.php @@ -11,52 +11,52 @@ */ final class RegionCurrencies implements IteratorAggregate { - /** - * @phpstan-ignore-next-line - */ - public static function from(array $data): self - { - $currencies = array_values(array_map(fn($currency) => RegionCurrency::from($currency), $data)); - - usort($currencies, function (RegionCurrency $a, RegionCurrency $b) { - if ($a->from && $b->from) { - return $a->from <=> $b->from; - } elseif ($a->to && $b->to) { - return $b->to <=> $a->to; - } - - return -1; - }); - - return new self($currencies); - } - - /** - * @param RegionCurrency[] $currencies - */ - private function __construct( - private readonly array $currencies - ) { - } - - public function getIterator(): Traversable - { - return new ArrayIterator($this->currencies); - } - - public function at(string $date): ?RegionCurrency - { - $currencies = array_filter( - $this->currencies, - function (RegionCurrency $currency) use ($date) { - return $currency->tender - && ($currency->to === null || $currency->to >= $date) - && ($currency->from === null || $currency->from <= $date); - } - ); - - $currencies = array_values($currencies); - - return current($currencies) ?: null; - } + /** + * @phpstan-ignore-next-line + */ + public static function from(array $data): self + { + $currencies = array_values(array_map(fn($currency) => RegionCurrency::from($currency), $data)); + + usort($currencies, function (RegionCurrency $a, RegionCurrency $b) { + if ($a->from && $b->from) { + return $a->from <=> $b->from; + } elseif ($a->to && $b->to) { + return $b->to <=> $a->to; + } + + return -1; + }); + + return new self($currencies); + } + + /** + * @param RegionCurrency[] $currencies + */ + private function __construct( + private readonly array $currencies + ) { + } + + public function getIterator(): Traversable + { + return new ArrayIterator($this->currencies); + } + + public function at(string $date): ?RegionCurrency + { + $currencies = array_filter( + $this->currencies, + function (RegionCurrency $currency) use ($date) { + return $currency->tender + && ($currency->to === null || $currency->to >= $date) + && ($currency->from === null || $currency->from <= $date); + } + ); + + $currencies = array_values($currencies); + + return current($currencies) ?: null; + } } diff --git a/lib/Territory/RegionCurrency.php b/lib/Territory/RegionCurrency.php index 8a8e717..1d34194 100644 --- a/lib/Territory/RegionCurrency.php +++ b/lib/Territory/RegionCurrency.php @@ -4,30 +4,30 @@ final class RegionCurrency { - /** - * @param array $data - */ - public static function from(array $data): self - { - $code = key($data); - $properties = current($data); + /** + * @param array $data + */ + public static function from(array $data): self + { + $code = key($data); + $properties = current($data); - assert(is_string($code)); - assert(is_array($properties)); + assert(is_string($code)); + assert(is_array($properties)); - return new self( - code: $code, - from: $properties['_from'] ?? null, - to: $properties['_to'] ?? null, - tender: ($properties['_tender'] ?? null) != 'false', - ); - } + return new self( + code: $code, + from: $properties['_from'] ?? null, + to: $properties['_to'] ?? null, + tender: ($properties['_tender'] ?? null) != 'false', + ); + } - private function __construct( - public readonly string $code, - public readonly ?string $from, - public readonly ?string $to, - public readonly bool $tender, - ) { - } + private function __construct( + public readonly string $code, + public readonly ?string $from, + public readonly ?string $to, + public readonly bool $tender, + ) { + } } diff --git a/lib/TerritoryCode.php b/lib/TerritoryCode.php index 498c343..82f2d8d 100644 --- a/lib/TerritoryCode.php +++ b/lib/TerritoryCode.php @@ -13,375 +13,375 @@ */ final class TerritoryCode { - /** - * @link https://github.com/unicode-org/cldr-json/blob/45.0.0/cldr-json/cldr-localenames-full/main/en-001/territories.json - */ - public const CODES = - [ - '001', - '002', - '003', - '005', - '009', - '011', - '013', - '014', - '015', - '017', - '018', - '019', - '021', - '029', - '030', - '034', - '035', - '039', - '053', - '054', - '057', - '061', - 142, - 143, - 145, - 150, - 151, - 154, - 155, - 202, - 419, - 'AC', - 'AD', - 'AE', - 'AF', - 'AG', - 'AI', - 'AL', - 'AM', - 'AO', - 'AQ', - 'AR', - 'AS', - 'AT', - 'AU', - 'AW', - 'AX', - 'AZ', - 'BA', - 'BB', - 'BD', - 'BE', - 'BF', - 'BG', - 'BH', - 'BI', - 'BJ', - 'BL', - 'BM', - 'BN', - 'BO', - 'BQ', - 'BR', - 'BS', - 'BT', - 'BV', - 'BW', - 'BY', - 'BZ', - 'CA', - 'CC', - 'CD', - 'CF', - 'CG', - 'CH', - 'CI', - 'CK', - 'CL', - 'CM', - 'CN', - 'CO', - 'CP', - 'CQ', - 'CR', - 'CU', - 'CV', - 'CW', - 'CX', - 'CY', - 'CZ', - 'DE', - 'DG', - 'DJ', - 'DK', - 'DM', - 'DO', - 'DZ', - 'EA', - 'EC', - 'EE', - 'EG', - 'EH', - 'ER', - 'ES', - 'ET', - 'EU', - 'EZ', - 'FI', - 'FJ', - 'FK', - 'FM', - 'FO', - 'FR', - 'GA', - 'GB', - 'GD', - 'GE', - 'GF', - 'GG', - 'GH', - 'GI', - 'GL', - 'GM', - 'GN', - 'GP', - 'GQ', - 'GR', - 'GS', - 'GT', - 'GU', - 'GW', - 'GY', - 'HK', - 'HM', - 'HN', - 'HR', - 'HT', - 'HU', - 'IC', - 'ID', - 'IE', - 'IL', - 'IM', - 'IN', - 'IO', - 'IQ', - 'IR', - 'IS', - 'IT', - 'JE', - 'JM', - 'JO', - 'JP', - 'KE', - 'KG', - 'KH', - 'KI', - 'KM', - 'KN', - 'KP', - 'KR', - 'KW', - 'KY', - 'KZ', - 'LA', - 'LB', - 'LC', - 'LI', - 'LK', - 'LR', - 'LS', - 'LT', - 'LU', - 'LV', - 'LY', - 'MA', - 'MC', - 'MD', - 'ME', - 'MF', - 'MG', - 'MH', - 'MK', - 'ML', - 'MM', - 'MN', - 'MO', - 'MP', - 'MQ', - 'MR', - 'MS', - 'MT', - 'MU', - 'MV', - 'MW', - 'MX', - 'MY', - 'MZ', - 'NA', - 'NC', - 'NE', - 'NF', - 'NG', - 'NI', - 'NL', - 'NO', - 'NP', - 'NR', - 'NU', - 'NZ', - 'OM', - 'PA', - 'PE', - 'PF', - 'PG', - 'PH', - 'PK', - 'PL', - 'PM', - 'PN', - 'PR', - 'PS', - 'PT', - 'PW', - 'PY', - 'QA', - 'QO', - 'RE', - 'RO', - 'RS', - 'RU', - 'RW', - 'SA', - 'SB', - 'SC', - 'SD', - 'SE', - 'SG', - 'SH', - 'SI', - 'SJ', - 'SK', - 'SL', - 'SM', - 'SN', - 'SO', - 'SR', - 'SS', - 'ST', - 'SV', - 'SX', - 'SY', - 'SZ', - 'TA', - 'TC', - 'TD', - 'TF', - 'TG', - 'TH', - 'TJ', - 'TK', - 'TL', - 'TM', - 'TN', - 'TO', - 'TR', - 'TT', - 'TV', - 'TW', - 'TZ', - 'UA', - 'UG', - 'UM', - 'UN', - 'US', - 'UY', - 'UZ', - 'VA', - 'VC', - 'VE', - 'VG', - 'VI', - 'VN', - 'VU', - 'WF', - 'WS', - 'XA', - 'XB', - 'XK', - 'YE', - 'YT', - 'ZA', - 'ZM', - 'ZW', - 'ZZ', - ]; + /** + * @link https://github.com/unicode-org/cldr-json/blob/45.0.0/cldr-json/cldr-localenames-full/main/en-001/territories.json + */ + public const CODES = + [ + '001', + '002', + '003', + '005', + '009', + '011', + '013', + '014', + '015', + '017', + '018', + '019', + '021', + '029', + '030', + '034', + '035', + '039', + '053', + '054', + '057', + '061', + 142, + 143, + 145, + 150, + 151, + 154, + 155, + 202, + 419, + 'AC', + 'AD', + 'AE', + 'AF', + 'AG', + 'AI', + 'AL', + 'AM', + 'AO', + 'AQ', + 'AR', + 'AS', + 'AT', + 'AU', + 'AW', + 'AX', + 'AZ', + 'BA', + 'BB', + 'BD', + 'BE', + 'BF', + 'BG', + 'BH', + 'BI', + 'BJ', + 'BL', + 'BM', + 'BN', + 'BO', + 'BQ', + 'BR', + 'BS', + 'BT', + 'BV', + 'BW', + 'BY', + 'BZ', + 'CA', + 'CC', + 'CD', + 'CF', + 'CG', + 'CH', + 'CI', + 'CK', + 'CL', + 'CM', + 'CN', + 'CO', + 'CP', + 'CQ', + 'CR', + 'CU', + 'CV', + 'CW', + 'CX', + 'CY', + 'CZ', + 'DE', + 'DG', + 'DJ', + 'DK', + 'DM', + 'DO', + 'DZ', + 'EA', + 'EC', + 'EE', + 'EG', + 'EH', + 'ER', + 'ES', + 'ET', + 'EU', + 'EZ', + 'FI', + 'FJ', + 'FK', + 'FM', + 'FO', + 'FR', + 'GA', + 'GB', + 'GD', + 'GE', + 'GF', + 'GG', + 'GH', + 'GI', + 'GL', + 'GM', + 'GN', + 'GP', + 'GQ', + 'GR', + 'GS', + 'GT', + 'GU', + 'GW', + 'GY', + 'HK', + 'HM', + 'HN', + 'HR', + 'HT', + 'HU', + 'IC', + 'ID', + 'IE', + 'IL', + 'IM', + 'IN', + 'IO', + 'IQ', + 'IR', + 'IS', + 'IT', + 'JE', + 'JM', + 'JO', + 'JP', + 'KE', + 'KG', + 'KH', + 'KI', + 'KM', + 'KN', + 'KP', + 'KR', + 'KW', + 'KY', + 'KZ', + 'LA', + 'LB', + 'LC', + 'LI', + 'LK', + 'LR', + 'LS', + 'LT', + 'LU', + 'LV', + 'LY', + 'MA', + 'MC', + 'MD', + 'ME', + 'MF', + 'MG', + 'MH', + 'MK', + 'ML', + 'MM', + 'MN', + 'MO', + 'MP', + 'MQ', + 'MR', + 'MS', + 'MT', + 'MU', + 'MV', + 'MW', + 'MX', + 'MY', + 'MZ', + 'NA', + 'NC', + 'NE', + 'NF', + 'NG', + 'NI', + 'NL', + 'NO', + 'NP', + 'NR', + 'NU', + 'NZ', + 'OM', + 'PA', + 'PE', + 'PF', + 'PG', + 'PH', + 'PK', + 'PL', + 'PM', + 'PN', + 'PR', + 'PS', + 'PT', + 'PW', + 'PY', + 'QA', + 'QO', + 'RE', + 'RO', + 'RS', + 'RU', + 'RW', + 'SA', + 'SB', + 'SC', + 'SD', + 'SE', + 'SG', + 'SH', + 'SI', + 'SJ', + 'SK', + 'SL', + 'SM', + 'SN', + 'SO', + 'SR', + 'SS', + 'ST', + 'SV', + 'SX', + 'SY', + 'SZ', + 'TA', + 'TC', + 'TD', + 'TF', + 'TG', + 'TH', + 'TJ', + 'TK', + 'TL', + 'TM', + 'TN', + 'TO', + 'TR', + 'TT', + 'TV', + 'TW', + 'TZ', + 'UA', + 'UG', + 'UM', + 'UN', + 'US', + 'UY', + 'UZ', + 'VA', + 'VC', + 'VE', + 'VG', + 'VI', + 'VN', + 'VU', + 'WF', + 'WS', + 'XA', + 'XB', + 'XK', + 'YE', + 'YT', + 'ZA', + 'ZM', + 'ZW', + 'ZZ', + ]; - /** - * Whether a currency code is defined. - * - * @param string $code - * A currency code; for example, EUR. - */ - public static function is_defined(string $code): bool - { - return in_array($code, self::CODES); - } + /** + * Whether a currency code is defined. + * + * @param string $code + * A currency code; for example, EUR. + */ + public static function is_defined(string $code): bool + { + return in_array($code, self::CODES); + } - /** - * @param string $code - * A currency code; for example, EUR. - * - * @throws TerritoryNotDefined - */ - public static function assert_is_defined(string $code): void - { - self::is_defined($code) - or throw new TerritoryNotDefined($code); - } + /** + * @param string $code + * A currency code; for example, EUR. + * + * @throws TerritoryNotDefined + */ + public static function assert_is_defined(string $code): void + { + self::is_defined($code) + or throw new TerritoryNotDefined($code); + } - /** - * Returns a {@see CurrencyCode} of the specified code. - * - * @param string $code - * A currency code; for example, EUR. - * - * @throws TerritoryNotDefined - */ - public static function of(string $code): self - { - static $instances; + /** + * Returns a {@see CurrencyCode} of the specified code. + * + * @param string $code + * A currency code; for example, EUR. + * + * @throws TerritoryNotDefined + */ + public static function of(string $code): self + { + static $instances; - self::assert_is_defined($code); + self::assert_is_defined($code); - return $instances[$code] ??= new self($code); - } + return $instances[$code] ??= new self($code); + } - /** - * @param string $value - * A territory value; for example, CA. - */ - private function __construct( - public readonly string $value, - ) { - } + /** + * @param string $value + * A territory value; for example, CA. + */ + private function __construct( + public readonly string $value, + ) { + } - /** - * Returns the {@see $value} of the currency. - */ - public function __toString() : string - { - return $this->value; - } + /** + * Returns the {@see $value} of the currency. + */ + public function __toString(): string + { + return $this->value; + } - public function __serialize(): array - { - return [ 'value' => $this->value ]; - } + public function __serialize(): array + { + return [ 'value' => $this->value ]; + } - /** - * @param array{ value: string } $data - */ - public function __unserialize(array $data): void - { - $this->value = $data['value']; - } + /** + * @param array{ value: string } $data + */ + public function __unserialize(array $data): void + { + $this->value = $data['value']; + } } diff --git a/lib/TerritoryNotDefined.php b/lib/TerritoryNotDefined.php index b2e19e3..b7b48d7 100644 --- a/lib/TerritoryNotDefined.php +++ b/lib/TerritoryNotDefined.php @@ -10,16 +10,16 @@ */ final class TerritoryNotDefined extends InvalidArgumentException implements Exception { - /** - * @param string $territory_code - * The ISO code of the territory. - */ + /** + * @param string $territory_code + * The ISO code of the territory. + */ public function __construct( - public readonly string $territory_code, - string $message = null, - Throwable $previous = null - ) { - $message ??= "Territory not defined for code: $territory_code."; + public readonly string $territory_code, + string $message = null, + Throwable $previous = null + ) { + $message ??= "Territory not defined for code: $territory_code."; parent::__construct($message, 0, $previous); } diff --git a/lib/TimeFormatter.php b/lib/TimeFormatter.php index f06aa89..dfcf19a 100644 --- a/lib/TimeFormatter.php +++ b/lib/TimeFormatter.php @@ -29,16 +29,16 @@ */ final class TimeFormatter extends DateTimeFormatter { - /** - * Resolves length defined in `timeFormats` into a pattern. - */ - protected function resolve_pattern( - string|DateTimeFormatLength|DateTimeFormatId $pattern_or_length_or_id - ): string { - if ($pattern_or_length_or_id instanceof DateTimeFormatLength) { - return $this->calendar['timeFormats'][$pattern_or_length_or_id->value]; - } + /** + * Resolves length defined in `timeFormats` into a pattern. + */ + protected function resolve_pattern( + string|DateTimeFormatLength|DateTimeFormatId $pattern_or_length_or_id + ): string { + if ($pattern_or_length_or_id instanceof DateTimeFormatLength) { + return $this->calendar['timeFormats'][$pattern_or_length_or_id->value]; + } - return parent::resolve_pattern($pattern_or_length_or_id); - } + return parent::resolve_pattern($pattern_or_length_or_id); + } } diff --git a/lib/UTF8Helpers.php b/lib/UTF8Helpers.php index 1dc5baa..577de02 100644 --- a/lib/UTF8Helpers.php +++ b/lib/UTF8Helpers.php @@ -6,9 +6,9 @@ final class UTF8Helpers { - static public function trim(string $string): string - { - /** @var string */ - return preg_replace('/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $string); - } + public static function trim(string $string): string + { + /** @var string */ + return preg_replace('/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $string); + } } diff --git a/lib/UnitLength.php b/lib/UnitLength.php index 825f031..1fe5c7b 100644 --- a/lib/UnitLength.php +++ b/lib/UnitLength.php @@ -4,7 +4,7 @@ enum UnitLength: string { - case LONG = 'long'; - case SHORT = 'short'; - case NARROW = 'narrow'; + case LONG = 'long'; + case SHORT = 'short'; + case NARROW = 'narrow'; } diff --git a/lib/Units.php b/lib/Units.php index 64d510d..0fef257 100644 --- a/lib/Units.php +++ b/lib/Units.php @@ -16,183 +16,183 @@ class Units { use UnitsCompanion; - public const DEFAULT_LENGTH = UnitLength::LONG; - public const COUNT_PREFIX = 'unitPattern-count-'; - - static private function length_to_unit_type(UnitLength $length): ListType - { - return match ($length) { - UnitLength::LONG => ListType::UNIT, - UnitLength::SHORT => ListType::UNIT_SHORT, - UnitLength::NARROW => ListType::UNIT_NARROW, - }; - } - - /** - * @phpstan-ignore-next-line - */ - private readonly array $data; - - public readonly Sequence $sequence; - - public function __construct( - public readonly Locale $locale - ) { - /** @phpstan-ignore-next-line */ - $this->data = $locale['units']; - $this->sequence = new Sequence($this); - } - - /** - * @var array - * Where _key_ is a unit name. - */ - private array $units = []; - - /** - * @return mixed - */ - public function __get(string $property) - { - $unit = strtr($property, '_', '-'); - - if (isset($this->data[self::DEFAULT_LENGTH->value][$unit])) { - return $this->units[$property] ??= new Unit($this, $unit); - } - - throw new PropertyNotDefined(property: $property, container: $this); - } - - /** - * @throws LogicException if the specified unit is not defined. - */ - public function assert_is_unit(string $unit): void - { - $this->data[self::DEFAULT_LENGTH->value][$unit] - ?? throw new LogicException("No such unit: $unit"); - } - - public function name_for(string $unit, UnitLength $length = self::DEFAULT_LENGTH): string - { - $unit = strtr($unit, '_', '-'); - - return $this->data[$length->value][$unit]['displayName']; - } - - /** - * @param float|int|numeric-string $number - */ - public function format(float|int|string $number, string $unit, UnitLength $length = self::DEFAULT_LENGTH): string - { - $pattern = $this->pattern_for_unit($unit, $number, $length); - $number = $this->ensure_number_if_formatted($number); - - return strtr($pattern, ['{0}' => $number]); - } - - /** - * Format a combination of units is X per Y, such as _miles per hour_ or _liters per second_. - * - * @param float|int|numeric-string $number - * - * @see https://www.unicode.org/reports/tr35/tr35-72/tr35-general.html#compound-units - */ - public function format_compound( - float|int|string $number, - string $number_unit, - string $per_unit, - UnitLength $length = self::DEFAULT_LENGTH - ): string { - $formatted = $this->format($number, $number_unit, $length); - $data = $this->data[$length->value][$per_unit]; - - if (isset($data['perUnitPattern'])) { - return strtr($data['perUnitPattern'], [ - - '{0}' => $formatted - - ]); - } - - $denominator = $this->pattern_for_denominator($per_unit, $number, $length); - $pattern = $this->pattern_for_combination($length); - - return strtr($pattern, [ - - '{0}' => $formatted, - '{1}' => $denominator - - ]); - } - - /** - * Units may be used in composed sequences, such as 5° 30′ for 5 degrees 30 minutes, - * or 3 ft 2 in. For that purpose, the appropriate width of the unit listPattern can be used - * to compose the units in a sequence. - * - * @param array $units_and_numbers - * - * @see https://www.unicode.org/reports/tr35/tr35-72/tr35-general.html#Unit_Sequences - */ - public function format_sequence(array $units_and_numbers, UnitLength $length = self::DEFAULT_LENGTH): string - { - $list = []; - - foreach ($units_and_numbers as $unit => $number) { - $list[] = $this->format($number, $unit, $length); - } - - return $this->locale->format_list($list, self::length_to_unit_type($length)); - } - - /** - * @param float|int|numeric-string $number - */ - private function pattern_for_unit(string $unit, float|int|string $number, UnitLength $length): string - { - $this->assert_is_unit($unit); - - $count = $this->count_for($number); - - return $this->data[$length->value][$unit][self::COUNT_PREFIX . $count]; - } - - /** - * @param float|int|numeric-string $number - */ - private function pattern_for_denominator(string $unit, float|int|string $number, UnitLength $length): string - { - $pattern = $this->pattern_for_unit($unit, $number, $length); - - return UTF8Helpers::trim(str_replace('{0}', '', $pattern)); - } - - private function pattern_for_combination(UnitLength $length): string - { - return $this->data[$length->value]['per']['compoundUnitPattern']; - } - - private Plurals $plurals; - - /** - * @param float|int|numeric-string $number - */ - private function count_for(float|int|string $number): string - { - $plurals = $this->plurals ??= $this->locale->repository->plurals; - - return $plurals->rule_for($number, $this->locale->language); - } - - /** - * @param float|int|numeric-string $number - */ - private function ensure_number_if_formatted(float|int|string $number): string - { - if (is_string($number)) { - return $number; - } - - return $this->locale->format_number($number); - } + public const DEFAULT_LENGTH = UnitLength::LONG; + public const COUNT_PREFIX = 'unitPattern-count-'; + + private static function length_to_unit_type(UnitLength $length): ListType + { + return match ($length) { + UnitLength::LONG => ListType::UNIT, + UnitLength::SHORT => ListType::UNIT_SHORT, + UnitLength::NARROW => ListType::UNIT_NARROW, + }; + } + + /** + * @phpstan-ignore-next-line + */ + private readonly array $data; + + public readonly Sequence $sequence; + + public function __construct( + public readonly Locale $locale + ) { + /** @phpstan-ignore-next-line */ + $this->data = $locale['units']; + $this->sequence = new Sequence($this); + } + + /** + * @var array + * Where _key_ is a unit name. + */ + private array $units = []; + + /** + * @return mixed + */ + public function __get(string $property) + { + $unit = strtr($property, '_', '-'); + + if (isset($this->data[self::DEFAULT_LENGTH->value][$unit])) { + return $this->units[$property] ??= new Unit($this, $unit); + } + + throw new PropertyNotDefined(property: $property, container: $this); + } + + /** + * @throws LogicException if the specified unit is not defined. + */ + public function assert_is_unit(string $unit): void + { + $this->data[self::DEFAULT_LENGTH->value][$unit] + ?? throw new LogicException("No such unit: $unit"); + } + + public function name_for(string $unit, UnitLength $length = self::DEFAULT_LENGTH): string + { + $unit = strtr($unit, '_', '-'); + + return $this->data[$length->value][$unit]['displayName']; + } + + /** + * @param float|int|numeric-string $number + */ + public function format(float|int|string $number, string $unit, UnitLength $length = self::DEFAULT_LENGTH): string + { + $pattern = $this->pattern_for_unit($unit, $number, $length); + $number = $this->ensure_number_if_formatted($number); + + return strtr($pattern, [ '{0}' => $number ]); + } + + /** + * Format a combination of units is X per Y, such as _miles per hour_ or _liters per second_. + * + * @param float|int|numeric-string $number + * + * @see https://www.unicode.org/reports/tr35/tr35-72/tr35-general.html#compound-units + */ + public function format_compound( + float|int|string $number, + string $number_unit, + string $per_unit, + UnitLength $length = self::DEFAULT_LENGTH + ): string { + $formatted = $this->format($number, $number_unit, $length); + $data = $this->data[$length->value][$per_unit]; + + if (isset($data['perUnitPattern'])) { + return strtr($data['perUnitPattern'], [ + + '{0}' => $formatted + + ]); + } + + $denominator = $this->pattern_for_denominator($per_unit, $number, $length); + $pattern = $this->pattern_for_combination($length); + + return strtr($pattern, [ + + '{0}' => $formatted, + '{1}' => $denominator + + ]); + } + + /** + * Units may be used in composed sequences, such as 5° 30′ for 5 degrees 30 minutes, + * or 3 ft 2 in. For that purpose, the appropriate width of the unit listPattern can be used + * to compose the units in a sequence. + * + * @param array $units_and_numbers + * + * @see https://www.unicode.org/reports/tr35/tr35-72/tr35-general.html#Unit_Sequences + */ + public function format_sequence(array $units_and_numbers, UnitLength $length = self::DEFAULT_LENGTH): string + { + $list = []; + + foreach ($units_and_numbers as $unit => $number) { + $list[] = $this->format($number, $unit, $length); + } + + return $this->locale->format_list($list, self::length_to_unit_type($length)); + } + + /** + * @param float|int|numeric-string $number + */ + private function pattern_for_unit(string $unit, float|int|string $number, UnitLength $length): string + { + $this->assert_is_unit($unit); + + $count = $this->count_for($number); + + return $this->data[$length->value][$unit][self::COUNT_PREFIX . $count]; + } + + /** + * @param float|int|numeric-string $number + */ + private function pattern_for_denominator(string $unit, float|int|string $number, UnitLength $length): string + { + $pattern = $this->pattern_for_unit($unit, $number, $length); + + return UTF8Helpers::trim(str_replace('{0}', '', $pattern)); + } + + private function pattern_for_combination(UnitLength $length): string + { + return $this->data[$length->value]['per']['compoundUnitPattern']; + } + + private Plurals $plurals; + + /** + * @param float|int|numeric-string $number + */ + private function count_for(float|int|string $number): string + { + $plurals = $this->plurals ??= $this->locale->repository->plurals; + + return $plurals->rule_for($number, $this->locale->language); + } + + /** + * @param float|int|numeric-string $number + */ + private function ensure_number_if_formatted(float|int|string $number): string + { + if (is_string($number)) { + return $number; + } + + return $this->locale->format_number($number); + } } diff --git a/lib/Units/NumberPerUnit.php b/lib/Units/NumberPerUnit.php index 6cfd29c..54ca792 100644 --- a/lib/Units/NumberPerUnit.php +++ b/lib/Units/NumberPerUnit.php @@ -15,51 +15,51 @@ */ final class NumberPerUnit { - /** - * @uses get_as_long - * @uses get_as_short - * @uses get_as_narrow - */ - use AccessorTrait; + /** + * @uses get_as_long + * @uses get_as_short + * @uses get_as_narrow + */ + use AccessorTrait; - /** - * @param float|int|numeric-string $number - */ - public function __construct( - private readonly float|int|string $number, - private readonly string $number_unit, - private readonly string $per_unit, - private readonly Units $units - ) { - } + /** + * @param float|int|numeric-string $number + */ + public function __construct( + private readonly float|int|string $number, + private readonly string $number_unit, + private readonly string $per_unit, + private readonly Units $units + ) { + } - public function __toString(): string - { - return $this->as(Units::DEFAULT_LENGTH); - } + public function __toString(): string + { + return $this->as(Units::DEFAULT_LENGTH); + } - private function get_as_long(): string - { - return $this->as(UnitLength::LONG); - } + private function get_as_long(): string + { + return $this->as(UnitLength::LONG); + } - private function get_as_short(): string - { - return $this->as(UnitLength::SHORT); - } + private function get_as_short(): string + { + return $this->as(UnitLength::SHORT); + } - private function get_as_narrow(): string - { - return $this->as(UnitLength::NARROW); - } + private function get_as_narrow(): string + { + return $this->as(UnitLength::NARROW); + } - private function as(UnitLength $length): string - { - return $this->units->format_compound( - $this->number, - $this->number_unit, - $this->per_unit, - $length - ); - } + private function as(UnitLength $length): string + { + return $this->units->format_compound( + $this->number, + $this->number_unit, + $this->per_unit, + $length + ); + } } diff --git a/lib/Units/NumberWithUnit.php b/lib/Units/NumberWithUnit.php index 05b075a..8cf64ef 100644 --- a/lib/Units/NumberWithUnit.php +++ b/lib/Units/NumberWithUnit.php @@ -15,50 +15,50 @@ */ final class NumberWithUnit { - /** - * @uses get_as_long - * @uses get_as_short - * @uses get_as_narrow - */ - use AccessorTrait; + /** + * @uses get_as_long + * @uses get_as_short + * @uses get_as_narrow + */ + use AccessorTrait; - /** - * @param float|int|numeric-string $number - */ - public function __construct( - private readonly float|int|string $number, - private readonly string $unit, - private readonly Units $units - ) { - } + /** + * @param float|int|numeric-string $number + */ + public function __construct( + private readonly float|int|string $number, + private readonly string $unit, + private readonly Units $units + ) { + } - public function __toString(): string - { - return $this->as(Units::DEFAULT_LENGTH); - } + public function __toString(): string + { + return $this->as(Units::DEFAULT_LENGTH); + } - public function per(Unit|string $unit): NumberPerUnit - { - return new NumberPerUnit($this->number, $this->unit, $unit, $this->units); - } + public function per(Unit|string $unit): NumberPerUnit + { + return new NumberPerUnit($this->number, $this->unit, $unit, $this->units); + } - private function get_as_long(): string - { - return $this->as(UnitLength::LONG); - } + private function get_as_long(): string + { + return $this->as(UnitLength::LONG); + } - private function get_as_short(): string - { - return $this->as(UnitLength::SHORT); - } + private function get_as_short(): string + { + return $this->as(UnitLength::SHORT); + } - private function get_as_narrow(): string - { - return $this->as(UnitLength::NARROW); - } + private function get_as_narrow(): string + { + return $this->as(UnitLength::NARROW); + } - private function as(UnitLength $length): string - { - return $this->units->format($this->number, $this->unit, $length); - } + private function as(UnitLength $length): string + { + return $this->units->format($this->number, $this->unit, $length); + } } diff --git a/lib/Units/Sequence.php b/lib/Units/Sequence.php index cca09a4..f039168 100644 --- a/lib/Units/Sequence.php +++ b/lib/Units/Sequence.php @@ -21,48 +21,48 @@ final class Sequence { use SequenceCompanion; - /** - * @uses get_as_long - * @uses get_as_short - * @uses get_as_narrow - */ - use AccessorTrait; + /** + * @uses get_as_long + * @uses get_as_short + * @uses get_as_narrow + */ + use AccessorTrait; - /** - * @var array - */ - private array $sequence = []; + /** + * @var array + */ + private array $sequence = []; - public function __construct( - private readonly Units $units - ) { - } + public function __construct( + private readonly Units $units + ) { + } - public function __toString(): string - { - return $this->format(); - } + public function __toString(): string + { + return $this->format(); + } - private function get_as_long(): string - { - return $this->format(UnitLength::LONG); - } + private function get_as_long(): string + { + return $this->format(UnitLength::LONG); + } - private function get_as_short(): string - { - return $this->format(UnitLength::SHORT); - } + private function get_as_short(): string + { + return $this->format(UnitLength::SHORT); + } - private function get_as_narrow(): string - { - return $this->format(UnitLength::NARROW); - } + private function get_as_narrow(): string + { + return $this->format(UnitLength::NARROW); + } - /** - * Formats the sequence. - */ - public function format(UnitLength $length = Units::DEFAULT_LENGTH): string - { - return $this->units->format_sequence($this->sequence, $length); - } + /** + * Formats the sequence. + */ + public function format(UnitLength $length = Units::DEFAULT_LENGTH): string + { + return $this->units->format_sequence($this->sequence, $length); + } } diff --git a/lib/Units/Unit.php b/lib/Units/Unit.php index 2095d47..86c285a 100644 --- a/lib/Units/Unit.php +++ b/lib/Units/Unit.php @@ -20,47 +20,47 @@ */ final class Unit { - /** - * @uses get_name - * @uses get_long_name - * @uses get_short_name - * @uses get_narrow_name - */ - use AccessorTrait; + /** + * @uses get_name + * @uses get_long_name + * @uses get_short_name + * @uses get_narrow_name + */ + use AccessorTrait; - private function get_name(): string - { - return $this->long_name; - } + private function get_name(): string + { + return $this->long_name; + } - private function get_long_name(): string - { - return $this->name_for(UnitLength::LONG); - } + private function get_long_name(): string + { + return $this->name_for(UnitLength::LONG); + } - private function get_short_name(): string - { - return $this->name_for(UnitLength::SHORT); - } + private function get_short_name(): string + { + return $this->name_for(UnitLength::SHORT); + } - private function get_narrow_name(): string - { - return $this->name_for(UnitLength::NARROW); - } + private function get_narrow_name(): string + { + return $this->name_for(UnitLength::NARROW); + } - public function __construct( - private readonly Units $units, - private readonly string $unit - ) { - } + public function __construct( + private readonly Units $units, + private readonly string $unit + ) { + } - public function __toString(): string - { - return $this->unit; - } + public function __toString(): string + { + return $this->unit; + } - private function name_for(UnitLength $length): string - { - return $this->units->name_for($this->unit, $length); - } + private function name_for(UnitLength $length): string + { + return $this->units->name_for($this->unit, $length); + } } diff --git a/lib/Warmable.php b/lib/Warmable.php index 8ffab29..58108e5 100644 --- a/lib/Warmable.php +++ b/lib/Warmable.php @@ -9,8 +9,8 @@ */ interface Warmable { - /** - * @param Closure(string $progress):void $progress - */ - public function warm_up(Closure $progress): void; + /** + * @param Closure(string $progress):void $progress + */ + public function warm_up(Closure $progress): void; } diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..b78567b --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,21 @@ + + + lib + tests + + + + + + + + + + + tests/bootstrap.php + + + tests/GitHub/UrlResolverTest.php + + diff --git a/tests/Cache/CacheCollectionTest.php b/tests/Cache/CacheCollectionTest.php index 3068e6f..7bfebc2 100644 --- a/tests/Cache/CacheCollectionTest.php +++ b/tests/Cache/CacheCollectionTest.php @@ -11,13 +11,13 @@ class CacheCollectionTest extends TestCase { - protected function makeCache(): Cache - { - return new CacheCollection([ + protected function makeCache(): Cache + { + return new CacheCollection([ - new RuntimeCache(), - new FileCache(CACHE_DIR), + new RuntimeCache(), + new FileCache(CACHE_DIR), - ]); - } + ]); + } } diff --git a/tests/Cache/FileCacheTest.php b/tests/Cache/FileCacheTest.php index a930c2e..03d96a8 100644 --- a/tests/Cache/FileCacheTest.php +++ b/tests/Cache/FileCacheTest.php @@ -9,8 +9,8 @@ class FileCacheTest extends TestCase { - protected function makeCache(): Cache - { - return new FileCache(CACHE_DIR); - } + protected function makeCache(): Cache + { + return new FileCache(CACHE_DIR); + } } diff --git a/tests/Cache/RedisCacheTest.php b/tests/Cache/RedisCacheTest.php index f8ebfa2..60c65db 100644 --- a/tests/Cache/RedisCacheTest.php +++ b/tests/Cache/RedisCacheTest.php @@ -8,12 +8,12 @@ class RedisCacheTest extends TestCase { - protected function makeCache(): Cache - { - $redis = new Redis(); - // @phpstan-ignore-next-line - $redis->connect(getenv('ICANBOOGIE_CLDR_REDIS_HOST'), getenv('ICANBOOGIE_CLDR_REDIS_PORT')); + protected function makeCache(): Cache + { + $redis = new Redis(); + // @phpstan-ignore-next-line + $redis->connect(getenv('ICANBOOGIE_CLDR_REDIS_HOST'), getenv('ICANBOOGIE_CLDR_REDIS_PORT')); - return new RedisCache($redis); - } + return new RedisCache($redis); + } } diff --git a/tests/Cache/RunTimeCacheTest.php b/tests/Cache/RunTimeCacheTest.php index 01e21ce..786b55c 100644 --- a/tests/Cache/RunTimeCacheTest.php +++ b/tests/Cache/RunTimeCacheTest.php @@ -7,8 +7,8 @@ class RunTimeCacheTest extends TestCase { - protected function makeCache(): Cache - { - return new RuntimeCache(); - } + protected function makeCache(): Cache + { + return new RuntimeCache(); + } } diff --git a/tests/Cache/TestCase.php b/tests/Cache/TestCase.php index 53ed26e..6b0fff1 100644 --- a/tests/Cache/TestCase.php +++ b/tests/Cache/TestCase.php @@ -3,24 +3,25 @@ namespace Test\ICanBoogie\CLDR\Cache; use ICanBoogie\CLDR\Cache; + use function uniqid; abstract class TestCase extends \PHPUnit\Framework\TestCase { - public function testCache(): void - { - $cache = $this->makeCache(); - $this->assertNull($cache->get($path = $this->generatePath())); - $cache->set($path, $data = [ uniqid() => uniqid() ]); - $this->assertSame($data, $cache->get($path)); - $cache->set($path, $data = [ uniqid() => uniqid() ]); - $this->assertSame($data, $cache->get($path)); - } + public function testCache(): void + { + $cache = $this->makeCache(); + $this->assertNull($cache->get($path = $this->generatePath())); + $cache->set($path, $data = [ uniqid() => uniqid() ]); + $this->assertSame($data, $cache->get($path)); + $cache->set($path, $data = [ uniqid() => uniqid() ]); + $this->assertSame($data, $cache->get($path)); + } - abstract protected function makeCache(): Cache; + abstract protected function makeCache(): Cache; - private function generatePath(): string - { - return uniqid() . '/' . uniqid(); - } + private function generatePath(): string + { + return uniqid() . '/' . uniqid(); + } } diff --git a/tests/CalendarCollectionTest.php b/tests/CalendarCollectionTest.php index 3ac3c3a..17f3c5f 100644 --- a/tests/CalendarCollectionTest.php +++ b/tests/CalendarCollectionTest.php @@ -11,59 +11,59 @@ final class CalendarCollectionTest extends TestCase { - private static CalendarCollection $collection; + private static CalendarCollection $collection; - public static function setupBeforeClass(): void - { - self::$collection = locale_for('fr')->calendars; - } + public static function setupBeforeClass(): void + { + self::$collection = locale_for('fr')->calendars; + } - public function test_offsetExists(): void - { - $this->expectException(BadMethodCallException::class); - self::$collection->offsetExists('gregorian'); - } + public function test_offsetExists(): void + { + $this->expectException(BadMethodCallException::class); + self::$collection->offsetExists('gregorian'); + } - public function test_offsetSet(): void - { - $this->expectException(OffsetNotWritable::class); - self::$collection['gregorian'] = null; - } + public function test_offsetSet(): void + { + $this->expectException(OffsetNotWritable::class); + self::$collection['gregorian'] = null; + } - public function test_offsetUnset(): void - { - $this->expectException(OffsetNotWritable::class); - unset(self::$collection['gregorian']); - } + public function test_offsetUnset(): void + { + $this->expectException(OffsetNotWritable::class); + unset(self::$collection['gregorian']); + } - #[DataProvider('provide_test_get')] - public function test_get(string $calendar_id): void - { - $calendar = self::$collection[$calendar_id]; - $this->assertInstanceOf(Calendar::class, $calendar); - } + #[DataProvider('provide_test_get')] + public function test_get(string $calendar_id): void + { + $calendar = self::$collection[$calendar_id]; + $this->assertInstanceOf(Calendar::class, $calendar); + } - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_get(): array - { - return [ + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_get(): array + { + return [ - [ 'buddhist' ], - [ 'chinese' ], - [ 'coptic' ], - [ 'dangi' ], - [ 'ethiopic' ], - [ 'generic' ], - [ 'gregorian' ], - [ 'hebrew' ], - [ 'indian' ], - [ 'islamic' ], - [ 'japanese' ], - [ 'persian' ], - [ 'roc' ] + [ 'buddhist' ], + [ 'chinese' ], + [ 'coptic' ], + [ 'dangi' ], + [ 'ethiopic' ], + [ 'generic' ], + [ 'gregorian' ], + [ 'hebrew' ], + [ 'indian' ], + [ 'islamic' ], + [ 'japanese' ], + [ 'persian' ], + [ 'roc' ] - ]; - } + ]; + } } diff --git a/tests/CalendarTest.php b/tests/CalendarTest.php index 4eeb724..8ed5711 100644 --- a/tests/CalendarTest.php +++ b/tests/CalendarTest.php @@ -14,141 +14,141 @@ final class CalendarTest extends TestCase { - private static Calendar $sut; - - public static function setupBeforeClass(): void - { - self::$sut = locale_for('fr')->calendars['gregorian']; - } - - #[DataProvider('provider_test_property_instanceof')] - public function test_property_instanceof(string $property, string $expected): void - { - $instance = self::$sut->$property; - $this->assertInstanceOf($expected, $instance); // @phpstan-ignore-line - $this->assertSame($instance, self::$sut->$property); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provider_test_property_instanceof(): array - { - return [ - - [ 'locale', Locale::class ], - [ 'datetime_formatter', DateTimeFormatter::class ], - [ 'date_formatter', DateFormatter::class ], - [ 'time_formatter', TimeFormatter::class ] - - ]; - } - - public function test_get_undefined_property(): void - { - $this->expectException(PropertyNotDefined::class); - self::$sut->undefined_property; // @phpstan-ignore-line - } - - #[DataProvider('provide_test_access')] - public function test_access(string $key): void - { - $this->assertTrue(self::$sut->offsetExists($key)); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_access(): array - { - return [ - - [ 'months' ], - [ 'days' ], - [ 'quarters' ], - [ 'dayPeriods' ], - [ 'eras' ], - [ 'dateFormats' ], - [ 'timeFormats' ], - [ 'dateTimeFormats' ] - - ]; - } - - #[DataProvider('provide_test_date_patterns_shortcuts')] - public function test_date_patterns_shortcuts(string $property, string $path): void - { - $path_parts = explode('/', $path); - $expected = self::$sut; - - foreach ($path_parts as $part) { - $expected = $expected[$part]; - } - - $this->assertEquals(self::$sut->$property, $expected); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_date_patterns_shortcuts(): array - { - return [ - - [ 'standalone_abbreviated_days', 'days/stand-alone/abbreviated' ], - [ 'standalone_abbreviated_eras', 'eras/eraAbbr' ], - [ 'standalone_abbreviated_months', 'months/stand-alone/abbreviated' ], - [ 'standalone_abbreviated_quarters', 'quarters/stand-alone/abbreviated' ], - [ 'standalone_narrow_days', 'days/stand-alone/narrow' ], - [ 'standalone_narrow_eras', 'eras/eraNarrow' ], - [ 'standalone_narrow_months', 'months/stand-alone/narrow' ], - [ 'standalone_narrow_quarters', 'quarters/stand-alone/narrow' ], - [ 'standalone_short_days', 'days/stand-alone/short' ], - [ 'standalone_short_eras', 'eras/eraAbbr' ], - [ 'standalone_short_months', 'months/stand-alone/abbreviated' ], - [ 'standalone_short_quarters', 'quarters/stand-alone/abbreviated' ], - [ 'standalone_wide_days', 'days/stand-alone/wide' ], - [ 'standalone_wide_eras', 'eras/eraNames' ], - [ 'standalone_wide_months', 'months/stand-alone/wide' ], - [ 'standalone_wide_quarters', 'quarters/stand-alone/wide' ], - [ 'abbreviated_days', 'days/format/abbreviated' ], - [ 'abbreviated_eras', 'eras/eraAbbr' ], - [ 'abbreviated_months', 'months/format/abbreviated' ], - [ 'abbreviated_quarters', 'quarters/format/abbreviated' ], - [ 'narrow_days', 'days/format/narrow' ], - [ 'narrow_eras', 'eras/eraNarrow' ], - [ 'narrow_months', 'months/format/narrow' ], - [ 'narrow_quarters', 'quarters/format/narrow' ], - [ 'short_days', 'days/format/short' ], - [ 'short_eras', 'eras/eraAbbr' ], - [ 'short_months', 'months/format/abbreviated' ], - [ 'short_quarters', 'quarters/format/abbreviated' ], - [ 'wide_days', 'days/format/wide' ], - [ 'wide_eras', 'eras/eraNames' ], - [ 'wide_months', 'months/format/wide' ], - [ 'wide_quarters', 'quarters/format/wide' ] - - ]; - } - - public function testFormatDateTime(): void - { - $actual = self::$sut->format_datetime('2018-11-24 20:12:22 UTC', DateTimeFormatLength::FULL); - - $this->assertSame("samedi 24 novembre 2018 à 20:12:22 UTC", $actual); - } - - public function testFormatDate(): void - { - $actual = self::$sut->format_date('2018-11-24 20:12:22 UTC', DateTimeFormatLength::LONG); - - $this->assertSame("24 novembre 2018", $actual); - } - - public function testFormatTime(): void - { - $actual = self::$sut->format_time('2018-11-24 20:12:22 UTC', DateTimeFormatLength::LONG); - - $this->assertSame("20:12:22 UTC", $actual); - } + private static Calendar $sut; + + public static function setupBeforeClass(): void + { + self::$sut = locale_for('fr')->calendars['gregorian']; + } + + #[DataProvider('provider_test_property_instanceof')] + public function test_property_instanceof(string $property, string $expected): void + { + $instance = self::$sut->$property; + $this->assertInstanceOf($expected, $instance); // @phpstan-ignore-line + $this->assertSame($instance, self::$sut->$property); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provider_test_property_instanceof(): array + { + return [ + + [ 'locale', Locale::class ], + [ 'datetime_formatter', DateTimeFormatter::class ], + [ 'date_formatter', DateFormatter::class ], + [ 'time_formatter', TimeFormatter::class ] + + ]; + } + + public function test_get_undefined_property(): void + { + $this->expectException(PropertyNotDefined::class); + self::$sut->undefined_property; // @phpstan-ignore-line + } + + #[DataProvider('provide_test_access')] + public function test_access(string $key): void + { + $this->assertTrue(self::$sut->offsetExists($key)); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_access(): array + { + return [ + + [ 'months' ], + [ 'days' ], + [ 'quarters' ], + [ 'dayPeriods' ], + [ 'eras' ], + [ 'dateFormats' ], + [ 'timeFormats' ], + [ 'dateTimeFormats' ] + + ]; + } + + #[DataProvider('provide_test_date_patterns_shortcuts')] + public function test_date_patterns_shortcuts(string $property, string $path): void + { + $path_parts = explode('/', $path); + $expected = self::$sut; + + foreach ($path_parts as $part) { + $expected = $expected[$part]; + } + + $this->assertEquals(self::$sut->$property, $expected); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_date_patterns_shortcuts(): array + { + return [ + + [ 'standalone_abbreviated_days', 'days/stand-alone/abbreviated' ], + [ 'standalone_abbreviated_eras', 'eras/eraAbbr' ], + [ 'standalone_abbreviated_months', 'months/stand-alone/abbreviated' ], + [ 'standalone_abbreviated_quarters', 'quarters/stand-alone/abbreviated' ], + [ 'standalone_narrow_days', 'days/stand-alone/narrow' ], + [ 'standalone_narrow_eras', 'eras/eraNarrow' ], + [ 'standalone_narrow_months', 'months/stand-alone/narrow' ], + [ 'standalone_narrow_quarters', 'quarters/stand-alone/narrow' ], + [ 'standalone_short_days', 'days/stand-alone/short' ], + [ 'standalone_short_eras', 'eras/eraAbbr' ], + [ 'standalone_short_months', 'months/stand-alone/abbreviated' ], + [ 'standalone_short_quarters', 'quarters/stand-alone/abbreviated' ], + [ 'standalone_wide_days', 'days/stand-alone/wide' ], + [ 'standalone_wide_eras', 'eras/eraNames' ], + [ 'standalone_wide_months', 'months/stand-alone/wide' ], + [ 'standalone_wide_quarters', 'quarters/stand-alone/wide' ], + [ 'abbreviated_days', 'days/format/abbreviated' ], + [ 'abbreviated_eras', 'eras/eraAbbr' ], + [ 'abbreviated_months', 'months/format/abbreviated' ], + [ 'abbreviated_quarters', 'quarters/format/abbreviated' ], + [ 'narrow_days', 'days/format/narrow' ], + [ 'narrow_eras', 'eras/eraNarrow' ], + [ 'narrow_months', 'months/format/narrow' ], + [ 'narrow_quarters', 'quarters/format/narrow' ], + [ 'short_days', 'days/format/short' ], + [ 'short_eras', 'eras/eraAbbr' ], + [ 'short_months', 'months/format/abbreviated' ], + [ 'short_quarters', 'quarters/format/abbreviated' ], + [ 'wide_days', 'days/format/wide' ], + [ 'wide_eras', 'eras/eraNames' ], + [ 'wide_months', 'months/format/wide' ], + [ 'wide_quarters', 'quarters/format/wide' ] + + ]; + } + + public function testFormatDateTime(): void + { + $actual = self::$sut->format_datetime('2018-11-24 20:12:22 UTC', DateTimeFormatLength::FULL); + + $this->assertSame("samedi 24 novembre 2018 à 20:12:22 UTC", $actual); + } + + public function testFormatDate(): void + { + $actual = self::$sut->format_date('2018-11-24 20:12:22 UTC', DateTimeFormatLength::LONG); + + $this->assertSame("24 novembre 2018", $actual); + } + + public function testFormatTime(): void + { + $actual = self::$sut->format_time('2018-11-24 20:12:22 UTC', DateTimeFormatLength::LONG); + + $this->assertSame("20:12:22 UTC", $actual); + } } diff --git a/tests/ContextTransformsTest.php b/tests/ContextTransformsTest.php index 273f909..faedc87 100644 --- a/tests/ContextTransformsTest.php +++ b/tests/ContextTransformsTest.php @@ -9,151 +9,151 @@ final class ContextTransformsTest extends TestCase { - #[DataProvider('provide_test_transform')] - public function test_transform( - string $str, - string $expected, - string $usage, - string $type, - array $rules - ): void { - $this->assertSame($expected, (new ContextTransforms($rules))->transform($str, $usage, $type)); - } + #[DataProvider('provide_test_transform')] + public function test_transform( + string $str, + string $expected, + string $usage, + string $type, + array $rules + ): void { + $this->assertSame($expected, (new ContextTransforms($rules))->transform($str, $usage, $type)); + } - public static function provide_test_transform(): array - { - return [ + public static function provide_test_transform(): array + { + return [ - [ - "juin", - "juin", - ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW, - ContextTransforms::TYPE_STAND_ALONE, - [ + [ + "juin", + "juin", + ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW, + ContextTransforms::TYPE_STAND_ALONE, + [ - ] + ] - ], + ], - [ - "juin", - "Juin", - ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW, - ContextTransforms::TYPE_STAND_ALONE, - [ - ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW => [ + [ + "juin", + "Juin", + ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW, + ContextTransforms::TYPE_STAND_ALONE, + [ + ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW => [ - ContextTransforms::TYPE_STAND_ALONE => ContextTransforms::TRANSFORM_TITLECASE_FIRSTWORD + ContextTransforms::TYPE_STAND_ALONE => ContextTransforms::TRANSFORM_TITLECASE_FIRSTWORD - ] - ] + ] + ] - ], + ], - [ - "juin", - "juin", - ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW, - ContextTransforms::TYPE_STAND_ALONE, - [ - ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW => [ + [ + "juin", + "juin", + ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW, + ContextTransforms::TYPE_STAND_ALONE, + [ + ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW => [ - ContextTransforms::TYPE_STAND_ALONE => ContextTransforms::TRANSFORM_NO_CHANGE + ContextTransforms::TYPE_STAND_ALONE => ContextTransforms::TRANSFORM_NO_CHANGE - ] - ] + ] + ] - ], + ], - [ - "juin", - "Juin", - ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW, - ContextTransforms::TYPE_STAND_ALONE, - [ - ContextTransforms::USAGE_ALL => [ + [ + "juin", + "Juin", + ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW, + ContextTransforms::TYPE_STAND_ALONE, + [ + ContextTransforms::USAGE_ALL => [ - ContextTransforms::TYPE_STAND_ALONE => ContextTransforms::TRANSFORM_TITLECASE_FIRSTWORD + ContextTransforms::TYPE_STAND_ALONE => ContextTransforms::TRANSFORM_TITLECASE_FIRSTWORD - ] - ] + ] + ] - ], + ], - [ - "juin", - "juin", - ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW, - ContextTransforms::TYPE_STAND_ALONE, - [ - ContextTransforms::USAGE_ALL => [ + [ + "juin", + "juin", + ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW, + ContextTransforms::TYPE_STAND_ALONE, + [ + ContextTransforms::USAGE_ALL => [ - ContextTransforms::TYPE_STAND_ALONE => ContextTransforms::TRANSFORM_NO_CHANGE + ContextTransforms::TYPE_STAND_ALONE => ContextTransforms::TRANSFORM_NO_CHANGE - ] - ] + ] + ] - ], + ], - [ - "juin", - "Juin", - ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW, - ContextTransforms::TYPE_STAND_ALONE, - [ - ContextTransforms::USAGE_ALL => [ + [ + "juin", + "Juin", + ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW, + ContextTransforms::TYPE_STAND_ALONE, + [ + ContextTransforms::USAGE_ALL => [ - ContextTransforms::TYPE_STAND_ALONE => ContextTransforms::TRANSFORM_NO_CHANGE + ContextTransforms::TYPE_STAND_ALONE => ContextTransforms::TRANSFORM_NO_CHANGE - ], + ], - ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW => [ + ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW => [ - ContextTransforms::TYPE_STAND_ALONE => ContextTransforms::TRANSFORM_TITLECASE_FIRSTWORD + ContextTransforms::TYPE_STAND_ALONE => ContextTransforms::TRANSFORM_TITLECASE_FIRSTWORD - ] - ] + ] + ] - ], + ], - [ - "juin", - "juin", - ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW, - ContextTransforms::TYPE_STAND_ALONE, - [ - ContextTransforms::USAGE_ALL => [ + [ + "juin", + "juin", + ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW, + ContextTransforms::TYPE_STAND_ALONE, + [ + ContextTransforms::USAGE_ALL => [ - ContextTransforms::TYPE_STAND_ALONE => ContextTransforms::TRANSFORM_TITLECASE_FIRSTWORD + ContextTransforms::TYPE_STAND_ALONE => ContextTransforms::TRANSFORM_TITLECASE_FIRSTWORD - ], + ], - ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW => [ + ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW => [ - ContextTransforms::TYPE_STAND_ALONE => ContextTransforms::TRANSFORM_NO_CHANGE + ContextTransforms::TYPE_STAND_ALONE => ContextTransforms::TRANSFORM_NO_CHANGE - ] - ] + ] + ] - ], + ], - ]; - } + ]; + } - public function test_should_throw_exception_on_unknown_transform(): void - { - $usage = ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW; - $type = ContextTransforms::TYPE_STAND_ALONE; + public function test_should_throw_exception_on_unknown_transform(): void + { + $usage = ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW; + $type = ContextTransforms::TYPE_STAND_ALONE; - $this->expectException(LogicException::class); - (new ContextTransforms([ + $this->expectException(LogicException::class); + (new ContextTransforms([ - $usage => [ + $usage => [ - $type => uniqid() + $type => uniqid() - ] + ] - ]))->transform("juin", $usage, $type); - } + ]))->transform("juin", $usage, $type); + } } diff --git a/tests/CurrencyNotDefinedTest.php b/tests/CurrencyNotDefinedTest.php index 2040d8f..e008b7d 100644 --- a/tests/CurrencyNotDefinedTest.php +++ b/tests/CurrencyNotDefinedTest.php @@ -9,41 +9,41 @@ final class CurrencyNotDefinedTest extends TestCase { - #[DataProvider('provide_instance')] - public function test_instance( - string $currency_code, - ?string $message, - string $expected_message, - Exception $previous = null - ): void { - $sut = new CurrencyNotDefined($currency_code, $message, $previous); - - $this->assertSame($currency_code, $sut->currency_code); - $this->assertSame($expected_message, $sut->getMessage()); - $this->assertSame($previous, $sut->getPrevious()); - } - - public static function provide_instance(): array - { - $currency_code = 'EUR'; - $previous = new Exception(); - - return [ - - "should format a message" => [ - $currency_code, - null, - "Currency code is not defined: $currency_code", - null, - ], - - "should use custom message" => [ - $currency_code, - $message = "Madonna", - $message, - $previous, - ], - - ]; - } + #[DataProvider('provide_instance')] + public function test_instance( + string $currency_code, + ?string $message, + string $expected_message, + Exception $previous = null + ): void { + $sut = new CurrencyNotDefined($currency_code, $message, $previous); + + $this->assertSame($currency_code, $sut->currency_code); + $this->assertSame($expected_message, $sut->getMessage()); + $this->assertSame($previous, $sut->getPrevious()); + } + + public static function provide_instance(): array + { + $currency_code = 'EUR'; + $previous = new Exception(); + + return [ + + "should format a message" => [ + $currency_code, + null, + "Currency code is not defined: $currency_code", + null, + ], + + "should use custom message" => [ + $currency_code, + $message = "Madonna", + $message, + $previous, + ], + + ]; + } } diff --git a/tests/CurrencyTest.php b/tests/CurrencyTest.php index f0be1ff..b0e2726 100644 --- a/tests/CurrencyTest.php +++ b/tests/CurrencyTest.php @@ -9,99 +9,99 @@ final class CurrencyTest extends TestCase { - #[DataProvider("provide_is_defined")] - public function test_is_defined(string $code, bool $expected): void - { - $actual = Currency::is_defined($code); - - $this->assertSame($expected, $actual); - } - - public static function provide_is_defined(): array - { - return [ - - [ 'PES', true ], - [ 'UAH', true ], - [ 'ZZZ', false ], - - ]; - } - - #[DataProvider("provide_is_defined")] - public function test_assert_is_defined(string $code, bool $defined): void - { - if (!$defined) { - $this->expectException(CurrencyNotDefined::class); - } else { - $this->assertTrue(true); - } - - Currency::assert_is_defined($code); - } - - #[DataProvider("provide_is_defined")] - public function test_of(string $code, bool $defined): void - { - if (!$defined) { - $this->expectException(CurrencyNotDefined::class); - } - - $actual = Currency::of($code); - - $this->assertEquals($code, $actual->code); - } - - #[DataProvider('provide_fraction_properties')] - public function test_fraction_properties(string $code, string $property, int $expected): void - { - $currency = Currency::of($code); - - $this->assertSame($expected, $currency->fraction->$property); - } - - public static function provide_fraction_properties(): array - { - return [ - - [ 'EUR', 'digits', 2 ], - [ 'EUR', 'rounding', 0 ], - [ 'EUR', 'cash_digits', 2 ], - [ 'EUR', 'cash_rounding', 0 ], - - [ 'HUF', 'digits', 2 ], - [ 'HUF', 'rounding', 0 ], - [ 'HUF', 'cash_digits', 0 ], - [ 'HUF', 'cash_rounding', 0 ], - - [ 'LYD', 'digits', 3 ], - [ 'LYD', 'rounding', 0 ], - [ 'LYD', 'cash_digits', 3 ], - [ 'LYD', 'cash_rounding', 0 ], - - [ 'DKK', 'digits', 2 ], - [ 'DKK', 'rounding', 0 ], - [ 'DKK', 'cash_digits', 2 ], - [ 'DKK', 'cash_rounding', 50 ], - - ]; - } - - public function test_serialization(): void - { - $sut = Currency::of('EUR'); - $actual = unserialize(serialize($sut)); - - $this->assertEquals($sut, $actual); - } - - public function test_localize(): void - { - $sut = Currency::of('EUR'); - $localized = $sut->localized(locale_for('fr')); - - $actual = $localized->format(12345.67); - - $this->assertEquals('12 345,67 €', $actual); - } + #[DataProvider("provide_is_defined")] + public function test_is_defined(string $code, bool $expected): void + { + $actual = Currency::is_defined($code); + + $this->assertSame($expected, $actual); + } + + public static function provide_is_defined(): array + { + return [ + + [ 'PES', true ], + [ 'UAH', true ], + [ 'ZZZ', false ], + + ]; + } + + #[DataProvider("provide_is_defined")] + public function test_assert_is_defined(string $code, bool $defined): void + { + if (!$defined) { + $this->expectException(CurrencyNotDefined::class); + } else { + $this->assertTrue(true); + } + + Currency::assert_is_defined($code); + } + + #[DataProvider("provide_is_defined")] + public function test_of(string $code, bool $defined): void + { + if (!$defined) { + $this->expectException(CurrencyNotDefined::class); + } + + $actual = Currency::of($code); + + $this->assertEquals($code, $actual->code); + } + + #[DataProvider('provide_fraction_properties')] + public function test_fraction_properties(string $code, string $property, int $expected): void + { + $currency = Currency::of($code); + + $this->assertSame($expected, $currency->fraction->$property); + } + + public static function provide_fraction_properties(): array + { + return [ + + [ 'EUR', 'digits', 2 ], + [ 'EUR', 'rounding', 0 ], + [ 'EUR', 'cash_digits', 2 ], + [ 'EUR', 'cash_rounding', 0 ], + + [ 'HUF', 'digits', 2 ], + [ 'HUF', 'rounding', 0 ], + [ 'HUF', 'cash_digits', 0 ], + [ 'HUF', 'cash_rounding', 0 ], + + [ 'LYD', 'digits', 3 ], + [ 'LYD', 'rounding', 0 ], + [ 'LYD', 'cash_digits', 3 ], + [ 'LYD', 'cash_rounding', 0 ], + + [ 'DKK', 'digits', 2 ], + [ 'DKK', 'rounding', 0 ], + [ 'DKK', 'cash_digits', 2 ], + [ 'DKK', 'cash_rounding', 50 ], + + ]; + } + + public function test_serialization(): void + { + $sut = Currency::of('EUR'); + $actual = unserialize(serialize($sut)); + + $this->assertEquals($sut, $actual); + } + + public function test_localize(): void + { + $sut = Currency::of('EUR'); + $localized = $sut->localized(locale_for('fr')); + + $actual = $localized->format(12345.67); + + $this->assertEquals('12 345,67 €', $actual); + } } diff --git a/tests/DateFormatterTest.php b/tests/DateFormatterTest.php index 36dbcf8..ddca3b4 100644 --- a/tests/DateFormatterTest.php +++ b/tests/DateFormatterTest.php @@ -10,50 +10,50 @@ final class DateFormatterTest extends TestCase { - /** - * @var array - */ - private static array $formatters = []; - - public static function setupBeforeClass(): void - { - self::$formatters['en'] = new DateFormatter(locale_for('en')->calendar); - self::$formatters['fr'] = new DateFormatter(locale_for('fr')->calendar); - } - - #[DataProvider('provide_test_format')] - public function test_format( - string $locale, - string $datetime, - string|DateTimeFormatLength|DateTimeFormatId $pattern, - string $expected - ): void { - $actual = self::$formatters[$locale]->format($datetime, $pattern); - - $this->assertEquals($expected, $actual); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_format(): array - { - return [ - - [ 'en', '2013-11-05 21:22:23', DateTimeFormatLength::FULL, 'Tuesday, November 5, 2013' ], - [ 'en', '2013-11-05 21:22:23', DateTimeFormatLength::LONG, 'November 5, 2013' ], - [ 'en', '2013-11-05 21:22:23', DateTimeFormatLength::MEDIUM, 'Nov 5, 2013' ], - [ 'en', '2013-11-05 21:22:23', DateTimeFormatLength::SHORT, '11/5/13' ], - - [ 'fr', '2013-11-05 21:22:23', DateTimeFormatLength::FULL, 'mardi 5 novembre 2013' ], - [ 'fr', '2013-11-05 21:22:23', DateTimeFormatLength::LONG, '5 novembre 2013' ], - [ 'fr', '2013-11-05 21:22:23', DateTimeFormatLength::MEDIUM, '5 nov. 2013' ], - [ 'fr', '2013-11-05 21:22:23', DateTimeFormatLength::SHORT, '05/11/2013' ], - - # datetime patterns must be supported too - [ 'en', '2013-11-05 21:22:23', DateTimeFormatId::from('yMMMEd'), 'Tue, Nov 5, 2013' ], - [ 'fr', '2013-11-05 21:22:23', 'd MMMM y', '5 novembre 2013' ] - - ]; - } + /** + * @var array + */ + private static array $formatters = []; + + public static function setupBeforeClass(): void + { + self::$formatters['en'] = new DateFormatter(locale_for('en')->calendar); + self::$formatters['fr'] = new DateFormatter(locale_for('fr')->calendar); + } + + #[DataProvider('provide_test_format')] + public function test_format( + string $locale, + string $datetime, + string|DateTimeFormatLength|DateTimeFormatId $pattern, + string $expected + ): void { + $actual = self::$formatters[$locale]->format($datetime, $pattern); + + $this->assertEquals($expected, $actual); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_format(): array + { + return [ + + [ 'en', '2013-11-05 21:22:23', DateTimeFormatLength::FULL, 'Tuesday, November 5, 2013' ], + [ 'en', '2013-11-05 21:22:23', DateTimeFormatLength::LONG, 'November 5, 2013' ], + [ 'en', '2013-11-05 21:22:23', DateTimeFormatLength::MEDIUM, 'Nov 5, 2013' ], + [ 'en', '2013-11-05 21:22:23', DateTimeFormatLength::SHORT, '11/5/13' ], + + [ 'fr', '2013-11-05 21:22:23', DateTimeFormatLength::FULL, 'mardi 5 novembre 2013' ], + [ 'fr', '2013-11-05 21:22:23', DateTimeFormatLength::LONG, '5 novembre 2013' ], + [ 'fr', '2013-11-05 21:22:23', DateTimeFormatLength::MEDIUM, '5 nov. 2013' ], + [ 'fr', '2013-11-05 21:22:23', DateTimeFormatLength::SHORT, '05/11/2013' ], + + # datetime patterns must be supported too + [ 'en', '2013-11-05 21:22:23', DateTimeFormatId::from('yMMMEd'), 'Tue, Nov 5, 2013' ], + [ 'fr', '2013-11-05 21:22:23', 'd MMMM y', '5 novembre 2013' ] + + ]; + } } diff --git a/tests/DateTimeAccessorTest.php b/tests/DateTimeAccessorTest.php index 3fbb69b..5b1c99d 100644 --- a/tests/DateTimeAccessorTest.php +++ b/tests/DateTimeAccessorTest.php @@ -9,39 +9,39 @@ final class DateTimeAccessorTest extends TestCase { - #[DataProvider('provide_test_properties')] - public function test_properties(string $property, int $expected): void - { - $datetime = new \DateTime("2016-09-17T12:22:32+02:00"); - - $this->assertSame($expected, (new DateTimeAccessor($datetime))->$property); - } - - public static function provide_test_properties(): array - { - return [ - - [ 'year', 2016 ], - [ 'month', 9 ], - [ 'day', 17 ], - [ 'hour', 12 ], - [ 'minute', 22 ], - [ 'second', 32 ], - [ 'quarter', 3 ], - [ 'week', 37 ], - [ 'year_day', 261 ], - [ 'weekday', 6 ], - - ]; - } - - public function test_should_throw_exception_accessing_undefined_property(): void - { - $this->expectException(LogicException::class); - $property = uniqid(); - - $this->expectException(\LogicException::class); - // @phpstan-ignore-next-line - (new DateTimeAccessor(new \DateTime))->$property; - } + #[DataProvider('provide_test_properties')] + public function test_properties(string $property, int $expected): void + { + $datetime = new \DateTime("2016-09-17T12:22:32+02:00"); + + $this->assertSame($expected, (new DateTimeAccessor($datetime))->$property); + } + + public static function provide_test_properties(): array + { + return [ + + [ 'year', 2016 ], + [ 'month', 9 ], + [ 'day', 17 ], + [ 'hour', 12 ], + [ 'minute', 22 ], + [ 'second', 32 ], + [ 'quarter', 3 ], + [ 'week', 37 ], + [ 'year_day', 261 ], + [ 'weekday', 6 ], + + ]; + } + + public function test_should_throw_exception_accessing_undefined_property(): void + { + $this->expectException(LogicException::class); + $property = uniqid(); + + $this->expectException(\LogicException::class); + // @phpstan-ignore-next-line + (new DateTimeAccessor(new \DateTime()))->$property; + } } diff --git a/tests/DateTimeFormatterTest.php b/tests/DateTimeFormatterTest.php index c7d3649..c57d582 100644 --- a/tests/DateTimeFormatterTest.php +++ b/tests/DateTimeFormatterTest.php @@ -11,550 +11,555 @@ final class DateTimeFormatterTest extends TestCase { - /** - * @var array - */ - private static array $formatters = []; - - public static function setupBeforeClass(): void - { - self::$formatters = [ - - 'en' => new DateTimeFormatter(locale_for('en')->calendar), - 'fr' => new DateTimeFormatter(locale_for('fr')->calendar), - - ]; - } - - public function test_get_calendar(): void - { - $this->assertInstanceOf(Calendar::class, self::$formatters['en']->calendar); - } - - #[DataProvider('provide_test_format')] - public function test_format( - string $locale_id, - string $datetime, - string|DateTimeFormatLength $format, - string $expected - ): void { - $formatter = self::$formatters[$locale_id]; - - $this->assertSame($expected, $formatter->format($datetime, $format)); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_format(): array - { - return [ - - # test: era - - [ 'fr', '2016-06-06 13:30:40', 'G', 'ap. J.-C.' ], - [ 'fr', '2016-06-06 13:30:40', 'GG', 'ap. J.-C.' ], - [ 'fr', '2016-06-06 13:30:40', 'GGG', 'ap. J.-C.' ], - [ 'fr', '2016-06-06 13:30:40', 'GGGG', 'après Jésus-Christ' ], - [ 'fr', '2016-06-06 13:30:40', 'GGGGG', 'ap. J.-C.' ], - [ 'fr', '2016-06-06 13:30:40', 'GGGGGG', '' ], - - [ 'fr', '-0605-06-06 13:30:40', 'G', 'av. J.-C.' ], - [ 'fr', '-0605-06-06 13:30:40', 'GG', 'av. J.-C.' ], - [ 'fr', '-0605-06-06 13:30:40', 'GGG', 'av. J.-C.' ], - [ 'fr', '-0605-06-06 13:30:40', 'GGGG', 'avant Jésus-Christ' ], - [ 'fr', '-0605-06-06 13:30:40', 'GGGGG', 'av. J.-C.' ], - [ 'fr', '-0605-06-06 13:30:40', 'GGGGGG', '' ], - - # test: format year one figure - - [ 'en', '0005-01-01', 'y', '5' ], - [ 'en', '0005-01-01', 'yy', '05' ], - [ 'en', '0005-01-01', 'yyy', '005' ], - [ 'en', '0005-01-01', 'yyyy', '0005' ], - [ 'en', '0005-01-01', 'yyyyy', '00005' ], - - # test: format year two figures - - [ 'en', '0045-01-01', 'y', '45' ], - [ 'en', '0045-01-01', 'yy', '45' ], - [ 'en', '0045-01-01', 'yyy', '045' ], - [ 'en', '0045-01-01', 'yyyy', '0045' ], - [ 'en', '0045-01-01', 'yyyyy', '00045' ], - - # test: format year three figures - - [ 'en', '0345-01-01', 'y', '345' ], - [ 'en', '0345-01-01', 'yy', '45' ], - [ 'en', '0345-01-01', 'yyy', '345' ], - [ 'en', '0345-01-01', 'yyyy', '0345' ], - [ 'en', '0345-01-01', 'yyyyy', '00345' ], - - # test: format year four figures - - [ 'en', '2345-01-01', 'y', '2345' ], - [ 'en', '2345-01-01', 'yy', '45' ], - [ 'en', '2345-01-01', 'yyy', '2345' ], - [ 'en', '2345-01-01', 'yyyy', '2345' ], - [ 'en', '2345-01-01', 'yyyyy', '02345' ], - - /* failing because DateTime converts the date 12345 to 2005 - * FIXME - # test: format year five figures - - array('en', '12345-01-01', 'y', '12345'), - array('en', '12345-01-01', 'yy', '45'), - array('en', '12345-01-01', 'yyy', '12345'), - array('en', '12345-01-01', 'yyyy', '12345'), - array('en', '12345-01-01', 'yyyyy', '112345'), - */ - - # test: format month - - [ 'en', '2012-02-13', 'M', '2' ], - [ 'en', '2012-02-13', 'MM', '02' ], - [ 'en', '2012-02-13', 'MMM', 'Feb' ], - [ 'en', '2012-02-13', 'MMMM', 'February' ], - [ 'en', '2012-02-13', 'MMMMM', 'F' ], - [ 'en', '2012-02-13', 'MMMMMM', '' ], - - # test: format month in french - - [ 'fr', '2012-02-13', 'M', '2' ], - [ 'fr', '2012-02-13', 'MM', '02' ], - [ 'fr', '2012-02-13', 'MMM', 'févr.' ], - [ 'fr', '2012-02-13', 'MMMM', 'février' ], - [ 'fr', '2012-02-13', 'MMMMM', 'F' ], - [ 'fr', '2012-02-13', 'MMMMMM', '' ], - - # test: format stand-alone month - - [ 'en', '2012-02-13', 'L', '2' ], - [ 'en', '2012-02-13', 'LL', '02' ], - [ 'en', '2012-02-13', 'LLL', 'Feb' ], - [ 'en', '2012-02-13', 'LLLL', 'February' ], - [ 'en', '2012-02-13', 'LLLLL', 'F' ], - [ 'en', '2012-02-13', 'LLLLLL', '' ], - - # test: format stand-alone month in french - - [ 'fr', '2012-02-13', 'L', '2' ], - [ 'fr', '2012-02-13', 'LL', '02' ], - [ 'fr', '2012-02-13', 'LLL', 'Févr.' ], - [ 'fr', '2012-02-13', 'LLLL', 'Février' ], - [ 'fr', '2012-02-13', 'LLLLL', 'F' ], - [ 'fr', '2012-02-13', 'LLLLLL', '' ], - - # test: format week of year - - [ 'en', '2012-01-01', 'w', '52' ], - [ 'en', '2012-01-02', 'w', '1' ], - [ 'en', '2012-01-02', 'ww', '01' ], - [ 'en', '2012-01-02', 'www', '' ], - [ 'en', '2012-12-30', 'w', '52' ], - [ 'en', '2012-12-30', 'ww', '52' ], - [ 'en', '2012-12-30', 'www', '' ], - - # test: format week of month - - [ 'en', '2012-01-01', 'W', '1' ], - [ 'en', '2012-01-02', 'W', '1' ], - [ 'en', '2012-01-09', 'W', '2' ], - [ 'en', '2012-01-16', 'W', '3' ], - [ 'en', '2012-01-23', 'W', '4' ], - [ 'en', '2012-01-30', 'W', '5' ], - [ 'en', '2012-01-30', 'WW', '' ], - [ 'en', '2012-01-30', 'WWW', '' ], - [ 'en', '2012-01-30', 'WWWW', '' ], - [ 'en', '2012-01-30', 'WWWWW', '' ], - - # test: format day of the month - - [ 'en', '2012-01-01', 'd', '1' ], - [ 'en', '2012-01-01', 'dd', '01' ], - [ 'en', '2012-01-13', 'd', '13' ], - [ 'en', '2012-01-13', 'dd', '13' ], - [ 'en', '2012-01-13', 'ddd', '' ], - [ 'en', '2012-01-13', 'dddd', '' ], - [ 'en', '2012-01-13', 'ddddd', '' ], - - # test: format day of the year - - [ 'en', '2012-01-01', 'D', '1' ], - [ 'en', '2012-01-01', 'DD', '01' ], - [ 'en', '2012-01-01', 'DDD', '001' ], - [ 'en', '2012-01-01', 'DDDD', '' ], - [ 'en', '2012-01-01', 'DDDDD', '' ], - [ 'en', '2012-01-13', 'DD', '13' ], - [ 'en', '2012-01-13', 'DDD', '013' ], - [ 'en', '2012-06-13', 'DD', '165' ], - [ 'en', '2012-06-13', 'DDD', '165' ], - [ 'en', '2012-06-13', 'DDDD', '' ], - [ 'en', '2012-06-13', 'DDDDD', '' ], - - # test: format day of week - /* FIXME - array('en', '2012-06-01', 'F', '1'), - array('en', '2012-06-03', 'F', '3'), - array('en', '2012-06-05', 'F', '5'), - */ - - # test: format weekday - - [ 'en', '2012-06-01', 'E', 'Fri' ], - [ 'en', '2012-06-01', 'EE', 'Fri' ], - [ 'en', '2012-06-01', 'EEE', 'Fri' ], - [ 'en', '2012-06-01', 'EEEE', 'Friday' ], - [ 'en', '2012-06-01', 'EEEEE', 'F' ], - [ 'en', '2012-06-01', 'EEEEEE', 'Fr' ], - [ 'en', '2012-06-01', 'EEEEEEE', '' ], - - # test: format weekday in french - - [ 'fr', '2012-06-01', 'E', 'ven.' ], - [ 'fr', '2012-06-01', 'EE', 'ven.' ], - [ 'fr', '2012-06-01', 'EEE', 'ven.' ], - [ 'fr', '2012-06-01', 'EEEE', 'vendredi' ], - [ 'fr', '2012-06-01', 'EEEEE', 'V' ], - [ 'fr', '2012-06-01', 'EEEEEE', 've' ], - [ 'fr', '2012-06-01', 'EEEEEEE', '' ], - - # test: format local weekday - - [ 'en', '2012-06-01', 'e', '5' ], - [ 'en', '2012-06-01', 'ee', '5' ], - [ 'en', '2012-06-01', 'eee', 'Fri' ], - [ 'en', '2012-06-01', 'eeee', 'Friday' ], - [ 'en', '2012-06-01', 'eeeee', 'F' ], - [ 'en', '2012-06-01', 'eeeeee', 'Fr' ], - [ 'en', '2012-06-01', 'eeeeeee', '' ], - - # test: format local weekday in french - - [ 'fr', '2012-06-01', 'e', '5' ], - [ 'fr', '2012-06-01', 'ee', '5' ], - [ 'fr', '2012-06-01', 'eee', 'ven.' ], - [ 'fr', '2012-06-01', 'eeee', 'vendredi' ], - [ 'fr', '2012-06-01', 'eeeee', 'V' ], - [ 'fr', '2012-06-01', 'eeeeee', 've' ], - [ 'fr', '2012-06-01', 'eeeeeee', '' ], - - # test: format stand-alone weekday - - [ 'en', '2012-06-01', 'c', '5' ], - [ 'en', '2012-06-01', 'cc', '' ], - [ 'en', '2012-06-01', 'ccc', 'Fri' ], - [ 'en', '2012-06-01', 'cccc', 'Friday' ], - [ 'en', '2012-06-01', 'ccccc', 'F' ], - [ 'en', '2012-06-01', 'cccccc', 'Fr' ], - [ 'en', '2012-06-01', 'ccccccc', '' ], - - # test: format stand-alone weekday in french - - [ 'fr', '2012-06-01', 'c', '5' ], - [ 'fr', '2012-06-01', 'cc', '' ], - [ 'fr', '2012-06-01', 'ccc', 'Ven.' ], - [ 'fr', '2012-06-01', 'cccc', 'Vendredi' ], - [ 'fr', '2012-06-01', 'ccccc', 'V' ], - [ 'fr', '2012-06-01', 'cccccc', 'Ve' ], - [ 'fr', '2012-06-01', 'ccccccc', '' ], - - # test: format period - - [ 'en', '2012-06-01 00:00:00', 'a', 'AM' ], - [ 'en', '2012-06-01 06:00:00', 'a', 'AM' ], - [ 'en', '2012-06-01 12:00:00', 'a', 'PM' ], - [ 'en', '2012-06-01 18:00:00', 'a', 'PM' ], - - # test: format period in french - - [ 'fr', '2012-06-01 00:00:00', 'a', 'AM' ], - [ 'fr', '2012-06-01 06:00:00', 'a', 'AM' ], - [ 'fr', '2012-06-01 12:00:00', 'a', 'PM' ], - [ 'fr', '2012-06-01 18:00:00', 'a', 'PM' ], - - # test: format hour 12 - - [ 'en', '2012-06-01 00:00:00', 'h', '0' ], - [ 'en', '2012-06-01 06:00:00', 'h', '6' ], - [ 'en', '2012-06-01 12:00:00', 'h', '12' ], - [ 'en', '2012-06-01 18:00:00', 'h', '6' ], - - [ 'en', '2012-06-01 00:00:00', 'hh', '00' ], - [ 'en', '2012-06-01 06:00:00', 'hh', '06' ], - [ 'en', '2012-06-01 12:00:00', 'hh', '12' ], - [ 'en', '2012-06-01 18:00:00', 'hh', '06' ], - - [ 'en', '2012-06-01 18:00:00', 'hhh', '' ], - - # test: format hour 24 - - [ 'en', '2012-06-01 00:00:00', 'H', '0' ], - [ 'en', '2012-06-01 06:00:00', 'H', '6' ], - [ 'en', '2012-06-01 12:00:00', 'H', '12' ], - [ 'en', '2012-06-01 18:00:00', 'H', '18' ], - - [ 'en', '2012-06-01 00:00:00', 'HH', '00' ], - [ 'en', '2012-06-01 06:00:00', 'HH', '06' ], - [ 'en', '2012-06-01 12:00:00', 'HH', '12' ], - [ 'en', '2012-06-01 18:00:00', 'HH', '18' ], - - [ 'en', '2012-06-01 18:00:00', 'HHH', '' ], - - # test: format hour in period - - [ 'en', '2012-06-01 00:00:00', 'K', '0' ], - [ 'en', '2012-06-01 00:00:00', 'KK', '00' ], - [ 'en', '2012-06-01 00:00:00', 'KKK', '' ], - [ 'en', '2012-06-01 00:00:00', 'KKKK', '' ], - [ 'en', '2012-06-01 00:00:00', 'KKKKK', '' ], - [ 'en', '2012-06-01 06:00:00', 'K', '6' ], - [ 'en', '2012-06-01 06:00:00', 'KK', '06' ], - [ 'en', '2012-06-01 06:00:00', 'KKK', '' ], - [ 'en', '2012-06-01 06:00:00', 'KKKK', '' ], - [ 'en', '2012-06-01 06:00:00', 'KKKKK', '' ], - [ 'en', '2012-06-01 12:00:00', 'K', '0' ], - [ 'en', '2012-06-01 12:00:00', 'KK', '00' ], - [ 'en', '2012-06-01 12:00:00', 'KKK', '' ], - [ 'en', '2012-06-01 12:00:00', 'KKKK', '' ], - [ 'en', '2012-06-01 12:00:00', 'KKKKK', '' ], - [ 'en', '2012-06-01 18:00:00', 'K', '6' ], - [ 'en', '2012-06-01 18:00:00', 'KK', '06' ], - [ 'en', '2012-06-01 18:00:00', 'KKK', '' ], - [ 'en', '2012-06-01 18:00:00', 'KKKK', '' ], - [ 'en', '2012-06-01 18:00:00', 'KKKKK', '' ], - - # test: format hour in day - - [ 'en', '2012-06-01 00:00:00', 'k', '24' ], - [ 'en', '2012-06-01 00:00:00', 'kk', '24' ], - [ 'en', '2012-06-01 00:00:00', 'kkk', '' ], - [ 'en', '2012-06-01 00:00:00', 'kkkk', '' ], - [ 'en', '2012-06-01 00:00:00', 'kkkkk', '' ], - [ 'en', '2012-06-01 06:00:00', 'k', '6' ], - [ 'en', '2012-06-01 06:00:00', 'kk', '06' ], - [ 'en', '2012-06-01 06:00:00', 'kkk', '' ], - [ 'en', '2012-06-01 06:00:00', 'kkkk', '' ], - [ 'en', '2012-06-01 06:00:00', 'kkkkk', '' ], - [ 'en', '2012-06-01 12:00:00', 'k', '12' ], - [ 'en', '2012-06-01 12:00:00', 'kk', '12' ], - [ 'en', '2012-06-01 12:00:00', 'kkk', '' ], - [ 'en', '2012-06-01 12:00:00', 'kkkk', '' ], - [ 'en', '2012-06-01 12:00:00', 'kkkkk', '' ], - [ 'en', '2012-06-01 18:00:00', 'k', '18' ], - [ 'en', '2012-06-01 18:00:00', 'kk', '18' ], - [ 'en', '2012-06-01 18:00:00', 'kkk', '' ], - [ 'en', '2012-06-01 18:00:00', 'kkkk', '' ], - [ 'en', '2012-06-01 18:00:00', 'kkkkk', '' ], - - # test: format minute - - [ 'en', '2012-06-01 23:01:45', 'm', '1' ], - [ 'en', '2012-06-01 23:01:45', 'mm', '01' ], - [ 'en', '2012-06-01 23:12:45', 'm', '12' ], - [ 'en', '2012-06-01 23:12:45', 'mm', '12' ], - - # test: format second - - [ 'en', '2012-06-01 23:01:02', 's', '2' ], - [ 'en', '2012-06-01 23:01:02', 'ss', '02' ], - [ 'en', '2012-06-01 23:12:45', 's', '45' ], - [ 'en', '2012-06-01 23:12:45', 'ss', '45' ], - - # test: format zone - - [ 'en', '2012-06-01 01:23:45+0200', 'z', 'GMT+0200' ], - - # test: format quarter one figure - - [ 'en', '2012-01-13', 'Q', '1' ], - [ 'en', '2012-02-13', 'Q', '1' ], - [ 'en', '2012-03-13', 'Q', '1' ], - [ 'en', '2012-04-13', 'Q', '2' ], - [ 'en', '2012-05-13', 'Q', '2' ], - [ 'en', '2012-06-13', 'Q', '2' ], - [ 'en', '2012-07-13', 'Q', '3' ], - [ 'en', '2012-08-13', 'Q', '3' ], - [ 'en', '2012-09-13', 'Q', '3' ], - [ 'en', '2012-10-13', 'Q', '4' ], - [ 'en', '2012-11-13', 'Q', '4' ], - [ 'en', '2012-12-13', 'Q', '4' ], - - # test: format quarter two figures - - [ 'en', '2012-01-13', 'QQ', '01' ], - [ 'en', '2012-04-13', 'QQ', '02' ], - [ 'en', '2012-07-13', 'QQ', '03' ], - [ 'en', '2012-10-13', 'QQ', '04' ], - - # test: format quarter abbreviated - - [ 'en', '2012-01-13', 'QQQ', 'Q1' ], - [ 'en', '2012-04-13', 'QQQ', 'Q2' ], - [ 'en', '2012-07-13', 'QQQ', 'Q3' ], - [ 'en', '2012-10-13', 'QQQ', 'Q4' ], - - # test: format quarter abbreviated in french - - [ 'fr', '2012-01-13', 'QQQ', 'T1' ], - [ 'fr', '2012-04-13', 'QQQ', 'T2' ], - [ 'fr', '2012-07-13', 'QQQ', 'T3' ], - [ 'fr', '2012-10-13', 'QQQ', 'T4' ], - - # test: format quarter wide - - [ 'en', '2012-01-13', 'QQQQ', '1st quarter' ], - [ 'en', '2012-04-13', 'QQQQ', '2nd quarter' ], - [ 'en', '2012-07-13', 'QQQQ', '3rd quarter' ], - [ 'en', '2012-10-13', 'QQQQ', '4th quarter' ], - - # test: format quarter wide in french - - [ 'fr', '2012-01-13', 'QQQQ', '1er trimestre' ], - [ 'fr', '2012-04-13', 'QQQQ', '2e trimestre' ], - [ 'fr', '2012-07-13', 'QQQQ', '3e trimestre' ], - [ 'fr', '2012-10-13', 'QQQQ', '4e trimestre' ], - - # test: format quarter invalid wide - [ 'fr', '2012-10-13', 'QQQQQ', '' ], - - # test: format stand-alone quarter one figure - - [ 'en', '2012-01-13', 'q', '1' ], - [ 'en', '2012-02-13', 'q', '1' ], - [ 'en', '2012-03-13', 'q', '1' ], - [ 'en', '2012-04-13', 'q', '2' ], - [ 'en', '2012-05-13', 'q', '2' ], - [ 'en', '2012-06-13', 'q', '2' ], - [ 'en', '2012-07-13', 'q', '3' ], - [ 'en', '2012-08-13', 'q', '3' ], - [ 'en', '2012-09-13', 'q', '3' ], - [ 'en', '2012-10-13', 'q', '4' ], - [ 'en', '2012-11-13', 'q', '4' ], - [ 'en', '2012-12-13', 'q', '4' ], - - # test: format quarter two figures - - [ 'en', '2012-01-13', 'qq', '01' ], - [ 'en', '2012-04-13', 'qq', '02' ], - [ 'en', '2012-07-13', 'qq', '03' ], - [ 'en', '2012-10-13', 'qq', '04' ], - - # test: format quarter abbreviated - - [ 'en', '2012-01-13', 'qqq', 'Q1' ], - [ 'en', '2012-04-13', 'qqq', 'Q2' ], - [ 'en', '2012-07-13', 'qqq', 'Q3' ], - [ 'en', '2012-10-13', 'qqq', 'Q4' ], - - # test: format quarter abbreviated in french - - [ 'fr', '2012-01-13', 'qqq', 'T1' ], - [ 'fr', '2012-04-13', 'qqq', 'T2' ], - [ 'fr', '2012-07-13', 'qqq', 'T3' ], - [ 'fr', '2012-10-13', 'qqq', 'T4' ], - - # test: format quarter wide - - [ 'en', '2012-01-13', 'qqqq', '1st quarter' ], - [ 'en', '2012-04-13', 'qqqq', '2nd quarter' ], - [ 'en', '2012-07-13', 'qqqq', '3rd quarter' ], - [ 'en', '2012-10-13', 'qqqq', '4th quarter' ], - - # test: format quarter wide in french - - [ 'fr', '2012-01-13', 'qqqq', '1er trimestre' ], - [ 'fr', '2012-04-13', 'qqqq', '2e trimestre' ], - [ 'fr', '2012-07-13', 'qqqq', '3e trimestre' ], - [ 'fr', '2012-10-13', 'qqqq', '4e trimestre' ], - - # test: format width(full|long|medium|short) - - [ 'en', '2013-11-02 22:23:45', DateTimeFormatLength::FULL, 'Saturday, November 2, 2013 at 10:23:45 PM CET' ], - [ 'en', '2013-11-02 22:23:45', DateTimeFormatLength::LONG, 'November 2, 2013 at 10:23:45 PM CET' ], - [ 'en', '2013-11-02 22:23:45', DateTimeFormatLength::MEDIUM, 'Nov 2, 2013, 10:23:45 PM' ], - [ 'en', '2013-11-02 22:23:45', DateTimeFormatLength::SHORT, '11/2/13, 10:23 PM' ], - - # test: format width(full|long|medium|short) in french - - [ 'fr', '2013-11-02 22:23:45', DateTimeFormatLength::FULL, 'samedi 2 novembre 2013 à 22:23:45 CET' ], - [ 'fr', '2013-11-02 22:23:45', DateTimeFormatLength::LONG, '2 novembre 2013 à 22:23:45 CET' ], - [ 'fr', '2013-11-02 22:23:45', DateTimeFormatLength::MEDIUM, '2 nov. 2013, 22:23:45' ], - [ 'fr', '2013-11-02 22:23:45', DateTimeFormatLength::SHORT, '02/11/2013 22:23' ], - - [ 'fr', '2016-06-06', "''y 'Madonna' y 'Yay", "'2016 Madonna 2016 Yay" ], - - ]; - } - - /* - * Format datetime - */ - - /* - public function test_format_datetime() - { - $f = Locale::from('fr')->date_formatter; - $t = new DateTime('2013-10-26 22:08:30', 'Europe/Paris'); - - $this->assertEquals('26 oct. 2013 22:08:30', $f->format_datetime($t)); - $this->assertEquals('26 oct. 2013 22:08:30', $f->format_datetime($t, null, null)); - $this->assertEquals('26 oct. 2013 22:08:30', $f->format_datetime($t, 'default', 'default')); - } - */ - - #[DataProvider('provide_test_format_with_id')] - public function test_format_with_skeleton(string $id, string $pattern, string $expected_result): void - { - $formatter = self::$formatters['fr']; - $datetime = new \DateTime('2013-10-26 22:08:30', new \DateTimeZone('Europe/Paris')); - - $result = $formatter->format($datetime, DateTimeFormatId::from($id)); - - $this->assertEquals($expected_result, $result); - $this->assertEquals($expected_result, $formatter->format($datetime, $pattern)); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_format_with_id(): array - { - return [ - - [ "d", "d", "26" ], - [ "Ed", "E d", "sam. 26" ], - [ "Ehm", "E h:mm a", "sam. 10:08 PM" ], - [ "EHm", "E HH:mm", "sam. 22:08" ], - [ "Ehms", "E h:mm:ss a", "sam. 10:08:30 PM" ], - [ "EHms", "E HH:mm:ss", "sam. 22:08:30" ], - [ "Gy", "y G", "2013 ap. J.-C." ], - [ "GyMMM", "MMM y G", "oct. 2013 ap. J.-C." ], - [ "GyMMMd", "d MMM y G", "26 oct. 2013 ap. J.-C." ], - [ "GyMMMEd", "E d MMM y G", "sam. 26 oct. 2013 ap. J.-C." ], - [ "h", "h a", "10 PM" ], - [ "H", "HH 'h'", "22 h" ], - [ "hm", "h:mm a", "10:08 PM" ], - [ "Hm", "HH:mm", "22:08" ], - [ "hms", "h:mm:ss a", "10:08:30 PM" ], - [ "Hms", "HH:mm:ss", "22:08:30" ], - [ "M", "L", "10" ], - [ "Md", "d/M", "26/10" ], - [ "MEd", "E d/M", "sam. 26/10" ], - [ "MMM", "LLL", "Oct." ], - [ "MMMd", "d MMM", "26 oct." ], - [ "MMMEd", "E d MMM", "sam. 26 oct." ], - [ "ms", "mm:ss", "08:30" ], - [ "y", "y", "2013" ], - [ "yM", "M/y", "10/2013" ], - [ "yMd", "d/M/y", "26/10/2013" ], - [ "yMEd", "E d/M/y", "sam. 26/10/2013" ], - [ "yMMM", "MMM y", "oct. 2013" ], - [ "yMMMd", "d MMM y", "26 oct. 2013" ], - [ "yMMMEd", "E d MMM y", "sam. 26 oct. 2013" ], - [ "yQQQ", "QQQ y", "T4 2013" ], - [ "yQQQQ", "QQQQ y", "4e trimestre 2013" ] - - ]; - } + /** + * @var array + */ + private static array $formatters = []; + + public static function setupBeforeClass(): void + { + self::$formatters = [ + + 'en' => new DateTimeFormatter(locale_for('en')->calendar), + 'fr' => new DateTimeFormatter(locale_for('fr')->calendar), + + ]; + } + + public function test_get_calendar(): void + { + $this->assertInstanceOf(Calendar::class, self::$formatters['en']->calendar); + } + + #[DataProvider('provide_test_format')] + public function test_format( + string $locale_id, + string $datetime, + string|DateTimeFormatLength $format, + string $expected + ): void { + $formatter = self::$formatters[$locale_id]; + + $this->assertSame($expected, $formatter->format($datetime, $format)); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_format(): array + { + return [ + + # test: era + + [ 'fr', '2016-06-06 13:30:40', 'G', 'ap. J.-C.' ], + [ 'fr', '2016-06-06 13:30:40', 'GG', 'ap. J.-C.' ], + [ 'fr', '2016-06-06 13:30:40', 'GGG', 'ap. J.-C.' ], + [ 'fr', '2016-06-06 13:30:40', 'GGGG', 'après Jésus-Christ' ], + [ 'fr', '2016-06-06 13:30:40', 'GGGGG', 'ap. J.-C.' ], + [ 'fr', '2016-06-06 13:30:40', 'GGGGGG', '' ], + + [ 'fr', '-0605-06-06 13:30:40', 'G', 'av. J.-C.' ], + [ 'fr', '-0605-06-06 13:30:40', 'GG', 'av. J.-C.' ], + [ 'fr', '-0605-06-06 13:30:40', 'GGG', 'av. J.-C.' ], + [ 'fr', '-0605-06-06 13:30:40', 'GGGG', 'avant Jésus-Christ' ], + [ 'fr', '-0605-06-06 13:30:40', 'GGGGG', 'av. J.-C.' ], + [ 'fr', '-0605-06-06 13:30:40', 'GGGGGG', '' ], + + # test: format year one figure + + [ 'en', '0005-01-01', 'y', '5' ], + [ 'en', '0005-01-01', 'yy', '05' ], + [ 'en', '0005-01-01', 'yyy', '005' ], + [ 'en', '0005-01-01', 'yyyy', '0005' ], + [ 'en', '0005-01-01', 'yyyyy', '00005' ], + + # test: format year two figures + + [ 'en', '0045-01-01', 'y', '45' ], + [ 'en', '0045-01-01', 'yy', '45' ], + [ 'en', '0045-01-01', 'yyy', '045' ], + [ 'en', '0045-01-01', 'yyyy', '0045' ], + [ 'en', '0045-01-01', 'yyyyy', '00045' ], + + # test: format year three figures + + [ 'en', '0345-01-01', 'y', '345' ], + [ 'en', '0345-01-01', 'yy', '45' ], + [ 'en', '0345-01-01', 'yyy', '345' ], + [ 'en', '0345-01-01', 'yyyy', '0345' ], + [ 'en', '0345-01-01', 'yyyyy', '00345' ], + + # test: format year four figures + + [ 'en', '2345-01-01', 'y', '2345' ], + [ 'en', '2345-01-01', 'yy', '45' ], + [ 'en', '2345-01-01', 'yyy', '2345' ], + [ 'en', '2345-01-01', 'yyyy', '2345' ], + [ 'en', '2345-01-01', 'yyyyy', '02345' ], + + /* failing because DateTime converts the date 12345 to 2005 + * FIXME + # test: format year five figures + + array('en', '12345-01-01', 'y', '12345'), + array('en', '12345-01-01', 'yy', '45'), + array('en', '12345-01-01', 'yyy', '12345'), + array('en', '12345-01-01', 'yyyy', '12345'), + array('en', '12345-01-01', 'yyyyy', '112345'), + */ + + # test: format month + + [ 'en', '2012-02-13', 'M', '2' ], + [ 'en', '2012-02-13', 'MM', '02' ], + [ 'en', '2012-02-13', 'MMM', 'Feb' ], + [ 'en', '2012-02-13', 'MMMM', 'February' ], + [ 'en', '2012-02-13', 'MMMMM', 'F' ], + [ 'en', '2012-02-13', 'MMMMMM', '' ], + + # test: format month in french + + [ 'fr', '2012-02-13', 'M', '2' ], + [ 'fr', '2012-02-13', 'MM', '02' ], + [ 'fr', '2012-02-13', 'MMM', 'févr.' ], + [ 'fr', '2012-02-13', 'MMMM', 'février' ], + [ 'fr', '2012-02-13', 'MMMMM', 'F' ], + [ 'fr', '2012-02-13', 'MMMMMM', '' ], + + # test: format stand-alone month + + [ 'en', '2012-02-13', 'L', '2' ], + [ 'en', '2012-02-13', 'LL', '02' ], + [ 'en', '2012-02-13', 'LLL', 'Feb' ], + [ 'en', '2012-02-13', 'LLLL', 'February' ], + [ 'en', '2012-02-13', 'LLLLL', 'F' ], + [ 'en', '2012-02-13', 'LLLLLL', '' ], + + # test: format stand-alone month in french + + [ 'fr', '2012-02-13', 'L', '2' ], + [ 'fr', '2012-02-13', 'LL', '02' ], + [ 'fr', '2012-02-13', 'LLL', 'Févr.' ], + [ 'fr', '2012-02-13', 'LLLL', 'Février' ], + [ 'fr', '2012-02-13', 'LLLLL', 'F' ], + [ 'fr', '2012-02-13', 'LLLLLL', '' ], + + # test: format week of year + + [ 'en', '2012-01-01', 'w', '52' ], + [ 'en', '2012-01-02', 'w', '1' ], + [ 'en', '2012-01-02', 'ww', '01' ], + [ 'en', '2012-01-02', 'www', '' ], + [ 'en', '2012-12-30', 'w', '52' ], + [ 'en', '2012-12-30', 'ww', '52' ], + [ 'en', '2012-12-30', 'www', '' ], + + # test: format week of month + + [ 'en', '2012-01-01', 'W', '1' ], + [ 'en', '2012-01-02', 'W', '1' ], + [ 'en', '2012-01-09', 'W', '2' ], + [ 'en', '2012-01-16', 'W', '3' ], + [ 'en', '2012-01-23', 'W', '4' ], + [ 'en', '2012-01-30', 'W', '5' ], + [ 'en', '2012-01-30', 'WW', '' ], + [ 'en', '2012-01-30', 'WWW', '' ], + [ 'en', '2012-01-30', 'WWWW', '' ], + [ 'en', '2012-01-30', 'WWWWW', '' ], + + # test: format day of the month + + [ 'en', '2012-01-01', 'd', '1' ], + [ 'en', '2012-01-01', 'dd', '01' ], + [ 'en', '2012-01-13', 'd', '13' ], + [ 'en', '2012-01-13', 'dd', '13' ], + [ 'en', '2012-01-13', 'ddd', '' ], + [ 'en', '2012-01-13', 'dddd', '' ], + [ 'en', '2012-01-13', 'ddddd', '' ], + + # test: format day of the year + + [ 'en', '2012-01-01', 'D', '1' ], + [ 'en', '2012-01-01', 'DD', '01' ], + [ 'en', '2012-01-01', 'DDD', '001' ], + [ 'en', '2012-01-01', 'DDDD', '' ], + [ 'en', '2012-01-01', 'DDDDD', '' ], + [ 'en', '2012-01-13', 'DD', '13' ], + [ 'en', '2012-01-13', 'DDD', '013' ], + [ 'en', '2012-06-13', 'DD', '165' ], + [ 'en', '2012-06-13', 'DDD', '165' ], + [ 'en', '2012-06-13', 'DDDD', '' ], + [ 'en', '2012-06-13', 'DDDDD', '' ], + + # test: format day of week + /* FIXME + array('en', '2012-06-01', 'F', '1'), + array('en', '2012-06-03', 'F', '3'), + array('en', '2012-06-05', 'F', '5'), + */ + + # test: format weekday + + [ 'en', '2012-06-01', 'E', 'Fri' ], + [ 'en', '2012-06-01', 'EE', 'Fri' ], + [ 'en', '2012-06-01', 'EEE', 'Fri' ], + [ 'en', '2012-06-01', 'EEEE', 'Friday' ], + [ 'en', '2012-06-01', 'EEEEE', 'F' ], + [ 'en', '2012-06-01', 'EEEEEE', 'Fr' ], + [ 'en', '2012-06-01', 'EEEEEEE', '' ], + + # test: format weekday in french + + [ 'fr', '2012-06-01', 'E', 'ven.' ], + [ 'fr', '2012-06-01', 'EE', 'ven.' ], + [ 'fr', '2012-06-01', 'EEE', 'ven.' ], + [ 'fr', '2012-06-01', 'EEEE', 'vendredi' ], + [ 'fr', '2012-06-01', 'EEEEE', 'V' ], + [ 'fr', '2012-06-01', 'EEEEEE', 've' ], + [ 'fr', '2012-06-01', 'EEEEEEE', '' ], + + # test: format local weekday + + [ 'en', '2012-06-01', 'e', '5' ], + [ 'en', '2012-06-01', 'ee', '5' ], + [ 'en', '2012-06-01', 'eee', 'Fri' ], + [ 'en', '2012-06-01', 'eeee', 'Friday' ], + [ 'en', '2012-06-01', 'eeeee', 'F' ], + [ 'en', '2012-06-01', 'eeeeee', 'Fr' ], + [ 'en', '2012-06-01', 'eeeeeee', '' ], + + # test: format local weekday in french + + [ 'fr', '2012-06-01', 'e', '5' ], + [ 'fr', '2012-06-01', 'ee', '5' ], + [ 'fr', '2012-06-01', 'eee', 'ven.' ], + [ 'fr', '2012-06-01', 'eeee', 'vendredi' ], + [ 'fr', '2012-06-01', 'eeeee', 'V' ], + [ 'fr', '2012-06-01', 'eeeeee', 've' ], + [ 'fr', '2012-06-01', 'eeeeeee', '' ], + + # test: format stand-alone weekday + + [ 'en', '2012-06-01', 'c', '5' ], + [ 'en', '2012-06-01', 'cc', '' ], + [ 'en', '2012-06-01', 'ccc', 'Fri' ], + [ 'en', '2012-06-01', 'cccc', 'Friday' ], + [ 'en', '2012-06-01', 'ccccc', 'F' ], + [ 'en', '2012-06-01', 'cccccc', 'Fr' ], + [ 'en', '2012-06-01', 'ccccccc', '' ], + + # test: format stand-alone weekday in french + + [ 'fr', '2012-06-01', 'c', '5' ], + [ 'fr', '2012-06-01', 'cc', '' ], + [ 'fr', '2012-06-01', 'ccc', 'Ven.' ], + [ 'fr', '2012-06-01', 'cccc', 'Vendredi' ], + [ 'fr', '2012-06-01', 'ccccc', 'V' ], + [ 'fr', '2012-06-01', 'cccccc', 'Ve' ], + [ 'fr', '2012-06-01', 'ccccccc', '' ], + + # test: format period + + [ 'en', '2012-06-01 00:00:00', 'a', 'AM' ], + [ 'en', '2012-06-01 06:00:00', 'a', 'AM' ], + [ 'en', '2012-06-01 12:00:00', 'a', 'PM' ], + [ 'en', '2012-06-01 18:00:00', 'a', 'PM' ], + + # test: format period in french + + [ 'fr', '2012-06-01 00:00:00', 'a', 'AM' ], + [ 'fr', '2012-06-01 06:00:00', 'a', 'AM' ], + [ 'fr', '2012-06-01 12:00:00', 'a', 'PM' ], + [ 'fr', '2012-06-01 18:00:00', 'a', 'PM' ], + + # test: format hour 12 + + [ 'en', '2012-06-01 00:00:00', 'h', '0' ], + [ 'en', '2012-06-01 06:00:00', 'h', '6' ], + [ 'en', '2012-06-01 12:00:00', 'h', '12' ], + [ 'en', '2012-06-01 18:00:00', 'h', '6' ], + + [ 'en', '2012-06-01 00:00:00', 'hh', '00' ], + [ 'en', '2012-06-01 06:00:00', 'hh', '06' ], + [ 'en', '2012-06-01 12:00:00', 'hh', '12' ], + [ 'en', '2012-06-01 18:00:00', 'hh', '06' ], + + [ 'en', '2012-06-01 18:00:00', 'hhh', '' ], + + # test: format hour 24 + + [ 'en', '2012-06-01 00:00:00', 'H', '0' ], + [ 'en', '2012-06-01 06:00:00', 'H', '6' ], + [ 'en', '2012-06-01 12:00:00', 'H', '12' ], + [ 'en', '2012-06-01 18:00:00', 'H', '18' ], + + [ 'en', '2012-06-01 00:00:00', 'HH', '00' ], + [ 'en', '2012-06-01 06:00:00', 'HH', '06' ], + [ 'en', '2012-06-01 12:00:00', 'HH', '12' ], + [ 'en', '2012-06-01 18:00:00', 'HH', '18' ], + + [ 'en', '2012-06-01 18:00:00', 'HHH', '' ], + + # test: format hour in period + + [ 'en', '2012-06-01 00:00:00', 'K', '0' ], + [ 'en', '2012-06-01 00:00:00', 'KK', '00' ], + [ 'en', '2012-06-01 00:00:00', 'KKK', '' ], + [ 'en', '2012-06-01 00:00:00', 'KKKK', '' ], + [ 'en', '2012-06-01 00:00:00', 'KKKKK', '' ], + [ 'en', '2012-06-01 06:00:00', 'K', '6' ], + [ 'en', '2012-06-01 06:00:00', 'KK', '06' ], + [ 'en', '2012-06-01 06:00:00', 'KKK', '' ], + [ 'en', '2012-06-01 06:00:00', 'KKKK', '' ], + [ 'en', '2012-06-01 06:00:00', 'KKKKK', '' ], + [ 'en', '2012-06-01 12:00:00', 'K', '0' ], + [ 'en', '2012-06-01 12:00:00', 'KK', '00' ], + [ 'en', '2012-06-01 12:00:00', 'KKK', '' ], + [ 'en', '2012-06-01 12:00:00', 'KKKK', '' ], + [ 'en', '2012-06-01 12:00:00', 'KKKKK', '' ], + [ 'en', '2012-06-01 18:00:00', 'K', '6' ], + [ 'en', '2012-06-01 18:00:00', 'KK', '06' ], + [ 'en', '2012-06-01 18:00:00', 'KKK', '' ], + [ 'en', '2012-06-01 18:00:00', 'KKKK', '' ], + [ 'en', '2012-06-01 18:00:00', 'KKKKK', '' ], + + # test: format hour in day + + [ 'en', '2012-06-01 00:00:00', 'k', '24' ], + [ 'en', '2012-06-01 00:00:00', 'kk', '24' ], + [ 'en', '2012-06-01 00:00:00', 'kkk', '' ], + [ 'en', '2012-06-01 00:00:00', 'kkkk', '' ], + [ 'en', '2012-06-01 00:00:00', 'kkkkk', '' ], + [ 'en', '2012-06-01 06:00:00', 'k', '6' ], + [ 'en', '2012-06-01 06:00:00', 'kk', '06' ], + [ 'en', '2012-06-01 06:00:00', 'kkk', '' ], + [ 'en', '2012-06-01 06:00:00', 'kkkk', '' ], + [ 'en', '2012-06-01 06:00:00', 'kkkkk', '' ], + [ 'en', '2012-06-01 12:00:00', 'k', '12' ], + [ 'en', '2012-06-01 12:00:00', 'kk', '12' ], + [ 'en', '2012-06-01 12:00:00', 'kkk', '' ], + [ 'en', '2012-06-01 12:00:00', 'kkkk', '' ], + [ 'en', '2012-06-01 12:00:00', 'kkkkk', '' ], + [ 'en', '2012-06-01 18:00:00', 'k', '18' ], + [ 'en', '2012-06-01 18:00:00', 'kk', '18' ], + [ 'en', '2012-06-01 18:00:00', 'kkk', '' ], + [ 'en', '2012-06-01 18:00:00', 'kkkk', '' ], + [ 'en', '2012-06-01 18:00:00', 'kkkkk', '' ], + + # test: format minute + + [ 'en', '2012-06-01 23:01:45', 'm', '1' ], + [ 'en', '2012-06-01 23:01:45', 'mm', '01' ], + [ 'en', '2012-06-01 23:12:45', 'm', '12' ], + [ 'en', '2012-06-01 23:12:45', 'mm', '12' ], + + # test: format second + + [ 'en', '2012-06-01 23:01:02', 's', '2' ], + [ 'en', '2012-06-01 23:01:02', 'ss', '02' ], + [ 'en', '2012-06-01 23:12:45', 's', '45' ], + [ 'en', '2012-06-01 23:12:45', 'ss', '45' ], + + # test: format zone + + [ 'en', '2012-06-01 01:23:45+0200', 'z', 'GMT+0200' ], + + # test: format quarter one figure + + [ 'en', '2012-01-13', 'Q', '1' ], + [ 'en', '2012-02-13', 'Q', '1' ], + [ 'en', '2012-03-13', 'Q', '1' ], + [ 'en', '2012-04-13', 'Q', '2' ], + [ 'en', '2012-05-13', 'Q', '2' ], + [ 'en', '2012-06-13', 'Q', '2' ], + [ 'en', '2012-07-13', 'Q', '3' ], + [ 'en', '2012-08-13', 'Q', '3' ], + [ 'en', '2012-09-13', 'Q', '3' ], + [ 'en', '2012-10-13', 'Q', '4' ], + [ 'en', '2012-11-13', 'Q', '4' ], + [ 'en', '2012-12-13', 'Q', '4' ], + + # test: format quarter two figures + + [ 'en', '2012-01-13', 'QQ', '01' ], + [ 'en', '2012-04-13', 'QQ', '02' ], + [ 'en', '2012-07-13', 'QQ', '03' ], + [ 'en', '2012-10-13', 'QQ', '04' ], + + # test: format quarter abbreviated + + [ 'en', '2012-01-13', 'QQQ', 'Q1' ], + [ 'en', '2012-04-13', 'QQQ', 'Q2' ], + [ 'en', '2012-07-13', 'QQQ', 'Q3' ], + [ 'en', '2012-10-13', 'QQQ', 'Q4' ], + + # test: format quarter abbreviated in french + + [ 'fr', '2012-01-13', 'QQQ', 'T1' ], + [ 'fr', '2012-04-13', 'QQQ', 'T2' ], + [ 'fr', '2012-07-13', 'QQQ', 'T3' ], + [ 'fr', '2012-10-13', 'QQQ', 'T4' ], + + # test: format quarter wide + + [ 'en', '2012-01-13', 'QQQQ', '1st quarter' ], + [ 'en', '2012-04-13', 'QQQQ', '2nd quarter' ], + [ 'en', '2012-07-13', 'QQQQ', '3rd quarter' ], + [ 'en', '2012-10-13', 'QQQQ', '4th quarter' ], + + # test: format quarter wide in french + + [ 'fr', '2012-01-13', 'QQQQ', '1er trimestre' ], + [ 'fr', '2012-04-13', 'QQQQ', '2e trimestre' ], + [ 'fr', '2012-07-13', 'QQQQ', '3e trimestre' ], + [ 'fr', '2012-10-13', 'QQQQ', '4e trimestre' ], + + # test: format quarter invalid wide + [ 'fr', '2012-10-13', 'QQQQQ', '' ], + + # test: format stand-alone quarter one figure + + [ 'en', '2012-01-13', 'q', '1' ], + [ 'en', '2012-02-13', 'q', '1' ], + [ 'en', '2012-03-13', 'q', '1' ], + [ 'en', '2012-04-13', 'q', '2' ], + [ 'en', '2012-05-13', 'q', '2' ], + [ 'en', '2012-06-13', 'q', '2' ], + [ 'en', '2012-07-13', 'q', '3' ], + [ 'en', '2012-08-13', 'q', '3' ], + [ 'en', '2012-09-13', 'q', '3' ], + [ 'en', '2012-10-13', 'q', '4' ], + [ 'en', '2012-11-13', 'q', '4' ], + [ 'en', '2012-12-13', 'q', '4' ], + + # test: format quarter two figures + + [ 'en', '2012-01-13', 'qq', '01' ], + [ 'en', '2012-04-13', 'qq', '02' ], + [ 'en', '2012-07-13', 'qq', '03' ], + [ 'en', '2012-10-13', 'qq', '04' ], + + # test: format quarter abbreviated + + [ 'en', '2012-01-13', 'qqq', 'Q1' ], + [ 'en', '2012-04-13', 'qqq', 'Q2' ], + [ 'en', '2012-07-13', 'qqq', 'Q3' ], + [ 'en', '2012-10-13', 'qqq', 'Q4' ], + + # test: format quarter abbreviated in french + + [ 'fr', '2012-01-13', 'qqq', 'T1' ], + [ 'fr', '2012-04-13', 'qqq', 'T2' ], + [ 'fr', '2012-07-13', 'qqq', 'T3' ], + [ 'fr', '2012-10-13', 'qqq', 'T4' ], + + # test: format quarter wide + + [ 'en', '2012-01-13', 'qqqq', '1st quarter' ], + [ 'en', '2012-04-13', 'qqqq', '2nd quarter' ], + [ 'en', '2012-07-13', 'qqqq', '3rd quarter' ], + [ 'en', '2012-10-13', 'qqqq', '4th quarter' ], + + # test: format quarter wide in french + + [ 'fr', '2012-01-13', 'qqqq', '1er trimestre' ], + [ 'fr', '2012-04-13', 'qqqq', '2e trimestre' ], + [ 'fr', '2012-07-13', 'qqqq', '3e trimestre' ], + [ 'fr', '2012-10-13', 'qqqq', '4e trimestre' ], + + # test: format width(full|long|medium|short) + + [ + 'en', + '2013-11-02 22:23:45', + DateTimeFormatLength::FULL, + 'Saturday, November 2, 2013 at 10:23:45 PM CET' + ], + [ 'en', '2013-11-02 22:23:45', DateTimeFormatLength::LONG, 'November 2, 2013 at 10:23:45 PM CET' ], + [ 'en', '2013-11-02 22:23:45', DateTimeFormatLength::MEDIUM, 'Nov 2, 2013, 10:23:45 PM' ], + [ 'en', '2013-11-02 22:23:45', DateTimeFormatLength::SHORT, '11/2/13, 10:23 PM' ], + + # test: format width(full|long|medium|short) in french + + [ 'fr', '2013-11-02 22:23:45', DateTimeFormatLength::FULL, 'samedi 2 novembre 2013 à 22:23:45 CET' ], + [ 'fr', '2013-11-02 22:23:45', DateTimeFormatLength::LONG, '2 novembre 2013 à 22:23:45 CET' ], + [ 'fr', '2013-11-02 22:23:45', DateTimeFormatLength::MEDIUM, '2 nov. 2013, 22:23:45' ], + [ 'fr', '2013-11-02 22:23:45', DateTimeFormatLength::SHORT, '02/11/2013 22:23' ], + + [ 'fr', '2016-06-06', "''y 'Madonna' y 'Yay", "'2016 Madonna 2016 Yay" ], + + ]; + } + + /* + * Format datetime + */ + + /* + public function test_format_datetime() + { + $f = Locale::from('fr')->date_formatter; + $t = new DateTime('2013-10-26 22:08:30', 'Europe/Paris'); + + $this->assertEquals('26 oct. 2013 22:08:30', $f->format_datetime($t)); + $this->assertEquals('26 oct. 2013 22:08:30', $f->format_datetime($t, null, null)); + $this->assertEquals('26 oct. 2013 22:08:30', $f->format_datetime($t, 'default', 'default')); + } + */ + + #[DataProvider('provide_test_format_with_id')] + public function test_format_with_skeleton(string $id, string $pattern, string $expected_result): void + { + $formatter = self::$formatters['fr']; + $datetime = new \DateTime('2013-10-26 22:08:30', new \DateTimeZone('Europe/Paris')); + + $result = $formatter->format($datetime, DateTimeFormatId::from($id)); + + $this->assertEquals($expected_result, $result); + $this->assertEquals($expected_result, $formatter->format($datetime, $pattern)); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_format_with_id(): array + { + return [ + + [ "d", "d", "26" ], + [ "Ed", "E d", "sam. 26" ], + [ "Ehm", "E h:mm a", "sam. 10:08 PM" ], + [ "EHm", "E HH:mm", "sam. 22:08" ], + [ "Ehms", "E h:mm:ss a", "sam. 10:08:30 PM" ], + [ "EHms", "E HH:mm:ss", "sam. 22:08:30" ], + [ "Gy", "y G", "2013 ap. J.-C." ], + [ "GyMMM", "MMM y G", "oct. 2013 ap. J.-C." ], + [ "GyMMMd", "d MMM y G", "26 oct. 2013 ap. J.-C." ], + [ "GyMMMEd", "E d MMM y G", "sam. 26 oct. 2013 ap. J.-C." ], + [ "h", "h a", "10 PM" ], + [ "H", "HH 'h'", "22 h" ], + [ "hm", "h:mm a", "10:08 PM" ], + [ "Hm", "HH:mm", "22:08" ], + [ "hms", "h:mm:ss a", "10:08:30 PM" ], + [ "Hms", "HH:mm:ss", "22:08:30" ], + [ "M", "L", "10" ], + [ "Md", "d/M", "26/10" ], + [ "MEd", "E d/M", "sam. 26/10" ], + [ "MMM", "LLL", "Oct." ], + [ "MMMd", "d MMM", "26 oct." ], + [ "MMMEd", "E d MMM", "sam. 26 oct." ], + [ "ms", "mm:ss", "08:30" ], + [ "y", "y", "2013" ], + [ "yM", "M/y", "10/2013" ], + [ "yMd", "d/M/y", "26/10/2013" ], + [ "yMEd", "E d/M/y", "sam. 26/10/2013" ], + [ "yMMM", "MMM y", "oct. 2013" ], + [ "yMMMd", "d MMM y", "26 oct. 2013" ], + [ "yMMMEd", "E d MMM y", "sam. 26 oct. 2013" ], + [ "yQQQ", "QQQ y", "T4 2013" ], + [ "yQQQQ", "QQQQ y", "4e trimestre 2013" ] + + ]; + } } diff --git a/tests/GitHub/UrlResolverTest.php b/tests/GitHub/UrlResolverTest.php index 817a69e..ec0fe71 100644 --- a/tests/GitHub/UrlResolverTest.php +++ b/tests/GitHub/UrlResolverTest.php @@ -8,105 +8,105 @@ final class UrlResolverTest extends TestCase { - #[DataProvider('provide_resolve')] - public function test_resolve(string $path, string $expected): void - { - $sut = new UrlResolver(); - - $this->assertSame($expected, $sut->resolve($path)); - } - - public static function provide_resolve(): array - { - $v = UrlResolver::DEFAULT_VERSION; - - return [ - - "annotations-derived/{locale}/annotations" => [ - "annotations-derived/fr-CA/annotations", - "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-annotations-derived-full/annotationsDerived/fr-CA/annotations.json" - ], - - "annotations/{locale}/annotations" => [ - "annotations/hi-Latn/annotations", - "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-annotations-full/annotations/hi-Latn/annotations.json" - ], - - "bcp47/calendar" => [ - "bcp47/calendar", - "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-bcp47/bcp47/calendar.json" - ], - - "bcp47/transform_keyboard" => [ - "bcp47/transform_keyboard", - "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-bcp47/bcp47/transform_keyboard.json" - ], - - "cal-buddhist/{locale}/ca-buddhist" => [ - "cal-buddhist/de-BE/ca-buddhist", - "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-cal-buddhist-full/main/de-BE/ca-buddhist.json" - ], - - "core/supplemental/plurals" => [ - "core/supplemental/plurals", - "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-core/supplemental/plurals.json" - ], - - "core/availableLocales" => [ - "core/availableLocales", - "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-core/availableLocales.json" - ], - - "core/defaultContent" => [ - "core/defaultContent", - "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-core/defaultContent.json" - ], - - "dates/{locale}/ca-gregorian" => [ - "dates/ko-KP/ca-gregorian", - "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-dates-full/main/ko-KP/ca-gregorian.json" - ], - - "dates/{locale}/dateFields" => [ - "dates/ko-KP/dateFields", - "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-dates-full/main/ko-KP/dateFields.json" - ], - - "localnames/{locale}/languages" => [ - "localenames/fr-MA/languages", - "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-localenames-full/main/fr-MA/languages.json" - ], - - "misc/{locale}/listPatterns" => [ - "misc/ms-SG/listPatterns", - "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-misc-full/main/ms-SG/listPatterns.json" - ], - - "numbers/{locale}/currencies" => [ - "numbers/en-150/currencies", - "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-numbers-full/main/en-150/currencies.json" - ], - - "numbers/{locale}/numbers" => [ - "numbers/en-150/numbers", - "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-numbers-full/main/en-150/numbers.json" - ], - - "rbnf/{language}" => [ - "rbnf/af", - "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-rbnf/rbnf/af.json" - ], - - "segments/{language}/suppressions" => [ - "segments/de/suppressions", - "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-segments-full/segments/de/suppressions.json" - ], - - "units/{locale}/units" => [ - "units/pt-MO/units", - "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-units-full/main/pt-MO/units.json" - ], - - ]; - } + #[DataProvider('provide_resolve')] + public function test_resolve(string $path, string $expected): void + { + $sut = new UrlResolver(); + + $this->assertSame($expected, $sut->resolve($path)); + } + + public static function provide_resolve(): array + { + $v = UrlResolver::DEFAULT_VERSION; + + return [ + + "annotations-derived/{locale}/annotations" => [ + "annotations-derived/fr-CA/annotations", + "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-annotations-derived-full/annotationsDerived/fr-CA/annotations.json" + ], + + "annotations/{locale}/annotations" => [ + "annotations/hi-Latn/annotations", + "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-annotations-full/annotations/hi-Latn/annotations.json" + ], + + "bcp47/calendar" => [ + "bcp47/calendar", + "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-bcp47/bcp47/calendar.json" + ], + + "bcp47/transform_keyboard" => [ + "bcp47/transform_keyboard", + "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-bcp47/bcp47/transform_keyboard.json" + ], + + "cal-buddhist/{locale}/ca-buddhist" => [ + "cal-buddhist/de-BE/ca-buddhist", + "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-cal-buddhist-full/main/de-BE/ca-buddhist.json" + ], + + "core/supplemental/plurals" => [ + "core/supplemental/plurals", + "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-core/supplemental/plurals.json" + ], + + "core/availableLocales" => [ + "core/availableLocales", + "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-core/availableLocales.json" + ], + + "core/defaultContent" => [ + "core/defaultContent", + "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-core/defaultContent.json" + ], + + "dates/{locale}/ca-gregorian" => [ + "dates/ko-KP/ca-gregorian", + "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-dates-full/main/ko-KP/ca-gregorian.json" + ], + + "dates/{locale}/dateFields" => [ + "dates/ko-KP/dateFields", + "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-dates-full/main/ko-KP/dateFields.json" + ], + + "localnames/{locale}/languages" => [ + "localenames/fr-MA/languages", + "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-localenames-full/main/fr-MA/languages.json" + ], + + "misc/{locale}/listPatterns" => [ + "misc/ms-SG/listPatterns", + "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-misc-full/main/ms-SG/listPatterns.json" + ], + + "numbers/{locale}/currencies" => [ + "numbers/en-150/currencies", + "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-numbers-full/main/en-150/currencies.json" + ], + + "numbers/{locale}/numbers" => [ + "numbers/en-150/numbers", + "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-numbers-full/main/en-150/numbers.json" + ], + + "rbnf/{language}" => [ + "rbnf/af", + "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-rbnf/rbnf/af.json" + ], + + "segments/{language}/suppressions" => [ + "segments/de/suppressions", + "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-segments-full/segments/de/suppressions.json" + ], + + "units/{locale}/units" => [ + "units/pt-MO/units", + "https://raw.githubusercontent.com/unicode-org/cldr-json/$v/cldr-json/cldr-units-full/main/pt-MO/units.json" + ], + + ]; + } } diff --git a/tests/ListFormatterTest.php b/tests/ListFormatterTest.php index f7e9eec..4a70683 100644 --- a/tests/ListFormatterTest.php +++ b/tests/ListFormatterTest.php @@ -9,49 +9,49 @@ final class ListFormatterTest extends TestCase { - #[DataProvider('provide_test_format')] - public function test_format(array $list, ListPattern $list_pattern, string $expected): void - { - $formatter = new ListFormatter(); - $this->assertSame($expected, $formatter->format($list, $list_pattern)); - } - - public static function provide_test_format(): array - { - $lp1 = ListPattern::from([ - - '2' => "{0} and {1}", - 'start' => "{0}, {1}", + #[DataProvider('provide_test_format')] + public function test_format(array $list, ListPattern $list_pattern, string $expected): void + { + $formatter = new ListFormatter(); + $this->assertSame($expected, $formatter->format($list, $list_pattern)); + } + + public static function provide_test_format(): array + { + $lp1 = ListPattern::from([ + + '2' => "{0} and {1}", + 'start' => "{0}, {1}", 'middle' => "{0}, {1}", 'end' => "{0}, and {1}", - ]); + ]); - $lp2 = ListPattern::from([ + $lp2 = ListPattern::from([ - '2' => "{0} et {1}", - 'start' => "{0}, {1}", - 'middle' => "{0}, {1}", - 'end' => "{0} et {1}", + '2' => "{0} et {1}", + 'start' => "{0}, {1}", + 'middle' => "{0}, {1}", + 'end' => "{0} et {1}", - ]); + ]); - return [ + return [ - [ [ ], $lp1, "" ], - [ [ 'one' ], $lp1, "one" ], - [ [ 'one', 'two' ], $lp1, "one and two" ], - [ [ 'one', 'two', 'three' ], $lp1, "one, two, and three" ], - [ [ 'one', 'two', 'three', 'four' ], $lp1, "one, two, three, and four" ], - [ [ 'one', 'two', 'three', 'four', 'five' ], $lp1, "one, two, three, four, and five" ], + [ [], $lp1, "" ], + [ [ 'one' ], $lp1, "one" ], + [ [ 'one', 'two' ], $lp1, "one and two" ], + [ [ 'one', 'two', 'three' ], $lp1, "one, two, and three" ], + [ [ 'one', 'two', 'three', 'four' ], $lp1, "one, two, three, and four" ], + [ [ 'one', 'two', 'three', 'four', 'five' ], $lp1, "one, two, three, four, and five" ], - [ [ ], $lp2, "" ], - [ [ 'un' ], $lp2, "un" ], - [ [ 'un', 'deux' ], $lp2, "un et deux" ], - [ [ 'un', 'deux', 'trois' ], $lp2, "un, deux et trois" ], - [ [ 'un', 'deux', 'trois', 'quatre' ], $lp2, "un, deux, trois et quatre" ], - [ [ 'un', 'deux', 'trois', 'quatre', 'cinq' ], $lp2, "un, deux, trois, quatre et cinq" ] + [ [], $lp2, "" ], + [ [ 'un' ], $lp2, "un" ], + [ [ 'un', 'deux' ], $lp2, "un et deux" ], + [ [ 'un', 'deux', 'trois' ], $lp2, "un, deux et trois" ], + [ [ 'un', 'deux', 'trois', 'quatre' ], $lp2, "un, deux, trois et quatre" ], + [ [ 'un', 'deux', 'trois', 'quatre', 'cinq' ], $lp2, "un, deux, trois, quatre et cinq" ] - ]; - } + ]; + } } diff --git a/tests/LocaleIdTest.php b/tests/LocaleIdTest.php index 4662f6c..8214504 100644 --- a/tests/LocaleIdTest.php +++ b/tests/LocaleIdTest.php @@ -9,37 +9,37 @@ final class LocaleIdTest extends TestCase { - #[DataProvider('provide_test_is_locale_available')] - public function test_is_locale_available(string $locale_id, bool $expected): void - { - $this->assertSame($expected, LocaleId::is_available($locale_id)); - } - - /** @phpstan-ignore-next-line */ - public static function provide_test_is_locale_available(): array - { - return [ - - [ 'fr', true ], - [ 'en', true ], - [ 'en-AG', true ], - [ 'fr-FR', false ], - [ 'en-US', false ], - - ]; - } - - public function test_of_fails_on_unavailable_id(): void - { - $this->expectException(LocaleNotAvailable::class); - - LocaleId::of('fr-FR'); - } - - public function test_of_use_parent(): void - { - $locale = LocaleId::of('en-AG'); - - $this->assertEquals('en-001', $locale->value); - } + #[DataProvider('provide_test_is_locale_available')] + public function test_is_locale_available(string $locale_id, bool $expected): void + { + $this->assertSame($expected, LocaleId::is_available($locale_id)); + } + + /** @phpstan-ignore-next-line */ + public static function provide_test_is_locale_available(): array + { + return [ + + [ 'fr', true ], + [ 'en', true ], + [ 'en-AG', true ], + [ 'fr-FR', false ], + [ 'en-US', false ], + + ]; + } + + public function test_of_fails_on_unavailable_id(): void + { + $this->expectException(LocaleNotAvailable::class); + + LocaleId::of('fr-FR'); + } + + public function test_of_use_parent(): void + { + $locale = LocaleId::of('en-AG'); + + $this->assertEquals('en-001', $locale->value); + } } diff --git a/tests/LocaleTest.php b/tests/LocaleTest.php index 527336a..6fca392 100644 --- a/tests/LocaleTest.php +++ b/tests/LocaleTest.php @@ -5,18 +5,13 @@ use ICanBoogie\CLDR\Calendar; use ICanBoogie\CLDR\CalendarCollection; use ICanBoogie\CLDR\ContextTransforms; -use ICanBoogie\CLDR\ListFormatter; use ICanBoogie\CLDR\Locale; use ICanBoogie\CLDR\LocaleId; use ICanBoogie\CLDR\LocalizedCurrencyFormatter; use ICanBoogie\CLDR\LocalizedListFormatter; -use ICanBoogie\CLDR\LocalizedLocale; use ICanBoogie\CLDR\LocalizedNumberFormatter; -use ICanBoogie\CLDR\LocalizedObject; -use ICanBoogie\CLDR\NumberFormatter; use ICanBoogie\CLDR\Numbers; use ICanBoogie\CLDR\Repository; -use ICanBoogie\CLDR\Spaces; use ICanBoogie\CLDR\Units; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; @@ -24,203 +19,205 @@ final class LocaleTest extends TestCase { - use StringHelpers; - - private static Locale $locale; - - public static function setupBeforeClass(): void - { - self::$locale = new Locale(get_repository(), LocaleId::of('fr')); - } - - public function test_get_code(): void - { - $this->assertEquals(LocaleId::of('fr'), self::$locale->id); - } - - public function test_get_language(): void - { - $locale = new Locale(get_repository(), LocaleId::of('fr-BE')); - - $this->assertEquals('fr', $locale->language); - } - - /** - * @param class-string $expected - */ - #[DataProvider('provide_test_properties_instanceof')] - public function test_properties_instanceof(string $property, string $expected): void - { - $locale = new Locale(get_repository(), LocaleId::of('fr')); - $instance = $locale->$property; - $this->assertInstanceOf($expected, $instance); - $this->assertSame($instance, $locale->$property); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_properties_instanceof(): array - { - return [ - - [ 'repository', Repository::class ], - [ 'calendars', CalendarCollection::class ], - [ 'calendar', Calendar::class ], - [ 'numbers', Numbers::class ], - [ 'number_formatter', LocalizedNumberFormatter::class ], - [ 'currency_formatter', LocalizedCurrencyFormatter::class ], - [ 'list_formatter', LocalizedListFormatter::class ], - [ 'context_transforms', ContextTransforms::class ], - [ 'units', Units::class ], - - ]; - } - - #[DataProvider('provide_test_sections')] - public function test_sections(string $section, string $key): void - { - $section_data = self::$locale[$section]; - $this->assertIsArray($section_data); - $this->assertArrayHasKey($key, $section_data); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_sections(): array - { - return [ - - [ 'ca-buddhist', 'months' ], - [ 'ca-chinese', 'months' ], - [ 'ca-coptic', 'months' ], - [ 'ca-dangi', 'months' ], - [ 'ca-ethiopic', 'months' ], - [ 'ca-hebrew', 'months' ], - [ 'ca-indian', 'months' ], - [ 'ca-islamic', 'months' ], - [ 'ca-japanese', 'months' ], - [ 'ca-persian', 'months' ], - [ 'ca-roc', 'months' ], - [ 'ca-generic', 'months' ], - [ 'ca-gregorian', 'months' ], - [ 'dateFields', 'era' ], - [ 'timeZoneNames', 'hourFormat' ], - [ 'languages', 'aa' ], - [ 'localeDisplayNames', 'localeDisplayPattern' ], - [ 'scripts', 'Arab' ], - [ 'territories', 'AC' ], - [ 'variants', 'AREVELA' ], - [ 'characters', 'exemplarCharacters' ], - [ 'contextTransforms', 'day-format-except-narrow' ], - [ 'delimiters', 'quotationStart' ], - [ 'layout', 'orientation' ], - [ 'listPatterns', 'listPattern-type-standard' ], - [ 'posix', 'messages' ], - [ 'currencies', 'ADP' ], - [ 'numbers', 'defaultNumberingSystem' ], - [ 'measurementSystemNames', 'metric' ], - [ 'units', 'long' ], - - ]; - } - - /** - * @param class-string $expected - */ - #[DataProvider('provide_localize')] - public function test_localize(mixed $locale, string $expected): void - { - $actual = self::$locale->localized($locale); - - $this->assertEquals($expected, $actual->name); - } - - public static function provide_localize(): array - { - return [ - - [ 'fr', "français" ], - [ LocaleId::of('fr'), "français" ], - [ locale_for('fr'), "français" ], - [ 'en', "French" ], - [ LocaleId::of('en'), "French" ], - [ locale_for('en'), "French" ], - - ]; - } - - public function test_format_number(): void - { - $this->assertStringSame( - "123 456,78", - self::$locale->format_number(123456.78) - ); - } - - public function test_format_percent(): void - { - $this->assertStringSame( - "12 %", - self::$locale->format_percent(.1234) - ); - } - - public function test_format_currency(): void - { - $this->assertStringSame( - "123 456,78 €", - self::$locale->format_currency(123456.78, 'EUR') - ); - } - - public function test_format_list(): void - { - $this->assertSame( - "lundi, mardi et mercredi", - self::$locale->format_list([ "lundi", "mardi", "mercredi" ]) - ); - } - - #[DataProvider("provide_context_transforms_availability")] - public function test_context_transforms_availability(string $locale_id, bool $expected): void - { - // @phpstan-ignore-next-line - $actual = count(locale_for($locale_id)['contextTransforms']) > 0; - - $this->assertSame($expected, $actual); - } - - // @phpstan-ignore-next-line - public static function provide_context_transforms_availability(): array - { - return [ - [ 'en', true ], - [ 'fr-BE', true ], - [ 'ja', false ], - [ 'zh', false ], - ]; - } - - public function test_context_transform(): void - { - $this->assertEquals( - "Juin", - self::$locale->context_transform( - "juin", - ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW, - ContextTransforms::TYPE_STAND_ALONE - ) - ); - } - - public function test_warm_up(): void - { - $n = 0; - - self::$locale->warm_up(function() use (&$n) { $n++; }); - - $this->assertEquals(31, $n); - } + use StringHelpers; + + private static Locale $locale; + + public static function setupBeforeClass(): void + { + self::$locale = new Locale(get_repository(), LocaleId::of('fr')); + } + + public function test_get_code(): void + { + $this->assertEquals(LocaleId::of('fr'), self::$locale->id); + } + + public function test_get_language(): void + { + $locale = new Locale(get_repository(), LocaleId::of('fr-BE')); + + $this->assertEquals('fr', $locale->language); + } + + /** + * @param class-string $expected + */ + #[DataProvider('provide_test_properties_instanceof')] + public function test_properties_instanceof(string $property, string $expected): void + { + $locale = new Locale(get_repository(), LocaleId::of('fr')); + $instance = $locale->$property; + $this->assertInstanceOf($expected, $instance); + $this->assertSame($instance, $locale->$property); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_properties_instanceof(): array + { + return [ + + [ 'repository', Repository::class ], + [ 'calendars', CalendarCollection::class ], + [ 'calendar', Calendar::class ], + [ 'numbers', Numbers::class ], + [ 'number_formatter', LocalizedNumberFormatter::class ], + [ 'currency_formatter', LocalizedCurrencyFormatter::class ], + [ 'list_formatter', LocalizedListFormatter::class ], + [ 'context_transforms', ContextTransforms::class ], + [ 'units', Units::class ], + + ]; + } + + #[DataProvider('provide_test_sections')] + public function test_sections(string $section, string $key): void + { + $section_data = self::$locale[$section]; + $this->assertIsArray($section_data); + $this->assertArrayHasKey($key, $section_data); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_sections(): array + { + return [ + + [ 'ca-buddhist', 'months' ], + [ 'ca-chinese', 'months' ], + [ 'ca-coptic', 'months' ], + [ 'ca-dangi', 'months' ], + [ 'ca-ethiopic', 'months' ], + [ 'ca-hebrew', 'months' ], + [ 'ca-indian', 'months' ], + [ 'ca-islamic', 'months' ], + [ 'ca-japanese', 'months' ], + [ 'ca-persian', 'months' ], + [ 'ca-roc', 'months' ], + [ 'ca-generic', 'months' ], + [ 'ca-gregorian', 'months' ], + [ 'dateFields', 'era' ], + [ 'timeZoneNames', 'hourFormat' ], + [ 'languages', 'aa' ], + [ 'localeDisplayNames', 'localeDisplayPattern' ], + [ 'scripts', 'Arab' ], + [ 'territories', 'AC' ], + [ 'variants', 'AREVELA' ], + [ 'characters', 'exemplarCharacters' ], + [ 'contextTransforms', 'day-format-except-narrow' ], + [ 'delimiters', 'quotationStart' ], + [ 'layout', 'orientation' ], + [ 'listPatterns', 'listPattern-type-standard' ], + [ 'posix', 'messages' ], + [ 'currencies', 'ADP' ], + [ 'numbers', 'defaultNumberingSystem' ], + [ 'measurementSystemNames', 'metric' ], + [ 'units', 'long' ], + + ]; + } + + /** + * @param class-string $expected + */ + #[DataProvider('provide_localize')] + public function test_localize(mixed $locale, string $expected): void + { + $actual = self::$locale->localized($locale); + + $this->assertEquals($expected, $actual->name); + } + + public static function provide_localize(): array + { + return [ + + [ 'fr', "français" ], + [ LocaleId::of('fr'), "français" ], + [ locale_for('fr'), "français" ], + [ 'en', "French" ], + [ LocaleId::of('en'), "French" ], + [ locale_for('en'), "French" ], + + ]; + } + + public function test_format_number(): void + { + $this->assertStringSame( + "123 456,78", + self::$locale->format_number(123456.78) + ); + } + + public function test_format_percent(): void + { + $this->assertStringSame( + "12 %", + self::$locale->format_percent(.1234) + ); + } + + public function test_format_currency(): void + { + $this->assertStringSame( + "123 456,78 €", + self::$locale->format_currency(123456.78, 'EUR') + ); + } + + public function test_format_list(): void + { + $this->assertSame( + "lundi, mardi et mercredi", + self::$locale->format_list([ "lundi", "mardi", "mercredi" ]) + ); + } + + #[DataProvider("provide_context_transforms_availability")] + public function test_context_transforms_availability(string $locale_id, bool $expected): void + { + // @phpstan-ignore-next-line + $actual = count(locale_for($locale_id)['contextTransforms']) > 0; + + $this->assertSame($expected, $actual); + } + + // @phpstan-ignore-next-line + public static function provide_context_transforms_availability(): array + { + return [ + [ 'en', true ], + [ 'fr-BE', true ], + [ 'ja', false ], + [ 'zh', false ], + ]; + } + + public function test_context_transform(): void + { + $this->assertEquals( + "Juin", + self::$locale->context_transform( + "juin", + ContextTransforms::USAGE_MONTH_FORMAT_EXCEPT_NARROW, + ContextTransforms::TYPE_STAND_ALONE + ) + ); + } + + public function test_warm_up(): void + { + $n = 0; + + self::$locale->warm_up(function () use (&$n) { + $n++; + }); + + $this->assertEquals(31, $n); + } } diff --git a/tests/LocalizedCurrencyFormatterTest.php b/tests/LocalizedCurrencyFormatterTest.php index 53e1986..6e4adbb 100644 --- a/tests/LocalizedCurrencyFormatterTest.php +++ b/tests/LocalizedCurrencyFormatterTest.php @@ -11,100 +11,100 @@ final class LocalizedCurrencyFormatterTest extends TestCase { - use StringHelpers; - - private CurrencyFormatter $sut; - - protected function setUp(): void - { - $this->sut = new CurrencyFormatter(new NumberFormatter()); - } - - #[DataProvider('provide_test_format')] - public function test_format( - string $currency_code, - string $locale_id, - float $number, - string $expected - ): void { - $formatter = new LocalizedCurrencyFormatter( - $this->sut, - locale_for($locale_id), - ); - - $this->assertStringSame($expected, $formatter->format($number, $currency_code)); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_format(): array - { - $s1 = Spaces::NARROW_NO_BREAK_SPACE; - $s2 = Spaces::NO_BREAK_SPACE; - - return [ - - [ 'IEP', 'fr', 123456.789, "123{$s1}456,79{$s2}£IE" ], - [ 'IEP', 'en', 123456.789, "IEP123,456.79" ], - [ 'EUR', 'fr', 123456.789, "123{$s1}456,79{$s2}€" ], - [ 'EUR', 'en', 123456.789, "€123,456.79" ], - [ 'USD', 'fr', 123456.789, "123{$s1}456,79{$s2}\$US" ], - [ 'USD', 'en', 123456.789, "\$123,456.79" ], - - ]; - } - - /** - * @param float|int|numeric-string $number - */ - #[DataProvider('provide_test_format_accounting')] - public function test_format_accounting( - string $currency_code, - string $locale_id, - float|int|string $number, - string $expected - ): void { - $formatter = new LocalizedCurrencyFormatter( - $this->sut, - locale_for($locale_id), - ); - - $this->assertStringSame( - $expected, - $formatter->format($number, $currency_code, $formatter::PATTERN_ACCOUNTING) - ); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_format_accounting(): array - { - $s1 = Spaces::NARROW_NO_BREAK_SPACE; - $s2 = Spaces::NO_BREAK_SPACE; - - return [ - - [ 'IEP', 'fr', 123456.789, "123{$s1}456,79{$s2}£IE" ], - [ 'IEP', 'en', 123456.789, "IEP123,456.79" ], - [ 'EUR', 'fr', 123456.789, "123{$s1}456,79{$s2}€" ], - [ 'EUR', 'en', 123456.789, "€123,456.79" ], - [ 'USD', 'fr', 123456.789, "123{$s1}456,79{$s2}\$US" ], - [ 'USD', 'en', 123456.789, "\$123,456.79" ], - - ]; - } - - public function test_should_format_with_custom_pattern(): void - { - $sut = new LocalizedCurrencyFormatter( - $this->sut, - locale_for('fr'), - ); - - $actual = $sut->format(123.45, 'EUR', '¤0.0'); - - $this->assertStringSame("€123,5", $actual); - } + use StringHelpers; + + private CurrencyFormatter $sut; + + protected function setUp(): void + { + $this->sut = new CurrencyFormatter(new NumberFormatter()); + } + + #[DataProvider('provide_test_format')] + public function test_format( + string $currency_code, + string $locale_id, + float $number, + string $expected + ): void { + $formatter = new LocalizedCurrencyFormatter( + $this->sut, + locale_for($locale_id), + ); + + $this->assertStringSame($expected, $formatter->format($number, $currency_code)); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_format(): array + { + $s1 = Spaces::NARROW_NO_BREAK_SPACE; + $s2 = Spaces::NO_BREAK_SPACE; + + return [ + + [ 'IEP', 'fr', 123456.789, "123{$s1}456,79{$s2}£IE" ], + [ 'IEP', 'en', 123456.789, "IEP123,456.79" ], + [ 'EUR', 'fr', 123456.789, "123{$s1}456,79{$s2}€" ], + [ 'EUR', 'en', 123456.789, "€123,456.79" ], + [ 'USD', 'fr', 123456.789, "123{$s1}456,79{$s2}\$US" ], + [ 'USD', 'en', 123456.789, "\$123,456.79" ], + + ]; + } + + /** + * @param float|int|numeric-string $number + */ + #[DataProvider('provide_test_format_accounting')] + public function test_format_accounting( + string $currency_code, + string $locale_id, + float|int|string $number, + string $expected + ): void { + $formatter = new LocalizedCurrencyFormatter( + $this->sut, + locale_for($locale_id), + ); + + $this->assertStringSame( + $expected, + $formatter->format($number, $currency_code, $formatter::PATTERN_ACCOUNTING) + ); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_format_accounting(): array + { + $s1 = Spaces::NARROW_NO_BREAK_SPACE; + $s2 = Spaces::NO_BREAK_SPACE; + + return [ + + [ 'IEP', 'fr', 123456.789, "123{$s1}456,79{$s2}£IE" ], + [ 'IEP', 'en', 123456.789, "IEP123,456.79" ], + [ 'EUR', 'fr', 123456.789, "123{$s1}456,79{$s2}€" ], + [ 'EUR', 'en', 123456.789, "€123,456.79" ], + [ 'USD', 'fr', 123456.789, "123{$s1}456,79{$s2}\$US" ], + [ 'USD', 'en', 123456.789, "\$123,456.79" ], + + ]; + } + + public function test_should_format_with_custom_pattern(): void + { + $sut = new LocalizedCurrencyFormatter( + $this->sut, + locale_for('fr'), + ); + + $actual = $sut->format(123.45, 'EUR', '¤0.0'); + + $this->assertStringSame("€123,5", $actual); + } } diff --git a/tests/LocalizedCurrencyTest.php b/tests/LocalizedCurrencyTest.php index bf6d396..fd94de2 100644 --- a/tests/LocalizedCurrencyTest.php +++ b/tests/LocalizedCurrencyTest.php @@ -11,97 +11,97 @@ final class LocalizedCurrencyTest extends TestCase { - use StringHelpers; - - private static Currency $currency; - private static LocalizedCurrency $sut; - - public static function setUpBeforeClass(): void - { - self::$currency = Currency::of('IEP'); - self::$sut = new LocalizedCurrency(self::$currency, locale_for('fr')); - } - - public function test_name(): void - { - $this->assertEquals("livre irlandaise", self::$sut->name); - } - - public function test_name_for(): void - { - $this->assertEquals("livre irlandaise", self::$sut->name); - $this->assertEquals("livre irlandaise", self::$sut->name_for(1)); - $this->assertEquals("livres irlandaises", self::$sut->name_for(10)); - } - - public function test_get_symbol(): void - { - $this->assertEquals("£IE", self::$sut->symbol); - } - - public function test_localize(): void - { - $localized = self::$currency->localized(locale_for('en')); - $this->assertInstanceOf(LocalizedCurrency::class, $localized); - $this->assertEquals("Irish Pound", $localized->name); - } - - #[DataProvider('provide_test_format')] - public function test_format(string $currency_code, string $locale_id, float|int $number, string $expected): void - { - $currency = Currency::of($currency_code); - $localized = new LocalizedCurrency($currency, locale_for($locale_id)); - $this->assertEquals($expected, $localized->format($number)); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_format(): array - { - return [ - - [ 'IEP', 'fr', 123456.789, "123 456,79 £IE" ], - [ 'IEP', 'en', 123456.789, "IEP123,456.79" ], - [ 'EUR', 'fr', 123456.789, "123 456,79 €" ], - [ 'EUR', 'en', 123456.789, "€123,456.79" ], - [ 'USD', 'fr', 123456.789, "123 456,79 \$US" ], - [ 'USD', 'en', 123456.789, "\$123,456.79" ], - - ]; - } - - #[DataProvider('provide_test_format_accounting')] - public function test_format_accounting( - string $currency_code, - string $locale_id, - float $number, - string $expected - ): void { - $currency = Currency::of($currency_code); - $localized = new LocalizedCurrency($currency, locale_for($locale_id)); - $actual = $localized->format($number, LocalizedCurrencyFormatter::PATTERN_ACCOUNTING); - - $this->assertStringSame($expected, $actual); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_format_accounting(): array - { - $s1 = Spaces::NARROW_NO_BREAK_SPACE; - $s2 = Spaces::NO_BREAK_SPACE; - - return [ - - [ 'IEP', 'fr', 123456.789, "123 456,79 £IE" ], - [ 'IEP', 'en', 123456.789, "IEP123,456.79" ], - [ 'EUR', 'fr', 123456.789, "123 456,79 €" ], - [ 'EUR', 'en', 123456.789, "€123,456.79" ], - [ 'USD', 'fr', 123456.789, "123{$s1}456,79{$s2}\$US" ], - [ 'USD', 'en', 123456.789, '$123,456.79' ], - - ]; - } + use StringHelpers; + + private static Currency $currency; + private static LocalizedCurrency $sut; + + public static function setUpBeforeClass(): void + { + self::$currency = Currency::of('IEP'); + self::$sut = new LocalizedCurrency(self::$currency, locale_for('fr')); + } + + public function test_name(): void + { + $this->assertEquals("livre irlandaise", self::$sut->name); + } + + public function test_name_for(): void + { + $this->assertEquals("livre irlandaise", self::$sut->name); + $this->assertEquals("livre irlandaise", self::$sut->name_for(1)); + $this->assertEquals("livres irlandaises", self::$sut->name_for(10)); + } + + public function test_get_symbol(): void + { + $this->assertEquals("£IE", self::$sut->symbol); + } + + public function test_localize(): void + { + $localized = self::$currency->localized(locale_for('en')); + $this->assertInstanceOf(LocalizedCurrency::class, $localized); + $this->assertEquals("Irish Pound", $localized->name); + } + + #[DataProvider('provide_test_format')] + public function test_format(string $currency_code, string $locale_id, float|int $number, string $expected): void + { + $currency = Currency::of($currency_code); + $localized = new LocalizedCurrency($currency, locale_for($locale_id)); + $this->assertEquals($expected, $localized->format($number)); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_format(): array + { + return [ + + [ 'IEP', 'fr', 123456.789, "123 456,79 £IE" ], + [ 'IEP', 'en', 123456.789, "IEP123,456.79" ], + [ 'EUR', 'fr', 123456.789, "123 456,79 €" ], + [ 'EUR', 'en', 123456.789, "€123,456.79" ], + [ 'USD', 'fr', 123456.789, "123 456,79 \$US" ], + [ 'USD', 'en', 123456.789, "\$123,456.79" ], + + ]; + } + + #[DataProvider('provide_test_format_accounting')] + public function test_format_accounting( + string $currency_code, + string $locale_id, + float $number, + string $expected + ): void { + $currency = Currency::of($currency_code); + $localized = new LocalizedCurrency($currency, locale_for($locale_id)); + $actual = $localized->format($number, LocalizedCurrencyFormatter::PATTERN_ACCOUNTING); + + $this->assertStringSame($expected, $actual); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_format_accounting(): array + { + $s1 = Spaces::NARROW_NO_BREAK_SPACE; + $s2 = Spaces::NO_BREAK_SPACE; + + return [ + + [ 'IEP', 'fr', 123456.789, "123 456,79 £IE" ], + [ 'IEP', 'en', 123456.789, "IEP123,456.79" ], + [ 'EUR', 'fr', 123456.789, "123 456,79 €" ], + [ 'EUR', 'en', 123456.789, "€123,456.79" ], + [ 'USD', 'fr', 123456.789, "123{$s1}456,79{$s2}\$US" ], + [ 'USD', 'en', 123456.789, '$123,456.79' ], + + ]; + } } diff --git a/tests/LocalizedDateTimeTest.php b/tests/LocalizedDateTimeTest.php index d33f0c7..6e78746 100644 --- a/tests/LocalizedDateTimeTest.php +++ b/tests/LocalizedDateTimeTest.php @@ -14,99 +14,99 @@ final class LocalizedDateTimeTest extends TestCase { - /** - * @var array - */ - private static array $localized_dates; - - public static function setupBeforeClass(): void - { - $datetime = new DateTime('2013-11-04 20:21:22 UTC'); - - self::$localized_dates['en'] = new LocalizedDateTime($datetime, locale_for('en')); - self::$localized_dates['fr'] = new LocalizedDateTime($datetime, locale_for('fr')); - self::$localized_dates['zh'] = new LocalizedDateTime($datetime, locale_for('zh')); - } - - public function test_get_target(): void - { - $ld = self::$localized_dates['en']; - - $this->assertInstanceOf(DateTime::class, $ld->target); - } - - public function test_get_locale(): void - { - $ld = self::$localized_dates['en']; - - $this->assertInstanceOf(Locale::class, $ld->locale); - } - - public function test_get_formatter(): void - { - $ld = self::$localized_dates['en']; - - $this->assertInstanceOf(DateTimeFormatter::class, $ld->formatter); - } - - #[DataProvider('provide_test_as')] - public function test_as(string $locale, DateTimeFormatLength $as, string $expected): void - { - $property = 'as_' . $as->value; - $method = 'format_' . $property; - - $this->assertEquals($expected, self::$localized_dates[$locale]->$property); - $this->assertEquals($expected, self::$localized_dates[$locale]->$method()); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_as(): array - { - return [ - - [ 'en', DateTimeFormatLength::FULL, "Monday, November 4, 2013 at 8:21:22 PM UTC" ], - [ 'en', DateTimeFormatLength::LONG, "November 4, 2013 at 8:21:22 PM UTC" ], - [ 'en', DateTimeFormatLength::MEDIUM, "Nov 4, 2013, 8:21:22 PM" ], - [ 'en', DateTimeFormatLength::SHORT, "11/4/13, 8:21 PM" ], - - [ 'fr', DateTimeFormatLength::FULL, "lundi 4 novembre 2013 à 20:21:22 UTC" ], - [ 'fr', DateTimeFormatLength::LONG, "4 novembre 2013 à 20:21:22 UTC" ], - [ 'fr', DateTimeFormatLength::MEDIUM, "4 nov. 2013, 20:21:22" ], - [ 'fr', DateTimeFormatLength::SHORT, "04/11/2013 20:21" ], - - [ 'zh', DateTimeFormatLength::FULL, "2013年11月4日星期一 UTC 20:21:22" ], - [ 'zh', DateTimeFormatLength::LONG, "2013年11月4日 UTC 20:21:22" ], - [ 'zh', DateTimeFormatLength::MEDIUM, "2013年11月4日 20:21:22" ], - [ 'zh', DateTimeFormatLength::SHORT, "2013/11/4 20:21" ], - - ]; - } - - #[DataProvider('provide_format_with_id')] - public function test_format_with_id(string $locale, string $id, string $expected): void - { - $actual = self::$localized_dates[$locale]->format(DateTimeFormatId::from($id)); - - $this->assertEquals($expected, $actual); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_format_with_id(): array - { - return [ - - [ 'en', 'yMMMEd', 'Mon, Nov 4, 2013' ], - [ 'en', 'yMEd', 'Mon, 11/4/2013' ], - - ]; - } - - public function test_to_string(): void - { - $this->assertEquals('2013-11-04T20:21:22+00:00', (string)self::$localized_dates['fr']); - } + /** + * @var array + */ + private static array $localized_dates; + + public static function setupBeforeClass(): void + { + $datetime = new DateTime('2013-11-04 20:21:22 UTC'); + + self::$localized_dates['en'] = new LocalizedDateTime($datetime, locale_for('en')); + self::$localized_dates['fr'] = new LocalizedDateTime($datetime, locale_for('fr')); + self::$localized_dates['zh'] = new LocalizedDateTime($datetime, locale_for('zh')); + } + + public function test_get_target(): void + { + $ld = self::$localized_dates['en']; + + $this->assertInstanceOf(DateTime::class, $ld->target); + } + + public function test_get_locale(): void + { + $ld = self::$localized_dates['en']; + + $this->assertInstanceOf(Locale::class, $ld->locale); + } + + public function test_get_formatter(): void + { + $ld = self::$localized_dates['en']; + + $this->assertInstanceOf(DateTimeFormatter::class, $ld->formatter); + } + + #[DataProvider('provide_test_as')] + public function test_as(string $locale, DateTimeFormatLength $as, string $expected): void + { + $property = 'as_' . $as->value; + $method = 'format_' . $property; + + $this->assertEquals($expected, self::$localized_dates[$locale]->$property); + $this->assertEquals($expected, self::$localized_dates[$locale]->$method()); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_as(): array + { + return [ + + [ 'en', DateTimeFormatLength::FULL, "Monday, November 4, 2013 at 8:21:22 PM UTC" ], + [ 'en', DateTimeFormatLength::LONG, "November 4, 2013 at 8:21:22 PM UTC" ], + [ 'en', DateTimeFormatLength::MEDIUM, "Nov 4, 2013, 8:21:22 PM" ], + [ 'en', DateTimeFormatLength::SHORT, "11/4/13, 8:21 PM" ], + + [ 'fr', DateTimeFormatLength::FULL, "lundi 4 novembre 2013 à 20:21:22 UTC" ], + [ 'fr', DateTimeFormatLength::LONG, "4 novembre 2013 à 20:21:22 UTC" ], + [ 'fr', DateTimeFormatLength::MEDIUM, "4 nov. 2013, 20:21:22" ], + [ 'fr', DateTimeFormatLength::SHORT, "04/11/2013 20:21" ], + + [ 'zh', DateTimeFormatLength::FULL, "2013年11月4日星期一 UTC 20:21:22" ], + [ 'zh', DateTimeFormatLength::LONG, "2013年11月4日 UTC 20:21:22" ], + [ 'zh', DateTimeFormatLength::MEDIUM, "2013年11月4日 20:21:22" ], + [ 'zh', DateTimeFormatLength::SHORT, "2013/11/4 20:21" ], + + ]; + } + + #[DataProvider('provide_format_with_id')] + public function test_format_with_id(string $locale, string $id, string $expected): void + { + $actual = self::$localized_dates[$locale]->format(DateTimeFormatId::from($id)); + + $this->assertEquals($expected, $actual); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_format_with_id(): array + { + return [ + + [ 'en', 'yMMMEd', 'Mon, Nov 4, 2013' ], + [ 'en', 'yMEd', 'Mon, 11/4/2013' ], + + ]; + } + + public function test_to_string(): void + { + $this->assertEquals('2013-11-04T20:21:22+00:00', (string)self::$localized_dates['fr']); + } } diff --git a/tests/LocalizedListFormatterTest.php b/tests/LocalizedListFormatterTest.php index 6dfe746..ebb7eaa 100644 --- a/tests/LocalizedListFormatterTest.php +++ b/tests/LocalizedListFormatterTest.php @@ -10,62 +10,62 @@ final class LocalizedListFormatterTest extends TestCase { - /** - * @param array $list - */ - #[DataProvider('provide_test_format')] - public function test_format(array $list, ListType $type, string $locale_id, string $expected): void - { - $locale = locale_for($locale_id); - $this->assertNotNull($locale); - $lp = new LocalizedListFormatter(new ListFormatter(), $locale); - $this->assertSame($expected, $lp->format($list, $type)); - } + /** + * @param array $list + */ + #[DataProvider('provide_test_format')] + public function test_format(array $list, ListType $type, string $locale_id, string $expected): void + { + $locale = locale_for($locale_id); + $this->assertNotNull($locale); + $lp = new LocalizedListFormatter(new ListFormatter(), $locale); + $this->assertSame($expected, $lp->format($list, $type)); + } - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_format(): array - { - $sd = ListType::STANDARD; - $st = ListType::UNIT_SHORT; + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_format(): array + { + $sd = ListType::STANDARD; + $st = ListType::UNIT_SHORT; - return [ + return [ - [ [], $sd, 'en', "" ], - [ [ 'one' ], $sd, 'en', "one" ], - [ [ 'one', 'two' ], $sd, 'en', "one and two" ], - [ [ 'one', 'two', 'three' ], $sd, 'en', "one, two, and three" ], - [ [ 'one', 'two', 'three', 'four' ], $sd, 'en', "one, two, three, and four" ], + [ [], $sd, 'en', "" ], + [ [ 'one' ], $sd, 'en', "one" ], + [ [ 'one', 'two' ], $sd, 'en', "one and two" ], + [ [ 'one', 'two', 'three' ], $sd, 'en', "one, two, and three" ], + [ [ 'one', 'two', 'three', 'four' ], $sd, 'en', "one, two, three, and four" ], - [ [], $st, 'en', "" ], - [ [ 'one' ], $st, 'en', "one" ], - [ [ 'one', 'two' ], $st, 'en', "one, two" ], - [ [ 'one', 'two', 'three' ], $st, 'en', "one, two, three" ], - [ [ 'one', 'two', 'three', 'four' ], $st, 'en', "one, two, three, four" ], + [ [], $st, 'en', "" ], + [ [ 'one' ], $st, 'en', "one" ], + [ [ 'one', 'two' ], $st, 'en', "one, two" ], + [ [ 'one', 'two', 'three' ], $st, 'en', "one, two, three" ], + [ [ 'one', 'two', 'three', 'four' ], $st, 'en', "one, two, three, four" ], - [ [], $sd, 'fr', "" ], - [ [ 'un' ], $sd, 'fr', "un" ], - [ [ 'un', 'deux' ], $sd, 'fr', "un et deux" ], - [ [ 'un', 'deux', 'trois' ], $sd, 'fr', "un, deux et trois" ], - [ [ 'un', 'deux', 'trois', 'quatre' ], $sd, 'fr', "un, deux, trois et quatre" ], + [ [], $sd, 'fr', "" ], + [ [ 'un' ], $sd, 'fr', "un" ], + [ [ 'un', 'deux' ], $sd, 'fr', "un et deux" ], + [ [ 'un', 'deux', 'trois' ], $sd, 'fr', "un, deux et trois" ], + [ [ 'un', 'deux', 'trois', 'quatre' ], $sd, 'fr', "un, deux, trois et quatre" ], - [ [], $sd, 'de', "" ], - [ [ 'eins' ], $sd, 'de', "eins" ], - [ [ 'eins', 'zwei' ], $sd, 'de', "eins und zwei" ], - [ [ 'eins', 'zwei', 'drei' ], $sd, 'de', "eins, zwei und drei" ], - [ [ 'eins', 'zwei', 'drei', 'vier' ], $sd, 'de', "eins, zwei, drei und vier" ], + [ [], $sd, 'de', "" ], + [ [ 'eins' ], $sd, 'de', "eins" ], + [ [ 'eins', 'zwei' ], $sd, 'de', "eins und zwei" ], + [ [ 'eins', 'zwei', 'drei' ], $sd, 'de', "eins, zwei und drei" ], + [ [ 'eins', 'zwei', 'drei', 'vier' ], $sd, 'de', "eins, zwei, drei und vier" ], - [ [ 'January', 'February', 'March' ], ListType::STANDARD, 'en', "January, February, and March" ], - [ [ 'Jan.', 'Feb.', 'Mar.' ], ListType::STANDARD_SHORT, 'en', "Jan., Feb., & Mar." ], - [ [ 'Jan.', 'Feb.', 'Mar.' ], ListType::STANDARD_NARROW, 'en', "Jan., Feb., Mar." ], - [ [ 'January', 'February', 'March' ], ListType::OR, 'en', "January, February, or March" ], - [ [ 'Jan.', 'Feb.', 'Mar.' ], ListType::OR_SHORT, 'en', "Jan., Feb., or Mar." ], - [ [ 'Jan.', 'Feb.', 'Mar.' ], ListType::OR_NARROW, 'en', "Jan., Feb., or Mar." ], - [ [ '3 feet', '7 inches' ], ListType::UNIT, 'en', "3 feet, 7 inches" ], - [ [ '3 ft', '7 in' ], ListType::UNIT_SHORT, 'en', "3 ft, 7 in" ], - [ [ '3\'', '7"' ], ListType::UNIT_NARROW, 'en', "3' 7\"" ], + [ [ 'January', 'February', 'March' ], ListType::STANDARD, 'en', "January, February, and March" ], + [ [ 'Jan.', 'Feb.', 'Mar.' ], ListType::STANDARD_SHORT, 'en', "Jan., Feb., & Mar." ], + [ [ 'Jan.', 'Feb.', 'Mar.' ], ListType::STANDARD_NARROW, 'en', "Jan., Feb., Mar." ], + [ [ 'January', 'February', 'March' ], ListType::OR, 'en', "January, February, or March" ], + [ [ 'Jan.', 'Feb.', 'Mar.' ], ListType::OR_SHORT, 'en', "Jan., Feb., or Mar." ], + [ [ 'Jan.', 'Feb.', 'Mar.' ], ListType::OR_NARROW, 'en', "Jan., Feb., or Mar." ], + [ [ '3 feet', '7 inches' ], ListType::UNIT, 'en', "3 feet, 7 inches" ], + [ [ '3 ft', '7 in' ], ListType::UNIT_SHORT, 'en', "3 ft, 7 in" ], + [ [ '3\'', '7"' ], ListType::UNIT_NARROW, 'en', "3' 7\"" ], - ]; - } + ]; + } } diff --git a/tests/LocalizedLocaleTest.php b/tests/LocalizedLocaleTest.php index b75c0af..e40d0eb 100644 --- a/tests/LocalizedLocaleTest.php +++ b/tests/LocalizedLocaleTest.php @@ -10,37 +10,37 @@ final class LocalizedLocaleTest extends TestCase { - #[DataProvider('provide_test_get_name')] - public function test_get_name(string $locale_id, string $code, string $expected): void - { - $locale = new Locale(get_repository(), LocaleId::of($code)); - $localized = new LocalizedLocale($locale, locale_for($locale_id)); - - $this->assertEquals($expected, $localized->name); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_get_name(): array - { - return [ - - [ 'fr', 'fr', "français" ], - [ 'fr', 'fr-CA', "français canadien" ], - [ 'en', 'fr', "French" ], - [ 'en', 'fr-CA', "Canadian French" ], - [ 'fr', 'nl', "néerlandais" ], - [ 'fr', 'nl-BE', "flamand" ], - - ]; - } - - public function test_localize(): void - { - $locale = new Locale(get_repository(), LocaleId::of('fr')); - $localized = $locale->localized(LocaleId::of('es')); - $this->assertInstanceOf(LocalizedLocale::class, $localized); - $this->assertEquals("francés", $localized->name); - } + #[DataProvider('provide_test_get_name')] + public function test_get_name(string $locale_id, string $code, string $expected): void + { + $locale = new Locale(get_repository(), LocaleId::of($code)); + $localized = new LocalizedLocale($locale, locale_for($locale_id)); + + $this->assertEquals($expected, $localized->name); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_get_name(): array + { + return [ + + [ 'fr', 'fr', "français" ], + [ 'fr', 'fr-CA', "français canadien" ], + [ 'en', 'fr', "French" ], + [ 'en', 'fr-CA', "Canadian French" ], + [ 'fr', 'nl', "néerlandais" ], + [ 'fr', 'nl-BE', "flamand" ], + + ]; + } + + public function test_localize(): void + { + $locale = new Locale(get_repository(), LocaleId::of('fr')); + $localized = $locale->localized(LocaleId::of('es')); + $this->assertInstanceOf(LocalizedLocale::class, $localized); + $this->assertEquals("francés", $localized->name); + } } diff --git a/tests/LocalizedNumberFormatterTest.php b/tests/LocalizedNumberFormatterTest.php index e01e14c..11e6e6b 100644 --- a/tests/LocalizedNumberFormatterTest.php +++ b/tests/LocalizedNumberFormatterTest.php @@ -9,34 +9,34 @@ final class LocalizedNumberFormatterTest extends TestCase { - #[DataProvider('provide_test_format')] - public function test_format(string $locale_id, float|int $number, ?string $pattern, string $expected): void - { - $formatter = new NumberFormatter(); - $localized = new LocalizedNumberFormatter($formatter, locale_for($locale_id)); + #[DataProvider('provide_test_format')] + public function test_format(string $locale_id, float|int $number, ?string $pattern, string $expected): void + { + $formatter = new NumberFormatter(); + $localized = new LocalizedNumberFormatter($formatter, locale_for($locale_id)); - $this->assertSame($expected, $localized->format($number, $pattern)); - } + $this->assertSame($expected, $localized->format($number, $pattern)); + } - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_format(): array - { - return [ + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_format(): array + { + return [ - [ 'en', 123, '#', "123" ], - [ 'en', -123, '#', "-123" ], - [ 'en', 123, '#;-#', "123" ], - [ 'en', -123, '#;-#', "-123" ], - [ 'en', 4123.37, '#,#00.#0', "4,123.37" ], - [ 'fr', 4123.37, '#,#00.#0', "4 123,37" ], - [ 'fr', -4123.37, '#,#00.#0', "-4 123,37" ], - [ 'en', .3789, '#0.#0 %', "37.89 %" ], - [ 'fr', .3789, '#0.#0 %', "37,89 %" ], - [ 'fr', 123456.78, null, "123 456,78" ], - [ 'en', 123456.78, null, "123,456.78" ] + [ 'en', 123, '#', "123" ], + [ 'en', -123, '#', "-123" ], + [ 'en', 123, '#;-#', "123" ], + [ 'en', -123, '#;-#', "-123" ], + [ 'en', 4123.37, '#,#00.#0', "4,123.37" ], + [ 'fr', 4123.37, '#,#00.#0', "4 123,37" ], + [ 'fr', -4123.37, '#,#00.#0', "-4 123,37" ], + [ 'en', .3789, '#0.#0 %', "37.89 %" ], + [ 'fr', .3789, '#0.#0 %', "37,89 %" ], + [ 'fr', 123456.78, null, "123 456,78" ], + [ 'en', 123456.78, null, "123,456.78" ] - ]; - } + ]; + } } diff --git a/tests/LocalizedTerritoryTest.php b/tests/LocalizedTerritoryTest.php index 55e12d1..59d495f 100644 --- a/tests/LocalizedTerritoryTest.php +++ b/tests/LocalizedTerritoryTest.php @@ -10,26 +10,26 @@ final class LocalizedTerritoryTest extends TestCase { - #[DataProvider('provide_test_get_name')] - public function test_get_name(string $locale_id, string $territory_code, string $expected): void - { - $territory = new Territory(get_repository(), TerritoryCode::of($territory_code)); - $localized = new LocalizedTerritory($territory, locale_for($locale_id)); + #[DataProvider('provide_test_get_name')] + public function test_get_name(string $locale_id, string $territory_code, string $expected): void + { + $territory = new Territory(get_repository(), TerritoryCode::of($territory_code)); + $localized = new LocalizedTerritory($territory, locale_for($locale_id)); - $this->assertEquals($expected, $localized->name); - } + $this->assertEquals($expected, $localized->name); + } - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_get_name(): array - { - return [ + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_get_name(): array + { + return [ - [ 'fr', 'AC', "Île de l’Ascension" ], - [ 'en', 'AC', "Ascension Island" ], - [ 'ja', 'AC', "アセンション島" ], + [ 'fr', 'AC', "Île de l’Ascension" ], + [ 'en', 'AC', "Ascension Island" ], + [ 'ja', 'AC', "アセンション島" ], - ]; - } + ]; + } } diff --git a/tests/NumberFormatterTest.php b/tests/NumberFormatterTest.php index e4ece1c..6460341 100644 --- a/tests/NumberFormatterTest.php +++ b/tests/NumberFormatterTest.php @@ -8,31 +8,31 @@ final class NumberFormatterTest extends TestCase { - #[DataProvider('provide_test_format')] - public function test_format(string $locale_id, float|int $number, string $pattern, string $expected): void - { - $formatter = new NumberFormatter(); - $symbols = locale_for($locale_id)->numbers->symbols; - $this->assertSame($expected, $formatter->format($number, $pattern, $symbols)); - } + #[DataProvider('provide_test_format')] + public function test_format(string $locale_id, float|int $number, string $pattern, string $expected): void + { + $formatter = new NumberFormatter(); + $symbols = locale_for($locale_id)->numbers->symbols; + $this->assertSame($expected, $formatter->format($number, $pattern, $symbols)); + } - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_format(): array - { - return [ + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_format(): array + { + return [ - [ 'en', 123, '#', "123" ], - [ 'en', -123, '#', "-123" ], - [ 'en', 123, '#;-#', "123" ], - [ 'en', -123, '#;-#', "-123" ], - [ 'en', 4123.37, '#,#00.#0', "4,123.37" ], - [ 'fr', 4123.37, '#,#00.#0', "4 123,37" ], - [ 'fr', -4123.37, '#,#00.#0', "-4 123,37" ], - [ 'en', .3789, '#0.#0 %', "37.89 %" ], - [ 'fr', .3789, '#0.#0 %', "37,89 %" ], + [ 'en', 123, '#', "123" ], + [ 'en', -123, '#', "-123" ], + [ 'en', 123, '#;-#', "123" ], + [ 'en', -123, '#;-#', "-123" ], + [ 'en', 4123.37, '#,#00.#0', "4,123.37" ], + [ 'fr', 4123.37, '#,#00.#0', "4 123,37" ], + [ 'fr', -4123.37, '#,#00.#0', "-4 123,37" ], + [ 'en', .3789, '#0.#0 %', "37.89 %" ], + [ 'fr', .3789, '#0.#0 %', "37,89 %" ], - ]; - } + ]; + } } diff --git a/tests/NumberPatternTest.php b/tests/NumberPatternTest.php index aeb5744..9ae0365 100644 --- a/tests/NumberPatternTest.php +++ b/tests/NumberPatternTest.php @@ -8,125 +8,146 @@ final class NumberPatternTest extends TestCase { - #[DataProvider('provide_test_properties')] - public function test_properties(string $pattern, array $properties): void - { - $instance = NumberPattern::from($pattern); - - $this->assertSame($pattern, (string) $instance); - - foreach ($properties as $property => $value) - { - $this->assertSame($value, $instance->$property); - } - } - - public static function provide_test_properties(): array - { - $default = [ - - 'positive_prefix' => '', - 'positive_suffix' => '', - 'negative_prefix' => '', - 'negative_suffix' => '', - 'multiplier' => 1, - 'decimal_digits' => 0, - 'max_decimal_digits' => 0, - 'integer_digits' => 0, - 'group_size1' => 0, - 'group_size2' => 0 - - ]; - - return [ - - [ "#,##0.###", array_merge($default, [ - - 'negative_prefix' => "-", - 'max_decimal_digits' => 3, - 'integer_digits' => 1, - 'group_size1' => 3 - - ]) ], - - [ "#,##0.00 ¤;(#,##0.00 ¤)", array_merge($default, [ - - 'positive_suffix' => " ¤", - 'negative_prefix' => "(", - 'negative_suffix' => " ¤)", - 'decimal_digits' => 2, - 'max_decimal_digits' => 2, - 'integer_digits' => 1, - 'group_size1' => 3 - - ]) ], - - [ "#,##0.##", array_merge($default, [ - - 'negative_prefix' => "-", - 'max_decimal_digits' => 2, - 'integer_digits' => 1, - 'group_size1' => 3 - - ]) ], - - [ "#,##0 %", array_merge($default, [ - - 'positive_suffix' => ' %', - 'negative_prefix' => '-', - 'negative_suffix' => ' %', - 'multiplier' => 100, - 'integer_digits' => 1, - 'group_size1' => 3 - - ]) ], - - [ "#,##0.### %", array_merge($default, [ - - 'positive_suffix' => " %", - 'negative_prefix' => "-", - 'negative_suffix' => " %", - 'multiplier' => 100, - 'max_decimal_digits' => 3, - 'integer_digits' => 1, - 'group_size1' => 3 - - ]) ], - - [ "#,##0.### ‰", array_merge($default, [ - - 'positive_suffix' => " ‰", - 'negative_prefix' => "-", - 'negative_suffix' => " ‰", - 'multiplier' => 1000, - 'max_decimal_digits' => 3, - 'integer_digits' => 1, - 'group_size1' => 3 - - ]) ] - - ]; - } - - /** - * @dataProvider provide_test_format_integer_with_decimal - */ - public function test_format_integer_with_decimal(string $pattern, int $integer, string $decimal, string $expected): void - { - $sut = NumberPattern::from($pattern); - $actual = $sut->format_integer_with_decimal($integer, $decimal, '/'); - $this->assertSame($expected, $actual); - } - - public static function provide_test_format_integer_with_decimal(): array - { - return [ - - [ "#,##0.###", 1, "0", '1'], - [ "#,##0.##0", 1, "3", '1/300'], - [ "#,##0.##0", 1, "345", '1/345'], - [ "#,##0.##0", 1, "34567", '1/34567'], - - ]; - } + #[DataProvider('provide_test_properties')] + public function test_properties(string $pattern, array $properties): void + { + $instance = NumberPattern::from($pattern); + + $this->assertSame($pattern, (string)$instance); + + foreach ($properties as $property => $value) { + $this->assertSame($value, $instance->$property); + } + } + + public static function provide_test_properties(): array + { + $default = [ + + 'positive_prefix' => '', + 'positive_suffix' => '', + 'negative_prefix' => '', + 'negative_suffix' => '', + 'multiplier' => 1, + 'decimal_digits' => 0, + 'max_decimal_digits' => 0, + 'integer_digits' => 0, + 'group_size1' => 0, + 'group_size2' => 0 + + ]; + + return [ + + [ + "#,##0.###", + array_merge($default, [ + + 'negative_prefix' => "-", + 'max_decimal_digits' => 3, + 'integer_digits' => 1, + 'group_size1' => 3 + + ]) + ], + + [ + "#,##0.00 ¤;(#,##0.00 ¤)", + array_merge($default, [ + + 'positive_suffix' => " ¤", + 'negative_prefix' => "(", + 'negative_suffix' => " ¤)", + 'decimal_digits' => 2, + 'max_decimal_digits' => 2, + 'integer_digits' => 1, + 'group_size1' => 3 + + ]) + ], + + [ + "#,##0.##", + array_merge($default, [ + + 'negative_prefix' => "-", + 'max_decimal_digits' => 2, + 'integer_digits' => 1, + 'group_size1' => 3 + + ]) + ], + + [ + "#,##0 %", + array_merge($default, [ + + 'positive_suffix' => ' %', + 'negative_prefix' => '-', + 'negative_suffix' => ' %', + 'multiplier' => 100, + 'integer_digits' => 1, + 'group_size1' => 3 + + ]) + ], + + [ + "#,##0.### %", + array_merge($default, [ + + 'positive_suffix' => " %", + 'negative_prefix' => "-", + 'negative_suffix' => " %", + 'multiplier' => 100, + 'max_decimal_digits' => 3, + 'integer_digits' => 1, + 'group_size1' => 3 + + ]) + ], + + [ + "#,##0.### ‰", + array_merge($default, [ + + 'positive_suffix' => " ‰", + 'negative_prefix' => "-", + 'negative_suffix' => " ‰", + 'multiplier' => 1000, + 'max_decimal_digits' => 3, + 'integer_digits' => 1, + 'group_size1' => 3 + + ]) + ] + + ]; + } + + /** + * @dataProvider provide_test_format_integer_with_decimal + */ + public function test_format_integer_with_decimal( + string $pattern, + int $integer, + string $decimal, + string $expected + ): void { + $sut = NumberPattern::from($pattern); + $actual = $sut->format_integer_with_decimal($integer, $decimal, '/'); + $this->assertSame($expected, $actual); + } + + public static function provide_test_format_integer_with_decimal(): array + { + return [ + + [ "#,##0.###", 1, "0", '1' ], + [ "#,##0.##0", 1, "3", '1/300' ], + [ "#,##0.##0", 1, "345", '1/345' ], + [ "#,##0.##0", 1, "34567", '1/34567' ], + + ]; + } } diff --git a/tests/NumberTest.php b/tests/NumberTest.php index 07aee9d..239ef40 100644 --- a/tests/NumberTest.php +++ b/tests/NumberTest.php @@ -8,59 +8,59 @@ final class NumberTest extends TestCase { - public function test_should_return_correct_precision(): void - { - $this->assertEquals(3, Number::precision_from(12.123)); - } + public function test_should_return_correct_precision(): void + { + $this->assertEquals(3, Number::precision_from(12.123)); + } - public function test_should_return_zero_precision_if_the_number_is_not_a_decimal(): void - { - $this->assertEquals(0, Number::precision_from(12)); - } + public function test_should_return_zero_precision_if_the_number_is_not_a_decimal(): void + { + $this->assertEquals(0, Number::precision_from(12)); + } - /** - * should round a number to the given precision - */ - #[DataProvider('provide_test_round_to')] - public function test_round_to(float|int $number, int $precision, float|int $expected): void - { - $this->assertEquals($expected, Number::round_to($number, $precision)); - } + /** + * should round a number to the given precision + */ + #[DataProvider('provide_test_round_to')] + public function test_round_to(float|int $number, int $precision, float|int $expected): void + { + $this->assertEquals($expected, Number::round_to($number, $precision)); + } - public static function provide_test_round_to(): array - { - return [ + public static function provide_test_round_to(): array + { + return [ - [ 12, 0, 12 ], - [ 12.2, 0, 12 ], - [ 12.5, 0, 13 ], - [ 12.25, 1, 12.3 ], - [ 12.25, 2, 12.25 ], - [ 12.25, 3, 12.25 ], + [ 12, 0, 12 ], + [ 12.2, 0, 12 ], + [ 12.5, 0, 13 ], + [ 12.25, 1, 12.3 ], + [ 12.25, 2, 12.25 ], + [ 12.25, 3, 12.25 ], - ]; - } + ]; + } - /** - * should round and split the given number by decimal - */ - #[DataProvider('provide_test_parse')] - public function test_parse(float|int $number, int $precision, array $expected): void - { - $this->assertSame($expected, Number::parse($number, $precision)); - } + /** + * should round and split the given number by decimal + */ + #[DataProvider('provide_test_parse')] + public function test_parse(float|int $number, int $precision, array $expected): void + { + $this->assertSame($expected, Number::parse($number, $precision)); + } - public static function provide_test_parse(): array - { - return [ + public static function provide_test_parse(): array + { + return [ - [ 12, 0, [ 12, null ] ], - [ 12.2, 0, [ 12, null ] ], - [ 12.5, 0, [ 13, null ] ], - [ 12.25, 1, [ 12, '3' ] ], - [ 12.25, 2, [ 12, '25' ] ], - [ 12.25, 3, [ 12, '250' ] ], + [ 12, 0, [ 12, null ] ], + [ 12.2, 0, [ 12, null ] ], + [ 12.5, 0, [ 13, null ] ], + [ 12.25, 1, [ 12, '3' ] ], + [ 12.25, 2, [ 12, '25' ] ], + [ 12.25, 3, [ 12, '250' ] ], - ]; - } + ]; + } } diff --git a/tests/NumbersTest.php b/tests/NumbersTest.php index 0e2f8ce..0fc2f53 100644 --- a/tests/NumbersTest.php +++ b/tests/NumbersTest.php @@ -9,164 +9,164 @@ final class NumbersTest extends TestCase { - #[DataProvider('provide_test_shortcuts')] - public function test_shortcuts(string $locale_id, string $property, string $offset): void - { - $locale = locale_for($locale_id); - /** @var array $numbers_data */ - $numbers_data = $locale['numbers']; - $numbers = new Numbers($locale, $numbers_data); - - $this->assertSame($numbers_data[$offset], $numbers->$property); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_shortcuts(): array - { - return [ - - [ 'fr', 'decimal_formats', 'decimalFormats-numberSystem-latn' ], - [ 'fr', 'scientific_formats', 'scientificFormats-numberSystem-latn' ], - [ 'fr', 'percent_formats', 'percentFormats-numberSystem-latn' ], - [ 'fr', 'currency_formats', 'currencyFormats-numberSystem-latn' ], - [ 'fr', 'misc_patterns', 'miscPatterns-numberSystem-latn' ] - - ]; - } - - #[DataProvider('provide_symbols')] - public function test_symbols(string $locale_id, Symbols $expected): void - { - $locale = locale_for($locale_id); - /** @var array $numbers_data */ - $numbers_data = $locale['numbers']; - $numbers = new Numbers($locale, $numbers_data); - - $this->assertEquals($expected, $numbers->symbols); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_symbols(): array - { - return [ - - [ - 'fr', - new Symbols( - ',', - ' ', - ';', - '%', - '-', - '+', - '≃', - 'E', - '×', - '‰', - '∞', - 'NaN', - '.', - ',', - ':' - ) - ], - [ - 'en', - new Symbols( - '.', - ',', - ';', - '%', - '-', - '+', - '~', - 'E', - '×', - '‰', - '∞', - 'NaN', - '.', - ',', - ':' - ) - ], - [ - 'ru', - new Symbols( - ',', - ' ', - ';', - '%', - '-', - '+', - '≈', - 'E', - '×', - '‰', - '∞', - 'не число', - '.', - ',', - ':' - ) - ], - ]; - } - - #[DataProvider('provide_test_decimal_width_shortcuts')] - public function test_decimal_width_shortcuts( - string $locale_id, - string $property, - string $offset, - string $width_offset - ): void { - $locale = locale_for($locale_id); - /** @var array $numbers_data */ - $numbers_data = $locale['numbers']; - $numbers = new Numbers($locale, $numbers_data); - - $this->assertSame($numbers_data[$offset][$width_offset]['decimalFormat'], $numbers->$property); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_decimal_width_shortcuts(): array - { - return [ - - [ 'fr', 'short_decimal_formats', 'decimalFormats-numberSystem-latn', 'short' ], - [ 'fr', 'long_decimal_formats', 'decimalFormats-numberSystem-latn', 'long' ] - - ]; - } - - #[DataProvider('provide_test_get_decimal_format')] - public function test_get_decimal_format(string $locale_id, string $expected): void - { - $locale = locale_for($locale_id); - /** @var array $numbers_data */ - $numbers_data = $locale['numbers']; - $numbers = new Numbers($locale, $numbers_data); - - $this->assertEquals($expected, $numbers->decimal_format); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_get_decimal_format(): array - { - return [ - - [ 'en', "#,##0.###" ], - [ 'fr', "#,##0.###" ], - [ 'ja', "#,##0.###" ] - - ]; - } + #[DataProvider('provide_test_shortcuts')] + public function test_shortcuts(string $locale_id, string $property, string $offset): void + { + $locale = locale_for($locale_id); + /** @var array $numbers_data */ + $numbers_data = $locale['numbers']; + $numbers = new Numbers($locale, $numbers_data); + + $this->assertSame($numbers_data[$offset], $numbers->$property); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_shortcuts(): array + { + return [ + + [ 'fr', 'decimal_formats', 'decimalFormats-numberSystem-latn' ], + [ 'fr', 'scientific_formats', 'scientificFormats-numberSystem-latn' ], + [ 'fr', 'percent_formats', 'percentFormats-numberSystem-latn' ], + [ 'fr', 'currency_formats', 'currencyFormats-numberSystem-latn' ], + [ 'fr', 'misc_patterns', 'miscPatterns-numberSystem-latn' ] + + ]; + } + + #[DataProvider('provide_symbols')] + public function test_symbols(string $locale_id, Symbols $expected): void + { + $locale = locale_for($locale_id); + /** @var array $numbers_data */ + $numbers_data = $locale['numbers']; + $numbers = new Numbers($locale, $numbers_data); + + $this->assertEquals($expected, $numbers->symbols); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_symbols(): array + { + return [ + + [ + 'fr', + new Symbols( + ',', + ' ', + ';', + '%', + '-', + '+', + '≃', + 'E', + '×', + '‰', + '∞', + 'NaN', + '.', + ',', + ':' + ) + ], + [ + 'en', + new Symbols( + '.', + ',', + ';', + '%', + '-', + '+', + '~', + 'E', + '×', + '‰', + '∞', + 'NaN', + '.', + ',', + ':' + ) + ], + [ + 'ru', + new Symbols( + ',', + ' ', + ';', + '%', + '-', + '+', + '≈', + 'E', + '×', + '‰', + '∞', + 'не число', + '.', + ',', + ':' + ) + ], + ]; + } + + #[DataProvider('provide_test_decimal_width_shortcuts')] + public function test_decimal_width_shortcuts( + string $locale_id, + string $property, + string $offset, + string $width_offset + ): void { + $locale = locale_for($locale_id); + /** @var array $numbers_data */ + $numbers_data = $locale['numbers']; + $numbers = new Numbers($locale, $numbers_data); + + $this->assertSame($numbers_data[$offset][$width_offset]['decimalFormat'], $numbers->$property); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_decimal_width_shortcuts(): array + { + return [ + + [ 'fr', 'short_decimal_formats', 'decimalFormats-numberSystem-latn', 'short' ], + [ 'fr', 'long_decimal_formats', 'decimalFormats-numberSystem-latn', 'long' ] + + ]; + } + + #[DataProvider('provide_test_get_decimal_format')] + public function test_get_decimal_format(string $locale_id, string $expected): void + { + $locale = locale_for($locale_id); + /** @var array $numbers_data */ + $numbers_data = $locale['numbers']; + $numbers = new Numbers($locale, $numbers_data); + + $this->assertEquals($expected, $numbers->decimal_format); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_get_decimal_format(): array + { + return [ + + [ 'en', "#,##0.###" ], + [ 'fr', "#,##0.###" ], + [ 'ja', "#,##0.###" ] + + ]; + } } diff --git a/tests/Plurals/OperandsTest.php b/tests/Plurals/OperandsTest.php index 382f258..3ab1b2d 100644 --- a/tests/Plurals/OperandsTest.php +++ b/tests/Plurals/OperandsTest.php @@ -11,47 +11,47 @@ */ final class OperandsTest extends TestCase { - /** - * @param numeric-string $number - * @param int[] $expected - * - * @return void - */ - #[DataProvider('provide_test_cases')] - public function test_cases(string $number, array $expected): void - { - $expected = array_combine([ 'n', 'i', 'v', 'w', 'f', 't', 'e' ], $expected); - $operands = Operands::from($number); - - $this->assertSame($expected, $operands->to_array()); - - foreach ($expected as $property => $value) { - $this->assertSame($value, $operands->$property); - } - } - - /** - * @see https://www.unicode.org/reports/tr35/tr35-72/tr35-numbers.html#table-plural-operand-examples - */ - public static function provide_test_cases(): array - { - return [ - - [ '1', [ 1, 1, 0, 0, 0, 0, 0 ] ], - [ '1.0', [ 1, 1, 1, 0, 0, 0, 0 ] ], - [ '1.00', [ 1, 1, 2, 0, 0, 0, 0 ] ], - [ '1.3', [ 1.3, 1, 1, 1, 3, 3, 0 ] ], - [ '1.30', [ 1.3, 1, 2, 1, 30, 3, 0 ] ], - [ '1.03', [ 1.03, 1, 2, 2, 3, 3, 0 ] ], - [ '1.230', [ 1.23, 1, 3, 2, 230, 23, 0 ] ], - [ '-2.350', [ 2.350, 2, 3, 2, 350, 35, 0 ] ], - [ '1200000', [ 1200000, 1200000, 0, 0, 0, 0, 0 ] ], - [ '1.2c6', [ 1200000, 1200000, 0, 0, 0, 0, 6 ] ], - [ '123c6', [ 123000000, 123000000, 0, 0, 0, 0, 6 ] ], - [ '123c5', [ 12300000, 12300000, 0, 0, 0, 0, 5 ] ], - [ '1200.50', [ 1200.5, 1200, 2, 1, 50, 5, 0 ] ], - [ '1.20050c3', [ 1200.5, 1200, 2, 1, 50, 5, 3 ] ], - - ]; - } + /** + * @param numeric-string $number + * @param int[] $expected + * + * @return void + */ + #[DataProvider('provide_test_cases')] + public function test_cases(string $number, array $expected): void + { + $expected = array_combine([ 'n', 'i', 'v', 'w', 'f', 't', 'e' ], $expected); + $operands = Operands::from($number); + + $this->assertSame($expected, $operands->to_array()); + + foreach ($expected as $property => $value) { + $this->assertSame($value, $operands->$property); + } + } + + /** + * @see https://www.unicode.org/reports/tr35/tr35-72/tr35-numbers.html#table-plural-operand-examples + */ + public static function provide_test_cases(): array + { + return [ + + [ '1', [ 1, 1, 0, 0, 0, 0, 0 ] ], + [ '1.0', [ 1, 1, 1, 0, 0, 0, 0 ] ], + [ '1.00', [ 1, 1, 2, 0, 0, 0, 0 ] ], + [ '1.3', [ 1.3, 1, 1, 1, 3, 3, 0 ] ], + [ '1.30', [ 1.3, 1, 2, 1, 30, 3, 0 ] ], + [ '1.03', [ 1.03, 1, 2, 2, 3, 3, 0 ] ], + [ '1.230', [ 1.23, 1, 3, 2, 230, 23, 0 ] ], + [ '-2.350', [ 2.350, 2, 3, 2, 350, 35, 0 ] ], + [ '1200000', [ 1200000, 1200000, 0, 0, 0, 0, 0 ] ], + [ '1.2c6', [ 1200000, 1200000, 0, 0, 0, 0, 6 ] ], + [ '123c6', [ 123000000, 123000000, 0, 0, 0, 0, 6 ] ], + [ '123c5', [ 12300000, 12300000, 0, 0, 0, 0, 5 ] ], + [ '1200.50', [ 1200.5, 1200, 2, 1, 50, 5, 0 ] ], + [ '1.20050c3', [ 1200.5, 1200, 2, 1, 50, 5, 3 ] ], + + ]; + } } diff --git a/tests/Plurals/RelationTest.php b/tests/Plurals/RelationTest.php index 0c6a7e7..7c5ecb0 100644 --- a/tests/Plurals/RelationTest.php +++ b/tests/Plurals/RelationTest.php @@ -11,45 +11,45 @@ */ final class RelationTest extends TestCase { - /** - * @dataProvider provide_test_cases - * - * @param float|int $number - */ - public function test_cases(string $relation, float|int $number, bool $expected): void - { - $operands = Operands::from($number); - - $this->assertSame($expected, Relation::from($relation)->evaluate($operands)); - } - - public static function provide_test_cases(): array - { - return [ - - [ "", 1, true ], - [ "n = 1", 1, true ], - [ "n = 1", 0, false ], - [ "n != 1", 0, true ], - [ "n != 1", 1, false ], - - [ "n = 1,2,3", 1, true ], - [ "n = 1,2,3", 2, true ], - [ "n = 1,2,3", 3, true ], - [ "n = 1,2,3", 4, false ], - - [ "n != 1,2,3", 1, false ], - [ "n != 1,2,3", 2, false ], - [ "n != 1,2,3", 3, false ], - [ "n != 1,2,3", 4, true ], - - [ "n = 2..4,15", 3.5, false ], - [ "n = 2..4,15", 3, true ], - [ "n != 2..4,15", 3.5, true ], - [ "n != 2..4,15", 3, false ], - - [ "n % 3 = 1.3", 4.3, true ] - - ]; - } + /** + * @dataProvider provide_test_cases + * + * @param float|int $number + */ + public function test_cases(string $relation, float|int $number, bool $expected): void + { + $operands = Operands::from($number); + + $this->assertSame($expected, Relation::from($relation)->evaluate($operands)); + } + + public static function provide_test_cases(): array + { + return [ + + [ "", 1, true ], + [ "n = 1", 1, true ], + [ "n = 1", 0, false ], + [ "n != 1", 0, true ], + [ "n != 1", 1, false ], + + [ "n = 1,2,3", 1, true ], + [ "n = 1,2,3", 2, true ], + [ "n = 1,2,3", 3, true ], + [ "n = 1,2,3", 4, false ], + + [ "n != 1,2,3", 1, false ], + [ "n != 1,2,3", 2, false ], + [ "n != 1,2,3", 3, false ], + [ "n != 1,2,3", 4, true ], + + [ "n = 2..4,15", 3.5, false ], + [ "n = 2..4,15", 3, true ], + [ "n != 2..4,15", 3.5, true ], + [ "n != 2..4,15", 3, false ], + + [ "n % 3 = 1.3", 4.3, true ] + + ]; + } } diff --git a/tests/Plurals/RuleTest.php b/tests/Plurals/RuleTest.php index 84d9cc5..a8bc3b0 100644 --- a/tests/Plurals/RuleTest.php +++ b/tests/Plurals/RuleTest.php @@ -11,75 +11,75 @@ */ final class RuleTest extends TestCase { - /** - * @param float|int|numeric-string $number $number - */ - #[DataProvider('provide_test_cases')] - public function test_cases(string $rule, float|int|string $number, bool $expected): void - { - $this->assertSame($expected, Rule::from($rule)->validate($number)); - } + /** + * @param float|int|numeric-string $number $number + */ + #[DataProvider('provide_test_cases')] + public function test_cases(string $rule, float|int|string $number, bool $expected): void + { + $this->assertSame($expected, Rule::from($rule)->validate($number)); + } - /** - * @link https://github.com/unicode-org/cldr-json/blob/45.0.0/cldr-json/cldr-core/supplemental/plurals.json - * - * @phpstan-ignore-next-line - */ - public static function provide_test_cases(): array - { - $r1 = "n = 0 or n != 1 and n % 100 = 1..19"; - $r2 = "n % 10 = 2..4 and n % 100 != 12..14"; - $r3 = "n % 10 = 3..4,9 and n % 100 != 10..19,70..79,90..99"; - $r4 = "n % 10 = 2..4 and n % 100 != 12..14"; - $r5 = "i = 2..4 and v = 0"; - $r6 = "v = 0 and i % 100 = 3..4 or f % 100 = 3..4"; - $r7 = "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14"; + /** + * @link https://github.com/unicode-org/cldr-json/blob/45.0.0/cldr-json/cldr-core/supplemental/plurals.json + * + * @phpstan-ignore-next-line + */ + public static function provide_test_cases(): array + { + $r1 = "n = 0 or n != 1 and n % 100 = 1..19"; + $r2 = "n % 10 = 2..4 and n % 100 != 12..14"; + $r3 = "n % 10 = 3..4,9 and n % 100 != 10..19,70..79,90..99"; + $r4 = "n % 10 = 2..4 and n % 100 != 12..14"; + $r5 = "i = 2..4 and v = 0"; + $r6 = "v = 0 and i % 100 = 3..4 or f % 100 = 3..4"; + $r7 = "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14"; - return [ + return [ - [ $r1, 0, true ], - [ $r1, 119, true ], - [ $r1, 219, true ], - [ $r1, 319, true ], - [ $r1, 1, false ], + [ $r1, 0, true ], + [ $r1, 119, true ], + [ $r1, 219, true ], + [ $r1, 319, true ], + [ $r1, 1, false ], - [ $r2, 12, false ], - [ $r2, 113, false ], - [ $r2, 214, false ], + [ $r2, 12, false ], + [ $r2, 113, false ], + [ $r2, 214, false ], - [ $r3, 3, true ], - [ $r3, 29, true ], - [ $r3, 1003.0, true ], - [ $r3, "1003.0", true ], - [ $r3, 8, false ], + [ $r3, 3, true ], + [ $r3, 29, true ], + [ $r3, 1003.0, true ], + [ $r3, "1003.0", true ], + [ $r3, 8, false ], - [ $r4, 2, true ], - [ $r4, 11, false ], - [ $r4, 12, false ], - [ $r4, 214, false ], + [ $r4, 2, true ], + [ $r4, 11, false ], + [ $r4, 12, false ], + [ $r4, 214, false ], - [ $r5, 2, true ], - [ $r5, 4, true ], - [ $r5, 5, false ], + [ $r5, 2, true ], + [ $r5, 4, true ], + [ $r5, 5, false ], - [ $r6, 3, true ], - [ $r6, 604, true ], - [ $r6, 5.4, true ], + [ $r6, 3, true ], + [ $r6, 604, true ], + [ $r6, 5.4, true ], - [ $r7, 22, true ], - [ $r7, 1002, true ], - [ $r7, 3.2, true ], - [ $r7, 1000.2, true ], - [ $r7, "1000.2", true ], + [ $r7, 22, true ], + [ $r7, 1002, true ], + [ $r7, 3.2, true ], + [ $r7, 1000.2, true ], + [ $r7, "1000.2", true ], - [ "e = 0", "1.0000001c6", false ], - [ "e = 0 or e != 0..5", "1.0000001c6", true ], - [ $r8 = "e = 0 and i != 0 and i % 1000000 = 0 and v = 0 or e != 0..5", "1.0000001c6", true ], - [ $r8, "1.1c6", true ], - [ $r8, "1c6", true ], - [ $r8, 1000000, true ], - [ $r8, 1000000.1, false ], + [ "e = 0", "1.0000001c6", false ], + [ "e = 0 or e != 0..5", "1.0000001c6", true ], + [ $r8 = "e = 0 and i != 0 and i % 1000000 = 0 and v = 0 or e != 0..5", "1.0000001c6", true ], + [ $r8, "1.1c6", true ], + [ $r8, "1c6", true ], + [ $r8, 1000000, true ], + [ $r8, 1000000.1, false ], - ]; - } + ]; + } } diff --git a/tests/Plurals/SamplesTest.php b/tests/Plurals/SamplesTest.php index 5444e94..0fcf9fd 100644 --- a/tests/Plurals/SamplesTest.php +++ b/tests/Plurals/SamplesTest.php @@ -14,38 +14,44 @@ */ final class SamplesTest extends TestCase { - /** - * @param array $expected - */ - #[DataProvider('provide_test_samples')] - public function test_samples(string $rules, array $expected): void - { - $samples = Samples::from($rules); - $expected = array_map('strval', $expected); // Samples are strings. - - $this->assertSame($expected, iterator_to_array($samples)); - } - - public static function provide_test_samples(): array - { - return [ - - [ " @integer 2, 10", [ 2, 10 ] ], - [ " @integer 2~10", range(2, 10) ], - [ " @decimal 1.0~1.5", range(1, 1.5, .1) ], - [ " @decimal 0.0~0.9", range(0, 0.9, .1) ], - [ " @integer 2~10, 100, … @decimal 1.0~1.5, 3.5, …", array_merge(range(2, 10), [ 100 ], range(1, 1.5, .1), [ 3.5 ]) ], - [ " @integer 1000000, 1c6, 2c6, 3c6, 4c6, …", [ '1000000', '1c6', '2c6', '3c6', '4c6' ] ], - [ " @decimal 1.0000001c6, 1.1c6, 2.0000001c6, 2.1c6, 3.0000001c6, 3.1c6, …", [ '1.0000001c6', '1.1c6', '2.0000001c6', '2.1c6', '3.0000001c6', '3.1c6' ] ], - - ]; - } - - public function test_same_rules_should_return_same_instance(): void - { - $rules = " @integer 2, 10"; - $samples = Samples::from($rules); - - $this->assertSame($samples, Samples::from($rules)); - } + /** + * @param array $expected + */ + #[DataProvider('provide_test_samples')] + public function test_samples(string $rules, array $expected): void + { + $samples = Samples::from($rules); + $expected = array_map('strval', $expected); // Samples are strings. + + $this->assertSame($expected, iterator_to_array($samples)); + } + + public static function provide_test_samples(): array + { + return [ + + [ " @integer 2, 10", [ 2, 10 ] ], + [ " @integer 2~10", range(2, 10) ], + [ " @decimal 1.0~1.5", range(1, 1.5, .1) ], + [ " @decimal 0.0~0.9", range(0, 0.9, .1) ], + [ + " @integer 2~10, 100, … @decimal 1.0~1.5, 3.5, …", + array_merge(range(2, 10), [ 100 ], range(1, 1.5, .1), [ 3.5 ]) + ], + [ " @integer 1000000, 1c6, 2c6, 3c6, 4c6, …", [ '1000000', '1c6', '2c6', '3c6', '4c6' ] ], + [ + " @decimal 1.0000001c6, 1.1c6, 2.0000001c6, 2.1c6, 3.0000001c6, 3.1c6, …", + [ '1.0000001c6', '1.1c6', '2.0000001c6', '2.1c6', '3.0000001c6', '3.1c6' ] + ], + + ]; + } + + public function test_same_rules_should_return_same_instance(): void + { + $rules = " @integer 2, 10"; + $samples = Samples::from($rules); + + $this->assertSame($samples, Samples::from($rules)); + } } diff --git a/tests/PluralsTest.php b/tests/PluralsTest.php index 90ce6a5..84966ad 100644 --- a/tests/PluralsTest.php +++ b/tests/PluralsTest.php @@ -13,182 +13,182 @@ */ final class PluralsTest extends TestCase { - private Plurals $plurals; + private Plurals $plurals; - protected function setUp(): void - { - // @phpstan-ignore-next-line - $this->plurals = new Plurals(get_repository()->supplemental['plurals']); - } + protected function setUp(): void + { + // @phpstan-ignore-next-line + $this->plurals = new Plurals(get_repository()->supplemental['plurals']); + } - #[DataProvider('provide_test_samples_for')] - public function test_samples_for(string $locale, array $expected_keys): void - { - $samples = $this->plurals->samples_for($locale); + #[DataProvider('provide_test_samples_for')] + public function test_samples_for(string $locale, array $expected_keys): void + { + $samples = $this->plurals->samples_for($locale); - $this->assertSame($expected_keys, array_keys($samples)); - $this->assertContainsOnlyInstancesOf(Samples::class, $samples); - } + $this->assertSame($expected_keys, array_keys($samples)); + $this->assertContainsOnlyInstancesOf(Samples::class, $samples); + } - public static function provide_test_samples_for(): array - { - return [ + public static function provide_test_samples_for(): array + { + return [ - [ - 'fr', - [ - - Plurals::COUNT_ONE, - Plurals::COUNT_MANY, - Plurals::COUNT_OTHER - - ] - ], - - [ - 'ar', - [ - - Plurals::COUNT_ZERO, - Plurals::COUNT_ONE, - Plurals::COUNT_TWO, - Plurals::COUNT_FEW, - Plurals::COUNT_MANY, - Plurals::COUNT_OTHER - - ] - ], - - [ - 'bs', - [ - - Plurals::COUNT_ONE, - Plurals::COUNT_FEW, - Plurals::COUNT_OTHER - - ] - ], - - ]; - } - - public function test_samples_should_be_the_same_for_the_same_locale(): void - { - $samples = $this->plurals->samples_for('fr'); - - $this->assertSame($samples, $this->plurals->samples_for('fr')); - } - - #[DataProvider('provide_test_rules_for')] - public function test_rules_for(string $locale, array $expected_keys): void - { - $rules = $this->plurals->rules_for($locale); - - $this->assertSame($expected_keys, $rules); - } - - public static function provide_test_rules_for(): array - { - return [ - - [ - 'fr', - [ - - Plurals::COUNT_ONE, - Plurals::COUNT_MANY, - Plurals::COUNT_OTHER - - ] - ], - - [ - 'ar', - [ - - Plurals::COUNT_ZERO, - Plurals::COUNT_ONE, - Plurals::COUNT_TWO, - Plurals::COUNT_FEW, - Plurals::COUNT_MANY, - Plurals::COUNT_OTHER - - ] - ], - - [ - 'bs', - [ - - Plurals::COUNT_ONE, - Plurals::COUNT_FEW, - Plurals::COUNT_OTHER - - ] - ], - - ]; - } - - /** - * @param float|int|numeric-string $number - */ - #[DataProvider('provide_test_rule_for')] - public function test_rule_for(float|int|string $number, string $locale, string $expected): void - { - $this->assertSame($expected, $this->plurals->rule_for($number, $locale)); - } - - public static function provide_test_rule_for(): array - { - return [ - - [ 0, 'ar', Plurals::COUNT_ZERO ], - [ 1, 'ar', Plurals::COUNT_ONE ], - [ 1.0, 'ar', Plurals::COUNT_ONE ], - [ '1.0000', 'ar', Plurals::COUNT_ONE ], - [ 2, 'ar', Plurals::COUNT_TWO ], - [ '2.0000', 'ar', Plurals::COUNT_TWO ], - [ 3, 'ar', Plurals::COUNT_FEW ], - [ 20, 'ar', Plurals::COUNT_MANY ], - [ 100000, 'ar', Plurals::COUNT_OTHER ], - - ]; - } - - #[DataProvider('provide_test_rule_with_samples')] - public function test_rule_with_samples(string $locale): void - { - $plurals = $this->plurals; - $samples_per_count = $plurals->samples_for($locale); - - foreach ($samples_per_count as $expected => $samples) { - foreach ($samples as $number) { - /** @var numeric-string $number */ - $count = $plurals->rule_for($number, $locale); - - try { - $this->assertSame($expected, $count); - } catch (Throwable) { - $this->fail("Expected `$expected` but got `$count` for number `$number` ($locale)"); - } - } - } - } - - public static function provide_test_rule_with_samples(): array - { - return [ - - [ 'az' ], - [ 'be' ], - [ 'br' ], - [ 'cy' ], - [ 'es' ], - [ 'fr' ], - [ 'naq' ], - - ]; - } + [ + 'fr', + [ + + Plurals::COUNT_ONE, + Plurals::COUNT_MANY, + Plurals::COUNT_OTHER + + ] + ], + + [ + 'ar', + [ + + Plurals::COUNT_ZERO, + Plurals::COUNT_ONE, + Plurals::COUNT_TWO, + Plurals::COUNT_FEW, + Plurals::COUNT_MANY, + Plurals::COUNT_OTHER + + ] + ], + + [ + 'bs', + [ + + Plurals::COUNT_ONE, + Plurals::COUNT_FEW, + Plurals::COUNT_OTHER + + ] + ], + + ]; + } + + public function test_samples_should_be_the_same_for_the_same_locale(): void + { + $samples = $this->plurals->samples_for('fr'); + + $this->assertSame($samples, $this->plurals->samples_for('fr')); + } + + #[DataProvider('provide_test_rules_for')] + public function test_rules_for(string $locale, array $expected_keys): void + { + $rules = $this->plurals->rules_for($locale); + + $this->assertSame($expected_keys, $rules); + } + + public static function provide_test_rules_for(): array + { + return [ + + [ + 'fr', + [ + + Plurals::COUNT_ONE, + Plurals::COUNT_MANY, + Plurals::COUNT_OTHER + + ] + ], + + [ + 'ar', + [ + + Plurals::COUNT_ZERO, + Plurals::COUNT_ONE, + Plurals::COUNT_TWO, + Plurals::COUNT_FEW, + Plurals::COUNT_MANY, + Plurals::COUNT_OTHER + + ] + ], + + [ + 'bs', + [ + + Plurals::COUNT_ONE, + Plurals::COUNT_FEW, + Plurals::COUNT_OTHER + + ] + ], + + ]; + } + + /** + * @param float|int|numeric-string $number + */ + #[DataProvider('provide_test_rule_for')] + public function test_rule_for(float|int|string $number, string $locale, string $expected): void + { + $this->assertSame($expected, $this->plurals->rule_for($number, $locale)); + } + + public static function provide_test_rule_for(): array + { + return [ + + [ 0, 'ar', Plurals::COUNT_ZERO ], + [ 1, 'ar', Plurals::COUNT_ONE ], + [ 1.0, 'ar', Plurals::COUNT_ONE ], + [ '1.0000', 'ar', Plurals::COUNT_ONE ], + [ 2, 'ar', Plurals::COUNT_TWO ], + [ '2.0000', 'ar', Plurals::COUNT_TWO ], + [ 3, 'ar', Plurals::COUNT_FEW ], + [ 20, 'ar', Plurals::COUNT_MANY ], + [ 100000, 'ar', Plurals::COUNT_OTHER ], + + ]; + } + + #[DataProvider('provide_test_rule_with_samples')] + public function test_rule_with_samples(string $locale): void + { + $plurals = $this->plurals; + $samples_per_count = $plurals->samples_for($locale); + + foreach ($samples_per_count as $expected => $samples) { + foreach ($samples as $number) { + /** @var numeric-string $number */ + $count = $plurals->rule_for($number, $locale); + + try { + $this->assertSame($expected, $count); + } catch (Throwable) { + $this->fail("Expected `$expected` but got `$count` for number `$number` ($locale)"); + } + } + } + } + + public static function provide_test_rule_with_samples(): array + { + return [ + + [ 'az' ], + [ 'be' ], + [ 'br' ], + [ 'cy' ], + [ 'es' ], + [ 'fr' ], + [ 'naq' ], + + ]; + } } diff --git a/tests/Provider/FailingProviderTest.php b/tests/Provider/FailingProviderTest.php index 62c669a..4d3a4b7 100644 --- a/tests/Provider/FailingProviderTest.php +++ b/tests/Provider/FailingProviderTest.php @@ -11,13 +11,13 @@ */ class FailingProviderTest extends TestCase { - public function test_provide(): void - { - $sut = new FailingProvider(); + public function test_provide(): void + { + $sut = new FailingProvider(); - $this->expectException(ResourceNotFound::class); - $this->expectExceptionMessageMatches("/Only warmed-up data is available/"); + $this->expectException(ResourceNotFound::class); + $this->expectExceptionMessageMatches("/Only warmed-up data is available/"); - $sut->provide("foo"); - } + $sut->provide("foo"); + } } diff --git a/tests/Provider/WebProviderTest.php b/tests/Provider/WebProviderTest.php index 2aeab31..3656540 100644 --- a/tests/Provider/WebProviderTest.php +++ b/tests/Provider/WebProviderTest.php @@ -8,22 +8,22 @@ final class WebProviderTest extends TestCase { - public function test_provide_ok(): void - { - $provider = new WebProvider(); - $data = $provider->provide('misc/fr/characters'); + public function test_provide_ok(): void + { + $provider = new WebProvider(); + $data = $provider->provide('misc/fr/characters'); - $this->assertIsArray($data); - $this->assertArrayHasKey('main', $data); - } + $this->assertIsArray($data); + $this->assertArrayHasKey('main', $data); + } - public function test_provide_failure(): void - { - $this->expectException(ResourceNotFound::class); - $provider = new WebProvider(); - $path = 'undefined_locale/characters'; + public function test_provide_failure(): void + { + $this->expectException(ResourceNotFound::class); + $provider = new WebProvider(); + $path = 'undefined_locale/characters'; - $this->expectException(ResourceNotFound::class); - $provider->provide($path); - } + $this->expectException(ResourceNotFound::class); + $provider->provide($path); + } } diff --git a/tests/RepositoryTest.php b/tests/RepositoryTest.php index be7817e..bd5a195 100644 --- a/tests/RepositoryTest.php +++ b/tests/RepositoryTest.php @@ -19,112 +19,112 @@ final class RepositoryTest extends TestCase { - private Repository $sut; - - protected function setUp(): void - { - $this->sut = get_repository(); - } - - /** - * @param class-string $expected - */ - #[DataProvider('provide_test_properties_instanceof')] - public function test_properties_instanceof(string $property, string $expected): void - { - $sut = $this->sut; - $instance = $sut->$property; - $this->assertInstanceOf($expected, $instance); - $this->assertSame($instance, $sut->$property); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_properties_instanceof(): array - { - return [ - - [ 'provider', Provider::class ], - [ 'supplemental', Supplemental::class ], - [ 'number_formatter', NumberFormatter::class ], - [ 'currency_formatter', CurrencyFormatter::class ], - [ 'list_formatter', ListFormatter::class ], - [ 'plurals', Plurals::class ], - - ]; - } - - public function test_format_number(): void - { - $this->assertSame( - "4,123.37", - $this->sut->format_number(4123.37, "#,#00.#0") - ); - } - - public function test_format_currency(): void - { - $this->assertSame( - "$4,123.37", - $this->sut->format_currency(4123.37, "¤#,#00.#0", null, '$') - ); - } - - public function test_format_list(): void - { - $list = [ 'one', 'two', 'three' ]; - $list_pattern = ListPattern::from([ - - '2' => "{0} and {1}", - 'start' => "{0}, {1}", - 'middle' => "{0}, {1}", - 'end' => "{0}, and {1}", - - ]); - - $this->assertSame("one, two, and three", $this->sut->format_list($list, $list_pattern)); - } - - public function test_locale_for_using_string(): void - { - $actual = $this->sut->locale_for('fr-BE'); - - $this->assertEquals('fr-BE', $actual->id->value); - } - - public function test_locale_for_using_id(): void - { - $actual = $this->sut->locale_for(LocaleId::of('fr-BE')); - - $this->assertEquals('fr-BE', $actual->id->value); - } - - public function test_locale_for_fails_on_unavailable_id(): void - { - $this->expectException(LocaleNotAvailable::class); - - $this->sut->locale_for('foo'); - } - - public function test_territory_for_using_string(): void - { - $actual = $this->sut->territory_for('CA'); - - $this->assertEquals('CA', $actual->code->value); - } - - public function test_territory_for_using_code(): void - { - $actual = $this->sut->territory_for(TerritoryCode::of('CA')); - - $this->assertEquals('CA', $actual->code->value); - } - - public function test_territory_for_fails_on_undefined_code(): void - { - $this->expectException(TerritoryNotDefined::class); - - $this->sut->territory_for('foo'); - } + private Repository $sut; + + protected function setUp(): void + { + $this->sut = get_repository(); + } + + /** + * @param class-string $expected + */ + #[DataProvider('provide_test_properties_instanceof')] + public function test_properties_instanceof(string $property, string $expected): void + { + $sut = $this->sut; + $instance = $sut->$property; + $this->assertInstanceOf($expected, $instance); + $this->assertSame($instance, $sut->$property); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_properties_instanceof(): array + { + return [ + + [ 'provider', Provider::class ], + [ 'supplemental', Supplemental::class ], + [ 'number_formatter', NumberFormatter::class ], + [ 'currency_formatter', CurrencyFormatter::class ], + [ 'list_formatter', ListFormatter::class ], + [ 'plurals', Plurals::class ], + + ]; + } + + public function test_format_number(): void + { + $this->assertSame( + "4,123.37", + $this->sut->format_number(4123.37, "#,#00.#0") + ); + } + + public function test_format_currency(): void + { + $this->assertSame( + "$4,123.37", + $this->sut->format_currency(4123.37, "¤#,#00.#0", null, '$') + ); + } + + public function test_format_list(): void + { + $list = [ 'one', 'two', 'three' ]; + $list_pattern = ListPattern::from([ + + '2' => "{0} and {1}", + 'start' => "{0}, {1}", + 'middle' => "{0}, {1}", + 'end' => "{0}, and {1}", + + ]); + + $this->assertSame("one, two, and three", $this->sut->format_list($list, $list_pattern)); + } + + public function test_locale_for_using_string(): void + { + $actual = $this->sut->locale_for('fr-BE'); + + $this->assertEquals('fr-BE', $actual->id->value); + } + + public function test_locale_for_using_id(): void + { + $actual = $this->sut->locale_for(LocaleId::of('fr-BE')); + + $this->assertEquals('fr-BE', $actual->id->value); + } + + public function test_locale_for_fails_on_unavailable_id(): void + { + $this->expectException(LocaleNotAvailable::class); + + $this->sut->locale_for('foo'); + } + + public function test_territory_for_using_string(): void + { + $actual = $this->sut->territory_for('CA'); + + $this->assertEquals('CA', $actual->code->value); + } + + public function test_territory_for_using_code(): void + { + $actual = $this->sut->territory_for(TerritoryCode::of('CA')); + + $this->assertEquals('CA', $actual->code->value); + } + + public function test_territory_for_fails_on_undefined_code(): void + { + $this->expectException(TerritoryNotDefined::class); + + $this->sut->territory_for('foo'); + } } diff --git a/tests/StringHelpers.php b/tests/StringHelpers.php index 581d2f0..c74f5c1 100644 --- a/tests/StringHelpers.php +++ b/tests/StringHelpers.php @@ -3,9 +3,10 @@ namespace Test\ICanBoogie\CLDR; use function bin2hex; -use const PHP_EOL; use function str_split; +use const PHP_EOL; + trait StringHelpers { protected function assertStringSame(string $expected, string $actual): void diff --git a/tests/Supplemental/FractionTest.php b/tests/Supplemental/FractionTest.php index 27a6f06..c3b9974 100644 --- a/tests/Supplemental/FractionTest.php +++ b/tests/Supplemental/FractionTest.php @@ -8,48 +8,48 @@ final class FractionTest extends TestCase { - /** - * @phpstan-ignore-next-line - */ - #[DataProvider('provide_from')] - public function test_from(array $data, int $digits, int $rounding, int $cash_digits, int $cash_rounding): void - { - $fraction = Fraction::from($data); - - $this->assertSame($digits, $fraction->digits); - $this->assertSame($rounding, $fraction->rounding); - $this->assertSame($cash_digits, $fraction->cash_digits); - $this->assertSame($cash_rounding, $fraction->cash_rounding); - } - - public static function provide_from(): array - { - return [ - - [ - [], - 2, - 0, - 2, - 0 - ], - - [ - [ '_digits' => '2', '_rounding' => '50', '_cashDigits' => '3', '_cashRounding' => '51' ], - 2, - 50, - 3, - 51 - ], - - [ - [ '_digits' => '2', '_rounding' => '50' ], - 2, - 50, - 2, - 50 - ], - - ]; - } + /** + * @phpstan-ignore-next-line + */ + #[DataProvider('provide_from')] + public function test_from(array $data, int $digits, int $rounding, int $cash_digits, int $cash_rounding): void + { + $fraction = Fraction::from($data); + + $this->assertSame($digits, $fraction->digits); + $this->assertSame($rounding, $fraction->rounding); + $this->assertSame($cash_digits, $fraction->cash_digits); + $this->assertSame($cash_rounding, $fraction->cash_rounding); + } + + public static function provide_from(): array + { + return [ + + [ + [], + 2, + 0, + 2, + 0 + ], + + [ + [ '_digits' => '2', '_rounding' => '50', '_cashDigits' => '3', '_cashRounding' => '51' ], + 2, + 50, + 3, + 51 + ], + + [ + [ '_digits' => '2', '_rounding' => '50' ], + 2, + 50, + 2, + 50 + ], + + ]; + } } diff --git a/tests/SupplementalTest.php b/tests/SupplementalTest.php index edad1e2..33f8a30 100644 --- a/tests/SupplementalTest.php +++ b/tests/SupplementalTest.php @@ -10,62 +10,62 @@ final class SupplementalTest extends TestCase { - private static Supplemental $sut; - - public static function setupBeforeClass(): void - { - self::$sut = get_repository()->supplemental; - } - - #[DataProvider('provide_test_sections')] - public function test_sections(string $section, string $key): void - { - $section_data = self::$sut[$section]; - $this->assertIsArray($section_data); - $this->assertArrayHasKey($key, $section_data); - } - - public static function provide_test_sections(): array - { - return [ - - [ 'aliases' , 'languageAlias' ], - [ 'calendarData' , 'buddhist' ], - [ 'calendarPreferenceData' , 'AE' ], - [ 'characterFallbacks' , 'U+00AD' ], - [ 'codeMappings' , 'AA' ], - [ 'currencyData' , 'fractions' ], - [ 'dayPeriods' , 'af' ], - [ 'gender' , 'personList' ], - [ 'grammaticalFeatures' , 'am-targets-nominal' ], - [ 'languageData' , 'aa' ], - [ 'languageGroups' , 'aav' ], - [ 'languageMatching' , 'written-new' ], - [ 'likelySubtags' , 'aa' ], - [ 'measurementData' , 'measurementSystem' ], - [ 'metaZones' , 'metazoneInfo' ], - [ 'numberingSystems' , 'armn' ], - [ 'ordinals' , 'af' ], - [ 'parentLocales' , 'en-150' ], - [ 'pluralRanges' , 'af' ], - [ 'plurals' , 'af' ], - [ 'primaryZones' , 'CL' ], - [ 'references' , 'R1000' ], - [ 'territoryContainment' , 'EU' ], - [ 'territoryInfo' , 'AC' ], - [ 'timeData' , 'AD' ], - [ 'unitPreferenceData' , 'area' ], - [ 'weekData' , 'minDays' ], - [ 'windowsZones' , 'mapTimezones' ], - - ]; - } - - public function test_default_calendar(): void - { - // @phpstan-ignore-next-line - $this->assertArrayHasKey('001', self::$sut['calendarPreferenceData']); - } + private static Supplemental $sut; + + public static function setupBeforeClass(): void + { + self::$sut = get_repository()->supplemental; + } + + #[DataProvider('provide_test_sections')] + public function test_sections(string $section, string $key): void + { + $section_data = self::$sut[$section]; + $this->assertIsArray($section_data); + $this->assertArrayHasKey($key, $section_data); + } + + public static function provide_test_sections(): array + { + return [ + + [ 'aliases', 'languageAlias' ], + [ 'calendarData', 'buddhist' ], + [ 'calendarPreferenceData', 'AE' ], + [ 'characterFallbacks', 'U+00AD' ], + [ 'codeMappings', 'AA' ], + [ 'currencyData', 'fractions' ], + [ 'dayPeriods', 'af' ], + [ 'gender', 'personList' ], + [ 'grammaticalFeatures', 'am-targets-nominal' ], + [ 'languageData', 'aa' ], + [ 'languageGroups', 'aav' ], + [ 'languageMatching', 'written-new' ], + [ 'likelySubtags', 'aa' ], + [ 'measurementData', 'measurementSystem' ], + [ 'metaZones', 'metazoneInfo' ], + [ 'numberingSystems', 'armn' ], + [ 'ordinals', 'af' ], + [ 'parentLocales', 'en-150' ], + [ 'pluralRanges', 'af' ], + [ 'plurals', 'af' ], + [ 'primaryZones', 'CL' ], + [ 'references', 'R1000' ], + [ 'territoryContainment', 'EU' ], + [ 'territoryInfo', 'AC' ], + [ 'timeData', 'AD' ], + [ 'unitPreferenceData', 'area' ], + [ 'weekData', 'minDays' ], + [ 'windowsZones', 'mapTimezones' ], + + ]; + } + + public function test_default_calendar(): void + { + // @phpstan-ignore-next-line + $this->assertArrayHasKey('001', self::$sut['calendarPreferenceData']); + } public function test_offset_exists(): void { @@ -76,33 +76,35 @@ public function test_offset_exists(): void $this->assertFalse(isset($s[uniqid()])); } - public function test_should_throw_exception_when_getting_undefined_offset(): void + public function test_should_throw_exception_when_getting_undefined_offset(): void { - $s = self::$sut; - $this->expectException(OffsetNotDefined::class); + $s = self::$sut; + $this->expectException(OffsetNotDefined::class); $s[uniqid()]; // @phpstan-ignore-line } - public function test_should_throw_exception_in_attempt_to_set_offset(): void + public function test_should_throw_exception_in_attempt_to_set_offset(): void { - $s = self::$sut; - $this->expectException(OffsetNotWritable::class); + $s = self::$sut; + $this->expectException(OffsetNotWritable::class); $s['timeData'] = null; } - public function test_should_throw_exception_in_attempt_to_unset_offset(): void + public function test_should_throw_exception_in_attempt_to_unset_offset(): void { - $s = self::$sut; - $this->expectException(OffsetNotWritable::class); + $s = self::$sut; + $this->expectException(OffsetNotWritable::class); unset($s['timeData']); } - public function test_warm_up(): void - { - $n = 0; + public function test_warm_up(): void + { + $n = 0; - self::$sut->warm_up(function() use (&$n) { $n++; }); + self::$sut->warm_up(function () use (&$n) { + $n++; + }); - $this->assertEquals(29, $n); - } + $this->assertEquals(29, $n); + } } diff --git a/tests/Territory/RegionCurrenciesTest.php b/tests/Territory/RegionCurrenciesTest.php index 9566bfe..6dc48fd 100644 --- a/tests/Territory/RegionCurrenciesTest.php +++ b/tests/Territory/RegionCurrenciesTest.php @@ -9,45 +9,45 @@ class RegionCurrenciesTest extends TestCase { - private RegionCurrencies $sut; - - protected function setUp(): void - { - parent::setUp(); - - $this->sut = RegionCurrencies::from([ - [ 'EUR' => [ '_from' => '1999-01-01' ] ], - [ 'BEC' => [ '_tender' => 'false', '_from' => '1970-01-01', '_to' => '1990-03-05' ] ], - [ 'BEF' => [ '_from' => '1831-02-07', '_to' => '2002-02-28' ] ], - ]); - } - - public function test_from(): void - { - $actual = iterator_to_array($this->sut); - $expected = [ - RegionCurrency::from([ 'BEF' => [ '_from' => '1831-02-07', '_to' => '2002-02-28' ] ]), - RegionCurrency::from([ 'BEC' => [ '_tender' => 'false', '_from' => '1970-01-01', '_to' => '1990-03-05' ] ]), - RegionCurrency::from([ 'EUR' => [ '_from' => '1999-01-01' ] ]), - ]; - - $this->assertEquals($expected, $actual); - } - - #[DataProvider("provide_at")] - public function test_at(string $date, string $expected): void - { - $actual = $this->sut->at($date); - $this->assertEquals($expected, $actual?->code); - } - - public static function provide_at(): array - { - return [ - [ '1900-01-01', 'BEF'], - // Both BEF and EUR are available, but BEF wins because it is older - [ '1999-01-01', 'BEF'], - [ '2024-01-01', 'EUR'], - ]; - } + private RegionCurrencies $sut; + + protected function setUp(): void + { + parent::setUp(); + + $this->sut = RegionCurrencies::from([ + [ 'EUR' => [ '_from' => '1999-01-01' ] ], + [ 'BEC' => [ '_tender' => 'false', '_from' => '1970-01-01', '_to' => '1990-03-05' ] ], + [ 'BEF' => [ '_from' => '1831-02-07', '_to' => '2002-02-28' ] ], + ]); + } + + public function test_from(): void + { + $actual = iterator_to_array($this->sut); + $expected = [ + RegionCurrency::from([ 'BEF' => [ '_from' => '1831-02-07', '_to' => '2002-02-28' ] ]), + RegionCurrency::from([ 'BEC' => [ '_tender' => 'false', '_from' => '1970-01-01', '_to' => '1990-03-05' ] ]), + RegionCurrency::from([ 'EUR' => [ '_from' => '1999-01-01' ] ]), + ]; + + $this->assertEquals($expected, $actual); + } + + #[DataProvider("provide_at")] + public function test_at(string $date, string $expected): void + { + $actual = $this->sut->at($date); + $this->assertEquals($expected, $actual?->code); + } + + public static function provide_at(): array + { + return [ + [ '1900-01-01', 'BEF' ], + // Both BEF and EUR are available, but BEF wins because it is older + [ '1999-01-01', 'BEF' ], + [ '2024-01-01', 'EUR' ], + ]; + } } diff --git a/tests/Territory/RegionCurrencyTest.php b/tests/Territory/RegionCurrencyTest.php index 7bf7de8..e75f591 100644 --- a/tests/Territory/RegionCurrencyTest.php +++ b/tests/Territory/RegionCurrencyTest.php @@ -8,35 +8,35 @@ class RegionCurrencyTest extends TestCase { - /** - * @param array $data - */ - #[DataProvider("provide_from")] - public function test_from(array $data): void - { - $code = key($data); - $properties = current($data) + [ - '_tender' => null, - '_from' => null, - '_to' => null, - ]; - - $actual = RegionCurrency::from($data); - - $this->assertEquals($code, $actual->code); - $this->assertEquals(!($properties['_tender'] === 'false'), $actual->tender); - $this->assertEquals($properties['_from'], $actual->from); - $this->assertEquals($properties['_to'], $actual->to); - } - - public static function provide_from(): array - { - return [ - - [ [ 'BEC' => [ '_tender' => 'false', '_from' => '1970-01-01', '_to' => '1990-03-05' ] ] ], - [ [ 'BEF' => [ '_from' => '1831-02-07', '_to' => '2002-02-28' ] ] ], - [ [ 'EUR' => [ '_from' => '1999-01-01' ] ] ], - - ]; - } + /** + * @param array $data + */ + #[DataProvider("provide_from")] + public function test_from(array $data): void + { + $code = key($data); + $properties = current($data) + [ + '_tender' => null, + '_from' => null, + '_to' => null, + ]; + + $actual = RegionCurrency::from($data); + + $this->assertEquals($code, $actual->code); + $this->assertEquals(!($properties['_tender'] === 'false'), $actual->tender); + $this->assertEquals($properties['_from'], $actual->from); + $this->assertEquals($properties['_to'], $actual->to); + } + + public static function provide_from(): array + { + return [ + + [ [ 'BEC' => [ '_tender' => 'false', '_from' => '1970-01-01', '_to' => '1990-03-05' ] ] ], + [ [ 'BEF' => [ '_from' => '1831-02-07', '_to' => '2002-02-28' ] ] ], + [ [ 'EUR' => [ '_from' => '1999-01-01' ] ] ], + + ]; + } } diff --git a/tests/TerritoryNotDefinedTest.php b/tests/TerritoryNotDefinedTest.php index e5a1a6c..a1d5a1f 100644 --- a/tests/TerritoryNotDefinedTest.php +++ b/tests/TerritoryNotDefinedTest.php @@ -9,37 +9,41 @@ final class TerritoryNotDefinedTest extends TestCase { - #[DataProvider('provide_instance')] - public function test_instance(string $territory_code, ?string $message, string $expected_message, Exception $previous = null): void - { - $sut = new TerritoryNotDefined($territory_code, $message, $previous); - - $this->assertSame($territory_code, $sut->territory_code); - $this->assertSame($expected_message, $sut->getMessage()); - $this->assertSame($previous, $sut->getPrevious()); - } - - public static function provide_instance(): array - { - $territory_code = 'FR'; - $previous = new Exception(); - - return [ - - "should format a message" => [ - $territory_code, - null, - "Territory not defined for code: $territory_code.", - null, - ], - - "should use custom message" => [ - $territory_code, - $message = "Madonna", - $message, - $previous, - ], - - ]; - } + #[DataProvider('provide_instance')] + public function test_instance( + string $territory_code, + ?string $message, + string $expected_message, + Exception $previous = null + ): void { + $sut = new TerritoryNotDefined($territory_code, $message, $previous); + + $this->assertSame($territory_code, $sut->territory_code); + $this->assertSame($expected_message, $sut->getMessage()); + $this->assertSame($previous, $sut->getPrevious()); + } + + public static function provide_instance(): array + { + $territory_code = 'FR'; + $previous = new Exception(); + + return [ + + "should format a message" => [ + $territory_code, + null, + "Territory not defined for code: $territory_code.", + null, + ], + + "should use custom message" => [ + $territory_code, + $message = "Madonna", + $message, + $previous, + ], + + ]; + } } diff --git a/tests/TerritoryTest.php b/tests/TerritoryTest.php index ef2e52b..7458aa3 100644 --- a/tests/TerritoryTest.php +++ b/tests/TerritoryTest.php @@ -11,197 +11,197 @@ final class TerritoryTest extends TestCase { - public function test_get_info(): void - { - $territory = new Territory(get_repository(), TerritoryCode::of('FR')); - $this->assertIsArray($territory->info); - } - - public function test_get_containment(): void - { - $territory = new Territory(get_repository(), TerritoryCode::of('EU')); - $this->assertIsArray($territory->containment); - } - - public function test_is_containing(): void - { - $territory = new Territory(get_repository(), TerritoryCode::of('EU')); - - $this->assertTrue($territory->is_containing('FR')); - $this->assertFalse($territory->is_containing('TA')); - } - - #[DataProvider('provide_test_get_currency')] - public function test_get_currency(string $expected, string $territory_code): void - { - $territory = new Territory(get_repository(), TerritoryCode::of($territory_code)); - $actual = $territory->currency; - - $this->assertEquals($expected, $actual?->code); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_get_currency(): array - { - return [ - - [ 'EUR', 'FR' ], - [ 'MMK', 'MM' ], - [ 'USD', 'US' ] - - ]; - } - - #[DataProvider('provide_test_currency_at')] - public function test_currency_at(string $expected, string $territory_code, mixed $date): void - { - $territory = new Territory(get_repository(), TerritoryCode::of($territory_code)); - $this->assertEquals($expected, $territory->currency_at($date)?->code); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_currency_at(): array - { - return [ - - [ 'EUR', 'FR', null ], - [ 'EUR', 'FR', 'now' ], - [ 'EUR', 'FR', new \DateTime() ], - [ 'FRF', 'FR', '1960-01-01' ], - [ 'FRF', 'FR', '1977-06-06' ], - [ 'FRF', 'FR', new \DateTime('1977-06-06') ], - [ 'USD', 'US', '1792-01-01' ] - - ]; - } - - #[DataProvider('provide_test_get_language')] - public function test_get_language(string $expected, string $territory_code): void - { - $territory = new Territory(get_repository(), TerritoryCode::of($territory_code)); - $this->assertSame($expected, $territory->language); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_get_language(): array - { - return [ - - [ 'fr', 'FR' ], - [ 'en', 'US' ], - [ 'es', 'ES' ] - - ]; - } - - public function test_get_population(): void - { - $territory = new Territory(get_repository(), TerritoryCode::of('ES')); - $this->assertNotEmpty($territory->population); - } - - #[DataProvider('provide_test_name_as')] - public function test_name_as(string $expected, string $territory_code, string $locale_id): void - { - $territory = new Territory(get_repository(), TerritoryCode::of($territory_code)); - $this->assertEquals($expected, $territory->name_as(LocaleId::of($locale_id))); - $this->assertEquals($expected, $territory->name_as($locale_id)); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_name_as(): array - { - return [ - - [ "France", "FR", "fr" ], - [ "France", "FR", "fr-BE" ], - [ "Francia", "FR", "it" ], - [ "フランス", "FR", "ja" ] - - ]; - } - - #[DataProvider('provide_test_get_name_as')] - public function test_get_name_as(string $expected, string $territory_code, string $locale_id): void - { - $territory = new Territory(get_repository(), TerritoryCode::of($territory_code)); - $this->assertEquals($expected, $territory->{'name_as_' . $locale_id}); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_get_name_as(): array - { - return [ - - [ "France", "FR", "fr" ], - [ "France", "FR", "fr_BE" ], - [ "Francia", "FR", "it" ], - [ "フランス", "FR", "ja" ] - - ]; - } - - #[DataProvider('provide_test_get_property')] - public function test_get_property(string $expected, string $territory_code, string $property): void - { - $territory = new Territory(get_repository(), TerritoryCode::of($territory_code)); - $this->assertEquals($expected, $territory->$property); - } - - /** - * @link https://github.com/unicode-org/cldr-json/blob/45.0.0/cldr-json/cldr-core/supplemental/weekData.json - * - * @phpstan-ignore-next-line - */ - public static function provide_test_get_property(): array - { - return [ - - # first_day - - [ "mon", "FR", 'first_day' ], - [ "sat", "EG", 'first_day' ], - [ "sun", "BS", 'first_day' ], - [ "fri", "MV", 'first_day' ], - - # weekend_start - - [ "sat", "FR", 'weekend_start' ], - [ "sat", "AE", 'weekend_start' ], - [ "thu", "AF", 'weekend_start' ], - [ "sun", "IN", 'weekend_start' ], - - # weekend_end - - [ "sun", "FR", 'weekend_end' ], - [ "sun", "AE", 'weekend_end' ], - [ "fri", "AF", 'weekend_end' ] - - ]; - } - - public function test_to_string(): void - { - $territory_code = 'US'; - $territory = new Territory(get_repository(), TerritoryCode::of($territory_code)); - $this->assertEquals($territory_code, (string)$territory); - } - - public function test_localize(): void - { - $territory = new Territory(get_repository(), TerritoryCode::of('FR')); - $actual = $territory->localized(LocaleId::of('fr')); - - $this->assertInstanceOf(LocalizedTerritory::class, $actual); - } + public function test_get_info(): void + { + $territory = new Territory(get_repository(), TerritoryCode::of('FR')); + $this->assertIsArray($territory->info); + } + + public function test_get_containment(): void + { + $territory = new Territory(get_repository(), TerritoryCode::of('EU')); + $this->assertIsArray($territory->containment); + } + + public function test_is_containing(): void + { + $territory = new Territory(get_repository(), TerritoryCode::of('EU')); + + $this->assertTrue($territory->is_containing('FR')); + $this->assertFalse($territory->is_containing('TA')); + } + + #[DataProvider('provide_test_get_currency')] + public function test_get_currency(string $expected, string $territory_code): void + { + $territory = new Territory(get_repository(), TerritoryCode::of($territory_code)); + $actual = $territory->currency; + + $this->assertEquals($expected, $actual?->code); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_get_currency(): array + { + return [ + + [ 'EUR', 'FR' ], + [ 'MMK', 'MM' ], + [ 'USD', 'US' ] + + ]; + } + + #[DataProvider('provide_test_currency_at')] + public function test_currency_at(string $expected, string $territory_code, mixed $date): void + { + $territory = new Territory(get_repository(), TerritoryCode::of($territory_code)); + $this->assertEquals($expected, $territory->currency_at($date)?->code); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_currency_at(): array + { + return [ + + [ 'EUR', 'FR', null ], + [ 'EUR', 'FR', 'now' ], + [ 'EUR', 'FR', new \DateTime() ], + [ 'FRF', 'FR', '1960-01-01' ], + [ 'FRF', 'FR', '1977-06-06' ], + [ 'FRF', 'FR', new \DateTime('1977-06-06') ], + [ 'USD', 'US', '1792-01-01' ] + + ]; + } + + #[DataProvider('provide_test_get_language')] + public function test_get_language(string $expected, string $territory_code): void + { + $territory = new Territory(get_repository(), TerritoryCode::of($territory_code)); + $this->assertSame($expected, $territory->language); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_get_language(): array + { + return [ + + [ 'fr', 'FR' ], + [ 'en', 'US' ], + [ 'es', 'ES' ] + + ]; + } + + public function test_get_population(): void + { + $territory = new Territory(get_repository(), TerritoryCode::of('ES')); + $this->assertNotEmpty($territory->population); + } + + #[DataProvider('provide_test_name_as')] + public function test_name_as(string $expected, string $territory_code, string $locale_id): void + { + $territory = new Territory(get_repository(), TerritoryCode::of($territory_code)); + $this->assertEquals($expected, $territory->name_as(LocaleId::of($locale_id))); + $this->assertEquals($expected, $territory->name_as($locale_id)); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_name_as(): array + { + return [ + + [ "France", "FR", "fr" ], + [ "France", "FR", "fr-BE" ], + [ "Francia", "FR", "it" ], + [ "フランス", "FR", "ja" ] + + ]; + } + + #[DataProvider('provide_test_get_name_as')] + public function test_get_name_as(string $expected, string $territory_code, string $locale_id): void + { + $territory = new Territory(get_repository(), TerritoryCode::of($territory_code)); + $this->assertEquals($expected, $territory->{'name_as_' . $locale_id}); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_get_name_as(): array + { + return [ + + [ "France", "FR", "fr" ], + [ "France", "FR", "fr_BE" ], + [ "Francia", "FR", "it" ], + [ "フランス", "FR", "ja" ] + + ]; + } + + #[DataProvider('provide_test_get_property')] + public function test_get_property(string $expected, string $territory_code, string $property): void + { + $territory = new Territory(get_repository(), TerritoryCode::of($territory_code)); + $this->assertEquals($expected, $territory->$property); + } + + /** + * @link https://github.com/unicode-org/cldr-json/blob/45.0.0/cldr-json/cldr-core/supplemental/weekData.json + * + * @phpstan-ignore-next-line + */ + public static function provide_test_get_property(): array + { + return [ + + # first_day + + [ "mon", "FR", 'first_day' ], + [ "sat", "EG", 'first_day' ], + [ "sun", "BS", 'first_day' ], + [ "fri", "MV", 'first_day' ], + + # weekend_start + + [ "sat", "FR", 'weekend_start' ], + [ "sat", "AE", 'weekend_start' ], + [ "thu", "AF", 'weekend_start' ], + [ "sun", "IN", 'weekend_start' ], + + # weekend_end + + [ "sun", "FR", 'weekend_end' ], + [ "sun", "AE", 'weekend_end' ], + [ "fri", "AF", 'weekend_end' ] + + ]; + } + + public function test_to_string(): void + { + $territory_code = 'US'; + $territory = new Territory(get_repository(), TerritoryCode::of($territory_code)); + $this->assertEquals($territory_code, (string)$territory); + } + + public function test_localize(): void + { + $territory = new Territory(get_repository(), TerritoryCode::of('FR')); + $actual = $territory->localized(LocaleId::of('fr')); + + $this->assertInstanceOf(LocalizedTerritory::class, $actual); + } } diff --git a/tests/TimeFormatterTest.php b/tests/TimeFormatterTest.php index a57e6c8..047c7aa 100644 --- a/tests/TimeFormatterTest.php +++ b/tests/TimeFormatterTest.php @@ -10,50 +10,50 @@ final class TimeFormatterTest extends TestCase { - /** - * @var array - */ - private static array $formatters = []; - - public static function setupBeforeClass(): void - { - self::$formatters['en'] = new TimeFormatter(locale_for('en')->calendar); - self::$formatters['fr'] = new TimeFormatter(locale_for('fr')->calendar); - } - - #[DataProvider('provide_test_format')] - public function test_format( - string $locale_id, - string $datetime, - string|DateTimeFormatLength|DateTimeFormatId $pattern, - string $expected - ): void { - $actual = self::$formatters[$locale_id]->format($datetime, $pattern); - - $this->assertEquals($expected, $actual); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_format(): array - { - return [ - - [ 'en', '2013-11-05 21:22:23', DateTimeFormatLength::FULL, '9:22:23 PM CET' ], - [ 'en', '2013-11-05 21:22:23', DateTimeFormatLength::LONG, '9:22:23 PM CET' ], - [ 'en', '2013-11-05 21:22:23', DateTimeFormatLength::MEDIUM, '9:22:23 PM' ], - [ 'en', '2013-11-05 21:22:23', DateTimeFormatLength::SHORT, '9:22 PM' ], - - [ 'fr', '2013-11-05 21:22:23', DateTimeFormatLength::FULL, '21:22:23 CET' ], - [ 'fr', '2013-11-05 21:22:23', DateTimeFormatLength::LONG, '21:22:23 CET' ], - [ 'fr', '2013-11-05 21:22:23', DateTimeFormatLength::MEDIUM, '21:22:23' ], - [ 'fr', '2013-11-05 21:22:23', DateTimeFormatLength::SHORT, '21:22' ], - - # datetime patterns must be supported too - [ 'en', '2013-11-05 21:22:23', DateTimeFormatId::from('yMMMEd'), 'Tue, Nov 5, 2013' ], - [ 'fr', '2013-11-05 21:22:23', 'd MMMM y', '5 novembre 2013' ] - - ]; - } + /** + * @var array + */ + private static array $formatters = []; + + public static function setupBeforeClass(): void + { + self::$formatters['en'] = new TimeFormatter(locale_for('en')->calendar); + self::$formatters['fr'] = new TimeFormatter(locale_for('fr')->calendar); + } + + #[DataProvider('provide_test_format')] + public function test_format( + string $locale_id, + string $datetime, + string|DateTimeFormatLength|DateTimeFormatId $pattern, + string $expected + ): void { + $actual = self::$formatters[$locale_id]->format($datetime, $pattern); + + $this->assertEquals($expected, $actual); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_format(): array + { + return [ + + [ 'en', '2013-11-05 21:22:23', DateTimeFormatLength::FULL, '9:22:23 PM CET' ], + [ 'en', '2013-11-05 21:22:23', DateTimeFormatLength::LONG, '9:22:23 PM CET' ], + [ 'en', '2013-11-05 21:22:23', DateTimeFormatLength::MEDIUM, '9:22:23 PM' ], + [ 'en', '2013-11-05 21:22:23', DateTimeFormatLength::SHORT, '9:22 PM' ], + + [ 'fr', '2013-11-05 21:22:23', DateTimeFormatLength::FULL, '21:22:23 CET' ], + [ 'fr', '2013-11-05 21:22:23', DateTimeFormatLength::LONG, '21:22:23 CET' ], + [ 'fr', '2013-11-05 21:22:23', DateTimeFormatLength::MEDIUM, '21:22:23' ], + [ 'fr', '2013-11-05 21:22:23', DateTimeFormatLength::SHORT, '21:22' ], + + # datetime patterns must be supported too + [ 'en', '2013-11-05 21:22:23', DateTimeFormatId::from('yMMMEd'), 'Tue, Nov 5, 2013' ], + [ 'fr', '2013-11-05 21:22:23', 'd MMMM y', '5 novembre 2013' ] + + ]; + } } diff --git a/tests/Units/NumberPerUnitTest.php b/tests/Units/NumberPerUnitTest.php index 0cece82..af46bc6 100644 --- a/tests/Units/NumberPerUnitTest.php +++ b/tests/Units/NumberPerUnitTest.php @@ -12,56 +12,63 @@ final class NumberPerUnitTest extends TestCase { - use StringHelpers; + use StringHelpers; - public function test_to_string(): void - { - $stu = new NumberPerUnit(123.4504, 'digital-gigabyte', 'duration-hour', $this->units_for('fr')); + public function test_to_string(): void + { + $stu = new NumberPerUnit(123.4504, 'digital-gigabyte', 'duration-hour', $this->units_for('fr')); - $this->assertSame("123,45 gigaoctets par heure", (string)$stu); - } + $this->assertSame("123,45 gigaoctets par heure", (string)$stu); + } - /** - * @dataProvider provide_test_cases - * - * @param float|int|numeric-string $number - */ - public function test_cases( - string $locale, - float|int|string $number, - string $number_unit, - string $per_unit, - UnitLength $length, - string $expected - ): void { - $stu = new NumberPerUnit($number, $number_unit, $per_unit, $this->units_for($locale)); + /** + * @dataProvider provide_test_cases + * + * @param float|int|numeric-string $number + */ + public function test_cases( + string $locale, + float|int|string $number, + string $number_unit, + string $per_unit, + UnitLength $length, + string $expected + ): void { + $stu = new NumberPerUnit($number, $number_unit, $per_unit, $this->units_for($locale)); - $this->assertSame($expected, $stu->{'as_' . $length->value}); - } + $this->assertSame($expected, $stu->{'as_' . $length->value}); + } - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_cases(): array - { - return [ + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_cases(): array + { + return [ - [ 'en', 12.345, 'volume-liter', 'duration-hour', UnitLength::LONG, "12.345 liters per hour" ], - [ 'en', 12.345, 'volume-liter', 'duration-hour', UnitLength::SHORT, "12.345 L/h" ], - [ 'en', 12.345, 'volume-liter', 'duration-hour', UnitLength::NARROW, "12.345L/h" ], + [ 'en', 12.345, 'volume-liter', 'duration-hour', UnitLength::LONG, "12.345 liters per hour" ], + [ 'en', 12.345, 'volume-liter', 'duration-hour', UnitLength::SHORT, "12.345 L/h" ], + [ 'en', 12.345, 'volume-liter', 'duration-hour', UnitLength::NARROW, "12.345L/h" ], - [ 'fr', 12.345, 'volume-liter', 'duration-hour', UnitLength::LONG, "12,345 litres par heure" ], - [ 'fr', 12.345, 'volume-liter', 'duration-hour', UnitLength::SHORT, "12,345 l/h" ], - [ 'fr', 12.345, 'volume-liter', 'duration-hour', UnitLength::NARROW, "12,345l/h" ], + [ 'fr', 12.345, 'volume-liter', 'duration-hour', UnitLength::LONG, "12,345 litres par heure" ], + [ 'fr', 12.345, 'volume-liter', 'duration-hour', UnitLength::SHORT, "12,345 l/h" ], + [ 'fr', 12.345, 'volume-liter', 'duration-hour', UnitLength::NARROW, "12,345l/h" ], - [ 'fr', 12.345, 'volume-liter', 'area-square-meter', UnitLength::LONG, "12,345 litres par mètre carré" ], - [ 'fr', 12.345, 'angle-revolution', 'length-light-year', UnitLength::LONG, "12,345 tours par années-lumière" ], + [ 'fr', 12.345, 'volume-liter', 'area-square-meter', UnitLength::LONG, "12,345 litres par mètre carré" ], + [ + 'fr', + 12.345, + 'angle-revolution', + 'length-light-year', + UnitLength::LONG, + "12,345 tours par années-lumière" + ], - ]; - } + ]; + } - private function units_for(string $locale): Units - { - return locale_for($locale)->units; - } + private function units_for(string $locale): Units + { + return locale_for($locale)->units; + } } diff --git a/tests/Units/NumberWithUnitTest.php b/tests/Units/NumberWithUnitTest.php index 47409aa..c0095ce 100644 --- a/tests/Units/NumberWithUnitTest.php +++ b/tests/Units/NumberWithUnitTest.php @@ -12,94 +12,107 @@ final class NumberWithUnitTest extends TestCase { - use StringHelpers; - - public function test_to_string(): void - { - $stu = new NumberWithUnit(123.4504, 'digital-gigabyte', $this->units_for('fr')); - - $this->assertSame("123,45 gigaoctets", (string)$stu); - } - - /** - * @dataProvider provide_test_cases - * - * @param float|int|numeric-string $number - */ - public function test_cases( - string $locale, - string $unit, - float|int|string $number, - UnitLength $length, - string $expected - ): void { - $stu = new NumberWithUnit($number, $unit, $this->units_for($locale)); - - $this->assertSame($expected, $stu->{'as_' . $length->value}); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_cases(): array - { - return [ - - [ 'fr', 'acceleration-g-force', 123.4504, UnitLength::LONG, "123,45 fois l’accélération de pesanteur terrestre" ], - [ 'fr', 'digital-gigabyte', 123.4504, UnitLength::LONG, "123,45 gigaoctets" ], - [ 'fr', 'digital-gigabyte', 123.4504, UnitLength::SHORT, "123,45 Go" ], - [ 'fr', 'digital-gigabyte', 123.4504, UnitLength::NARROW, "123,45Go" ], - [ 'fr', 'duration-hour', 123.4504, UnitLength::LONG, "123,45 heures" ], - [ 'fr', 'duration-hour', 123.4504, UnitLength::SHORT, "123,45 h" ], - [ 'fr', 'duration-hour', 123.4504, UnitLength::NARROW, "123,45h" ], - - ]; - } - - /** - * @dataProvider provide_per - * - * @param float|int|numeric-string $number - */ - public function test_per( - string $locale, - float|int|string $number, - string $number_unit, - string $per_unit, - UnitLength $length, - string $expected - ): void { - $stu = new NumberWithUnit($number, $number_unit, $this->units_for($locale)); - - $this->assertSame( - $expected, - $stu->per($per_unit)->{'as_' . $length->value} - ); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_per(): array - { - return [ - - [ 'en', 12.345, 'volume-liter', 'duration-hour', UnitLength::LONG, "12.345 liters per hour" ], - [ 'en', 12.345, 'volume-liter', 'duration-hour', UnitLength::SHORT, "12.345 L/h" ], - [ 'en', 12.345, 'volume-liter', 'duration-hour', UnitLength::NARROW, "12.345L/h" ], - - [ 'fr', 12.345, 'volume-liter', 'duration-hour', UnitLength::LONG, "12,345 litres par heure" ], - [ 'fr', 12.345, 'volume-liter', 'duration-hour', UnitLength::SHORT, "12,345 l/h" ], - [ 'fr', 12.345, 'volume-liter', 'duration-hour', UnitLength::NARROW, "12,345l/h" ], - - [ 'fr', 12.345, 'volume-liter', 'area-square-meter', UnitLength::LONG, "12,345 litres par mètre carré" ], - [ 'fr', 12.345, 'angle-revolution', 'length-light-year', UnitLength::LONG, "12,345 tours par années-lumière" ], - - ]; - } - - private function units_for(string $locale_id): Units - { - return locale_for($locale_id)->units; - } + use StringHelpers; + + public function test_to_string(): void + { + $stu = new NumberWithUnit(123.4504, 'digital-gigabyte', $this->units_for('fr')); + + $this->assertSame("123,45 gigaoctets", (string)$stu); + } + + /** + * @dataProvider provide_test_cases + * + * @param float|int|numeric-string $number + */ + public function test_cases( + string $locale, + string $unit, + float|int|string $number, + UnitLength $length, + string $expected + ): void { + $stu = new NumberWithUnit($number, $unit, $this->units_for($locale)); + + $this->assertSame($expected, $stu->{'as_' . $length->value}); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_cases(): array + { + return [ + + [ + 'fr', + 'acceleration-g-force', + 123.4504, + UnitLength::LONG, + "123,45 fois l’accélération de pesanteur terrestre" + ], + [ 'fr', 'digital-gigabyte', 123.4504, UnitLength::LONG, "123,45 gigaoctets" ], + [ 'fr', 'digital-gigabyte', 123.4504, UnitLength::SHORT, "123,45 Go" ], + [ 'fr', 'digital-gigabyte', 123.4504, UnitLength::NARROW, "123,45Go" ], + [ 'fr', 'duration-hour', 123.4504, UnitLength::LONG, "123,45 heures" ], + [ 'fr', 'duration-hour', 123.4504, UnitLength::SHORT, "123,45 h" ], + [ 'fr', 'duration-hour', 123.4504, UnitLength::NARROW, "123,45h" ], + + ]; + } + + /** + * @dataProvider provide_per + * + * @param float|int|numeric-string $number + */ + public function test_per( + string $locale, + float|int|string $number, + string $number_unit, + string $per_unit, + UnitLength $length, + string $expected + ): void { + $stu = new NumberWithUnit($number, $number_unit, $this->units_for($locale)); + + $this->assertSame( + $expected, + $stu->per($per_unit)->{'as_' . $length->value} + ); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_per(): array + { + return [ + + [ 'en', 12.345, 'volume-liter', 'duration-hour', UnitLength::LONG, "12.345 liters per hour" ], + [ 'en', 12.345, 'volume-liter', 'duration-hour', UnitLength::SHORT, "12.345 L/h" ], + [ 'en', 12.345, 'volume-liter', 'duration-hour', UnitLength::NARROW, "12.345L/h" ], + + [ 'fr', 12.345, 'volume-liter', 'duration-hour', UnitLength::LONG, "12,345 litres par heure" ], + [ 'fr', 12.345, 'volume-liter', 'duration-hour', UnitLength::SHORT, "12,345 l/h" ], + [ 'fr', 12.345, 'volume-liter', 'duration-hour', UnitLength::NARROW, "12,345l/h" ], + + [ 'fr', 12.345, 'volume-liter', 'area-square-meter', UnitLength::LONG, "12,345 litres par mètre carré" ], + [ + 'fr', + 12.345, + 'angle-revolution', + 'length-light-year', + UnitLength::LONG, + "12,345 tours par années-lumière" + ], + + ]; + } + + private function units_for(string $locale_id): Units + { + return locale_for($locale_id)->units; + } } diff --git a/tests/Units/SequenceTest.php b/tests/Units/SequenceTest.php index 378eed3..1d91127 100644 --- a/tests/Units/SequenceTest.php +++ b/tests/Units/SequenceTest.php @@ -9,47 +9,47 @@ final class SequenceTest extends TestCase { - public function test_format(): void - { - $unit = "digital-megabyte"; - $method = strtr($unit, '-', '_'); - $number = mt_rand(100, 200); - $expected = uniqid(); - $length = UnitLength::NARROW; - - $units = $this->getMockBuilder(Units::class) - ->onlyMethods([ 'format_sequence' ]) - ->disableOriginalConstructor() - ->getMock(); - $units - ->expects($this->once()) - ->method('format_sequence') - ->with([ $unit => $number ], $length) - ->willReturn($expected); - - $unit = new Sequence($units); - $this->assertSame($expected, $unit->$method($number)->format($length)); - } - - public function test_to_string(): void - { - $unit = "digital-megabyte"; - $method = strtr($unit, '-', '_'); - $number = mt_rand(100, 200); - $expected = uniqid(); - $length = Units::DEFAULT_LENGTH; - - $units = $this->getMockBuilder(Units::class) - ->onlyMethods([ 'format_sequence' ]) - ->disableOriginalConstructor() - ->getMock(); - $units - ->expects($this->once()) - ->method('format_sequence') - ->with([ $unit => $number ], $length) - ->willReturn($expected); - - $unit = new Sequence($units); - $this->assertSame($expected, (string)$unit->$method($number)); - } + public function test_format(): void + { + $unit = "digital-megabyte"; + $method = strtr($unit, '-', '_'); + $number = mt_rand(100, 200); + $expected = uniqid(); + $length = UnitLength::NARROW; + + $units = $this->getMockBuilder(Units::class) + ->onlyMethods([ 'format_sequence' ]) + ->disableOriginalConstructor() + ->getMock(); + $units + ->expects($this->once()) + ->method('format_sequence') + ->with([ $unit => $number ], $length) + ->willReturn($expected); + + $unit = new Sequence($units); + $this->assertSame($expected, $unit->$method($number)->format($length)); + } + + public function test_to_string(): void + { + $unit = "digital-megabyte"; + $method = strtr($unit, '-', '_'); + $number = mt_rand(100, 200); + $expected = uniqid(); + $length = Units::DEFAULT_LENGTH; + + $units = $this->getMockBuilder(Units::class) + ->onlyMethods([ 'format_sequence' ]) + ->disableOriginalConstructor() + ->getMock(); + $units + ->expects($this->once()) + ->method('format_sequence') + ->with([ $unit => $number ], $length) + ->willReturn($expected); + + $unit = new Sequence($units); + $this->assertSame($expected, (string)$unit->$method($number)); + } } diff --git a/tests/Units/UnitTest.php b/tests/Units/UnitTest.php index 99609ff..382bf6e 100644 --- a/tests/Units/UnitTest.php +++ b/tests/Units/UnitTest.php @@ -10,42 +10,42 @@ final class UnitTest extends TestCase { - #[DataProvider('provide_test_properties')] - public function test_properties(string $unit, string $property, UnitLength $length, string $expected): void - { - $units = $this->getMockBuilder(Units::class) - ->disableOriginalConstructor() - ->onlyMethods([ 'name_for' ]) - ->getMock(); - - $units - ->expects($this->once()) - ->method('name_for') - ->with($unit, $length) - ->willReturn($expected); - - $this->assertSame($expected, (new Unit($units, $unit))->$property); - } - - public static function provide_test_properties(): array - { - return [ - - [ 'acceleration-g-force', 'name', UnitLength::LONG, "fois la gravitation terrestre" ], - [ 'acceleration-g-force', 'long_name', UnitLength::LONG, "fois la gravitation terrestre" ], - [ 'acceleration-g-force', 'short_name', UnitLength::SHORT, "G" ], - [ 'acceleration-g-force', 'narrow_name', UnitLength::NARROW, "G" ], - - ]; - } - - public function test_to_string(): void - { - $unit = uniqid(); - $units = $this->getMockBuilder(Units::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->assertSame($unit, (string) new Unit($units, $unit)); - } + #[DataProvider('provide_test_properties')] + public function test_properties(string $unit, string $property, UnitLength $length, string $expected): void + { + $units = $this->getMockBuilder(Units::class) + ->disableOriginalConstructor() + ->onlyMethods([ 'name_for' ]) + ->getMock(); + + $units + ->expects($this->once()) + ->method('name_for') + ->with($unit, $length) + ->willReturn($expected); + + $this->assertSame($expected, (new Unit($units, $unit))->$property); + } + + public static function provide_test_properties(): array + { + return [ + + [ 'acceleration-g-force', 'name', UnitLength::LONG, "fois la gravitation terrestre" ], + [ 'acceleration-g-force', 'long_name', UnitLength::LONG, "fois la gravitation terrestre" ], + [ 'acceleration-g-force', 'short_name', UnitLength::SHORT, "G" ], + [ 'acceleration-g-force', 'narrow_name', UnitLength::NARROW, "G" ], + + ]; + } + + public function test_to_string(): void + { + $unit = uniqid(); + $units = $this->getMockBuilder(Units::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->assertSame($unit, (string)new Unit($units, $unit)); + } } diff --git a/tests/UnitsTest.php b/tests/UnitsTest.php index 22b3acd..37d6c4a 100644 --- a/tests/UnitsTest.php +++ b/tests/UnitsTest.php @@ -10,245 +10,245 @@ final class UnitsTest extends TestCase { - use StringHelpers; - - /** - * @param float|int|numeric-string $number - */ - #[DataProvider('provide_test_cases')] - public function test_cases( - string $locale, - string $unit, - float|int|string $number, - UnitLength $length, - string $expected - ): void { - $actual = $this->units_for($locale)->$unit($number)->{'as_' . $length->value}; - - $this->assertSame($expected, $actual); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_cases(): array - { - return [ - - [ - 'fr', - 'acceleration_g_force', - 123.4504, - UnitLength::LONG, - "123,45 fois l’accélération de pesanteur terrestre" - ], - [ 'fr', 'digital_gigabyte', 123.4504, UnitLength::LONG, "123,45 gigaoctets" ], - [ 'fr', 'digital_gigabyte', 123.4504, UnitLength::SHORT, "123,45 Go" ], - [ 'fr', 'digital_gigabyte', 123.4504, UnitLength::NARROW, "123,45Go" ], - [ 'fr', 'duration_hour', 123.4504, UnitLength::LONG, "123,45 heures" ], - [ 'fr', 'duration_hour', 123.4504, UnitLength::SHORT, "123,45 h" ], - [ 'fr', 'duration_hour', 123.4504, UnitLength::NARROW, "123,45h" ], - - ]; - } - - /** - * @param float|int|numeric-string $number - */ - #[DataProvider('provide_test_format_compound')] - public function test_format_compound( - string $locale, - float|int|string $number, - string $number_unit, - string $per_unit, - UnitLength $length, - string $expected - ): void { - $actual = $this->units_for($locale)->format_compound($number, $number_unit, $per_unit, $length); - - $this->assertSame($expected, $actual); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_format_compound(): array - { - return [ - - [ 'en', 12.345, 'volume-liter', 'duration-hour', UnitLength::LONG, "12.345 liters per hour" ], - [ 'en', 12.345, 'volume-liter', 'duration-hour', UnitLength::SHORT, "12.345 L/h" ], - [ 'en', 12.345, 'volume-liter', 'duration-hour', UnitLength::NARROW, "12.345L/h" ], - - [ 'fr', 12.345, 'volume-liter', 'duration-hour', UnitLength::LONG, "12,345 litres par heure" ], - [ 'fr', 12.345, 'volume-liter', 'duration-hour', UnitLength::SHORT, "12,345 l/h" ], - [ 'fr', 12.345, 'volume-liter', 'duration-hour', UnitLength::NARROW, "12,345l/h" ], - - [ 'fr', 12.345, 'volume-liter', 'area-square-meter', UnitLength::LONG, "12,345 litres par mètre carré" ], - [ - 'fr', - 12.345, - 'angle-revolution', - 'length-light-year', - UnitLength::LONG, - "12,345 tours par années-lumière" - ], - - ]; - } - - #[DataProvider('provide_test_format_sequence')] - public function test_format_sequence(string $locale, callable $sequence, string $expected): void - { - $actual = $sequence($this->units_for($locale)); - - $this->assertStringSame($expected, $actual); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_test_format_sequence(): array - { - $s1 = Spaces::NARROW_NO_BREAK_SPACE; - $s2 = Spaces::NO_BREAK_SPACE; - - return [ - - [ - 'en', - fn(Units $units) => $units->sequence - ->angle_degree(5) - ->duration_minute(30) - ->as_long, - "5 degrees, 30 minutes" - ], - - [ - 'en', - fn(Units $units) => $units->sequence - ->angle_degree(5) - ->duration_minute(30) - ->as_narrow, - "5° 30m" - ], - - [ - 'en', - fn(Units $units) => $units->sequence - ->length_foot(3) - ->length_inch(2) - ->as_short, - "3 ft, 2 in" - ], - - [ - 'en', - fn(Units $units) => $units->sequence - ->length_foot(3) - ->length_inch(2) - ->as_narrow, - "3′ 2″" - ], - - [ - 'en', - fn(Units $units) => $units->sequence - ->duration_hour(12) - ->duration_minute(34) - ->duration_second(56) - ->as_long, - "12 hours, 34 minutes, 56 seconds" - ], - - [ - 'en', - fn(Units $units) => $units->sequence - ->duration_hour(12) - ->duration_minute(34) - ->duration_second(56) - ->as_short, - "12 hr, 34 min, 56 sec" - ], - - [ - 'en', - fn(Units $units) => $units->sequence - ->duration_hour(12) - ->duration_minute(34) - ->duration_second(56) - ->as_narrow, - "12h 34m 56s" - ], - - [ - 'fr', - fn(Units $units) => $units->sequence - ->duration_hour(12) - ->duration_minute(34) - ->duration_second(56) - ->as_long, - "12{$s2}heures, 34 minutes et 56{$s2}secondes" - ], - - [ - 'fr', - fn(Units $units) => $units->sequence - ->duration_hour(12) - ->duration_minute(34) - ->duration_second(56) - ->as_short, - "12{$s1}h, 34{$s2}min et 56{$s1}s" - ], - - [ - 'fr', - fn(Units $units) => $units->sequence - ->duration_hour(12) - ->duration_minute(34) - ->duration_second(56) - ->as_narrow, - "12h 34min 56s" - ], - - ]; - } - - #[DataProvider('provide_name_for')] - public function test_name_for(string $unit, UnitLength $length, string $expected_name): void - { - $actual = $this->units_for('fr')->name_for($unit, $length); - - $this->assertSame($expected_name, $actual); - } - - /** - * @phpstan-ignore-next-line - */ - public static function provide_name_for(): array - { - return [ - - [ 'angle_degree', UnitLength::LONG, "degrés" ], - [ 'angle_degree', UnitLength::SHORT, "°" ], - [ 'angle_degree', UnitLength::NARROW, "°" ], - [ 'digital-megabyte', UnitLength::LONG, "mégaoctets" ], - [ 'digital-megabyte', UnitLength::SHORT, "Mo" ], - [ 'digital-megabyte', UnitLength::NARROW, "Mo" ], - - ]; - } - - public function test_getter(): void - { - $units = $this->units_for('fr'); - $unit = $units->angle_degree; - - $this->assertSame($unit, $units->angle_degree); - } - - private function units_for(string $locale_id): Units - { - return new Units(locale_for($locale_id)); - } + use StringHelpers; + + /** + * @param float|int|numeric-string $number + */ + #[DataProvider('provide_test_cases')] + public function test_cases( + string $locale, + string $unit, + float|int|string $number, + UnitLength $length, + string $expected + ): void { + $actual = $this->units_for($locale)->$unit($number)->{'as_' . $length->value}; + + $this->assertSame($expected, $actual); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_cases(): array + { + return [ + + [ + 'fr', + 'acceleration_g_force', + 123.4504, + UnitLength::LONG, + "123,45 fois l’accélération de pesanteur terrestre" + ], + [ 'fr', 'digital_gigabyte', 123.4504, UnitLength::LONG, "123,45 gigaoctets" ], + [ 'fr', 'digital_gigabyte', 123.4504, UnitLength::SHORT, "123,45 Go" ], + [ 'fr', 'digital_gigabyte', 123.4504, UnitLength::NARROW, "123,45Go" ], + [ 'fr', 'duration_hour', 123.4504, UnitLength::LONG, "123,45 heures" ], + [ 'fr', 'duration_hour', 123.4504, UnitLength::SHORT, "123,45 h" ], + [ 'fr', 'duration_hour', 123.4504, UnitLength::NARROW, "123,45h" ], + + ]; + } + + /** + * @param float|int|numeric-string $number + */ + #[DataProvider('provide_test_format_compound')] + public function test_format_compound( + string $locale, + float|int|string $number, + string $number_unit, + string $per_unit, + UnitLength $length, + string $expected + ): void { + $actual = $this->units_for($locale)->format_compound($number, $number_unit, $per_unit, $length); + + $this->assertSame($expected, $actual); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_format_compound(): array + { + return [ + + [ 'en', 12.345, 'volume-liter', 'duration-hour', UnitLength::LONG, "12.345 liters per hour" ], + [ 'en', 12.345, 'volume-liter', 'duration-hour', UnitLength::SHORT, "12.345 L/h" ], + [ 'en', 12.345, 'volume-liter', 'duration-hour', UnitLength::NARROW, "12.345L/h" ], + + [ 'fr', 12.345, 'volume-liter', 'duration-hour', UnitLength::LONG, "12,345 litres par heure" ], + [ 'fr', 12.345, 'volume-liter', 'duration-hour', UnitLength::SHORT, "12,345 l/h" ], + [ 'fr', 12.345, 'volume-liter', 'duration-hour', UnitLength::NARROW, "12,345l/h" ], + + [ 'fr', 12.345, 'volume-liter', 'area-square-meter', UnitLength::LONG, "12,345 litres par mètre carré" ], + [ + 'fr', + 12.345, + 'angle-revolution', + 'length-light-year', + UnitLength::LONG, + "12,345 tours par années-lumière" + ], + + ]; + } + + #[DataProvider('provide_test_format_sequence')] + public function test_format_sequence(string $locale, callable $sequence, string $expected): void + { + $actual = $sequence($this->units_for($locale)); + + $this->assertStringSame($expected, $actual); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_test_format_sequence(): array + { + $s1 = Spaces::NARROW_NO_BREAK_SPACE; + $s2 = Spaces::NO_BREAK_SPACE; + + return [ + + [ + 'en', + fn(Units $units) => $units->sequence + ->angle_degree(5) + ->duration_minute(30) + ->as_long, + "5 degrees, 30 minutes" + ], + + [ + 'en', + fn(Units $units) => $units->sequence + ->angle_degree(5) + ->duration_minute(30) + ->as_narrow, + "5° 30m" + ], + + [ + 'en', + fn(Units $units) => $units->sequence + ->length_foot(3) + ->length_inch(2) + ->as_short, + "3 ft, 2 in" + ], + + [ + 'en', + fn(Units $units) => $units->sequence + ->length_foot(3) + ->length_inch(2) + ->as_narrow, + "3′ 2″" + ], + + [ + 'en', + fn(Units $units) => $units->sequence + ->duration_hour(12) + ->duration_minute(34) + ->duration_second(56) + ->as_long, + "12 hours, 34 minutes, 56 seconds" + ], + + [ + 'en', + fn(Units $units) => $units->sequence + ->duration_hour(12) + ->duration_minute(34) + ->duration_second(56) + ->as_short, + "12 hr, 34 min, 56 sec" + ], + + [ + 'en', + fn(Units $units) => $units->sequence + ->duration_hour(12) + ->duration_minute(34) + ->duration_second(56) + ->as_narrow, + "12h 34m 56s" + ], + + [ + 'fr', + fn(Units $units) => $units->sequence + ->duration_hour(12) + ->duration_minute(34) + ->duration_second(56) + ->as_long, + "12{$s2}heures, 34 minutes et 56{$s2}secondes" + ], + + [ + 'fr', + fn(Units $units) => $units->sequence + ->duration_hour(12) + ->duration_minute(34) + ->duration_second(56) + ->as_short, + "12{$s1}h, 34{$s2}min et 56{$s1}s" + ], + + [ + 'fr', + fn(Units $units) => $units->sequence + ->duration_hour(12) + ->duration_minute(34) + ->duration_second(56) + ->as_narrow, + "12h 34min 56s" + ], + + ]; + } + + #[DataProvider('provide_name_for')] + public function test_name_for(string $unit, UnitLength $length, string $expected_name): void + { + $actual = $this->units_for('fr')->name_for($unit, $length); + + $this->assertSame($expected_name, $actual); + } + + /** + * @phpstan-ignore-next-line + */ + public static function provide_name_for(): array + { + return [ + + [ 'angle_degree', UnitLength::LONG, "degrés" ], + [ 'angle_degree', UnitLength::SHORT, "°" ], + [ 'angle_degree', UnitLength::NARROW, "°" ], + [ 'digital-megabyte', UnitLength::LONG, "mégaoctets" ], + [ 'digital-megabyte', UnitLength::SHORT, "Mo" ], + [ 'digital-megabyte', UnitLength::NARROW, "Mo" ], + + ]; + } + + public function test_getter(): void + { + $units = $this->units_for('fr'); + $unit = $units->angle_degree; + + $this->assertSame($unit, $units->angle_degree); + } + + private function units_for(string $locale_id): Units + { + return new Units(locale_for($locale_id)); + } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 52014ab..e5f368a 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -21,50 +21,50 @@ const CACHE_DIR = __DIR__ . '/../' . FileCache::RECOMMENDED_DIR; if (!file_exists(CACHE_DIR)) { - mkdir(CACHE_DIR); + mkdir(CACHE_DIR); } function create_provider(): Provider { - static $provider; + static $provider; - if ($provider) { - return $provider; - } + if ($provider) { + return $provider; + } - $redis = new Redis(); - $host = getenv('ICANBOOGIE_CLDR_REDIS_HOST'); - $port = getenv('ICANBOOGIE_CLDR_REDIS_PORT'); + $redis = new Redis(); + $host = getenv('ICANBOOGIE_CLDR_REDIS_HOST'); + $port = getenv('ICANBOOGIE_CLDR_REDIS_PORT'); - assert($host !== false && strlen($host) > 0); - assert($port !== false && strlen($port) > 0); + assert($host !== false && strlen($host) > 0); + assert($port !== false && strlen($port) > 0); - if (!$redis->connect($host, (int)$port)) { - echo "Unable to connect to Redis"; + if (!$redis->connect($host, (int)$port)) { + echo "Unable to connect to Redis"; - exit(1); - } + exit(1); + } - return $provider = new CachedProvider( - new WebProvider, - new CacheCollection([ - new RuntimeCache(), - new RedisCache($redis), - new FileCache(CACHE_DIR) - ]) - ); + return $provider = new CachedProvider( + new WebProvider(), + new CacheCollection([ + new RuntimeCache(), + new RedisCache($redis), + new FileCache(CACHE_DIR) + ]) + ); } function get_repository(): Repository { - static $repository; + static $repository; - return $repository ??= new Repository(create_provider()); + return $repository ??= new Repository(create_provider()); } function locale_for(string|LocaleId $id): Locale { - return get_repository()->locale_for($id); + return get_repository()->locale_for($id); } date_default_timezone_set('Europe/Madrid');