create-psr20-clock
PSR-20 Clock Generator
Overview
Generates PSR-20 compliant clock implementations for time abstraction.
When to Use
- Time-dependent business logic
- Testing time-sensitive code
- Scheduling and time calculations
- Reproducible time behavior
Template: Clock Interface
<?php
declare(strict_types=1);
namespace App\Infrastructure\Clock;
use DateTimeImmutable;
interface ClockInterface
{
public function now(): DateTimeImmutable;
}
Template: System Clock
<?php
declare(strict_types=1);
namespace App\Infrastructure\Clock;
use DateTimeImmutable;
use Psr\Clock\ClockInterface;
final readonly class SystemClock implements ClockInterface
{
public function __construct(
private ?string $timezone = null,
) {
}
public function now(): DateTimeImmutable
{
$now = new DateTimeImmutable('now');
if ($this->timezone !== null) {
return $now->setTimezone(new \DateTimeZone($this->timezone));
}
return $now;
}
}
Template: Frozen Clock (Testing)
<?php
declare(strict_types=1);
namespace App\Infrastructure\Clock;
use DateTimeImmutable;
use Psr\Clock\ClockInterface;
final class FrozenClock implements ClockInterface
{
public function __construct(
private DateTimeImmutable $frozenAt,
) {
}
public function now(): DateTimeImmutable
{
return $this->frozenAt;
}
public function setTo(DateTimeImmutable $dateTime): void
{
$this->frozenAt = $dateTime;
}
public static function at(string $datetime): self
{
return new self(new DateTimeImmutable($datetime));
}
public static function fromTimestamp(int $timestamp): self
{
return new self((new DateTimeImmutable())->setTimestamp($timestamp));
}
}
Template: Offset Clock
<?php
declare(strict_types=1);
namespace App\Infrastructure\Clock;
use DateInterval;
use DateTimeImmutable;
use Psr\Clock\ClockInterface;
final readonly class OffsetClock implements ClockInterface
{
public function __construct(
private ClockInterface $baseClock,
private DateInterval $offset,
private bool $subtract = false,
) {
}
public function now(): DateTimeImmutable
{
$now = $this->baseClock->now();
return $this->subtract
? $now->sub($this->offset)
: $now->add($this->offset);
}
public static function ahead(ClockInterface $clock, DateInterval $offset): self
{
return new self($clock, $offset, false);
}
public static function behind(ClockInterface $clock, DateInterval $offset): self
{
return new self($clock, $offset, true);
}
public static function daysAhead(ClockInterface $clock, int $days): self
{
return new self($clock, new DateInterval("P{$days}D"), false);
}
public static function daysBehind(ClockInterface $clock, int $days): self
{
return new self($clock, new DateInterval("P{$days}D"), true);
}
}
Template: Monotonic Clock
<?php
declare(strict_types=1);
namespace App\Infrastructure\Clock;
use DateTimeImmutable;
use Psr\Clock\ClockInterface;
final class MonotonicClock implements ClockInterface
{
private ?DateTimeImmutable $lastTime = null;
public function __construct(
private readonly ClockInterface $baseClock,
) {
}
public function now(): DateTimeImmutable
{
$current = $this->baseClock->now();
if ($this->lastTime !== null && $current <= $this->lastTime) {
// Ensure time always moves forward
$current = $this->lastTime->modify('+1 microsecond');
}
$this->lastTime = $current;
return $current;
}
}
Template: Unit Test
<?php
declare(strict_types=1);
namespace App\Tests\Unit\Infrastructure\Clock;
use App\Infrastructure\Clock\FrozenClock;
use App\Infrastructure\Clock\OffsetClock;
use App\Infrastructure\Clock\SystemClock;
use DateInterval;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase;
#[Group('unit')]
#[CoversClass(SystemClock::class)]
#[CoversClass(FrozenClock::class)]
#[CoversClass(OffsetClock::class)]
final class ClockTest extends TestCase
{
public function test_system_clock_returns_current_time(): void
{
$clock = new SystemClock();
$before = new DateTimeImmutable();
$now = $clock->now();
$after = new DateTimeImmutable();
self::assertGreaterThanOrEqual($before, $now);
self::assertLessThanOrEqual($after, $now);
}
public function test_system_clock_with_timezone(): void
{
$clock = new SystemClock('UTC');
$now = $clock->now();
self::assertSame('UTC', $now->getTimezone()->getName());
}
public function test_frozen_clock_returns_fixed_time(): void
{
$frozenTime = new DateTimeImmutable('2024-01-15 10:30:00');
$clock = new FrozenClock($frozenTime);
self::assertEquals($frozenTime, $clock->now());
self::assertEquals($frozenTime, $clock->now());
}
public function test_frozen_clock_can_be_updated(): void
{
$clock = FrozenClock::at('2024-01-15 10:30:00');
$newTime = new DateTimeImmutable('2024-06-01 12:00:00');
$clock->setTo($newTime);
self::assertEquals($newTime, $clock->now());
}
public function test_offset_clock_adds_time(): void
{
$baseClock = FrozenClock::at('2024-01-15 10:30:00');
$clock = OffsetClock::daysAhead($baseClock, 5);
$expected = new DateTimeImmutable('2024-01-20 10:30:00');
self::assertEquals($expected, $clock->now());
}
public function test_offset_clock_subtracts_time(): void
{
$baseClock = FrozenClock::at('2024-01-15 10:30:00');
$clock = OffsetClock::daysBehind($baseClock, 5);
$expected = new DateTimeImmutable('2024-01-10 10:30:00');
self::assertEquals($expected, $clock->now());
}
}
Usage Example
<?php
use App\Infrastructure\Clock\FrozenClock;
use App\Infrastructure\Clock\SystemClock;
// Production: Use system clock
$clock = new SystemClock('UTC');
$service = new SubscriptionService($clock);
// Testing: Use frozen clock
$clock = FrozenClock::at('2024-01-15 10:30:00');
$service = new SubscriptionService($clock);
// Service using clock
final readonly class SubscriptionService
{
public function __construct(
private ClockInterface $clock,
) {
}
public function isExpired(Subscription $subscription): bool
{
return $subscription->expiresAt() < $this->clock->now();
}
public function daysUntilExpiry(Subscription $subscription): int
{
$diff = $this->clock->now()->diff($subscription->expiresAt());
return $diff->invert ? 0 : $diff->days;
}
}
File Placement
| Component | Path |
|---|---|
| Clock Interface | src/Infrastructure/Clock/ClockInterface.php |
| System Clock | src/Infrastructure/Clock/SystemClock.php |
| Frozen Clock | src/Infrastructure/Clock/FrozenClock.php |
| Offset Clock | src/Infrastructure/Clock/OffsetClock.php |
| Monotonic Clock | src/Infrastructure/Clock/MonotonicClock.php |
| Tests | tests/Unit/Infrastructure/Clock/ |
Requirements
{
"require": {
"psr/clock": "^1.0"
}
}
More from dykyi-roman/awesome-claude-code
psr-overview-knowledge
PHP Standards Recommendations (PSR) overview knowledge base. Provides comprehensive reference for all accepted PSRs including PSR-1,3,4,6,7,11,12,13,14,15,16,17,18,20. Use for PSR selection decisions and compliance audits.
22detect-code-smells
Detects code smells in PHP codebases. Identifies God Class, Feature Envy, Data Clumps, Long Parameter List, Long Method, Primitive Obsession, Message Chains, Inappropriate Intimacy. Generates actionable reports with refactoring recommendations.
15clean-arch-knowledge
Clean Architecture knowledge base. Provides patterns, antipatterns, and PHP-specific guidelines for Clean Architecture and Hexagonal Architecture audits.
15ddd-knowledge
DDD architecture knowledge base. Provides patterns, antipatterns, and PHP-specific guidelines for Domain-Driven Design audits.
14testing-knowledge
Testing knowledge base for PHP 8.4 projects. Provides testing pyramid, AAA pattern, naming conventions, isolation principles, DDD testing guidelines, and PHPUnit patterns.
12bug-root-cause-finder
Root cause analysis methods for PHP bugs. Provides 5 Whys technique, fault tree analysis, git bisect guidance, and stack trace parsing.
12