أساسيات JavaScript

التاريخ والوقت في JavaScript

45 دقيقة الدرس 29 من 60

مقدمة إلى كائن Date

يعد التعامل مع التواريخ والأوقات من أكثر المهام شيوعا في تطوير الويب. سواء كنت تبني نظام حجز أو تعرض طوابع زمنية على مدونة أو تحسب مواعيد نهائية، فأنت بحاجة لفهم كيف يتعامل JavaScript مع التواريخ. يوفر JavaScript كائن Date المدمج لإنشاء التواريخ والأوقات ومعالجتها وتنسيقها. في هذا الدرس ستتقن كل جانب من جوانب العمل مع التواريخ -- من إنشاء نسخ تاريخ بسيطة إلى بناء أدوات عملية مثل الساعات وحاسبات العمر وعرض الوقت النسبي.

كائن Date في JavaScript يمثل لحظة واحدة في الزمن. داخليا يخزن عدد المللي ثانية التي انقضت منذ 1 يناير 1970 الساعة 00:00:00 بتوقيت UTC والذي يعرف بـ حقبة Unix أو توقيت POSIX. هذا التمثيل الرقمي يجعل من الممكن إجراء عمليات حسابية على التواريخ ومقارنتها والتحويل بين التنسيقات والمناطق الزمنية المختلفة.

إنشاء كائنات Date

هناك أربع طرق رئيسية لإنشاء كائن Date في JavaScript. كل طريقة تخدم حالة استخدام مختلفة وفهم متى تستخدم كل واحدة أمر أساسي لكتابة كود نظيف ويمكن التنبؤ به.

1. التاريخ والوقت الحالي

أبسط طريقة لإنشاء كائن Date هي بدون أي وسيطات. هذا يعطيك التاريخ والوقت الحالي في لحظة تنفيذ الكود.

مثال: إنشاء التاريخ الحالي

const now = new Date();
console.log(now);
// المخرجات: شيء مثل "Sat Feb 07 2026 14:30:00 GMT+0300"

// المخرجات الدقيقة تعتمد على منطقتك الزمنية ووقت تشغيل الكود
console.log(typeof now); // "object"

2. من طابع زمني (مللي ثانية)

يمكنك إنشاء Date من طابع زمني رقمي يمثل المللي ثانية منذ حقبة Unix. يستخدم هذا بشكل شائع عند العمل مع واجهات برمجة التطبيقات أو قواعد البيانات التي تخزن التواريخ كأرقام.

مثال: إنشاء تاريخ من طابع زمني

// 1 يناير 2025، 00:00:00 UTC
const fromTimestamp = new Date(1735689600000);
console.log(fromTimestamp);

// حقبة Unix نفسها (الطابع الزمني 0)
const epoch = new Date(0);
console.log(epoch); // Thu Jan 01 1970 ...

// الطوابع الزمنية السالبة تذهب قبل الحقبة
const beforeEpoch = new Date(-86400000); // يوم واحد قبل الحقبة
console.log(beforeEpoch); // Wed Dec 31 1969 ...

3. من نص تاريخ

يمكنك تمرير نص تاريخ إلى منشئ Date. سيحاول JavaScript تحليله. لكن تحليل نصوص التاريخ قد يكون غير متسق عبر المتصفحات لذا يجب استخدام تنسيقات موحدة.

مثال: إنشاء تاريخ من نص

// تنسيق ISO 8601 (مُوصى به -- الأكثر موثوقية عبر المتصفحات)
const isoDate = new Date('2025-06-15T10:30:00Z');
console.log(isoDate);

// نص تاريخ فقط (يُفسر كـ UTC)
const dateOnly = new Date('2025-06-15');
console.log(dateOnly);

// تنسيقات أخرى (أقل موثوقية -- تجنبها في الإنتاج)
const informal = new Date('June 15, 2025');
console.log(informal);

