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

Technical Evaluation

Architecture fit. beste/clock is a thin, framework-agnostic set of implementations of Psr\Clock\ClockInterface (PSR-20) — a single method, now(): DateTimeInterface. That is the entire contract, which is exactly what makes it a good architectural seam: domain and application services can depend on an interface they own conceptually while the concrete source of time is bound at the container level. The package deliberately does not compete with Carbon; it sits underneath it, and every implementation returns DateTimeImmutable, so Carbon/Illuminate\Support\Carbon consumers can continue to operate on the returned values. For a Laravel codebase already committed to Carbon for formatting and date math, this is a complementary abstraction, not a replacement.

The substantive gap versus a "Laravel package" is that there is no service provider and no auto-discovery. That is a deliberate design choice consistent with PSR-20 (the spec is framework-neutral), but it means the integration work — binding ClockInterface to a concrete implementation, choosing per-environment behavior — is ours, and it is roughly one class and one provider registration.

Integration feasibility. High, with low effort. composer require beste/clock plus a binding in a dedicated provider is the whole of it. The available implementations map cleanly onto our needs: UTCClock as the production default (avoids ambient date.timezone drift), LocalizedClock where a tenant or regional timezone must be honored, FrozenClock for deterministic tests, MinuteClock for coarse-grained idempotency buckets, and WrappingClock to adapt a legacy object that already exposes now() without rewriting it. Notably, the README's implementations are all now()-only — there is no sleeping, advancing, or drift simulation built in, so FrozenClock::setTo() plus our own test helpers is the extent of the time-travel toolkit.

Technical risk. Moderate, concentrated in adoption and longevity rather than code quality:

  • Bus factor. Zero Packagist dependents and 75 GitHub stars means effectively no downstream pressure testing this against real-world Laravel permutations. We would be an early, load-bearing adopter. Mitigating factor: MIT license, and the total surface area is small enough that a fork or in-house reimplementation is a day of work, not a quarter.
  • Version churn history. The 2.x → 3.x line flip-flopped on the underlying interface (stella-maris/clock → psr/clock → reverted → psr/clock again in 3.0). That history is now resolved and stabilized on the PSR, but it signals a maintainer who iterated publicly. Pin the constraint and review before major bumps.
  • Dependency conflict surface. Any transitive dependency still on stella-maris/clock may produce a Composer-level disagreement with psr/clock. Cheap to check, but must be checked before we commit.
  • Missing capability, not missing quality. There is no framework integration for the scheduler, queues, or Eloquent timestamps, and there is no mock/spy clock with assertion helpers. Those are build-it-ourselves items, not defects.

Key questions for the team.

  1. Does our product actually need injectable time, or does Carbon::setTestNow() cover the flakiness we currently experience? If the answer is "only in tests," the ROI case weakens considerably.
  2. Which components must consume the clock, and which may keep using Carbon's facade? A partial answer produces two sources of truth — worse than one.
  3. What is our stance on FrozenClock versus Carbon::setTestNow() in the test suite — one, the other, or a documented hybrid?
  4. Do we require sub-second precision, leap-second handling, or NTP-synchronized time? If so, these implementations are a starting point only.
  5. Who owns the fork if upstream goes quiet — and what is the trigger for making that call?

Integration Approach

Stack fit. PHP ^8.0 per the 2.3.0 changelog note, with the PSR-20 contract stable across 3.x. That covers Laravel 9 through current. No Laravel version constraint to negotiate, no published config, no published migrations, no facade — nothing that couples us to a framework release cycle. In practice this is the lowest-friction category of dependency: an interface plus six small classes.

Migration path. Adoption is naturally incremental and should be sequenced that way:

  1. Baseline. Install, verify no Composer conflicts (particularly any lingering stella-maris/clock), lock the constraint.
  2. Bind once. A single ClockServiceProvider (or a block in AppServiceProvider) registering SystemClock/UTCClock as a singleton, with an environment-conditional switch to FrozenClock for the test environment.
  3. Test harness. A base TestCase helper that freezes and resets the clock, plus a convention that feature tests set time explicitly rather than relying on wall-clock behavior. This is where the QA-cycle benefit is actually realized.
  4. Greenfield first. All new services, commands, jobs, and domain value objects type-hint ClockInterface. No retrofitting in the same PR.
  5. Retrofit by risk. Prioritize the time-dependent code that has historically produced flaky tests or production incidents — trial expiries, subscription renewals, rate-limit windows, promotion start/end, scheduled report cutoffs. Leave cosmetic and display-only timestamps alone.
  6. Enforce. A static analysis rule or code review checklist that flags new Carbon::now(), time(), and date() calls inside domain/application layers.

