🇷🇺 Русский · 🇬🇧 English · 🇩🇪 Deutsch · 🇫🇷 Français · 🇪🇸 Español · 🇮🇹 Italiano
CalendarDate — Carbon-grade calendar arithmetic
Purpose
A calendar date over DateTimeImmutable: month/year arithmetic with overflow control (addMonthsNoOverflow), period boundaries, isoFormat and diffForHumans in 6 languages — without ICU/ext-intl. Declensions come from cloud-castle/inflector.
What for and how to use
A Carbon replacement where you only need correct calendar arithmetic and localized output: CalendarDate::parse()/now(), then addMonthsNoOverflow(), startOfQuarter(), diffForHumans(null, 'ru').
Example
$date = CalendarDate::parse('2026-01-31', 'UTC');
$date->addMonthsNoOverflow(1)->format('Y-m-d'); // 2026-02-28, not “3 March”
$date->isWeekend(); // is it a weekend day
$date->diffInDays($other); // integer difference
$date->next(5)->format('Y-m-d'); // the nearest Friday
$date->subDays(5)->diffForHumans($date, 'en'); // “5 days ago”
Anti-patterns
Do not pick it solely for tight Laravel integration (facades, Eloquent date casts) — that is the only area where Carbon is richer; the calendar API itself is at Carbon parity.
Lifecycle
flowchart LR
A[parse / now / fromClock] --> B[CalendarDate]
B -->|addMonthsNoOverflow / startOfQuarter| B
B -->|isWeekend / diffInDays / next| B
B -->|isoFormat / diffForHumans| C[CloudCastle inflector]
C --> D[Localised string]
Feature comparison with competitors
| Aspect | 🏆 CloudCastle | symfony | carbon | lcobucci | beste | ergebnis |
|---|---|---|---|---|---|---|
| Month arithmetic without overflow | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| Period boundaries (startOf/endOf) | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| Weekday and relative predicates (isWeekend/isToday) | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| Integer diffs (diffInDays…diffInYears) | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| Navigation (next/previous/firstOfMonth) | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| diffForHumans in 6 languages | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| Localization without ICU/ext-intl | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Date from injected clock (determinism) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Total | 🏆 8 | 0 | 6 | 0 | 0 | 0 |
🇷🇺 Русский · 🇬🇧 English · 🇩🇪 Deutsch · 🇫🇷 Français · 🇪🇸 Español · 🇮🇹 Italiano