Fix IntlExtension ignoring explicit formats when a date formatter prototype is set

This commit is contained in:
Fabien Potencier
2026-07-12 10:44:14 +02:00
parent bfbd962c5f
commit 083b6dcabe
3 changed files with 42 additions and 8 deletions
+1
View File
@@ -1,5 +1,6 @@
# 3.29.0 (2026-XX-XX)
* Fix `IntlExtension` ignoring explicit date/time formats and the `format_date`/`format_time` filters when a date formatter prototype is configured
* Add a `format_list` filter to `IntlExtension` to format a list of strings using PHP 8.5's `IntlListFormatter`
* Fix array access with a `Stringable` key coercing the key to string for `ArrayAccess` objects that use object keys (such as `SplObjectStorage`)
* Fix duplicated macro argument names triggering a PHP fatal error instead of a `SyntaxError`
+15 -8
View File
@@ -405,7 +405,7 @@ final class IntlExtension extends AbstractExtension
* @param \DateTimeInterface|string|null $date A date or null to use the current time
* @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged
*/
public function formatDateTime(Environment $env, $date, ?string $dateFormat = 'medium', ?string $timeFormat = 'medium', string $pattern = '', $timezone = null, string $calendar = 'gregorian', ?string $locale = null): string
public function formatDateTime(Environment $env, $date, ?string $dateFormat = null, ?string $timeFormat = null, string $pattern = '', $timezone = null, string $calendar = 'gregorian', ?string $locale = null): string
{
$date = $env->getExtension(CoreExtension::class)->convertDate($date, $timezone);
@@ -428,7 +428,7 @@ final class IntlExtension extends AbstractExtension
* @param \DateTimeInterface|string|null $date A date or null to use the current time
* @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged
*/
public function formatDate(Environment $env, $date, ?string $dateFormat = 'medium', string $pattern = '', $timezone = null, string $calendar = 'gregorian', ?string $locale = null): string
public function formatDate(Environment $env, $date, ?string $dateFormat = null, string $pattern = '', $timezone = null, string $calendar = 'gregorian', ?string $locale = null): string
{
return $this->formatDateTime($env, $date, $dateFormat, 'none', $pattern, $timezone, $calendar, $locale);
}
@@ -437,7 +437,7 @@ final class IntlExtension extends AbstractExtension
* @param \DateTimeInterface|string|null $date A date or null to use the current time
* @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged
*/
public function formatTime(Environment $env, $date, ?string $timeFormat = 'medium', string $pattern = '', $timezone = null, string $calendar = 'gregorian', ?string $locale = null): string
public function formatTime(Environment $env, $date, ?string $timeFormat = null, string $pattern = '', $timezone = null, string $calendar = 'gregorian', ?string $locale = null): string
{
return $this->formatDateTime($env, $date, 'none', $timeFormat, $pattern, $timezone, $calendar, $locale);
}
@@ -475,17 +475,24 @@ final class IntlExtension extends AbstractExtension
$calendar = 'gregorian' === $calendar ? \IntlDateFormatter::GREGORIAN : \IntlDateFormatter::TRADITIONAL;
$dateFormatValue = $dateFormats[$dateFormat] ?? null;
$timeFormatValue = self::TIME_FORMATS[$timeFormat] ?? null;
$dateFormatValue = null === $dateFormat ? null : $dateFormats[$dateFormat];
$timeFormatValue = null === $timeFormat ? null : self::TIME_FORMATS[$timeFormat];
if ($this->dateFormatterPrototype) {
$dateFormatValue = $dateFormatValue ?: $this->dateFormatterPrototype->getDateType();
$timeFormatValue = $timeFormatValue ?: $this->dateFormatterPrototype->getTimeType();
$dateFormatValue ??= $this->dateFormatterPrototype->getDateType();
$timeFormatValue ??= $this->dateFormatterPrototype->getTimeType();
$timezone = $timezone ?: $this->dateFormatterPrototype->getTimeZone()->toDateTimeZone();
$calendar = $calendar ?: $this->dateFormatterPrototype->getCalendar();
$pattern = $pattern ?: $this->dateFormatterPrototype->getPattern();
// fall back to the prototype's pattern only when nothing else was given, else it would override the explicit date/time formats;
// a pattern describes a full datetime rendering, so it cannot be honored by format_date/format_time, which pass 'none' for the other part
if ('' === $pattern && null === $dateFormat && null === $timeFormat) {
$pattern = $this->dateFormatterPrototype->getPattern();
}
}
$dateFormatValue ??= \IntlDateFormatter::MEDIUM;
$timeFormatValue ??= \IntlDateFormatter::MEDIUM;
$timezoneName = $timezone ? $timezone->getName() : '(none)';
$hash = $locale.'|'.$dateFormatValue.'|'.$timeFormatValue.'|'.$timezoneName.'|'.$calendar.'|'.$pattern;
@@ -97,6 +97,32 @@ class IntlExtensionTest extends TestCase
);
}
public function testFormatterProtoDoesNotOverrideExplicitFormats(): void
{
$dateFormatterProto = new \IntlDateFormatter('nl_NL', \IntlDateFormatter::MEDIUM, \IntlDateFormatter::MEDIUM, new \DateTimeZone('Europe/Amsterdam'));
$ext = new IntlExtension($dateFormatterProto);
$env = new Environment(new ArrayLoader());
$date = new \DateTime('2020-02-20T22:22:00+00:00', new \DateTimeZone('UTC'));
$this->assertSame('20 feb 2020', $ext->formatDate($env, $date));
$this->assertSame('22:22:00', $ext->formatTime($env, $date));
$this->assertSame('donderdag 20 februari 2020', $ext->formatDateTime($env, $date, 'full', 'none'));
}
public function testFormatterProtoWithCustomPatternIsUsedByDefault(): void
{
$dateFormatterProto = new \IntlDateFormatter('nl_NL', \IntlDateFormatter::MEDIUM, \IntlDateFormatter::MEDIUM, new \DateTimeZone('Europe/Amsterdam'), \IntlDateFormatter::GREGORIAN, 'yyyy-MM-dd');
$ext = new IntlExtension($dateFormatterProto);
$env = new Environment(new ArrayLoader());
$date = new \DateTime('2020-02-20T22:22:00+00:00', new \DateTimeZone('UTC'));
$this->assertSame('2020-02-20', $ext->formatDateTime($env, $date));
$this->assertSame('20 feb 2020', $ext->formatDate($env, $date, 'medium'));
// the prototype pattern describes a full datetime rendering, so format_date/format_time ignore it
$this->assertSame('20 feb 2020', $ext->formatDate($env, $date));
$this->assertSame('22:22:00', $ext->formatTime($env, $date));
}
public function testDateFormatterCacheIsBounded(): void
{
$ext = new IntlExtension();