اختبار التاريخ والوقت

اختبار التاريخ والوقت (Date and time testing) يتحقق من أن البرنامج يتصرف صحيحاً في اللحظات التي يجعلها التقويم والساعة مميزة: حين تنتهي فترة تجريبية أو رمز وصول، وحين يتجدد اشتراك، وحين تعمل مهمة مجدولة، وحين يبلغ المستخدم 18 عاماً، وحين ينتهي شهر أو سنة أو فترة توقيت صيفي. الساعة مُدخل مثل أي حقل في نموذج، لكن أحداً لا يكتبه، لذا لا تختبر معظم الفرق إلا القيمة الوحيدة التي كانت للساعة يوم تشغيل الاختبارات. واختبار التواريخ المستقبلية (Future date testing)، ويُسمّى أيضاً محاكاة التاريخ (Date simulation) أو اختبار السفر عبر الزمن (Time-travel testing)، يجعل هذا المُدخل قابلاً للتحكم: تضبط الساعة، أو تزيح التواريخ داخل البيانات، إلى اللحظات المهمة، وتتحقق من الحالة المتوقعة عند كل منها.

يشرح هذا الدليل كيف تجعل الوقت قابلاً للتحكم في الكود وفي بيئات الاختبار، وكيف تختار بين تحريك الساعة وإزاحة بيانات الاختبار، وأي الحالات الحدّية في التقويم والمناطق الزمنية والطوابع الزمنية تغطي، وكيف تجمّد الوقت في Playwright وJest وVitest وJava وPython و⁦.NET⁩، ثم يختم بمثال تطبيقي لفترة تجريبية مدتها 30 يوماً وقائمة تحقق جاهزة للنسخ. أما طريقة عرض التواريخ والأرقام والعملات في كل إعداد محلي فيشرحها درس «التواريخ والأرقام والعملات والمناطق الزمنية» في خارطة طريق ضمان الجودة؛ وهذا الدليل يتناول السلوك عبر الزمن.

ما الذي يغطيه اختبار التاريخ والوقت

كل قاعدة تذكر مدة أو موعداً نهائياً أو وحدة من وحدات التقويم تقرأ الساعة في مكان ما، وهناك تختبئ عيوبها. فالساعة مُدخل خفي: ينجح الاختبار يوم كتابته، ويظهر الإخفاق بعد 30 يوماً، أو في 29 فبراير، أو في ليلة تغيير الساعة، أو في 1 يناير حين لا يراقب أحد. وتفلت عيوب الوقت أيضاً لأن لكل بيئة ساعتها ومنطقتها الزمنية — حاسوب في الرياض، وخادم CI بتوقيت UTC، وخادم إنتاج في مكان آخر — ولأن النتيجة المتوقعة تعتمد على وقت تشغيل الاختبار، فينتهي كثير من هذه الفحوص دون قيمة متوقعة ثابتة أصلاً.

واختبار التاريخ والوقت ليس مستوى اختبار مستقلاً، بل عائلة من شروط الاختبار — لحظات زمنية — تُطبَّق على اختبارات الواجهة واختبارات API والمهام الدفعية والتقارير. والتقنية المستخدمة هي تحليل القيم الحدّية (Boundary value analysis): لكل قاعدة زمنية حدود، والقيم المهمة تقع قبل كل حد مباشرة وعنده تماماً وبعده مباشرة. والساعة مُدخل حيثما وُجد في المنتج:

  • الانتهاء — الفترات التجريبية وكلمات المرور والجلسات ورموز الوصول والقسائم وعروض الأسعار ورموز التحقق لمرة واحدة (OTP) والروابط الموقّعة والشهادات.
  • التجديد والفوترة — الاشتراكات والفواتير وفترات السماح ومتابعة المتأخرات والغرامات والفوائد التي تُحتسب يومياً.
  • الجدولة — مهام cron والتذكيرات والإشعارات والتقارير، ومهام الاحتفاظ بالبيانات وحذفها بعد عدد من الأيام.
  • الأهلية — حدود العمر (هل يبلغ 18 عاماً اليوم أم غداً؟) وفترات التجربة الوظيفية ومهل التراجع والاسترداد وفترات الضمان.
  • التقارير — «اليوم» و«هذا الأسبوع» و«هذا الشهر» والفترات المالية وكل ما يُجمَّع حسب اليوم، وكلها تعتمد على المنطقة الزمنية.
  • الترتيب والسجل — سجلات التدقيق و«أحدث» السجلات والفرز حسب created_at والمعرّفات المرتّبة زمنياً.

جرّبها: تحليل القيم الحدّية

اجعل الوقت قابلاً للتحكم: احقن الساعة

أكثر تغيير يرفع قابلية الاختبار هو التوقف عن قراءة «الآن» في عمق الكود. فحين يستدعي منطق الأعمال ⁦Date.now()⁩ أو ⁦Instant.now()⁩ أو DateTime.UtcNow أو ⁦datetime.now()⁩ مباشرة، تكون الطريقة الوحيدة لاختباره في لحظة أخرى هي تغيير ساعة الجهاز كله. أما حين يتلقى المنطق ساعة — واجهة صغيرة تعيد اللحظة الحالية — فيستطيع الاختبار أن يمرر ساعة ثابتة مضبوطة على ⁦2026-03-02T06:59:59Z⁩، بينما يمرر الإنتاج ساعة النظام. هذا هو حقن الساعة (Clock injection): في Java توجد java.time.Clock مع Clock.fixed وClock.offset منذ Java 8، ويوصي توثيقها بتمرير Clock إلى كل دالة تحتاج اللحظة الحالية؛ وأضافت ⁦.NET 8⁩ الصنف System.TimeProvider ومعه FakeTimeProvider للاختبارات؛ وفي JavaScript وPython تؤدي دالة صغيرة أو معامل الغرض نفسه.

وفي الاختبار الشامل والاختبار اليدوي ترتفع الفكرة نفسها مستوى: إعداد «سفر عبر الزمن» (Time travel) خاص ببيئات الاختبار. تقرأ البنية غير الإنتاجية إزاحةً للساعة أو لحظة ثابتة من الإعدادات، أو من نقطة إدارة لا توجد إلا في بيئات الاختبار، فيستطيع المختبِر أن يقول «هذا الحساب يعيش في 2 مارس 2026» دون أن يلمس ساعة الخادم. ويجب عندئذ أن يستخدم التطبيق هذه الساعة في كل مكان — طبقة الويب، والمهام الخلفية، واستعلامات قاعدة البيانات التي تستدعي ⁦NOW()⁩ أو CURRENT_TIMESTAMP — وإلا اختلفت أجزاؤه على اليوم الذي نحن فيه.

