From 588fc8af346ae5dc2251a361cdf04f793da1988d Mon Sep 17 00:00:00 2001 From: Birgir Haraldsson Date: Thu, 18 Dec 2025 12:39:17 +0100 Subject: [PATCH 01/13] Update phpunit.xml.dist Migrated with '--migrate-configuration' --- phpunit.xml.dist | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d23fe89..3e5d146 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,12 +1,14 @@ - - - - ./tests - - - - - ./src - - - \ No newline at end of file + + + + + ./src + + + + + ./tests + + + From 43eac1c280fc31e4d346e13c30660b6f428179a6 Mon Sep 17 00:00:00 2001 From: Birgir Haraldsson Date: Thu, 18 Dec 2025 12:39:55 +0100 Subject: [PATCH 02/13] Add formatTimestamp to Formatter interface --- src/Formatter.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Formatter.php b/src/Formatter.php index 9dc5d1e..a7f2f0f 100644 --- a/src/Formatter.php +++ b/src/Formatter.php @@ -5,4 +5,6 @@ interface Formatter { public function format(\DateTimeInterface $date): string; + + public function formatTimestamp(string $format, \DateTimeImmutable $timestamp): string; } From 539f372004476ceab02d79aeab422c8abfceca87 Mon Sep 17 00:00:00 2001 From: Birgir Haraldsson Date: Thu, 18 Dec 2025 12:40:28 +0100 Subject: [PATCH 03/13] Fix argument name in strftime formatter --- src/LocalizedStrftimeFormatter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LocalizedStrftimeFormatter.php b/src/LocalizedStrftimeFormatter.php index ccdeb98..51fe302 100644 --- a/src/LocalizedStrftimeFormatter.php +++ b/src/LocalizedStrftimeFormatter.php @@ -23,9 +23,9 @@ public function __construct( private string $format, ) {} - public function setLocale(string $local): void + public function setLocale(string $locale): void { - $this->locale = $local; + $this->locale = $locale; } public function setFormat(string $format): void From 6dbb5406308f047e4610ff5321e84412731aeb51 Mon Sep 17 00:00:00 2001 From: Birgir Haraldsson Date: Thu, 18 Dec 2025 12:40:44 +0100 Subject: [PATCH 04/13] Implement formatTimestamp in formatters --- src/DateTimeFormatter.php | 5 +++++ src/LocalizedStrftimeFormatter.php | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/src/DateTimeFormatter.php b/src/DateTimeFormatter.php index a9deb24..592111c 100644 --- a/src/DateTimeFormatter.php +++ b/src/DateTimeFormatter.php @@ -17,4 +17,9 @@ public function setFormat(string $format): void { $this->format = $format; } + + public function formatTimestamp(string $format, \DateTimeImmutable $timestamp): string + { + return $timestamp->format($format); + } } diff --git a/src/LocalizedStrftimeFormatter.php b/src/LocalizedStrftimeFormatter.php index 51fe302..6a53c70 100644 --- a/src/LocalizedStrftimeFormatter.php +++ b/src/LocalizedStrftimeFormatter.php @@ -39,6 +39,12 @@ public function format(\DateTimeInterface $date): string return $this->strftime($this->format, $date, $this->locale); } + public function formatTimestamp(string $format, \DateTimeImmutable $timestamp): string + { + $date = DateTimeImmutable::createFromInterface($timestamp); + return $this->strftime($format, $date, $this->locale); + } + private function strftime(string $format, DateTimeImmutable $timestamp, string $local): string { // remove trailing part not supported by ext-intl locale From 3020e5291acf5fa60a76063dd7eb29b69d727c6e Mon Sep 17 00:00:00 2001 From: Birgir Haraldsson Date: Thu, 18 Dec 2025 13:06:21 +0100 Subject: [PATCH 05/13] Add en_US locale --- src/Locales/EnUs.php | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/Locales/EnUs.php diff --git a/src/Locales/EnUs.php b/src/Locales/EnUs.php new file mode 100644 index 0000000..84c3bac --- /dev/null +++ b/src/Locales/EnUs.php @@ -0,0 +1,29 @@ + 'g:i A', + 'LTS' => 'g:i:s A', + 'L' => 'n/j/y', + 'LL' => 'F j, Y', + 'll' => 'M j, Y', + 'LLL' => 'F j, Y [at] g:i A', + 'lll' => 'M j, Y, g:i A', + 'LLLL' => 'l, F j, Y [at] g:i A', + 'llll' => 'D, M j, Y, g:i A', + default => null, + }; + } + + public function formatChar(string $char, \DateTimeImmutable $dateTime): ?string + { + return null; + } +} From b09943d78591df1fc34fcfef9a0cb43c21421391 Mon Sep 17 00:00:00 2001 From: Birgir Haraldsson Date: Thu, 18 Dec 2025 13:08:40 +0100 Subject: [PATCH 06/13] Change localized DT formatter to be comparable with Intl Because the intl extension with the IntlDateFormatter is extremely slow, we have this class. But until now, we were not quite comparable with the actual output of the intl formatter. --- src/LocalizedDateTimeFormatter.php | 42 ++++++++++++++++++------------ 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/LocalizedDateTimeFormatter.php b/src/LocalizedDateTimeFormatter.php index 962dc41..7178728 100644 --- a/src/LocalizedDateTimeFormatter.php +++ b/src/LocalizedDateTimeFormatter.php @@ -4,6 +4,18 @@ final class LocalizedDateTimeFormatter implements LocalizedFormatter, MutableFormatter { + private const LOCALIZED_SHORT_FORMATS = [ + // Sorting this list correctly is important because of how we replace from it + 'LTS', + 'LT', + 'LLLL', + 'llll', + 'LLL', + 'lll', + 'LL', + 'll', + 'L', + ]; /** @var array */ private static array $localFormats = []; @@ -34,11 +46,21 @@ public function setLocale(string $locale): void public function format(\DateTimeInterface $date): string { - return $this->formatTimestamp($this->format, \DateTimeImmutable::createFromInterface($date), $this->locale); + return $this->formatTimestamp($this->format, \DateTimeImmutable::createFromInterface($date)); } - public function formatTimestamp(string $format, \DateTimeImmutable $timestamp, string $locale): string + public function formatTimestamp(string $format, \DateTimeImmutable $timestamp): string { + foreach (self::LOCALIZED_SHORT_FORMATS as $localizedFormat) { + if (!str_contains($format, $localizedFormat)) { + continue; + } + $tmpFormat = $this->localeFormat->resolveFormat($localizedFormat); + if ($tmpFormat !== null) { + $format = str_replace($localizedFormat, $tmpFormat, $format); + } + } + $result = ''; $length = mb_strlen($format); $inEscaped = false; @@ -70,25 +92,11 @@ public function formatTimestamp(string $format, \DateTimeImmutable $timestamp, s continue; } $localResult = $this->localeFormat->formatChar($char, $timestamp); - if ($localResult) { + if ($localResult) { $result .= $localResult; continue; } - $input = mb_substr($format, $i); - if ($char === 'L' && preg_match('/^(LTS|LT|L{1,4})/', $input, $match)) { - $code = $match[0]; - $newFormat = $this->localeFormat->resolveFormat($code); - if ($newFormat) { - $result .= $this->formatTimestamp($newFormat, $timestamp, $locale); - } - else { - $result .= $code; - } - $i += mb_strlen($code) - 1; - continue; - } - $result .= $timestamp->format($char); } return $result; From 347b3466fbea277245db520ceb45ac6d7a501666 Mon Sep 17 00:00:00 2001 From: Birgir Haraldsson Date: Thu, 18 Dec 2025 13:09:39 +0100 Subject: [PATCH 07/13] Update locales to be comparable with intl --- src/Locales/EnGb.php | 12 ++++++-- src/Locales/IsIs.php | 45 +++++++++++++++------------- src/Locales/SvSe.php | 71 +++++++++++++++++++++++--------------------- 3 files changed, 70 insertions(+), 58 deletions(-) diff --git a/src/Locales/EnGb.php b/src/Locales/EnGb.php index 22be010..a76f419 100644 --- a/src/Locales/EnGb.php +++ b/src/Locales/EnGb.php @@ -8,19 +8,25 @@ final class EnGb implements LocaleFormat { public function resolveFormat(string $format): ?string { - return match($format) { + return match ($format) { 'LT' => 'H:i', 'LTS' => 'H:i:s', 'L' => 'd/m/Y', 'LL' => 'j F Y', - 'LLL' => 'j F H:i', - 'LLLL' => 'l, j F Y H:i', + 'll' => 'j M Y', + 'LLL' => 'j F Y [at] H:i', + 'lll' => 'j M Y, H:i', + 'LLLL' => 'l, j F Y [at] H:i', + 'llll' => 'D, j M Y, H:i', default => null, }; } public function formatChar(string $char, \DateTimeImmutable $dateTime): ?string { + if ($char === 'M' && $dateTime->format('n') === '9') { + return 'Sept'; + } return null; } } diff --git a/src/Locales/IsIs.php b/src/Locales/IsIs.php index 2c6cf39..711ea5e 100644 --- a/src/Locales/IsIs.php +++ b/src/Locales/IsIs.php @@ -7,13 +7,13 @@ final class IsIs implements LocaleFormat { private const DAYS_SHORT = [ - 1 => 'mán', - 2 => 'þri', - 3 => 'mið', - 4 => 'fim', - 5 => 'fös', - 6 => 'lau', - 7 => 'sun', + 1 => 'mán.', + 2 => 'þri.', + 3 => 'mið.', + 4 => 'fim.', + 5 => 'fös.', + 6 => 'lau.', + 7 => 'sun.', ]; private const DAYS_LONG = [ @@ -27,18 +27,18 @@ final class IsIs implements LocaleFormat ]; private const MONTHS_SHORT = [ - 1 => 'jan', - 2 => 'feb', - 3 => 'mar', - 4 => 'apr', + 1 => 'jan.', + 2 => 'feb.', + 3 => 'mar.', + 4 => 'apr.', 5 => 'maí', - 6 => 'jún', - 7 => 'júl', - 8 => 'ágú', - 9 => 'sep', - 10 => 'okt', - 11 => 'nóv', - 12 => 'des', + 6 => 'jún.', + 7 => 'júl.', + 8 => 'ágú.', + 9 => 'sep.', + 10 => 'okt.', + 11 => 'nóv.', + 12 => 'des.', ]; private const MONTHS_LONG = [ @@ -72,10 +72,13 @@ public function resolveFormat(string $format): ?string return match($format) { 'LT' => 'H:i', 'LTS' => 'H:i:s', - 'L' => 'd.m.Y', + 'L' => 'j.n.Y', 'LL' => 'j. F Y', - 'LLL' => 'j. F [kl.] H:i', - 'LLLL' => 'l j. F Y [kl.] H:i', + 'll' => 'j. M Y', + 'LLL' => 'j. F Y [kl.] H:i', + 'lll' => 'j. M Y, H:i', + 'LLLL' => 'l, j. F Y [kl.] H:i', + 'llll' => 'D j. M Y, H:i', default => null, }; } diff --git a/src/Locales/SvSe.php b/src/Locales/SvSe.php index 80391b1..c6dd9ce 100644 --- a/src/Locales/SvSe.php +++ b/src/Locales/SvSe.php @@ -7,53 +7,53 @@ final class SvSe implements LocaleFormat { private const MONTHS_SHORT = [ - 1 => 'Jan', - 2 => 'Feb', - 3 => 'Mar', - 4 => 'Apr', - 5 => 'Maj', - 6 => 'Jun', - 7 => 'Jul', - 8 => 'Aug', - 9 => 'Sep', - 10 => 'Okt', - 11 => 'Nov', - 12 => 'Dec', + 1 => 'jan.', + 2 => 'feb.', + 3 => 'mars', + 4 => 'apr.', + 5 => 'maj', + 6 => 'juni', + 7 => 'juli', + 8 => 'aug.', + 9 => 'sep.', + 10 => 'okt.', + 11 => 'nov.', + 12 => 'dec.', ]; private const MONTHS_LONG = [ - 1 => 'Januari', - 2 => 'Februari', - 3 => 'Mars', - 4 => 'April', - 5 => 'Maj', - 6 => 'Juni', - 7 => 'Juli', - 8 => 'Augusti', - 9 => 'September', - 10 => 'Oktober', - 11 => 'November', - 12 => 'December', + 1 => 'januari', + 2 => 'februari', + 3 => 'mars', + 4 => 'april', + 5 => 'maj', + 6 => 'juni', + 7 => 'juli', + 8 => 'augusti', + 9 => 'september', + 10 => 'oktober', + 11 => 'november', + 12 => 'december', ]; private const DAYS_SHORT = [ 1 => 'mån', 2 => 'tis', 3 => 'ons', - 4 => 'tor', + 4 => 'tors', 5 => 'fre', 6 => 'lör', 7 => 'sön', ]; private const DAYS_LONG = [ - 1 => 'Måndag', - 2 => 'Tisdag', - 3 => 'Onsdag', - 4 => 'Torsdag', - 5 => 'Fredag', - 6 => 'Lördag', - 7 => 'Söndag', + 1 => 'måndag', + 2 => 'tisdag', + 3 => 'onsdag', + 4 => 'torsdag', + 5 => 'fredag', + 6 => 'lördag', + 7 => 'söndag', ]; public function formatChar(string $char, \DateTimeImmutable $dateTime): ?string @@ -72,10 +72,13 @@ public function resolveFormat(string $format): ?string return match($format) { 'LT' => 'H:i', 'LTS' => 'H:i:s', - 'L' => 'd.m.Y', + 'L' => 'Y-m-d', 'LL' => 'j F Y', - 'LLL' => 'j F [kl.] H:i', + 'll' => 'j M Y', + 'LLL' => 'j F Y [kl.] H:i', + 'lll' => 'j M Y H:i', 'LLLL' => 'l j F Y [kl.] H:i', + 'llll' => 'D j M Y H:i', default => null, }; } From cb5215462fe7afdd2e1dc2a2ebe2f2d5cc00980c Mon Sep 17 00:00:00 2001 From: Birgir Haraldsson Date: Thu, 18 Dec 2025 13:10:01 +0100 Subject: [PATCH 08/13] Fix test after being intl comparable --- tests/LocalizedDateTimeFormatterTest.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/LocalizedDateTimeFormatterTest.php b/tests/LocalizedDateTimeFormatterTest.php index ddbbd1e..882352f 100644 --- a/tests/LocalizedDateTimeFormatterTest.php +++ b/tests/LocalizedDateTimeFormatterTest.php @@ -1,7 +1,6 @@ assertSame('mánudagur 21. ágúst kl. 16:26 Test', $formatter->format($date)); + $this->assertSame('mánudagur 21. ágúst 2023 kl. 16:26 Test', $formatter->format($date)); } public function testEnglishFormat(): void @@ -23,6 +22,6 @@ public function testEnglishFormat(): void $date = new DateTimeImmutable('2023-08-21 16:26:14'); - $this->assertSame('Monday 21 August 16:26 Test', $formatter->format($date)); + $this->assertSame('Monday 21 August 2023 at 16:26 Test', $formatter->format($date)); } } From 1b80dc0e4e2df5893b80fd87ec81d0b2d4704537 Mon Sep 17 00:00:00 2001 From: Birgir Haraldsson Date: Thu, 18 Dec 2025 13:13:43 +0100 Subject: [PATCH 09/13] Add test to make sure we compare with intl Using only the "type" arguments to the intl formatter, we can mimic the special localized formats (that are not a part of DateTime formatting). Only the "llll" format does not work that way, and we need a pattern to test that out. Links at the top of the file can be used to verify. --- tests/IntlComparisonTest.php | 241 +++++++++++++++++++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 tests/IntlComparisonTest.php diff --git a/tests/IntlComparisonTest.php b/tests/IntlComparisonTest.php new file mode 100644 index 0000000..066514c --- /dev/null +++ b/tests/IntlComparisonTest.php @@ -0,0 +1,241 @@ + [IntlDateFormatter::NONE, IntlDateFormatter::SHORT], + 'LTS' => [IntlDateFormatter::NONE, IntlDateFormatter::MEDIUM], + 'L' => [IntlDateFormatter::SHORT, IntlDateFormatter::NONE], + 'LL' => [IntlDateFormatter::LONG, IntlDateFormatter::NONE], + 'll' => [IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE], + 'LLL' => [IntlDateFormatter::LONG, IntlDateFormatter::SHORT], + 'lll' => [IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT], + 'LLLL' => [IntlDateFormatter::FULL, IntlDateFormatter::SHORT], + // This is not available via intl formats without setting a pattern + // 'llll' => [], + ]; + + /** + * @return array + */ + public static function provideLocales(): array + { + return [ + 'en' => ['en_US', new EnUs()], + 'uk' => ['en_GB', new EnGb()], + 'is' => ['is_IS', new IsIs()], + 'se' => ['sv_SE', new SvSe()], + ]; + } + + /** + * @return array + */ + public static function provideMonths(): array + { + $ret = []; + foreach (self::provideLocales() as $key => $args) { + for ($month = 1; $month <= 12; $month++) { + $args[2] = $month; + $ret["$key-$month"] = $args; + } + } + return $ret; + } + + /** + * @return array + */ + public static function provideWeekdays(): array + { + $ret = []; + foreach (self::provideLocales() as $key => $args) { + for ($wd = 1; $wd <= 7; $wd++) { + $args[2] = $wd; + $ret["$key-$wd"] = $args; + } + } + return $ret; + } + + /** + * @return array + */ + public static function provideFormats(): array + { + $ret = []; + foreach (self::provideLocales() as $key => $args) { + foreach (array_keys(self::INTL_FORMAT_MAP) as $k) { + $args[2] = $k; + $ret["$key-$k"] = $args; + } + } + return $ret; + } + + /** + * @dataProvider provideMonths + */ + public function testFullMonth(string $locale, LocaleFormat $localeFormat, int $month): void + { + LocalizedDateTimeFormatter::addLocaleFormat($locale, $localeFormat); + $formatter = new LocalizedDateTimeFormatter($locale, 'F'); + $intlFormatter = new IntlDateFormatter($locale, IntlDateFormatter::LONG, IntlDateFormatter::SHORT, null, null, 'MMMM'); + + $m = $this->pad($month); + $date = new DateTimeImmutable("2023-$m-03 11:11:11"); + + $this->assertSame( + $intlFormatter->format($date), + $formatter->format($date), + ); + } + + /** + * @dataProvider provideMonths + */ + public function testAbbrMonth(string $locale, LocaleFormat $localeFormat, int $month): void + { + LocalizedDateTimeFormatter::addLocaleFormat($locale, $localeFormat); + $formatter = new LocalizedDateTimeFormatter($locale, 'M'); + $intlFormatter = new IntlDateFormatter($locale, IntlDateFormatter::LONG, IntlDateFormatter::SHORT, null, null, 'MMM'); + + $m = $this->pad($month); + $date = new DateTimeImmutable("2023-$m-03 11:11:11"); + + $this->assertSame( + $intlFormatter->format($date), + $formatter->format($date), + ); + } + + /** + * @dataProvider provideWeekdays + */ + public function testFullWeekday(string $locale, LocaleFormat $localeFormat, int $weekday): void + { + LocalizedDateTimeFormatter::addLocaleFormat($locale, $localeFormat); + $formatter = new LocalizedDateTimeFormatter($locale, 'l'); + $intlFormatter = new IntlDateFormatter($locale, IntlDateFormatter::LONG, IntlDateFormatter::SHORT, null, null, 'EEEE'); + + $w = $this->pad($weekday); + $date = new DateTimeImmutable("2025-12-$w 11:11:11"); + + $this->assertSame( + $intlFormatter->format($date), + $formatter->format($date), + ); + } + + /** + * @dataProvider provideWeekdays + */ + public function testAbbrWeekday(string $locale, LocaleFormat $localeFormat, int $weekday): void + { + LocalizedDateTimeFormatter::addLocaleFormat($locale, $localeFormat); + $formatter = new LocalizedDateTimeFormatter($locale, 'D'); + $intlFormatter = new IntlDateFormatter($locale, IntlDateFormatter::LONG, IntlDateFormatter::SHORT, null, null, 'EEE'); + + $w = $this->pad($weekday); + $date = new DateTimeImmutable("2025-12-$w 11:11:11"); + + $this->assertSame( + $intlFormatter->format($date), + $formatter->format($date), + ); + } + + /** + * @dataProvider provideFormats + */ + public function testFormats(string $locale, LocaleFormat $localeFormat, string $format): void + { + LocalizedDateTimeFormatter::addLocaleFormat($locale, $localeFormat); + $formatter = new LocalizedDateTimeFormatter($locale, $format); + [$dateType, $timeType] = self::INTL_FORMAT_MAP[$format]; + + $intlFormatter = new IntlDateFormatter( + $locale, + $dateType, + $timeType, + ); + + foreach ($this->createDates() as $date) { + $this->assertSame( + $intlFormatter->format($date), + $formatter->format($date), + ); + } + } + + /** + * @dataProvider provideLocales + */ + public function testSpecialLocalFormat(string $locale, LocaleFormat $localeFormat): void + { + LocalizedDateTimeFormatter::addLocaleFormat($locale, $localeFormat); + $formatter = new LocalizedDateTimeFormatter($locale, 'llll'); + + $intlFormatter = new IntlDateFormatter( + $locale, + IntlDateFormatter::MEDIUM, + IntlDateFormatter::SHORT, + ); + + $dates = $this->createDates(); + foreach ($dates as $date) { + // This should just contain the same as "lll" + $this->assertStringContainsString( + (string)$intlFormatter->format($date), + $formatter->format($date), + ); + } + $intlFormatter->setPattern('eee'); + foreach ($dates as $date) { + $this->assertStringContainsString( + (string)$intlFormatter->format($date), + $formatter->format($date), + ); + } + } + + /** + * @return list + */ + private function createDates(): array + { + $date = new DateTimeImmutable('2025-01-03 09:11:11'); + $dates = [ + $date, + $date->add(new \DateInterval('PT12H')), + ]; + + for ($i = 1; $i <= 7; $i++) { + $dates[] = $date = $date->add(new \DateInterval('P1D')); + } + for ($i = 1; $i <= 12; $i++) { + $dates[] = $date = $date->add(new \DateInterval('P1M')); + } + return $dates; + } + + private function pad(int $month): string + { + return str_pad((string)$month, 2, '0', STR_PAD_LEFT); + } +} From 68eaa09106a38a468046e5f7dd532bdcdc0ffdfb Mon Sep 17 00:00:00 2001 From: Birgir Haraldsson Date: Fri, 19 Dec 2025 09:40:31 +0100 Subject: [PATCH 10/13] Skip intl comparison tests if ICU Data version < 76 Because we compare with the v76 version, we need to skip the comparison tests if the version isn't correct --- tests/IntlComparisonTest.php | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/IntlComparisonTest.php b/tests/IntlComparisonTest.php index 066514c..6ad26a5 100644 --- a/tests/IntlComparisonTest.php +++ b/tests/IntlComparisonTest.php @@ -88,6 +88,12 @@ public static function provideFormats(): array return $ret; } + public static function setUpBeforeClass(): void + { + parent::setUpBeforeClass(); + self::checkIcuDataVersion(); + } + /** * @dataProvider provideMonths */ @@ -214,6 +220,26 @@ public function testSpecialLocalFormat(string $locale, LocaleFormat $localeForma } } + private static function checkIcuDataVersion(): void + { + try { + $reflector = new \ReflectionExtension('intl'); + ob_start(); + $reflector->info(); + $output = strip_tags((string)ob_get_clean()); + preg_match('/^ICU Data version (?:=>)?(.*)$/m', $output, $matches); + $icuDataVersion = trim($matches[1]); + } catch (\ReflectionException) { + $icuDataVersion = ''; + } + if ((int)$icuDataVersion < 76) { + self::markTestSkipped(sprintf( + 'Intl comparison tests expect ICU Data version to be at least 76. Current is %s', + $icuDataVersion, + )); + } + } + /** * @return list */ From f19158b2ae3f1c05dca201237d2e5882860253c5 Mon Sep 17 00:00:00 2001 From: Birgir Haraldsson Date: Fri, 19 Dec 2025 11:07:27 +0100 Subject: [PATCH 11/13] Bump php versions for unit tests actions --- .github/workflows/unit-test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index d73a185..8d4edf3 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -21,10 +21,11 @@ jobs: - "8.2" - "8.3" - "8.4" + - "8.5" experimental: - false include: - - php-version: "8.5" + - php-version: "8.6" composer-options: "--ignore-platform-reqs" experimental: true From 629b5785bc862adb6ae009f505c994776c0a1af8 Mon Sep 17 00:00:00 2001 From: Birgir Haraldsson Date: Tue, 13 Jan 2026 11:50:32 +0100 Subject: [PATCH 12/13] Rename formatTimestamp to formatDate Also, use DateTimeInterface instead of Immutable --- src/DateTimeFormatter.php | 4 ++-- src/Formatter.php | 2 +- src/LocalizedDateTimeFormatter.php | 5 +++-- src/LocalizedStrftimeFormatter.php | 5 ++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/DateTimeFormatter.php b/src/DateTimeFormatter.php index 592111c..9000689 100644 --- a/src/DateTimeFormatter.php +++ b/src/DateTimeFormatter.php @@ -18,8 +18,8 @@ public function setFormat(string $format): void $this->format = $format; } - public function formatTimestamp(string $format, \DateTimeImmutable $timestamp): string + public function formatDate(string $format, \DateTimeInterface $date): string { - return $timestamp->format($format); + return $date->format($format); } } diff --git a/src/Formatter.php b/src/Formatter.php index a7f2f0f..e766b42 100644 --- a/src/Formatter.php +++ b/src/Formatter.php @@ -6,5 +6,5 @@ interface Formatter { public function format(\DateTimeInterface $date): string; - public function formatTimestamp(string $format, \DateTimeImmutable $timestamp): string; + public function formatDate(string $format, \DateTimeInterface $date): string; } diff --git a/src/LocalizedDateTimeFormatter.php b/src/LocalizedDateTimeFormatter.php index 7178728..d47c343 100644 --- a/src/LocalizedDateTimeFormatter.php +++ b/src/LocalizedDateTimeFormatter.php @@ -46,11 +46,12 @@ public function setLocale(string $locale): void public function format(\DateTimeInterface $date): string { - return $this->formatTimestamp($this->format, \DateTimeImmutable::createFromInterface($date)); + return $this->formatDate($this->format, $date); } - public function formatTimestamp(string $format, \DateTimeImmutable $timestamp): string + public function formatDate(string $format, \DateTimeInterface $date): string { + $timestamp = \DateTimeImmutable::createFromInterface($date); foreach (self::LOCALIZED_SHORT_FORMATS as $localizedFormat) { if (!str_contains($format, $localizedFormat)) { continue; diff --git a/src/LocalizedStrftimeFormatter.php b/src/LocalizedStrftimeFormatter.php index 6a53c70..39d8276 100644 --- a/src/LocalizedStrftimeFormatter.php +++ b/src/LocalizedStrftimeFormatter.php @@ -39,10 +39,9 @@ public function format(\DateTimeInterface $date): string return $this->strftime($this->format, $date, $this->locale); } - public function formatTimestamp(string $format, \DateTimeImmutable $timestamp): string + public function formatDate(string $format, \DateTimeInterface $date): string { - $date = DateTimeImmutable::createFromInterface($timestamp); - return $this->strftime($format, $date, $this->locale); + return $this->strftime($format, DateTimeImmutable::createFromInterface($date), $this->locale); } private function strftime(string $format, DateTimeImmutable $timestamp, string $local): string From 4280edf78ce6d508f0dd2f5b99add759572863a8 Mon Sep 17 00:00:00 2001 From: Andreas Sundqvist Date: Wed, 28 Jan 2026 09:26:23 +0100 Subject: [PATCH 13/13] Revert last commit Add new interface to allow keeping BC --- src/DateTimeFormatter.php | 7 ++++++- src/Formatter.php | 5 ++++- src/FormatterDateTime.php | 8 ++++++++ src/LocalizedDateTimeFormatter.php | 7 ++++++- src/LocalizedStrftimeFormatter.php | 7 ++++++- 5 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 src/FormatterDateTime.php diff --git a/src/DateTimeFormatter.php b/src/DateTimeFormatter.php index 9000689..157d3ca 100644 --- a/src/DateTimeFormatter.php +++ b/src/DateTimeFormatter.php @@ -2,7 +2,7 @@ namespace Sunkan\Dictus; -final class DateTimeFormatter implements Formatter, MutableFormatter +final class DateTimeFormatter implements FormatterDateTime, MutableFormatter { public function __construct( private string $format, @@ -18,6 +18,11 @@ public function setFormat(string $format): void $this->format = $format; } + public function formatTimestamp(string $format, \DateTimeImmutable $timestamp): string + { + return $timestamp->format($format); + } + public function formatDate(string $format, \DateTimeInterface $date): string { return $date->format($format); diff --git a/src/Formatter.php b/src/Formatter.php index e766b42..5649a54 100644 --- a/src/Formatter.php +++ b/src/Formatter.php @@ -6,5 +6,8 @@ interface Formatter { public function format(\DateTimeInterface $date): string; - public function formatDate(string $format, \DateTimeInterface $date): string; + /** + * @deprecated use FormatterDateTime::formatDate + */ + public function formatTimestamp(string $format, \DateTimeImmutable $timestamp): string; } diff --git a/src/FormatterDateTime.php b/src/FormatterDateTime.php new file mode 100644 index 0000000..3c3b04e --- /dev/null +++ b/src/FormatterDateTime.php @@ -0,0 +1,8 @@ +formatDate($this->format, $date); } + public function formatTimestamp(string $format, \DateTimeImmutable $timestamp): string + { + return $this->formatDate($format, $timestamp); + } + public function formatDate(string $format, \DateTimeInterface $date): string { $timestamp = \DateTimeImmutable::createFromInterface($date); diff --git a/src/LocalizedStrftimeFormatter.php b/src/LocalizedStrftimeFormatter.php index 39d8276..6598e63 100644 --- a/src/LocalizedStrftimeFormatter.php +++ b/src/LocalizedStrftimeFormatter.php @@ -5,7 +5,7 @@ use DateTimeImmutable; use IntlDateFormatter; -final class LocalizedStrftimeFormatter implements LocalizedFormatter, MutableFormatter +final class LocalizedStrftimeFormatter implements LocalizedFormatter, MutableFormatter, FormatterDateTime { private const INTL_FORMATS = [ '%a' => 'EEE', // An abbreviated textual representation of the day Sun through Sat @@ -39,6 +39,11 @@ public function format(\DateTimeInterface $date): string return $this->strftime($this->format, $date, $this->locale); } + public function formatTimestamp(string $format, \DateTimeImmutable $timestamp): string + { + return $this->strftime($format, $timestamp, $this->locale); + } + public function formatDate(string $format, \DateTimeInterface $date): string { return $this->strftime($format, DateTimeImmutable::createFromInterface($date), $this->locale);