Compatibility notes. Keep Eloquent's created_at/updated_at on the framework's own handling — overriding those through an injected clock is a well-known source of inconsistency and is not worth the purity. Where a value must be an actual DateTimeImmutable at a persistence boundary, convert explicitly rather than passing DateTimeInterface through. Be aware that ClockInterface::now() is declared to return DateTimeInterface while these implementations return DateTimeImmutable; type-hint the concrete return only if we control the call site, since a future alternative implementation is free to return a mutable instance.

Timezone sequencing. Make UTCClock the production default and require any LocalizedClock usage to be an explicit, justified decision at a display or regional-policy boundary. This prevents the most common failure mode in multi-region applications, where "now" means different things in different code paths depending on ambient PHP configuration.

Operational Impact

Maintenance. Low, and the low-maintenance property is structural rather than a function of upstream activity: the interface is a spec we don't control and can't break, and the implementations are a few dozen lines each. Even in the worst case — upstream abandons the package — the replacement cost is trivial. The corollary is that we should not expect upstream to fix our problems; the practical posture is "adopt with a documented fork trigger," not "adopt and wait for support."

Support model. There is no Laravel-idiomatic documentation, no facade, no config, and few enough adopters that community answers will be scarce. Internal documentation is a first-class deliverable here, not an afterthought: how to bind, how to freeze in tests, when to use UTCClock versus LocalizedClock, and how to handle legacy now()-bearing objects via WrappingClock. Budget a short internal write-up and an onboarding note; the package itself is easy, the convention is what needs documenting.

Scaling. Negligible runtime cost — one object allocation per resolution if registered as a singleton, no I/O, no reflection at call time. The scaling concern is organizational: as teams and services multiply, inconsistent clock usage compounds. The mitigation is the container binding plus a lint rule, established early. MinuteClock deserves a note because it truncates rather than rounds, which makes boundary equality assertions subtly wrong; if we use it for idempotency buckets, that behavior must be covered by tests.

Failure modes to design against.

  • Frozen clock in production. An environment-conditional binding that defaults to FrozenClock on a misconfigured APP_ENV would silently halt time for business logic. Make production the default branch and require an explicit opt-in for freezing.
  • Dual sources of truth. Tests using both Carbon::setTestNow() and an injected FrozenClock can disagree, producing passes that don't reflect production. Pick one primary mechanism and document the exception cases.
  • Timezone drift. SystemClock resolves timezone from ambient PHP configuration, which can differ between web, queue worker, and CLI. Standardizing on UTCClock in application code eliminates an entire incident class.
  • Partial adoption. Half the code using the clock and half calling Carbon::now() yields tests that pass while production behavior diverges. This is the most likely failure mode and the one most worth a policy for.
  • WrappingClock as a permanent shim. Useful for migration, but wrapping a legacy now()-bearing object indefinitely preserves the coupling we were trying to remove. Time-box it.

Ramp-up. Genuinely low. One interface, one method, six implementations, all covered by copy-pasteable README examples. A mid-level engineer is productive within an hour. The real ramp is the team norm — knowing when to inject a clock, which implementation to choose, and how to freeze time in tests — which is a documentation and code-review problem, not a training problem.

Recommendation. Adopt for the specific purpose of making time-dependent business logic testable and timezone-explicit, with eyes open that we are adopting a low-adoption library and therefore own the integration, the documentation, and the contingency. Do not adopt it as a wholesale replacement for Carbon, and do not attempt a big-bang retrofit — the value accrues from consistent use in the narrow set of code paths that actually cause incidents, and the risk accrues from spreading it thinly across code that doesn't need it.

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.
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
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