Мета-документ 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. Прошлые участники
Этот документ является результатом работы многих людей в предыдущие годы, мы признаём их вклад:
* Примечание: Порядок убывающий хронологический.