lcobucci/clock
Clock abstraction for PHP to decouple your code from DateTimeImmutable instantiation. Depend on the Clock interface and use SystemClock for real time (with timezone support) or FrozenClock for deterministic, test-friendly time in unit tests.
Installation:
composer require lcobucci/clock
Basic Usage:
Inject Clock interface into your services. Use SystemClock in production and FrozenClock in tests:
use Lcobucci\Clock\Clock;
use Lcobucci\Clock\SystemClock;
class MyService {
public function __construct(private Clock $clock) {}
public function getCurrentTime() {
return $this->clock->now();
}
}
First Use Case:
Replace new DateTimeImmutable() calls with $clock->now() in business logic. Example:
$expired = $clock->now() > $item->expiryDate;
Lcobucci\Clock\Clock (PSR-20 compliant)SystemClock (production)FrozenClock (testing)SystemClock::fromUTC(), FrozenClock::fromUTC()Service Container Setup (Laravel):
// app/Providers/AppServiceProvider.php
public function register() {
$this->app->bind(Clock::class, function() {
return new SystemClock(new DateTimeZone('UTC'));
});
}
Testing Setup:
$this->app->instance(Clock::class, new FrozenClock(new DateTimeImmutable('2023-01-01')));
Time-Based Logic:
class Order {
public function isExpired(Clock $clock): bool {
return $clock->now() > $this->expiryDate;
}
}
Time Adjustments:
// For testing time travel
$clock = new FrozenClock($baseTime->modify('+1 day'));
Timezone Handling:
// Explicit timezone binding
$this->app->bind(Clock::class, fn() => new SystemClock(new DateTimeZone('America/New_York')));
Laravel Carbon Compatibility:
$carbonTime = Carbon::instance($clock->now());
Database Operations:
// Use clock time for timestamps
$record->created_at = $clock->now()->format('Y-m-d H:i:s');
Event Scheduling:
// Schedule events based on clock time
$event->scheduleAt($clock->now()->modify('+1 hour'));
Timezone Ambiguity:
SystemClock to avoid relying on system defaults.SystemClock::fromUTC() vs SystemClock::fromSystemTimezone().Immutable Objects:
SystemClock is immutable. Avoid modifying instances after creation.DateTimeImmutable:
DateTimeImmutable objects. Ensure your code handles these properly (e.g., no modify() calls that return DateTime).Clock Time Mismatch:
SystemClock and application logic.FrozenClock to debug time-sensitive operations.Performance:
SystemClock is lightweight. Avoid creating multiple instances unnecessarily.Custom Clock Implementations:
class CustomClock implements Clock {
public function now(): DateTimeImmutable {
return new DateTimeImmutable('now', new DateTimeZone('Custom/Timezone'));
}
}
Time Adjustment:
// For testing relative time changes
$clock = new FrozenClock($baseTime);
$adjustedClock = new class($clock) implements Clock {
public function now(): DateTimeImmutable {
return $this->clock->now()->modify('+2 hours');
}
};
Laravel Configuration:
AppServiceProvider to ensure consistency across the application.$this->app->singleton(Clock::class, fn() => new SystemClock(new DateTimeZone(config('app.timezone'))));
Testing:
FrozenClock for unit tests to isolate time-dependent logic.$this->beforeApplicationDestroyed(function () {
$this->app->instance(Clock::class, new SystemClock(new DateTimeZone('UTC')));
});
Time Travel Testing:
// Test future/past scenarios
$futureClock = new FrozenClock(new DateTimeImmutable('+1 year'));
$this->app->instance(Clock::class, $futureClock);
Performance Testing:
SystemClock vs FrozenClock in high-frequency operations.Legacy Code:
DateTime logic:
class ClockDecorator implements Clock {
public function __construct(private Clock $clock) {}
public function now(): DateTimeImmutable {
return $this->clock->now();
}
}
How can I help you explore Laravel packages today?