Skip to content

Clock Component


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 easy to test.

The component ships with two implementations:

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(): SystemClock
Returns 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(): SystemClock
Returns 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(): DateTimeImmutable
Returns 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); // false

FrozenClock

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:00

Constructor

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(): FrozenClock
Returns a new instance frozen at now using the current default timezone.

public static function fromUTC(): FrozenClock
Returns a new instance frozen at now using the UTC timezone.

<?php

use Phalcon\Time\Clock\FrozenClock;

$clock = FrozenClock::fromUTC();

now()

public function now(): DateTimeImmutable
Returns 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): FrozenClock
Replaces 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:00

adjust()

public function adjust(string $modifier): FrozenClock
Mutates 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"
}

Exception

Any exception thrown by the component is a Phalcon\Time\Clock\Exception, which extends the base PHP \Exception class. At the moment, it is only thrown by FrozenClock::adjust() when the modifier string cannot be parsed.

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)); // true