في هذه الصفحة
ما الذي تفعله الفئة#
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');
الأنماط: أم القرى والجدولي#
| النمط | كيف تُحدَّد أطوال الأشهر |
|---|---|
HijriVariant::UmmAlQura (الافتراضي) | جدول مضمَّن لأطوال الأشهر للسنوات 1300 إلى 1500 هـ (من 1882-11-12 إلى 2077-11-16). وخارج هذا الجدول تُستخدم قواعد النمط الجدولي (Tabular) بديلًا. |
HijriVariant::Tabular | التقويم المدني الحسابي: دورة من 30 سنة فيها 11 سنة كبيسة. حتمي لكل سنة، لكنه قد يختلف عن أم القرى بيوم أو يومين. |
$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
نقطتا ارتكاز إضافيتان: 1 رمضان 1446 يوافق 2025-03-01، و1 محرم 1447 يوافق 2025-06-26 (أم القرى).
islamic-umalqura في ICU/CLDR. وقورنت بداية كل شهر من 1318 هـ إلى 1500 هـ (2196 شهرًا) بتقويم أم القرى الرسمي لدى KACST في 2026-10-08، فلم يظهر أي اختلاف. أما السنوات 1300 إلى 1317 هـ فمصدرها بيانات ICU/CLDR، والموقع الرسمي لا يعرضها. ولا يعيد أي من النمطين إنتاج تقاويم رؤية الهلال (المعتمدة في إيران والمغرب وغيرهما)، وقد تختلف عنها بيوم. وتتغير حدود اليوم عند منتصف الليل المدني لا عند الغروب. أما مواعيد الشعائر الدينية فيعلنها المرجع المختص.للتحقق في الشيفرة، تُرجع Hijri::ummAlQuraVerifiedRange() النطاق [1318, 1500]، وتُرجع Hijri::isUmmAlQuraVerified($year) قيمة true للسنوات الواقعة فيه:
echo json_encode(Hijri::ummAlQuraVerifiedRange()); // [1318,1500]
var_dump(Hijri::isUmmAlQuraVerified(1446)); // bool(true)
var_dump(Hijri::isUmmAlQuraVerified(1317)); // bool(false)
تغطية الجدول#
خارج السنوات 1300 إلى 1500 هـ لا تُطلق create() وmake() استثناءً، بل تستقرئان بقواعد النمط الجدولي، وقد لا تلتقي المجموعتان من القواعد بسلاسة عند الحد. استخدم Hijri::hasUmmAlQuraData($year) و$date->usesUmmAlQuraTable() لمعرفة أي القاعدتين طُبّقت فعلًا.
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
$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) (استقراء جدولي)
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، أو طابعًا زمنيًا Unix من النوع 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
دلالات السلاسل النصية#
- تتحول الأرقام الفارسية والعربية إلى أرقام إنجليزية وتُقصّ المسافات من الطرفين؛ والسلاسل الفارغة تُطلق
InvalidDateException. - السلسلة بالصيغة
Y/m/dأوY-m-d(سنة من 3 أو 4 أرقام، وH:i[:s]اختياري) وسنتها أقل من 1700 تُقرأ تاريخًا هجريًا في النمط المختار. - كل ما عدا ذلك، بما فيه السنوات 1700 فما فوق، يُحلَّل تاريخًا ميلاديًا أو نصًا حرًّا.
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) وآخر يوم هو 9665/12/آخر يوم (9999-10-01)، أي السنوات الميلادية من 622 إلى 9999. وأي قيمة خارج ذلك تُطلق InvalidDateException من create() وmake() والطوابع الزمنية الصحيحة وadd*()/sub*() ودوال التحويل؛ ولا تُطلق أبدًا 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(). ويضيف Hijri الدالتين getVariant() وusesUmmAlQuraTable().
ترقيم أيام الأسبوع#
تبدأ getDayOfWeek() والرمز w من الأحد: 0 = الأحد إلى 6 = السبت (كما في PHP). أما Jalali فيبدأ من السبت. والرمز N هو الرقم بحسب ISO، من 1 = الاثنين إلى 7 = الأحد.
$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. تُبطل الشرطة المائلة العكسية المعنى الخاص للمحرف الذي يليها. لاحظ أن اللغة الافتراضية هي العربية.
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 | السنة (4 أرقام ورقمان)، والشهر واليوم مع صفر بادئ وبدونه |
H G h g i s | نظام 24 ساعة ونظام 12 ساعة، والدقائق، والثواني |
F M | اسم الشهر بحسب اللغة |
l | اسم يوم الأسبوع بحسب اللغة |
w N | رقم يوم الأسبوع، الأحد = 0؛ ورقم ISO، الاثنين = 1 |
z t L | رقم اليوم في السنة بدءًا من الصفر؛ وعدد أيام الشهر؛ و1 للسنة الكبيسة (355 يومًا) |
a A | ص/م بحسب اللغة (ص/م وق.ظ/ب.ظ وam/pm) |
S W | فارغ دائمًا؛ وأسبوع ISO للحظة الميلادية |
c r | Y-m-d\TH:i:sP مع التاريخ الهجري؛ وRFC 2822 للحظة الميلادية |
U e T P p O Z I u v | تُفوَّض إلى اللحظة الأساسية |
تُرجع الدالة الساكنة Hijri::monthName($month, $locale = 'ar') اسم شهر، وتُطلق InvalidDateException لشهر خارج النطاق من 1 إلى 12:
echo Hijri::monthName(9), ' | ', Hijri::monthName(9, 'fa'), ' | ', Hijri::monthName(9, 'en');
// رمضان | رمضان | Ramadan
// Hijri::monthName(13) throws: Invalid Hijri month: 13
الإزاحة والضبط#
مجموعة الدوال هي نفسها في Jalali: 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 (يبقى النمط محفوظًا)
الإزاحة إلى ما بعد السنة 1 أو 9665، أو بمقدار مبالغ فيه، تُطلق InvalidDateException.
المقارنة والقياس#
تعمل المقارنة (eq ne gt gte lt lte equals isBefore isAfter between isPast isFuture isToday) على اللحظات وتقبل أي تقويم. تحسب diffInDays() الأيام الكاملة؛ أما diffInMonths() وdiffInYears() فتحسبان الأشهر والسنوات الهجرية الكاملة بنمط هذه النسخة (يُعاد أولًا التعبير عن التاريخ الآخر بهذا النمط). ويعمل الوسيط $absolute كما في Jalali.
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) | 29 أو 30؛ وتُطلق InvalidDateException لشهر خارج النطاق من 1 إلى 12 |
Hijri::daysInYear($y, $variant) | مجموع أطوال الأشهر الاثني عشر (354 أو 355 عمليًا) |
Hijri::isLeapYear($y, $variant) | صحيحة عندما تكون السنة من 355 يومًا |
Hijri::hasUmmAlQuraData($y) | صحيحة للسنوات 1300 إلى 1500 هـ |
Hijri::ummAlQuraVerifiedRange(), Hijri::isUmmAlQuraVerified($y) | النطاق [1318, 1500] الذي قورن بتقويم KACST الرسمي، وصحيحة لسنة تقع فيه |
Hijri::gregorianToHijri($gy, $gm, $gd, $variant) | [year, month, day] |
Hijri::hijriToGregorian($hy, $hm, $hd, $variant) | [year, month, day] |
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]
ملاحظات عملية#
- اختر النمط مرة واحدة للتطبيق كله ومرّره حيثما احتجت إليه. لا تخلط الأنماط في مقارنة واحدة، إلا إذا أردت أن ترى الفرق بين النمطين على مستوى اليوم.
- للتواريخ بين 1300 و1500 هـ يكون جدول أم القرى هو المرجع في المكتبة. وخارج هذا النطاق تُحسب التواريخ بقواعد النمط الجدولي.
- التحويل من التقويمات الأخرى وإليها في التحويل والمقارنة بين التواريخ؛ ومساعدات Carbon (
toHijri()وcreateFromHijri()) في ماكروات Carbon.