الدليل النهائي للتلاعب بالتاريخ والوقت

نشرت: 2022-03-11

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

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

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

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

بالنسبة للمبتدئين ، فإن الإجابة الصحيحة لكود الإنتاج هي دائمًا استخدام مكتبة مناسبة بدلاً من إنشاء مكتبة خاصة بك. الصعوبات المحتملة في حساب DateTime التي تمت مناقشتها في هذه المقالة ليست سوى غيض من فيض ، ولكن لا يزال من المفيد معرفتها ، مع وجود مكتبة أو بدونها.

تساعد مكتبات التاريخ والوقت إذا فهمتها بشكل صحيح

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

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

أيضًا ، يمكن لمكتبات التاريخ / الوقت أن تأخذك فقط حتى الآن. تعمل جميع مكتبات التاريخ من خلال منحك الوصول إلى هياكل البيانات الملائمة لتمثيل التاريخ والوقت. إذا كنت تقوم بإرسال واستقبال البيانات من خلال واجهة برمجة تطبيقات REST ، فستحتاج في النهاية إلى تحويل التاريخ إلى سلسلة والعكس صحيح لأن JSON ليس لديها بنية بيانات أصلية لتمثيل DateTime. ستساعدك المفاهيم التي أوضحتها هنا على تجنب بعض المشكلات الشائعة التي قد تطرأ عند إجراء عمليات التحويل من تاريخ إلى سلسلة ومن سلسلة حتى تاريخه.

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

توحيد الوقت

التاريخ والوقت هي نقطة زمنية محددة للغاية. دعونا نفكر في هذا. أثناء كتابة هذا المقال ، تظهر الساعة على الكمبيوتر المحمول يوم 21 يوليو الساعة 1:29 مساءً. هذا ما نطلق عليه "التوقيت المحلي" ، الوقت الذي أراه على ساعات الحائط حولي وعلى ساعة معصمي.

امنح أو خذ بضع دقائق ، إذا طلبت من صديقتي مقابلتي في مقهى قريب الساعة 3:00 مساءً ، يمكنني أن أتوقع رؤيتها هناك في ذلك الوقت تقريبًا. وبالمثل ، لن يكون هناك أي لبس إذا قلت بدلاً من ذلك ، على سبيل المثال ، "لنلتقي بعد ساعة ونصف". نتحدث بشكل روتيني عن الوقت بهذه الطريقة مع الأشخاص الذين يعيشون في نفس المدينة أو المنطقة الزمنية.

لنفكر في سيناريو مختلف: أريد أن أخبر صديقًا يعيش في أوبسالا بالسويد أنني أريد التحدث إليه في الخامسة مساءً. أرسل له رسالة ، "مرحبًا أنطون ، لنتحدث الساعة 5 مساءً." أحصل على الرد على الفور ، "وقتك أم وقتي؟"

أخبرني أنطون أنه يعيش في المنطقة الزمنية لأوروبا الوسطى وهي UTC + 01:00. أنا أعيش في UTC + 05: 45. هذا يعني أنه عندما تكون الساعة 5 مساءً حيث أعيش ، تكون الساعة 5 مساءً - 5:45 = 11:15 صباحًا بالتوقيت العالمي المنسق ، وهو ما يترجم إلى 11:15 صباحًا بالتوقيت العالمي المنسق + 01:00 = 12:15 مساءً في أوبسالا ، وهو مثالي لكليهما منا.

انتبه أيضًا إلى الاختلاف بين المنطقة الزمنية (توقيت أوروبا الوسطى) وإزاحة المنطقة الزمنية (UTC + 05: 45). يمكن للبلدان أن تقرر تغيير إزاحات المنطقة الزمنية الخاصة بها للتوقيت الصيفي لأسباب سياسية أيضًا. كل عام تقريبًا ، هناك تغيير في القواعد في بلد واحد على الأقل ، مما يعني أن أي كود به هذه القواعد المخبأة يجب أن يظل محدثًا - يجدر التفكير في ما تعتمد عليه قاعدة التعليمات البرمجية الخاصة بك لهذا لكل طبقة من تطبيقك.

وهذا سبب وجيه آخر نوصي بأن تتعامل الواجهة الأمامية فقط مع المناطق الزمنية في معظم الحالات. عندما لا يحدث ذلك ، ماذا يحدث عندما لا تتطابق القواعد التي يستخدمها محرك قاعدة البيانات لديك مع تلك الموجودة في الواجهة الأمامية أو الخلفية؟

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

توحيد التنسيق

يعد توحيد الوقت أمرًا رائعًا لأنني بحاجة فقط إلى تخزين التوقيت العالمي المنسق (UTC) وطالما أنني أعرف المنطقة الزمنية للمستخدم ، يمكنني دائمًا التحويل إلى وقتهم. على العكس من ذلك ، إذا كنت أعرف التوقيت المحلي للمستخدم وأعرف منطقته الزمنية ، فيمكنني تحويل ذلك إلى التوقيت العالمي المنسق (UTC).

لكن يمكن تحديد التواريخ والأوقات بتنسيقات عديدة مختلفة. بالنسبة إلى التاريخ ، يمكنك كتابة "30 يوليو" أو "30 يوليو" أو "7/30" (أو 30/7 ، حسب المكان الذي تعيش فيه). في الوقت الحالي ، يمكنك كتابة "9:30 مساءً" أو "2130".

اجتمع العلماء من جميع أنحاء العالم لمعالجة هذه المشكلة وقرروا تنسيقًا لوصف الوقت الذي يحبه المبرمجون حقًا لأنه قصير ودقيق. نود أن نطلق عليه "تنسيق تاريخ ISO" ، وهو إصدار مبسط من تنسيق ISO-8601 الممتد ويبدو كالتالي:

صورة تعرض إصدارًا مبسطًا من تنسيق ISO-8601 الموسع يسمى تنسيق التاريخ ISO.

بالنسبة إلى 00:00 أو UTC ، نستخدم "Z" بدلاً من ذلك ، مما يعني وقت الزولو ، وهو اسم آخر لـ UTC.

معالجة التاريخ والحساب في JavaScript

قبل أن نبدأ بأفضل الممارسات ، سنتعرف على معالجة التاريخ باستخدام JavaScript لفهم بناء الجملة والمفاهيم العامة. على الرغم من أننا نستخدم JavaScript ، إلا أنه يمكنك تكييف هذه المعلومات مع لغة البرمجة المفضلة لديك بسهولة.

سنستخدم حساب التاريخ لحل المشكلات الشائعة المتعلقة بالتاريخ والتي يصادفها معظم المطورين.

هدفي هو جعلك تشعر بالراحة عند إنشاء كائن تاريخ من سلسلة واستخراج مكونات من سلسلة. هذا شيء يمكن أن تساعدك به مكتبة التاريخ ، ولكن من الأفضل دائمًا أن تفهم كيف يتم ذلك وراء الكواليس.

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

كائن تاريخ JavaScript

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

نظرًا للاختلافات بين تطبيقات المستعرض والمعالجة غير الصحيحة للتوقيت الصيفي (DST) ، لا يُنصح بالاعتماد على كائن التاريخ للتطبيقات ذات المهام الحرجة ، ومن المحتمل أن تستخدم مكتبة DateTime مثل Luxon أو date-fns أو dayjs. (مهما كان ما تستخدمه ، تجنب Moment.js التي كانت شائعة في يوم من الأيام — غالبًا ما تسمى ببساطة moment ، كما تظهر في التعليمات البرمجية — نظرًا لأنها أصبحت مهملة الآن.)

ولكن للأغراض التعليمية ، سنستخدم الطرق التي يوفرها كائن Date () لمعرفة كيفية تعامل JavaScript مع DateTime.

الحصول على التاريخ الحالي

 const currentDate = new Date();

إذا لم تمرر أي شيء إلى مُنشئ التاريخ ، فإن كائن التاريخ الذي تم إرجاعه يحتوي على التاريخ والوقت الحاليين.

يمكنك بعد ذلك تنسيقه لاستخراج جزء التاريخ فقط على النحو التالي:

 const currentDate = new Date(); const currentDayOfMonth = currentDate.getDate(); const currentMonth = currentDate.getMonth(); // Be careful! January is 0, not 1 const currentYear = currentDate.getFullYear(); const dateString = currentDayOfMonth + "-" + (currentMonth + 1) + "-" + currentYear; // "27-11-2020"

ملاحظة: مأزق "كانون الثاني (يناير) صفر" أمر شائع ولكنه ليس عالميًا. يجدر التحقق مرة أخرى من توثيق أي لغة (أو تنسيق التكوين: على سبيل المثال ، يعتمد cron بشكل ملحوظ على 1) قبل البدء في استخدامه.

الحصول على الطابع الزمني الحالي

إذا كنت تريد بدلاً من ذلك الحصول على الطابع الزمني الحالي ، فيمكنك إنشاء كائن تاريخ جديد واستخدام طريقة getTime ().

 const currentDate = new Date(); const timestamp = currentDate.getTime();

في JavaScript ، الطابع الزمني هو عدد المللي ثانية التي مرت منذ 1 يناير 1970.

إذا كنت لا تنوي دعم <IE8 ، فيمكنك استخدام Date.now() للحصول على الطابع الزمني مباشرة دون الحاجة إلى إنشاء كائن تاريخ جديد.

اعراب تاريخ

يتم تحويل سلسلة إلى كائن تاريخ JavaScript بطرق مختلفة.

يقبل مُنشئ كائن التاريخ مجموعة متنوعة من تنسيقات التاريخ:

 const date1 = new Date("Wed, 27 July 2016 13:30:00"); const date2 = new Date("Wed, 27 July 2016 07:45:00 UTC"); const date3 = new Date("27 July 2016 13:30:00 UTC+05:45");

لاحظ أنك لست بحاجة إلى تضمين يوم الأسبوع لأن JS يمكنها تحديد يوم الأسبوع لأي تاريخ.

يمكنك أيضًا تمرير السنة والشهر واليوم والساعات والدقائق والثواني كوسيطات منفصلة:

 const date = new Date(2016, 6, 27, 13, 30, 0);

بالطبع ، يمكنك دائمًا استخدام تنسيق تاريخ ISO:

 const date = new Date("2016-07-27T07:45:00Z");

ومع ذلك ، قد تواجه مشكلة عندما لا تقدم المنطقة الزمنية بشكل صريح!

 const date1 = new Date("25 July 2016"); const date2 = new Date("July 25, 2016");

يمنحك أي من هذين الأمرين 25 يوليو 2016 الساعة 00:00:00 بالتوقيت المحلي.

إذا كنت تستخدم تنسيق ISO ، حتى إذا أعطيت التاريخ فقط وليس الوقت والمنطقة الزمنية ، فسيقبل تلقائيًا المنطقة الزمنية على أنها التوقيت العالمي المنسق (UTC).

هذا يعني ذاك:

 new Date("25 July 2016").getTime() !== new Date("2016-07-25").getTime() new Date("2016-07-25").getTime() === new Date("2016-07-25T00:00:00Z").getTime()

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

لحسن الحظ ، تحتوي JavaScript الحديثة على بعض وظائف التدويل المريحة المضمنة في مساحة الاسم القياسية Intl التي تجعل تنسيق التاريخ عملية مباشرة.

لهذا سنحتاج إلى كائنين: Date و Intl.DateTimeFormat ، تمت تهيئتهما باستخدام تفضيلات الإخراج لدينا. لنفترض أننا نرغب في استخدام التنسيق الأمريكي (M / D / YYYY) ، سيبدو هذا كما يلي:

 const firstValentineOfTheDecade = new Date(2020, 1, 14); // 1 for February const enUSFormatter = new Intl.DateTimeFormat('en-US'); console.log(enUSFormatter.format(firstValentineOfTheDecade)); // 2/14/2020

إذا أردنا بدلاً من ذلك التنسيق الهولندي (D / M / YYYY) ، فسنقوم فقط بتمرير رمز ثقافة مختلف إلى مُنشئ DateTimeFormat :

 const nlBEFormatter = new Intl.DateTimeFormat('nl-BE'); console.log(nlBEFormatter.format(firstValentineOfTheDecade)); // 14/2/2020

أو شكل أطول من التنسيق الأمريكي ، مع توضيح اسم الشهر:

 const longEnUSFormatter = new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric', }); console.log(longEnUSFormatter.format(firstValentineOfTheDecade)); // February 14, 2020

الآن ، إذا أردنا تنسيقًا ترتيبيًا مناسبًا في يوم من الشهر - أي "الرابع عشر" بدلاً من "14" فقط - فهذا للأسف يحتاج إلى القليل من الحل ، لأن القيم الصالحة فقط day حتى كتابة هذه السطور هي "numeric" أو "2-digit" . استعارة نسخة Flavio Copes من كود Mathias Bynens للاستفادة من جزء آخر من Intl لهذا ، يمكننا تخصيص إخراج يوم الشهر عبر formatToParts() :

 const pluralRules = new Intl.PluralRules('en-US', { type: 'ordinal' }) const suffixes = { 'one': 'st', 'two': 'nd', 'few': 'rd', 'other': 'th' } const convertToOrdinal = (number) => `${number}${suffixes[pluralRules.select(number)]}` // At this point: // convertToOrdinal("1") === "1st" // convertToOrdinal("2") === "2nd" // etc. const extractValueAndCustomizeDayOfMonth = (part) => { if (part.type === "day") { return convertToOrdinal(part.value); } return part.value; }; console.log( longEnUSFormatter.formatToParts(firstValentineOfTheDecade) .map(extractValueAndCustomizeDayOfMonth) .join("") ); // February 14th, 2020

لسوء الحظ ، لا يدعم Internet Explorer (IE) formatToParts على الإطلاق حتى كتابة هذه السطور ، ولكن جميع تقنيات سطح المكتب والجوّال والخلفية (مثل Node.js) تدعمها. بالنسبة لأولئك الذين يحتاجون إلى دعم IE ويحتاجون تمامًا إلى ترتيبي ، فإن sidenote أدناه (أو الأفضل ، مكتبة التاريخ المناسبة) يوفر إجابة.

إذا كنت بحاجة إلى دعم المتصفحات القديمة مثل IE قبل الإصدار 11 ، فإن تنسيق التاريخ في JavaScript يكون أصعب لأنه لم تكن هناك وظائف قياسية لتنسيق التاريخ مثل strftime في Python أو PHP.

في PHP على سبيل المثال ، تمنحك الدالة strftime("Today is %b %d %Y %X", mktime(5,10,0,12,30,99)) Today is Dec 30 1999 05:10:00 .

يمكنك استخدام مجموعة مختلفة من الأحرف مسبوقة بـ % للحصول على التاريخ بتنسيقات مختلفة. (انتبه ، لا تخصص كل لغة نفس المعنى لكل حرف - على وجه الخصوص ، يمكن تبديل الحرفين "M" و "m" لدقائق وشهور.)

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

 var currentDate = new Date (); var date = currentDate.getDate(); var month = currentDate.getMonth(); var year = currentDate.getFullYear();

يمكننا الحصول على التاريخ بتنسيق MM / DD / YYYY كـ

 var monthDateYear = (month+ 1 ) + "/" + date + "/" + year;

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

يمكننا معالجة هذا باستخدام وظيفة "وسادة" التي تضيف 0 بادئة.

 function pad ( n ) { return n< 10 ? '0' +n : n; }

الآن ، نحصل على التاريخ الصحيح بتنسيق MM / DD / YYYY باستخدام:

 var mmddyyyy = pad(month + 1 ) + "/" + pad(date) + "/" + year;

إذا أردنا DD-MM-YYYY بدلاً من ذلك ، فإن العملية مماثلة:

 var ddmmyyyy = pad(date) + "-" + pad(month + 1 ) + "-" + year;

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

 var monthNames = [ "January" , "February" , "March" , "April" , "May" , "June" , "July" , "August" , "September" , "October" , "November" , "December" ]; var dateWithFullMonthName = monthNames[month] + " " + pad(date) + ", " + year;

يحب بعض الأشخاص عرض التاريخ على أنه الأول من يناير ، 2013. لا مشكلة ، كل ما نحتاجه هو دالة ordinal للدالة المساعدة تُرجع الأول لـ 1 ، و 12 لـ 12 ، و 103 لـ 103 ، وما إلى ذلك ، والباقي بسيط:

 var ordinalDate = ordinal(date) + " " + monthNames[month] + ", " + year;

من السهل تحديد يوم الأسبوع من كائن التاريخ ، لذلك دعنا نضيف ذلك في:

 var daysOfWeek = [ "Sun" , "Mon" , "Tue" , "Wed" , "Thu" , "Fri" , "Sat" ]; ordinalDateWithDayOfWeek = daysOfWeek[currentDate.getDay()] + ", " + ordinalDate;

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

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

بمجرد أن تعرف كيفية تحليل التاريخ وتنسيقه ، فإن تغيير التاريخ من تنسيق إلى آخر هو مجرد مسألة الجمع بين الاثنين.

على سبيل المثال ، إذا كان لديك تاريخ بالتنسيق 21 تموز (يوليو) 2013 وأردت تغيير التنسيق إلى 21 تموز (يوليو) 2013 ، فيمكن تحقيقه على النحو التالي:

 const myDate = new Date("Jul 21, 2013"); const dayOfMonth = myDate.getDate(); const month = myDate.getMonth(); const year = myDate.getFullYear(); function pad(n) { return n<10 ? '0'+n : n } const ddmmyyyy = pad(dayOfMonth) + "-" + pad(month + 1) + "-" + year; // "21-07-2013"

استخدام وظائف الترجمة في JavaScript Date Object

يجب أن تعمل طرق تنسيق التاريخ التي ناقشناها أعلاه في معظم التطبيقات ، ولكن إذا كنت تريد حقًا ترجمة تنسيق التاريخ ، أقترح عليك استخدام طريقة toLocaleDateString() لكائن Date :

 const today = new Date().toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric', });

... يعطينا شيئًا مثل 26 Jul 2016 .

يؤدي تغيير اللغة إلى "en-US" إلى الحصول على "Jul 26، 2016" بدلاً من ذلك. لاحظ كيف تغير التنسيق ، لكن خيارات العرض ظلت كما هي — وهي ميزة مفيدة للغاية. كما هو موضح في القسم السابق ، تعمل تقنية Intl.DateTimeFormat الأحدث بشكل مشابه جدًا لذلك ، ولكنها تتيح لك إعادة استخدام كائن منسق بحيث تحتاج فقط إلى تعيين الخيارات مرة واحدة.

باستخدام toLocaleDateString() ، من الجيد دائمًا تمرير خيارات التنسيق ، حتى إذا كان الإخراج يبدو جيدًا على جهاز الكمبيوتر الخاص بك. يمكن أن يؤدي ذلك إلى حماية واجهة المستخدم من الاختراق في المواقع غير المتوقعة بأسماء أشهر طويلة حقًا أو الظهور محرجًا بسبب الأسماء القصيرة.

إذا كنت أرغب في الحصول على شهر كامل "يوليو" بدلاً من ذلك ، فكل ما أفعله هو تغيير معلمة الشهر في الخيارات إلى "طويلة". جافا سكريبت تتعامل مع كل شيء بالنسبة لي. بالنسبة إلى en-US ، أحصل الآن على 26 تموز (يوليو) 2016.

ملاحظة: إذا كنت تريد أن يستخدم المتصفح لغة المستخدم تلقائيًا ، يمكنك تمرير "غير محدد" كمعامل أول.

