التقويم الجلالي

فئة التاريخ الجلالي (الفارسي، الشمسي الهجري) غير القابلة للتغيير: إنشاء التواريخ وتحليلها، وتنسيقها برموز date في PHP، وإزاحتها وضبطها، ومقارنتها وقياس الفروق بينها، مع نطاق السنوات الدقيق وسلوك الأخطاء.

في هذه الصفحة
  1. ما الذي تفعله الفئة
  2. إنشاء التواريخ
    1. من المكوّنات: create()
    2. من أي قيمة: make()
    3. دلالات السلاسل النصية في make()
    4. بصيغة محددة: createFromFormat()
  3. النطاق المدعوم والأخطاء
  4. قراءة القيم
    1. ترقيم أيام الأسبوع
  5. التنسيق
  6. الإزاحة والضبط
  7. المقارنة والقياس
  8. الدوال الساكنة المساعدة
  9. ملاحظات عملية

ما الذي تفعله الفئة#

RtlyKit\Calendar\Jalali هي قيمة تاريخ ووقت لا تتغير بعد إنشائها، في التقويم الفارسي (الشمسي الهجري). تحتفظ الفئة في داخلها بلحظة واحدة من النوع DateTimeImmutable؛ ولذلك لها منطقة زمنية وطابع زمني (Unix timestamp) ونظير ميلادي دقيق، وتُحسب السنة والشهر واليوم الجلاليّة من هذه اللحظة. كل دالة تعديل تُرجع كائنًا جديدًا، ويبقى الأصل كما هو. الفئة final وتنفّذ العقد المشترك CalendarDate الموصوف في التحويل والمقارنة بين التواريخ.

المتطلبات. ثبّت الحزمة أولًا (التثبيت). تفترض كل الأمثلة الترويسة التالية:

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

use RtlyKit\Calendar\Jalali;
use RtlyKit\Exceptions\InvalidDateException;
use function RtlyKit\jdate;   // مساعد ضمن فضاء الأسماء، مكافئ لـ Jalali::make()
من المفيد أن تعرف. يعتمد التحويل قاعدة حسابية ثابتة بدورة من 33 سنة (السنة كبيسة إذا كان باقي قسمتها على 33 هو 1 أو 5 أو 9 أو 13 أو 17 أو 22 أو 26 أو 30). أما التقويم الرسمي في إيران فيُعرَّف فلكيًا. وقد قارنّا نتائج القاعدة بالتقويم الرسمي المنشور للسنوات 1206 إلى 1497 (293 تاريخًا لعيد النوروز)، وبالتعريف الفلكي المحسوب للسنوات 1178 إلى 1502، فكانت متطابقة. خارج هذه النافذة تستمر القاعدة نفسها، وقد يختلف يوم واحد في بعض السنوات البعيدة. في المواعيد القانونية التي تتوقف على النشر الرسمي، ارجع إلى الجهة المختصة.

إنشاء التواريخ#

من المكوّنات: create()#

تبني create($year, $month, $day, $hour = 0, $minute = 0, $second = 0, $timezone = null) تاريخًا من مكوّنات جلالية. يُتحقَّق من التاريخ بحسب الطول الفعلي للشهر في تلك السنة، فيُرفض 1404/12/30 (السنة 1404 ليست كبيسة) ويُقبل 1403/12/30. وإذا لم تُحدَّد منطقة زمنية تُستخدم المنطقة الزمنية الافتراضية في PHP (date_default_timezone_get()).

$tehran = new DateTimeZone('Asia/Tehran');
$d = Jalali::create(1404, 7, 15, 14, 30, 5, $tehran);

echo $d;                                   // 1404/07/15 14:30:05
echo $d->getTimestamp();                   // 1759834805
echo $d->toGregorian()->format('c');       // 2025-10-07T14:30:05+03:30

من أي قيمة: make()#

تقبل Jalali::make($time = null, $timezone = null) كائنًا من DateTimeInterface، أو CalendarDate آخر (Hijri أو Hebrew أو Jalali)، أو طابعًا زمنيًا Unix من النوع int، أو سلسلة نصية، أو null (الآن). والمساعد jdate() اسم مستعار خفيف لها ضمن فضاء الأسماء.

$utc = new DateTimeZone('UTC');

