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

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.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit

  • symfony/clock is a micro-abstraction, not a framework: one interface (ClockInterface with now(), sleep(), withTimeZone()) and a handful of implementations (NativeClock, MockClock, MonotonicClock). It plugs into Laravel's container cleanly — a single bind(ClockInterface::class, NativeClock::class) in a service provider is the whole integration surface.
  • Fit is strongest where time is a business input, not a timestamp: trial expiry, subscription renewals, rate-limit windows, SLA clocks, dunning schedules, promo windows, retry/backoff in queued jobs, audit "as-of" decisions. It is not a fit for created_at/updated_at bookkeeping, which Eloquent already owns.
  • Overlap is the central architectural question. Laravel already ships Carbon::setTestNow(), travel()/freezeTime() in Illuminate\Foundation\Testing, and (11+) the Illuminate\Support\Sleep facade. symfony/clock does not replace these; it competes for the same developer habit. The architectural value is the injectable interface (constructor-visible dependency, swappable in prod, not only under test), which Carbon's static test-now does not give you. If we adopt it, we must say plainly that it is the standard for domain time while Carbon remains the standard for formatting and DB values — otherwise we get three time idioms in one codebase.
  • Existing usage elsewhere in the app: Symfony\Component\Clock\ClockInterface is already consumed by several Symfony components; psr/clock is a hard dependency, so an app-level PSR-20 adapter is nearly free if we prefer the PSR interface over Symfony's (which adds sleep() and withTimeZone()).

Integration feasibility

  • Low effort. Composer install, one provider, one binding, optionally an alias to Psr\Clock\ClockInterface. No config file, no migrations, no publishes, no env vars.
  • Adoption is strictly incremental and per-class; there is no "cutover." That is the main feasibility argument and also the main risk (see below).
  • Interop with Carbon is a one-liner: now() returns \DateTimeImmutable, and CarbonImmutable::createFromInterface($clock->now()) bridges into the existing ecosystem.

Technical risk

  • Partial adoption is worse than none. If only 40% of a domain reads time through the clock, tests still flake on the other 60%, and the abstraction earns its cost without delivering determinism. This is the primary risk, and it is a governance risk, not a library risk.
  • Production mis-binding. MockClock bound in a non-test environment freezes application time globally. Mitigation: bind MockClock only inside the test case/ClockSensitiveTrait scope, never in a provider gated on APP_ENV.
  • Version floor. The MockClock::sleep() negative-value divergence was fixed in 6.4.30 / 7.3.8. The new v8.1.9 patch release adds fixes for DatePoint being one hour off around the DST fall-back hour, relative-date DatePoint behavior, and MockClock::sleep() with dates before 1970. There are no breaking changes, new features, or deprecations in this release, but the 8.x floor should now be >=8.1.9 if we use that line. The 8.x line requires PHP >= 8.4; 7.4 remains the safe LTS target. If DST or pre-1970 MockClock behavior is relevant on 7.4, confirm equivalent fixes are present in that branch.
  • Adoption signal is misleading. "435 stars / 0 dependents" is a metric artifact of Symfony's split-repo publishing (most installs resolve through the monorepo/transitive constraints). Do not read this as low adoption; conversely, do not read Symfony's brand as a substitute for a fit assessment.
  • Clock semantics confusion. Wall-clock time (NativeClock) must not be used to measure elapsed durations; MonotonicClock exists precisely because system time can jump (NTP, DST, VM suspend). Mixing the two in retry/timeout logic is a subtle, hard-to-reproduce bug class.

Key questions

  1. How many current test failures/flakes trace to system time, and in which domains? (If the number is small, this is a nice-to-have, not a project.)
  2. Which time-sensitive domains will be fully converted, and who enforces "fully"? Name the bounded contexts.
  3. Are we standardizing on Symfony\Component\Clock\ClockInterface or Psr\Clock\ClockInterface? The PSR choice is more portable and survives a framework change.
  4. What is our PHP/Laravel matrix, and can we sit on the 7.4 LTS branch rather than tracking 8.x? If 8.x is required, can we pin >=8.1.9 and accept PHP >= 8.4?
  5. Does Illuminate\Support\Sleep::fake() plus travel() already cover the queued-job and scheduling cases? Where it does, we should not add a second mechanism.
  6. Is there a lint/static-analysis rule available to prevent now(), time(), date(), Carbon::now() inside the converted domain layer? Without enforcement, adoption decays.

Integration Approach

Stack fit

  • Laravel 10/11/12, PHP 8.2–8.3 → symfony/clock 7.4 LTS. PHP 8.4+ shops may take 8.x, pinned to >=8.1.9. Symfony 6.4 (PHP >= 8.1) remains viable for older Laravel.
  • symfony/clock is frequently already present transitively (Symfony console/http-foundation-driven packages, rate limiter, messenger). Run composer why symfony/clock before adding a direct requirement — we may only be pinning a constraint, not adding a dependency.
  • Fits the existing DI style: constructor injection, no facades, no service locator. Works unchanged under Octane and in queued workers because the clock is stateless (per-request timezone overrides use withTimeZone(), which returns a clone rather than mutating).

