DEV Community

Cover image for Jalali Dates in Modern PHP: Building ParsiDate — Immutable, Zero-Dependency, with Holidays Built In
Mahdyar
Mahdyar

Posted on

Jalali Dates in Modern PHP: Building ParsiDate — Immutable, Zero-Dependency, with Holidays Built In

If you build software for 85+ million Persian speakers, you need Jalali (Solar Hijri) dates. And if you do it in PHP, you've probably met the usual suspects: libraries built a decade ago, strftime() calls that PHP 8.1 deprecated, mutable date objects that cause spooky action-at-a-distance in queue workers, and holiday lists you have to maintain by hand.

I built ParsiDate to fix that for my own projects. It's now at v1.1.0, MIT licensed, with 136 passing tests:

composer require mahdyaralipor/parsidate
Enter fullscreen mode Exit fullscreen mode

Design decisions

1. Immutable by default. Every arithmetic method returns a new instance:

$date = ParsiDate::create(1403, 1, 1);
$later = $date->addDays(10);   // $date is untouched
Enter fullscreen mode Exit fullscreen mode

If you've ever debugged a Carbon object mutated three call frames away, you know why this matters.

2. Zero dependencies. composer.json requires exactly one thing: php: ^8.1. No Carbon, no framework, no polyfills. It works in Laravel, Symfony, WordPress, or a bare script.

3. Holidays are data, not your problem. Official Iranian public holidays (1400–1406, verified date-by-date against the official calendar) ship inside the package, with an API for variable holidays:

$date->isHoliday();        // true on Nowruz
$date->holidayName();      // "جشن نوروز"
$date->addWorkdays(5);     // skips Fridays + public holidays
$date->workdaysUntil($other);
Enter fullscreen mode Exit fullscreen mode

4. Periods are iterable. Date ranges are first-class:

$period = ParsiPeriod::ofMonth(1403, 1);

$period->countWorkdays();
$period->overlap($other);       // ParsiPeriod|null
$period->splitByDays(7);        // weekly chunks

foreach ($period->fridays() as $friday) {
    echo $friday->format('Y/m/d');
}
Enter fullscreen mode Exit fullscreen mode

5. Persian digits everywhere. Parsing and formatting both understand ۰۱۲۳۴۵۶۷۸۹, because real user input uses them:

ParsiDate::parse('۱۴۰۳/۰۶/۱۵');
$date->formatPersian('Y/m/d');  // ۱۴۰۳/۰۶/۱۵
Enter fullscreen mode Exit fullscreen mode

6. Hijri conversion included. $date->toHijri() returns a value object with Arabic + Latin month names and array/JSON output. One honest caveat, documented in the README: it uses the tabular (arithmetic) calendar, so it can differ ±1–2 days from moon-sighting calendars.

What's next

  • Recurring events / RRULE-style helpers
  • More calendar systems on request
  • Framework bridges (a Laravel service provider is the most requested)

Repo: https://github.com/mahdyaralipor/parsidate

If you work with Jalali dates in production, I'd love to hear what your current setup is and where it hurts — that's what shapes the roadmap. And if it's useful, a ⭐ helps others find it.

Top comments (0)