All guides
No guide matches your search.
Holiday calendar
Use official Iranian holiday dates, see where each date comes from, and fix moon-sighting differences with your own offsets, month starts and extra or removed days.
On this page
What it is
Iran sets its religious holidays by moon sighting. A table can only estimate them, and an estimate is sometimes a day or two off. RtlyKit\Holiday\HolidayCalendar solves this in two ways. It ships the published official dates for many Jalali years. And it lets you correct everything else with a few small settings.
A HolidayCalendar is immutable and has no global state. Each with*() method returns a new calendar. It has the same lookup methods as the static IranHolidays class (isHoliday(), getTitles(), all(), isBusinessDay() and more). IranHolidays uses the default calendar, which is HolidayCalendar::default().
Where the dates come from
Every Jalali year has one of three sources. Ask the calendar with sourceOf($year), which returns a HolidaySource.
| Source | Years | What it means |
|---|---|---|
Official | 1394 and 1396 to 1405 | The dates of the official yearly calendar of the Calendar Center at the University of Tehran, checked against a second source (time.ir) |
Reported | 1380 to 1393 and 1395 | Dates taken from a published list. For 1395 we used PDF digits and a news list of the official holidays. The set of holidays in these years may be incomplete: the Imam Reza day or the Imam Hasan Askari day is missing in some of them. We have no published source to fill them, so add a day you know with withHoliday() |
Estimated | every other year | Calculated from the Umm al-Qura table, and reported only inside AH 1300 to 1500 |
Fixed holidays, such as Nowruz or 22 Bahman, fall on the same Jalali date every year. They are exact and are not part of this question.
<?php
require 'vendor/autoload.php';
use RtlyKit\Holiday\HolidayCalendar;
use RtlyKit\Holiday\IranHolidays;
$cal = HolidayCalendar::default(); // IranHolidays::calendar() is the same calendar
echo $cal->sourceOf(1405)->value; // official
echo $cal->sourceOf(1395)->value; // reported
echo $cal->sourceOf(1406)->value; // estimated
echo implode(', ', IranHolidays::getTitles(1405, 1, 1)); // جشن نوروز, عید فطرIn the year 1405, Eid al-Fitr 1447 AH falls on 1405/01/01, the same day as Nowruz. That is the official date. The table estimate puts it on 1404/12/29.
Tutorial: correct a year step by step
The year 1406 has no official data yet, so it is estimated. We will fix it in four steps.
1. Shift all estimated dates with an offset
If you see that the estimate is one day early all year, move every estimated Islamic holiday with withIslamicOffset(). It accepts -3 to +3 days. It changes estimated years only. Official years and fixed holidays do not move.
$later = $cal->withIslamicOffset(1);
echo implode(', ', $cal->getTitles(1406, 3, 25)); // عاشورای حسینی
echo implode(', ', $later->getTitles(1406, 3, 26)); // عاشورای حسینی2. State the real start of a Hijri month
One flat offset cannot follow the moon month by month. When you know the real first day of a month, give it to withHijriMonthStart($hijriYear, $hijriMonth, $firstDay). The date is Gregorian: a '2027-06-08' string (Persian and Arabic digits are fine) or any DateTimeInterface. A string that contains a NUL byte throws InvalidDateException. Every Islamic holiday of that month is worked out from this day. This works in official years too, and it wins over official data.
// Muharram 1449 is computed to start on 2027-06-06. The moon is seen two days later:
$moved = $cal->withHijriMonthStart(1449, 1, '2027-06-08');
echo implode(', ', $moved->getTitles(1406, 3, 25)); // (nothing)
echo implode(', ', $moved->getTitles(1406, 3, 26)); // تاسوعای حسینی
echo implode(', ', $moved->getTitles(1406, 3, 27)); // عاشورای حسینیThe date must be within 3 days of the computed start. If you also gave a neighbouring month, the two starts must be 29 or 30 days apart. Otherwise the call throws InvalidDateException.
3. Add or remove a day
withHoliday($date, $title) adds a title to a day. withoutHoliday($date) removes the whole day, and withoutHoliday($date, $title) removes one title. A date is a Jalali object or a Jalali date string such as '1406/02/03' ( YYYY/MM/DD or YYYY-MM-DD , 3 or 4 digit year, Persian digits allowed). A string whose year is 1700 or more is not a Jalali date and throws InvalidDateException , so a Gregorian date such as '2027/05/05' is never taken for another day. A time or free text is rejected as well.
$mine = $moved
->withHoliday('1406/02/03', 'Company day')
->withoutHoliday('1406/01/13'); // remove Nature Day
var_dump($mine->isHoliday(1406, 2, 3)); // bool(true)
var_dump($mine->isHoliday(1406, 1, 13)); // bool(false)4. See why a day is a holiday
statusOf() returns each title of a day together with its origin. The origin is a HolidayOrigin: Fixed, Official, Reported, Estimated or User.
foreach ($mine->statusOf(1406, 2, 3) as $entry) {
echo $entry->title, ' ', $entry->origin->value, "\n"; // Company day user
}
foreach ($mine->statusOf(1406, 3, 27) as $entry) {
echo $entry->title, ' ', $entry->origin->value, "\n"; // عاشورای حسینی user
}
foreach ($cal->statusOf(1405, 1, 1) as $entry) {
echo $entry->title, ' ', $entry->origin->value, "\n";
}
// جشن نوروز fixed
// عید فطر officialYou can use this to show a small note next to dates that are only estimates.
All options
| Method | What it does |
|---|---|
HolidayCalendar::default() | Official data on, offset 0, no changes |
HolidayCalendar::fromArray($config) | Builds a calendar from an array in the shape of the Laravel config (see below). Unknown keys and bad values throw |
withIslamicOffset(int $days) | Moves estimated Islamic holidays by -3 to +3 days |
withHijriMonthStart($year, $month, $day) | Sets the real first day of a Hijri month. Holidays of that month follow it |
withHoliday($date, $title) | Adds a title to a day |
withoutHoliday($date, $title = null) | Removes one title, or the whole day |
withOfficialData(bool $use) | false treats every year as an estimate, as releases before 0.2.0 did |
sourceOf($jalaliYear) | The HolidaySource of a year, before your changes |
statusOf($year, $month, $day) | The titles of a day with their HolidayOrigin |
getTitles(), getTitle(), isHoliday(), all(), allTitles(), allFixed(), isWeekend(), isBusinessDay(), nextBusinessDay() | The same lookups as IranHolidays, with your changes applied. allFixed() always lists the fixed table only |
Which rule wins
For every day and every title the order is the same:
- Your changes:
withHoliday(),withoutHoliday()andwithHijriMonthStart(). - The official table, for the years it covers, while
withOfficialData(true)is on. - The Umm al-Qura estimate, moved by
withIslamicOffset().
Fixed Jalali holidays are never moved by an offset or a month start. You can still remove one with withoutHoliday().
Laravel config
Publish the config file with:
php artisan vendor:publish --tag=rtly-kit-configIt creates config/rtly-kit.php. The holidays block has these keys:
'holidays' => [
'islamic_offset' => 0, // -3..3, estimated years only
'hijri_month_starts' => [], // '1447-10' => '2026-03-21'
'extra' => [], // '1405/02/03' => 'Title' or ['Title 1', 'Title 2']
'removed' => [], // '1405/02/03' or '1405/02/03' => 'Title'
'use_official_data' => true,
],The service provider builds one HolidayCalendar from this block and binds it in the container. Get it with app(HolidayCalendar::class). It is created the first time you ask for it, so a bad value fails at that point. Missing keys use their defaults. The holidays entry must be an array. Any other value (for example the string 'oops') throws InvalidDateException that names rtly-kit.holidays when the calendar is first resolved. islamic_offset may also be an integer-like string such as '1' or '-2', which is what env() returns. Floats and other strings are rejected.
use RtlyKit\Holiday\HolidayCalendar;
$calendar = app(HolidayCalendar::class);
$calendar->isHoliday(1406, 3, 27);Good to know. The static IranHolidays class and the helper is_iran_holiday() always use the default calendar, not the one from your config. Use app(HolidayCalendar::class) when you want your settings applied.
What changed in 0.2.0
In the Jalali years 1380 to 1405 the static methods now return the official or reported dates. They used to return the table estimate. Years outside 1380 to 1405, and calendars built with withOfficialData(false), return what earlier releases returned. Some examples:
- 1405/01/01 is Eid al-Fitr 1447 AH. 1404/12/29 is no longer a day for it.
- Eid al-Fitr 1446 AH is on 1404/01/11, not 1404/01/10.
- In official years, only the titles in the official table appear. For example, the 8 Rabi I day (Imam Hasan Askari) is not in the 1394 and 1395 data.
The side-by-side list is in Upgrade.
Good to know
Good to know. There is no official data for 1406 yet. When it is published, it will be added to the data file and nothing else changes. For estimated years, a flat offset fixes a steady shift but not month-by-month moon sighting. Use withHijriMonthStart() for exact months. Where the data came from is described in Accuracy and data.
Related: Iranian holidays, Hijri calendar, Laravel setup.
Was this page helpful?