echo Jalali::make(new DateTimeImmutable('2026-03-21 12:00', $utc)); // 1405/01/01 12:00:00
echo Jalali::make(0, $utc);                                        // 1348/10/11 00:00:00
echo jdate('2026-03-21')->format('l j F Y');                       // شنبه 1 فروردین 1405
echo Jalali::now();      // اللحظة الحالية في المنطقة الزمنية الافتراضية
echo Jalali::today();    // اليوم عند 00:00:00

دلالات السلاسل النصية في make()#

تُعالَج السلاسل النصية بهذا الترتيب:

  1. تُحوَّل الأرقام الفارسية والعربية إلى أرقام إنجليزية، وتُقصّ المسافات من الطرفين. السلسلة الفارغة أو المكوّنة من مسافات فقط تُطلق InvalidDateException.
  2. إذا كانت السلسلة بالصيغة Y/m/d أو Y-m-d بسنة من 3 أو 4 أرقام، يليها اختياريًا H:i أو H:i:s (يفصلهما مسافة أو T)، وكانت السنة أقل من 1700، فإنها تُقرأ تاريخًا جلاليًا.
  3. كل سلسلة أخرى، بما فيها أي سنة 1700 أو أكبر، تُمرَّر إلى DateTimeImmutable وتُقرأ تاريخًا ميلاديًا أو نصًا حرًّا ('2025-10-07' و'tomorrow' و'10 October 2025').
echo Jalali::make('1403/12/30');          // 1403/12/30 00:00:00
echo Jalali::make('۱۴۰۴/۰۷/۱۵');          // 1404/07/15 00:00:00   (أرقام فارسية)
echo Jalali::make('1404-07-15 08:05');    // 1404/07/15 08:05:00
echo Jalali::make('2025-10-07')->format('Y/m/d');  // 1404/07/15   (نص ميلادي)
echo Jalali::make('1700/01/01')->format('Y/m/d');  // 1078/10/12   (السنة 1700 فما فوق تُقرأ ميلادية)
حدّ السنة 1700. تُقرأ سلسلة مثل '0001-01-01' أو '1500-06-01' على أنها تاريخ جلالي. إذا أردت تاريخًا ميلاديًا قبل السنة 1700، فأنشئ كائن DateTimeImmutable ومرّره إلى make().

المدخلات غير الصالحة تُطلق دائمًا InvalidDateException:

foreach (['1404/12/30', '1404/13/01', '1404/07/15 25:00', '', 'garbage'] as $s) {
    try {
        Jalali::make($s);
    } catch (InvalidDateException $e) {
        echo "'$s': ", $e->getMessage(), "\n";
    }
}
// '1404/12/30': Invalid Jalali date: 1404/12/30
// '1404/13/01': Invalid Jalali date: 1404/13/1
// '1404/07/15 25:00': Invalid time: 25:0:0
// '': Unable to parse an empty date string.
// 'garbage': Unable to parse date: garbage

بصيغة محددة: createFromFormat()#

تحلّل Jalali::createFromFormat($format, $time, $timezone = null) النص بصياغة رموز DateTime::createFromFormat في PHP (ويجوز أن تكون الأرقام فارسية)، لكنها تفسّر السنة والشهر واليوم قيمًا جلالية. لا تنطبق القيود الخاصة بالتقويم الميلادي مثل «فبراير فيه 29 يومًا على الأكثر»؛ فالتقويم الجلالي هو الذي يتحقق من النتيجة.

echo Jalali::createFromFormat('Y/m/d H:i', '1404/07/15 08:30', $tehran); // 1404/07/15 08:30:00
echo Jalali::createFromFormat('d-m-Y', '31-06-1404');                    // 1404/06/31 00:00:00

try {
    Jalali::createFromFormat('Y/m/d', 'abc');
} catch (InvalidDateException $e) {
    echo $e->getMessage();   // Unable to parse 'abc' with format 'Y/m/d'
}

النطاق المدعوم والأخطاء#

