sanmai/duoclock
PSR-20 clock abstraction with dual time access (DateTimeImmutable, time(), microtime()) plus mockable sleep/usleep/nanosleep for testing. Includes deterministic TimeSpy and tick helpers to measure elapsed time. Minimal dependency (psr/clock) and easy to mock.
composer require sanmai/duoclock
DualClock holds two coordinated clocks: local (e.g., app server) and remote (e.g., external service/API).ClockSource instances (e.g., LocalClock, RemoteClock).use Sanmai\Duoclock\DualClock;
use Sanmai\Duoclock\Source\LocalClock;
use Sanmai\Duoclock\Source\RemoteClock;
$clock = new DualClock(
LocalClock::fromDateTime(new \DateTimeImmutable()),
RemoteClock::fromString($webhookHeader['X-Client-Timestamp'])
);
$this->app->singleton(DualClock::class, function () {
$local = LocalClock::fromCurrent();
$remote = RemoteClock::fromConfig(config('services.external.time_offset'));
return new DualClock($local, $remote);
});
public function handle(DualClock $clock)
{
$clientTime = $clock->normalizeTo('remote', $incomingTimestamp); // $incomingTimestamp is local-time
$event = new Event(['scheduled_at' => $clientTime]);
}
$drift = $clock->drift()->getTotalSeconds();
if (abs($drift) > 5) {
throw new \RuntimeException("Clock drift exceeded 5s: {$drift}s");
}
$clock = new DualClock(
LocalClock::fromString('2025-01-01 12:00:00'),
RemoteClock::fromString('2025-01-01 12:00:10') // remote is 10s ahead
);
RemoteClock must be constructed before or with known offset; don’t assume remote time is ever “now” unless you’ve verified sync.offset() for static calibration (e.g., config-based), and drift() for observed deviation over time.DateTimeImmutable; avoid reusing instances across threads or async flows.DualClock isn’t serializable. Store only the offset() or drift() data if needed (e.g., in job payload).ClockSource for HTTP time sources (e.g., NTP, API Date header) — RemoteClock is generic but not opinionated about source.DualClock::debug() (if available in future) or log $clock->drift() at the start of long-running tasks to catch gradual drift.How can I help you explore Laravel packages today?