تقویم هجری

کلاس تغییرناپذیر Hijri با جدول ماه‌های ام‌القری و گونهٔ حسابی Tabular. ساخت و خواندن تاریخ، قالب‌بندی به عربی و فارسی و انگلیسی، جابه‌جایی، مقایسه و اختلاف.

در این صفحه
  1. این کلاس چه می‌کند
  2. گونه‌ها: ام‌القری و Tabular
    1. پوشش جدول
  3. ساخت تاریخ
    1. create()
    2. make() و تابع کمکی
    3. رشته چطور خوانده می‌شود
  4. بازهٔ سال و خطاها
  5. خواندن مقدارها
    1. شمارهٔ روز هفته
  6. قالب‌بندی
  7. جابه‌جایی و برش
  8. مقایسه و اختلاف
  9. محاسبه‌های تقویمی
  10. نکته‌های کاربردی

این کلاس چه می‌کند#

کلاس RtlyKit\Calendar\Hijri یک تاریخ و زمان در تقویم هجری قمری است و تغییر نمی‌کند. مثل دو کلاس دیگر، یک DateTimeImmutable زیرش است و قرارداد مشترک CalendarDate را دارد (تبدیل و مقایسهٔ تاریخ‌ها). چیزی که هجری را متفاوت می‌کند گونه (variant) است، یعنی قاعده‌ای که طول ماه‌ها را می‌گوید. هر شیء گونهٔ خودش را دارد و هر متدی که تاریخ را عوض کند آن را نگه می‌دارد.

مثال‌ها با این چند خط شروع می‌شوند (نصب):

<?php
require 'vendor/autoload.php';

use RtlyKit\Calendar\Hijri;
use RtlyKit\Calendar\HijriVariant;
use RtlyKit\Calendar\Jalali;
use RtlyKit\Exceptions\InvalidDateException;
use function RtlyKit\hdate;   // تابع کمکی فضای‌نام‌دار، همان Hijri::make()

$utc = new DateTimeZone('UTC');

گونه‌ها: ام‌القری و Tabular#

گونهطول ماه‌ها از کجا می‌آید
HijriVariant::UmmAlQura (پیش‌فرض)جدول طول ماه‌ها که داخل بسته است و سال‌های ۱۳۰۰ تا ۱۵۰۰ هجری قمری را می‌پوشاند (از 1882-11-12 تا 2077-11-16). بیرون از آن، قاعدهٔ Tabular جای آن را می‌گیرد.
HijriVariant::Tabularتقویم مدنی حسابی: چرخهٔ ۳۰ ساله با ۱۱ سال کبیسه. برای هر سال جواب قطعی می‌دهد، ولی ممکن است یک یا دو روز با ام‌القری فرق داشته باشد.
$uq  = Hijri::create(1446, 10, 1, 0, 0, 0, $utc);                       // ام‌القری
$tab = Hijri::create(1446, 10, 1, 0, 0, 0, $utc, HijriVariant::Tabular);

echo $uq->toGregorian()->format('Y-m-d');   // 2025-03-30
echo $tab->toGregorian()->format('Y-m-d');  // 2025-03-31
echo $uq->getVariant()->name;               // UmmAlQura

دو نقطهٔ مرجع دیگر در ام‌القری: اول رمضان ۱۴۴۶ برابر 2025-03-01 و اول محرم ۱۴۴۷ برابر 2025-06-26 است.

جدول ام‌القری از داده‌های تقویم islamic-umalqura در ICU/CLDR ساخته شده است. آغاز ماه‌ها را برای سال‌های ۱۳۱۸ تا ۱۵۰۰ هجری قمری با تقویم رسمی KACST مقایسه کرده‌ایم (در 2026-10-08، ۲۱۹۶ ماه، بدون هیچ اختلاف).

خوب است بدانید. سایت رسمی KACST سال‌های ۱۳۰۰ تا ۱۳۱۷ را ندارد. برای این سال‌ها از داده‌ی ICU/CLDR استفاده می‌شود. هیچ‌کدام از دو گونه رؤیت هلال را (مثل ایران یا مراکش) بازتولید نمی‌کند و ممکن است یک روز با اعلام رسمی فرق داشته باشد. مرز روز هم نیمه‌شب مدنی است، نه غروب. برای تاریخ‌های مناسکی که مرجع رسمی اعلام می‌کند، به اعلام همان مرجع تکیه کنید.

پوشش جدول#

بیرون از ۱۳۰۰ تا ۱۵۰۰، create() و make() خطا نمی‌دهند. آن‌ها با قاعدهٔ Tabular ادامه می‌دهند. با Hijri::hasUmmAlQuraData($year) و $date->usesUmmAlQuraTable() می‌فهمید کدام قاعده به کار رفته. بازهٔ سال‌هایی که با تقویم رسمی KACST مقایسه شده‌اند را هم با Hijri::ummAlQuraVerifiedRange() و Hijri::isUmmAlQuraVerified($year) می‌گیرید.

