đ Originally published on unfoldcms.com â reposted here for the DEV community. (I work on UnfoldCMS.)
Most date libraries make you choose. You either get a great API with one calendar, or you bolt on a Persian-calendar wrapper and lose half of what Carbon gives you. If your app serves users in Iran, Afghanistan, or the Gulf, that trade-off shows up the day someone asks for a Jalali date next to a Gregorian one.
TL;DR: Carbon is the standard for dates in PHP, but it only speaks Gregorian. MultiCarbon is a small open-source package that extends Carbon directly to add Jalali (Solar Hijri) and Hijri (Islamic Lunar) calendars â so format(), addMonths(), diffForHumans(), and Laravel integration all keep working, just in the calendar you want. This post shows how to use it in a real Laravel app, with code you can paste in today.
It's MIT-licensed and built as part of UnfoldCMS, our self-hosted CMS. Full disclosure: I work on UnfoldCMS, and we open-sourced MultiCarbon because we needed it ourselves.
The Problem: Carbon Only Knows One Calendar
Carbon is excellent. It handles timezones, diffing, immutability, serialization, and ships deep into Laravel â $user->created_at is a Carbon instance out of the box. The catch is that Carbon is Gregorian-only.
If you need a Persian (Jalali) date, you've historically had two bad options:
- A standalone Jalali library â works, but it's a separate object type. You lose Carbon's timezone handling, its diffing, its Laravel casts. Now you're converting back and forth all over your code.
- A thin wrapper â closer, but most wrappers re-implement a fraction of Carbon's surface. The moment you call a method they forgot, it breaks.
Both paths end the same way: you maintain two mental models for dates in one app. That's where bugs live.
What MultiCarbon Does Differently
MultiCarbon extends Carbon\Carbon instead of wrapping it. That one design choice changes everything. Because a MultiCarbon instance is a Carbon instance, every Carbon method works â you just gain the ability to switch the active calendar on any instance.
use MultiCarbon\MultiCarbon;
// Create a date in the Jalali calendar
$date = MultiCarbon::createJalali(1404, 1, 1); // Nowruz 1404
echo $date->format('Y/m/d'); // "1404/01/01"
echo $date->format('l j F Y'); // "ŘŹŮ
ؚ٠1 ŮŘąŮعدŰŮ 1404"
// Same instance, different calendar â the timestamp never moves
echo $date->hijri()->format('Y/m/d'); // "1446/08/21"
echo $date->gregorian()->format('Y/m/d'); // "2025/03/21"
The underlying Unix timestamp stays fixed. ->jalali(), ->hijri(), and ->gregorian() only change how the date is read and displayed â not the moment in time it points to. That's the whole trick, and it's why nothing else breaks.
It's Still Just Carbon
Here's the part that matters for a real codebase. Every Carbon method you already use keeps working, even in Jalali or Hijri mode:
$date = MultiCarbon::createJalali(1404, 6, 15, 14, 30, 0);
$date->addDays(10); // Carbon native
$date->addMonths(2); // calendar-aware (2 Jalali months)
$date->isPast(); // true / false
$date->isWeekend(); // Friday detection (Iranian week)
$date->diffForHumans(); // "Űł Ů
ا٠ٞŰŘ´" â localized Persian
$date->getTimestamp(); // correct Unix timestamp
$date->timezone('Asia/Tehran'); // full timezone support
$date->copy()->startOfMonth(); // 1404/06/01 00:00:00
// Hand it to anything expecting Carbon â it just works
function logDate(\Carbon\Carbon $d) { /* ... */ }
logDate($date); // MultiCarbon IS Carbon
No converting types. No second mental model. You write the same Carbon you already know.
How to Add Multi-Calendar Dates to a Laravel App
Here's a practical walkthrough â install, display Jalali dates in Blade, and store dates the safe way.
Step 1: Install
composer require hpakdaman/multicarbon
It needs PHP 8.1+ and Carbon 2.x or 3.x. In Laravel, the service provider and facade auto-discover â there's nothing to register.
Step 2: Show a Jalali Date in Blade
The package ships Blade directives, so converting a stored Gregorian date to Jalali in a view is one line:
{{-- Convert any stored datetime to Jalali --}}
@jalali($post->published_at, 'Y/m/d') {{-- 1404/06/15 --}}
{{-- Current date, Hijri --}}
@hdate('Y/m/d') {{-- 1446/08/15 --}}
{{-- Current Jalali date with time --}}
@jdate('Y/m/d H:i:s')
No controller changes. No casting gymnastics. The directive takes whatever Carbon-compatible value you pass and renders it in the target calendar.
Step 3: Use the Global Helpers Anywhere
Outside Blade â in jobs, commands, API resources â there are plain helpers:
jdate('Y/m/d'); // "1404/06/15" â current Jalali date
jdate('Y/m/d', $timestamp); // from a Unix timestamp
hdate('Y/m/d'); // "1446/08/15" â current Hijri date
multicarbon(); // a MultiCarbon instance to chain off
Step 4: Store Gregorian, Display Jalali
This is the one rule that saves you pain: store dates in your database as Gregorian (UTC), and convert only at display time. Your database stays sane, your queries stay normal, and the calendar becomes purely a presentation concern.
// Storing â let Laravel store the Gregorian datetime as usual
$post->published_at = now();
$post->save();
// Displaying â convert at the edge
echo MultiCarbon::fromCarbon($post->published_at)->jalali()->format('Y/m/d');
Because MultiCarbon extends Carbon, fromCarbon() accepts the value Laravel already gave you. No migration, no schema change.
What You Actually Get
A quick map of the feature surface so you know what's in the box:
| Capability | How |
|---|---|
| Three calendars |
->jalali(), ->hijri(), ->gregorian() on any instance |
| Calendar-aware math |
addMonths(), addYears(), startOfMonth(), endOfYear() respect calendar boundaries |
| Localized output | Persian + Arabic month names, weekday names, AM/PM, diffForHumans()
|
| Digit display | Latin, Farsi (۹۲۳۴), or Arabic-Indic (٥٢٣٤) digits |
| Iranian week | Saturday as first day, Friday as the weekend |
| Laravel integration | Auto-discovery, Facade, Blade directives (@jalali, @hijri, @jdate, @hdate) |
| Global helpers |
jdate(), hdate(), multicarbon()
|
| Dependencies | Zero beyond Carbon itself |
A useful detail for forms and imports: you can parse a calendar-specific string directly, instead of converting by hand first.
// Parse a Jalali date the user typed
$mc = MultiCarbon::parseFormat('Y/m/d', '1404/01/15');
// Parse a Hijri date
use MultiCarbon\CalendarMode;
$mc = MultiCarbon::parseFormat('Y/m/d', '1446/07/15', CalendarMode::HIJRI);
How Does It Keep Full Carbon Compatibility?
MultiCarbon intercepts property access (year, month, day) and a few key methods (format(), setDate(), addMonths()), then checks who is calling.
It uses debug_backtrace() to tell the difference between your code and Carbon's own internals:
-
Your code reads
$date->yearâ it returns the year in the active calendar (Jalali or Hijri). -
Carbon internally reads
$this->yearâ it returns Gregorian, so Carbon's own engine keeps working correctly.
The conversion only happens at the boundary between your code and Carbon. Everything underneath â diffing, timezone math, serialization â runs on plain Gregorian, untouched. That's why you don't lose any Carbon behavior: the library never gets in Carbon's way.
When to Reach for This
A few honest guidelines on where MultiCarbon fits:
- You serve Persian or Arabic-speaking users and need to show dates in their calendar. This is the core case.
- You're already on Carbon / Laravel and don't want a second date type polluting your code. MultiCarbon slots in without rewrites.
- You need calendar-aware math â "add 3 Jalali months" is not the same as "add ~90 days," and MultiCarbon handles the month-length differences for you.
When not to bother: if your app is Gregorian-only, you don't need this â plain Carbon is perfect. MultiCarbon earns its place specifically when more than one calendar is in play.
Why We Built It (and Open-Sourced It)
MultiCarbon came out of building UnfoldCMS, a self-hosted CMS where content gets published and scheduled across different locales. We needed Jalali and Hijri dates to behave exactly like Carbon dates everywhere â in the admin, in the API, in scheduled publishing â without maintaining two date systems.
Rather than keep it internal, we released it under MIT so anyone hitting the same wall can use it. If you're building a CMS or app on Laravel, or working with our headless REST API, the same calendar handling is available to you directly.
You can install it now: composer require hpakdaman/multicarbon. Source and docs are on GitHub.
Frequently Asked Questions
Does MultiCarbon replace Carbon?
No â it extends Carbon. A MultiCarbon instance is a Carbon instance, so you can pass it to any code that expects Carbon, and all of Carbon's methods work. You add it alongside Carbon, you don't swap Carbon out.
Does it work with Carbon 3?
Yes. MultiCarbon requires PHP 8.1+ and supports both Carbon 2.x and 3.x. Pick whichever your project is on.
Will it change how my dates are stored in the database?
No. The recommended pattern is to store dates as Gregorian (UTC) the way Laravel already does, and convert to Jalali or Hijri only when displaying. No migration or schema change is needed.
Does it support Hijri (Islamic) dates, or only Jalali?
Both. MultiCarbon handles Jalali (Solar Hijri), Hijri (Islamic Lunar), and Gregorian. Switch between them on any instance with ->jalali(), ->hijri(), and ->gregorian().
Can I show Persian (Farsi) digits instead of Latin numbers?
Yes. MultiCarbon can render dates with Latin (1234), Farsi (۹۲۳۴), or Arabic-Indic (٥٢٣٤) digits, and it localizes month names, weekday names, and "diff for humans" output.
MultiCarbon is open-source under the MIT license. It's built and maintained as part of UnfoldCMS, a self-hosted CMS for developers. Install with composer require hpakdaman/multicarbon â source on GitHub.
đŹ First published on my own site: https://unfoldcms.com/blog/multi-calendar-dates-laravel-jalali-hijri/
UnfoldCMS is a self-hosted, developer-first CMS. If any of this was useful â or you disagree â I'm in the comments.
Top comments (0)