symfony/clock
Decouples your PHP applications from the system clock by injecting a ClockInterface, so you can get the current time, force a timezone, or pause execution with sleep, and swap in a mock clock for testing.
UTC internally and localizing only at the edges is a product-visible decision (session expiry, order cutoffs, billing dates). withTimeZone() gives you one enforced place to express it instead of a convention documented in a wiki.ClockInterface, NativeClock, MockClock) that you should not hand-roll. The real cost is adoption discipline — banning time(), now(), and new DateTimeImmutable() in domain code — not the dependency itself.Adopt when:
time(), Carbon::now(), and new DateTime().usleep() and you want predictable, injectable delays.Look elsewhere or defer when:
Carbon::now() is fine; adding an abstraction here is ceremony.Carbon::setTestNow() / travel() / freezeTime() for tests and Illuminate\Support\Sleep for testable pauses. If your only need is test-time manipulation, you likely don't need a production-code clock interface — the differentiator here is injection, not mocking.jest.useFakeTimers() or similar.Diligence notes: MIT, Symfony-maintained, steady patch cadence with many "no significant changes" releases — that's a stable API signal, not an abandoned one, and adoption risk is near zero for a leaf utility. Two caveats: the v8 line requires PHP 8.4, so confirm your runtime matrix and pin to v7.x if needed; and the "0 dependents" figure is almost certainly a data artifact for a component consumed via symfony/symfony — don't cite it internally as a maturity signal either way.
Update — v8.1.9 (patch, no breaking changes, no new features, no deprecations): all four fixes are correctness fixes in the timezone/date layer: DatePoint being one hour off when its reference falls in the DST fall-back hour, new DatePoint() one hour off during that same hour, DatePoint incorrectly keeping the current time-of-day for dates with a relative part, and MockClock::sleep() broken for dates before 1970. Two implications for the assessment: (1) pin to >= 8.1.9 if you're on the v8 line — the DST fall-back bug is exactly the failure mode you're buying this package to avoid, and it lived in the convenience DatePoint type, not just in exotic code paths; (2) this is a useful reality check on the pitch — a clock abstraction gives you injectability and a single policy point, it does not outsource DST correctness. Keep DST-boundary cases (spring-forward gap, fall-back ambiguity, pre-1970 relative dates if you do historical/audit work) in your own test matrix rather than assuming the dependency covers them. Active patching on these edge cases is a positive maintenance signal, but it also means "we adopted a clock library" is not a closed ticket.
Executives: "A free, Symfony-backed utility that makes time-based product rules — trials, deadlines, retention, expiry — testable in seconds instead of days. It's a hygiene investment, not a feature: it reduces flaky releases and eliminates a recurring class of timezone bug in global deployments. It's also actively maintained — the latest patch fixes real daylight-saving edge cases, which is the kind of thing we'd otherwise discover in production. No architecture change, incremental rollout, MIT license, near-zero maintenance surface. The win is faster, safer shipping of time-boxed features and fewer of those 'the discount ran an extra hour in Sydney' incidents." Frame success as: reduction in flaky tests, reduction in timezone-related incidents, and shorter cycle time on time-boxed features. Do not pitch it as a differentiator or a roadmap item on its own, and don't promise it makes DST bugs disappear — it makes them findable.
Engineering leads: "Constructor-inject ClockInterface instead of calling now() statically. Production gets NativeClock; tests get MockClock and control time precisely. Swap in withTimeZone('UTC') to enforce one policy. The real migration is mechanical — if (time() > $expiry) becomes if ($clock->now() > $expiry) — and it's incremental, starting wherever the tests are flakiest. Pin >= 8.1.9: that patch fixes DatePoint drifting an hour during the DST fall-back hour and MockClock::sleep() misbehaving for pre-1970 dates, so anything older undermines the exact guarantee we're buying. Treat DST boundaries as our own test obligation, not the library's. Caveat: we're already on Carbon and Sleep; this is worth it only if we want time as an injected dependency we can swap and audit, not just frozen in tests. So: propose a one-sprint pilot on the two most time-sensitive services, measure flaky-test rate before and after, and decide from data rather than adopting repo-wide."
How can I help you explore Laravel packages today?