On this page
Before 1.0.0, a minor release may contain breaking changes. Each one is listed here with the steps to migrate. The policy is on API stability. This page starts with the newest version. The last section covers the early pre-release builds, from before 0.1.0.
From 0.1.x to 0.2.0#
Most applications need no change. Check the items below. Items 9 to 12 change what the library returns for some inputs.
ext-mbstringis no longer required.composer.jsonused to ask for the extension. Now it needs only PHP. There is nothing to do. If you addedext-mbstringto your owncomposer.jsononly for this package, you can remove it.- Float formatting.
Format::withSeparator()andformat_number()now print a float as the shortest decimal that reads back as the same float, cut to 15 significant digits. Binary noise is gone. Strings are unchanged and stay exact. Pass a string when you need more digits.
| Call | Before | After |
|---|---|---|
withSeparator(-1234567.891) | -۱٬۲۳۴٬۵۶۷٫۸۹۱۰۰۰۰۰۰۰۶۱۴۶۷ | -۱٬۲۳۴٬۵۶۷٫۸۹۱ |
withSeparator(0.1 + 0.2) | ۰٫۳ | ۰٫۳ (unchanged) |
withSeparator(-0.0) | ۰ | ۰ |
If you format the result of float arithmetic, round first (round($x, 2)) or pass a string.
NumberToWords::fromWords()is strict. It used to add the words up in any order.
| Input | Before | After |
|---|---|---|
دو صد | 102 | InvalidNumberException (invalid_number_words) |
بیست یک | 21 | InvalidNumberException |
صد و بیست و | 120 | InvalidNumberException |
پنج و بیست | 25 | InvalidNumberException |
سی و پنج | 35 | 35 |
Write و between parts and keep the order hundreds, tens, units. Everything convert() returns still parses.
Slugify::make()throwsRtlyKitException(invalid_argument,argument=text) for text that is not valid UTF-8. Before, only the separator was checked. CatchRtlyKitThrowableor clean the input first.Hijri::format()andHebrew::format()reject a pattern longer than 256 bytes withInvalidDateException(input_too_long,argument=format,limit= 256).Jalali::format()already did. The limit is inHijri::MAX_FORMAT_LENGTHandHebrew::MAX_FORMAT_LENGTH.- A trailing backslash in a format pattern is dropped in all three calendars.
format('Y\')returns just the year. IranHolidays::all(),allTitles()andallFixed()work for every Jalali year from -620 to 9377. Before, years before the Hijri epoch and the last day of 9377 threwinvalid_date. Where Islamic holidays cannot be derived, only the fixed holidays are returned. For a year outside the range they throwInvalidDateException(date_out_of_range, contextyear,min,max).- Error messages that repeat your input cut it to 40 characters and stay valid UTF-8. If you match on message text, match on
getErrorCode()instead. - Official holiday dates for 1380 to 1405. In these Jalali years the static methods (
IranHolidays,is_iran_holiday()) now return the published dates instead of the Umm al-Qura estimate. Other years are unchanged.
| Day | Before (estimate) | After (official) |
|---|---|---|
| 1404/01/10 | عید فطر | no holiday |
| 1404/01/11 | تعطیل عید فطر | عید فطر |
| 1404/01/01 | جشن نوروز + شهادت امام علی | جشن نوروز |
| 1404/12/29 | ملی شدن صنعت نفت + عید فطر | ملی شدن صنعت نفت |
| 1405/01/01 | جشن نوروز + تعطیل عید فطر | جشن نوروز + عید فطر |
In official years only the titles in the official table appear. Years 1380 to 1393 and 1395 are reported: the dates come from one source, and the list of days may be incomplete. To get the old behaviour for all years, use HolidayCalendar::default()->withOfficialData(false). To see where a year comes from, use IranHolidays::sourceOf($year). More in Holiday calendar.
- High-latitude prayer times.
PrayerTimeshas a newHighLatitudeRule. The default isAngleBased. It acts only where a Fajr or Isha time would be missing, or farther from sunrise or sunset than the rule allows. Below about 44 degrees north nothing changes. North of about 44 to 46 degrees, around the June solstice, some values differ. A time that wasnullnow has a value.
| Place and day (MWL) | Before (same as None) | After (default) |
|---|---|---|
| Stockholm, 2026-06-21 | Fajr null, Isha null | Fajr 01:54, Isha 23:40 |
| Munich, 2026-06-21 | Fajr 01:50, Isha 00:12 | Fajr 02:51, Isha 23:32 |
To get the old output, call $prayer->withHighLatitudeRule(HighLatitudeRule::None). nextPrayer() now also finds a prayer that falls after midnight. See High latitudes.
- Mobile validation. The shape check is the same, so no input changes validity. Two things are new. Every result has an
allocateddetail andMobile::isAllocated()returns it as a bool. The operator table follows the numbering plan more closely: the Shatel Mobile key998is narrowed to09981and09982, the Aptel key is widened from99910to9991, and0923,0931,0932and0934are added.
| Number | Operator before | Operator after |
|---|---|---|
09981234567 | شاتل موبایل | شاتل موبایل |
09983112345 | شاتل موبایل | null (allocated, holder not confirmed) |
09231234567 | null | رایتل |
09321234567 | null | تالیا |
If you compare the whole details() array, expect the extra key allocated.
- Arabic number words.
convert($n, 'ar')without options gives the same text as before for every number below 109. It now also accepts numbers up to 1027, where it used to thrownumber_too_large. New options and ordinals are on the Arabic number words page. - Platforms. PHP 8.2 to 8.5, Laravel 11, 12 and 13, and Carbon 3 are supported. See API stability.
From the early pre-release builds (before 0.1.0): namespaced helpers#
1. Global helper functions are no longer defined by default#
jdate(), to_persian(), is_national_code() and the other helpers as global functions as soon as Composer loaded the package. That could clash with your code or another package, so it was removed. The helpers now live in the RtlyKit namespace and nothing is defined globally.The 27 helpers: jdate, hdate, hebrew_date, to_persian_digits, to_english_digits, to_persian, to_english, is_national_code, is_sheba, is_bank_card, is_mobile, is_postal_code, is_vehicle_plate, validate_national_code, validate_sheba, validate_bank_card, validate_mobile, validate_postal_code, validate_vehicle_plate, number_to_words, normalize_text, contains_rtl, text_direction, is_iran_holiday, prayer_times, format_number and ordinal.
Option A: import what you use (recommended).
use function RtlyKit\jdate;
use function RtlyKit\is_national_code;
echo jdate('2026-03-21')->format('Y/m/d'); // 1405/01/01
Or call the fully qualified name: \RtlyKit\jdate('2026-03-21').
Option B: bring back the old short global names. Call Globals::register() once, for example in your bootstrap file:
\RtlyKit\Globals::register();
echo jdate('2026-03-21')->format('Y/m/d'); // 1405/01/01, works as before
register() defines only the names that are still free. It never replaces an existing function and never throws. It returns the names it skipped because something else already uses them. You can call it more than once:
$skipped = \RtlyKit\Globals::register();
if ($skipped !== []) {
error_log('RTLY-Kit globals skipped: '.implode(', ', $skipped)); // use \RtlyKit\name() for those
}
The Laravel service provider does not register globals either. In Blade, write {{ \RtlyKit\jdate($post->created_at)->format('j F Y') }}, or call Globals::register() from your AppServiceProvider::register(). More on Helpers and globals.
2. Other changes to be aware of#
| Change | What to do |
|---|---|
Validators take any value. All six implement RtlyKit\Contracts\Validator (validate(mixed): Result, isValid(mixed): bool). Non-string input (null, arrays, objects) returns an invalid Result with error invalid_type. Input over 4096 bytes returns input_too_long. They never throw for bad input. | Remove defensive try blocks and type checks around validators. See Validators overview. |
Exceptions carry codes. getErrorCode() (an ErrorCode enum) and getContext() exist on every library exception. | Nothing to change. Match on the code and not on the message text. See Error handling. |
Slugify::make() throws RtlyKitException (input_too_long or invalid_argument) for an invalid UTF-8 separator or one longer than 64 bytes. | This matters only if you pass a separator that users control. Catch RtlyKitThrowable. |
Calendars share a contract. Jalali, Hijri and Hebrew implement RtlyKit\Contracts\CalendarDate. Comparisons and diffIn*() accept a CalendarDate or any DateTimeInterface, make() accepts a CalendarDate, and equals(), isBefore(), isAfter() are new aliases. | Existing calls keep working. See Convert and compare. |
Reference data moved to resources/data/*.php. | If you read the old table classes directly (they were never public API), switch to public classes such as Hijri, Sheba::getBankName() or NationalCode::getLocation(). |
Makkah Isha in Ramadan changed. PrayerTimes::METHOD_MAKKAH now returns Isha as Maghrib + 120 minutes during Ramadan (Umm al-Qura practice) and + 90 minutes otherwise. The previous build always used 90. | Ramadan Isha moves 30 minutes later. This is a bug fix. Update any stored expected values. See the example below. |
A check of the Makkah behaviour (2026-03-01 is in Ramadan, 2026-03-21 is not):
use RtlyKit\Prayer\PrayerTimes;
$mecca = PrayerTimes::forCity('mecca', PrayerTimes::METHOD_MAKKAH);
foreach (['2026-03-01', '2026-03-21'] as $d) {
$t = $mecca->getTimes(new DateTimeImmutable($d));
echo $d, ' maghrib ', $t['maghrib'], ' isha ', $t['isha'], "\n";
}
// 2026-03-01 maghrib 18:25 isha 20:25
// 2026-03-21 maghrib 18:32 isha 20:02
Changelog highlights for the 0.x series#
These are the points of the changelog that users see. The repository's CHANGELOG.md has the full list.
Behaviour that changed#
- Out-of-range calendar input throws. Years, timestamps and huge
add*()andsub*()amounts outside the supported ranges raiseInvalidDateExceptionand not aTypeErroror a wrapped-around date. Ranges: Jalali -620 to 9377, Hijri 1 to 9665, Hebrew 3762 to 13759 (Limits). make()string rules are fixed. A year below 1700 (Hebrew: 3000 or more) is read in the calendar of the class. Everything else is read as Gregorian. Blank strings throw, andnullis now. A Gregorian ISO string with a year below 1700 is read as Jalali or Hijri, so pass aDateTimeImmutablefor such dates (FAQ).- Holidays. Official dates for Jalali 1380 to 1405 (see above). For other years, Islamic holidays are reported only for AH 1300 to 1500, and a year outside the Jalali range throws
InvalidDateException. - Number input.
NumberToWords::convert()andFormat::ordinal()accept whole floats (3.0) and throwInvalidNumberExceptionfor fractional floats,NaNandINF. - Input caps (security): strings over 4096 bytes are rejected by
NumberToWordsandFormat, andFormat::withSeparator()rejects numbers over 1000 characters. They throwInvalidNumberException(input_too_long). - Data tables keep confirmed entries.
NationalCode::getLocation()covers 547 prefixes and returnsnullfor the rest. The bank BIN (39), Sheba code (38) and operator tables keep only entries confirmed by two sources. A name that was returned before may now benull.BankCardrejects numbers made of one repeated digit. - Prayer-time core rewritten from standard astronomical equations. The difference from the earlier implementation was at most 2 minutes on evening events (mean 0.5 minute or less) across the comparison grid. Daylight saving is applied per event, and the
$elevationconstructor argument is new.
Added#
- A pure-PHP Hebrew calendar (
Hebrew) with noext-calendarneeded. The Hijri calendar was rebuilt around the Umm al-Qura table for AH 1300 to 1500, with aHijriVariant::Tabularfallback. - Structured validation:
Result(isValid(),errors(),details()) andvalidate_*()helpers with stable error codes. NumberToWords::fromWords(), numbers up to 21 digits, negatives, and Arabic number words (number_to_words($n, 'ar')).Normalizer::fixHalfSpace()andclean(),Detector::isRtlLocale()andisHebrew(),IranHolidays::getTitles(),isBusinessDay()andnextBusinessDay(), andPrayerTimes::nextPrayer().- Laravel: the
Jalalifacade and the factory in the container, auto-discovery, six validation rules with fa, en and ar messages, andJalaliCast. - Carbon macros
toJalali,jformat,createFromJalali,toHijri,toHebrew,createFromHijri,createFromHebrew. - The exception hierarchy under
RtlyKitThrowable, theErrorCodeenum,Globals::register(), and theCalendarDateandValidatorcontracts.
Fixed#
- Makkah-method Isha in Ramadan (described above).
Format::ordinal()for 30 and 23.Format::withSeparator()accepts Persian and Arabic digits without rounding.Mobileaccepts the0098,98and bare9xxxxxxxxxforms.Shebaaccepts the bare 24-digit form.- Prayer-time handling of time zones around daylight saving, and errors for an unknown method or Asr factor.
- The duplicate
hdate()helper declaration.
~0.2.0), read this page before each minor upgrade, and run your own tests against any date and prayer-time outputs that you store.