All guides
No guide matches your search.
Troubleshooting and FAQ
Answers to the questions people ask most. Missing helpers, weekday numbers, string years, time zones, Carbon macros, the Eloquent cast, prayer times and holidays.
On this page
We checked every answer below against the code and ran the examples. If your problem is not here, look at Error handling (what each exception code means) and Limits first.
Setup and helpers
I get "Call to undefined function jdate()". Where are the helpers?
The helpers are namespaced functions in RtlyKit. The package defines no global function by default, so it can never clash with your code or another package. Import the ones you use, call them by their full name, or switch on the short global names once at boot:
<?php
require __DIR__.'/vendor/autoload.php';
use function RtlyKit\jdate; // option 1: import
echo jdate('2026-03-21')->format('Y/m/d'), "\n"; // 1405/01/01
echo \RtlyKit\jdate('2026-03-21')->format('Y/m/d'), "\n"; // option 2: full name
$skipped = \RtlyKit\Globals::register(); // option 3: short global names
echo \jdate('2026-03-21')->format('Y/m/d'), "\n"; // 1405/01/01
var_dump($skipped); // array(0) {} (nothing was already taken)Globals::register() never replaces an existing function and never throws. It returns the names it skipped, so you can log them. In Blade, call \RtlyKit\jdate(...) or register the globals from AppServiceProvider::register(). The Laravel provider does not register them for you. See Helpers and globals.
Which exception should I catch?
Catch RtlyKit\Exceptions\RtlyKitThrowable (or RtlyKitException) for the whole library, or a subclass such as InvalidDateException. Validators do not throw at all, so they need no try. Decide by getErrorCode() and not by the message text. Details: Error handling.
Calendars
The weekday number is different for Jalali and Hijri
This is on purpose. Jalali::getDayOfWeek() (and the w format token) counts from Saturday, the first day of the Persian week. Saturday is 0 and Friday is 6. Hijri and Hebrew count from Sunday like PHP's date('w'), so Saturday is 6. Here is the same day, 2026-03-21 (a Saturday):
use function RtlyKit\{jdate, hdate, hebrew_date};
echo jdate('2026-03-21')->getDayOfWeek(), ' ',
hdate('2026-03-21')->getDayOfWeek(), ' ',
hebrew_date('2026-03-21')->getDayOfWeek(), "\n"; // 0 6 6If you want one numbering for all calendars, use toGregorian()->format('w').
jdate('1405-01-01') and jdate('2026-03-21') both work, but a Gregorian year like 999 does not
A string like Y/m/d or Y-m-d is read as a date of the calendar itself when the year is below 1700 (for Hebrew, when the year is 3000 or more). Otherwise it is read as Gregorian. So a real Gregorian date with a small year is misread. Pass a DateTimeImmutable instead. It is never reinterpreted:
use function RtlyKit\jdate;
echo jdate('2026-03-21')->format('Y/m/d'), "\n"; // 1405/01/01 (year >= 1700: Gregorian)
echo jdate('1405-01-01')->toGregorian()->format('Y-m-d'), "\n"; // 2026-03-21 (year < 1700: Jalali)
echo jdate('0999-01-01')->format('Y/m/d'), "\n"; // 0999/01/01 (Jalali year 999)
echo jdate(new DateTimeImmutable('0999-01-01'))->format('Y/m/d'), "\n"; // 0377/10/11 (Gregorian year 999)Persian and Arabic digits are converted first, so '۱۴۰۴/۰۱/۰۱' works. A blank string throws InvalidDateException, and null means "now".
format() prints English digits. How do I get Persian digits?
Formatting returns Latin digits on purpose, so the result is safe for databases and URLs. Convert the output when you show it:
use function RtlyKit\{jdate, to_persian};
echo jdate('2026-03-21')->format('Y/m/d'), "\n"; // 1405/01/01
echo to_persian(jdate('2026-03-21')->format('Y/m/d')), "\n"; // ۱۴۰۵/۰۱/۰۱Hijri::format() can also write the digits itself through its $digits argument (latin, persian or arabic).
jdate('1404/12/30') throws, but 1403/12/30 works
Esfand has 30 days only in a leap year. 1403 is a leap year and 1404 is not. So 1404/12/30 does not exist and throws InvalidDateException with the code invalid_date. Month 13, or day 31 in a month of the second half of the year (such as 1404/07/31), behaves the same way.
"Today" is a day off for my users
jdate() with no argument uses the PHP default time zone (date_default_timezone_get(), often UTC on servers). Near midnight in Tehran that is the previous or next Jalali day. Pass a DateTimeZone, or set the default time zone in your application. The time zone argument converts an incoming moment:
use function RtlyKit\jdate;
$utc = new DateTimeImmutable('2026-03-20 22:00', new DateTimeZone('UTC'));
echo jdate($utc)->format('Y/m/d H:i'), "\n"; // 1404/12/29 22:00
echo jdate($utc, new DateTimeZone('Asia/Tehran'))->format('Y/m/d H:i'), "\n"; // 1405/01/01 01:30I get an InvalidDateException for a far-future year or a huge number
Every calendar has a fixed range (Jalali -620 to 9377, Hijri 1 to 9665, Hebrew 3762 to 13759, Gregorian 1 to 9999). Huge add*() amounts and timestamps are refused too. This is on purpose. You get an exception and never a wrapped-around date. See Limits.
The Hijri date is one day off from my local announcement
Hijri uses the Umm al-Qura calendar (the Saudi civil calendar), which is calculated in advance. Local moon-sighting authorities, including Iran's, can start a month one or two days later. The embedded table covers AH 1300 to 1500. Outside it, and for HijriVariant::Tabular, the date is arithmetic and can differ from observation by a day or two. See Hijri calendar and Accuracy and data.
Carbon and Laravel
Call to undefined method toJalali() on a Carbon object
The Carbon macros (toJalali, jformat, createFromJalali, toHijri, toHebrew, createFromHijri, createFromHebrew) are registered only when nesbot/carbon (version 3) is installed. Composer lists it as a suggestion, so install it yourself with composer require nesbot/carbon. The macros are registered when Composer's autoloader loads the helpers file of the package (and again by the Laravel provider on boot), for Carbon and CarbonImmutable. Laravel's Illuminate\Support\Carbon extends Carbon, so it works too:
use Carbon\Carbon;
echo Carbon::parse('2026-03-21')->toJalali()->format('Y/m/d'), "\n"; // 1405/01/01
echo Carbon::createFromJalali(1405, 1, 1)->toDateString(), "\n"; // 2026-03-21
var_dump(Carbon::hasMacro('toJalali')); // bool(true)If hasMacro() is false, Carbon is missing or is an unsupported major version. See Carbon macros.
The Laravel validation rules (national_code, sheba, ...) are not found
With package auto-discovery, the service provider is registered for you. If you turned discovery off for this package (dont-discover) or run an old cached config, register RtlyKit\Laravel\RtlyKitServiceProvider yourself and clear the caches. The rule names are national_code, sheba, bank_card, iran_mobile (alias mobile), postal_code and vehicle_plate. The messages come in Persian, English and Arabic and follow app()->getLocale(). Your own lang/{locale}/validation.php lines still win. See Laravel setup.
My Eloquent model throws InvalidDateException when I assign a date
JalaliCast accepts a Jalali, any DateTimeInterface, a Unix timestamp, a Gregorian string, or a Jalali string such as 1404/01/15 10:30 (Persian digits and / or - are fine). Anything else is an error. An impossible date, junk text and non-scalar values (such as an array) throw InvalidDateException on assignment. null and the empty string become null. Catch it in your form request or controller:
$post->published_at = '1404/01/15 10:30'; // stored as 2025-04-04 10:30:00
$post->published_at = '1404/13/45'; // throws InvalidDateException (invalid_date)
$post->published_at = 'hello'; // throws InvalidDateException (invalid_date)
$post->published_at = ['x']; // throws InvalidDateException (invalid_date)Keep the column a normal Gregorian datetime. The cast writes Y-m-d H:i:s and reads it back as an immutable Jalali. The cast does no time zone conversion. See Laravel validation and cast.
Validators and data
is_national_code(13542419) is false, but the string version is true
An int has already lost its leading zeros. 0013542419 became 13542419, eight digits. Validators turn integers into strings but cannot bring the zeros back. Keep identifiers as strings everywhere (form fields, JSON, database columns):
use function RtlyKit\is_national_code;
var_dump(is_national_code(13542419)); // bool(false)
var_dump(is_national_code('0013542419')); // bool(true)getBankName() returns null for a valid card or Sheba
The BIN, Sheba-code and operator tables contain only entries confirmed by at least two sources. They are small on purpose (39 BINs, 38 Sheba codes). null means "unknown" and not "invalid". The validity of a card comes from the Luhn checksum and that of a Sheba from mod-97, independent of the tables. The same holds for NationalCode::getLocation() (547 prefixes), which gives the place where the card was issued and not the birthplace. Do not reject a user because a name is null. See Accuracy and data.
Why does a validator never throw, even for null or an array?
Validators take mixed and answer with false or an invalid Result: invalid_type for non-scalar values and input_too_long above 4096 bytes. So you can pass raw request input directly. See Validators overview.
is_mobile() is true, but allocated is false
is_mobile() checks the shape of the number. Mobile::isAllocated() and the allocated detail also ask whether the prefix is in a block that the national numbering plan lists for mobile service. A valid number can have allocated = false when its prefix is newer than the plan in our data. It does not make the number invalid. See Mobile numbers.
Numbers and text
format_number() cuts a float to 15 digits
A PHP float cannot hold most decimals exactly. So the formatter writes a float as its shortest decimal that reads back the same, cut to 15 significant digits. 1234567.891 prints cleanly, 1 / 3 prints 15 threes, and 123456789.123456789 as a float loses its last digits. Pass decimals as strings when you need more digits:
use function RtlyKit\format_number;
echo format_number(1234567.891), "\n"; // ۱٬۲۳۴٬۵۶۷٫۸۹۱
echo format_number(123456789.123456789), "\n"; // ۱۲۳٬۴۵۶٬۷۸۹٫۱۲۳۴۵۷ (float, 15 digits)
echo format_number('123456789.123456789'), "\n"; // ۱۲۳٬۴۵۶٬۷۸۹٫۱۲۳۴۵۶۷۸۹ (string, exact)number_to_words() throws for 1.5, for huge numbers and for other languages
Words are defined for whole numbers only. 1.5, NaN and INF throw InvalidNumberException. A whole float such as 3.0 is accepted. Persian goes up to 21 digits and Arabic up to 27 digits. Only fa and ar exist. Any other locale throws UnsupportedLocaleException. For gender, case and vowel marks in Arabic, see Arabic number words. Limits: Limits.
Prayer times and holidays
The prayer times are in the wrong time zone or shifted by an hour
Times are HH:MM in the local time of the time zone of the calculator, for the local calendar day of the date you pass. PrayerTimes::forCity() uses the zone of the city. A calculator built with new PrayerTimes() and no zone uses the PHP default (date_default_timezone_get(), which is UTC in a fresh container), so pass a DateTimeZone. Daylight saving is applied for each event, so a table across a clock change is right on both sides (Cairo's Dhuhr on 2026-10-08 and 2026-10-31):
use RtlyKit\Prayer\PrayerTimes;
$cairo = PrayerTimes::forCity('cairo', PrayerTimes::METHOD_EGYPT);
echo $cairo->getTimes(new DateTimeImmutable('2026-10-08'))['dhuhr'], ' ', // 12:43 (summer time)
$cairo->getTimes(new DateTimeImmutable('2026-10-31'))['dhuhr'], "\n"; // 11:39 (standard time)A date you pass in another zone is first converted to the zone of the calculator. 2026-03-20 23:30 UTC and 2026-03-21 03:00 Asia/Tehran both give the times of 21 March in Tehran.
Some prayer times are null
Far north or south, in some seasons there is no sunrise or sunset at all (polar day or night). Then sunrise and maghrib are null, and asr is null in polar night. This is a result and not an error, so handle it in your UI. Fajr and Isha are filled in by the default high-latitude rule. With withHighLatitudeRule(HighLatitudeRule::None), they can be null too:
use RtlyKit\Prayer\PrayerTimes;
$oslo = new PrayerTimes(69.65, 18.96, PrayerTimes::METHOD_MWL, PrayerTimes::ASR_STANDARD, new DateTimeZone('Europe/Oslo'));
echo json_encode($oslo->getTimes(new DateTimeImmutable('2026-06-21'))), "\n";
// {"fajr":"03:10","sunrise":null,"dhuhr":"12:46","asr":"17:58","maghrib":null,"isha":"22:10"}nextPrayer() returned a time that is tomorrow
After Isha, nextPrayer() moves on to tomorrow's Fajr. The result has a date key (Y-m-d) that tells you which day the prayer falls on:
use RtlyKit\Prayer\PrayerTimes;
$next = PrayerTimes::forCity('tehran')->nextPrayer(new DateTimeImmutable('2026-03-21 23:00', new DateTimeZone('Asia/Tehran')));
echo json_encode($next), "\n"; // {"name":"fajr","time":"04:42","date":"2026-03-22"}My times differ by a few minutes from my mosque or another app
Authorities use different twilight angles, safety margins and rounding. The calculation matches the published tables we compared with within about a minute (see What we checked). Choose the method your community follows. Set the Asr factor (ASR_HANAFI makes Asr later) and, for high places, the elevation argument. If your authority adds a few minutes, use withTune(). See Prayer times.
A religious holiday is a day off from the official calendar
For the Jalali years 1380 to 1405 the library uses the published official dates. For other years it estimates the Islamic holidays from the Hijri calendar, while Iran announces them by moon sighting, so an estimate can be off by one or two days. Fixed Jalali holidays (such as Nowruz and 22 Bahman) are exact. You can correct estimated years with an offset, a real month start or your own days: see Holiday calendar. IranHolidays::sourceOf($year) tells you which source a year uses.
Was this page helpful?