Search by

PSR-20 набор часов для PHP 8.1+: системные, замороженные, мок-, монотонные, со смещением, масштабом и усечением, шаговые и очередь; TimeSpan с наносекундной точностью, Stopwatch, Deadline, DatePoint и CalendarDate — календарная арифметика месяцев/лет с контролем переполнения и локализацией уровня Ca

v1.4.0 2026-09-28 19:35 UTC

README

🇷🇺 Русский · 🇬🇧 English · 🇩🇪 Deutsch · 🇫🇷 Français · 🇪🇸 Español · 🇮🇹 Italiano

CloudCastle Clock

CloudCastle Clock

Packagist Version PHP Version License Downloads Monthly Downloads Packagist Stars Daily Downloads Security advisories

GitVerse Issues Releases CI

PHPStan Psalm PHPMD PHPCS Coverage Infection MSI OpenSSF Scorecard

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. Функциональность

Возможность🏆 CloudCastlesymfonycarbonlcobuccibesteergebnisnew¹
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 дат без наследования✅❌✅❌❌❌❌
Всего🏆 248102420

2. Безопасность и корректность

Свойство🏆 CloudCastlesymfonycarbonlcobuccibesteergebnisnew¹
Иммутабельные показания (DateTimeImmutable)✅✅✅✅✅✅✅
Явная временная зона (не зависит от date.timezone)✅✅✅✅❌✅❌
Детерминизм тестов без обязательного глобального состояния✅✅❌✅✅✅❌
Монотонность (устойчивость к откату системных часов)✅✅❌❌❌❌❌
Типизированные исключения вместо false/warning на границе ввода✅❌✅❌❌❌❌
Санитайзинг враждебного ввода в сообщениях исключений✅❌❌❌❌❌❌
Контроль целочисленного переполнения длительностей✅❌❌❌❌❌❌
Минимальные зависимости (≤2, без ICU/ext-intl)✅❌❌✅✅✅✅
Всего🏆 8434342

2.1. Соответствие стандартам безопасности composer-пакетов

СтандартCloudCastlesymfonycarbonlcobuccibesteergebnis🏆 Победители
Политика безопасности (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
Всего🏆 833334CloudCastle

Источники: файлы 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базовый уровень (не библиотека)
🏆 CloudCastle114,5быстрейшее среди библиотек
🏆 ergebnis115,6наравне с лидером (в пределах погрешности)
🏆 lcobucci116наравне с лидером (в пределах погрешности)
🏆 beste116,8наравне с лидером (в пределах погрешности)
symfony154,6аналог
carbon649,8аналог

4. Производительность: unix-метка

Текущая unix-метка (целые секунды), 200 000 раз.

РешениеВремя (мс)Итог
new¹16базовый уровень (не библиотека)
🏆 CloudCastle27,8быстрейшее среди библиотек
ergebnis122,3аналог
lcobucci123аналог
beste125,6аналог
symfony164,2аналог
carbon658,7аналог

5. Потребление памяти библиотекой

Прирост памяти самой библиотеки на 200 000 вызовов now() (изолированный процесс, без веса автолоадера).

РешениеПамять (KB)Итог
new¹1базовый уровень (не библиотека)
🏆 beste3легчайшее среди библиотек
lcobucci5аналог
ergebnis5аналог
symfony30аналог
CloudCastle31аналог
carbon3 425аналог

6. Утечки памяти

Рост памяти после прогрева за 200 000 вызовов now() (0 — утечек нет).

РешениеПамять (KB)Итог
🏆 CloudCastle0без утечек
symfony0без утечек
carbon0без утечек
lcobucci0без утечек
beste0без утечек
ergebnis0без утечек
new¹0базовый уровень (не библиотека)

7. Пик памяти процесса

Пик памяти изолированного процесса на 200 000 вызовов now().

РешениеПамять (KB)Итог
🏆 CloudCastle7 035легчайшее среди библиотек
🏆 symfony7 035наравне с лидером (в пределах погрешности)
🏆 lcobucci7 035наравне с лидером (в пределах погрешности)
🏆 beste7 035наравне с лидером (в пределах погрешности)
🏆 ergebnis7 035наравне с лидером (в пределах погрешности)
new¹7 035базовый уровень (не библиотека)
carbon10 303аналог

8. Качество кода

Инструмент🏆 CloudCastlesymfonycarbonlcobuccibesteergebnis
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%—————
Всего🏆 823324

Инструменты аналогов — по 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.

Документация

Лицензия

MIT © CloudCastle (alex-4-17@yandex.ru)

🇷🇺 Русский · 🇬🇧 English · 🇩🇪 Deutsch · 🇫🇷 Français · 🇪🇸 Español · 🇮🇹 Italiano