On this page
What this gives you#
If nesbot/carbon is installed, RTLY-Kit adds a few macros to Carbon\Carbon and Carbon\CarbonImmutable. They move you between Carbon and the calendar classes in one call, in both directions. Carbon is optional. Without it nothing is registered and nothing breaks. Laravel apps already ship Carbon, so the macros are there without any extra step (see Laravel setup).
Before you start. Install RTLY-Kit (Installation) and Carbon 3 (composer require nesbot/carbon).
How registration works#
Composer loads the package's helper file on autoload. That file starts the integration. It checks whether the Carbon classes exist and, if they do, registers the macros once. You call nothing yourself:
<?php
require 'vendor/autoload.php';
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use RtlyKit\Calendar\HijriVariant;
use RtlyKit\Calendar\Jalali;
var_dump(Carbon::hasMacro('toJalali')); // bool(true)
Macro reference#
| Macro | Kind | Returns | Notes |
|---|---|---|---|
toJalali() | instance | Jalali | Same moment and time zone as the Carbon object |
jformat($format = 'Y/m/d H:i:s') | instance | string | Shortcut for toJalali()->format($format). Tokens as in Jalali formatting |
toHijri(?HijriVariant $variant = null) | instance | Hijri | Umm al-Qura unless you give a variant |
toHebrew() | instance | Hebrew | |
createFromJalali($y, $m, $d, $h = 0, $i = 0, $s = 0, $tz = null) | static | the class you called it on | Carbon:: returns Carbon. CarbonImmutable:: returns CarbonImmutable |
createFromHijri($y, $m, $d, $h = 0, $i = 0, $s = 0, $tz = null, ?HijriVariant $variant = null) | static | the class you called it on | The variant defaults to Umm al-Qura |
createFromHebrew($y, $m, $d, $h = 0, $i = 0, $s = 0, $tz = null) | static | the class you called it on | Month numbers by position, see Hebrew months |
The $tz argument takes a DateTimeZone, a time zone name or null. With null the PHP default zone applies, not the zone of any existing Carbon object.
From Carbon to a calendar#
$c = Carbon::create(2026, 3, 21, 12, 0, 0, 'UTC');
echo $c->toJalali(); // 1405/01/01 12:00:00
echo get_class($c->toJalali()); // RtlyKit\Calendar\Jalali
echo $c->jformat('l j F Y'); // شنبه 1 فروردین 1405
echo $c->jformat(); // 1405/01/01 12:00:00
echo $c->toHijri()->format('j F Y', 'en'); // 2 Shawwal 1447
echo $c->toHebrew()->format('j F Y'); // 3 Nisan 5786
The result is a calendar object, not a Carbon object. Use the calendar API from there, for example Persian digits with $c->toJalali()->format('Y/m/d', true). You can keep working with it and go back at any time with toGregorian():
echo Carbon::now('UTC')->setDate(2026, 3, 21)->toJalali()->addMonths(1)->format('Y/m/d'); // 1405/02/01
Calling the calendar's make() with a Carbon instance does the same, because Carbon is a DateTimeInterface:
echo Jalali::make($c)->format('Y/m/d H:i'); // 1405/01/01 12:00
echo Jalali::make(Carbon::create(2026, 3, 21, 22, 0, 0, 'UTC'), new DateTimeZone('Asia/Tehran'))->format('Y/m/d H:i'); // 1405/01/02 01:30
From calendar parts to Carbon#
$a = Carbon::createFromJalali(1405, 1, 1, 0, 0, 0, 'UTC');
echo get_class($a), ' ', $a->toDateTimeString(); // Carbon\Carbon 2026-03-21 00:00:00
$b = CarbonImmutable::createFromJalali(1405, 1, 1, 8, 30, 0, 'Asia/Tehran');
echo get_class($b), ' ', $b->toIso8601String(); // Carbon\CarbonImmutable 2026-03-21T08:30:00+03:30
echo Carbon::createFromHijri(1446, 9, 1, 0, 0, 0, 'UTC')->toDateString(); // 2025-03-01
echo Carbon::createFromHebrew(5786, 1, 1, 0, 0, 0, 'UTC')->toDateString(); // 2025-09-23
To use the Tabular Hijri variant, pass it as the last argument: Carbon::createFromHijri(1446, 10, 1, 0, 0, 0, 'UTC', HijriVariant::Tabular).
Errors#
The macros check input through the calendar classes. Invalid or out-of-range input throws RtlyKit\Exceptions\InvalidDateException, not a Carbon InvalidFormatException. An unknown time zone name does the same:
use RtlyKit\Exceptions\RtlyKitThrowable;
$cases = [
fn () => Carbon::createFromJalali(1404, 12, 30),
fn () => Carbon::createFromJalali(1404, 1, 1, 0, 0, 0, 'Nowhere/Land'),
fn () => Carbon::createFromHijri(9999, 1, 1),
fn () => Carbon::createFromHebrew(1, 1, 1),
];
foreach ($cases as $f) {
try { $f(); }
catch (RtlyKitThrowable $e) { echo get_class($e), ': ', $e->getMessage(), "\n"; }
}
// RtlyKit\Exceptions\InvalidDateException: Invalid Jalali date: 1404/12/30
// RtlyKit\Exceptions\InvalidDateException: Unknown timezone: Nowhere/Land
// RtlyKit\Exceptions\InvalidDateException: Invalid Hijri date: 9999/1/1
// RtlyKit\Exceptions\InvalidDateException: Invalid Hebrew date: 1/1/1
The supported ranges are those of the calendars: Jalali -620..9377, Hijri 1..9665, Hebrew 3762..13759.
Good to know#
- Macros are global per class. If another package defines a macro with the same name (
toJalali,toHijriand so on), the one registered last wins. Call the calendar classes directly (Jalali::make($carbon)) when you need to be sure. - Time zones travel with the object.
toJalali()uses the zone of the Carbon object, so set the zone before converting when the calendar day matters. - Weekday numbers differ between calendars. Do not feed
getDayOfWeek()of one calendar into another. See Convert and compare dates.