echo Hijri::hasUmmAlQuraData(1299) ? 1 : 0;   // 0
echo Hijri::hasUmmAlQuraData(1300) ? 1 : 0;   // 1
echo Hijri::hasUmmAlQuraData(1500) ? 1 : 0;   // 1
echo Hijri::hasUmmAlQuraData(1501) ? 1 : 0;   // 0

echo json_encode(Hijri::ummAlQuraVerifiedRange());              // [1318,1500]
var_dump(Hijri::isUmmAlQuraVerified(1446));                     // bool(true)
var_dump(Hijri::isUmmAlQuraVerified(1310));                     // bool(false)

$o = Hijri::create(1600, 1, 1, 0, 0, 0, $utc);
echo $o->toGregorian()->format('Y-m-d');     // 2173-12-06
var_dump($o->usesUmmAlQuraTable());            // bool(false)  (ادامه با قاعدهٔ Tabular)
var_dump(Hijri::create(1446, 9, 1, 0, 0, 0, $utc)->usesUmmAlQuraTable()); // bool(true)

ساخت تاریخ#

create()#

متد Hijri::create($year, $month, $day, $hour = 0, $minute = 0, $second = 0, $timezone = null, $variant = HijriVariant::UmmAlQura) طول واقعی ماه را در گونهٔ انتخاب‌شده چک می‌کند.

$h = Hijri::create(1446, 9, 1, 0, 0, 0, $utc);

echo $h;                                  // 1446/09/01 00:00:00
echo $h->toGregorian()->format('Y-m-d');  // 2025-03-01
var_dump($h->usesUmmAlQuraTable());       // bool(true)

make() و تابع کمکی#

متد Hijri::make($time = null, $timezone = null, $variant = HijriVariant::UmmAlQura) این ورودی‌ها را قبول می‌کند: DateTimeInterface، هر CalendarDate، زمان یونیکس (int)، رشته یا null (همین الان). تابع hdate($time, $timezone) آن را با گونهٔ پیش‌فرض صدا می‌زند. Hijri::now() و Hijri::today() آرگومان‌های ($timezone, $variant) دارند.

echo Hijri::make('2025-03-01', $utc);                     // 1446/09/01 00:00:00
echo hdate('2025-03-01')->format('j F Y', 'en');          // 1 Ramadan 1446
echo Hijri::make(Jalali::create(1404, 1, 1, 0, 0, 0, $utc)); // 1446/09/21 00:00:00
echo Hijri::make('2025-03-31', $utc)->format('Y/m/d');    // 1446/10/02
echo Hijri::make('2025-03-31', $utc, HijriVariant::Tabular)->format('Y/m/d'); // 1446/10/01

رشته چطور خوانده می‌شود#

  1. رقم‌های فارسی و عربی به انگلیسی تبدیل می‌شوند و فاصله‌های دو سر رشته حذف می‌شود. رشتهٔ خالی InvalidDateException می‌دهد.
  2. رشته‌ای با شکل Y/m/d یا Y-m-d (سال ۳ یا ۴ رقم، با H:i[:s] اختیاری) که سالش کمتر از ۱۷۰۰ باشد، تاریخ هجری در گونهٔ انتخاب‌شده است.
  3. هر چیز دیگر، از جمله سال‌های ۱۷۰۰ به بعد، میلادی یا متن آزاد خوانده می‌شود.
echo Hijri::make('1446/09/01', $utc);   // 1446/09/01 00:00:00   (هجری)
echo Hijri::make('1700/01/01', $utc);   // 1111/07/10 00:00:00   (میلادیِ 1700-01-01)

try { Hijri::make('1446/02/31'); }
catch (InvalidDateException $e) { echo $e->getMessage(); }  // Invalid Hijri date: 1446/2/31
گونه هنگام ساخت از شیء هجری. Hijri::make($hijriInstance) همان شیء را بدون تغییر برمی‌گرداند و $variant را نادیده می‌گیرد. اگر همان تاریخ را در گونهٔ دیگر می‌خواهید، از لحظه بسازید: Hijri::make($h->toGregorian(), null, HijriVariant::Tabular).

بازهٔ سال و خطاها#

سال هجری از Hijri::MIN_YEAR = 1 تا Hijri::MAX_YEAR = 9665 پشتیبانی می‌شود. اولین روز 1/1/1 (برابر 0622-07-19) و آخرین روز، آخر ماه ۱۲ سال ۹۶۶۵ (برابر 9999-10-01) است. هر چیز بیرون از این بازه، چه از create() و make() و زمان یونیکس و add*() و sub*() و چه از تبدیل‌ها، فقط InvalidDateException می‌دهد. TypeError یا ValueError بیرون نمی‌آید.

