Michaël Perrin

Sustainable web development.

4-digit years for localized dates in Twig templates

The localizeddate filter in Twig (in the Intl Extension) formats dates according to the current or given locale.

While you can display dates with the dd/mm/yy / mm/dd/yy / yy/mm/dd formats depending on the locale, it’s not possible to display a 4-digit year like in 31/12/2014 (French style) or 12/31/2014 (US style) or any other format depending on the locale.

This post shows how to implement a Twig extension to display such dates, keeping the localization benefit.

You probably know the Twig Intl Extension to localize dates like this:

{{ post.published_at|localizeddate('short', 'none') }}

It uses the current locale along with the PHP IntlDateFormatter to format dates depending on the current locale.

The short format will display a date like dd/mm/yy for the french (fr_FR) locale, while it will display a date like mm/dd/yy for the en_US locale. The medium format displays a date like Jan 12, 1952.

It’s therefore impossible to display a localized date in the dd/mm/yyyy (fr_FR) or mm/dd/yyyy (en_US) formats.

Here is a solution to display localized dates with a 4-digit year, whatever the year, whatever the locale. The solution is to extend the Twig Intl extension in a new extension, so as to customize the date format generated by the PHP IntlDateFormatter class, transforming the “yy” (2-digit years) pattern to “y” (4-digit years), according to the ICU date format syntax.

<?php  
namespace Acme\DemoBundle\Twig;

class AcmeIntlExtension extends \Twig_Extensions_Extension_Intl  
{
    public function getFilters()
    {
        return array(
            new \Twig_SimpleFilter('localizeddate', array($this, 'twigLocalizedDateFilter'), array('needs_environment' => true)),
        );
    }

    public function twigLocalizedDateFilter($env, $date, $dateFormat = 'medium', $timeFormat = 'medium', $locale = null, $timezone = null, $format = null)
    {
        $date = twig_date_converter($env, $date, $timezone);

        $formatValues = array(
            'none'   => \IntlDateFormatter::NONE,
            'short'  => \IntlDateFormatter::SHORT,
            'medium' => \IntlDateFormatter::MEDIUM,
            'long'   => \IntlDateFormatter::LONG,
            'full'   => \IntlDateFormatter::FULL,
        );

        $formatter = \IntlDateFormatter::create(
            $locale,
            $formatValues[$dateFormat],
            $formatValues[$timeFormat],
            $date->getTimezone()->getName(),
            \IntlDateFormatter::GREGORIAN,
            $format
        );

        if ($format === null) {
            // Replace yy to y (but yyy should be kept yyy, and yyyy kept as yyyy)
            // This way, years are NEVER shown as "yy" (eg. 14), but always like "yyyy" (eg. 2014)
            $pattern = preg_replace(':(^|[^y])y{2,2}([^y]|$):', '$1y$2', $formatter->getPattern());

            $formatter->setPattern($pattern);
        }

        return $formatter->format($date->getTimestamp());
    }
}

Declare the extension in the app/config.yml file:

services:  
    acme.twig.extension.intl:
        class: Acme\DemoBundle\Twig\AcmeIntlExtension
        tags:
            - { name: twig.extension }

There was no other easy way to change the pattern of the date, so I had to use the regular expression to change “yy” to “y”, while preserving “yyy” or “yyyy”.

Michaël Perrin

Read more posts by this author.

comments powered by Disqus