const slashFormat = new Date('06/15/2025');
console.log(slashFormat);
تحذير: تحليل نصوص التاريخ هو أحد أكثر المجالات عدم اتساقا في JavaScript. النص '2025-06-15' يُعامل كمنتصف ليل UTC بينما '2025/06/15' يُعامل كمنتصف ليل محلي في بعض المتصفحات. استخدم دائما تنسيق ISO 8601 (YYYY-MM-DDTHH:mm:ssZ) للحصول على نتائج أكثر قابلية للتنبؤ في جميع البيئات.

4. من مكونات فردية

الطريقة الأكثر وضوحا لإنشاء Date هي بتمرير قيم فردية للسنة والشهر واليوم والساعة والدقيقة والثانية والمللي ثانية. هذا يمنحك تحكما كاملا ويتجنب غموض التحليل.

مثال: إنشاء تاريخ من المكونات

// new Date(year, monthIndex, day, hours, minutes, seconds, milliseconds)
// مهم: monthIndex يبدأ من 0 (0 = يناير، 11 = ديسمبر)

const christmas = new Date(2025, 11, 25); // 25 ديسمبر 2025
console.log(christmas);

const specificTime = new Date(2025, 5, 15, 14, 30, 0);
// 15 يونيو 2025 الساعة 2:30:00 مساء (التوقيت المحلي)
console.log(specificTime);

// السنة والشهر فقط مطلوبان؛ الباقي يأخذ القيمة الافتراضية 0 أو 1
const monthStart = new Date(2025, 0); // 1 يناير 2025
console.log(monthStart);

// السنوات المكونة من رقمين تُربط بالتسعينيات
const oldDate = new Date(99, 0, 1); // 1 يناير 1999 (وليس 0099)
console.log(oldDate);
ملاحظة: معامل الشهر يبدأ من الصفر مما يعني أن يناير هو 0 وديسمبر هو 11. هذا أحد أكثر مصادر الأخطاء شيوعا عند العمل مع التواريخ في JavaScript. تذكر دائما أن تطرح 1 عند تعيين الشهر وتضيف 1 عند عرضه.

الحصول على مكونات التاريخ (دوال الاسترجاع)

بمجرد حصولك على كائن Date يمكنك استخراج المكونات الفردية باستخدام دوال الاسترجاع. كل دالة تعيد القيمة بالتوقيت المحلي ما لم تستخدم نسخة UTC.

مثال: استخراج مكونات التاريخ

const date = new Date(2025, 5, 15, 14, 30, 45, 500);
// 15 يونيو 2025، 2:30:45.500 مساء

console.log(date.getFullYear());     // 2025
console.log(date.getMonth());        // 5 (يونيو، يبدأ من 0)
console.log(date.getDate());         // 15 (يوم الشهر)
console.log(date.getDay());          // 0 (الأحد، 0=أحد، 6=سبت)
console.log(date.getHours());        // 14
console.log(date.getMinutes());      // 30
console.log(date.getSeconds());      // 45
console.log(date.getMilliseconds()); // 500
console.log(date.getTime());         // الطابع الزمني بالمللي ثانية
console.log(date.getTimezoneOffset()); // فرق الدقائق عن UTC

مثال: دوال استرجاع UTC

const date = new Date('2025-06-15T14:30:00Z');

// دوال UTC تعيد القيم بالتوقيت العالمي المنسق
console.log(date.getUTCFullYear());  // 2025
console.log(date.getUTCMonth());     // 5
console.log(date.getUTCDate());      // 15
console.log(date.getUTCDay());       // 0
console.log(date.getUTCHours());     // 14
console.log(date.getUTCMinutes());   // 30
console.log(date.getUTCSeconds());   // 0

// مقارنة المحلي مع UTC
console.log('الساعات المحلية:', date.getHours());
console.log('ساعات UTC:', date.getUTCHours());
// قد تختلف هذه بناء على منطقتك الزمنية

تعيين مكونات التاريخ (دوال الإعداد)

يمكنك تعديل كائن Date بعد إنشائه باستخدام دوال الإعداد. كل دالة تغير المكون المقابل في مكانه وتعيد الطابع الزمني الجديد. يتعامل JavaScript تلقائيا مع التجاوز -- مثلا تعيين التاريخ إلى 32 في شهر من 30 يوما سينتقل إلى الشهر التالي.

