Skip to content

Мета-документ Clock

1. Краткое описание

Получение текущего времени в приложениях обычно достигается с помощью функций time() или microtime, либо через создание объекта new \DateTimeImmutable().

В силу природы течения времени эти методы нельзя использовать тогда, когда необходимы предсказуемые результаты, например при тестировании.

Данный ClockInterface призван предоставить стандартный способ работы с временем, обеспечивающий совместимость не только при использовании «реального» времени, но и в случаях, когда требуются предсказуемые результаты. Это позволяет избежать необходимости использовать расширения PHP для тестирования или переопределять функцию time() в локальном пространстве имён.

2. Зачем это нужно?

В настоящее время существует несколько библиотек, предоставляющих данную функциональность, однако совместимости между этими библиотеками нет, поскольку каждая из них поставляется со своим собственным интерфейсом часов.

Symfony предоставляет пакет symfony/phpunit-bridge, в котором есть класс Symfony\Bridge\PhpUnit\ClockMock, позволяющий подменять встроенные функции времени и даты PHP, однако это не решает проблему подмены вызовов new \DateTimeImmutable(). Также он не полностью подменяет время при вызовах из других библиотек, которые опираются на системное время.

Carbon\Carbon и его форк Cake\Chronos\Chronos действительно предоставляют подмену через статический метод setTestNow(), но это не обеспечивает изоляции и требует повторного вызова для отключения подмены.

Преимущества:

  • Единообразный интерфейс для получения текущего времени;
  • Простота подмены системного времени для обеспечения воспроизводимости результатов.

Недостатки:

  • Дополнительные накладные расходы и усилия разработчика для получения текущего времени — не так просто, как вызов time() или date().

3. Область применения

3.1 Цели

  • Предоставить простой и легко подменяемый способ чтения текущего времени;
  • Обеспечить совместимость между библиотеками при чтении часов.

3.2 Не входит в область применения

  • Данный PSR не даёт рекомендаций о том, как и когда использовать описанные в этом документе концепции, поэтому он не является стандартом кодирования;
  • Данный PSR не даёт рекомендаций о том, как обрабатывать часовые пояса при получении текущего времени. Это остаётся на усмотрение реализации.
  • Данный PSR не охватывает методы планирования, такие как sleep() или wait(), поскольку такие методы не связаны с получением текущего времени.

4. Подходы

4.1 Выбранный подход

Мы решили формализовать существующие практики, применяемые в нескольких других пакетах. Среди популярных пакетов, предоставляющих данную функциональность:

(Список не является исчерпывающим!)

Некоторые из них предоставляют интерфейсы, а некоторые используют расширение класса часов для подмены текущего времени.

Все эти реализации предоставляют метод now(), возвращающий объект DateTimeImmutable. Поскольку объект DateTimeImmutable позволяет получить Unix-временную метку путём вызова getTimestamp() или format('u.U'), данный интерфейс не определяет специальных методов для получения Unix-временной метки или любой другой временной информации, которая недоступна через объект DateTimeImmutable.

4.2 Часовые пояса

Время в настоящее время определяется взаимодействием электромагнитного излучения с возбуждёнными состояниями определённых атомов: СИ определяет одну секунду как продолжительность 9192631770 циклов излучения, соответствующих переходу между двумя уровнями энергии основного состояния атома цезия-133 при 0K. Это означает, что получение текущего времени всегда вернёт одно и то же время, независимо от того, где оно наблюдается. Хотя часовой пояс определяет место наблюдения времени, он не изменяет фактический «момент» времени.

Это означает, что в целях настоящего PSR часовой пояс считается деталью реализации интерфейса.

Реализация несёт ответственность за то, чтобы часовой пояс обрабатывался в соответствии с бизнес-логикой приложения. Это достигается либо путём обеспечения того, что вызов now() будет возвращать только объект DateTimeImmutable с известным часовым поясом (неявный контракт), либо путём явного изменения часового пояса на корректный для приложения. Это можно сделать, вызвав setTimezone() для создания нового объекта DateTimeImmutable с заданным часовым поясом.

Данные действия не определены в этом интерфейсе.

4.2 Примеры реализаций

final class SystemClock implements \Psr\Clock\ClockInterface
{
    public function now(): \DateTimeImmutable
    {
        return new \DateTimeImmutable();
    }
}

final class FrozenClock implements \Psr\Clock\ClockInterface
{
    private \DateTimeImmutable $now;

    public function __construct(\DateTimeImmutable $now)
    {
        $this->now = $now;
    }

    public function now(): \DateTimeImmutable
    {
        return clone $this->now;
    }
}

5. Участники

5.1 Редактор

  • Chris Seufert

5.2 Спонсор

  • Chuck Burgess

5.3 Члены рабочей группы

  • Luís Cobucci
  • Pol Dellaiera
  • Ben Edmunds
  • Jérôme Gamez
  • Andreas Heigl
  • Andreas Möller

6. Голосования

7. Ссылки по теме

  • https://github.com/ergebnis/clock/blob/main/src/Clock.php
  • https://github.com/icecave/chrono/blob/master/src/Clock/ClockInterface.php
  • https://github.com/Kdyby/DateTimeProvider/blob/master/src/DateTimeProviderInterface.php
  • https://github.com/kreait/clock-php/blob/main/src/Clock.php
  • https://github.com/lcobucci/clock/blob/2.1.x/src/Clock.php
  • https://github.com/mangoweb-backend/clock/blob/master/src/Clock.php
  • https://martinfowler.com/bliki/ClockWrapper.html

8. Прошлые участники

Этот документ является результатом работы многих людей в предыдущие годы, мы признаём их вклад:

* Примечание: Порядок убывающий хронологический.