إذا كنت ترغب في إظهار الإصدار الرقمي للتاريخ ولا تريد التشويش على MM / DD / YYYY مقابل DD / MM / YYYY للإعدادات المحلية المختلفة ، أقترح الحل البسيط التالي:

 const today = new Date().toLocaleDateString(undefined, { day: 'numeric', month: 'numeric', year: 'numeric', });

على جهاز الكمبيوتر الخاص بي ، هذا الناتج 7/26/2016 . إذا كنت تريد التأكد من أن هذا الشهر والتاريخ يتكونان من رقمين ، فما عليك سوى تغيير الخيارات:

 const today = new Date().toLocaleDateString(undefined, { day: '2-digit', month: '2-digit', year: 'numeric', });

هذه النواتج 07/26/2016 . فقط ما أردناه!

يمكنك أيضًا استخدام بعض الوظائف الأخرى ذات الصلة لتعريب طريقة عرض كل من الوقت والتاريخ:

رمز انتاج | وصف
 now.toLocaleTimeString()
"4:21:38 ص" عرض نسخة مترجمة من الوقت فقط
 now.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit', });
"04:21:38 صباحًا" عرض الوقت المترجم بناءً على الخيارات المتوفرة
 now.toLocaleString()
"22/7/2016 ، 4:21:38 صباحًا" عرض التاريخ والوقت للغة المستخدم
 now.toLocaleString(undefined, { day: 'numeric', month: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit', });
"22/7/2016 ، 04:21 صباحًا" عرض التاريخ والوقت المترجمين بناءً على الخيارات المتوفرة

حساب التواريخ والأوقات النسبية

في ما يلي مثال لإضافة 20 يومًا إلى تاريخ جافا سكريبت (أي معرفة التاريخ بعد 20 يومًا من تاريخ معروف):

 const myDate = new Date("July 20, 2016 15:00:00"); const nextDayOfMonth = myDate.getDate() + 20; myDate.setDate(nextDayOfMonth); const newDate = myDate.toLocaleString();

يمثل كائن التاريخ الأصلي الآن تاريخًا بعد 20 يومًا من 20 يوليو ويحتوي newDate على سلسلة مترجمة تمثل ذلك التاريخ. في متصفحي ، يحتوي newDate على "8/9/2016 ، 3:00:00 مساءً".

لحساب الطوابع الزمنية النسبية بفارق أكثر دقة عن الأيام الكاملة ، يمكنك استخدام Date.getTime() و Date.setTime() للعمل مع الأعداد الصحيحة التي تمثل عدد المللي ثانية منذ حقبة معينة - أي 1 يناير 1970. بالنسبة إلى على سبيل المثال ، إذا كنت تريد معرفة الوقت الحالي بعد 17 ساعة:

 const msSinceEpoch = (new Date()).getTime(); const seventeenHoursLater = new Date(msSinceEpoch + 17 * 60 * 60 * 1000);

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

كما هو الحال مع كل شيء آخر متعلق بالتاريخ ، فإن مقارنة التواريخ لها مشاكلها الخاصة.

أولاً ، نحتاج إلى إنشاء كائنات التاريخ. لحسن الحظ ، <،> ، <= ، و> = جميع الأعمال. لذا فإن المقارنة بين 19 تموز (يوليو) 2014 و 18 تموز (يوليو) 2014 سهلة مثل:

 const date1 = new Date("July 19, 2014"); const date2 = new Date("July 28, 2014"); if(date1 > date2) { console.log("First date is more recent"); } else { console.log("Second date is more recent"); }

يعد التحقق من المساواة أكثر تعقيدًا ، نظرًا لأن كائنين من التاريخ يمثلان نفس التاريخ لا يزالان كائنين مختلفين للتاريخ ولن يكونا متساويين. تعتبر مقارنة سلاسل التاريخ فكرة سيئة ، على سبيل المثال ، "20 يوليو 2014" و "20 يوليو 2014" يمثلان نفس التاريخ ولكن لهما تمثيلات سلسلة مختلفة. يوضح المقتطف أدناه النقطة الأولى:

 const date1 = new Date("June 10, 2003"); const date2 = new Date(date1); const equalOrNot = date1 == date2 ? "equal" : "not equal"; console.log(equalOrNot);

هذا سوف الناتج not equal .

يمكن إصلاح هذه الحالة بالذات من خلال مقارنة الأعداد الصحيحة المكافئة للتواريخ (الطوابع الزمنية الخاصة بهم) على النحو التالي:

 date1.getTime() == date2.getTime()

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

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

 const userEnteredString = "12/20/1989"; // MM/DD/YYYY format const dateStringFromAPI = "1989-12-20T00:00:00Z"; const dateFromUserEnteredString = new Date(userEnteredString) const dateFromAPIString = new Date(dateStringFromAPI); if (dateFromUserEnteredString.getTime() == dateFromAPIString.getTime()) { transferOneMillionDollarsToUserAccount(); } else { doNothing(); }

كلاهما يمثل نفس التاريخ ولكن لسوء الحظ لن يحصل المستخدم الخاص بك على مليون دولار.

إليك المشكلة: تفترض JavaScript دائمًا أن المنطقة الزمنية هي المنطقة التي يوفرها المتصفح لها ما لم ينص صراحة على خلاف ذلك.

هذا يعني ، بالنسبة لي ، أن new Date ("12/20/1989") سينشئ تاريخًا 1989-12-20T00: 00: 00 + 5: 45 أو 1989-12-19T18: 15: 00Z وهو ليس هو نفسه 1989-12-20T00: 00: 00Z من حيث الطابع الزمني.

لا يمكن تغيير المنطقة الزمنية لكائن تاريخ موجود فقط ، لذلك هدفنا الآن هو إنشاء كائن تاريخ جديد ولكن باستخدام التوقيت العالمي المنسق (UTC) بدلاً من المنطقة الزمنية المحلية.

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

  1. أنشئ سلسلة تاريخ بتنسيق ISO من تاريخ إدخال المستخدم واستخدمها لإنشاء كائن تاريخ. استخدام تنسيق تاريخ ISO صالح لإنشاء كائن تاريخ مع جعل هدف UTC مقابل المحلي واضحًا للغاية.
 const userEnteredDate = "12/20/1989"; const parts = userEnteredDate.split("/"); const userEnteredDateISO = parts[2] + "-" + parts[0] + "-" + parts[1]; const userEnteredDateObj = new Date(userEnteredDateISO + "T00:00:00Z"); const dateFromAPI = new Date("1989-12-20T00:00:00Z"); const result = userEnteredDateObj.getTime() == dateFromAPI.getTime(); // true

يعمل هذا أيضًا إذا لم تحدد الوقت منذ ذلك الحين والذي سينتقل افتراضيًا إلى منتصف الليل (على سبيل المثال ، 00: 00: 00Z):

 const userEnteredDate = new Date("1989-12-20"); const dateFromAPI = new Date("1989-12-20T00:00:00Z"); const result = userEnteredDate.getTime() == dateFromAPI.getTime(); // true

تذكر: إذا تم تمرير مُنشئ التاريخ في سلسلة بتنسيق تاريخ ISO الصحيح لـ YYYY-MM-DD ، فإنه يفترض UTC تلقائيًا.

  1. توفر JavaScript وظيفة Date.UTC () الأنيقة التي يمكنك استخدامها للحصول على الطابع الزمني UTC للتاريخ. نستخرج المكونات من التاريخ ونمررها إلى الوظيفة.
 const userEnteredDate = new Date("12/20/1989"); const userEnteredDateTimeStamp = Date.UTC(userEnteredDate.getFullYear(), userEnteredDate.getMonth(), userEnteredDate.getDate(), 0, 0, 0); const dateFromAPI = new Date("1989-12-20T00:00:00Z"); const result = userEnteredDateTimeStamp == dateFromAPI.getTime(); // true ...

معرفة الفرق بين تاريخين

السيناريو الشائع الذي ستصادفه هو العثور على الفرق بين تاريخين.

نناقش حالتين من حالات الاستخدام:

إيجاد عدد الأيام بين تاريخين

قم بتحويل كلا التاريخين إلى طابع زمني UTC ، وابحث عن الفرق بالمللي ثانية وابحث عن الأيام المكافئة.

 const dateFromAPI = "2016-02-10T00:00:00Z"; const now = new Date(); const datefromAPITimeStamp = (new Date(dateFromAPI)).getTime(); const nowTimeStamp = now.getTime(); const microSecondsDiff = Math.abs(datefromAPITimeStamp - nowTimeStamp); // Math.round is used instead of Math.floor to account for certain DST cases // Number of milliseconds per day = // 24 hrs/day * 60 minutes/hour * 60 seconds/minute * 1000 ms/second const daysDiff = Math.round(microSecondsDiff / (1000 * 60 * 60 * 24)); console.log(daysDiff);

معرفة عمر المستخدم من تاريخ ميلاده

 const birthDateFromAPI = "12/10/1989";

ملاحظة: لدينا تنسيق غير قياسي. اقرأ مستند API لتحديد ما إذا كان هذا يعني 12 أكتوبر أو 10 ديسمبر. قم بالتغيير إلى تنسيق ISO وفقًا لذلك.

 const parts = birthDateFromAPI.split("/"); const birthDateISO = parts[2] + "-" + parts[0] + "-" + parts[1]; const birthDate = new Date(birthDateISO); const today = new Date(); let age = today.getFullYear() - birthDate.getFullYear(); if(today.getMonth() < birthDate.getMonth()) { age--; } if(today.getMonth() == birthDate.getMonth() && today.getDate() < birthDate.getDate()) { age--; }

أعلم أن هناك طرقًا أكثر إيجازًا لكتابة هذا الرمز ولكني أحب كتابته بهذه الطريقة بسبب الوضوح المطلق للمنطق.

اقتراحات لتجنب الجحيم التاريخ

الآن بعد أن أصبحنا مرتاحين لحساب التاريخ ، فنحن في وضع يسمح لنا بفهم أفضل الممارسات التي يجب اتباعها وأسباب اتباعها.

الحصول على DateTime من المستخدم

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

لإزالة أي ارتباك ، أقترح دائمًا إنشاء تاريخ باستخدام تنسيق التاريخ new Date(year, month, day, hours, minutes, seconds, milliseconds) حتى إذا كان لديك التاريخ بتنسيق صالح قابل للتحليل. إذا اتبع جميع المبرمجين في فريقك هذه القاعدة البسيطة ، فسيكون من السهل جدًا الحفاظ على الكود على المدى الطويل نظرًا لأنه واضح بقدر ما يمكنك مع مُنشئ Date .

الجزء الرائع هو أنه يمكنك استخدام المتغيرات التي تسمح لك بحذف أي من المعلمات الأربعة الأخيرة إذا كانت صفرًا ؛ على سبيل المثال ، new Date(2012, 10, 12) هو نفسه new Date(2012, 10, 12, 0, 0, 0, 0) لأن المعلمات غير المحددة افتراضية إلى الصفر.

على سبيل المثال ، إذا كنت تستخدم منتقي التاريخ والوقت الذي يمنحك التاريخ 2012-10-12 والوقت 12:30 ، يمكنك استخراج الأجزاء وإنشاء كائن تاريخ جديد على النحو التالي:

 const dateFromPicker = "2012-10-12"; const timeFromPicker = "12:30"; const dateParts = dateFromPicker.split("-"); const timeParts = timeFromPicker.split(":"); const localDate = new Date(dateParts[0], dateParts[1]-1, dateParts[2], timeParts[0], timeParts[1]);

حاول تجنب إنشاء تاريخ من سلسلة ما لم يكن بتنسيق تاريخ ISO. استخدم طريقة التاريخ (السنة والشهر والتاريخ والساعات والدقائق والثواني والميكروثانية) بدلاً من ذلك.

الحصول على التاريخ فقط

إذا كنت تحصل على التاريخ فقط ، تاريخ ميلاد المستخدم على سبيل المثال ، فمن الأفضل تحويل التنسيق إلى تنسيق تاريخ ISO صالح لإزالة أي معلومات عن المنطقة الزمنية التي يمكن أن تتسبب في تحول التاريخ إلى الأمام أو الخلف عند التحويل إلى التوقيت العالمي المنسق (UTC). علي سبيل المثال:

 const dateFromPicker = "12/20/2012"; const dateParts = dateFromPicker.split("/"); const ISODate = dateParts[2] + "-" + dateParts[0] + "-" + dateParts[1]; const birthDate = new Date(ISODate).toISOString();

في حال نسيت ، إذا قمت بإنشاء كائن Date بإدخال بتنسيق تاريخ ISO صالح (YYYY-MM-DD) ، فسيتم تعيينه افتراضيًا على UTC بدلاً من التعيين الافتراضي إلى المنطقة الزمنية للمتصفح.

تخزين التاريخ

قم دائمًا بتخزين DateTime بتنسيق UTC. أرسل دائمًا سلسلة تاريخ ISO أو طابعًا زمنيًا إلى النهاية الخلفية.

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

أيضًا ، يجب أن يكون واضحًا أنه لا يجب عليك أبدًا إرسال سلسلة DateTime مثل "20 يوليو 1989 ، 12:10 مساءً" إلى النهاية الخلفية. حتى إذا قمت بإرسال المنطقة الزمنية أيضًا ، فإنك تزيد من جهد المبرمجين الآخرين لفهم نواياك وتحليل التاريخ وتخزينه بشكل صحيح.

استخدم أساليب toISOString() أو toJSON() لكائن التاريخ لتحويل DateTime المحلي إلى UTC.

 const dateFromUI = "12-13-2012"; const timeFromUI = "10:20"; const dateParts = dateFromUI.split("-"); const timeParts = timeFromUI.split(":"); const date = new Date(dateParts[2], dateParts[0]-1, dateParts[1], timeParts[0], timeParts[1]); const dateISO = date.toISOString(); $.post("http://example.com/", {date: dateISO}, ...)

عرض التاريخ والوقت

  1. احصل على الطابع الزمني أو التاريخ المنسق ISO من واجهة برمجة تطبيقات REST.
  2. قم بإنشاء كائن Date .
  3. استخدم toLocaleString() أو toLocaleDateString() و toLocaleTimeString() أو مكتبة التاريخ لعرض التوقيت المحلي.
 const dateFromAPI = "2016-01-02T12:30:00Z"; const localDate = new Date(dateFromAPI); const localDateString = localDate.toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric', }); const localTimeString = localDate.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit', });

