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

Clock Laravel Package

beste/clock

A collection of PSR-20 clock implementations for PHP, including SystemClock, LocalizedClock, UTCClock, FrozenClock for tests, MinuteClock, and WrappingClock. Freeze time, set time zones, or wrap any object with a now() method. Install via composer require beste/clock.

View on GitHub
Deep Wiki
Context7

Getting Started

beste/clock is a small set of PSR-20 (Psr\Clock\ClockInterface) implementations. There is no service provider, no facade, and no config file — you wire it yourself, which is exactly what makes it easy to swap in tests.

composer require beste/clock   # PHP 8.1+, requires psr/clock ^1.0

First place to look is the README's clock table; you will realistically only use four of them day-to-day: UTCClock (persistence), SystemClock (display), LocalizedClock (business hours per region), and FrozenClock (tests).

Register a default binding in AppServiceProvider::register():

use Psr\Clock\ClockInterface;
use Beste\Clock\UTCClock;

public function register(): void
{
    $this->app->singleton(ClockInterface::class, fn () => UTCClock::create());
}

Then inject the interface instead of calling now() / Carbon::now() directly:

final class StartTrial
{
    public function __construct(private ClockInterface $clock) {}

    public function __invoke(User $user): void
    {
        $startedAt = $this->clock->now();          // DateTimeImmutable, UTC
        $user->trial_ends_at = $startedAt->modify('+14 days');
        $user->save();
    }
}

First concrete win: pick one time-dependent service (trial expiry, rate limiting, "is the promotion live?"), inject the clock, and write a test that freezes time. That single conversion usually justifies the dependency.

Implementation Patterns

Constructor injection, one now() read per operation. Capture $now = $this->clock->now() at the top of a method and derive everything from that variable. Calling ->now() three times inside one transaction can straddle a second/minute boundary and produce off-by-one bugs in expiry checks.

Two bindings, two purposes. Persist and compare in UTC (UTCClock), render in the user's zone. Keep the clock UTC and do presentation-time conversion at the edge ($clock->now()->setTimezone($user->timezone)), or inject LocalizedClock::in($user->timezone) where the domain genuinely needs local time (store opening hours, "is it Monday for this customer?").

Testing with FrozenClock. Override the container binding per test rather than in phpunit.xml:

protected function setUp(): void
{
    parent::setUp();

    $this->clock = FrozenClock::at(new DateTimeImmutable('2024-03-01 09:00:00', new DateTimeZone('UTC')));
    $this->app->instance(ClockInterface::class, $this->clock);
}

public function test_trial_expires(): void
{
    // ... act
    $this->clock->setTo($this->clock->now()->modify('+15 days'));
    // ... assert expired
}

A small base-test helper ($this->travelClockTo('2024-12-31 23:59:59')) keeps this consistent across the suite. FrozenClock::withNowFrom(SystemClock::create()) is useful when you want "freeze at whatever now is" without hardcoding a date.

MinuteClock::wrapping() for cache keys and buckets. Anything keyed per-minute — rate-limit counters, "last seen" throttles, lock names — becomes trivially testable:

$bucket = MinuteClock::wrapping($this->clock)->now()->format('YmdHi');

WrappingClock::wrapping() for legacy and third-party code. If a vendor SDK or an existing internal service already exposes now(): DateTimeImmutable, wrap it instead of refactoring:

$this->app->instance(ClockInterface::class, WrappingClock::wrapping($legacyService));

It is also the cleanest way to bridge a Carbon-based now() provider into PSR-20 without touching business logic.

Queues and scheduler. Laravel's scheduler and dispatch()->delay() use the real system clock, and no clock adapter changes that. The useful pattern is a guard inside the job: inject the clock and re-check whether the job should still run (if ($this->clock->now() < $invoice->due_at) return;). That makes jobs idempotent and makes the guard testable by freezing time — while still letting the scheduler fire on real time.

Interop with Carbon/Eloquent. Your models still cast to Carbon. Convert at the boundary:

$model->created_at = CarbonImmutable::createFromInterface($this->clock->now());

Never let DateTimeImmutable from the clock leak into code that expects Carbon — createFromInterface() (or CarbonImmutable::instance()) keeps that seam explicit.

Time-sensitive policies. Business rules like "orders placed before 14:00 ship same day" belong in a service that takes ClockInterface and a timezone, not in a model accessor calling now().

Gotchas and Tips

  • FrozenClock is mutable. setTo() changes the instance. If you singleton() a frozen clock in a base test case, state leaks between tests. Bind it fresh per test via app()->instance() in setUp().
  • Laravel's travel(), freezeTime(), and Carbon::setTestNow() do not move this clock. They patch Carbon's test-now; SystemClock and FrozenClock ignore them entirely. Pick one strategy per test — freezing the injected clock — or you will get contradictory assertions. If you must mix, keep both in sync explicitly.
  • SystemClock follows date_default_timezone_set(). Laravel sets that from config('app.timezone') at boot, so SystemClock::create() silently returns your app timezone, not UTC. If you want determinism, prefer UTCClock::create() for anything persisted or compared cross-system, and reserve SystemClock for human-facing output.
  • The clock is a time source, not a time store. Never cache $clock->now() in a singleton, a static, or a config value — in long-running queue workers and Octane that value goes stale for the life of the process.
  • MinuteClock truncates sub-minute precision but keeps the timezone of the wrapped clock's value. Two MinuteClocks in different zones will produce different bucket keys — normalize to UTC before wrapping if keys are shared across regions.
  • Version 3.0 swapped stella-maris/clock for psr/clock. If another dependency still pulls stella-maris/clock, your ClockInterface type-hint may be the wrong interface. Run composer why psr/clock / composer why stella-maris/clock when the container resolution "obviously correct" type fails to autowire.
  • No service provider means no vendor:publish, no config, no alias. Everything is in AppServiceProvider. Consider binding a project-specific App\Clock\Clock interface that extends the PSR one so tests and refactors have a stable local name.
  • Debugging tip: dump(app(ClockInterface::class)::class) as the first line of a failing test tells you instantly whether the container gave you a frozen or real clock.
  • Extension point: nothing stops you implementing Psr\Clock\ClockInterface yourself (an NTP-backed clock, a clock that reads a simulated time from a request header in staging). Bind conditionally by environment — UTCClock in production, FrozenClock when app()->runningUnitTests().
  • Scope discipline. This package does not replace Carbon, does not format, does not parse. Its entire job is to make "what time is it?" an injectable seam. Apply it where time drives decisions; leave now() in views and log lines alone.
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.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast