تعلم كيفية تنسيق وتحويل الوقت في JavaScript بدون مفاجآت: الطوابع الزمنية، سلاسل ISO، المناطق الزمنية، التوقيت الصيفي، قواعد التحليل، وأنماط موثوقة.

أخطاء الوقت في JavaScript نادرًا ما تظهر كـ "الساعة خاطئة" بصراحة. تظهر على شكل تغييرات صغيرة مربكة: تاريخ صحيح على حاسوبك لكن خاطئ على جهاز زميل، استجابة API تبدو سليمة حتى تُعرض في منطقة زمنية مختلفة، أو تقرير "خاطئ بيوم واحد" حول تغيير موسمي.
ستلاحظ عادة واحدًا (أو أكثر) من هذه الأمور:
+02:00) مختلف عما توقعت.مصدر كبير للمشاكل هو أن كلمة الوقت يمكن أن تشير إلى مفاهيم مختلفة:
كائن Date المدمج في JavaScript يحاول تغطية كل هذه الحالات، لكنه يمثل في الأساس لحظة زمنية بينما يدفعك باستمرار نحو العرض المحلي، مما يجعل التحويلات العرضية سهلة الحدوث.
هذا الدليل عملي عمداً: كيفية الحصول على تحويلات متوقعة عبر المتصفحات والخوادم، كيفية اختيار صيغ أكثر أمانًا (مثل ISO 8601)، وكيفية اكتشاف الفخوخ الكلاسيكية (ثواني مقابل ملّي ثانية، UTC مقابل محلي، واختلافات التحليل). الهدف ليس مزيدًا من النظرية—بل أقل "لماذا تحركت القيمة؟" من المفاجآت.
أخطاء الوقت في JavaScript غالبًا تبدأ بخلط تمثيلات تبدو قابلة للاستبدال لكنها ليست كذلك.
1) ملّي ثانية منذ الحقبة (عدد)
عدد عادي مثل 1735689600000 عادةً ما يكون "ملّي ثانية منذ 1970-01-01T00:00:00Z". يمثل لحظة زمنية بدون تنسيق أو منطقة زمنية مرفقة.
2) كائن Date (غلاف حول لحظة)
Date يخزن نفس نوع اللحظة مثل الطابع الزمني. الجزء المربك: عندما تطبع Date، يقوم JavaScript بتنسيقه باستخدام قواعد البيئة المحلية ما لم تطلب خلاف ذلك.
3) سلسلة منسقة (عرض بشري)
سلاسل مثل "2025-01-01"، "01/01/2025 10:00"، أو "2025-01-01T00:00:00Z" ليست كلّها متشابهة. بعضها غير غامض (ISO 8601 مع Z)، والبعض الآخر يعتمد على الإقليم، وبعضها لا يتضمن منطقة زمنية إطلاقًا.
يمكن أن تعرض نفس اللحظة بشكل مختلف حسب المنطقة الزمنية:
const instant = new Date("2025-01-01T00:00:00Z");
instant.toLocaleString("en-US", { timeZone: "UTC" });
// "1/1/2025, 12:00:00 AM"
instant.toLocaleString("en-US", { timeZone: "America/Los_Angeles" });
// "12/31/2024, 4:00:00 PM" (اليوم السابق)
اختر تمثيلًا داخليًا واحدًا (عادةً ملّي ثانية منذ الحقبة أو ISO 8601 UTC) والتزم به عبر التطبيق وواجهات الـ API. حوّل إلى/من Date والسلاسل المنسقة فقط عند الحدود: تحليل الإدخال وعرض الواجهة.
"الطابع الزمني" عادة يعني زمن الحقبة (Unix time): عدد الوحدات منذ 1970-01-01 00:00:00 UTC. المشكلة: أنظمة مختلفة تستخدم وحدات مختلفة.
Date في JavaScript هو مصدر معظم الارتباك لأنه يستخدم الملّي ثانية. العديد من واجهات الـ API، قواعد البيانات، والسجلات تستخدم الثواني.
17040672001704067200000نفس اللحظة، لكن نسخة الملّي ثانية تحتوي ثلاث أرقام إضافية.
استخدم ضرب/قسمة صريحة لكي يكون الوحدة واضحة:
// seconds -> Date
const seconds = 1704067200;
const d1 = new Date(seconds * 1000);
// milliseconds -> Date
const ms = 1704067200000;
const d2 = new Date(ms);
// Date -> seconds
const secondsOut = Math.floor(d2.getTime() / 1000);
// Date -> milliseconds
const msOut = d2.getTime();
Date()هذا يبدو معقولًا، لكنه خاطئ عندما تكون ts بالثواني:
const ts = 1704067200; // seconds
const d = new Date(ts); // WRONG: treated as milliseconds
النتيجة ستكون تاريخًا في 1970، لأن 1,704,067,200 ملّي ثانية هو فقط نحو 19 يومًا بعد الحقبة.
عندما لا تكون متأكدًا من الوحدة، أضف حواجز سريعة:
function asDateFromUnknownEpoch(x) {
// heuristic بسيط: الثواني ~1e9-1e10، الملّي ثانية ~1e12-1e13
if (x < 1e11) return new Date(x * 1000); // افترض ثواني
return new Date(x); // افترض ملّي ثانية
}
const input = Number(valueFromApi);
console.log({ input, digits: String(Math.trunc(input)).length });
console.log('as ISO:', asDateFromUnknownEpoch(input).toISOString());
إذا كان عدد "الأرقام" ~10 فربما تكون ثواني. إذا كان ~13 فربما ملّي ثانية. اطبع toISOString() أثناء التصحيح: إنها غير غامضة وتساعدك على اكتشاف أخطاء الوحدة فورًا.
Date في JavaScript يمكن أن يكون مربكًا لأنه يخزن لحظة واحدة في الزمن، لكنه يعرض تلك اللحظة بمناطق زمنية مختلفة. داخليًا، Date هو أساسًا "ملّي ثانية منذ حقبة Unix"، ذلك الرقم يمثل لحظة في UTC. يحدث "التحول" عندما تطلب من JavaScript تنسيق تلك اللحظة كـ وقت محلي (بناءً على إعدادات الحاسوب/الخادم) مقابل UTC.
لعديد من واجهات Date هناك إصدارات محلية وإصدارات UTC. تعيد أرقامًا مختلفة لنفس اللحظة:
const d = new Date('2025-01-01T00:30:00Z');
d.getHours(); // ساعة بالـ *وقت المحلي*
d.getUTCHours(); // ساعة بالـ UTC
d.toString(); // سلسلة الوقت المحلي
d.toISOString(); // UTC (دائمًا تنتهي بـ Z)
إذا كان جهازك في نيويورك (UTC-5)، قد تظهر تلك اللحظة كـ "19:30" في اليوم السابق محليًا. على خادم مضبوط على UTC ستظهر كـ "00:30". نفس اللحظة، عرض مختلف.
السجلات غالبًا ما تستخدم Date#toString() أو تدرج Date ضمن سلسلة ضمنيًا، والذي يستخدم المنطقة الزمنية المحلية للبيئة. هذا يعني أن نفس الكود قد يطبع طوابع زمنية مختلفة على حاسوبك المحمول، في CI، وفي الإنتاج.
خزن وانقل الوقت كـ UTC (مثلًا ملّي ثانية منذ الحقبة أو ISO 8601 مع Z). حوّل إلى زمن المستخدم فقط عند العرض:
toISOString() أو مرر ملّي ثانية منذ الحقبةIntl.DateTimeFormatإذا كنت تبني تطبيقًا بسرعة، من المفيد تضمين هذا في عقود الـ API مبكرًا: سم الحقول بوضوح (createdAtMs, createdAtIso) وابق الخادم (Go + PostgreSQL) والعميل (React) متوافقين حول ما تمثله كل حقول.
إذا احتجت لإرسال تواريخ/أوقات بين المتصفح، الخادم، وقاعدة البيانات، فـ سلاسل ISO 8601 هي الافتراض الآمن. هي صريحة، مدعومة على نطاق واسع، والأهم أنها تحمل معلومات المنطقة الزمنية.
صيغانان جيدتان للتبادل:
2025-03-04T12:30:00Z2025-03-04T12:30:00+02:00ماذا يعني "Z"؟
Z تُشير إلى Zulu time، وهو اسم آخر لـ UTC. إذًا 2025-03-04T12:30:00Z يعني "12:30 بتوقيت UTC".
متى يهم التعويض مثل +02:00؟
التعويضات مهمة عندما يكون الحدث مربوطًا بسياق منطقة زمنية محلية (مواعيد، حجوزات، أوقات فتح المتاجر). 2025-03-04T12:30:00+02:00 يصف لحظة متقدمة بساعتين عن UTC، وليست نفس اللحظة مثل 2025-03-04T12:30:00Z.
سلاسل مثل 03/04/2025 فخ: هل هو 4 مارس أم 3 أبريل؟ تفسرها بيئات ومستخدمون مختلفون بشكل مختلف. فضّل 2025-03-04 (تاريخ ISO) أو تاريخ ISO كامل.
const iso = "2025-03-04T12:30:00Z";
const d = new Date(iso);
const back = d.toISOString();
console.log(iso); // 2025-03-04T12:30:00Z
console.log(back); // 2025-03-04T12:30:00.000Z
هذا السلوك "الذهاب والإياب" هو بالضبط ما تريده للواجهات: متسق، متوقع، ومدرك للمنطقة الزمنية.
Date.parse() يبدو مريحًا: أعطه سلسلة، استرجع طابعًا زمنيًا. المشكلة أن أي شيء ليس بصيغة ISO 8601 واضحة قد يعتمد على التخمينات في المتصفح. تلك التخمينات اختلفت عبر محركات المتصفحات وإصداراتها، مما يعني أن نفس المدخل قد يُحلل بشكل مختلف (أو لا يُحلل) اعتمادًا على أين يعمل كودك.
Date.parse()JavaScript يقيّد التحليل بشكل موثوق لسلاسل شبيهة بـ ISO 8601 (وحتى عندها قد تهم تفاصيل مثل المنطقة الزمنية). بالنسبة للصيغ "الصديقة"—مثل "03/04/2025", "March 4, 2025", أو "2025-3-4"—قد تفسر المتصفحات:
إذا لم تستطع التنبؤ بشكل السلسلة بدقة، فلا يمكنك التنبؤ بالنتيجة.
YYYY-MM-DDفخ شائع هو الشكل البسيط "YYYY-MM-DD" (مثل "2025-01-15"). يتوقع كثير من المطورين أن يُفسر كمنتصف الليل المحلي. في الواقع، يعامل بعض البيئات هذا الشكل كـ منتصف الليل UTC.
هذا الاختلاف مهم: منتصف الليل في UTC عند تحويله إلى وقت محلي يمكن أن يصبح اليوم السابق في المناطق سلبية التعويض (مثل الأمريكتين) أو يحرك الساعة بشكل غير متوقع. هذه طريقة سهلة للحصول على أخطاء "لماذا تاريخي يختلف بيوم؟".
لـ إدخال الخادم/الـ API:
2025-01-15T13:45:00Z أو 2025-01-15T13:45:00+02:00."YYYY-MM-DD") وتجنب تحويله إلى Date ما لم تحدد أيضًا المنطقة الزمنية المقصودة.لـ إدخال المستخدم:
03/04/2025 إلا إذا كانت واجهتك تُجبر المعنى.بدلاً من الاعتماد على Date.parse() ليُقرر، اختر أحد الأنماط التالية:
new Date(year, monthIndex, day) للتواريخ المحلية).عندما تكون بيانات الوقت مهمة، "يعمل على جهازي" لا يكفي—اجعل قواعد التحليل صريحة ومتسقة.
إذا كان هدفك "عرض التاريخ/الوقت بالطريقة التي يتوقعها الناس"، أفضل أداة في JavaScript هي Intl.DateTimeFormat. تستخدم قواعد إقليم المستخدم (الترتيب، الفواصل، أسماء الشهور) وتتجنب النهج الهش لبناء السلاسل يدويًا مثل month + '/' + day.
البناء اليدوي غالبًا ما يُدخل تنسيقًا أمريكيًا ثابتًا، ينسى الأصفار البادئة، أو ينتج نتائج مربكة 24/12 ساعة. Intl.DateTimeFormat يجعل أيضًا صريحًا أي منطقة زمنية تعرض—وهذا حاسم عندما يتم تخزين بياناتك في UTC لكن يجب أن يعكس الواجهة زمن المستخدم.
لـ "فقط نسق بشكل جيد"، dateStyle وtimeStyle هما الأبسط:
const d = new Date('2025-01-05T16:30:00Z');
// إقليم المستخدم + منطقة زمنية المستخدم
console.log(new Intl.DateTimeFormat(undefined, {
dateStyle: 'medium',
timeStyle: 'short'
}).format(d));
// فرض منطقة زمنية محددة (ممتاز لأوقات الأحداث)
console.log(new Intl.DateTimeFormat('en-GB', {
dateStyle: 'full',
timeStyle: 'short',
timeZone: 'UTC'
}).format(d));
إذا احتجت دورة ساعات ثابتة (مثلاً تبديل إعداد المستخدم)، استخدم hour12:
console.log(new Intl.DateTimeFormat('en-US', {
hour: 'numeric',
minute: '2-digit',
hour12: true
}).format(d));
اختر دالة تنسيق واحدة لكل "نوع" من الطوابع في واجهتك (وقت رسالة، مدخل سجل، بداية حدث)، واجعل قرار timeZone متعمدًا:
هذا يمنحك مخرجات متسقة وصديقة للإقليم بدون صيانة مجموعة هشة من سلاسل التنسيق المخصصة.
التوقيت الصيفي (DST) هو عندما تغير المنطقة الزمنية تعويضها عن UTC (عادة بساعة واحدة) في تواريخ محددة. الجزء المربك: DST لا يغيّر فقط التعويض—إنه يغيّر وجود أوقات محلية معينة.
عند تقديم الساعة للأمام (spring forward)، مدى من الأوقات المحلية لا يحدث أبدًا. مثال: في العديد من المناطق، تقفز الساعة من 01:59 إلى 03:00، لذا 02:30 المحلي "مفقود".
عند إرجاع الساعة للخلف (fall back)، مدى من الأوقات يحدث مرتين. مثال: 01:30 قد يحدث مرة قبل التغيير ومرة بعده، مما يعني أن نفس وقت الساعة يمكن أن يشير إلى لحظتين مختلفتين.
هاتان ليستا متماثلتين حول حدود DST:
إذا بدأ DST الليلة، فقد تكون "غدًا في 9:00" بعيدًا 23 ساعة فقط. إذا انتهى DST الليلة، قد يكون 25 ساعة.
// سيناريو: جدولة "نفس الوقت المحلي غدًا"
const d = new Date(2025, 2, 8, 9, 0); // 8 مارس، 9:00 محلي
const plus24h = new Date(d.getTime() + 24 * 60 * 60 * 1000);
const nextDaySameLocal = new Date(d);
nextDaySameLocal.setDate(d.getDate() + 1);
// حول DST، plus24h و nextDaySameLocal قد يختلفان بساعة.
إذا قمت بشيء مثل date.setHours(2, 30, 0, 0) في يوم "تقديم الساعة"، قد يقوم JavaScript بتطبيعه إلى وقت صالح آخر (غالبًا 03:30)، لأن 02:30 غير موجود محليًا.
setDate) بدل إضافة الملّي ثانية.Z حتى تكون اللحظة غير غامضة.مصدر شائع للأخطاء هو استخدام Date لتمثيل شيء ليس لحظة تقويم.
الطابع الزمني يجيب "متى حدث هذا؟" (لحظة محددة مثل 2025-12-23T10:00:00Z). الفترة (duration) تجيب "كم المدة؟" (مثل "3 دقائق 12 ثانية"). هذان مفهومان مختلفان وخلطهما يؤدي إلى حسابات مربكة وتأثيرات غير متوقعة بالمنطقة الزمنية/DST.
Date أداة خاطئة للفتراتDate دائمًا يمثل نقطة على الخط الزمني نسبة لحقبة. إذا خزنت "90 ثانية" كـ Date، فأنت في الواقع تخزن "1970-01-01 زائد 90 ثانية" بمنطقة زمنية معينة. تنسيقه قد يظهر فجأة كـ 01:01:30، يتحرك بساعة، أو يأخذ تاريخًا لم تقصده.
للفترات، فضّل الأعداد البسيطة:
HH:mm:ssمُنسق بسيط يعمل لعدادات العد التنازلي وطول الوسائط:
function formatHMS(totalSeconds) {
const s = Math.max(0, Math.floor(totalSeconds));
const hh = String(Math.floor(s / 3600)).padStart(2, "0");
const mm = String(Math.floor((s % 3600) / 60)).padStart(2, "0");
const ss = String(s % 60).padStart(2, "0");
return `${hh}:${mm}:${ss}`;
}
formatHMS(75); // "00:01:15" (عداد تنازلي)
formatHMS(5423); // "01:30:23" (مدة وسائط)
إذا كنت تحول من دقائق، اضرب أولًا (minutes * 60) واحتفظ بالقيمة رقمية حتى العرض.
عند مقارنة الأوقات في JavaScript، الأسلم هو مقارنة الأعداد، ليس النصوص المنسقة. كائن Date هو في الأساس غلاف حول طابع عددي (ملّي ثانية منذ الحقبة)، لذا تريد أن تنتهي المقارنات كـ "عدد مقابل عدد".
استخدم getTime() (أو Date.valueOf()، الذي يعيد نفس الرقم) للمقارنة بشكل موثوق:
const a = new Date('2025-01-10T12:00:00Z');
const b = new Date('2025-01-10T12:00:01Z');
if (a.getTime() < b.getTime()) {
// a أبكر
}
// يعمل أيضًا:
if (+a < +b) {
// unary + يستدعي valueOf()
}
تجنب مقارنة سلاسل منسقة مثل "1/10/2025, 12:00 PM"—هذه تعتمد على الإقليم ولن ترتب بشكل صحيح. الاستثناء الرئيسي هو سلاسل ISO 8601 بنفس الصيغة والمنطقة الزمنية (مثل جميعها تنتهي بـ ...Z)، فهي قابلة للترتيب قحفيًا.
الفرز حسب الوقت بسيط إذا فرزت بواسطة ملّي ثانية منذ الحقبة:
items.sort((x, y) => new Date(x.createdAt).getTime() - new Date(y.createdAt).getTime());
التصفية ضمن نطاق نفس الفكرة:
const start = new Date('2025-01-01T00:00:00Z').getTime();
const end = new Date('2025-02-01T00:00:00Z').getTime();
const inRange = items.filter(i => {
const t = new Date(i.createdAt).getTime();
return t >= start && t < end;
});
"بداية اليوم" تعتمد على ما إذا كنت تقصد الوقت المحلي أو UTC:
// بداية/نهاية اليوم المحلي
const d = new Date(2025, 0, 10); // 10 يناير بالوقت المحلي
const localStart = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0);
const localEnd = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59, 59, 999);
// بداية/نهاية اليوم بالـ UTC
const utcStart = new Date(Date.UTC(2025, 0, 10, 0, 0, 0, 0));
const utcEnd = new Date(Date.UTC(2025, 0, 10, 23, 59, 59, 999));
اختر تعريفًا مبكرًا والتزم به خلال المقارنات والمنطق المتعلق بالنطاق.
أخطاء الوقت تبدو عشوائية حتى تحدد ما لديك (طابع؟ سلسلة؟ Date؟) وأين أُدخل التحول (التحليل، التحويل بالمنطقة الزمنية، التنسيق).
ابدأ بتسجيل نفس القيمة بثلاث طرق مختلفة. هذا يكشف سريعًا ما إذا كانت المشكلة ثوانٍ مقابل ملّي ثانية، محلي مقابل UTC، أو تحليل السلسلة.
console.log('raw input:', input);
const d = new Date(input);
console.log('toISOString (UTC):', d.toISOString());
console.log('toString (local):', d.toString());
console.log('timezone offset (min):', d.getTimezoneOffset());
ما الذي تبحث عنه:
toISOString() خاطئًا بشكل كبير (مثل سنة 1970 أو مستقبل بعيد)، فاشتبِه في ثوانٍ مقابل ملّي ثانية.toISOString() صحيحًا لكن toString() "متحركًا"، فأنت ترى عرض المنطقة الزمنية المحلية.getTimezoneOffset() حسب التاريخ، فأنت تعبر حدود التوقيت الصيفي.العديد من تقارير "يعمل على جهازي" هي ببساطة اختلافات في الإعدادات الافتراضية للبيئة.
console.log(Intl.DateTimeFormat().resolvedOptions());
console.log('TZ:', process.env.TZ);
console.log(Intl.DateTimeFormat().resolvedOptions().timeZone);
إذا كان خادمك يعمل في UTC لكن حاسوبك المحمول في منطقة محلية، فسوف يختلف الإخراج المنسق ما لم تحدد timeZone صراحة.
أنشئ اختبارات وحدة حول حدود DST والأوقات الحدّية:
23:30 → 00:30إذا كنت تتكرر بسرعة، اجعل هذه الاختبارات جزءًا من هيكلك. على سبيل المثال، عند توليد تطبيق React + Go يمكنك إضافة مجموعة "عقد وقت" صغيرة مقدَّمًا (أمثلة حمولة API + تأكيدات التحليل/التنسيق) حتى تُكتشف الانكسارات قبل النشر.
"2025-03-02 10:00".locale و(عند الحاجة) timeZone.المعالجة الموثوقة للوقت في JavaScript تدور حول اختيار "مصدر الحقيقة" والاتساق من التخزين إلى العرض.
اختزن واحسب في UTC. اعتبر الوقت المحلي للمستخدم تفصيل عرض فقط.
انقل التواريخ بين الأنظمة كسلاسل ISO 8601 مع تعويض صريح (ويفضل Z). إذا اضطررت لإرسال أرقام، وثّق الوحدة وابقها ثابتة (الملّي ثانية هو الافتراض الشائع في JS).
انسق للعرض البشري بـ Intl.DateTimeFormat (أو toLocaleString) ومرّر timeZone صريحًا عندما تحتاج إلى إخراج حتمي (مثلاً، عرض الأوقات دائمًا بـ UTC أو منطقة أعمال محددة).
Z (مثال: 2025-12-23T10:15:00Z). إذا استعملت الطوابع العددية، اجعل اسم الحقل مثل createdAtMs ليكون الوحدة واضحة.فكّر في مكتبة مخصصة إذا احتجت أحداث متكررة، قواعد منطقة زمنية معقدة، حسابات آمنة بالنسبة لـ DST ("نفس الوقت المحلي غدًا"), أو الكثير من التحليل من مدخلات غير متسقة. القيمة في واجهات أوضح وخطأ أقل لحالات الحافة.
إذا أردت التعمق أكثر، تصفّح المزيد من الأدلة المتعلقة بالوقت على /blog. إذا كنت تقيم أدوات أو خيارات دعم، انظر /pricing.