eventsauce/clock
Simple clock abstraction for PHP. Use SystemClock in production and TestClock in tests to control time deterministically. Get now() as DateTimeImmutable, access timeZone(), move time forward, tick to system time, or fixate to a specific moment.
Installation
composer require eventsauce/clock
Add to composer.json if using a custom package:
"require": {
"eventsauce/clock": "^2.0"
}
Basic Usage
The package provides a Clock interface and a default SystemClock implementation. Start by injecting the Clock interface into your services:
use EventSauce\Clock\Clock;
class MyService {
public function __construct(private Clock $clock) {}
}
First Use Case: Time-Aware Logic
Use the clock()->now() method to get the current time in a deterministic way:
$now = $clock->now(); // Returns a \DateTimeImmutable
Laravel Service Provider
Bind the Clock interface to a concrete implementation (e.g., SystemClock or a custom clock) in a service provider:
use EventSauce\Clock\Clock;
use EventSauce\Clock\SystemClock;
public function register() {
$this->app->bind(Clock::class, function () {
return new SystemClock();
});
}
Testing with Fake Clocks
Replace SystemClock with a FakeClock for predictable tests:
use EventSauce\Clock\FakeClock;
$clock = new FakeClock(new \DateTimeImmutable('2023-01-01'));
$this->app->instance(Clock::class, $clock);
Scheduling Use the clock to determine if a task should run:
if ($clock->now() > $scheduledTime) {
$this->executeTask();
}
Time Windows Validate time-based constraints (e.g., business hours):
$openTime = new \DateTimeImmutable('09:00');
$closeTime = new \DateTimeImmutable('17:00');
$now = $clock->now();
if ($now < $openTime || $now > $closeTime) {
throw new \RuntimeException('Outside business hours');
}
Time Travel for Debugging In tests, simulate time progression:
$clock->setTime(new \DateTimeImmutable('2023-01-02'));
Queues and Jobs Use the clock to determine job execution time:
class ProcessOrder implements ShouldQueue {
public function __construct(private Clock $clock) {}
public function handle() {
$this->logExecutionTime($this->clock->now());
}
}
Caching Use the clock for cache invalidation logic:
$cacheKey = 'user:'.$userId;
$lastUpdated = $this->cache->get($cacheKey . ':last_updated');
if ($lastUpdated && $this->clock->now() > $lastUpdated->modify('+1 hour')) {
$this->cache->forget($cacheKey);
}
Time Zone Sensitivity
The SystemClock uses the system's default time zone. Ensure consistency across environments:
// Force a time zone (e.g., in a service provider)
date_default_timezone_set('UTC');
Immutable DateTime
The clock returns DateTimeImmutable objects. Avoid modifying them directly:
// Bad: Modifies the original
$now = $clock->now();
$now->modify('+1 day');
// Good: Creates a new instance
$tomorrow = $clock->now()->modify('+1 day');
Thread Safety
The SystemClock is thread-safe, but custom clocks may not be. Ensure thread safety if extending:
class CustomClock implements Clock {
private $time;
public function __construct(\DateTimeImmutable $initialTime) {
$this->time = $initialTime;
}
public function now(): \DateTimeImmutable {
return $this->time; // Thread-safe if $this->time is immutable
}
}
Log Clock Time Add logging to track time progression in tests or production:
\Log::debug('Current clock time:', ['time' => $clock->now()->format('Y-m-d H:i:s')]);
Verify FakeClock in Tests
Ensure FakeClock is properly injected and used:
$this->app->instance(Clock::class, new FakeClock(new \DateTimeImmutable('2023-01-01')));
$this->assertEquals('2023-01-01', $clock->now()->format('Y-m-d'));
Custom Clock Implementations Create a clock for specific use cases (e.g., database-backed time):
class DatabaseClock implements Clock {
public function now(): \DateTimeImmutable {
return \DB::table('clock')->value('current_time');
}
}
Time Adjustment Middleware Wrap the clock to adjust time for testing or feature flags:
class AdjustedClock implements Clock {
public function __construct(private Clock $clock, private int $offsetMinutes = 0) {}
public function now(): \DateTimeImmutable {
return $this->clock->now()->modify("+{$this->offsetMinutes} minutes");
}
}
Clock-Aware Models Extend Eloquent models to use the clock for time-sensitive logic:
use EventSauce\Clock\Clock;
class Post extends Model {
public function __construct(array $attributes = [], Clock $clock) {
parent::__construct($attributes);
$this->clock = $clock;
}
public function isRecent(): bool {
return $this->clock->now() < $this->created_at->modify('+7 days');
}
}
How can I help you explore Laravel packages today?