cloud-castle / clock
PSR-20 набор часов для PHP 8.1+: системные, замороженные, мок-, монотонные, со смещением, масштабом и усечением, шаговые и очередь; TimeSpan с наносекундной точностью, Stopwatch, Deadline, DatePoint и CalendarDate — календарная арифметика месяцев/лет с контролем переполнения и локализацией уровня Ca
Requires
- php: >=8.1
- cloud-castle/inflector: ^1.4
- psr/clock: ^1.0
Requires (Dev)
- beste/clock: ^3.0
- deptrac/deptrac: ^3.0 || ^4.0
- ergebnis/clock: ^0.2 || ^1.0 || ^2.0
- ergebnis/composer-normalize: ^2.45
- friendsofphp/php-cs-fixer: ^3.75
- icanhazstring/composer-unused: ^0.9
- infection/infection: ^0.29 || ^0.33
- lcobucci/clock: ^3.0
- nesbot/carbon: ^2.72 || ^3.0
- php-parallel-lint/php-parallel-lint: ^1.4
- phpmd/phpmd: ^2.15
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^1.12 || ^2.1
- phpstan/phpstan-deprecation-rules: ^1.2 || ^2.0
- phpstan/phpstan-phpunit: ^1.4 || ^2.0
- phpstan/phpstan-strict-rules: ^1.6 || ^2.0
- phpunit/phpunit: ^10.5 || ^11.5
- psalm/plugin-phpunit: ^0.19 || ^0.20
- rector/rector: ^1.2 || ^2.0
- roave/security-advisories: dev-latest
- squizlabs/php_codesniffer: ^3.12 || ^4.0
- symfony/clock: ^6.4 || ^7.0
- vimeo/psalm: ^6.0
- webmozart/assert: ^1.11
Suggests
None
Provides
Conflicts
None
Replaces
None
- v1.4.0
- v1.3.1
- v1.3.0
- v1.1.2
- v1.1.1
- v1.1.0
- dev-main / 1.0.x-dev
- v1.0.5
- v1.0.2
- v1.0.1
- v1.0.0
- v0.1.0
- dev-feat/superset-v1.4
- dev-fix/security-audit
- dev-fix/honest-retry
- dev-feat/calendar-full
- dev-fix/pages-site-index
- dev-docs/sync-calendar
- dev-fix/ci-resilience
- dev-fix/comparison-mbstring
- dev-feat/calendar-date
- dev-claude/relaxed-spence-d4fb51
- dev-feat/superset-v1.2
- dev-chore/deps
This package is auto-updated.
Last update: 2026-09-28 20:47:20 UTC
README
🇷🇺 Русский · 🇬🇧 English · 🇩🇪 Deutsch · 🇫🇷 Français · 🇪🇸 Español · 🇮🇹 Italiano
CloudCastle Clock
PSR-20 часы для PHP 8.1+ с детерминированным временем в тестах: 11 реализаций часов (системные, замороженные, мок-, монотонные, со смещением, масштабом, усечением, шаговые, очередь, на замыкании, адаптер PSR-20),
TimeSpanс наносекундной точностью,Stopwatch,Deadline,DatePointиCalendarDate— календарная работа уровня Carbon с локализацией без ICU/ext-intl. Лёгкие зависимости:psr/clockиcloud-castle/inflector.
Установка
composer require cloud-castle/clock
Требуется PHP 8.1+ и расширение mbstring (его запрашивает
cloud-castle/inflector — в типовых сборках PHP оно уже включено).
Быстрый старт
<?php
use CloudCastle\Clock\CalendarDate;
use CloudCastle\Clock\CalendarPeriod;
use CloudCastle\Clock\Clock;
use CloudCastle\Clock\ClockHolder;
use CloudCastle\Clock\Deadline;
use CloudCastle\Clock\MockClock;
use CloudCastle\Clock\Stopwatch;
use CloudCastle\Clock\TimeSpan;
// Продакшн: системные часы в нужной зоне, внедряются как ClockInterface.
$clock = Clock::utc();
$moment = $clock->now(); // DateTimeImmutable
$unix = $clock->timestamp(); // int без создания объекта даты
// Тесты: мок-часы с виртуальным сном — retry/backoff за миллисекунды.
$mock = MockClock::from('2026-01-01 00:00:00', 'UTC');
$mock->sleep(30.0); // время сдвинулось, процесс не спал
$mock->advance(TimeSpan::ofMinutes(5)->seconds());
// Глобальные часы со скоуп-подменой: после колбэка всё как было.
ClockHolder::within($mock, function (): void {
// Clock::now() внутри читает мок-часы.
});
// Длительности с наносекундной точностью.
$span = TimeSpan::fromString('1h 30m')->plus(TimeSpan::ofSeconds(15.5));
echo $span->toIso8601(); // PT1H30M15.5S
// Дедлайны, привязанные к часам.
$deadline = Deadline::after($clock, TimeSpan::ofSeconds(30));
if (!$deadline->isExpired()) {
$left = $deadline->remaining(); // TimeSpan
}
// Секундомер на монотонном таймере — с кругами.
$watch = Stopwatch::start();
$watch->lap('подготовка');
$seconds = $watch->stop();
// Календарная работа уровня Carbon без ICU/ext-intl.
$date = CalendarDate::parse('2026-01-31', 'UTC');
echo $date->addMonthsNoOverflow(1)->format('Y-m-d'); // 2026-02-28 (не «3 марта»)
echo $date->subDays(5)->diffForHumans($date, 'ru'); // «5 дней назад» (склонение через inflector)
// Периоды: ленивая итерация с шагом, без материализации диапазона.
foreach (CalendarPeriod::between($date, $date->addMonths(3), '1 month') as $month) {
echo $month->format('Y-m-d');
}
echo TimeSpan::fromString('1h 30m')->forHumans('ru', 2); // «1 час 30 минут»
// Макросы: доменные методы дат без наследования.
CalendarDate::macro('isPayday', static fn (CalendarDate $d): bool => $d->format('j') === '25');
$date->callMacro('isPayday'); // false
// Легаси-часы с методом now() без PSR-20 — тоже часы пакета.
$adapted = Clock::adapt($legacyClock);
В тестах PHPUnit 10+ подмена глобального времени — одной строкой, восстановление после теста автоматическое:
use CloudCastle\Clock\Testing\ClockSensitiveTrait;
final class InvoiceTest extends TestCase
{
use ClockSensitiveTrait;
public function testDueDate(): void
{
$clock = self::mockTime('2026-01-01 10:00');
$clock->sleep(86_400); // сутки виртуально
self::assertSame('2026-01-02', (new DatePoint())->format('Y-m-d'));
}
}
Возможности
- 11 реализаций часов:
SystemClock,FrozenClock,MockClock(виртуальныйsleep()),MonotonicClock,OffsetClock,ScaledClock,TruncatingClock,StepClock,QueueClock,CallbackClock,PsrClockAdapter(оборачивает любые PSR-20 часы и легаси-объекты с публичнымnow()). Системные и монотонные часы умеют реальныйsleep(), досыпая остаток после сигнала. TimeSpan— длительность с наносекундной точностью: фабрики от наносекунд до недель, разбор ISO 8601 /1h 30m/ relative-строк PHP, точная арифметика с контролем переполнения, человекочитаемыйforHumans()на 6 языках.CalendarPeriod— ленивая итерация календарного периода генератором с шагомTimeSpan/DateInterval/строкой и управлением границами.- Макросы
CalendarDate— доменные методы дат без наследования, с типизированнымcallMacro()для статанализа. Testing\ClockSensitiveTrait—mockTime()/unmockTime()в PHPUnit 10+ с автоматическим восстановлением глобальных часов.Deadline— дедлайны сisExpired()/remaining(), детерминированные в тестах.Stopwatch— монотонный секундомер с кругами иmeasure(callable).DatePoint— моменты от часов приложения с типизированными ошибками.CalendarDate— календарная работа уровня Carbon без ICU/ext-intl: арифметика месяцев/лет с контролем переполнения (addMonthsNoOverflow()), границы периодов/декады/века, предикаты (isWeekend(),isToday(),isLastOfMonth()), разницы (diffInDays()…diffInYears()), навигация (next(),firstOfMonth()), fluent-сеттеры, форматтеры (toDateString(), ISO/RFC/ATOM/W3C), детерминированный парсинг относительных строк (parse('next friday')от часов приложения — без глобальногоsetTestNow), локализованные названия иdiffForHumans()на 6 языках (склонения — черезcloud-castle/inflector, включая три славянские формы русского).ClockHolder— глобальные часы сwithin()(скоуп-подмена с автовосстановлением).- Дешёвые примитивы
timestamp()/microtime()без материализации объекта даты.
Полное описание каждой возможности — в wiki.
Сравнение с аналогами
Все таблицы ниже сгенерированы автоматически из честных сравнительных тестов (benchmarks/compare.php) на ОДИНАКОВОЙ операции для всех аналогов, PHP 8.3.32, без Xdebug.
1. Функциональность
| Возможность | 🏆 CloudCastle | symfony | carbon | lcobucci | beste | ergebnis | new¹ |
|---|---|---|---|---|---|---|---|
| PSR-20 ClockInterface (now(): DateTimeImmutable) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
| Замороженные часы для детерминизма тестов | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
| Мок-часы с виртуальным sleep() | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Монотонные часы (защита от прыжков системного времени) | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Смещение времени декоратором (OffsetClock) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Масштаб времени декоратором (ScaledClock) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Усечение моментов до произвольной единицы | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Value object длительности с наносекундной точностью | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Дедлайны с остатком времени (Deadline) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Шаговые часы и часы-очередь для сценарных тестов | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Секундомер с кругами в составе пакета | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Дешёвые примитивы timestamp()/microtime() без объекта даты | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Момент от часов приложения (DatePoint) | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Скоуп-подмена глобальных часов с автовосстановлением | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Адаптер произвольных PSR-20 часов | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ |
| Календарная арифметика месяцев/лет с контролем переполнения (CalendarDate) | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Локализованный diffForHumans и isoFormat без ext-intl | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Календарные предикаты и разницы (isWeekend, diffInDays, next) | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| PHPUnit-трейт подмены времени (mockTime/unmockTime) | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Реальный sleep() у системных и монотонных часов | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Обёртка любого объекта с методом now() в PSR-20 | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
| Итерация календарного периода с шагом (CalendarPeriod) | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Человекочитаемая длительность (TimeSpan::forHumans) | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Макросы: расширение API дат без наследования | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Всего | 🏆 24 | 8 | 10 | 2 | 4 | 2 | 0 |
2. Безопасность и корректность
| Свойство | 🏆 CloudCastle | symfony | carbon | lcobucci | beste | ergebnis | new¹ |
|---|---|---|---|---|---|---|---|
| Иммутабельные показания (DateTimeImmutable) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Явная временная зона (не зависит от date.timezone) | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ |
| Детерминизм тестов без обязательного глобального состояния | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ |
| Монотонность (устойчивость к откату системных часов) | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Типизированные исключения вместо false/warning на границе ввода | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Санитайзинг враждебного ввода в сообщениях исключений | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Контроль целочисленного переполнения длительностей | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Минимальные зависимости (≤2, без ICU/ext-intl) | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ |
| Всего | 🏆 8 | 4 | 3 | 4 | 3 | 4 | 2 |
2.1. Соответствие стандартам безопасности composer-пакетов
| Стандарт | CloudCastle | symfony | carbon | lcobucci | beste | ergebnis | 🏆 Победители |
|---|---|---|---|---|---|---|---|
| Политика безопасности (SECURITY.md или опубликованный процесс) | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | CloudCastle, symfony, carbon, beste, ergebnis |
| Контроль известных уязвимостей зависимостей (roave/security-advisories + composer audit) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | CloudCastle |
| OpenSSF Scorecard: опубликованная оценка ≥ 7/10 | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | symfony |
| CWE-20: валидация ввода с типизированными исключениями | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | CloudCastle, carbon |
| CWE-117/CWE-150: нейтрализация управляющих символов в сообщениях об ошибках | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | CloudCastle |
| CWE-190: контроль целочисленного переполнения | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | CloudCastle |
| Цепочка поставок: ≤2 runtime-зависимости без polyfill/ICU | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | CloudCastle, lcobucci, beste, ergebnis |
| OpenSSF Best Practices: автотесты с мутационным контролем в CI | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | CloudCastle, lcobucci, ergebnis |
| Лицензия OSI в дистрибутиве (MIT) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | CloudCastle, symfony, carbon, lcobucci, beste, ergebnis |
| Всего | 🏆 8 | 3 | 3 | 3 | 3 | 4 | CloudCastle |
Источники: файлы SECURITY.md в репозиториях (API GitHub), require-dev дистрибутивов на Packagist, api.securityscorecards.dev (symfony/clock — оценка монорепозитория symfony/symfony; OpenSSF Scorecard оценивает только проекты на GitHub, поэтому у CloudCastle на GitVerse оценки нет — честный минус площадки); строки CWE — по тестам безопасности пакетов; symfony/clock для PHP 8.1 — ветка 6.4 с polyfill-php83.
3. Производительность: now()
Получение текущего момента now(), 200 000 раз (минимум из 15).
| Решение | Время (мс) | Итог |
|---|---|---|
| new¹ | 109,7 | базовый уровень (не библиотека) |
| 🏆 CloudCastle | 114,5 | быстрейшее среди библиотек |
| 🏆 ergebnis | 115,6 | наравне с лидером (в пределах погрешности) |
| 🏆 lcobucci | 116 | наравне с лидером (в пределах погрешности) |
| 🏆 beste | 116,8 | наравне с лидером (в пределах погрешности) |
| symfony | 154,6 | аналог |
| carbon | 649,8 | аналог |
4. Производительность: unix-метка
Текущая unix-метка (целые секунды), 200 000 раз.
| Решение | Время (мс) | Итог |
|---|---|---|
| new¹ | 16 | базовый уровень (не библиотека) |
| 🏆 CloudCastle | 27,8 | быстрейшее среди библиотек |
| ergebnis | 122,3 | аналог |
| lcobucci | 123 | аналог |
| beste | 125,6 | аналог |
| symfony | 164,2 | аналог |
| carbon | 658,7 | аналог |
5. Потребление памяти библиотекой
Прирост памяти самой библиотеки на 200 000 вызовов now() (изолированный процесс, без веса автолоадера).
| Решение | Память (KB) | Итог |
|---|---|---|
| new¹ | 1 | базовый уровень (не библиотека) |
| 🏆 beste | 3 | легчайшее среди библиотек |
| lcobucci | 5 | аналог |
| ergebnis | 5 | аналог |
| symfony | 30 | аналог |
| CloudCastle | 31 | аналог |
| carbon | 3 425 | аналог |
6. Утечки памяти
Рост памяти после прогрева за 200 000 вызовов now() (0 — утечек нет).
| Решение | Память (KB) | Итог |
|---|---|---|
| 🏆 CloudCastle | 0 | без утечек |
| symfony | 0 | без утечек |
| carbon | 0 | без утечек |
| lcobucci | 0 | без утечек |
| beste | 0 | без утечек |
| ergebnis | 0 | без утечек |
| new¹ | 0 | базовый уровень (не библиотека) |
7. Пик памяти процесса
Пик памяти изолированного процесса на 200 000 вызовов now().
| Решение | Память (KB) | Итог |
|---|---|---|
| 🏆 CloudCastle | 7 035 | легчайшее среди библиотек |
| 🏆 symfony | 7 035 | наравне с лидером (в пределах погрешности) |
| 🏆 lcobucci | 7 035 | наравне с лидером (в пределах погрешности) |
| 🏆 beste | 7 035 | наравне с лидером (в пределах погрешности) |
| 🏆 ergebnis | 7 035 | наравне с лидером (в пределах погрешности) |
| new¹ | 7 035 | базовый уровень (не библиотека) |
| carbon | 10 303 | аналог |
8. Качество кода
| Инструмент | 🏆 CloudCastle | symfony | carbon | lcobucci | beste | ergebnis |
|---|---|---|---|---|---|---|
| PHPStan | ✅ max + strict | ❌ | ✅ | ✅ + strict | ✅ + strict | ✅ + strict |
| Psalm | ✅ level 1 | ✅ (monorepo) | ❌ | ❌ | ✅ | ❌ |
| PHPMD | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| PHPCS / CS-Fixer | ✅ PSR-12 + fixer | ✅ fixer | ✅ | ✅ | ❌ | ✅ fixer |
| Rector | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ |
| Deptrac (слои архитектуры) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Мутационное тестирование (Infection) | ✅ MSI 100.0% | ❌ | ❌ | ✅ | ❌ | ✅ |
| Покрытие строк (живое) | ✅ 100.0% | — | — | — | — | — |
| Всего | 🏆 8 | 2 | 3 | 3 | 2 | 4 |
Инструменты аналогов — по require-dev их опубликованных дистрибутивов (снапшот июль 2026); symfony/clock — по монорепозиторию symfony/symfony. Метрики CloudCastle — живые результаты локального прогона.
¹ Базовый уровень (нативные вызовы/примитивы без полноты решения) показан для контекста и не претендует на победу среди библиотек-аналогов.
Результаты, отличающиеся от лучшего менее чем на 5%, отмечены как равные: у этих решений горячий путь совпадает посимвольно (new DateTimeImmutable('now', $tz)), и на таком масштабе разницу определяет шум машины, а не код.
Честная выжимка
Плюсы:
- функциональный суперсет всех сравниваемых PSR-20 библиотек вместе взятых,
включая календарную работу уровня Carbon (
CalendarDate); - детерминизм времени в тестах без глобального состояния (изолированные экземпляры часов) и со скоуп-подменой там, где глобальность удобна;
- наносекундная арифметика длительностей с контролем переполнения — тихих искажений не бывает;
- лёгкие зависимости: только
psr/clockиcloud-castle/inflector(корректные склонения без ICU/ext-intl) — на порядок легче связки Carbon +symfony/translation.
Минусы:
- моложе и менее распространён, чем
symfony/clockиnesbot/carbon: меньше ответов на StackOverflow; - нет готовой интеграции с Laravel-экосистемой (фасады, Eloquent-касты дат),
как у Carbon, — сознательное ограничение области: пакет фреймворк-агностичен
(сам календарный API
CalendarDate— от арифметики и предикатов до парсинга относительных строк иdiffForHumans— Carbon-паритетен); - локализация — шесть языков (ru/en/de/fr/es/it) против 200+ локалей Carbon: для экзотических языков Carbon пока богаче;
- собственная память библиотеки (≈31 KB на 200 000 вызовов
now()) выше, чем у минималистичных beste/lcobucci/ergebnis (3–5 KB): это цена расширенного контракта часов и календаря — осознанный компромисс в пользу функциональности, утечек при этом нет (0 KB роста после прогрева).
Рекомендации по применению:
- Бизнес-сервисы и финтех — внедряйте
ClockInterface, в тестахMockClock/FrozenClock: детерминированные проверки TTL, дедлайнов, retry. - Очереди, лимитеры, кэши —
MonotonicClockдля интервалов, устойчивых к переводу системных часов;Deadlineдля тайм-аутов. - Профилирование и метрики —
Stopwatchс кругами;timestamp()на горячих путях вместоnow()->getTimestamp(). - Симуляции и сценарные тесты —
StepClock/QueueClock/ScaledClock. - Календарь и локализация —
CalendarDateдля арифметики месяцев/лет без переполнения (addMonthsNoOverflow()), детерминированного парсинга относительных строк (parse('next friday')от часов приложения), форматтеров (toDateString(), ISO/RFC/ATOM), предикатов (isWeekend(),isToday()), разниц (diffInDays()) иdiffForHumans()на 6 языках без ICU/ext-intl;CalendarPeriod— для графиков платежей и отчётов по периодам. - Тесты легаси-кода без DI —
ClockSensitiveTraitвместо ручной очистки глобальных часов;Clock::adapt()— чтобы подключить старые часы проекта. - Не стоит брать только ради тесной интеграции с Laravel (фасады, Eloquent-касты дат) — это единственное, чем Carbon здесь богаче.
Разработка
composer install
composer check # линтеры + статический анализ + тесты
composer fix # автоисправления (Rector, PHP CS Fixer, PHPCBF)
composer ci # полный CI-пайплайн локально
Полный список команд с описаниями: composer run-script --list.
Документация
- Репозиторий: https://gitverse.ru/cloud-castle/clock
- Wiki (руководства и страницы возможностей): https://gitverse.ru/cloud-castle/clock/wiki
- История изменений: CHANGELOG.md
- Обновление между версиями: UPGRADING.md
- Как внести вклад: CONTRIBUTING.md
- Кодекс поведения: CODE_OF_CONDUCT.md
- Политика безопасности: SECURITY.md
Лицензия
MIT © CloudCastle (alex-4-17@yandex.ru)
🇷🇺 Русский · 🇬🇧 English · 🇩🇪 Deutsch · 🇫🇷 Français · 🇪🇸 Español · 🇮🇹 Italiano