Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IntlExtension >= 3.8 throws Exception when formatDateTime is used and $timezone === null #3965

Closed
TPantelConventus opened this issue Jan 16, 2024 · 6 comments

Comments

@TPantelConventus
Copy link

TPantelConventus commented Jan 16, 2024

Background

The Exception occurred after I updated from 3.7 to 3.8 and the Workaround for me right now is to downgrade to 3.7.

Description

I use the fomat_datetime filter in many places in my app like this:

{{ presentation.presentationFileUploadedAt | format_datetime('medium', timezone=false, locale=app.request.locale) }}

Before the update of "twig/intl-extra" from 3.7 to 3.8 that worked without any problems, but when i updated to 3.8 it suddenly gave me Excpetions:

An exception has been thrown during the rendering of a template ("Twig\Extra\Intl\IntlExtension::createDateFormatter(): Argument #5 ($timezone) must be of type ?DateTimeZone, bool given, called in /var/www/html/vendor/twig/intl-extra/IntlExtension.php on line 379").

That was wondering me a lot, so I looked into IntlExtension.php to look for the corresponding line:

/**
     * @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
    {
        $date = twig_date_converter($env, $date, $timezone);

        $formatterTimezone = $timezone;
        if (null === $formatterTimezone) {
            $formatterTimezone = $date->getTimezone();
        } elseif (\is_string($formatterTimezone)) {
            $formatterTimezone = new \DateTimeZone($timezone);
        }
      // THE NEXT LINE IS LINE 379
  $formatter = $this->createDateFormatter($locale, $dateFormat, $timeFormat, $pattern, $formatterTimezone, $calendar); 

        if (false === $ret = $formatter->format($date)) {
            throw new RuntimeError('Unable to format the given date.');
        }

        return $ret;
    }

So formatDateTime (which the twig filter uses) accepts $timezone === false, but then in line 379 createDateFormatter() is called with $formatterTimezone being false, throwing the Exception, because:

private function createDateFormatter(?string $locale, ?string $dateFormat, ?string $timeFormat, string $pattern, ?\DateTimeZone $timezone, string $calendar): \IntlDateFormatter

The solution would be to modify the formatDateTimeFunction:

/**
     * @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
    {
        $date = twig_date_converter($env, $date, $timezone);

        $formatterTimezone = $timezone;
        if (null === $formatterTimezone || false === $formatterTimezone ) {
            $formatterTimezone = $date->getTimezone();
        } elseif (\is_string($formatterTimezone)) {
            $formatterTimezone = new \DateTimeZone($timezone);
        }
      // THE NEXT LINE IS LINE 379
  $formatter = $this->createDateFormatter($locale, $dateFormat, $timeFormat, $pattern, $formatterTimezone, $calendar); 

        if (false === $ret = $formatter->format($date)) {
            throw new RuntimeError('Unable to format the given date.');
        }

        return $ret;
    }

Replacing the twig filter from timezone=false to timezone=null is NOT a valid solution, as it prevents the Exception from being thrown, BUT results in a wrong time being displayed, because the $date variable in the function is depending on $timezone and not $formatterTimezone

@xabbuh
Copy link
Contributor

xabbuh commented Jan 16, 2024

probably related to #3903

/cc @keulinho

@fabpot
Copy link
Contributor

fabpot commented Jan 17, 2024

What about checking for null and false?

@xabbuh
Copy link
Contributor

xabbuh commented Jan 17, 2024

To be honest from reading the comments I did not really understand if and if so how the behaviour should have beeen different for null and false or if they should always have been treated the same way.

@lochmueller
Copy link

Hey @xabbuh
do you try this?
{{ presentation.presentationFileUploadedAt | format_datetime('medium', timezone=null, locale=app.request.locale) }} or
{{ presentation.presentationFileUploadedAt | format_datetime('medium', locale=app.request.locale) }} (null is the default)
Why you set the timezone to "false" instead of "null", because null is already the "undefined" state. I think the formatDateTime method should be type hinted with "?string" to avoid wrong variable types...?!
Regards,
Tim

@TPantelConventus
Copy link
Author

TPantelConventus commented Feb 7, 2024

Hey @xabbuh do you try this? {{ presentation.presentationFileUploadedAt | format_datetime('medium', timezone=null, locale=app.request.locale) }} or {{ presentation.presentationFileUploadedAt | format_datetime('medium', locale=app.request.locale) }} (null is the default) Why you set the timezone to "false" instead of "null", because null is already the "undefined" state. I think the formatDateTime method should be type hinted with "?string" to avoid wrong variable types...?! Regards, Tim

Because setting the timezone to null (the default) will lead to a different output than setting the timezone to false, which is intended and also documented in the Twig docs:

Timezone
By default, the date is displayed by applying the default timezone (the one specified in php.ini or declared in Twig -- see below), but you can override it by explicitly specifying a timezone:

{{ datetime|format_datetime(locale='en', timezone='Pacific/Midway') }}

If the date is already a DateTime object, and if you want to keep its current timezone, pass false as the timezone value:

{{ datetime|format_datetime(locale='en', timezone=false) }}

@fryght
Copy link

fryght commented Feb 8, 2024

Looking at the code it seems that the CoreExtension::dateConverter already takes care of timezone=false (or setting the proper timezone when one is passed and loading the default timezone when null is passed)

With that, it seems extraneous to also attempt handling the timezone again in the format_datetime filter.

@fabpot fabpot closed this as completed Apr 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

5 participants