Open-source project
lcobucci/clock avatar
lcobucci/clock

lcobucci/clock: injecting the current time instead of calling it

Yet another clock abstraction

799 stars33 forksPHPMIT

At a glance

What is it?
This is a clock abstraction for PHP with a single interface and two implementations, one reading the system time and one frozen at a fixed instant, so that code which compares timestamps can be tested without sleeping. It is a small library with a large amount of opinion about testability.
Who is it for?
Adopt lcobucci/clock if your PHP code branches on the current time and you want those branches testable, especially with code that filters on an expiry timestamp. Do not adopt it for a single one-off timestamp in a script, because the dependency and the interface you then have to thread through your call graph are more than the problem is worth.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 7 days ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The whole idea is one line long

The README calls it yet another clock abstraction, and then explains the purpose: to decouple projects from DateTimeImmutable instantiation so that we can test things properly. That is the entire design. The problem being solved is that PHP code which asks what time it is, by calling new DateTimeImmutable or time, becomes untestable in the way that matters, because a test that wants to exercise the expired branch has to either wait for real time to pass or mock a global function. The library removes the direct call. Your object depends on an interface, and the test supplies a frozen implementation. That is the classic dependency inversion move applied to time, and the small size of the package is not an accident, it is the point: an abstraction with one method and two implementations can be threaded through code without the test suite becoming a mock factory.

One interface, two implementations, no surprises

The usage section names the contract and the two implementations. Your objects depend on the Lcobucci\Clock\Clock interface, and you use SystemClock to retrieve the current time or FrozenClock to retrieve a specific time, for testing. SystemClock is constructed with a timezone, and there are named constructors for the common cases: SystemClock::fromSystemTimezone() and SystemClock::fromUTC(). FrozenClock takes a fixed DateTimeImmutable, and it too has a fromUTC() convenience constructor that freezes at the current moment, which is useful when you want a stable baseline but still a realistic value. The README shows the SystemClock being built with new SystemClock(new DateTimeZone('America/Sao_Paulo')). There is no configuration, no service discovery and no container integration in the library itself, which is appropriate: it is a value object, and the wiring is your framework's job.

The example is an expiry filter, which is the canonical case

The README's example is worth reading closely because it shows the shape of usage rather than a toy. A function filterData takes a Clock and an array of objects, and returns the objects whose expiresAt is later than the clock's now():

php
use Lcobucci\Clock\Clock;
use Lcobucci\Clock\SystemClock;
use Lcobucci\Clock\FrozenClock;

function filterData(Clock $clock, array $objects): array
{
    return array_filter(
        $objects,
        static function (stdClass $object) use ($clock): bool {
            return $object->expiresAt > $clock->now();
        }
    );
}

The three objects in the example carry expiry dates in 2017 and the frozen clock sits at 2017-05-07 18:49:30, so the last item is the one filtered out. Swap the SystemClock for a FrozenClock and the same test becomes deterministic, which is the payoff. The type hint on the parameter is what makes it work: the closure captures the clock rather than reaching for a global, so a test passes a different implementation and the production code does not change at all.

The build runs mutation testing, which is the quality signal

The Makefile is where this project shows what it values. The default target all runs install, phpcbf, phpcs, phpstan, phpunit and infection in sequence, and each of those is a separate target you can run on its own. The static analysis and style checks are there, with phpcs running through a phpcs.xml.dist configuration and phpstan through a phpstan.neon.dist, and phpstan is run with xdebug.mode=off. The interesting one is infection, which runs PHPUnit under Xdebug in coverage mode to produce a coverage XML file, then runs vendor/bin/infection against that coverage. Infection is a mutation testing tool: it changes your code in small ways and checks that your tests actually notice. A project that runs mutation testing as part of its default build is making a statement about how much it trusts its test suite, and for a library whose entire value proposition is that its tests can be trusted, that is consistent. The parallelism for the tools comes from nproc, and there is a renovate.json for dependency updates.

Three minor versions a year, and the branch is the detail

The maintenance picture is healthy but the branch layout is worth understanding. The default branch is 3.7.x, the release list shows 3.4.0 on 2025-10-08, 3.5.0 on 2025-10-27 and 3.6.0 on 2026-04-13, and the repository is not archived with the last push on 2026-10-16. So there is active work on a 3.7 line that has not yet been tagged, while the newest stable release is 3.6.0 from April. If you are starting a new project, pin 3.6.0 or the latest stable on Packagist rather than tracking the branch. There is also a stable line and an unstable one, which the badges distinguish, and the coverage badge points at the 3.4.x branch while the build badge points at 3.6.x, a small inconsistency that suggests the badge configuration is not always updated when branches move.

Where the clock abstraction stops being worth it

The limitation is the same one every injectable dependency has, and it is worth being concrete about it. To use the library you have to change a signature to accept a Clock, and that change propagates. Every caller of the function now has to supply one, which in a large codebase means either a factory, a service container entry or a constructor default argument. If the time-dependent behaviour is confined to one class, the abstraction may cost more in wiring than it saves. The other failure mode is partial adoption, where some code takes a Clock and other code still calls new DateTimeImmutable, and the tests pass anyway because the untested part happens not to matter today. There is also the question of granularity: the interface exposes now(), which is enough for comparisons against an expiry and not enough for anything that needs a monotonic reading or a duration between two events measured reliably, where the system clock can jump. The library does not address that, and its scope says it does not try to.

Editorial conclusion

Adopt lcobucci/clock if your PHP code branches on the current time and you want those branches testable, especially with code that filters on an expiry timestamp. Do not adopt it for a single one-off timestamp in a script, because the dependency and the interface you then have to thread through your call graph are more than the problem is worth. Before you commit, check which branch of the repository you are on, since the default branch is 3.7.x while the newest release listed is 3.6.0, and read composer.json for the PHP version floor before you pin it.

Frequently asked questions

What is lcobucci/clock?

It is an MIT licensed PHP library described as yet another clock abstraction, whose purpose is to decouple projects from DateTimeImmutable instantiation so that things can be tested properly. It provides a Clock interface with SystemClock and FrozenClock implementations.

How do I install lcobucci/clock?

Through Composer, with composer require lcobucci/clock. The package is published on Packagist as lcobucci/clock, and the README links both Packagist and Composer's site for the install path.

What is the difference between SystemClock and FrozenClock?

SystemClock returns the current time and can be built with a timezone, with named constructors SystemClock::fromSystemTimezone() and SystemClock::fromUTC(). FrozenClock always returns a fixed instant, constructed from a DateTimeImmutable, with a fromUTC() constructor that freezes at the current moment.

How is lcobucci/clock tested?

The Makefile runs a full quality chain by default: phpcbf, phpcs, phpstan, phpunit and infection, the last being mutation testing that runs PHPUnit under coverage and then checks the tests catch deliberately broken code. The newest release is 3.6.0 from 2026-04-13 and the last push was on 2026-10-16.

Official sources

  1. Issues
  2. lcobucci/clock on GitHub
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/lcobucci-clock.svg)](https://hysenlabs.com/projects/lcobucci-clock)