أما تغيير ساعة جهاز أو عملية (إزاحة الساعة على مستوى البيئة) فما زال له مكانه، للأنظمة القديمة وللبرامج التي لا تستطيع تعديلها، لكن له آثاراً جانبية يجب أن يخطط لها الاختبار:

  • TLS: تُفحص صلاحية الشهادة مقابل الساعة المحلية، فالساعة التي تُقدَّم إلى ما بعد انتهاء شهادة، أو تُرجَع إلى ما قبل بدايتها، تجعل طلبات HTTPS تفشل بأخطاء شهادات لا علاقة لها بالميزة المختبَرة.
  • الرموز والتواقيع: رمز JWT أو رمز OAuth أو رابط موقّع أصدرته خدمة بالساعة الحقيقية يبدو منتهياً، أو غير صالح بعد، لمكوّن ساعته مُزاحة.
  • مزامنة الوقت: عملاء NTP (مثل chrony وsystemd-timesyncd وخدمة Windows Time) يعيدون الساعة إلى الوقت الحقيقي، أحياناً في منتصف الاختبار؛ أوقفها على الجهاز المختبَر وأعد تشغيلها بعد ذلك.
  • البيئات المشتركة: ساعة النظام ملك لكل من على الخادم، فسفر مختبِر واحد عبر الزمن يكسر اختبارات الجميع. والحاويات تشارك المضيف ساعته الفعلية، لأن نطاقات الوقت (Time namespaces) في لينكس لا تفصل إلا الساعة الرتيبة (Monotonic) وساعة الإقلاع (Boottime)، لا CLOCK_REALTIME.
  • بيانات متروكة في المستقبل: السجلات التي أُنشئت والساعة متقدمة تحتفظ بطوابعها الزمنية المستقبلية بعد عودة الساعة، فينكسر الفرز واستعلامات «الأحدث» والمعرّفات المرتّبة زمنياً؛ أعد ضبط البيانات مع الساعة.
  • تشغيلات التعويض: تقديم الساعة أياماً قد يجعل المجدوِل يشغّل كل المهام الفائتة دفعة واحدة، أو يتخطاها كلها، بحسب المجدوِل.

اختبار التواريخ المستقبلية: الحدود والحالات المتوقعة

اختبار التواريخ المستقبلية، ويُسمّى أيضاً Forward date testing، يتحقق مما يفعله النظام حين يمضي الوقت: انتهت الفترة التجريبية، وتأخرت الفاتورة، وبلغ المستخدم 18 عاماً، وانقضت مدة الاحتفاظ بالبيانات. والطريقة واحدة لكل قاعدة: اكتب القاعدة لحظةً حدّية، واذكر اللحظات المحيطة بها، ودوّن الحالة المتوقعة عند كل لحظة قبل أن تشغّل أي شيء.

الدقة تحدد مقدار «قبل مباشرة». فإن كان النظام يخزّن الثواني فاختبر قبل الحد بثانية وعنده تماماً وبعده بثانية؛ وإن كان يخزّن أجزاء الألف من الثانية فقبله بجزء من الألف يهمّ أيضاً. وقد ينقل التخزين لحظةً إلى الحد نفسه: تقرّب MySQL القيمة ذات الكسور الثانية حين تُدرج في عمود بخانات كسرية أقل، والدقة الافتراضية لعمودي DATETIME وTIMESTAMP هي 0، فالحدث الواقع عند 06:59:59.700 يُخزَّن على أنه 07:00:00، أي على الحد تماماً، فيُعدّ أنه وقع بعد انتهاء الفترة التجريبية. اختبر قيماً تنتهي بـ ⁦.499⁩ و⁦.500⁩ قبل الحد مباشرة.

ثم احسم مع مالك المنتج هل الحد شامل أم غير شامل (Inclusive أو Exclusive)، لأن «صالح حتى 2 مارس» يحتمل ثلاث قراءات على الأقل: حتى بداية 2 مارس، أو حتى نهايته، أو حتى اللحظة التي يبلغ فيها عمر الشراء 30 يوماً — وبأي منطقة زمنية؟ والمعايير تجعل الاختيار صريحاً: يجب رفض رمز JWT بمجرد ألا يعود الوقت الحالي قبل exp، وقبوله ابتداءً من nbf (RFC 7519). اكتب القاعدة المختارة في معايير القبول على شكل مقارنة، مثل ⁦active while now < end⁩.

  • قبل كل حد وعنده وبعده: ⁦-1 s⁩ وتماماً و⁦+1 s⁩ (أو ⁦-1 ms⁩ إن كانت أجزاء الألف مخزّنة).
  • فجوات طويلة: المستخدم يعود بعد 400 يوم؛ مهمة لم تعمل أسبوعاً؛ موعد تذكير كان قد مضى حين أُنشئ السجل.
  • ترتيب الأحداث: تجديد وإلغاء في اليوم نفسه؛ دفعة تصل بعد الانتهاء بثانية؛ طلب استرداد في آخر يوم من المهلة.
  • القيم المشتقة: عدّادات «الأيام المتبقية» وأشرطة التقدم ورسائل البريد التي تقول «تنتهي غداً»، تُفحص عند 23:59 و00:00 في منطقة المستخدم.
  • الآثار الخلفية: الحساب منتهٍ للطلبات عند الحد، لكن المهمة الليلية التي تخفّض خطته تعمل لاحقاً؛ تحقق مما يراه المستخدم بينهما.
  • التواريخ الماضية مُدخلاً: سجلات بتاريخ سابق، واستيراد بيانات تاريخية، وتاريخ بداية في الماضي يجعل تاريخ النهاية قبل اليوم.

جرّبها: محاكي اختبار التاريخ والوقت جرّبها: تحليل القيم الحدّية جرّبها: حاسبة التواريخ

محاكاة التاريخ أم إزاحة التواريخ: حرّك الساعة أم البيانات

هناك طريقتان لنقل النظام إلى المستقبل. محاكاة التاريخ (Date simulation)، أو السفر عبر الزمن، تحرّك الساعة التي يقرؤها التطبيق — بساعة محقونة أو بإعداد سفر عبر الزمن أو بساعة نظام وهمية — وتترك البيانات كما هي. أما إزاحة التواريخ (Date shifting) فتحرّك البيانات: يُزاح كل تاريخ في مجموعة بيانات الاختبار بالمقدار نفسه، أو يُكتب نسبةً إلى مرجع مثل «اليوم ناقص 29 يوماً»، فتبقى العلاقات بين التواريخ صحيحة — 29 يوماً منذ الشراء، و3 أيام حتى التجديد — أياً كان يوم تشغيل الحزمة، ومع الساعة الحقيقية.

الإزاحة تحفظ الفترات لا مواضع التقويم. أزِح كل تاريخ 30 يوماً إلى الأمام فيقع شراء 31 يناير في 2 مارس: تبقى الأيام الـ 29 حتى نهاية الفترة التجريبية، لكن نهاية الشهر ويوم الأسبوع ويوم 29 فبراير لا تبقى، ما لم تختر المقدار عمداً (مضاعفات 7 تحفظ أيام الأسبوع؛ والعدد الصحيح من السنوات يحفظ الشهر واليوم لكنه لا يحفظ أيام الأسبوع ولا الأيام الكبيسة). ويجب أن يتحرك كل حقل مشتق مع مصدره: الأعمار، وتواريخ الاستحقاق، وأرقام أسابيع ISO، والفترات المالية، ومفاتيح التقسيم، وأي تجزئة (Hash) أو توقيع محسوب على تاريخ.

  • استخدم محاكاة التاريخ للسلوك المرتبط بتقويم بعينه: نهايات الأشهر، و29 فبراير، ونهاية السنة، ويوم تغيير التوقيت الصيفي، وتغيير قواعد tz، وحد 2038، وكل ما تعتمد نتيجته المتوقعة على التاريخ الدقيق.
  • استخدم إزاحة التواريخ للقواعد النسبية، في البيئات المشتركة وفي التشغيل المتوازي: «ينتهي بعد 30 يوماً من الشراء»، و«ذكّر قبل التجديد بثلاثة أيام»، و«أرشف بعد 90 يوماً». لا تُلمس أي ساعة، فلا تتأثر TLS ولا الرموز ولا المختبِرون الآخرون ولا NTP.
  • اجمع بينهما: بيانات مُزاحة لمعظم حزمة اختبار الانحدار، ومجموعة صغيرة من اختبارات الساعة المحاكاة مثبّتة على لحظات تقويمية محددة.
  • في الطريقتين تحتفظ الأنظمة الخارجية بساعاتها: بيئة الاختبار لدى مزوّد الدفع، ومزوّد الهوية، وخادم البريد لن تشاركك تاريخك المحاكى ولا بياناتك المُزاحة.
-- Date shifting (PostgreSQL): move a whole data set 30 days forward,
-- so every relative age stays the same; shift derived columns with it
UPDATE subscriptions
   SET signed_up_at = signed_up_at + INTERVAL '30 days',
       trial_end    = trial_end    + INTERVAL '30 days',
       renews_on    = renews_on    + 30;   -- renews_on is a DATE: + 30 adds days

// Relative fixtures instead of fixed dates (JavaScript)
const DAY = 24 * 60 * 60 * 1000;
const now = clock.now();                // injected clock, milliseconds since the epoch
const account = { signedUpAt: new Date(now - 29 * DAY) };   // 29 days ago
// expected: trial active, "1 day left"

جرّبها: مولّد نطاقات التواريخ جرّبها: حاسبة الفرق بين التواريخ

حالات التقويم الحدّية: نهايات الأشهر والسنوات الكبيسة والأسابيع

حساب الأشهر هو حيث تختلف المكتبات. فإضافة شهر إلى 31 يناير ليس لها جواب صحيح واحد، وكل مكتبة تختار جواباً. الكائن Date في JavaScript يفيض (Overflow): استدعاء setMonth على 31 يناير 2026 يطلب 31 فبراير، فينقلب إلى 3 مارس 2026 (ومثال MDN نفسه يحوّل 31 يناير 2016 إلى 2 مارس 2016). أما LocalDate.plusMonths في Java فتقصّ (Clamp) إلى آخر يوم صالح، فيصبح ⁦2026-01-31⁩ زائد شهر هو ⁦2026-02-28⁩، والدالة add في واجهة Temporal تقصّ بالطريقة نفسها افتراضياً (⁦overflow: "constrain"⁩)، أو ترمي RangeError مع ⁦overflow: "reject"⁩. وفي التجديد «الشهري» الذي يقع يوم 31 ينجرف السلوكان كلاهما: الفيض يدفع كل تجديد إلى الشهر التالي (3 مارس، ثم 3 أبريل، ثم 3 مايو)، والقص المطبَّق على التجديد السابق ينزلق إلى يوم 28 (28 فبراير، ثم 28 مارس، ثم 28 أبريل). ولا تُحفظ نهايات الأشهر إلا حين يُحسب كل تجديد من التاريخ الأصلي، أي البداية زائد n شهراً (28 فبراير، ثم 31 مارس، ثم 30 أبريل). ويجب أن تحدد المتطلبات أي قاعدة تنطبق، وأن تغطي الاختبارات الأيام 29 و30 و31 وعدة تجديدات متتالية؛ وأداة محاكاة اختبار التاريخ والوقت (Date and Time Test Simulator) تعرض هذه الجداول جنباً إلى جنب.

«30 يوماً» و«شهر واحد» قاعدتان مختلفتان: من 31 يناير 2026 تنتهي الثلاثون يوماً في 2 مارس، والشهر المقصوص في 28 فبراير. وتتبع السنوات الكبيسة القاعدة الغريغورية: السنة التي تقبل القسمة على 4 كبيسة، إلا سنوات القرون فلا تكون كبيسة إلا إذا قبلت القسمة على 400 — فسنتا 2028 و2000 كبيستان، و2100 ليست كبيسة. ومن وُلد في 29 فبراير فيوم ميلاده لا يوجد إلا كل أربع سنوات، وعلى المنتج أن يقرر هل يبلغ 18 عاماً في 28 فبراير أم في 1 مارس في السنوات الأخرى.

وللأسابيع تقويمها الخاص. في ISO 8601 يبدأ الأسبوع يوم الاثنين، والأسبوع الأول هو الذي يحتوي أول خميس في السنة، فتكون السنة 52 أو 53 أسبوعاً، وقد تنتمي أيام يناير الأولى إلى السنة السابقة: في سنة 2026 ثلاثة وخمسون أسبوعاً بحسب ISO، ويقع 1 يناير 2027 في الأسبوع 53 من 2026. وأنماط التنسيق تتعثر هنا: في DateTimeFormatter في Java يعني YYYY سنة الأسبوع ويعني yyyy السنة التقويمية، فيطبع النمط YYYY-MM-dd يوم 31 ديسمبر 2026 على أنه ⁦2027-12-31⁩ في الإعداد المحلي en-US الذي يبدأ أسبوعه يوم الأحد.

One rule, three libraries: add 1 month to 31 January 2026

JavaScript Date   d = new Date(2026, 0, 31); d.setMonth(d.getMonth() + 1)
                  -> 3 March 2026   (31 February overflows into March)
Java              LocalDate.of(2026, 1, 31).plusMonths(1)
                  -> 2026-02-28     (clamped to the last valid day)
Temporal          Temporal.PlainDate.from("2026-01-31").add({ months: 1 })
                  -> 2026-02-28     (overflow: "constrain", the default)
                  ...add({ months: 1 }, { overflow: "reject" })
                  -> RangeError
"30 days" instead of "1 month"
                  -> 2026-03-02

Monthly renewal from 31 January 2026, next three renewals
overflow, from the previous renewal (JS setMonth)     2026-03-03, 2026-04-03, 2026-05-03
clamp, from the previous renewal (Java plusMonths)   2026-02-28, 2026-03-28, 2026-04-28
clamp, from the original date (start + n months)     2026-02-28, 2026-03-31, 2026-04-30

