Skip to content

558 twig filters voor datums#435

Open
ilaria-orlando wants to merge 10 commits intomasterfrom
558-twig-filters-voor-datums
Open

558 twig filters voor datums#435
ilaria-orlando wants to merge 10 commits intomasterfrom
558-twig-filters-voor-datums

Conversation

@ilaria-orlando
Copy link

@ilaria-orlando ilaria-orlando commented Mar 24, 2026

Type

  • Enhancement

Pull request description

Replaces SpoonDate with IntlDateFormatter to format dates and timestamps.

Summary by Sourcery

Replace legacy date utilities with IntlDateFormatter-based Twig filters for consistent date, time, and relative time formatting across frontend, backend, and shared templates.

Enhancements:

  • Update frontend Twig date, datetime, and time filters to use IntlDateFormatter with locale-aware patterns instead of SpoonDate and user-configured formats.
  • Update backend Twig date, datetime, and time filters to rely on IntlDateFormatter for interface-locale-based formatting.
  • Refactor the generic spoonDate Twig modifier to delegate to IntlDateFormatter for standardized date formatting.
  • Implement a custom time-ago calculation using language labels for singular and plural units instead of SpoonDate::getTimeAgo.
  • Add a Language::lblWithParameters helper to support parameterized label formatting in the frontend language layer.

Documentation:

  • Add translatable locale labels for time-ago strings in English, Dutch, and French.

@sourcery-ai
Copy link

sourcery-ai bot commented Mar 24, 2026

Reviewer's Guide

Replaces SpoonDate-based Twig date/time formatting with IntlDateFormatter and introduces a custom time-ago implementation and localized labels, while adding a helper for parameterized labels.

Sequence diagram for the new timeAgo Twig filter

sequenceDiagram
    participant TwigTemplate
    participant FrontendTemplateModifiers as FrontendTemplateModifiers
    participant FrontendLanguage as FrontendCoreLanguageLanguage

    TwigTemplate->>FrontendTemplateModifiers: timeAgo(timestamp)
    activate FrontendTemplateModifiers
    FrontendTemplateModifiers->>FrontendTemplateModifiers: now = time()
    FrontendTemplateModifiers->>FrontendTemplateModifiers: diff = |now - timestamp|
    FrontendTemplateModifiers->>FrontendTemplateModifiers: compute seconds, minutes, hours, days, months, years
    FrontendTemplateModifiers->>FrontendTemplateModifiers: select count and keySingular/keyPlural
    alt count <= 0
        FrontendTemplateModifiers->>FrontendLanguage: lbl(TimeAgoEmpty)
        FrontendLanguage-->>FrontendTemplateModifiers: label
        FrontendTemplateModifiers-->>TwigTemplate: label
    else count == 1
        FrontendTemplateModifiers->>FrontendLanguage: lbl(keySingular)
        FrontendLanguage-->>FrontendTemplateModifiers: label
        FrontendTemplateModifiers-->>TwigTemplate: label
    else count > 1
        FrontendTemplateModifiers->>FrontendLanguage: lblWithParameters(keyPlural, [count])
        FrontendLanguage-->>FrontendTemplateModifiers: formattedLabel
        FrontendTemplateModifiers-->>TwigTemplate: formattedLabel
    end
    deactivate FrontendTemplateModifiers
Loading

Class diagram for updated date and time Twig modifiers

classDiagram
    class FrontendCoreEngineTemplateModifiers {
        <<static>>
        +string formatDate(var)
        +string formatDateTime(var)
        +string formatTime(var)
        +string timeAgo(timestamp)
    }

    class BackendCoreEngineTemplateModifiers {
        <<static>>
        +string formatDate(var)
        +string formatDateTime(var)
        +string formatTime(var)
    }

    class CommonCoreTwigExtensionsBaseTwigModifiers {
        <<static>>
        +string spoonDate(timestamp, format, language)
    }

    class FrontendCoreLanguageLanguage {
        <<static>>
        +string lbl(key, fallback)
        +string lblWithParameters(key, parameters, fallback)
        +string getLabel(key, fallback)
    }

    class BackendCoreLanguageLanguage {
        <<static>>
        +string getInterfaceLanguage()
    }

    class FrontendCoreLanguageLocale {
        <<static>>
        +string frontendLanguage()
    }

    class IntlDateFormatter {
        +IntlDateFormatter(locale, dateType, timeType, timezone, calendar, pattern)
        +string format(timestamp)
    }

    class BackendCoreEngineAuthentication {
        <<static>>
        +User getUser()
    }

    class User {
        +string getSetting(key)
    }

    %% Relationships
    FrontendCoreEngineTemplateModifiers ..> FrontendCoreLanguageLocale : uses
    FrontendCoreEngineTemplateModifiers ..> IntlDateFormatter : uses
    FrontendCoreEngineTemplateModifiers ..> FrontendCoreLanguageLanguage : uses

    BackendCoreEngineTemplateModifiers ..> BackendCoreLanguageLanguage : uses
    BackendCoreEngineTemplateModifiers ..> IntlDateFormatter : uses
    BackendCoreEngineTemplateModifiers ..> BackendCoreEngineAuthentication : uses
    BackendCoreEngineAuthentication ..> User : returns

    CommonCoreTwigExtensionsBaseTwigModifiers ..> BackendCoreLanguageLanguage : uses
    CommonCoreTwigExtensionsBaseTwigModifiers ..> IntlDateFormatter : uses

    FrontendCoreLanguageLanguage ..> FrontendCoreLanguageLanguage : calls getLabel
