Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Php Datetime Ago Laravel Package

smirik/php-datetime-ago

A small PHP library that turns timestamps and DateTime objects into human-friendly "time ago" strings like "5 minutes ago" or "2 days ago." Lightweight, no framework required, with configurable formats and simple, localization-friendly output for feeds, comments, and ac...

View on GitHub
Deep Wiki
Context7

Getting Started

smirik/php-datetime-ago is a tiny, framework-agnostic helper that turns a DateTime into a human string ("5 minutes ago", "2 days ago"). There is no Laravel service provider, facade, or auto-discovery — it is a plain Composer library. Treat it as a small utility you wrap yourself.

Install and inspect

composer require smirik/php-datetime-ago

Before writing any code, read three things in the repo, because the surface area is small enough to read end-to-end in ten minutes:

  1. README.md — the canonical entry point and the only documented API.
  2. src/ — usually one or two classes; confirm the exact class name, namespace, and method signature (this package predates PSR-4 conventions being universal, so verify the namespace against composer.json's autoload block).
  3. composer.json — check the PHP constraint. A package this small and this old may declare php: >=5.4 and may not have been tested on PHP 8.2+. If the constraint blocks install on your PHP version, you can often still use it via --ignore-platform-reqs in dev, but do not ship that to production without a smoke test.

First use case

The natural first use is a "posted X ago" label on a listing page. The typical shape of the call is: instantiate the class, pass it a DateTimeInterface, receive a string.

use Smirik\PHPDateTimeAgo\DateTimeAgo; // verify against README

$ago = new DateTimeAgo();

echo $ago->get(new DateTimeImmutable('2024-01-01 09:00:00'));
// "3 months ago" (exact wording depends on the package's string table)

Build one working example in routes/web.php or a Tinker session before wiring it into anything else. Confirm the output for four boundaries specifically: seconds, minutes/hours, days, and anything older than a month. Those boundaries are where small "time ago" libraries differ most, and where you'll discover whether the package suits your UI copy.

Implementation Patterns

Wrap it in a service provider. Even for a one-class library, bind it in the container so you can swap the implementation later and so tests can fake it. Register a singleton in AppServiceProvider::register():

$this->app->singleton(DateTimeAgo::class, fn () => new DateTimeAgo());

Resolve it via constructor injection or app(DateTimeAgo::class). This is the seam that lets you replace the package with Carbon six months from now without touching call sites.

Model accessor for presentation. Keep the formatting out of controllers. A computed accessor keeps Blade templates dumb:

public function getPostedAgoAttribute(): string
{
    return app(DateTimeAgo::class)->get($this->published_at);
}

Guard against null timestamps — most of these libraries throw a TypeError rather than returning an empty string when handed null. Return '' or a placeholder explicitly.

Blade directive for repeated markup. If the label appears in a dozen views, register a directive in a dedicated BladeServiceProvider:

Blade::directive('ago', fn ($expression) => "<?php echo e(app(\\Smirik\\PHPDateTimeAgo\\DateTimeAgo::class)->get($expression)); ?>");

Then <span>{{-- @ago($post->published_at) --}}</span> in templates. Directives are string-compiled, so keep the expression simple and test the compiled output once — malformed escaping here is a common source of XSS in otherwise safe apps.

API resources. For JSON responses, resolve the string once and expose it as a derived field rather than shipping the raw timestamp and formatting client-side:

return [
    'published_at' => $this->published_at?->toIso8601String(),
    'published_ago' => $this->published_at
        ? app(DateTimeAgo::class)->get($this->published_at)
        : null,
];

Because the value is computed server-side, it will not update between client polls — if your UI needs live-updating labels, ship the ISO timestamp too and let JS handle the ticking, or include the label only in server-rendered HTML.

Activity feeds and audit logs. This is where the package is genuinely pleasant: a list of 50 mixed events (login, comment, upload) all rendered through one call, with no per-type branching. Normalise heterogeneous timestamps to DateTimeImmutable at the boundary, then map over them.

Timezone discipline. Instantiate from your app's configured timezone, not the server default:

$date = (new DateTimeImmutable($post->published_at))->setTimezone(new DateTimeZone(config('app.timezone')));

Laravel stores UTC by default; the package almost certainly does raw subtraction on two DateTime objects. If you pass a UTC date and it internally uses new DateTime('now') in the server's local zone, you get an offset error that is easy to miss in testing (a 2-hour skew still reads "2 hours ago" plausibly). Pin the timezone explicitly on every input.

Fallback pattern. The honest architectural advice: this package occupies the same niche as $date->diffForHumans(), which ships with Laravel for free via Carbon. Use it if you specifically prefer its output strings or its compact dependency footprint. Otherwise, define the interface yourself and implement it with Carbon:

interface TimeAgoFormatter { public function get(\DateTimeInterface $d): string; }

Ship the Carbon implementation, keep the package behind the interface if you want its phrasing, and you retain the option to drop the dependency with zero call-site changes.

Gotchas and Tips

Carbon is the elephant in the room. diffForHumans() already handles past and future ("3 days ago" / "in 3 days"), pluralisation, and dozens of locales. Verify what this package does with a future timestamp before adopting it — many minimal implementations subtract naively and return nonsense like "-3 days ago" or a wrap-around value. If your data can contain future dates (scheduled posts, expiry notices), test this on day one.

Locale support is probably absent or hardcoded. Check src/ for a string table. If the output is English-only with no setLocale() equivalent, and your app serves multiple languages, you'll be stuck. The escape hatch is to have the package produce a structured result (or reimplement the diff) and map it through Laravel's __() translation files:

trans_choice('time.minutes_ago', $minutes, ['count' => $minutes]);

This is often the better pattern anyway — it keeps translations in lang/ where your team already looks.

Construction cost in loops. If the class builds new DateTime('now') in its constructor, creating a fresh instance inside a @foreach over 200 rows means 200 clock reads and 200 object allocations. Bind it as a singleton (as above) if it's stateless — but if it caches "now" at construction, a singleton will freeze the reference time and every label will drift as the request ages. Read the constructor before deciding; the safe default is a fresh instance per render, accepting the minor cost.

Testing with frozen time. Use Carbon::setTestNow() in tests, but only if the package's internals use DateTime (which honours the same underlying clock in modern PHP+Carbon setups — actually it does not; Carbon::setTestNow() only affects Carbon). If the package calls new DateTime() directly, your tests will be time-dependent. Mitigate by injecting the "now" value if the API allows it; if it doesn't, assert on ranges rather than exact strings, or run those assertions against a fixed input date far in the past where drift doesn't matter.

Escaping and whitespace. These libraries sometimes return strings with leading/trailing spaces or non-breaking spaces. In tests this shows up as a maddening assertSame failure. Prefer assertStringContainsString or trim() at the boundary. Also never {!! !!} the output — even though it's generated, treating it as trusted text sets a bad precedent in the codebase and breaks if someone later swaps in user-configurable formatting.

Maintenance and PHP 8. With 22 stars and zero dependents, expect the repo to be effectively unmaintained. Symptoms: no declare(strict_types=1), no return types, array() syntax, untested on PHP 8. The composer.json platform constraint is the first thing that will break on upgrade. Fork it, add types, and point your repositories entry at your fork — the code is small enough that adopting it is a legitimate strategy and costs less than working around it.

Extension points are limited. Without an interface or strategy injector, customising the output means either subclassing and overriding the string-producing method (fragile, since internals may be private) or copying the file into your own namespace. If you find yourself patching heavily, that's the signal to write the 40-line formatter yourself — it's less code than the integration glue.

Debugging tips. When output looks wrong, check in this order: (1) timezone on the input, (2) whether the input is a string being coerced by new DateTime() with ambiguous format, (3) the boundary thresholds in the source (e.g. does it switch from "hours" to "days" at 24 or at 23?), (4) whether it's using floor or round — a 47-minute interval showing as "1 hour ago" is a rounding decision, not a bug. Write one table-driven test covering every boundary the source defines; it takes five minutes and documents the package's behaviour permanently.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin