شعار أوسكار أوسكار كل المقالات

التقويم الهجري في التطبيقات: أم القرى مقابل الحساب الجدولي

شكوى واحدة تكفي: «التاريخ الهجري في تطبيقكم غلط». المستخدمة تنظر إلى شاشة جوّالها وترى يوماً، وتنظر إلى تطبيقنا فترى يوماً آخر. الفارق يوم واحد. والنتيجة أنها لم تعد تثق بأي رقم آخر في التطبيق — لا بحساب الأسابيع، ولا بالمواعيد، ولا بأي شيء.

هذا أقسى ما في أخطاء التواريخ: الخطأ صغير جدّاً وأثره على الثقة كامل. من يشكّ في تاريخك يشكّ في حساباتك كلّها. وقد استغرقنا وقتاً حتى فهمنا أن المشكلة لم تكن خطأً في الحساب أصلاً — بل أننا نحسب تقويماً مختلفاً عن التقويم الذي تراه المستخدمة.

لماذا يوجد أكثر من تقويم هجري؟

التقويم الهجري قمري: الشهر يبدأ بالهلال. ومن هنا تنشأ أكثر من طريقة لتحديد بداية الشهر:

  • الحساب الجدولي: خوارزمية رياضية بحتة تفترض دورة ثابتة للسنوات الكبيسة، فتُنتج تقويماً منتظماً يمكن حسابه لأي تاريخ ماضٍ أو مستقبل ببضعة أسطر. مزيّته أنه محدّد ومتوقّع تماماً.
  • تقويم أم القرى: التقويم المعتمد رسمياً في المملكة العربية السعودية، ويقوم على معايير حسابية فلكية لإمكان الرؤية، لا على دورة رياضية منتظمة. وهو ما تعتمده الجهات الرسمية وما تعرضه أنظمة الجوّالات لمستخدمي المنطقة.

والفارق العملي بينهما ليس نظرياً: انزياح يوم واحد في أشهر معيّنة. يوم واحد لا يبدو شيئاً في وثيقة تقنية، وهو كافٍ تماماً ليقول المستخدم إن تطبيقك خاطئ.

كنّا نستخدم الحساب الجدولي. لم نكن مخطئين حسابياً — كنّا نجيب على سؤال لم يسأله أحد.

القرار: مرجعك هو شاشة الجوّال

القرار الذي حسم الأمر عندنا: المرجع ليس ورقة حساب صحيحة، بل ما يراه المستخدم على جهازه.

هذا قرار منتَج لا قرار هندسي. المستخدمة لا تقارن تطبيقك بمرجع فلكي؛ تقارنه بالتاريخ في أعلى شاشتها وبالتقويم على حاسوبها وبالتاريخ في المستندات الرسمية. فإن طابقتها فأنت صحيح عندها، وإن خالفتها فأنت خاطئ ولو كان حسابك سليماً.

قاعدة: في التعريب، «الصحيح» هو ما يطابق ما يراه المستخدم في بيئته الرسمية، لا ما يطابق أنقى حساب. اكتب هذا القرار في تعليق فوق دالتك، وإلا سيأتي من «يصلحه» بعد سنة.

التنفيذ: لا تحسبه بنفسك

المتصفّحات الحديثة تدعم التقويم الإسلامي بمعيار أم القرى مباشرة عبر واجهة التدويل. لا حاجة لمكتبة خارجية ولا لخوارزمية مكتوبة يدوياً:

new Intl.DateTimeFormat('en-US-u-ca-islamic-umalqura',
  { day: 'numeric', month: 'numeric', year: 'numeric' })
  .formatToParts(date)

الوسم -u-ca-islamic-umalqura هو ما يطلب هذا التقويم تحديداً. وانتبه أن قيماً أخرى موجودة وتعطي نتائج مختلفة — islamic وislamic-civil وislamic-tbla — وكلّها ليست ما تريده إن كان جمهورك سعودياً. اطلب islamic-umalqura صريحاً.

المصيدة الأولى: لماذا en-US لا ar؟

هذا يبدو غريباً في تطبيق عربي: نطلب الصيغة بالإنجليزية الأمريكية ثم نبني النصّ العربي بأنفسنا. السبب دقيق ومهم.

لو طلبت الصيغة بلغة عربية فقد تُعاد لك الأرقام بالمحارف العربية-الهندية (٤ ٥ ٦) بحسب المنطقة. وهذه المحارف لا يحلّلها parseInt — يُرجع لك NaN بلا أي خطأ. فتظهر في الواجهة قيمة فارغة أو NaN في تاريخ مستخدم بعينه ولا تستطيع تكرارها على جهازك.

طلب الصيغة بلغة تضمن الأرقام اللاتينية يجعل التحليل آمناً دائماً. ثم تستخدم formatToParts — لا format — فتحصل على اليوم والشهر والسنة كعناصر مسمّاة بدل نصّ واحد تقسمه بفواصل قد تتغيّر بين متصفّح ومنطقة:

if (p.type === 'day') hD = parseInt(p.value, 10)

بعدها تُركّب النصّ العربي بنفسك من مصفوفة أسماء الأشهر التي تتحكّم فيها. فتضمن التسمية والتنسيق واللاحقة بلا مفاجآت.

المصيدة الثانية: انزياح المنطقة الزمنية

هذه أخطر مصيدة في تواريخ الويب كلّها، وسببها أن كائن التاريخ يحمل لحظة زمنية لا يوماً. فتاريخ منتصف الليل بالتوقيت العالمي يصبح في منطقة زمنية سالبة اليوم السابق. فتحسب هجرياً لليوم الخطأ — وتظهر المشكلة عند بعض المستخدمين فقط بحسب مناطقهم.

الحلّ سطر واحد قبل أي تحويل:

d.setHours(12, 0, 0, 0)

تثبيت الوقت على منتصف النهار يجعل هامش الأمان اثنتي عشرة ساعة في كل اتجاه، فلا تنزلق أي منطقة زمنية معروفة إلى يوم مجاور. هذه الحيلة تنطبق على كل عملية تحوّل تاريخاً إلى «يوم» — لا على التقويم الهجري وحده.

تحذير: أي عيب في التواريخ يظهر «عند بعض المستخدمين فقط» هو منطقة زمنية حتى يثبت العكس. ولن تكرّره على جهازك أبداً إن كنت في منطقة موجبة — اختبر بتغيير منطقة نظامك، لا بتغيير التاريخ.

المصيدة الثالثة: لا تثق بالنتيجة قبل التحقّق

دعم التقويمات في واجهة التدويل ليس مضموناً في كل بيئة. متصفّح قديم أو نسخة مصغّرة من بيئة التشغيل قد لا تفشل صريحاً، بل تُرجع نتيجة بتقويم آخر أو أجزاءً ناقصة. أي أن كودك «يعمل» ويعطي تاريخاً خاطئاً.

لذلك نتحقّق من القيم الثلاث قبل استخدامها، ونرمي خطأً صريحاً إن نقصت واحدة:

if (!hD || !hM || !hY) throw new Error('umm al-qura unavailable')

هذا يحوّل فشلاً صامتاً إلى مسار معروف نتحكّم فيه — وهو نفس المبدأ الذي أنقذنا في تشخيص المصادقة: لا تبتلع الخطأ.

المصيدة الرابعة: الاحتياط ولماذا يجب أن يبقى

وقد يُسأل: إن كان الحساب الجدولي هو ما استبدلناه، فلماذا نُبقيه؟ لأن تاريخاً تقريبياً بانزياح يوم أفضل بكثير من لا تاريخ أو من رسالة خطأ في وجه المستخدم. فحين يفشل التحقّق نسقط إلى الخوارزمية الجدولية، التي تحسب بأعداد صحيحة من رقم اليوم اليوليوسي بلا أي اعتماد على المتصفّح:

const jd = Math.floor(d.getTime() / 86400000) + 2440588

الرقم 2440588 هو رقم اليوم اليوليوسي لبداية حساب الوقت في الأنظمة (أول يناير 1970)، والقاسم 86400000 هو عدد المللي ثانية في اليوم. من هذه النقطة تُطبَّق معادلات التقويم الجدولي بعمليات صحيحة فقط.

الترتيب الصحيح إذن: الأدقّ أولاً، والأضمن احتياطاً. ولا تعكس الترتيب طلباً للأمان — ستحصل على تطبيق مستقرّ يعطي التاريخ الخطأ للجميع.

التخزين بالميلادي والعرض بالهجري

قاعدة معمارية لا استثناء لها: خزّن التواريخ بالميلادي دائماً — ككائن تاريخ أو طابع زمني — وحوّل عند العرض فقط. لا تخزّن نصّاً هجرياً في قاعدة بياناتك أبداً.

الأسباب ثلاثة: الحسابات (الفروق، الترتيب، الاستعلامات بمدى) تعمل على الميلادي فقط؛ وتغيّر طريقة الحساب لاحقاً يجعل بياناتك المخزّنة خاطئة بلا إمكان تصحيح؛ وقواعد البيانات تفهرس الطوابع الزمنية لا النصوص. ومن حفظ «١٥ رمضان ١٤٤٧» نصّاً لن يستطيع أبداً أن يسأل «ما مضى ثلاثون يوماً على إدخاله؟».

تفاصيل العرض التي تُكمل الصورة

  • أسماء الأشهر بمصفوفة عندك، عربية وإنجليزية، فتضمن التسمية المتوقّعة في كل بيئة. وانتبه لصيغتَي «ذو القعدة» و«ذو الحجة» فبعض المصادر تكتبهما بصور مختلفة.
  • اللاحقة الصحيحة: «هـ» في العربية و«AH» في الإنجليزية. وجودها يُزيل اللبس فوراً عن أي رقم سنة.
  • الأرقام في العرض: اختر صيغة واحدة والتزم بها في كل التطبيق. الخلط بين الصيغتين في شاشة واحدة يبدو خللاً بصرياً حتى لو كانت القيم صحيحة.
  • اتجاه النصّ: التاريخ المركّب من أرقام وحروف داخل نصّ عربي قد ينقلب ترتيبه بصرياً. اعرض التاريخ في عنصر مستقلّ بدل دمجه وسط جملة طويلة.
  • اعرض التقويمين معاً حيث يهمّ الأمر — الميلادي بين قوسين بعد الهجري. أرخص طريقة لإزالة الشكّ من رأس المستخدم بلا أن يسألك.

أسئلة شائعة

هل أستخدم مكتبة تواريخ هجرية؟ لا حاجة في الويب — الدعم مبنيّ في المتصفّح، وأي مكتبة تضيف حجماً لملفّك بلا مقابل حقيقي. المكتبة تصبح مبرَّرة فقط إن احتجت عمليات حسابية معقّدة على التقويم الهجري نفسه لا مجرّد عرضه.

هل تتغيّر نتيجة أم القرى بين المتصفّحات؟ النتائج متقاربة جدّاً لأنها تستند إلى نفس بيانات التقويم، لكن الفروق النادرة ممكنة بين إصدارات بيانات التدويل. لهذا يبقى التحقّق من القيم ومسار الاحتياط ضروريين.

ماذا عن حساب مدد تعتمد على التقويم الهجري؟ احسب دائماً بالميلادي ثم اعرض بالهجري. حساب فرق الأيام على أرقام هجرية يورّطك في اختلاف طول الشهر بين 29 و30 يوماً بطريقة غير منتظمة.

وفي تطبيقات الجوّال الأصلية؟ نفس المبدأ بأدوات مختلفة: استخدم واجهة التقويم الهجري في منصّتك واطلب معيار أم القرى صريحاً، ولا تكتب الخوارزمية بنفسك إلا كمسار احتياطي.

خلاصة

لم يكن خطؤنا حسابياً، بل كنّا نحسب تقويماً آخر. الحساب الجدولي صحيح رياضياً ويخالف ما تراه المستخدمة، وتقويم أم القرى هو ما يطابق شاشتها ومستنداتها الرسمية.

والوصفة المختصرة: اطلب islamic-umalqura صريحاً، بلغة تضمن الأرقام اللاتينية، عبر formatToParts لا نصّاً تقسمه، مع تثبيت الوقت على الظهر قبل التحويل، وتحقّق من القيم قبل الثقة بها، واحتفظ بالخوارزمية الجدولية احتياطاً لا أصلاً. وخزّن بالميلادي دائماً واعرض بالهجري.

والدرس الأوسع: في التعريب، القرار الصحيح ليس الأدقّ نظرياً بل الأقرب لما يعرفه المستخدم.

التواريخ ليست وحدها ما يُفقد الثقة — أي رقم يخالف توقّع المستخدم يفعل ذلك: مبادئ تجربة المستخدم عملياً →