eschmar/time-ago-bundle
TimeAgoBundle provides a simple Twig filter for Symfony that expresses time differences in words, returning actual dates beyond ±7 days. Supports many languages, configurable date format, and future times. Install via Composer; works with Symfony 2.8–5.x.
Read this first: it is a Symfony bundle, not a Laravel package. There is no TimeAgoServiceProvider, no package auto-discovery, and no Blade directive. If the app you are actually shipping is Laravel + Blade, the correct first move is to close the tab and use Carbon::parse($date)->diffForHumans() — it is already in the framework, is locale-aware via Carbon::setLocale(), has no 7-day cutoff, and is maintained. Come back to this package only if you maintain a Symfony app, or a Laravel app that genuinely renders Twig (e.g. via rcrowe/twigbridge) and you want the bundle's translation corpus.
Where to look first, once you have decided you need it:
vendor/eschmar/time-ago-bundle/composer.json — confirm the Symfony constraint. ^v2.0.0 targets Symfony ^5.0; ^v1.1.0 targets ^4.x. If you are on Symfony 6/7 you will likely need a fork or a composer.json constraint patch before anything else.src/ — the bundle class, the Twig extension (ago filter), and the service that formats the difference. This is a few hundred lines; read it in one sitting rather than trusting the README.Resources/translations/ (or translations/) — 26 locales of relative-time strings. This is the single most valuable asset in the repo.DependencyInjection/ — the eschmar_time_ago.format config key.Minimal steps in a Symfony app:
composer require eschmar/time-ago-bundle ^v2.0.0
// config/bundles.php
Eschmar\TimeAgoBundle\EschmarTimeAgoBundle::class => ['all' => true],
# config/packages/eschmar_time_ago.yaml
eschmar_time_ago:
format: 'd/m/Y' # used for anything older than 7 days
First use case — a list/feed where absolute timestamps are noise:
{{ post.createdAt|ago }}
{# 3 minutes ago / in 4 hours / 14/03/2022 #}
Feed and activity lists. The intended use is a single unqualified |ago on a \DateTime/DateTimeInterface in an index or activity stream. Because the filter silently degrades to a formatted date past ±7 days, one filter call handles both "recent" and "archive" rows and you avoid {% if %} branching in the template. Do not fight that default; it is the whole design.
Per-call format override. The optional argument wins over config, so use it where the fallback needs to be more verbose than your global default:
{{ comment.createdAt|ago('M j, Y \\a\\t H:i') }}
Keep these overrides few. If you find yourself passing a format to every call site, move it into eschmar_time_ago.format instead — that is what the config key is for.
Translation wiring. The output is passed through Symfony's translator, so locale comes from framework.default_locale / the request locale, not from the filter. Verify with bin/console debug:translation <locale> <domain> that the bundle's keys are loaded for the locale you care about before assuming a language is missing. Set translator.fallbacks to en so an untranslated locale degrades to English strings rather than raw keys.
Bridging into a Laravel/Twig stack. If you must use it outside Symfony, do not try to boot the Bundle class — register the extension by hand in a small service provider:
$twig->addExtension(new TimeAgoExtension($app['translator'] /* or a Laravel-backed adapter */));
Be aware the extension probably expects a Symfony\Contracts\Translation\TranslatorInterface; you will need an adapter around Laravel's Translator. At that point, compare honestly against writing a 15-line Blade directive plus diffForHumans() — usually the directive wins.
Wrapping for portability. Put |ago behind your own time_ago filter or a view presenter. When the bundle goes stale (see below) you can swap the implementation in one place instead of grepping templates.
Testing. Relative output is time-dependent, so freeze the clock (Carbon::setTestNow() or Symfony's ClockMock) and assert against an explicit reference instant. Avoid asserting "just now" in integration tests — it is a moving target and will flake on slow CI.
replaces entry if needed, and expect to patch the Symfony constraint yourself. Plan for this in your dependency policy; do not let it become a silent blocker on a Symfony upgrade.+4 hours works, but pluralization and phrasing for future distances in less-common locales may be rough. Spot-check any locale you actually ship.Y-m-d H:i:s, which looks like a database dump in a UI. Always set eschmar_time_ago.format explicitly, and use a locale-appropriate format (d/m/Y vs m/d/Y) rather than a machine format.DateTime object's own timezone. Normalize stored dates to UTC at the boundary and convert for display, or your >7-day rows will silently shift.|ago outside the cached region.bin/console debug:twig should list the ago filter; bin/console debug:container --tag=twig.extension should show the extension service. If the filter is missing, the bundle is not in config/bundles.php (Symfony 4+) or AppKernel::registerBundles() (Symfony <4) — not a caching problem.translations/ using the bundle's domain name (check DependencyInjection/ for the exact domain) rather than patching vendor/. The extension and translator are ordinary services, so service decoration is available if you need to change behaviour.diffForHumans() gives you the same output with better maintenance. The bundle earns its place only if you need its specific locale set or are already inside Symfony's Twig/translator stack.How can I help you explore Laravel packages today?