Overview
Phalcon\Time\Clock is a small abstraction that returns the current time as a DateTimeImmutable object. It allows you to decouple your code from the system clock, which makes time-dependent logic predictable and straightforward to test.
The component ships with two implementations:
- Phalcon\Time\Clock\SystemClock - returns the real, system time
- Phalcon\Time\Clock\FrozenClock - returns a fixed time, useful for testing
Both implementations adhere to the Phalcon\Time\Clock\ClockInterface contract:
<?php
namespace Phalcon\Time\Clock;
use DateTimeImmutable;
interface ClockInterface
{
public function now(): DateTimeImmutable;
}SystemClock
Phalcon\Time\Clock\SystemClock returns the current time using the timezone passed to its constructor. The class is final and cannot be extended.
<?php
use DateTimeZone;
use Phalcon\Time\Clock\SystemClock;
$clock = new SystemClock(new DateTimeZone('Pacific/Niue'));
echo $clock->now()->format('Y-m-d H:i:s');Constructor
public function __construct(DateTimeZone $timezone)The constructor requires a DateTimeZone object that will be used every time now() is called.
Factory methods
For convenience, two static constructors are exposed:
public static function fromSystemTimezone(): SystemClockReturns a new instance configured with the current default timezone (as returned by date_default_timezone_get()).
<?php
use Phalcon\Time\Clock\SystemClock;
$clock = SystemClock::fromSystemTimezone();public static function fromUTC(): SystemClockReturns a new instance configured with the UTC timezone.
<?php
use Phalcon\Time\Clock\SystemClock;
$clock = SystemClock::fromUTC();
echo $clock->now()->format(DATE_ATOM);now()
public function now(): DateTimeImmutableReturns a new DateTimeImmutable representing now in the configured timezone. Each call returns a fresh object with the current time.
<?php
use Phalcon\Time\Clock\SystemClock;
$clock = SystemClock::fromUTC();
$first = $clock->now();
sleep(1);
$second = $clock->now();
var_dump($first == $second); // falseFrozenClock
Phalcon\Time\Clock\FrozenClock always returns the same point in time until you change it. This is the implementation of choice for testing code that depends on the current time. The class is final and cannot be extended.
<?php
use DateTimeImmutable;
use Phalcon\Time\Clock\FrozenClock;
$clock = new FrozenClock(new DateTimeImmutable('2026-01-01 12:00:00'));
echo $clock->now()->format('Y-m-d H:i:s'); // 2026-01-01 12:00:00
echo $clock->now()->format('Y-m-d H:i:s'); // 2026-01-01 12:00:00Constructor
public function __construct(DateTimeImmutable $now)The constructor accepts a DateTimeImmutable object, which will be returned by every subsequent call to now() until the clock is mutated.
Factory methods
Same convenience constructors as SystemClock are available:
public static function fromSystemTimezone(): FrozenClockReturns a new instance frozen at now using the current default timezone.
public static function fromUTC(): FrozenClockReturns a new instance frozen at now using the UTC timezone.
<?php
use Phalcon\Time\Clock\FrozenClock;
$clock = FrozenClock::fromUTC();now()
public function now(): DateTimeImmutableReturns the DateTimeImmutable currently held by the clock. The returned value does not change until set() or adjust() is called.
set()
public function set(DateTimeImmutable $now): FrozenClockReplaces the time held by the clock. Every consumer that has a reference to the clock will observe the new value on the next call to now().
<?php
use DateTimeImmutable;
use Phalcon\Time\Clock\FrozenClock;
$clock = new FrozenClock(new DateTimeImmutable('2026-01-01 12:00:00'));
echo $clock->now()->format('Y-m-d H:i:s'); // 2026-01-01 12:00:00
$clock->set(new DateTimeImmutable('2026-06-15 09:30:00'));
echo $clock->now()->format('Y-m-d H:i:s'); // 2026-06-15 09:30:00adjust()
public function adjust(string $modifier): FrozenClockMutates the clock by applying a DateTimeImmutable::modify() expression. Every consumer that has a reference to the clock will observe the new value on the next call to now(). If the modifier string is invalid, a Phalcon\Time\Clock\Exception is thrown.
<?php
use DateTimeImmutable;
use Phalcon\Time\Clock\FrozenClock;
$clock = new FrozenClock(new DateTimeImmutable('2026-01-01 12:00:00'));
$clock->adjust('+1 day');
echo $clock->now()->format('Y-m-d H:i:s'); // 2026-01-02 12:00:00
$clock->adjust('+2 hours');
echo $clock->now()->format('Y-m-d H:i:s'); // 2026-01-02 14:00:00<?php
use DateTimeImmutable;
use Phalcon\Time\Clock\Exception;
use Phalcon\Time\Clock\FrozenClock;
$clock = new FrozenClock(new DateTimeImmutable('2026-01-01 12:00:00'));
try {
$clock->adjust('not a real modifier');
} catch (Exception $ex) {
echo $ex->getMessage(); // Invalid modifier: "not a real modifier"
}Dependency Injection
Because the clock is exposed through a small interface, it is a good candidate for dependency injection. Inject the interface into your services and pass the implementation that suits the context (system or frozen).
<?php
use Phalcon\Di\Di;
use Phalcon\Time\Clock\ClockInterface;
use Phalcon\Time\Clock\SystemClock;
$container = new Di();
$container->setShared(
ClockInterface::class,
function () {
return SystemClock::fromUTC();
}
);In your services, type-hint against ClockInterface:
<?php
use Phalcon\Time\Clock\ClockInterface;
class InvoiceService
{
public function __construct(
private ClockInterface $clock
) {
}
public function isOverdue(\DateTimeImmutable $dueDate): bool
{
return $this->clock->now() > $dueDate;
}
}In your tests, swap the implementation for a FrozenClock so that time-dependent assertions become deterministic.
<?php
use DateTimeImmutable;
use Phalcon\Time\Clock\FrozenClock;
$clock = new FrozenClock(new DateTimeImmutable('2026-01-01 12:00:00'));
$service = new InvoiceService($clock);
$dueDate = new DateTimeImmutable('2025-12-25 00:00:00');
var_dump($service->isOverdue($dueDate)); // trueExceptions
Any exception thrown by the component is a Phalcon\Time\Clock\Exception, which extends the base PHP \Exception class.
Granular Exceptions
The component raises granular subclasses of Phalcon\Time\Clock\Exception so callers can catch a specific failure mode. Existing catch (Phalcon\Time\Clock\Exception $e) blocks continue to work unchanged.
| Class | Parent | Thrown when |
|---|---|---|
Phalcon\Time\Clock\Exceptions\InvalidModifier |
Phalcon\Time\Clock\Exception |
FrozenClock::adjust() is given a modifier string that PHP cannot parse. |