Loading

File-Level Changes

Change Details Files
Replace SpoonDate with IntlDateFormatter for frontend date, datetime, time, and generic spoonDate Twig modifiers.
  • Switched imports from SpoonDate to IntlDateFormatter in frontend TemplateModifiers and BaseTwigModifiers.
  • Updated formatDate, formatDateTime, and formatTime to use IntlDateFormatter with explicit patterns instead of user/settings-based SpoonDate formats.
  • Changed BaseTwigModifiers::spoonDate to use IntlDateFormatter with a fixed date pattern instead of SpoonDate::getDate.
src/Frontend/Core/Engine/TemplateModifiers.php
src/Common/Core/Twig/Extensions/BaseTwigModifiers.php
Replace backend date/time Twig modifiers to use IntlDateFormatter instead of SpoonDate.
  • Switched import from SpoonDate to IntlDateFormatter in backend TemplateModifiers.
  • Updated backend formatDate, formatDateTime, and formatTime to use IntlDateFormatter with fixed patterns, ignoring per-user date/time settings.
src/Backend/Core/Engine/TemplateModifiers.php
Introduce a custom timeAgo implementation using Language labels instead of SpoonDate::getTimeAgo.
  • Reimplemented timeAgo to compute differences in seconds/minutes/hours/days/months/years and pick singular/plural label keys.
  • Added fallback handling for non-positive differences using a dedicated TimeAgoEmpty label.
  • Use Language::lbl and new Language::lblWithParameters helper to render localized messages with counts.
src/Frontend/Core/Engine/TemplateModifiers.php
src/Frontend/Core/Language/Language.php
Add localized label definitions for new time-ago messages in installer locale data.
  • Added TimeAgoEmpty, TimeAgo{Second,Minute,Hour,Day,Month,Year}{Singular,Plural} labels with en/nl/fr translations.
  • Used sprintf-style placeholders (%1$s) for pluralized variants to be consumed by lblWithParameters.
src/Backend/Core/Installer/Data/locale.xml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 4 issues, and left some high level feedback:

  • The new IntlDateFormatter usages in the front- and backend TemplateModifiers replace user-/configurable date/time formats with hardcoded patterns (e.g. 'dd.MM.yyyy', 'EEEE d MMMM yyyy', 'HH:mm'), which changes behavior and ignores existing settings; consider deriving the pattern or style from the existing configuration instead of hardcoding.
  • BaseTwigModifiers::spoonDate() now ignores its $format and $language parameters and always uses BackendLanguage::getInterfaceLanguage() with a fixed pattern, which breaks the previous contract and likely frontend usage; it should respect the passed format and language (or be clearly deprecated and replaced).
  • The timeAgo() implementation no longer wraps the relative text in an with a title showing the exact date/time, changing the generated markup and losing the precise timestamp tooltip; if this is not intentional, consider preserving the previous HTML structure while still using the new labels.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new IntlDateFormatter usages in the front- and backend TemplateModifiers replace user-/configurable date/time formats with hardcoded patterns (e.g. 'dd.MM.yyyy', 'EEEE d MMMM yyyy', 'HH:mm'), which changes behavior and ignores existing settings; consider deriving the pattern or style from the existing configuration instead of hardcoding.
- BaseTwigModifiers::spoonDate() now ignores its $format and $language parameters and always uses BackendLanguage::getInterfaceLanguage() with a fixed pattern, which breaks the previous contract and likely frontend usage; it should respect the passed format and language (or be clearly deprecated and replaced).
- The timeAgo() implementation no longer wraps the relative text in an <abbr> with a title showing the exact date/time, changing the generated markup and losing the precise timestamp tooltip; if this is not intentional, consider preserving the previous HTML structure while still using the new labels.