foreach ([[0, 1, 1], [9666, 1, 1], [1446, 1, 31], [1446, 13, 1]] as [$y, $m, $d]) {
    try { Hijri::create($y, $m, $d); }
    catch (InvalidDateException $e) { echo $e->getMessage(), "\n"; }
}
// Invalid Hijri date: 0/1/1
// Invalid Hijri date: 9666/1/1
// Invalid Hijri date: 1446/1/31
// Invalid Hijri date: 1446/13/1

try { Hijri::make(new DateTimeImmutable('0600-01-01', $utc)); }
catch (InvalidDateException $e) { echo $e->getMessage(); }
// Date out of the supported Hijri range (1..9665): -22

try { Hijri::create(9665, 1, 1)->addYears(1); }
catch (InvalidDateException $e) { echo $e->getMessage(); }
// Hijri year out of the supported range: 9666

خواندن مقدارها#

گیرنده‌ها همان‌هایی هستند که قرارداد مشترک می‌گوید: getYear()، getMonth()، getDay()، getHour()، getMinute()، getSecond()، getTimestamp()، getTimezone()، toGregorian()، toDateString() (به شکل Y/m/d) و toDateTimeString(). هجری دو متد دیگر هم دارد: getVariant() و usesUmmAlQuraTable().

شمارهٔ روز هفته#

getDayOfWeek() و توکن w از یکشنبه شروع می‌کنند: ۰ = یکشنبه تا ۶ = شنبه (مثل PHP). در جلالی شمارش از شنبه شروع می‌شود. توکن N شمارهٔ ISO است: ۱ = دوشنبه تا ۷ = یکشنبه.

$h = Hijri::create(1446, 9, 1, 0, 0, 0, $utc);   // روز شنبه
echo $h->getDayOfWeek();                         // 6
foreach (range(0, 6) as $i) { echo $h->addDays($i)->getDayOfWeek(); }  // 6012345

قالب‌بندی#

format($pattern = 'Y/m/d H:i:s', $locale = 'ar', $digits = 'latin'). مقدار $locale یکی از ar، fa یا en است (مقدار ناشناخته مثل en رفتار می‌کند). این مقدار نام ماه و روز و نشانگر قبل و بعد از ظهر را تعیین می‌کند. $digits یکی از latin، persian یا arabic است. بک‌اسلش نویسهٔ بعدی را همان‌طور که هست می‌نویسد (بک‌اسلشِ آخر الگو نادیده گرفته می‌شود). الگوی بلندتر از Hijri::MAX_FORMAT_LENGTH (۲۵۶ بایت) InvalidDateException با کد input_too_long می‌دهد. زبان پیش‌فرض عربی است.

echo $h->format('l j F Y');                 // السبت 1 رمضان 1446
echo $h->format('l j F Y', 'fa');           // شنبه 1 رمضان 1446
echo $h->format('l j F Y', 'en');           // Saturday 1 Ramadan 1446
echo $h->format('Y/m/d', 'ar', 'arabic');   // ١٤٤٦/٠٩/٠١
echo $h->format('j F Y', 'fa', 'persian');  // ۱ رمضان ۱۴۴۶
echo $h->format('t L z N');                 // 29 0 237 6

$pm = Hijri::create(1446, 9, 1, 15, 0, 0, $utc);
echo $pm->format('g:i a', 'ar');            // 3:00 م
echo $pm->format('g:i A', 'en');            // 3:00 PM
توکنمعنی
Y y m n d jسال (۴ و ۲ رقمی)، ماه و روز، با صفر ابتدایی و بدون آن
H G h g i sساعت ۲۴ و ۱۲ ساعته، دقیقه، ثانیه
F Mنام ماه به زبان انتخاب‌شده
lنام روز هفته به زبان انتخاب‌شده
w Nشمارهٔ روز هفته با یکشنبه = ۰. و شمارهٔ ISO با دوشنبه = ۱
z t Lشمارهٔ روز در سال از صفر. تعداد روزهای ماه. و ۱ برای سال کبیسه (۳۵۵ روز)
a Aقبل و بعد از ظهر، بسته به زبان (ص/م، ق.ظ/ب.ظ، am/pm)
S Wهمیشه خالی. و هفتهٔ ISO لحظهٔ میلادی
c rY-m-d\TH:i:sP با تاریخ هجری. و RFC 2822 لحظهٔ میلادی
U e T P p O Z I u vاز لحظهٔ زیرین گرفته می‌شوند

متد استاتیک Hijri::monthName($month, $locale = 'ar') نام ماه را می‌دهد. برای ماه بیرون از ۱ تا ۱۲ InvalidDateException می‌دهد:

echo Hijri::monthName(9), ' | ', Hijri::monthName(9, 'fa'), ' | ', Hijri::monthName(9, 'en');
// رمضان | رمضان | Ramadan
// Hijri::monthName(13) throws: Invalid Hijri month: 13

جابه‌جایی و برش#

متدها همان‌هایی هستند که در جلالی دیدید: addDays/Hours/Minutes/Seconds، addMonths، addYears، نسخه‌های sub* و startOf/endOf برای روز و ماه و سال. ماه، ماه هجریِ همان گونه است. اگر ماه مقصد کوتاه‌تر باشد، روز کم می‌شود تا در ماه جا شود.

echo Hijri::create(1446, 4, 30, 0, 0, 0, $utc)->addMonths(1);  // 1446/05/29 00:00:00  (روز کم شد)
echo $h->addYears(1);                                          // 1447/09/01 00:00:00
echo $h->subMonths(9);                                         // 1445/12/01 00:00:00
echo $h->addDays(29);                                          // 1446/10/01 00:00:00
echo $h->endOfMonth();                                         // 1446/09/29 23:59:59
echo $h->endOfYear();                                          // 1446/12/29 23:59:59
echo $h->startOfYear();                                        // 1446/01/01 00:00:00
echo $tab->addMonths(1)->getVariant()->name;                  // Tabular  (گونه می‌ماند)

رفتن به بیرون از سال ۱ یا ۹۶۶۵، یا جابه‌جایی با مقدار غیرمنطقی، InvalidDateException می‌دهد.

مقایسه و اختلاف#

مقایسه‌ها (eq ne gt gte lt lte equals isBefore isAfter between isPast isFuture isToday) روی لحظه‌ها انجام می‌شوند و هر تقویمی را قبول می‌کنند. diffInDays() روز کامل می‌شمارد. diffInMonths() و diffInYears() ماه و سال کامل هجری را می‌شمارند، در گونهٔ همین شیء (تاریخ دیگر اول به همان گونه برده می‌شود). پارامتر $absolute مثل جلالی کار می‌کند.

echo Hijri::create(1446, 1, 1, 0, 0, 0, $utc)->diffInMonths(Hijri::create(1447, 3, 1, 0, 0, 0, $utc)); // 14
echo Hijri::create(1440, 1, 1, 0, 0, 0, $utc)->diffInYears(Hijri::create(1446, 1, 1, 0, 0, 0, $utc));  // 6
echo $h->diffInDays(Hijri::create(1446, 10, 1, 0, 0, 0, $utc));                                        // 29

محاسبه‌های تقویمی#

متدخروجی
Hijri::isValid($y, $m, $d, $variant)bool. برای مقدار بیرون از بازه false
Hijri::daysInMonth($y, $m, $variant)۲۹ یا ۳۰. برای ماه بیرون از ۱ تا ۱۲ InvalidDateException
Hijri::daysInYear($y, $variant)جمع طول ۱۲ ماه (در عمل ۳۵۴ یا ۳۵۵)
Hijri::isLeapYear($y, $variant)true وقتی سال ۳۵۵ روز دارد
Hijri::hasUmmAlQuraData($y)true برای ۱۳۰۰ تا ۱۵۰۰ هجری قمری
Hijri::ummAlQuraVerifiedRange()[1318, 1500]، سال‌هایی که با تقویم رسمی KACST مقایسه شده‌اند
Hijri::isUmmAlQuraVerified($y)true برای سالی که در این بازه باشد
Hijri::gregorianToHijri($gy, $gm, $gd, $variant)[سال, ماه, روز]
Hijri::hijriToGregorian($hy, $hm, $hd, $variant)[سال, ماه, روز]
echo implode(',', array_map(fn ($m) => Hijri::daysInMonth(1446, $m), range(1, 12)));
// 29,30,30,30,29,30,30,29,29,30,29,29
echo Hijri::daysInYear(1446);                                    // 354
echo Hijri::daysInMonth(1446, 12, HijriVariant::Tabular);        // 29
echo json_encode(Hijri::gregorianToHijri(2025, 3, 1));           // [1446,9,1]
echo json_encode(Hijri::hijriToGregorian(1446, 9, 1));           // [2025,3,1]

نکته‌های کاربردی#

  • گونه را یک بار برای کل برنامه انتخاب کنید و همه‌جا همان را بدهید. دو گونه را در یک مقایسه قاطی نکنید، مگر برای دیدن اختلاف‌های یک‌روزه.
  • برای سال‌های ۱۳۰۰ تا ۱۵۰۰ هجری قمری مرجع کتابخانه جدول ام‌القری است. بیرون از آن، نتیجه با قاعدهٔ حسابی ادامه پیدا می‌کند.
  • تبدیل به تقویم‌های دیگر در تبدیل و مقایسهٔ تاریخ‌ها آمده است. متدهای Carbon (toHijri() و createFromHijri()) در ماکروهای Carbon هستند.