Dates every date test data set should contain
2026-01-29 .. 2026-01-31    monthly rules on the 29th, 30th and 31st
2026-02-28, 2026-03-01      end of a short February
2028-02-29, 2028-03-01      leap day and the day after
2100-02-28                  2100 is not a leap year (no 29 February)
2026-12-31, 2027-01-01      year end; both fall in ISO week 2026-W53
2025-12-29                  already in ISO week 2026-W01
2026-12-27 .. 2026-12-31    Java pattern YYYY-MM-dd prints 2027 in en-US

جرّبها: محاكي اختبار التاريخ والوقت جرّبها: مولّد بيانات اختبار التواريخ جرّبها: حاسبة التواريخ

المناطق الزمنية والتوقيت الصيفي وتغييرات قاعدة tz

خزّن اللحظات بتوقيت UTC أو بفارق صريح، واحفظ منطقة المستخدم باسمها في IANA مثل Europe/Berlin أو Asia/Riyadh، لا بفارق ثابت: فالفارق حقيقة عن لحظة واحدة، والمنطقة مجموعة قواعد تتغير مع الزمن. وحوّل إلى الوقت المحلي عند الأطراف فقط. يشرح درس «التواريخ والأرقام والعملات والمناطق الزمنية» في خارطة الطريق طريقة العرض؛ أما السلوك عبر الزمن فتأتي عيوبه من ثلاثة مصادر.

الأول: التوقيت الصيفي (DST) يصنع أوقاتاً محلية غير موجودة وأوقاتاً تحدث مرتين. في Europe/Berlin يوم 29 مارس 2026 تقفز الساعة من 02:00 إلى 03:00، فلا وجود للساعة 02:30 (فجوة، Gap)؛ وفي 25 أكتوبر 2026 تعود من 03:00 إلى 02:00، فتحدث 02:30 مرتين، عند ⁦00:30Z⁩ وعند ⁦01:30Z⁩ (تداخل، Overlap). وفي America/New_York يقع التغييران في 8 مارس 2026 (فجوة ⁦02:00–03:00⁩) و1 نوفمبر 2026 (تداخل ⁦01:00–02:00⁩). وتحسم المكتبات هذه الحالات بقاعدة: ZonedDateTime في Java وخيار الحسم الافتراضي "compatible" في Temporal يدفعان الوقت الواقع في الفجوة إلى الأمام (02:30 تصبح 03:30) ويختاران الفارق الأبكر في التداخل، ويمكن أيضاً أن يُطلب من Temporal رفض الحالتين.

والمهمة اليومية المجدولة الساعة 02:30 بالتوقيت المحلي تحتاج إلى سلوك معلن في اليومين — تعمل 03:00، أو 03:30، أو تُتخطى، أو تعمل مرة واحدة لا مرتين. وcronie، خادم cron في كثير من توزيعات لينكس، يشغّل مهام الفترة المتخطاة فور التغيير ويتجنب تشغيلها مرتين في الفترة المكررة، إذا كان تغيير الساعة أقل من ثلاث ساعات؛ والمجدوِلات الأخرى تختلف، فاختبر المهمة لا التعبير وحده. والمدد تختلف أيضاً: 30 يوماً تقويمياً من 10 مارس 2026 الساعة 10:00 في Europe/London تنتهي الساعة 10:00 بالتوقيت الصيفي البريطاني (BST) يوم 9 أبريل، أما 720 ساعة فتنتهي الساعة 11:00، لأن الساعة تقدمت يوم 29 مارس.

الثاني: المناطق التي لا تطبق التوقيت الصيفي تُخفي كل هذا. فالمنطقة Asia/Riyadh هي UTC+03:00 طوال السنة بلا أي تغيير، ولن يرى فريق في الرياض فجوة أو تداخلاً ما لم يختبر منطقة فيها ذلك. وأضف فروقاً ليست ساعات كاملة — Asia/Kolkata عند ⁦+05:30⁩ وAsia/Kathmandu عند ⁦+05:45⁩ — لتكشف الكود الذي يخزّن الفارق عدداً صحيحاً من الساعات.

الثالث: القواعد نفسها تتغير، وتسجّل قاعدة بيانات المناطق الزمنية (tz database أو tzdata) كل تغيير في إصدار جديد. توقفت المكسيك عن تطبيق التوقيت الصيفي بعد 2022 إلا قرب الحدود الأمريكية (الإصدار 2022f)، فصارت America/Mexico_City عند ⁦UTC-06:00⁩ طوال السنة منذ 30 أكتوبر 2022. وتوقفت باراغواي عن تغيير ساعاتها بعد تقديمها في 6 أكتوبر 2024 وبقيت عند ⁦UTC-03:00⁩ (الإصدار 2025a، يناير 2025)، فالمنطقة America/Asuncion عند ⁦-03⁩ طوال 2026. وتنتقل مانيتوبا (America/Winnipeg) إلى ⁦-05⁩ بصورة دائمة في 31 أكتوبر 2026 (الإصدار 2026e، 29 سبتمبر 2026). وبيئة التشغيل التي تحمل بيانات أقدم تحسب بالقواعد القديمة: فـ JDK 22.0.2 مع بياناته المضمّنة 2024a يحوّل ⁦2026-07-01T12:00Z⁩ إلى 08:00 بفارق ⁦-04:00⁩ في أسونسيون بدلاً من 09:00 بفارق ⁦-03:00⁩. اختبر على إصدار tzdata الذي تنشره فعلاً، في كل بيئة تشغيل — نظام التشغيل وJVM وNode.js مع ICU والمتصفح وقاعدة البيانات — وأضف اختباراً يفشل إن كان التحويل لمنطقة تغيرت قواعدها مؤخراً خاطئاً.

Daylight saving changes in 2026: what a 02:30 local time becomes

Zone              Day      Clocks           02:30 local time
Europe/Berlin     29 Mar   02:00 -> 03:00   does not exist (gap); java.time and Temporal
                                            "compatible" resolve it to 03:30 +02:00
Europe/Berlin     25 Oct   03:00 -> 02:00   happens twice (overlap):
                                            00:30Z at +02:00 and 01:30Z at +01:00
America/New_York  8 Mar    02:00 -> 03:00   does not exist; resolved to 03:30 -04:00
America/New_York  1 Nov    02:00 -> 01:00   happens once; 01:00-01:59 happens twice
Europe/London     29 Mar   01:00 -> 02:00   normal; 01:00-01:59 does not exist
Asia/Riyadh       none     +03:00 all year  always exists, exactly once

30 calendar days vs 720 hours across a change (Europe/London)
2026-03-10 10:00 GMT + 30 days = 2026-04-09 10:00 BST (09:00Z)
2026-03-10 10:00 GMT + 720 h   = 2026-04-09 11:00 BST (10:00Z)

Zones whose rules changed recently (tzdata release that records it)
America/Mexico_City  -06:00 all year since 2022-10-30 (2022f)
                     2026-07-01T12:00Z -> 06:00; older data: 07:00 -05:00
America/Asuncion     -03:00 all year since 2024-10-06 (2025a)
                     2026-07-01T12:00Z -> 09:00; older data (e.g. JDK 22.0.2
                     with tzdata 2024a): 08:00 -04:00
