On many web applications and websites you are able to see dates and read them easily without knowing which day is today or without knowing which day is the 21 of May, January etc. This feature is achieved usually in the browser using JavaScript. So what's the problem? The libraries for formatting aren't (usually) lightweight and you will end up with at least 3 libraries to achieve something like that. In case you want to implement such a feature on your application and you don't need to update the dates dinamically on the view, you can make this task on your own server.
In this article we'll show you 2 ways to display dates in the time ago format: using the KnpTimeBundle (a) or implementing the Twig_Extensions_Extension_Date
extension of Twig. Both of them provide the same functionality but the implementation of 1 may be longer for some developers, so it's up to you which method is easier to implement for you.
A. The Bundle (easy) way
If you don't have to worry about the size of your application and you are allowed to install third party bundles, then it will be easier to achieve your goal (a lot easier):
1. Install and register the KnpTimeBundle
We are talking about the KnpTimeBundle. This bundle does one simple job: takes dates and return friendly "2 hours ago"-type messages. To install this bundle in your Symfony project execute the following command on the terminal:
composer require knplabs/knp-time-bundle
For more information about this bundle, please visit the official repository at Github here. Once the installation of the bundle finishes, be sure to enable the Bundle in the Kernel of Symfony (app/AppKernel.php
):
<?php
// app/AppKernel.php
public function registerBundles()
{
$bundles = [
// ...
new Knp\Bundle\TimeBundle\KnpTimeBundle(),
// ...
];
// ...
}
And you're ready to continue with the next step.
2. Add App Locale and Enable translator
Usually, on every default Symfony application the locale already exists and it's by default en
(english). This value is used in your entire application, not only in the bundle. In case it doesn't exists provide the locale parameter with the identifier of your own language:
Note
There are a lot of supported languages, so be sure to check the translations files to see which languages are supported.
parameters:
locale: en
# Or the locale of your language e.g : es,de,nl,pt,pl etc
As next, you need to enable the translator because it's usually commented, so be sure that you translator setting in the app/config/config.yml
file is uncommented and the fallbacks uses the previous declared locale parameter:
framework:
translator: { fallbacks: ['%locale%'] }
Once this is made, you will be able to use the ago filter of the bundle to display the readable description of the time difference.
3. Using the Twig ago filter
The KnpTimeBundle exposes a new twig filter namely ago
. This filter expects as target a DateTime object that refers to $since
(the origin date) and an optional parameter $to
that specifies from where should the difference should be made (another DateTime object), for example:
{#
In this example we convert the now string to a date
The date can be retrieven from the controller etc.
#}
{% set myDate = "now"|date %}
{#
Modify our date by removing 4 days
#}
{% set myDate = myDate|date_modify('-4 day') %}
{# Displays according to your locale:
4 days ago
vor 4 Tagen
hace 4 días
etc
#}
{{ myDate|ago}}
{#
And if you need to differentiate the date from another day but not now
provide the first argument:
#}
{% set fromTomorrow = "now"|date_modify('+1 day') %}
{# Displays according to your locale:
5 days ago
vor 5 Tagen
hace 5 días
etc
#}
{{ myDate|ago(fromTomorrow)}}
Easy isn't ?
B. The self-implemented way
If you are happy today and have time to waste, then you may learn something new. The self implemented way takes a little more to implement but if you aren't able to install a third party bundle, then this is your best option.
1. Create Twig_Extensions_Extension_Date extension
The first you will need to do is to create in your project the Twig_Extensions_Extension_Date
extension in your project. This class refers to the official date extension of Twig by Fabien Potencier. The advantages of the following class is that its able to use the translator module of Symfony too, so it's something similar to the bundle of the first step.
Create the Extension file namely Twig_Extensions_Extension_Date.php
on some directory of your application and change the namespace according to yours. In this case, we are creating the class inside the Extensions
folder in the AppBundle
:
<?php
namespace AppBundle\Extensions;
/**
* This file is part of Twig.
*
* (c) 2014 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Component\Translation\TranslatorInterface;
use Twig_Environment;
use Twig_SimpleFilter;
use Twig_Extension;
/**
* @author Robin van der Vleuten <[email protected]>
*/
class Twig_Extensions_Extension_Date extends Twig_Extension
{
public static $units = array(
'y' => 'year',
'm' => 'month',
'd' => 'day',
'h' => 'hour',
'i' => 'minute',
's' => 'second',
);
/**
* @var TranslatorInterface
*/
private $translator;
public function __construct(TranslatorInterface $translator = null)
{
$this->translator = $translator;
}
/**
* {@inheritdoc}
*/
public function getFilters()
{
return array(
new Twig_SimpleFilter('time_diff', array($this, 'diff'), array('needs_environment' => true)),
);
}
/**
* Filter for converting dates to a time ago string like Facebook and Twitter has.
*
* @param Twig_Environment $env a Twig_Environment instance
* @param string|DateTime $date a string or DateTime object to convert
* @param string|DateTime $now A string or DateTime object to compare with. If none given, the current time will be used.
*
* @return string the converted time
*/
public function diff(Twig_Environment $env, $date, $now = null)
{
// Convert both dates to DateTime instances.
$date = twig_date_converter($env, $date);
$now = twig_date_converter($env, $now);
// Get the difference between the two DateTime objects.
$diff = $date->diff($now);
// Check for each interval if it appears in the $diff object.
foreach (self::$units as $attribute => $unit) {
$count = $diff->$attribute;
if (0 !== $count) {
return $this->getPluralizedInterval($count, $diff->invert, $unit);
}
}
return '';
}
protected function getPluralizedInterval($count, $invert, $unit)
{
if ($this->translator) {
$id = sprintf('diff.%s.%s', $invert ? 'in' : 'ago', $unit);
return $this->translator->transChoice($id, $count, array('%count%' => $count), 'date');
}
if (1 !== $count) {
$unit .= 's';
}
return $invert ? "in $count $unit" : "$count $unit ago";
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'date';
}
}
2. Add App Locale and Enable translator
Usually, on every default Symfony application the locale already exists and it's by default en
(english). This value is used in your entire application, not only in the bundle. In case it doesn't exists provide the locale parameter with the identifier of your own language:
parameters:
locale: en
# Or the locale of your language e.g : es,de,nl,pt,pl etc
As next, you need to enable the translator because it's usually commented, so be sure that you translator setting in the app/config/config.yml
file is uncommented and the fallbacks uses the previous declared locale parameter:
framework:
translator: { fallbacks: ['%locale%'] }
Once this is made, you will be able to use the translator module on your extension.
3. Register extension
As next, proceed to register the extension with the translator service as argument and providing the path to the class of the previous step:
services:
twig.extension.date:
# the namespace with the name of the Twig Extensions created class
class: AppBundle\Extensions\Twig_Extensions_Extension_Date
arguments: ["@translator"]
tags:
- { name: twig.extension }
4. Creating translation files
As next you need to create the translation files, however, for lazy developers like me, there are always a way to make it everything easy. The translation files in this case will be in the xliff format because we can copy the translations of the KnpTimeBundle and so we won't need to write our own translation files. However, note that the namespace in KnpTimeBundle
is time, but in this extension is date
due to the providen name of the service twig.extension.date
.
Go to the translation files of the KnpTimeBundle here and choose the one(s) you need and copy them in to the app/Resources/translations/
folder of your project (if the translations folder doesn't exist, then create it). For example, the following file (app/Resources/translations/date.de.xliff
) provides the translation for our dates in German:
Important
Note that the id
of every trans-unit
is a string. In the repository of KnpTimeBundle the ids are numbers, so be sure to change the id by the content of the source attribute, otherwise Symfony won't find any item to translate. The name of the xliff files need to follow the filename pattern (in this case) date.<lang-identifier>.xliff
.
<?xml version="1.0"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="file.ext">
<body>
<trans-unit id="diff.ago.year">
<source>diff.ago.year</source>
<target>vor einem Jahr|vor %count% Jahren</target>
</trans-unit>
<trans-unit id="diff.ago.month">
<source>diff.ago.month</source>
<target>vor einem Monat|vor %count% Monaten</target>
</trans-unit>
<trans-unit id="diff.ago.day">
<source>diff.ago.day</source>
<target>vor %count% Tag|vor %count% Tagen</target>
</trans-unit>
<trans-unit id="diff.ago.hour">
<source>diff.ago.hour</source>
<target>vor einer Stunde|vor %count% Stunden</target>
</trans-unit>
<trans-unit id="diff.ago.minute">
<source>diff.ago.minute</source>
<target>vor einer Minute|vor %count% Minuten</target>
</trans-unit>
<trans-unit id="diff.ago.second">
<source>diff.ago.second</source>
<target>vor einer Sekunde|vor %count% Sekunden</target>
</trans-unit>
<trans-unit id="diff.empty">
<source>diff.empty</source>
<target>jetzt</target>
</trans-unit>
<trans-unit id="diff.in.second">
<source>diff.in.second</source>
<target>in einer Sekunde|in %count% Sekunden</target>
</trans-unit>
<trans-unit id="diff.in.hour">
<source>diff.in.hour</source>
<target>in einer Stunde|in %count% Stunden</target>
</trans-unit>
<trans-unit id="diff.in.minute">
<source>diff.in.minute</source>
<target>in einer Minute|in %count% Minuten</target>
</trans-unit>
<trans-unit id="diff.in.day">
<source>diff.in.day</source>
<target>in einem Tag|in %count% Tagen</target>
</trans-unit>
<trans-unit id="diff.in.month">
<source>diff.in.month</source>
<target>in einem Monat|in %count% Monaten</target>
</trans-unit>
<trans-unit id="diff.in.year">
<source>diff.in.year</source>
<target>in einem Jahr|in %count% Jahren</target>
</trans-unit>
</body>
</file>
</xliff>
5. Use time_diff filter in the views
In the same way the ago filter of the KnpTimeBundle does, the time_diff
filter expects as target a DateTime object that refers to $since
(the origin date) and an optional parameter $to
that specifies from where should the difference should be made (another DateTime object), for example:
{#
In this example we convert the now string to a date
The date can be retrieven from the controller etc.
#}
{% set myDate = "now"|date %}
{#
Modify our date by removing 4 days
#}
{% set myDate = myDate|date_modify('-4 day') %}
{# Displays according to your locale:
4 days ago
vor 4 Tagen
hace 4 días
etc
#}
{{ myDate|time_diff}}
{#
And if you need to differentiate the date from another day but not now
provide the first argument:
#}
{% set fromTomorrow = "now"|date_modify('+1 day') %}
{# Displays according to your locale:
5 days ago
vor 5 Tagen
hace 5 días
etc
#}
{{ myDate|time_diff(fromTomorrow)}}
Happy coding !