مثال: تعديل مكونات التاريخ

const date = new Date(2025, 0, 1); // 1 يناير 2025
console.log(date); // Wed Jan 01 2025

date.setFullYear(2026);
console.log(date); // Thu Jan 01 2026

date.setMonth(5); // تعيين إلى يونيو (يبدأ من 0)
console.log(date); // Mon Jun 01 2026

date.setDate(15);
console.log(date); // Mon Jun 15 2026

date.setHours(10);
date.setMinutes(30);
date.setSeconds(0);
console.log(date); // Mon Jun 15 2026 10:30:00

// التجاوز يُعالج تلقائيا
date.setDate(35); // يونيو فيه 30 يوما فقط
console.log(date); // ينتقل إلى 5 يوليو 2026

// يمكنك أيضا استخدام قيم سالبة
date.setDate(0); // اليوم 0 = آخر يوم من الشهر السابق
console.log(date); // 30 يونيو 2026
نصيحة احترافية: سلوك التجاوز في setDate() مفيد للغاية. تعيين التاريخ إلى 0 يعطيك آخر يوم من الشهر السابق. هذه واحدة من أسهل الطرق لمعرفة عدد أيام شهر معين: new Date(year, month + 1, 0).getDate() يعيد عدد أيام ذلك الشهر.

الدوال الثابتة لـ Date

يوفر كائن Date دوال ثابتة تستدعيها على فئة Date نفسها وليس على نسخة منها.

مثال: Date.now() و Date.parse()

// Date.now() يعيد الطابع الزمني الحالي بالمللي ثانية
const timestamp = Date.now();
console.log(timestamp); // مثلا 1738900200000

// هذا مكافئ لـ (لكن أسرع من):
const sameTimestamp = new Date().getTime();

// Date.parse() يحلل نص تاريخ ويعيد طابعا زمنيا
const parsed = Date.parse('2025-06-15T10:30:00Z');
console.log(parsed); // 1750067400000

// نصوص التاريخ غير الصالحة تعيد NaN
const invalid = Date.parse('not a date');
console.log(invalid); // NaN

// قياس الأداء باستخدام Date.now()
const start = Date.now();
// ... عملية ما ...
for (let i = 0; i < 1000000; i++) { /* عمل */ }
const end = Date.now();
console.log(`استغرقت العملية ${end - start} مللي ثانية`);

الطوابع الزمنية ووقت Unix

الطابع الزمني هو قيمة رقمية تمثل نقطة محددة في الزمن. في JavaScript تقاس الطوابع الزمنية بالـمللي ثانية منذ حقبة Unix. العديد من اللغات والأنظمة الأخرى تستخدم الثواني منذ الحقبة بدلا من ذلك. فهم هذا الاختلاف أمر حاسم عند تبادل البيانات بين الأنظمة.

مثال: العمل مع الطوابع الزمنية

// JavaScript يستخدم المللي ثانية
const jsTimestamp = Date.now();
console.log(jsTimestamp); // مثلا 1738900200000

// طوابع Unix الزمنية (تستخدمها العديد من الـ APIs وقواعد البيانات) تستخدم الثواني
const unixTimestamp = Math.floor(Date.now() / 1000);
console.log(unixTimestamp); // مثلا 1738900200

// تحويل طابع Unix الزمني إلى Date في JavaScript
const fromUnix = new Date(unixTimestamp * 1000);
console.log(fromUnix);

// تحويل Date في JavaScript إلى طابع Unix زمني
const date = new Date('2025-01-01T00:00:00Z');
const toUnix = Math.floor(date.getTime() / 1000);
console.log(toUnix); // 1735689600

// ثوابت طوابع زمنية مفيدة
const ONE_SECOND = 1000;
const ONE_MINUTE = 60 * 1000;
const ONE_HOUR = 60 * 60 * 1000;
const ONE_DAY = 24 * 60 * 60 * 1000;
const ONE_WEEK = 7 * 24 * 60 * 60 * 1000;

تنسيق التواريخ

يوفر JavaScript عدة دوال لتحويل التواريخ إلى نصوص قابلة للقراءة. النهج الأكثر مرونة يستخدم واجهة Intl.DateTimeFormat أو مجموعة دوال toLocaleString.

دوال النص المدمجة

مثال: دوال تنسيق التاريخ الأساسية

const date = new Date(2025, 5, 15, 14, 30, 0);

// التمثيل النصي الكامل
console.log(date.toString());
// "Sun Jun 15 2025 14:30:00 GMT+0300 (Arabian Standard Time)"

// جزء التاريخ فقط
console.log(date.toDateString());
// "Sun Jun 15 2025"

// جزء الوقت فقط
console.log(date.toTimeString());
// "14:30:00 GMT+0300 (Arabian Standard Time)"

// تنسيق ISO 8601 (دائما UTC)
console.log(date.toISOString());
// "2025-06-15T11:30:00.000Z"

// نص UTC
console.log(date.toUTCString());
// "Sun, 15 Jun 2025 11:30:00 GMT"

// تحويل JSON يستخدم تنسيق ISO
console.log(JSON.stringify({ created: date }));
// '{"created":"2025-06-15T11:30:00.000Z"}'

التنسيق المحلي

مثال: toLocaleDateString و toLocaleTimeString

const date = new Date(2025, 5, 15, 14, 30, 0);

// التنسيق المحلي الافتراضي
console.log(date.toLocaleDateString());
// "6/15/2025" (أمريكي) أو "15/6/2025" (بريطاني) حسب لغة المتصفح

// تحديد المنطقة
console.log(date.toLocaleDateString('en-US'));
// "6/15/2025"

console.log(date.toLocaleDateString('en-GB'));
// "15/06/2025"

console.log(date.toLocaleDateString('ar-SA'));
// يعرض بالأرقام العربية والتنسيق العربي

// مع خيارات للتنسيق المخصص
console.log(date.toLocaleDateString('en-US', {
    weekday: 'long',
    year: 'numeric',
    month: 'long',
    day: 'numeric'
}));
// "Sunday, June 15, 2025"

// تنسيق الوقت مع المنطقة
console.log(date.toLocaleTimeString('en-US', {
    hour: '2-digit',
    minute: '2-digit',
    hour12: true
}));
// "02:30 PM"

console.log(date.toLocaleTimeString('en-GB', {
    hour: '2-digit',
    minute: '2-digit',
    hour12: false
}));
// "14:30"

Intl.DateTimeFormat

للمزيد من التحكم والأداء الأفضل عند تنسيق تواريخ كثيرة استخدم Intl.DateTimeFormat. هذا ينشئ كائن منسق قابل لإعادة الاستخدام.

مثال: Intl.DateTimeFormat

// إنشاء منسق قابل لإعادة الاستخدام
const formatter = new Intl.DateTimeFormat('en-US', {
    weekday: 'long',
    year: 'numeric',
    month: 'long',
    day: 'numeric',
    hour: 'numeric',
    minute: '2-digit',
    timeZoneName: 'short'
});

const date = new Date(2025, 5, 15, 14, 30, 0);
console.log(formatter.format(date));
// "Sunday, June 15, 2025 at 2:30 PM GMT+3"

// منسق عربي
const arFormatter = new Intl.DateTimeFormat('ar-EG', {
    weekday: 'long',
    year: 'numeric',
    month: 'long',
    day: 'numeric'
});
console.log(arFormatter.format(date));

// التنسيق بمنطقة زمنية محددة
const tokyoFormatter = new Intl.DateTimeFormat('en-US', {
    timeZone: 'Asia/Tokyo',
    hour: '2-digit',
    minute: '2-digit',
    timeZoneName: 'long'
});
console.log(tokyoFormatter.format(date));

// formatToParts يعطيك مصفوفة من المكونات
const parts = formatter.formatToParts(date);
console.log(parts);
// [{type: "weekday", value: "Sunday"}, {type: "literal", value: ", "}, ...]

الحساب على التواريخ

لا يوفر JavaScript دوال مدمجة لإضافة أو طرح الوقت من التواريخ لكن يمكنك إنجاز العمليات الحسابية على التواريخ بمعالجة الطوابع الزمنية أو استخدام دوال الاسترجاع والإعداد معا.

إضافة وطرح الوقت

مثال: إضافة أيام وشهور وسنوات

// إضافة أيام باستخدام setDate
function addDays(date, days) {
    const result = new Date(date);
    result.setDate(result.getDate() + days);
    return result;
}

const today = new Date(2025, 5, 15);
console.log(addDays(today, 10));  // 25 يونيو 2025
console.log(addDays(today, 20));  // 5 يوليو 2025 (انتقال تلقائي)
console.log(addDays(today, -5));  // 10 يونيو 2025 (طرح أيام)

// إضافة شهور باستخدام setMonth
function addMonths(date, months) {
    const result = new Date(date);
    result.setMonth(result.getMonth() + months);
    return result;
}

console.log(addMonths(today, 3));  // 15 سبتمبر 2025
console.log(addMonths(today, -2)); // 15 أبريل 2025

// إضافة سنوات
function addYears(date, years) {
    const result = new Date(date);
    result.setFullYear(result.getFullYear() + years);
    return result;
}

console.log(addYears(today, 1)); // 15 يونيو 2026

// إضافة ساعات باستخدام الطوابع الزمنية
function addHours(date, hours) {
    return new Date(date.getTime() + hours * 60 * 60 * 1000);
}

const now = new Date();
console.log(addHours(now, 5)); // بعد 5 ساعات من الآن

حساب الفرق بين تاريخين

مثال: حسابات فرق التاريخ

function dateDifference(date1, date2) {
    const diffMs = Math.abs(date2.getTime() - date1.getTime());
    const diffSeconds = Math.floor(diffMs / 1000);
    const diffMinutes = Math.floor(diffMs / (1000 * 60));
    const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
    const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));

    return {
        milliseconds: diffMs,
        seconds: diffSeconds,
        minutes: diffMinutes,
        hours: diffHours,
        days: diffDays
    };
}

const start = new Date(2025, 0, 1);  // 1 يناير 2025
const end = new Date(2025, 11, 31);  // 31 ديسمبر 2025

const diff = dateDifference(start, end);
console.log(`${diff.days} يوم`);      // 364 يوم
console.log(`${diff.hours} ساعة`);    // 8736 ساعة

// الأيام حتى حدث معين
function daysUntil(targetDate) {
    const now = new Date();
    now.setHours(0, 0, 0, 0); // إعادة التعيين لبداية اليوم
    const target = new Date(targetDate);
    target.setHours(0, 0, 0, 0);
    const diffMs = target.getTime() - now.getTime();
    return Math.ceil(diffMs / (1000 * 60 * 60 * 24));
}

console.log(daysUntil(new Date(2025, 11, 25)));
// الأيام حتى عيد الميلاد 2025

مقارنة التواريخ

مقارنة التواريخ في JavaScript تتطلب بعض الحذر. لا يمكنك استخدام === لمقارنة كائني Date لأنهما كائنات والمقارنة تفحص مساواة المرجع وليس مساواة القيمة. بدلا من ذلك قارن طوابعهما الزمنية.

مثال: مقارنة التواريخ بشكل صحيح

const date1 = new Date(2025, 5, 15);
const date2 = new Date(2025, 5, 15);
const date3 = new Date(2025, 5, 20);

// خطأ: مقارنة مراجع الكائنات
console.log(date1 === date2); // false (كائنات مختلفة)
console.log(date1 == date2);  // false (كائنات مختلفة)

// صحيح: مقارنة الطوابع الزمنية
console.log(date1.getTime() === date2.getTime()); // true
console.log(date1.getTime() < date3.getTime());   // true
console.log(date3.getTime() > date1.getTime());   // true

// يمكنك استخدام عوامل المقارنة مباشرة على كائنات Date
// لأنها تُحول إلى أرقام (طوابع زمنية)
console.log(date1 < date3);  // true
console.log(date3 > date1);  // true

// دوال مساعدة لمقارنة التواريخ
function isSameDay(d1, d2) {
    return d1.getFullYear() === d2.getFullYear() &&
           d1.getMonth() === d2.getMonth() &&
           d1.getDate() === d2.getDate();
}

function isBefore(d1, d2) {
    return d1.getTime() < d2.getTime();
}

function isAfter(d1, d2) {
    return d1.getTime() > d2.getTime();
}

function isBetween(date, start, end) {
    const t = date.getTime();
    return t >= start.getTime() && t <= end.getTime();
}

console.log(isSameDay(date1, date2)); // true
console.log(isBefore(date1, date3));  // true

اعتبارات المنطقة الزمنية

المناطق الزمنية هي أحد أصعب جوانب العمل مع التواريخ. كائنات Date في JavaScript تُخزن دائما بتوقيت UTC داخليا لكن معظم دوال الاسترجاع والإعداد تعيد القيم بالمنطقة الزمنية المحلية للمستخدم. فهم هذه الازدواجية أمر أساسي لبناء تطبيقات تعمل بشكل صحيح عبر المناطق الزمنية.

مثال: العمل مع المناطق الزمنية

const date = new Date('2025-06-15T12:00:00Z'); // ظهر UTC

// getTimezoneOffset يعيد الفرق بالدقائق
// بين UTC والتوقيت المحلي (موجب = خلف UTC)
const offset = date.getTimezoneOffset();
console.log(`فرق المنطقة الزمنية: ${offset} دقيقة`);
console.log(`فرق المنطقة الزمنية: ${offset / 60} ساعة`);

// عرض الوقت في مناطق زمنية مختلفة
const timezones = ['America/New_York', 'Europe/London', 'Asia/Tokyo', 'Asia/Riyadh'];

timezones.forEach(tz => {
    const formatted = date.toLocaleString('en-US', { timeZone: tz });
    console.log(`${tz}: ${formatted}`);
});

// تحويل التوقيت المحلي إلى منطقة زمنية محددة
function getTimeInZone(date, timezone) {
    return new Intl.DateTimeFormat('en-US', {
        timeZone: timezone,
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
        hour: '2-digit',
        minute: '2-digit',
        second: '2-digit',
        hour12: false
    }).format(date);
}

console.log(getTimeInZone(new Date(), 'Asia/Riyadh'));
console.log(getTimeInZone(new Date(), 'America/New_York'));
تحذير: لا تخزن التواريخ أبدا كنصوص محلية في قاعدة البيانات. خزنها دائما بتوقيت UTC (باستخدام تنسيق ISO 8601 أو طوابع Unix الزمنية) وحولها إلى المنطقة الزمنية المحلية للمستخدم فقط عند العرض. هذا يمنع الغموض ويجعل من الممكن مقارنة وترتيب التواريخ بشكل صحيح بغض النظر عن موقع المستخدمين.

بناء ساعة رقمية حية

لنطبق ما تعلمناه لبناء ساعة حية عملية تتحدث كل ثانية. هذا المثال يوضح الحصول على الوقت الحالي وتنسيقه واستخدام setInterval للتحديث المستمر.

مثال: الساعة الرقمية

function updateClock() {
    const now = new Date();

    const hours = String(now.getHours()).padStart(2, '0');
    const minutes = String(now.getMinutes()).padStart(2, '0');
    const seconds = String(now.getSeconds()).padStart(2, '0');

    const timeString = `${hours}:${minutes}:${seconds}`;

    const days = ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء',
                  'الخميس', 'الجمعة', 'السبت'];
    const months = ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو',
                    'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'];

    const dayName = days[now.getDay()];
    const monthName = months[now.getMonth()];
    const dateString = `${dayName}، ${now.getDate()} ${monthName} ${now.getFullYear()}`;

    // إذا كان يعمل في المتصفح:
    // document.getElementById('clock').textContent = timeString;
    // document.getElementById('date').textContent = dateString;

    console.log(`${dateString} -- ${timeString}`);
}

// تحديث كل ثانية
updateClock(); // تشغيل فوري
setInterval(updateClock, 1000);

// للاستخدام في HTML:
// <div id="clock"></div>
// <div id="date"></div>

بناء حاسبة العمر

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

مثال: حاسبة عمر دقيقة

function calculateAge(birthDate) {
    const today = new Date();
    const birth = new Date(birthDate);

    let years = today.getFullYear() - birth.getFullYear();
    let months = today.getMonth() - birth.getMonth();
    let days = today.getDate() - birth.getDate();

    // تعديل إذا لم يأتِ اليوم بعد هذا الشهر
    if (days < 0) {
        months--;
        // الحصول على عدد أيام الشهر السابق
        const prevMonth = new Date(today.getFullYear(), today.getMonth(), 0);
        days += prevMonth.getDate();
    }

    // تعديل إذا لم يأتِ الشهر بعد هذا العام
    if (months < 0) {
        years--;
        months += 12;
    }

    return { years, months, days };
}

const age = calculateAge('1995-03-20');
console.log(`العمر: ${age.years} سنة، ${age.months} شهر، ${age.days} يوم`);

// الأيام حتى عيد الميلاد القادم
function daysUntilBirthday(birthDate) {
    const today = new Date();
    const birth = new Date(birthDate);

    let nextBirthday = new Date(
        today.getFullYear(),
        birth.getMonth(),
        birth.getDate()
    );

    // إذا مر عيد الميلاد هذا العام استخدم العام القادم
    if (nextBirthday < today) {
        nextBirthday.setFullYear(today.getFullYear() + 1);
    }

    const diffMs = nextBirthday.getTime() - today.getTime();
    return Math.ceil(diffMs / (1000 * 60 * 60 * 24));
}

console.log(`الأيام حتى عيد الميلاد: ${daysUntilBirthday('1995-03-20')}`);

// التحقق من السنة الكبيسة
function isLeapYear(year) {
    return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
}

console.log(isLeapYear(2024)); // true
console.log(isLeapYear(2025)); // false

الوقت النسبي ("منذ 3 أيام")

التطبيقات الحديثة غالبا تعرض الأوقات بتنسيق نسبي مثل "منذ 5 دقائق" أو "بعد 3 أيام". يمكنك بناء هذا باستخدام واجهة Intl.RelativeTimeFormat أو دالة مخصصة.

مثال: عرض الوقت النسبي

// استخدام Intl.RelativeTimeFormat
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });

console.log(rtf.format(-1, 'day'));    // "yesterday"
console.log(rtf.format(1, 'day'));     // "tomorrow"
console.log(rtf.format(-3, 'day'));    // "3 days ago"
console.log(rtf.format(2, 'hour'));    // "in 2 hours"
console.log(rtf.format(-1, 'week'));   // "last week"

// الوقت النسبي بالعربية
const rtfAr = new Intl.RelativeTimeFormat('ar', { numeric: 'auto' });
console.log(rtfAr.format(-1, 'day'));  // "أمس"
console.log(rtfAr.format(-3, 'day')); // "قبل 3 أيام"

// دالة مخصصة لاختيار الوحدة تلقائيا
function timeAgo(date) {
    const now = new Date();
    const diffMs = now.getTime() - new Date(date).getTime();
    const diffSeconds = Math.floor(diffMs / 1000);
    const diffMinutes = Math.floor(diffSeconds / 60);
    const diffHours = Math.floor(diffMinutes / 60);
    const diffDays = Math.floor(diffHours / 24);
    const diffWeeks = Math.floor(diffDays / 7);
    const diffMonths = Math.floor(diffDays / 30);
    const diffYears = Math.floor(diffDays / 365);

    const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });

    if (diffSeconds < 60) return rtf.format(-diffSeconds, 'second');
    if (diffMinutes < 60) return rtf.format(-diffMinutes, 'minute');
    if (diffHours < 24) return rtf.format(-diffHours, 'hour');
    if (diffDays < 7) return rtf.format(-diffDays, 'day');
    if (diffWeeks < 4) return rtf.format(-diffWeeks, 'week');
    if (diffMonths < 12) return rtf.format(-diffMonths, 'month');
    return rtf.format(-diffYears, 'year');
}

// اختبار بتواريخ مختلفة
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
console.log(timeAgo(fiveMinutesAgo)); // "5 minutes ago"

const twoDaysAgo = new Date(Date.now() - 2 * 24 * 60 * 60 * 1000);
console.log(timeAgo(twoDaysAgo)); // "2 days ago"

const threeMonthsAgo = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000);
console.log(timeAgo(threeMonthsAgo)); // "3 months ago"

أدوات عملية

إليك بعض دوال التاريخ الأكثر شيوعا التي تجمع كل ما تعلمناه.

مثال: دوال أدوات التاريخ المفيدة

// الحصول على عدد أيام الشهر
function getDaysInMonth(year, month) {
    return new Date(year, month + 1, 0).getDate();
}
console.log(getDaysInMonth(2025, 1)); // 28 (فبراير 2025)
console.log(getDaysInMonth(2024, 1)); // 29 (فبراير 2024، سنة كبيسة)

// الحصول على أول وآخر يوم في الشهر
function getMonthRange(year, month) {
    const firstDay = new Date(year, month, 1);
    const lastDay = new Date(year, month + 1, 0);
    return { firstDay, lastDay };
}
const june = getMonthRange(2025, 5);
console.log(june.firstDay); // 1 يونيو
console.log(june.lastDay);  // 30 يونيو

// التحقق من صلاحية التاريخ
function isValidDate(dateString) {
    const date = new Date(dateString);
    return !isNaN(date.getTime());
}
console.log(isValidDate('2025-06-15')); // true
console.log(isValidDate('invalid'));     // false

// تنسيق التاريخ كـ YYYY-MM-DD
function formatDate(date) {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const day = String(date.getDate()).padStart(2, '0');
    return `${year}-${month}-${day}`;
}
console.log(formatDate(new Date(2025, 5, 15))); // "2025-06-15"

// الحصول على مصفوفة تواريخ بين تاريخين
function getDateRange(startDate, endDate) {
    const dates = [];
    const current = new Date(startDate);
    const end = new Date(endDate);

    while (current <= end) {
        dates.push(new Date(current));
        current.setDate(current.getDate() + 1);
    }
    return dates;
}

const range = getDateRange('2025-06-01', '2025-06-07');
range.forEach(d => console.log(formatDate(d)));
// يطبع من 1 يونيو إلى 7 يونيو
ملاحظة: بينما كائن Date المدمج كافٍ للعديد من المهام فإن التطبيقات الكبيرة التي تحتاج معالجة متقدمة للمناطق الزمنية وأنظمة التقويم أو معالجات تاريخ معقدة قد تستفيد من واجهة Temporal الأحدث (حاليا في المرحلة 3 من مقترحات TC39) أو مكتبات معروفة. لمعظم حالات الاستخدام المغطاة في هذا الدرس يعمل كائن Date الأصلي بشكل ممتاز.

تمرين عملي

ابنِ تطبيق أدوات تاريخ شامل يتضمن الميزات التالية: (1) مؤقت عد تنازلي يعرض الأيام والساعات والدقائق والثواني حتى تاريخ مستهدف يقدمه المستخدم. (2) حاسبة عمر تقبل تاريخ ميلاد وتعرض العمر الدقيق بالسنوات والشهور والأيام مع عدد الأيام حتى عيد الميلاد القادم. (3) منسق تاريخ يأخذ مدخل تاريخ ويعرضه في خمسة تنسيقات مختلفة على الأقل تشمل ISO والتنسيق الأمريكي والتنسيق الأوروبي وتنسيق طويل قابل للقراءة وتنسيق وقت نسبي مثل "منذ 3 أشهر". (4) محول منطقة زمنية يعرض الوقت الحالي في أربع مناطق زمنية مختلفة على الأقل في وقت واحد. اختبر كل دالة بحالات حدية تشمل السنوات الكبيسة وحدود الشهور وحدود السنوات والانتقال بين 31 ديسمبر و1 يناير.