متى يجب تخزين الوقت المحلي أيضًا؟

"في بعض الأحيان يكون من المهم معرفة المنطقة الزمنية التي وقع فيها الحدث ، والتحويل إلى منطقة زمنية واحدة يؤدي إلى طمس هذه المعلومات بشكل نهائي.

"إذا كنت تقوم بترويج تسويقي وترغب في معرفة العملاء الذين قدموا طلباتهم في وقت الغداء ، فإن الطلب الذي يبدو أنه قد تم تقديمه عند الظهر بتوقيت جرينتش ليس مفيدًا للغاية عندما تم تقديمه بالفعل على وجبة الإفطار في نيويورك."

إذا واجهت هذا النوع من المواقف ، فسيكون من الحكمة توفير الوقت المحلي أيضًا. كالعادة ، نود إنشاء التاريخ بتنسيق ISO ، لكن علينا إيجاد إزاحة المنطقة الزمنية أولاً.

تخبرنا وظيفة getTimeZoneOffset() الخاصة بكائن التاريخ بعدد الدقائق التي تعطي وقت UTC المكافئ عند إضافتها إلى وقت محلي معين. أقترح تحويله إلى تنسيق (+ -) hh: mm لأنه يجعل الأمر أكثر وضوحًا أنه تعويض منطقة زمنية.

 const now = new Date(); const tz = now.gettime zoneOffset();