السنوات الجلالية المدعومة من Jalali::MIN_YEAR = -620 إلى Jalali::MAX_YEAR = 9377. وهذا هو النطاق الذي يقابل السنوات الميلادية من 1 إلى 9999 بالضبط: أول يوم مدعوم هو -620/01/01 (0001-03-21) وآخر يوم هو 9377/12/30 (9999-03-20). تتحقق كل نقطة دخول من النطاق ولا تُطلق إلا InvalidDateException (ولا تُطلق أبدًا TypeError أو ValueError أو استثناءات DateMalformed*)، ويشمل ذلك create() وmake() وcreateFromFormat() والطوابع الزمنية الصحيحة وadd*()/sub*() ودوال التحويل الساكنة.

foreach ([[-621, 1, 1], [9378, 1, 1], [1404, 12, 30], [1404, 0, 1], [1404, 7, 31]] as [$y, $m, $d]) {
    try {
        Jalali::create($y, $m, $d);
    } catch (InvalidDateException $e) {
        echo $e->getMessage(), "\n";
    }
}
// Invalid Jalali date: -621/1/1
// Invalid Jalali date: 9378/1/1
// Invalid Jalali date: 1404/12/30
// Invalid Jalali date: 1404/0/1
// Invalid Jalali date: 1404/7/31

ويُتحقَّق من مكوّنات الوقت أيضًا: Jalali::create(1404, 1, 1, 24, 0, 0) تُطلق Invalid time: 24:0:0. والطابع الزمني الصحيح الواقع خارج السنوات الميلادية 1 إلى 9999 يُطلق Timestamp out of the supported range: ....

اِلتقط InvalidDateException لمعالجة مشكلات التقويم وحدها، أو الفئة الأساسية RtlyKit\Exceptions\RtlyKitException (وكذلك الواجهة العلامة RtlyKitThrowable) لمعالجة كل أخطاء المكتبة. راجع معالجة الأخطاء.

قراءة القيم#

الدالةما تُرجعه
getYear(), getMonth(), getDay()السنة الجلالية، والشهر (من 1 إلى 12)، واليوم (من 1 إلى 31)
getHour(), getMinute(), getSecond()الوقت من اليوم في المنطقة الزمنية للنسخة
getDayOfWeek()من 0 = السبت (Shanbe) إلى 6 = الجمعة (Jomeh)
monthName()اسم الشهر بالفارسية، مثل مهر
getTimestamp(), getTimezone()الطابع الزمني Unix وDateTimeZone
toGregorian()الكائن DateTimeImmutable الأساسي
toDateString(), toDateTimeString(), __toString()Y/m/d وY/m/d H:i:s وY/m/d H:i:s

ترقيم أيام الأسبوع#

يبدأ الأسبوع الجلالي يوم السبت: تُرجع getDayOfWeek() ورمز التنسيق w القيمة 0 للسبت و6 للجمعة؛ أما الرمز N فمن 1 (السبت) إلى 7 (الجمعة). أما Hijri وHebrew فمختلفان: إذ يبدآن من الأحد (0 = الأحد) مثل الرمز w في PHP نفسه. لا تشارك أرقام أيام الأسبوع بين التقويمات؛ قارن اللحظات بدلًا من ذلك.

$d = Jalali::create(1404, 7, 15);           // يوم ثلاثاء
foreach (range(0, 6) as $i) {
    $x = $d->addDays($i);
    echo $x->getDayOfWeek(), ':', $x->format('l'), ' ';
}
// 3:سه‌شنبه 4:چهارشنبه 5:پنجشنبه 6:جمعه 0:شنبه 1:یکشنبه 2:دوشنبه

التنسيق#

تقيّم format($pattern = 'Y/m/d H:i:s', $persianDigits = false) رموز بأسلوب date() في PHP ضمن التقويم الجلالي. مرّر true وسيطًا ثانيًا لتحويل كل الأرقام إلى الفارسية. تُبطل الشرطة المائلة العكسية الخاصية الرمزية للمحرف الذي يليها. والمحارف التي ليست رموزًا تُنسخ كما هي. النمط الأطول من Jalali::MAX_FORMAT_LENGTH (256 بايتًا) يُطلق InvalidDateException.

$d = Jalali::create(1404, 7, 15, 14, 30, 5, $tehran);

echo $d->format('l j F Y');            // سه‌شنبه 15 مهر 1404
echo $d->format('Y/m/d', true);        // ۱۴۰۴/۰۷/۱۵
echo $d->format('c');                  // 1404-07-15T14:30:05+03:30
echo $d->format('h:i A');              // 02:30 بعد از ظهر
echo $d->format('g:i a');              // 2:30 ب.ظ
echo $d->format('\Y: Y, \d: d');       // Y: 1404, d: 15
echo $d->format('t L z W N D');        // 30 0 200 41 4 س
الرمزالمعنى
Y yالسنة، من 4 أرقام ومن رقمين
m nالشهر مع صفر بادئ وبدونه
F Mاسم الشهر بالفارسية (M مطابق لـ F: فأسماء الأشهر الفارسية لا اختصار لها)
d jاليوم مع صفر بادئ وبدونه
l Dاسم يوم الأسبوع؛ وD هي الصيغة من حرف واحد (ش ی د س چ پ ج)
w Nرقم يوم الأسبوع: من 0 إلى 6 ومن 1 إلى 7، ويبدأ بالسبت
zرقم اليوم في السنة الجلالية بدءًا من الصفر
t Lعدد أيام الشهر؛ و1 إذا كانت السنة كبيسة
H G h gنظام 24 ساعة ونظام 12 ساعة، مع صفر بادئ وبدونه
i sالدقائق والثواني
a Aق.ظ/ب.ظ وقبل از ظهر/بعد از ظهر
Sفارغ دائمًا (الفارسية لا لاحقة ترتيبية فيها)
Wرقم الأسبوع بحسب ISO-8601 للحظة الميلادية الأساسية
cY-m-d\TH:i:sP مع التاريخ الجلالي
rسلسلة RFC 2822 للحظة الميلادية (يشترط RFC الأسماء الإنجليزية الميلادية)
U e T P p O Z I u vتُفوَّض إلى اللحظة الأساسية (الطابع الزمني، والمنطقة، والإزاحة، وأجزاء الثانية الدقيقة، ...)

الإزاحة والضبط#

كل دالة تعديل تُرجع نسخة جديدة؛ وتبقى المنطقة الزمنية محفوظة.

الدالةالسلوك
addDays() subDays()أيام كاملة على اللحظة الأساسية
addHours() addMinutes() addSeconds() وصيغ sub*زمن منقضٍ فعلي
addMonths() subMonths()أشهر تقويمية؛ ويُقصَّر اليوم إلى طول الشهر الهدف
addYears() subYears()مثل 12 شهرًا لكل سنة؛ و30 Esfand في سنة كبيسة يصير 29 Esfand في سنة عادية
startOfDay() endOfDay()00:00:00 و23:59:59 من اليوم نفسه
startOfMonth() endOfMonth()اليوم الأول 00:00:00؛ واليوم الأخير 23:59:59
startOfYear() endOfYear()1 Farvardin 00:00:00؛ وآخر يوم من Esfand عند 23:59:59
$x = Jalali::create(1403, 12, 30);              // آخر يوم في سنة كبيسة
echo $x->addDays(1);                            // 1404/01/01 00:00:00
echo $x->subDays(30);                           // 1403/11/30 00:00:00
echo $x->addHours(30);                          // 1404/01/01 06:00:00
echo $x->addYears(1);                           // 1404/12/29 00:00:00  (مقصَّر)
echo $x->subMonths(13);                         // 1402/11/30 00:00:00
echo Jalali::create(1404, 6, 31)->addMonths(1); // 1404/07/30 00:00:00  (مقصَّر)

$m = Jalali::create(1404, 7, 15, 14, 30, 5);
echo $m->startOfMonth();   // 1404/07/01 00:00:00
echo $m->endOfMonth();     // 1404/07/30 23:59:59
echo $m->startOfYear();    // 1404/01/01 00:00:00
echo $m->endOfYear();      // 1404/12/29 23:59:59

تجاوز النطاق المدعوم، أو الإزاحة بمقدار مبالغ فيه، يُطلق InvalidDateException بدل أن يلتف الحساب حول نفسه:

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

try { $x->addDays(PHP_INT_MAX); }
catch (InvalidDateException $e) { echo $e->getMessage(); }
// Cannot shift a date by 9223372036854775807 days: out of the supported range.

المقارنة والقياس#

تقارن دوال المقارنة (eq ne gt gte lt lte equals isBefore isAfter between isPast isFuture isToday) اللحظات، وتقبل أي CalendarDate أو DateTimeInterface. فلا تتساوى قيمتان إلا إذا كانتا اللحظة نفسها، وهذا يتوقف على المنطقة الزمنية لا على التاريخ المطبوع وحده.

$a = Jalali::create(1405, 1, 1, 0, 0, 0, $tehran);
var_dump($a->eq(new DateTimeImmutable('2026-03-21 00:00', $tehran))); // bool(true)
var_dump($a->between(Jalali::create(1404, 1, 1), Jalali::create(1406, 1, 1))); // bool(true)
الدالةالدلالة
diffInDays($other, $absolute = true)أيام كاملة، مقطوعة نحو الصفر. ومع false تكون الإشارة إشارة $this - $other
diffInMonths($other, $absolute = true)أشهر جلالية كاملة؛ ويُحتسب اليوم ووقت اليوم (من 15 Farvardin إلى 14 Tir شهران)
diffInYears($other, $absolute = true)سنوات جلالية كاملة، على أساس الذكرى السنوية
$jan = Jalali::create(1404, 1, 1);
$feb = Jalali::create(1404, 2, 1);
echo $jan->diffInDays($feb);                 // 31
echo $jan->diffInDays($feb, false);          // -31
echo Jalali::create(1404, 1, 15)->diffInMonths(Jalali::create(1404, 4, 14)); // 2
echo Jalali::create(1380, 5, 5)->diffInYears(Jalali::create(1404, 5, 4));    // 23
echo Jalali::create(1380, 5, 5)->diffInYears(Jalali::create(1404, 5, 5));    // 24

الدوال الساكنة المساعدة#

الدالةما تُرجعه
Jalali::isValid($y, $m, $d)bool؛ تُرجع false (ولا تُطلق استثناءً أبدًا) للسنوات أو الأشهر أو الأيام الخارجة عن النطاق
Jalali::isLeapYear($y)كبيسة إذا كان $y mod 33 أحد الأعداد 1 و5 و9 و13 و17 و22 و26 و30
Jalali::daysInYear($y)365 أو 366
Jalali::daysInMonth($y, $m)31 للأشهر من 1 إلى 6، و30 للأشهر من 7 إلى 11، و29 أو 30 لشهر Esfand؛ و0 لرقم شهر غير صالح
Jalali::gregorianToJalali($gy, $gm, $gd)[year, month, day]؛ ويجب أن تكون السنة الميلادية من 1 إلى 9999
Jalali::jalaliToGregorian($jy, $jm, $jd)[year, month, day]؛ يُقبل اليوم من 1 إلى 31 لأي شهر، ويتجاوز الفائض إلى ما بعده
echo implode(',', array_filter(range(1399, 1410), Jalali::isLeapYear(...))); // 1399,1403,1408
echo Jalali::daysInMonth(1403, 12), ' ', Jalali::daysInMonth(1404, 12);        // 30 29
echo Jalali::daysInYear(1403), ' ', Jalali::daysInYear(1404);                  // 366 365
echo json_encode(Jalali::gregorianToJalali(2026, 3, 21));                      // [1405,1,1]
echo json_encode(Jalali::jalaliToGregorian(1405, 1, 1));                       // [2026,3,21]

try { Jalali::gregorianToJalali(10000, 1, 1); }
catch (InvalidDateException $e) { echo $e->getMessage(); }
// Gregorian date out of the supported range (years 1-9999): 10000-1-1

ملاحظات عملية#

  • المناطق الزمنية. التواريخ المنشأة بلا منطقة تستخدم المنطقة الافتراضية في PHP. مرّر DateTimeZone إلى create()/make() كلما وجب ألا تعتمد النتيجة على إعدادات الخادم. وتُقرأ الساعة والدقيقة والتاريخ نفسه في منطقة النسخة.
  • حدود الأيام. يتغيّر اليوم عند منتصف الليل المدني في منطقة النسخة، كما في كل أجزاء هذه المكتبة.
  • الاستخدام بوصفها قيمة. النسخ تنفّذ Stringable، ويمكن مشاركتها وتخزينها في المصفوفات بأمان؛ ورتّبها بحسب getTimestamp().
  • ذو صلة. التحويل إلى التقويمات الأخرى والمقارنة بينها في التحويل والمقارنة بين التواريخ؛ وإضافة تكامل Carbon في ماكروات Carbon؛ والاطلاع على العطل الإيرانية في العطل.