On this page
What you will do#
You will install the package, print a Jalali date, run a validation and catch an exception. It all runs on plain PHP. You need no framework, no config file and no extra package.
Before you start. You need PHP 8.2 or newer and Composer. If you have neither, use the Docker route in Installation.
1. Install#
composer require enaxon/rtly-kit
Composer installs the package and creates vendor/autoload.php. RTLY-Kit has no required Composer packages. The Carbon macros and the Laravel pieces switch on by themselves when Carbon or Laravel is present. More in Installation.
2. Your first date#
Create a file quick.php next to vendor/:
<?php
declare(strict_types=1);
require __DIR__.'/vendor/autoload.php';
use function RtlyKit\hdate;
use function RtlyKit\jdate;
echo jdate('2026-03-21')->format('l j F Y'), "\n"; // شنبه 1 فروردین 1405
echo hdate('2026-03-21')->format('j F Y', 'en'), "\n"; // 2 Shawwal 1447
Run it with php quick.php. The first line prints the Jalali date of 21 March 2026, which is Nowruz 1405. The second line shows the same day in the Hijri calendar.
Two things to notice:
- The helper functions live in the
RtlyKitnamespace. You import them withuse function. They cannot clash with your own functions or with another package. - The string
'2026-03-21'is read as a Gregorian date. A string like'1405/01/01'(a year below 1700 inY/m/dform) is read as Jalali. See String semantics of make().
echo jdate('1405/01/01')->toGregorian()->format('Y-m-d'), "\n"; // 2026-03-21
The helper returns a Jalali object. hdate() returns a Hijri object and hebrew_date() a Hebrew one. These objects are immutable. addDays(), startOfMonth() and the other modifiers give you a new object.
3. Optional: short global names#
By default no global function is defined. If you like plain jdate() and is_national_code(), switch them on once, for example in your bootstrap file:
$skipped = \RtlyKit\Globals::register(); // array of names it could not define
echo jdate('2026-03-21')->format('Y/m/d'), "\n"; // 1405/01/01
register() defines the 27 helper names that are still free. It never replaces an existing function and never throws. It returns the names it skipped, or an empty array. You can call it more than once. Use the namespaced form for any skipped name. Details: Helpers and globals.
4. Your first validation#
use function RtlyKit\is_national_code;
use function RtlyKit\validate_national_code;
use function RtlyKit\to_persian_digits;
use function RtlyKit\number_to_words;
var_dump(is_national_code('0013542419')); // bool(true)
$result = validate_national_code('0013542410');
var_dump($result->isValid()); // bool(false)
print_r($result->errors()); // [0] => invalid_checksum
echo to_persian_digits('1405/01/01'), "\n"; // ۱۴۰۵/۰۱/۰۱
echo number_to_words(1405), "\n"; // یک هزار و چهارصد و پنج
The is_*() functions return a plain bool. The validate_*() functions return a Result with isValid(), stable errors() keys and details(). Validators do not throw for bad user input. They report it. See Validators overview and National code.
5. Your first exception#
Calendar code works differently. An impossible date is a programming or data error, so the calendar classes throw. 1404 is not a leap year, so 30 Esfand 1404 does not exist:
use RtlyKit\Exceptions\InvalidDateException;
use RtlyKit\Exceptions\RtlyKitThrowable;
try {
jdate('1404/12/30');
} catch (InvalidDateException $e) {
echo get_class($e), ': ', $e->getMessage(), ' [', $e->getErrorCode()->value, "]\n";
}
// RtlyKit\Exceptions\InvalidDateException: Invalid Jalali date: 1404/12/30 [invalid_date]
try {
jdate('not a date');
} catch (RtlyKitThrowable $e) {
echo 'library error: ', $e->getMessage(), "\n"; // library error: Unable to parse date: not a date
}
Every exception of the library implements RtlyKit\Exceptions\RtlyKitThrowable and extends \InvalidArgumentException. One catch handles them all. Use getErrorCode() to branch, not the message text. A year outside the supported range (Jalali -620 to 9377, Hijri 1 to 9665, Hebrew 3762 to 13759) always raises InvalidDateException, never a TypeError. More in Error handling.
Good to know#
Jalali follows the arithmetic 33-year rule. It matches the official Iranian calendar for every year from 1206 to 1497, and the astronomical definition for 1178 to 1502. The Hijri month table matches the official KACST calendar for AH 1318 to 1500 (checked 2026-10-08). Details are in Accuracy and data.- Time zones. Without a
DateTimeZone, the PHP default zone is used. Pass a zone such asnew DateTimeZone('Asia/Tehran')when the calendar day matters. - Weekdays. Jalali counts days from Saturday = 0. Hijri and Hebrew count from Sunday = 0.
Where to go next#
- Dates: Jalali, Hijri, Hebrew, Convert and compare.
- Validation: Validators overview.
- Holidays and prayer times: Holidays, Holiday calendar, Prayer times.
- Laravel projects: Laravel setup.
- Problems: Troubleshooting and FAQ.