Migration path

  1. Add the direct constraint at the required floor (>=8.1.9 for 8.x; >=6.4.30/>=7.3.8 for the older lines where applicable); add the provider and bindings.
  2. Convert one high-value bounded context end to end (recommend: subscription/trial/entitlement logic — highest test cost, clearest business rules). Do not start with logging or metrics.
  3. In that context, replace now()/time()/Carbon::now() with the injected clock; replace usleep() with $clock->sleep() only where the sleep is domain-meaningful (retry backoff), not in test scaffolding.
  4. Add the test harness: MockClock per test, with setNow() fixtures. Establish one shared helper so every team writes time tests the same way.
  5. Hold. Measure flake rate and review feedback for one or two sprints before authorizing the next context.
  6. Then expand, context by context, with a static-analysis guardrail and a short written convention covering: clock for domain time, Carbon for formatting/persistence, monotonic clock for durations.

Compatibility

  • DateTimeImmutable out, so anything currently accepting Carbon needs a conversion at the boundary — cheap, but it is per-call-site work and should be counted.
  • No conflict with Carbon::setTestNow(); they can coexist, but coexistence should be temporary and deliberate, not permanent. Pick one per layer.
  • No conflict with Illuminate\Support\Sleep; if we adopt ClockInterface::sleep(), we should route queue/cron delay logic through it (or through Sleep) consistently rather than mixing both.
  • v8.1.9 only fixes DST/relative-date and pre-1970 MockClock edge cases; it does not change the DateTimeImmutable boundary conversion cost or the coexistence model above.

Sequencing

  • Week 1: dependency audit, version pin, provider + bindings, ADR published (clock vs. Carbon vs. Sleep boundaries).
  • Weeks 2–3: convert the pilot domain, build the shared test helper, convert its test suite.
  • Week 4: measure (flaky test count before/after, PR review friction), publish findings, go/no-go on expansion.
  • Ongoing: one context per sprint, guardrail rule enabled after the second context lands.

Operational Impact

Maintenance

  • Near-zero surface area: no configuration, no persistence, no scheduled tasks, no runtime services. The only ongoing chores are version bumps aligned with Symfony's release train and keeping the version floor above known bug fixes.
  • Symfony LTS provides a predictable support envelope (multi-year bug-fix and security windows on LTS branches), which makes upgrade planning routine rather than eventful. MIT license removes legal review as a blocker.
  • v8.1.9 is a patch-only release; treat it as a floor update, not a migration event. Cost remains concentrated in team discipline, not the artifact: the ADR, the guardrail rule, and code review attention.

Support

  • Upstream is Symfony's core team plus a large contributor base; documentation is a single concise page. There is no vendor to call, but the failure modes are shallow enough that in-house diagnosis is realistic.
  • Internally, name one owner for the clock convention (typically the platform/backend lead). Unowned abstractions drift back to Carbon::now() within a couple of quarters.

Scaling

  • Performance is a non-issue: object allocation per now() call in the same order as new DateTimeImmutable(). Compared with Carbon's heavier construction, this is neutral-to-cheaper.
  • Horizontally it is stateless and Octane-safe. The only scaling-adjacent concern is timezone consistency across regions — standardize on UTC inside the domain (withTimeZone('UTC') at the boundary) and convert at the presentation edge.
  • No impact on queue throughput; sleep() remains a blocking pause and must not be introduced into request paths.

Failure modes

  • Frozen production time — MockClock bound outside tests. Highest-severity, lowest-likelihood; prevented by binding scope discipline and a startup assertion in non-test environments.
  • False confidence — partial conversion, flaky tests persist, team concludes the abstraction "didn't work." Mitigated by context-by-context completion and by measuring flake rate rather than assuming it.
  • Timezone divergence — mixed use of server-local NativeClock and UTC-normalized clocks producing off-by-hours bugs at boundaries. Mitigated by normalizing at injection.
  • DST / relative-date edge cases in DatePoint — pre-v8.1.9 8.x installs can be one hour off around DST fall-back; mitigated by pinning 8.x to >=8.1.9 and verifying the 7.4 LTS branch for equivalent fixes if those cases matter.
  • Duration/wall-clock confusion — elapsed-time logic reading a wall clock and misbehaving around NTP corrections or DST. Mitigated by routing all duration measurement through MonotonicClock.
  • Silent version-floor regressions — resolving below the branch containing the MockClock negative-sleep, pre-1970 MockClock::sleep(), or DatePoint DST fixes. Mitigated by an explicit composer.json constraint, not a floating one.
  • Sleep misuse — $clock->sleep() in a web request or a synchronous path, holding a worker. Mitigated by convention plus review.

Ramp-up

  • A mid-level Laravel developer is productive in under a day: the README is the tutorial, and the concept (inject the clock) is familiar to anyone who has used Carbon::setTestNow(). The only genuine learning is the boundary convention — which clock for which purpose — and that should be a half-page ADR, not a training program.
  • For the pilot domain, expect modest reviewer friction in the first sprint as the team unlearns inline now(). That friction is the migration cost; it does not recur once the guardrail is in place.
  • Net TPM read: low-risk, low-cost, medium-value, adoption-dependent. v8.1.9 is a bug-fix floor bump, not an architectural change. The library will not fail us; the risk is that we half-adopt it and pay the cost without collecting the determinism dividend. Sequence accordingly.
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.
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
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