بالنسبة لمنطقتى الزمنية +05: 45 ، أحصل على -345 ، هذه ليست فقط علامة معاكسة ، ولكن رقم مثل -345 قد يكون محيرًا تمامًا لمطور الواجهة الخلفية. لذلك قمنا بتحويل هذا إلى +05: 45.

 const sign = tz > 0 ? "-" : "+"; const hours = pad(Math.floor(Math.abs(tz)/60)); const minutes = pad(Math.abs(tz)%60); const tzOffset = sign + hours + ":" + minutes;

الآن نحصل على باقي القيم وننشئ سلسلة ISO صالحة تمثل التاريخ والوقت المحلي.

 const localDateTime = now.getFullYear() + "-" + pad(now.getMonth()+1) + "-" + pad(now.getDate()) + "T" + pad(now.getHours()) + ":" + pad(now.getMinutes()) + ":" + pad(now.getSeconds());

إذا كنت تريد ، يمكنك التفاف التوقيت العالمي المتفق عليه والتواريخ المحلية في كائن.

 const eventDate = { utc: now.toISOString(), local: localDateTime, tzOffset: tzOffset, }

الآن ، في النهاية الخلفية ، إذا كنت تريد معرفة ما إذا كان الحدث قد وقع قبل وقت الظهيرة بالتوقيت المحلي ، فيمكنك تحليل التاريخ واستخدام وظيفة getHours() .

 const localDateString = eventDate.local; const localDate = new Date(localDateString); if(localDate.getHours() < 12) { console.log("Event happened before noon local time"); }

لم نستخدم tzOffset هنا ، لكننا ما زلنا نخزنه لأننا قد نحتاجه في المستقبل لأغراض التصحيح. يمكنك بالفعل إرسال إزاحة المنطقة الزمنية ووقت UTC فقط. لكني أحب تخزين التوقيت المحلي أيضًا لأنه سيتعين عليك في النهاية تخزين التاريخ في قاعدة بيانات ، كما أن تخزين التوقيت المحلي بشكل منفصل يسمح لك بالاستعلام المباشر بناءً على حقل بدلاً من الاضطرار إلى إجراء حسابات للحصول على التاريخ المحلي.

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

تكوين الخادم وقاعدة البيانات

قم دائمًا بتكوين الخوادم وقواعد البيانات الخاصة بك لاستخدام المنطقة الزمنية UTC. (لاحظ أن التوقيت العالمي المنسق (UTC) و GMT (توقيت غرينتش) ليسا نفس الشيء — توقيت غرينتش ، على سبيل المثال ، قد يعني التبديل إلى التوقيت الصيفي BST خلال فصل الصيف ، في حين أن التوقيت العالمي (UTC) لن يحدث أبدًا.)

لقد رأينا بالفعل مقدار ما يمكن أن تكون عليه تحويلات المنطقة الزمنية المؤلمة ، خاصةً عندما تكون غير مقصودة. يمكن أن يجعل إرسال UTC DateTime دائمًا وتكوين خوادمك لتكون في المنطقة الزمنية UTC حياتك أسهل. سيكون رمز النهاية الخاص بك أبسط وأنظف بكثير لأنه لا يتعين عليه إجراء أي تحويلات في المنطقة الزمنية. DateTime data coming in from servers across the world can be compared and sorted effortlessly.

Code in the back end should be able to assume the time zone of the server to be UTC (but should still have a check in place to be sure). A simple configuration check saves having to think about and code for conversions every time new DateTime code is written.

It's Time for Better Date Handling

Date manipulation is a hard problem. The concepts behind the practical examples in this article apply beyond JavaScript, and are just the beginning when it comes to properly handling DateTime data and calculations. Plus, every helper library will come with its own set of nuances—which is even true of the eventual official standard support{target=”_blank”} for these types of operations.

The bottom line is: Use ISO on the back end, and leave the front end to format things properly for the user. Professional programmers will be aware of some of the nuances, and will (all the more decidedly) use well-supported DateTime libraries on both the back end and the front end. Built-in functions on the database side are another story, but hopefully this article gives enough background to make wiser decisions in that context, too.

Related: Buggy JavaScript Code: The 10 Most Common Mistakes JavaScript Developers Make