America/Winnipeg     permanent -05:00 from 2026-10-31 (2026e), no fall-back on 1 Nov
                     2026-12-01T12:00Z -> 07:00; older data: 06:00 -06:00

جرّبها: محوّل المناطق الزمنية جرّبها: شارح تعابير cron

الطوابع الزمنية: وحدات الحقبة ومشكلة 2038 والثواني الكبيسة والصيغ

وقت Unix يعدّ الثواني منذ ⁦1970-01-01T00:00:00Z⁩؛ أما JavaScript وكثير من واجهات API فتعدّ أجزاء الألف من الثانية. والخلط بين الوحدتين سهل الاكتشاف حين تعرف علامته: القيمة 1772434800 (2 مارس 2026 الساعة ⁦07:00Z⁩ بالثواني) إذا قُرئت بأجزاء الألف صارت 21 يناير 1970، واللحظة نفسها بأجزاء الألف إذا قُرئت ثوانيَ وقعت في السنة 58136. ومطالبات JWT مثل exp وnbf وiat قيم من نوع NumericDate بالثواني (RFC 7519)، فالرمز الذي يحمل exp بأجزاء الألف يبدو صالحاً لعشرات آلاف السنين.

ومشكلة سنة 2038 هي حد العدّ بعدد صحيح ذي إشارة من 32 بتاً للثواني: أكبر قيمة 2147483647 هي ⁦2038-01-19T03:14:07Z⁩، وبعدها بثانية ينقلب العدّاد إلى ⁦-2147483648⁩، أي ⁦1901-12-13T20:45:52Z⁩. وليست مشكلة بعيدة بالنسبة إلى الاختبارات: فعقد مدته 15 عاماً، أو رهن عقاري، أو شهادة، أو تاريخ رمزي يعني «لا ينتهي أبداً» يتجاوزها منذ الآن. فنوع TIMESTAMP في MySQL مثلاً ينتهي مداه عند ⁦2038-01-19 03:14:07⁩ UTC، بينما يصل DATETIME إلى ⁦9999-12-31⁩. اختبر تاريخاً بعد 19 يناير 2038 في كل حقل يخزّن تاريخاً مستقبلياً، عبر كل طبقة — API وقاعدة البيانات والتصدير وتطبيقات الجوال.

والثواني الكبيسة (Leap seconds) لا وجود لها في وقت Unix: يعرّف POSIX كل يوم بأنه 86400 ثانية بالضبط، ويتجاهلها الكائن Date في JavaScript، ولا يسمح RFC 3339 بالقيمة 60 للثواني إلا حيث تقع ثانية كبيسة فعلاً. لا تحاول اختبارها داخل التطبيق؛ بل تحقق من أن المُدخل الذي يحوي ⁦:60⁩ يُرفض أو يُعالج كما تقول المتطلبات، ومن أن المدد تُقاس بساعة رتيبة (Monotonic clock) لا بالفرق بين قراءتين من ساعة الحائط.

الصيغ: تبادل اللحظات بصيغة RFC 3339، وهي ملف ISO 8601 للإنترنت — ⁦2026-03-02T07:00:00Z⁩ أو ⁦2026-03-02T10:00:00+03:00⁩، دائماً مع Z أو فارق. فالنص ⁦03/04/2026⁩ يعني 4 مارس في الولايات المتحدة و3 أبريل في معظم الأماكن الأخرى، ولا يضمن الكائن Date في JavaScript إلا صيغته المبنية على ISO 8601؛ أما النصوص الأخرى فيحلّلها المحرك كما يشاء. وحتى صيغ ISO فيها فخ: في JavaScript يُحلَّل النص الذي يحوي التاريخ وحده مثل ⁦2026-03-04⁩ بتوقيت UTC، بينما يُحلَّل التاريخ مع الوقت دون فارق مثل ⁦2026-03-04T00:00⁩ بالتوقيت المحلي. وفي PostgreSQL يتجاهل النوع timestamp without time zone أي فارق في المُدخل بصمت، بينما يحوّله النوع timestamp with time zone إلى UTC.

One instant, five encodings
RFC 3339, UTC           2026-03-02T07:00:00Z
RFC 3339, Riyadh        2026-03-02T10:00:00+03:00
Unix seconds            1772434800
Unix milliseconds       1772434800000
JWT "exp" (seconds)     1772434800

Unit mix-ups and what they look like
seconds read as ms      1772434800    -> 1970-01-21T12:20:34.800Z
ms read as seconds      1772434800000 -> a date in the year 58136

Signed 32-bit seconds (the 2038 problem)
 2147483647             2038-01-19T03:14:07Z   largest value
-2147483648             1901-12-13T20:45:52Z   one second later, after wrap-around
MySQL TIMESTAMP         up to 2038-01-19 03:14:07 UTC
MySQL DATETIME          up to 9999-12-31 23:59:59

Ambiguous or surprising input
03/04/2026              4 March (en-US) or 3 April (en-GB)
2026-03-04              JavaScript: midnight UTC (date-only form)
2026-03-04T00:00        JavaScript: midnight local time (date-time, no offset)
2026-03-04 10:00+03     PostgreSQL timestamp without time zone: offset ignored

جرّبها: محوّل طوابع Unix الزمنية جرّبها: محوّل ISO 8601 جرّبها: فاحص انتهاء صلاحية JWT

كيف تجمّد الوقت أو تزيحه في Playwright وJest وJava وPython و⁦.NET⁩

لكل منظومة شائعة طريقة مدعومة للتحكم في الوقت أثناء الاختبار. استخدمها في المستوى الذي يُتخذ فيه القرار: ساعة المتصفح لا تحرّك الخادم، والساعة الوهمية في الخادم لا تغيّر ما يعرضه المتصفح.

  • Playwright (المتصفح): يزيّف page.clock الكائن Date والمؤقّتات وperformance داخل الصفحة. تجمّد setFixedTime قيمتي ⁦Date.now()⁩ و⁦new Date()⁩ مع استمرار المؤقّتات؛ ويجب أن يسبق ⁦install({ time })⁩ أي استدعاء آخر للساعة، وهو الذي يتيح fastForward وrunFor وpauseAt وresume؛ أما setSystemTime فتغيّر الوقت دون إطلاق المؤقّتات، لمحاكاة تغيير الساعة. واجمعها مع الخيار timezoneId لتشغيل المتصفح في منطقة زمنية محددة.
  • Jest وVitest (اختبارات الوحدة في JavaScript): تستبدل ⁦useFakeTimers()⁩ الكائن Date ودوال المؤقّتات؛ وتضبط ⁦setSystemTime(date)⁩ الوقت الحالي دون إطلاق المؤقّتات؛ وتشغّل ⁦advanceTimersByTime(ms)⁩ المؤقّتات؛ وتعيد ⁦useRealTimers()⁩ كل شيء بعد الاختبار. وتعمل vi.setSystemTime في Vitest حتى دون مؤقّتات وهمية، وتزيّف عندئذ الكائن Date وحده.
  • Java: مرّر java.time.Clock؛ واستخدم ⁦Clock.fixed(instant, zone)⁩ للحظة مجمّدة، و⁦Clock.offset(baseClock, duration)⁩ للعمل في المستقبل أو الماضي.
  • Python: المكتبة time-machine ⁦(time_machine.travel(destination, tick=False))⁩ أو المكتبة freezegun ⁦(@freeze_time("2026-03-02"))⁩ ترقّعان datetime وtime أثناء الاختبار؛ ويستطيع كائن traveller في time-machine أن يستدعي ⁦shift()⁩ أو ⁦move_to()⁩ أثناء الاختبار.
  • ⁦.NET⁩: اعتمد على TimeProvider (مضمّن في ⁦.NET 8⁩ وما بعده، ومتاح للإصدارات الأقدم عبر الحزمة Microsoft.Bcl.TimeProvider)، واستخدم FakeTimeProvider من الحزمة Microsoft.Extensions.TimeProvider.Testing. تحرّك ⁦Advance(TimeSpan)⁩ وSetUtcNow الوقت إلى الأمام فقط (ترفض SetUtcNow أي وقت أبكر)، بينما تغيّر AdjustTime الوقت دون التأثير في المؤقّتات.
  • البرامج التي لا يمكن تعديلها: تعترض libfaketime استدعاءات الوقت لعملية واحدة على لينكس عبر LD_PRELOAD أو الأداة faketime، بإزاحة مثل ⁦FAKETIME="+30d"⁩ أو بتاريخ مطلق. ولا تعمل مع البرامج المربوطة ربطاً ساكناً (Statically linked)، وقد تتجاوزها بيئات تشغيل مثل Go.
// isTrialActive, is_trial_active and TrialService stand for the code under test.

// Playwright Test (page.goto("/account") resolves against baseURL in playwright.config).
// This fakes the browser clock only: the server must agree on "now".
import { test, expect } from "@playwright/test";
test.use({ timezoneId: "Asia/Riyadh" });
for (const [now, plan] of [["2026-03-02T06:59:59Z", "Trial"], ["2026-03-02T07:00:00Z", "Expired"]]) {
  test(`plan shown at ${now}`, async ({ page }) => {
    await page.clock.setFixedTime(new Date(now));
    await page.goto("/account");
    await expect(page.getByTestId("plan")).toHaveText(plan);
  });
}

// Jest test file, globals (Vitest: vi.useFakeTimers, vi.setSystemTime, vi.useRealTimers)
afterEach(() => jest.useRealTimers());
test("trial ends exactly at trialEnd", () => {
  jest.useFakeTimers();
  const trialEnd = new Date("2026-03-02T07:00:00Z");
  jest.setSystemTime(new Date("2026-03-02T06:59:59Z"));
  expect(isTrialActive(trialEnd)).toBe(true);
  jest.setSystemTime(trialEnd);
  expect(isTrialActive(trialEnd)).toBe(false);
});

// Java + JUnit 5: production code receives a Clock instead of calling Instant.now()
import java.time.*;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class TrialTest {
  static boolean isTrialActive(Instant end, Clock clock) { return clock.instant().isBefore(end); }

  @Test
  void trialEndsExactlyAtEnd() {
    Instant end = Instant.parse("2026-03-02T07:00:00Z");
    Clock before = Clock.fixed(end.minusSeconds(1), ZoneId.of("Asia/Riyadh"));
    assertTrue(isTrialActive(end, before));
    assertFalse(isTrialActive(end, Clock.offset(before, Duration.ofSeconds(1))));
  }
}

# Python (pytest body): time-machine
import datetime as dt
import time_machine
END = dt.datetime(2026, 3, 2, 7, 0, tzinfo=dt.timezone.utc)
with time_machine.travel(END - dt.timedelta(seconds=1), tick=False) as traveller:
    assert is_trial_active(END)
    traveller.shift(dt.timedelta(seconds=1))
    assert not is_trial_active(END)

// .NET 8+ test body (xUnit): TimeProvider in production, FakeTimeProvider in tests
// (package Microsoft.Extensions.TimeProvider.Testing, namespace Microsoft.Extensions.Time.Testing)
var end = new DateTimeOffset(2026, 3, 2, 7, 0, 0, TimeSpan.Zero);
var time = new FakeTimeProvider(end.AddSeconds(-1));
var trials = new TrialService(time);          // reads time.GetUtcNow()
Assert.True(trials.IsActive(end));
time.Advance(TimeSpan.FromSeconds(1));
Assert.False(trials.IsActive(end));

# libfaketime (Linux): fake the clock of one process only
TZ=UTC faketime "2026-03-02 06:59:59" ./expire-trials
LD_PRELOAD=/path/to/libfaketime.so.1 FAKETIME="+30d" ./expire-trials

جرّبها: محاكي اختبار التاريخ والوقت

مثال تطبيقي: اختبار فترة تجريبية مدتها 30 يوماً دون انتظار 30 يوماً

القاعدة، لمنتج خيالي اسمه «دفتر للفواتير» (Daftar Invoices): «تستمر الفترة التجريبية المجانية 30 يوماً من التسجيل. وتكون نشطة ما دام الوقت الحالي قبل trial_end. وتُرسل رسالة تذكير قبل trial_end بثلاثة أيام. وتعمل مهمة ليلية الساعة 00:05 بتوقيت Asia/Riyadh تخفّض الحسابات التي انتهت فترتها التجريبية.» يسجّل مستخدم يوم 31 يناير 2026 الساعة 10:00 في الرياض.

استخرج القيم المتوقعة أولاً. التسجيل ⁦2026-01-31T10:00:00+03:00⁩، أي ⁦2026-01-31T07:00:00Z⁩ أو وقت Unix 1769842800. وفبراير 2026 فيه 28 يوماً، فبعد 30 يوماً يكون 2 مارس 2026 الساعة 10:00 بتوقيت الرياض: ⁦trial_end = 2026-03-02T07:00:00Z⁩، ووقت Unix 1772434800. ولا توقيت صيفياً في Asia/Riyadh، فالثلاثون يوماً التقويمية و720 ساعة تعطيان اللحظة نفسها هنا؛ أما في Europe/London عبر 29 مارس فلا. ويحين موعد التذكير عند ⁦2026-02-27T07:00:00Z⁩ (الساعة 10:00 في الرياض). وأول تشغيل للمهمة الليلية يرى الفترة منتهية يكون يوم 3 مارس 2026 الساعة 00:05 بتوقيت الرياض، أي ⁦2026-03-02T21:05:00Z⁩.

ثم اختر اللحظات والحالة المتوقعة عند كل منها، واضبط الساعة على كل لحظة (أو أزِح بيانات الحساب ليصبح بذلك العمر)، وتحقق من كل موضع تظهر فيه القاعدة: API وصفحة الحساب والبريد والمهمة وقاعدة البيانات. وكرر الجدول لتسجيل في 31 يناير 2028: ففبراير فيه 29 يوماً، فتكون trial_end يوم 1 مارس 2028 الساعة 10:00 بتوقيت الرياض.

Daftar Invoices (fictional): 30-day trial, sign-up 2026-01-31 10:00 Asia/Riyadh
Rule: active while now < trial_end; trial_end = sign-up + 30 days

Moment (UTC)           Riyadh (+03:00)    Expected
2026-01-31T07:00:00Z   31 Jan 10:00:00    active, "30 days left", trial_end 2026-03-02T07:00:00Z
2026-02-27T06:59:59Z   27 Feb 09:59:59    no reminder yet
2026-02-27T07:00:00Z   27 Feb 10:00:00    reminder sent once: "Your trial ends on 2 March 2026"
2026-03-01T20:59:59Z   1 Mar 23:59:59     active, "ends tomorrow"
2026-03-01T21:00:00Z   2 Mar 00:00:00     active, "ends today"
2026-03-02T06:59:59Z   2 Mar 09:59:59     active (last second)
2026-03-02T07:00:00Z   2 Mar 10:00:00     ended: API refuses premium features (exclusive end)
2026-03-02T07:00:01Z   2 Mar 10:00:01     ended
2026-03-02T21:05:00Z   3 Mar 00:05:00     nightly job downgrades the account
(same job run twice)                      second run changes nothing (idempotent)

Wrong implementations the table catches
+1 month, clamped (Java plusMonths)    ends 2026-02-28 10:00 Riyadh   2 days early
+1 month, overflow (JS setMonth)       ends 2026-03-03 10:00 Riyadh   1 day late
sign-up day counted as day 1           ends 2026-03-01 10:00 Riyadh   1 day early
date-only end compared with UTC date   ends 2026-03-02 03:00 Riyadh   7 hours early

Leap-year variant: sign-up 2028-01-31 10:00 Riyadh -> trial_end 2028-03-01T07:00:00Z

جرّبها: محاكي اختبار التاريخ والوقت جرّبها: حاسبة الفرق بين التواريخ جرّبها: محوّل المناطق الزمنية

قائمة تحقق اختبار التاريخ والوقت والأخطاء الشائعة

انسخ القائمة إلى خطة اختبار أو إلى معايير قبول قصة مستخدم، واشطب ما لا ينطبق. والأخطاء التالية هي التي تسمح لعيوب الوقت بالوصول إلى الإنتاج:

  • المقارنة بالساعة الحقيقية: القيم المتوقعة المحسوبة بـ ⁦new Date()⁩ داخل الاختبار تنجح اليوم وتفشل عند منتصف الليل أو في نهاية الشهر أو في منطقة أخرى — وهذا هو الاختبار المتذبذب (Flaky test) الكلاسيكي المرتبط بالوقت.
  • اختبار «الآن» فقط: فترة تجريبية تُنشأ في الاختبار وتُفحص بعد ثانية لا تصل إلى نهايتها أبداً؛ اضبط الساعة، أو أزِح البيانات، إلى الحد.
  • تحريك ساعة واحدة: المتصفح في مارس، وAPI في يناير، وقاعدة البيانات في الوقت الحقيقي — والنتيجة لا تعكس أياً منها.
  • تخزين الفوارق بدل المناطق: الفارق ⁦+01:00⁩ المحفوظ لمستخدم في برلين خاطئ نصف السنة.
  • الثقة بحساب الأشهر في مكتبة دون قراءة توثيقها: الفيض والقص يعطيان تواريخ تجديد مختلفة.
  • نسيان إعادة الضبط: سجلات بتواريخ مستقبلية وساعة متروكة مُزاحة على خادم مشترك تكسر اختبارات الآخرين أياماً.
  • تشغيل CI بتوقيت UTC فقط، أو بتوقيت الرياض فقط: شغّل مهمة واحدة على الأقل في منطقة فيها توقيت صيفي وأخرى بفارق ⁦+05:30⁩ (المتغير TZ لمشغّل الاختبارات، وtimezoneId للمتصفح).
Date and time test checklist

Rules
[ ] Every time rule written as a comparison with an instant (active while now < end)
[ ] Inclusive or exclusive end agreed; the zone that defines "a day" named
[ ] "30 days" vs "1 month" vs "end of day" decided for each rule

Control of time
[ ] Code reads time from an injected clock / TimeProvider, not from "now" directly
[ ] Server, jobs and database use the same clock in test environments
[ ] Clock shifting plan covers TLS, tokens, NTP, other users and data clean-up

Moments
[ ] -1 s / exact / +1 s around every boundary (-1 ms if milliseconds are stored)
[ ] 23:59:59 and 00:00:00 in the user's zone and in UTC
[ ] The 28th, 29th, 30th and 31st; 29 February 2028; 28 February 2100
[ ] 31 December and 1 January; ISO week 53 (2026); YYYY vs yyyy in patterns
[ ] DST gap and overlap days in a DST zone (Europe/Berlin, America/New_York)
[ ] +05:30 and +05:45 offsets; a zone without DST (Asia/Riyadh)
[ ] A date after 2038-01-19T03:14:07Z in every stored future date

Data and formats
[ ] Instants in RFC 3339 with Z or an offset; zones stored as IANA names
[ ] Seconds vs milliseconds checked at every API boundary (JWT exp is in seconds)
[ ] Column types: timestamptz vs timestamp; MySQL TIMESTAMP range; precision
[ ] tzdata version known for every runtime; recently changed zones tested
[ ] Jobs idempotent; missed and repeated runs handled

جرّبها: مولّد بيانات اختبار التواريخ

الأسئلة الشائعة

ما هو اختبار التواريخ المستقبلية؟

اختبار التواريخ المستقبلية (Future date testing أو Forward date testing) يتحقق من سلوك البرنامج حين يمضي الوقت: الفترات التجريبية والرموز المنتهية، والفواتير المتأخرة، والتجديدات، وحدود العمر، ومدد الاحتفاظ بالبيانات. وبدلاً من الانتظار يضبط المختبِرون الساعة التي يقرؤها التطبيق على لحظة مستقبلية — أو يزيحون التواريخ في بيانات الاختبار — ويقارنون الحالة عند كل حد بالنتيجة المتوقعة.

ما هو اختبار محاكاة التاريخ والوقت؟

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

هل أزيح الساعة أم أزيح بيانات الاختبار؟

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

كيف أختبر شيئاً ينتهي بعد 30 يوماً دون انتظار؟

احسب لحظة الانتهاء من القاعدة، ثم نفّذ الفحص قبلها بثانية وعندها تماماً وبعدها بثانية، بساعة متحكَّم فيها (Clock.fixed أو FakeTimeProvider أو jest.setSystemTime أو page.clock.setFixedTime أو time-machine) أو ببيانات أُنشئت قبل 30 يوماً. واتفق أولاً على أن النهاية شاملة أم غير شاملة، وعلى المنطقة الزمنية التي تحدد اليوم.

كيف تُختبر المناطق الزمنية والتوقيت الصيفي؟

خزّن اللحظات بتوقيت UTC أو بفارق، والمناطق بأسمائها في IANA، ثم شغّل الاختبارات في منطقة واحدة على الأقل تطبق التوقيت الصيفي، وأخرى لا تطبقه، وثالثة بفارق نصف ساعة. وغطِّ الفجوة (لا وجود للساعة 02:30 يوم تقديم الساعة في Europe/Berlin)، والتداخل (تحدث 02:30 مرتين في أكتوبر)، والمهام المجدولة داخلهما، وإصدار tzdata الذي تحمله كل بيئة تشغيل.

ما هي مشكلة سنة 2038؟

الأنظمة التي تخزّن وقت Unix عدداً صحيحاً ذا إشارة من 32 بتاً للثواني لا تستطيع العدّ إلا حتى 2147483647، أي ⁦2038-01-19T03:14:07Z⁩؛ وبعدها بثانية تنقلب القيمة إلى تاريخ في ديسمبر 1901. وكل تاريخ مستقبلي مخزّن بعد تلك اللحظة — عقد طويل، أو شهادة، أو تاريخ رمزي بعيد — يكشفها اليوم، وكذلك نوع TIMESTAMP في MySQL الذي ينتهي مداه عند تلك اللحظة تماماً.

المراجع

  • Time Zone DatabaseIANAقاعدة بيانات tz للمناطق الزمنية في العالم وقواعد فروق التوقيت والتوقيت الصيفي تاريخياً، وتُحدَّث عدة مرات في السنة.
  • Time Zone Database — NEWSIANAملاحظات إصدار كل نسخة من tzdata، وفيها كل تغيير في قواعد المناطق الزمنية — مثل إنهاء المكسيك معظم التوقيت الصيفي (2022f)، وباراغواي عند ⁦-03⁩ دائماً (2025a)، ومانيتوبا عند ⁦-05⁩ دائماً (2026e).
  • RFC 3339: Date and Time on the Internet: TimestampsIETF · 2002ملف تعريف الطوابع الزمنية على الإنترنت المشتق من ISO 8601 (2026-09-23T10:15:00Z) وقواعد الإزاحة الزمنية.
  • ISO 8601 — Date and time formatISOالمعيار الدولي لكتابة التواريخ والأوقات والمدد والفترات دون لبس.
  • Seconds Since the Epoch — POSIX.1-2017 Base Definitions, section 4.16The Open Group · 2017تعريف POSIX لوقت Unix: معادلة التحويل من وقت UTC المفصّل إلى الثواني منذ الحقبة، وفيها كل يوم 86400 ثانية بالضبط (فلا تُحتسب الثواني الكبيسة).
  • RFC 7519: JSON Web Token (JWT)IETF · 2015يعرّف صيغة JWT — الترويسة والحمولة والتوقيع — والمطالبات المسجّلة iss وsub وaud وexp وnbf وiat وjti.
  • Date — JavaScript reference — MDNMDN Web Docs (Mozilla)كيف تمثّل JavaScript الوقت كأجزاء من الألف من الثانية منذ حقبة Unix وتحلّل سلاسل ISO 8601.
  • Temporal.ZonedDateTime — MDNMDN Web Docs (Mozilla)التاريخ والوقت في منطقة زمنية من IANA في JavaScript، وكيف تُحسم فجوات التوقيت الصيفي وتداخلاته (الخيار disambiguation بقيمه compatible وearlier وlater وreject).
  • Clock — PlaywrightMicrosoft (Playwright)كيف يتحكم Playwright في الوقت داخل المتصفح المختبَر: page.clock مع install وsetFixedTime وsetSystemTime وfastForward وrunFor وpauseAt، وواجهات الوقت التي يستبدلها.
  • The DATE, DATETIME, and TIMESTAMP Types — MySQL 8.4 Reference ManualOracle (MySQL)مدى أنواع التاريخ والوقت في MySQL — ينتهي TIMESTAMP عند ⁦2038-01-19 03:14:07⁩ UTC، وDATETIME عند ⁦9999-12-31⁩ — وتحويل المنطقة الزمنية الذي يطبقه TIMESTAMP عند التخزين.
  • PostgreSQL Documentation: SQL SyntaxThe PostgreSQL Global Development Groupالبنية المعجمية لـ SQL في PostgreSQL: المعرّفات والاقتباس والثوابت النصية والتهريب.

الأدوات المذكورة في هذا الدليل

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

تصميم الاختبارات جديد

أضف أو اطرح أياماً أو أسابيع أو أشهراً أو سنوات أو ساعات أو دقائق من تاريخ.

أدوات مساعدة

احسب الفرق الدقيق بين تاريخين بالسنوات والأشهر والأيام والساعات والوحدات الإجمالية.

أدوات مساعدة

حوّل التاريخ والوقت بين المناطق الزمنية (الرياض، عمّان، دلهي، UTC، نيويورك، لندن…).

أدوات مساعدة

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

بيانات الاختبار

نفّذ تحليلاً كاملاً للقيم الحدّية (ثنائي القيم وثلاثي القيم) مع فئات التكافؤ والنتائج المتوقّعة.

تصميم الاختبارات

التحقق مما إذا كان رمز JWT منتهي الصلاحية أو لم يصبح صالحاً بعد، أو المدة المتبقية حتى انتهاء صلاحيته.

الأمان

كيف تساعدك متقن سوفت

أدلة أخرى

أدلة أخرى
أدوات مساعدة

شرح تعابير cron

شرح تعابير cron حقلاً حقلاً: الدقيقة والساعة واليوم والشهر ويوم الأسبوع، والمعاملات * و/ و- و,، والجدولات الشائعة، والمناطق الزمنية، والأخطاء الشائعة.

وقت القراءة 5 دقائق
بيانات الاختبار

اختبار التوطين والتدويل

دليل اختبار التوطين: الفرق بين التدويل والتوطين، وجودة الترجمة، وصيغ الجمع، والتوطين الزائف، والتاريخ الهجري، والأرقام والعملات، واليونيكود والأتمتة.

وقت القراءة 13 دقائق
الأتمتة

الاختبارات المتذبذبة

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

وقت القراءة 9 دقائق
الأمان

ما هو JWT؟ شرح رموز JSON Web Token

شرح مبسّط لـ JWT: الأجزاء الثلاثة للرمز، والمطالبات مثل exp وsub، وكيفية فك ترميزه والتحقق منه، ولماذا فك الترميز ليس تحققاً، وقواعد الأمان.

وقت القراءة 4 دقائق
تصميم الاختبارات

الاختبار المبني على المخاطر

كيف تطبّق الاختبار المبني على المخاطر: تحديد مخاطر المنتج، وتقييم الاحتمال والأثر، وعمق الاختبار، واختيار اختبارات الانحدار، والمخاطر المتبقية.

وقت القراءة 10 دقائق
تصميم الاختبارات

ضمان جودة المنتج من البداية إلى النهاية

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

وقت القراءة 8 دقائق