## Individual Comments

### Comment 1
<location path="src/Common/Core/Twig/Extensions/BaseTwigModifiers.php" line_range="193" />
<code_context>
+    public static function spoonDate($timestamp, $format = 'Y-m-d H:i:s', $language = 'en')
</code_context>
<issue_to_address>
**issue (bug_risk):** spoonDate no longer respects its $format and $language parameters and always formats a date only.

Previously this delegated to `\SpoonDate::getDate($format, $timestamp, $language)`, which respected the provided format, language, and optional time component. The new version hardcodes `BackendLanguage::getInterfaceLanguage()`, ignores `$language` and `$format`, and always uses `'dd MMMM yyyy'`, changing behavior and potentially breaking existing templates relying on custom formats or date+time output. Please either (a) translate `$format` into an `IntlDateFormatter` pattern and use `$language` as the locale (falling back to styles when no format is given), or (b) keep `spoonDate` as a thin wrapper around the legacy behavior and introduce a separate Intl-based helper.
</issue_to_address>

### Comment 2
<location path="src/Frontend/Core/Engine/TemplateModifiers.php" line_range="36-42" />
<code_context>

-        // format the date
-        return SpoonDate::getDate($format, (int) $var, BackendLanguage::getInterfaceLanguage());
+        $date = new IntlDateFormatter(
+            BackendLanguage::getInterfaceLanguage(),
+            IntlDateFormatter::SHORT,
</code_context>
<issue_to_address>
**issue (bug_risk):** formatDate now ignores the configured frontend date format and uses a hardcoded pattern.

The previous implementation respected `Core.date_format_short` from `fork.settings`, enabling per-site date format configuration. The new hardcoded `'dd.MM.yyyy'` pattern changes existing behavior and ignores that CMS setting. If you still want to use Intl while honoring configuration, consider deriving the Intl pattern from the stored format or storing Intl-compatible patterns in settings. Otherwise, sites using custom date formats may see unexpected changes.
</issue_to_address>

### Comment 3
<location path="src/Backend/Core/Engine/TemplateModifiers.php" line_range="30-36" />
<code_context>

-        // format the date
-        return SpoonDate::getDate($format, (int) $var, BackendLanguage::getInterfaceLanguage());
+        $date = new IntlDateFormatter(
+            BackendLanguage::getInterfaceLanguage(),
+            IntlDateFormatter::SHORT,
+            IntlDateFormatter::NONE,
+            null,
+            null,
+            'dd MMMM yyyy'
+        )->format((int) $var);
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Backend formatDate no longer respects per-user date format settings.

Previously this used `Authentication::getUser()->getSetting('date_format')`, so each user could control their own date format. The new code hardcodes `'dd MMMM yyyy'`, removing that per-user configurability. Please consider deriving the pattern from the user’s setting (e.g. storing Intl-compatible patterns or mapping legacy formats to Intl) instead of hardcoding it.
</issue_to_address>

### Comment 4
<location path="src/Frontend/Core/Engine/TemplateModifiers.php" line_range="250-254" />
<code_context>
+            return Language::lbl('TimeAgoEmpty');
+        }
+
+        if ($count === 1) {
+            return Language::lbl($keySingular);
+        }
+
+        return Language::lblWithParameters(
+            $keyPlural,
+            [
</code_context>
<issue_to_address>
**question:** timeAgo now returns plain text without the previous <abbr> wrapper or exact timestamp tooltip.

Previously, the relative time was wrapped in an `<abbr>` with a `title` showing the exact timestamp, aiding accessibility and precision on hover. The new version returns only the localized relative string. If this change wasn’t intentional, consider keeping the `<abbr>` wrapper (with the precise timestamp in `title`) while still using the new `Language` labels for the visible text.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +36 to +42
$date = new IntlDateFormatter(
Locale::frontendLanguage(),
IntlDateFormatter::SHORT,
IntlDateFormatter::NONE,
null,
null,
'dd.MM.yyyy'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): formatDate now ignores the configured frontend date format and uses a hardcoded pattern.

The previous implementation respected Core.date_format_short from fork.settings, enabling per-site date format configuration. The new hardcoded 'dd.MM.yyyy' pattern changes existing behavior and ignores that CMS setting. If you still want to use Intl while honoring configuration, consider deriving the Intl pattern from the stored format or storing Intl-compatible patterns in settings. Otherwise, sites using custom date formats may see unexpected changes.

Comment on lines +30 to +36
$date = new IntlDateFormatter(
BackendLanguage::getInterfaceLanguage(),
IntlDateFormatter::SHORT,
IntlDateFormatter::NONE,
null,
null,
'dd MMMM yyyy'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Backend formatDate no longer respects per-user date format settings.

Previously this used Authentication::getUser()->getSetting('date_format'), so each user could control their own date format. The new code hardcodes 'dd MMMM yyyy', removing that per-user configurability. Please consider deriving the pattern from the user’s setting (e.g. storing Intl-compatible patterns or mapping legacy formats to Intl) instead of hardcoding it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Op zich ok voor mij. Maar dan ook de setting weghalen in de interface?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ja, als we hier bewust afscheid nemen van per-gebruiker datumformaten in de backend, is het logischer om die setting ook op te ruimen.

Concreet zou ik voorstellen:

  1. Functioneel beslissen

    • Bevestigen dat we in de backend voortaan altijd het interface-locale + een vaste (of per-locale) Intl-pattern gebruiken.
    • Controleren of die date_format-instelling nog elders in de backend wordt gebruikt (bijv. exports, logs, andere templates). Als niet, dan is verwijderen veilig.
  2. Opruimen in de code

    • De user-setting key date_format niet langer aanbieden in het gebruikersprofiel / instellingenformulier.
    • Eventuele referenties naar getSetting('date_format') verwijderen of migreren naar het nieuwe IntlDateFormatter-gebaseerde pad.
  3. Migratie / backwards compatibility

    • Als je zeker weet dat date_format nergens meer gebruikt wordt, kunnen we hem stilzwijgend laten uitsterven (hij blijft misschien nog in de database staan maar heeft geen effect).
    • Of, als je het proper wil doen, een kleine migratie schrijven die de kolom/waarde opruimt of een upgrade note toevoegen.

Wil je dat ik even meekijk of er nog andere usages van date_format in dit project zijn, dan kunnen we beslissen of het een volledige cleanup moet worden of enkel het verbergen van de instelling in de UI.

public static function formatDate($var): string
{
// get setting
$format = Authentication::getUser()->getSetting('date_format');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Die setting wordt niet meer gebruikt?

public static function formatDateTime($var): string
{
// get setting
$format = Authentication::getUser()->getSetting('datetime_format');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Die setting wordt niet meer gebruikt?

public static function formatTime($var): string
{
// get setting
$format = Authentication::getUser()->getSetting('time_format');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Die setting wordt niet meer gebruikt?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Die "format" wordt niet meer gebruikt?

public static function formatDateTime($var): string
{
// get setting
$format = FrontendModel::get('fork.settings')->get('Core', 'date_format_long');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Die setting wordt niet meer gebruikt?

public static function formatTime($var): string
{
// get setting
$format = FrontendModel::get('fork.settings')->get('Core', 'time_format');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Die setting wordt niet meer gebruikt?

return '';
}

// return
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ik kreeg dat niet goed omdat dat met translatorinterface werkt voor die vertalingen, was moeilijk om te combineren met de locale.

*
* @return string
*/
public static function lblWithParameters(string $key, array $parameters = [], bool $fallback = true): string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moet je dat dan ook niet voorzien voor messages en errors?

Comment on lines +30 to +36
$date = new IntlDateFormatter(
BackendLanguage::getInterfaceLanguage(),
IntlDateFormatter::SHORT,
IntlDateFormatter::NONE,
null,
null,
'dd MMMM yyyy'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Op zich ok voor mij. Maar dan ook de setting weghalen in de interface?

public static function formatTime($var): string
{
// get setting
$format = Authentication::getUser()->getSetting('time_format');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idem hier.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Niet logischer om dit te verwijderen. Dan is er geen referentie meer naar Spoon.
Denk dat je wel alle templates snel kan doorzoeken om te zien of het wordt gebruikt.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Al denk ik dat het wel nuttig is om een method aan te bieden waarmee we het formaat kunnen meegeven. Niet?

public static function formatDate($var): string
{
// get setting
$format = FrontendModel::get('fork.settings')->get('Core', 'date_format_short');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Zou dan ook de setting weghalen in de interface

public static function formatDateTime($var): string
{
// get setting
$format = FrontendModel::get('fork.settings')->get('Core', 'date_format_long');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idem

public static function formatTime($var): string
{
// get setting
$format = FrontendModel::get('fork.settings')->get('Core', 'time_format');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idem

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants