صُممت البرمجيات لتسهيل إنجاز الأعمال، وفي بعض الأحيان يتطلب الأمر التعامل مع التاريخ والوقت، إذ يمكن رؤية قيم التاريخ والوقت في معظم البرامج الحديثة، فمثلًا لو أردنا تطوير تطبيق يعطينا تنبيهات عن أوقات الصلاة، سيتوجب على البرنامج تشغيل تنبيه عند وقت وتاريخ محدد. مثال آخر هو تتبع الوقت في السيارات الحديثة، إذ نحتاج إلى إرسال تنبيهات إلى مالك السيارة لإبلاغه عن وجود مشكلة أو حاجة معينة للسيارة، أو تتبع التغييرات في قاعدة بيانات لإنشاء سجل تدقيق، أو الوقت الذي يتطلبه إنجاز عملية ما مثل عبور شارع معين للوصول إلى الوجهة …إلخ. يشير هذا إلى ضرورة التعامل مع التاريخ والوقت في البرامج والتفاعل معها وعرضها على المستخدمين بتنسيق واضح وسهل الفهم، فهذه خاصية أساسية لهكذا تطبيقات.
سنُنشئ خلال هذا المقال برنامج جو يمكنه معرفة التوقيت المحلي الحالي من خلال جهاز الحاسب الذي يعمل عليه، ثم عرضه على الشاشة بتنسيق سهل القراءة. بعد ذلك، سنفسّر سلسلة نصية لاستخراج معلومات التاريخ والوقت، كما سنغيّر أيضًا قيم التاريخ والوقت من منطقة زمنية إلى أخرى، ونضيف أو نطرح قيم الوقت لتحديد الفاصل الزمني بين وقتين.
المتطلبات
لتتابع هذا المقال، ستحتاج إلى:
- إصدار مُثبّت من جو 1.16 أو أعلى، ويمكنك الاستعانة بمقال تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu لإعداده.
- تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك macOS.
- تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز.
الحصول على التاريخ والوقت الحالي
سنستخدم خلال هذا القسم حزمة لغة جو time
للوصول إلى قيم التاريخ والوقت الحالي. تُقدّم لنا هذه الحزمة القياسية من مكتبة لغة جو القياسية -العديد من الدوال المتعلقة بالتاريخ والوقت، والتي يمكن من خلالها تمثيل نقاط معينة من الوقت باستخدام النوع time.Time
. يمكننا أيضًا من خلال هذه الدوال التقاط بعض المعلومات عن المنطقة الزمنية التي تمثِّل التاريخ والوقت المقابل.
كما هي العادة، سنحتاج لبدء إنشاء برامجنا إلى إنشاء مجلد للعمل ووضع الملفات فيه. يمكن أن نضع المجلد في أي مكان على الحاسب، إذ يكون للعديد من المبرمجين عادةً مجلدٌ يضعون داخله كافة مشاريعهم. سنستخدم في هذا المقال مجلدًا باسم "projects"، لذا فلننشئ هذا المجلد وننتقل إليه:
$ mkdir projects $ cd projects
الآن، من داخل هذا المجلد، سنشغّل الأمر mkdir
لإنشاء مجلد "datetime" ثم سنستخدم cd
للانتقال إليه:
$ mkdir datetime $ cd datetime
يمكننا الآن فتح ملف main.go
باستخدام محرر نانو nano أو أي محرر آخر تريده:
$ nano main.go
نضيف الدالة main
التي سنكتب فيها تعليمات الحصول على التاريخ والوقت وعرضهما:
package main import ( "fmt" "time" ) func main() { currentTime := time.Now() fmt.Println("The time is", currentTime) }
استخدمنا الدالة time.Now
من الحزمة time
للحصول على الوقت الحالي مثل قيمة من النوع time.Time
وتخزينها في المتغير currentTime
، ثم طبعنا قيمة هذا المتغير باستخدام الدالة fmt.Println
، إذ سيُطبع وفقًا لتنسيق سلسلة نصية افتراضي خاص بالنوع time.Time
.
شغّل الآن ملف البرنامج "main.go" باستخدام الأمر go run
:
$ go run main.go
سيبدو الخرج الذي يعرض التاريخ والوقت الحاليين مشابهًا لما يلي:
The time is 2021-08-15 14:30:45.0000001 -0500 CDT m=+0.000066626
طبعًا في كل مرة نُشغّل فيها هذا البرنامج سيكون التاريخ والوقت مختلفين، كما أن المنطقة الزمنية 0500 CDT-
مُتغيرة تبعًا للمنطقة الزمنية التي ضُبط عليها الحاسب كما سبق وأشرنا. نلاحظ وجود القيمة =m
، التي تُشير إلى ساعة رتيبة monotonic clock، وتُستخدم ضمنيًّا في جو عند قياس الاختلافات في الوقت، وقد صُممت لتعويض التغييرات المحتملة في تاريخ ووقت ساعة نظام الحاسب أثناء تشغيل البرنامج. من خلال هذه الساعة، ستبقى القيمة المُعادة من الدالة time.Now
صحيحة حتى لو جرى تغيير ساعة نظام الحاسب لاحقًا. مثلًا لو استدعينا الدالة time.Now
الآن وكان الوقت 10:50، ثم بعد دقيقتين جرى تأخير لساعة الحاسب بمقدار 60 دقيقة، ثم بعد 5 دقائق (من الاستدعاء الأول للدالة time.Now
) استدعينا الدالة time.Now
مرةً أخرى (في نفس البرنامج)، سيكون الخرج 10:55 وليس 9:55. لست بحاجة إلى فهم أكثر من ذلك حول آلية عمل هذه الساعة خلال المقال، لكن إذا كنت ترغب في معرفة المزيد حول الساعات الرتيبة وكيفية استخدامها، لكن يمكنك الذهاب إلى التوثيق الرسمي ورؤية المزيد من التفاصيل لو أحببت.
قد يكون التنسيق الذي يظهر به التاريخ والوقت على شاشة الخرج غير مناسب وقد ترغب بتغييره أو أنه يتضمن أجزاء من التاريخ أو الوقت أكثر مما تريد عرضه. لحسن الحظ، يوفر النوع time.Time
العديد من الدوال لتنسيق عرض التاريخ والوقت وعرض أجزاء محددة منهما؛ فمثلًا لو أردنا معرفة السنة فقط من المتغير currentTime
يمكننا استخدام التابع Year
أو يمكننا عرض الساعة فقط من خلال التابع Hour
.
لنفتح ملف "main.go" مرةً أخرى ونضيف التعديلات التالية لرؤية ذلك:
... func main() { currentTime := time.Now() fmt.Println("The time is", currentTime) fmt.Println("The year is", currentTime.Year()) fmt.Println("The month is", currentTime.Month()) fmt.Println("The day is", currentTime.Day()) fmt.Println("The hour is", currentTime.Hour()) fmt.Println("The minute is", currentTime.Hour()) fmt.Println("The second is", currentTime.Second()) }
شغّل البرنامج باستخدام go run
:
$ go run main.go
سيكون الخرج:
The time is 2021-08-15 14:30:45.0000001 -0500 CDT m=+0.000066626 The year is 2021 The month is August The day is 15 The hour is 14 The minute is 14 The second is 45
كما ذكرنا منذ قليل: في كل مرة نُشغّل فيها هذا البرنامج سيكون التاريخ أو الوقت مختلفين، لكن التنسيق يجب أن يكون متشابهًا. طبعنا في هذا المثال التاريخ كاملًا (السطر الأول)، ثم استخدمنا توابع النوع time.Time
لعرض تفاصيل محددة من التاريخ كلٌ منها بسطر منفرد؛ بحيث عرضنا السنة ثم الشهر ثم اليوم ثم الساعة وأخيرًا الثواني. ربما نلاحظ أن الشهر طُبع اسمه August
وليس رقمه كما في التاريخ الكامل، وذلك لأن التابع Month
يعيد الشهر على أنه قيمة من النوع time.Month
بدلًا من مجرد رقم، ويكون التنسيق عند طباعته سلسلة نصية string
.
لنحدّث ملف "main.go" مرةً أخرى ونضع استدعاءات التوابع السابقة كلها ضمن دالة fmt.Printf
لنتمكن من طباعة التاريخ والوقت الحاليين بتنسيق أقرب إلى ما قد نرغب في عرضه على المستخدم:
... func main() { currentTime := time.Now() fmt.Println("The time is", currentTime) fmt.Printf("%d-%d-%d %d:%d:%d\n", currentTime.Year(), currentTime.Month(), currentTime.Day(), currentTime.Hour(), currentTime.Hour(), currentTime.Second()) }
شغّل البرنامج باستخدام go run
:
$ go run main.go
سيكون الخرج على النحو التالي:
The time is 2021-08-15 14:30:45.0000001 -0500 CDT m=+0.000066626 2021-8-15 14:14:45
الخرج الآن أقرب بكثير إلى ما نريد، ولكن لا تزال هناك بعض الأشياء التي يمكن تعديلها في الخرج، فمثلًا عُرض الشهر رقمًا هذه المرة، لأننا استخدمنا العنصر النائب d%
الذي سيُجبر النوع time.Month
على استخدام رقم وليس سلسلة نصية. هنا قد نفضل عرض رقمين 08 بدلًا من رقم واحد 8، وبالتالي يجب أن نغيّر تنسيق fmt.Printf
وفقًا لذلك، ولكن ماذا لو أردنا أيضًا إظهار الوقت بتنسيق 12 ساعة بدلًا من 24 ساعة كما هو موضح في الخرج أعلاه؟ سيتطلب هذا بعض العمليات الحسابية الخاصة ضمن الدالة fmt.Printf
.
بالرغم من إمكانية طباعة التواريخ والأوقات باستخدام fmt.Printf
، ولكن يمكن أن يُصبح اﻷمر مرهقًا إذا أردنا إجراء تنسيقات أعقد، فقد ينتهي بنا الأمر بكتابة عدد كبير من الأسطر البرمجية لكل جزء نريد عرضه، أو قد نحتاج إلى إجراء عدد من العمليات الحسابية الخاصة لتحديد ما نريد عرضه.
أنشأنا في هذا القسم برنامجًا يستخدم الدالة time.Now
للحصول على الوقت الحالي واستخدمنا دوال مثل Year
و Hour
على النوع time.Time
لعرض معلومات عن التاريخ والوقت الحاليين، كما تعرّفنا على كيفية إجراء تنسيقات بسيطة على آلية عرضهما، مثل عرض أجزاء محددة من التاريخ والوقت. رأينا أيضًا أن عملية الحصول على تنسيقات أعقد تصبح أصعب باستخدام الدالة fmt.Printf
. لتسهيل الأمر توفر لغة جو تابع خاص لتنسيق التواريخ والأوقات ويعمل بطريقة مشابهة لعمل الدالة fmt.Printf
التي استخدمناها في البداية.
طباعة وتنسيق تواريخ محددة
إضافة إلى التوابع التي رأيناها في القسم السابق، يوفر النوع time.Time
تابعًا يُدعى Format
، يسمح لك بتقديم نسق layout على هيئة سلسلة نصية string
(بطريقة مشابهة لتنسيق السلاسل في دالتي fmt.Printf
و fmt.Sprintf
). يُخبر هذا النسق التابع Format
بالشكل الذي نريده لطباعة التاريخ والوقت.
نستخدم خلال هذا القسم نفس الشيفرة السابقة، ولكن بطريقة أكثر إيجازًا وسهولةً من خلال التابع Format
. بدايةً ربما يكون من الأفضل معرفة كيف يؤثر تابع Format
على خرج التاريخ والوقت في حال لم يتغير في كل مرة نُشغّل البرنامج، أي عندما نُثبّت التاريخ والوقت. نحصل حاليًا على الوقت الحالي باستخدام time.Now
، لذلك في كل مرة نُشغّل البرنامج سيظهر رقم مختلف. هناك دالةٌ مفيدة أخرى توفرها الحزمة time
، وهي الدالة time.Date
التي تسمح بتحديد تاريخ ووقت محددين لتمثيلهم مثل قيم من النوع time.Time
.
لنبدأ باستخدام الدالة time.Date
بدلًا من time.Now
في البرنامج. افتح ملف "main.go" مرةً أخرى واستبدل time.Now
بدالة time.Date
:
... func main() { theTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.Local) fmt.Println("The time is", theTime) }
تتضمن معاملات الدالة time.Date
السنة والشهر واليوم والساعة والدقيقة والثواني من التاريخ والوقت اللذين نريد تمثيلهما للنوع time.Time
. يمثل المعامل قبل الأخير النانو ثانية، والمعامل الأخير هو المنطقة الزمنية المطلوب إنشاء الوقت لها (نُغطي موضوع المناطق الزمنية لاحقًا). شغّل البرنامج باستخدام go run
:
$ go run main.go
سيكون الخرج على النحو التالي:
The time is 2021-08-15 14:30:45.0000001 -0500 CDT
سيكون الخرج الذي الذي سنراه في كل مرة نُشغّل فيها البرنامج هو نفسه باستثناء المنطقة الزمنية لأنها تعتمد على المنطقة الزمنية لجهاز الحاسب الذي تُشغّل عليه، وذلك لأننا نستخدم تاريخ ووقت محددين. طبعًا لا يزال تنسيق الخرج مشابهًا لما رأيناه من قبل، لأن البرنامج لا يزال يستخدم التنسيق الافتراضي للنوع time.Time
. بعد أن أصبح لدينا تاريخ ووقت قياسي للعمل به -يمكننا استخدامه لتغيير كيفية تنسيق الوقت عند عرضه باستخدام التابع Format
.
تنسيق عرض التاريخ والوقت باستخدام تابع Format
تتضمن العديد من لغات البرمجة طريقة مشابهة لتنسيق التواريخ والأوقات التي تُعرض، ولكن الطريقة التي تُنشئ بها لغة جو تصميمًا لتلك التنسيقات قد تكون مختلفةً قليلًا عن باقي لغات البرمجة. يستخدم تنسيق التاريخ في اللغات الأخرى نمطًا مشابهًا لكيفية عمل Printf
في لغة جو، إذ يُستخدم محرف %
متبوعًا بحرف يمثل جزءًا من التاريخ أو الوقت المطلوب إدراجه. مثلًا قد تُمثّل السنة المكونة من 4 أرقام بواسطة العنصر النائب Y%
موضوعًا ضمن السلسلة؛ أما في لغة جو تُمثّل هذه الأجزاء من التاريخ أو الوقت بمحارف تمثل تاريخًا محددًا. مثلًا، لتمثيل سنة معينة من 4 أرقام نكتب 2006 ضمن السلسلة. تتمثل فائدة هذا النوع من التصميم في أن ما نراه في الشيفرة يمثل ما سنراه في الخرج، وبالتالي عندما نكون قادرين على رؤية شكل الخرح، فإنه يسهل علينا التحقق من أن التنسيق الذي كتبناه يُطابق ما نبحث عنه، كما أنه يسهل على المطوّرين الآخرين فهم خرج البرنامج دون تشغيل البرنامج أصلًا.
يعتمد جو التصميم الافتراضي التالي، لعرض التاريخ والوقت:
01/02 03:04:05PM '06 -0700
إذا نظرت إلى كل جزء من التاريخ والوقت في هذا التصميم الافتراضي، فسترى أنها تزيد بمقدار واحد لكل جزء. يأتي الشهر أولًا 01 ثم يليه يوم الشهر 02 ثم الساعة 03 ثم الدقيقة 04 ثم الثواني 05 ثم السنة 06 (أي 2006)، وأخيرًا المنطقة الزمنية 07. يسهل هذا الأمر إنشاء تنسيقات التاريخ والوقت مستقبلًا. يمكن أيضًا العثور على أمثلة للخيارات المتاحة للتنسيق في توثيق لغة جو الخاصة بحزمة time
.
سنستخدم الآن التابع الجديد Format
لاستبدال وتنظيف تنسيق التاريخ الذي طبعناه في القسم الأخير. يتطلب الأمر -بدون Format
- عدة أسطر واستدعاءات لدوال من أجل عرض ما نريده، لكن باستخدام هذه الدالة يُصبح الأمر أسهل وأنظف.
لنفتح ملف "main.go" ونضيف استدعاء جديد للدالة fmt.Println
ونمرر لها القيمة المُعادة من استدعاء التابع Format
على المتغير theTime
:
... func main() { theTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.Local) fmt.Println("The time is", theTime) fmt.Println(theTime.Format("2006-1-2 15:4:5")) }
إذا نظرنا إلى التصميم المستخدم للتنسيق، فسنرى أنه يستخدم نفس التصميم الافتراضي في تحديد كيفية تنسيق التاريخ (January 2, 2006). شيء واحد يجب ملاحظته هو أن الساعة تستخدم 15 بدلًا من 03. يوضح هذا أننا نرغب في عرض الساعة بتنسيق 24 ساعة بدلًا من تنسيق 12 ساعة. شغّل البرنامج باستخدام go run
:
$ go run main.go
سيكون الخرج على النحو التالي:
The time is 2021-08-15 14:30:45.0000001 -0500 CDT 2021-8-15 14:30:45
نلاحظ أننا حصلنا على خرج مماثل للقسم الأخير لكن بطريقة أبسط. كل ما نحتاج إليه هو سطر واحد من التعليمات البرمجية وسلسلة تمثّل التصميم، ونترك الباقي للدالة Format
. اعتمادًا على التاريخ أو الوقت الذي نعرضه، من المحتمل أن يكون استخدام تنسيق متغير الطول مثل التنسيق السابق عند طباعة الأرقام مباشرةً صعب القراءة لنا أو للمستخدمين أو لأي شيفرة أخرى تحاول قراءة القيمة. يؤدي استخدام 1 لتنسيق الشهر إلى عرض شهر آذار (مارس) بالرقم 3، بينما يحتاج شهر تشرين الأول (أكتوبر) محرفين ويظهر بالرقم 10.
افتح الملف "main.go" وأضِف سطرًا إضافيًا إلى البرنامج لتجربة نسق آخر أكثر تنظيمًا. هذه المرة سنضع بادئة 0 قبل أجزاء التاريخ والوقت الفردية، ونجعل الساعة تستخدم تنسيق 12 ساعة.
func main() { theTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.Local) fmt.Println("The time is", theTime) fmt.Println(theTime.Format("2006-1-2 15:4:5")) fmt.Println(theTime.Format("2006-01-02 03:04:05 pm")) }
شغّل البرنامج باستخدام go run
:
$ go run main.go
سيكون الخرج:
The time is 2021-08-15 14:30:45.0000001 -0500 CDT 2021-8-15 14:30:45 2021-08-15 02:30:45 pm
بإضافة بادئة 0 إلى أجزاء سلسلة النسق، يصبح الرقم 8 للشهر في الخرج الجديد 08، ونفس الأمر بالنسبة للساعة التي أصبحت الآن بتنسيق 12 ساعة. لاحظ أن النسق الذي نكتبه يوافق ما نراه في الخرج، وهذا يجعل الأمور أبسط.
في بعض الحالات يكون هناك برامج أخرى تتفاعل مع البرنامج الذي لدينا، وقد يتضمن ذلك التعامل مع تنسيقات وتصميمات التواريخ في برنامجنا، كما أنه قد يكون من المتعب ومن العبء إعادة إنشاء هذه التنسيقات في كل مرة. في هذه الحالة ربما يكون من الأبسط استخدام تصميم تنسيق مُعرّف مُسبقًا ومعروف.
استخدام تنسيقات معرفة مسبقا
هناك العديد من التنسيقات جاهزة الاستخدام والشائعة للتاريخ، مثل العلامات الزمنية Timestamps لرسائل التسجيل log messages، وفي حال أردت إنشاء هكذا تنسيقات في كل مرة تحتاجها، سيكون أمرًا مملًا ومتعبًا. تتضمن حزمة time
تنسيقات جاهزة يمكنك استخدامها لجعل الأمور أسهل واختصار الجهد المكرر. أحد هذه التنسيقات هو RFC 3339، وهو مستند يُستخدم لتحديد كيفية عمل المعايير على الإنترنت، ويمكن بعد ذلك بناء تنسيقات RFC على بعضها. تحدد بعض تنسيقات RFC مثل RFC 2616 كيفية عمل بروتوكول HTTP، وهناك تنسيقات تُبنى على هذا التنسيق لتوسيع تعريف بروتوكول HTTP. لذا في حالة RFC 3339، يحدد RFC تنسيقًا قياسيًا لاستخدامه في الطوابع الزمنية على الإنترنت. التنسيق معروف ومدعوم جيدًا عبر الإنترنت، لذا فإن فرص رؤيته في مكان آخر عالية.
تُمثّل جميع تنسيقات الوقت المُعرّفة مسبقًا في الحزمة الزمنية time
بسلسلة ثابتة const string
تُسمى بالتنسيق الذي تمثله. هناك اثنين من التنسيقات المتاحة للتنسيق RFC 3339، هما: time.RFC3339
و time.RFC3339Nano
، والفرق بينهما أن التنسيق الثاني يُمثّل الوقت بالنانو ثانية.
لنفتح الآن ملف "main.go" ونعدّل البرنامج لاستخدام تنسيق time.RFC3339Nano
للخرج:
... func main() { theTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.Local) fmt.Println("The time is", theTime) fmt.Println(theTime.Format(time.RFC3339Nano)) }
نظرًا لأن التنسيقات المعرّفة مسبقًا هي قيم من النوع string
للتنسيق المطلوب، نحتاج فقط إلى استبدال التنسيق الذي كُنا تستخدمه عادةً بهذا التنسيق (أو أي تنسيق جاهز آخر نريده). شغّل البرنامج باستخدام go run
:
$ go run main.go
سيكون الخرج على النحو التالي:
The time is 2021-08-15 14:30:45.0000001 -0500 CDT 2021-08-15T14:30:45.0000001-05:00
يُعد تنسيق RFC 3339
جيدًا للاستخدام إذا كنا بحاجة إلى حفظ قيمة زمنية مثل سلسلة نصية string
في مكان ما، إذ يمكن قراءتها بسهولة من قبل العديد من لغات البرمجة والتطبيقات الأخرى، كما أنها مختصرة قدر الإمكان.
عدّلنا خلال هذا القسم البرنامج بحيث استخدمنا التابع Format
لطباعة التاريخ والوقت، إذ يمنحنا هذا النسق المرن إمكانية الحصول على الخرج الأخير. أخيرًا، تعرّفنا واستخدمنا واحدًا من السلاسل النصية المعرفة مسبقًا لطباعة الوقت والتاريخ اعتمادًا على تنسيقات جاهزة ومدعومة يمكن استخدامها مباشرةً دون تكلّف عناء كتابة تصميمات تنسيق يدويًا.
سنعدّل في القسم التالي برنامجنا لتحويل قيمة السلسلة string
إلى النوع time.Time
لنتمكن من معالجتها من خلال تحليلها Parsing. يمكنك الاطلاع على مقال تحليل التاريخ والوقت في dot NET على أكاديمية حسوب لمزيدٍ من المعلومات حول مفهوم التحليل.
تحويل السلاسل النصية إلى قيم زمنية عبر تحليلها
قد نصادف في العديد من التطبيقات تواريخ ممثلة بسلاسل نصية من النوع string
، وقد نرغب بإجراء بعض التعديلات أو بعض العمليات عليها؛ فمثلًا قد تحتاج إلى استخراج جزء التاريخ من القيمة، أو جزء الوقت، أو كامل القيمة للتعامل معها ..إلخ، إضافةً إلى إمكانية استخدام التابع Format
لإنشاء قيم string
من قيم النوع time.Time
. تتيح لغة جو أيضًا إمكانية التحويل بالعكس، أي من سلسلة إلى time.Time
من خلال الدالة time.Parse
، إذ تعمل هذه الدالة بآلية مشابهة للتابع Format
، بحيث تأخذ نسق التاريخ والوقت المتوقع إضافةً إلى قيمة السلسلة مثل معاملات.
لنفتح ملف "main.go" ونحدّثه لاستخدام دالة time.Parse
لتحويل timeString
إلى متغير time.Time
:
... func main() { timeString := "2021-08-15 02:30:45" theTime, err := time.Parse("2006-01-02 03:04:05", timeString) if err != nil { fmt.Println("Could not parse time:", err) } fmt.Println("The time is", theTime) fmt.Println(theTime.Format(time.RFC3339Nano)) }
على عكس التابع Format
، تُعيد الدالة time.Parse
قيمة خطأ محتملة في حالة عدم تطابق قيمة السلسلة المُمرّرة مع التنسيق المُمرّر. إذا نظرنا إلى النسق المستخدم، سنرى أن النسق المُعطى لدالة time.Parse
يستخدم 1 للشهر 2 لليوم من الشهر ..إلخ، وهذا هو نفس النسق المُستخدم في تابع Format
.
شغّل البرنامج باستخدام go run
:
$ go run main.go
سيكون الخرج كما يلي:
The time is 2021-08-15 02:30:45 +0000 UTC 2021-08-15T02:30:45Z
هناك بعض الأشياء التي يجب ملاحظتها في هذا الخرج، أولها هو أن المنطقة الزمنية الناتجة عن تحليل المتغير timeString
هي المنطقة الزمنية الافتراضية، وهي إزاحة 0+، والتي تُعرف بالتوقيت العالمي المُنسّق Coordinated Universal Time -أو اختصارًا UTC أو توقيت غرينتش، فنظرًا لعدم احتواء القيمة الزمنية أو التصميم على المنطقة الزمنية، لا تعرف الدالة time.Parse
المنطقة الزمنية التي تريد ربطها بها، وبالتالي تعدّه غرينتش. إذا كنت بحاجة لتحديد المنطقة الزمنية، يمكنك استخدام الدالة time.ParseInLocation
لذلك. يمكن ملاحظة استخدام نسق time.RFC3339Nano
، لكن الخرج لا يتضمن قيم بالنانو ثانية، وسبب ذلك هو أن القيم التي تحللها دالة time.Parse
ليست نانو ثانية، وبالتالي تُضبط القيمة لتكون 0 افتراضيًا، وعندما تكون النانو ثانية 0، لن يتضمّن استخدام التنسيق time.RFC3339Nano
قيمًا بالنانو ثانية في الخرج. يمكن للتابع time.Parse
أيضًا استخدام أيًّا من تنسيقات الوقت المعرفة مسبقًا والمتوفرة في حزمة الوقت time
عند تحليل قيمة سلسلة.
لنفتح ملف "main.go" ونعدّل قيمة timeString
، يحبث نحل مشكلة عدم ظهور قيم النانو ثانية عند استخدام تنسيق time.RFC3339Nano
ولنحدّث معاملات time.Parse
بطريقة توافق التعديلات الجديدة:
... func main() { timeString := "2021-08-15T14:30:45.0000001-05:00" theTime, err := time.Parse(time.RFC3339Nano, timeString) if err != nil { fmt.Println("Could not parse time:", err) } fmt.Println("The time is", theTime) fmt.Println(theTime.Format(time.RFC3339Nano)) }
شغّل البرنامج باستخدام go run
:
$ go run main.go
سيكون الخرج على النحو التالي:
The time is 2021-08-15 14:30:45.0000001 -0500 CDT 2021-08-15T14:30:45.0000001-05:00
يُظهر خرج Format
هذه المرة بأن time.Parse
كانت قادرة على تحليل كلٍ من المنطقة الزمنية وقيم النانو ثانية من متغير timeString
.
تعلمنا في هذا القسم كيفية استخدام دالة time.Parse
لتحليل سلسلة string
تُمثّل تاريخًا معينًا وفقًا لتصميم معين، وتعلمنا كيفية تحديد المنطقة الزمنية للسلسلة المُحللة. سنتعرّف في القسم التالي على كيفية التعامل مع المناطق الزمنية بطريقة أوسع وكيفية التبديل بين المناطق الزمنية المختلفة من خلال الميزات التي يوفرها النوع time.Time
.
التعامل مع المناطق الزمنية
يُشاع في التطبيقات التي يستخدمها مُستخدمين من أنحاء مختلفة من العالم -تخزّين التواريخ والأوقات باستخدام توقيت غرينيتش UTC، ثم التحويل إلى التوقيت المحلي للمستخدم عند الحاجة. يسمح ذلك بتخزين البيانات بتنسيق موحّد ويجعل الحسابات بينها أسهل، نظرًا لأن التحويل مطلوب فقط عند عرض التاريخ والوقت للمستخدم.
أنشأنا خلال المقال برنامجًا يعمل وفقًا للمنطقة الزمنية المحلية المتواجدين بها. لحفظ قيم البيانات من النوع time.Time
على أنها قيم UTC، نحتاج أولًا إلى تحويلها إلى قيم UTC باستخدام التابع UTC
الذي يُعيد القيمة الزمنية الموافقة لنظام UTC من متغير التاريخ الذي يستدعيها.
ملاحظة: يجري التحويل هنا من المنطقة المحلية التي يوجد بها الحاسب الذي نستخدمه إلى UTC، وبالتالي إذا كان حاسبنا موجود ضمن منطقة UTC لن نرى فرقًا.
لنفتح ملف "main.go" ونُطبق هذا الكلام:
... func main() { theTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.Local) fmt.Println("The time is", theTime) fmt.Println(theTime.Format(time.RFC3339Nano)) utcTime := theTime.UTC() fmt.Println("The UTC time is", utcTime) fmt.Println(utcTime.Format(time.RFC3339Nano)) }
هذه المرة يُنشئ البرنامج متغير theTime
على أنه فيمة من النوع time.Time
وفقًا لمنطقتنا الزمنية، ثم يطبعه بتنسيقين مختلفين، ثم يستخدم تابع UTC
للتحويل من المنطقة الزمنية المحلية الحالية إلى المنطقة الزمنية UTC.
شغّل البرنامج باستخدام go run
:
$ go run main.go
سيكون الخرج على النحو التالي:
The time is 2021-08-15 14:30:45.0000001 -0500 CDT 2021-08-15T14:30:45.0000001-05:00 The UTC time is 2021-08-15 19:30:45.0000001 +0000 UTC 2021-08-15T19:30:45.0000001Z
سيختلف الخرج اعتمادًا على المنطقة الزمنية المحلية، ولكن سنرى في الخرج السابق أن المنطقة الزمنية في المرة الأولى كانت بتوقيت CDT (التوقيت الصيفي المركزي لأمريكا الشمالية)، وهي "5-" ساعات من UTC. بعد استدعاء تابع UTC
وطباعة الوقت وفق نظام UTC، سنرى أن الوقت تغير من 14 إلى 19، لأن تحويل الوقت إلى UTC أضاف خمس ساعات. من الممكن أيضًا تحويل UTC إلى التوقيت المحلي باستخدام التابع Local
على متغير النوع time.Time
بنفس الأسلوب.
افتح ملف "main.go" مرةً أخرى، وضِف استدعاءً للتابع Local
على المتغير utcTime
لتحويله مرةً أخرى إلى المنطقة الزمنية المحلية:
... func main() { theTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.Local) fmt.Println("The time is", theTime) fmt.Println(theTime.Format(time.RFC3339Nano)) utcTime := theTime.UTC() fmt.Println("The UTC time is", utcTime) fmt.Println(utcTime.Format(time.RFC3339Nano)) localTime := utcTime.Local() fmt.Println("The Local time is", localTime) fmt.Println(localTime.Format(time.RFC3339Nano)) }
شغّل البرنامج باستخدام go run
:
$ go run main.go
ليكون الخرج:
The time is 2021-08-15 14:30:45.0000001 -0500 CDT 2021-08-15T14:30:45.0000001-05:00 The UTC time is 2021-08-15 19:30:45.0000001 +0000 UTC 2021-08-15T19:30:45.0000001Z The Local time is 2021-08-15 14:30:45.0000001 -0500 CDT 2021-08-15T14:30:45.0000001-05:00
نلاحظ أنه جرى التحويل من UTC إلى المنطقة الزمنية المحلية، وهذا يعني طرح خمس ساعات من UTC، وتغيير الساعة من 19 إلى 14.
عدّلنا خلال هذا القسم البرنامج لتحويل البيانات الزمنية من منطقة زمنية محلية إلى توقيت غرينتش UTC باستخدام التابع UTC
، كما أجرينا تحويلًا معاكسًا باستخدام التابع Local
.
تتمثل إحدى الميزات الإضافية التي تقدمها حزمة time
والتي يمكن أن تكون مفيدة في تطبيقاتك -في تحديد ما إذا كان وقت معين قبل أو بعد وقت آخر.
مقارنة الأوقات الزمنية
قد تكون مقارنة تاريخين مع بعضهما صعبة أحيانًا، بسبب المتغيرات التي يجب أخذها بالحسبان عند المقارنة، مثل الحاجة إلى الانتباه إلى المناطق الزمنية أو حقيقة أن الأشهر لها عدد مختلف من الأيام عن بعضها. توفر الحزمة الزمنية time
تابعين لتسهيل ذلك، هما: Before
و After
اللذان يمكن تطبيقهما على متغيرات النوع time.Time
. تقبل هذه التوابع قيمةً زمنيةً واحدة وتعيدان إما true
أو false
اعتمادًا على ما إذا كان الوقت المُمثّل بالمتغير الذي يستدعيهما قبل أو بعد الوقت المقدم.
لنفتح ملف "main.go" ونرى كيف تجري الأمور:
... func main() { firstTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.UTC) fmt.Println("The first time is", firstTime) secondTime := time.Date(2021, 12, 25, 16, 40, 55, 200, time.UTC) fmt.Println("The second time is", secondTime) fmt.Println("First time before second?", firstTime.Before(secondTime)) fmt.Println("First time after second?", firstTime.After(secondTime)) fmt.Println("Second time before first?", secondTime.Before(firstTime)) fmt.Println("Second time after first?", secondTime.After(firstTime)) }
شغّل البرنامج باستخدام go run
:
$ go run main.go
سيكون الخرج على النحو التالي:
The first time is 2021-08-15 14:30:45.0000001 +0000 UTC The second time is 2021-12-25 16:40:55.0000002 +0000 UTC First time before second? true First time after second? false Second time before first? false Second time after first? true
بما أننا نستخدم في الشيفرة أعلاه نظام الوقت UTC، فيجب أن يكون الخرج متشابهًا بغض النظر عن المنطقة الزمنية. نلاحظ أنه عند استخدام تابع Before
مع المتغير firstTime
وتمرير الوقت secondTime
الذي نريد المقارنة به، ستكون النتيجة true
أي أن 2021-08-15 قبل 2021-12-25. عند استخدام After
مع المتغير firstTime
وتمرير secondTime
، تكون النتيجة false
لأن 2021-08-15 ليس بعد 2021-12-25. يؤدي تغيير ترتيب استدعاء التوابع على secondTime
إلى إظهار نتائج معاكسة.
هناك طريقة أخرى لمقارنة القيم الزمنية في حزمة الوقت وهي تابع Sub
، الذي يطرح تاريخًا من تاريخ آخر ويُعيد قيمةً من نوع جديد هو time.Duration
. على عكس قيم النوع time.Time
التي تمثل نقاط زمنية مطلقة، تمثل قيمة time.Duration
فرقًا في الوقت. مثلًا، قد تعني عبارة "في ساعة واحدة" مدة duration لأنها تعني شيئًا مختلفًا بناءً على الوقت الحالي من اليوم، لكنها تمثّل "عند الظهر" وقتًا محددًا ومطلقًا. تستخدم لغة جو النوع time.Duration
في بعض الحالات، مثل الوقت الذي نريد فيه تحديد المدة التي يجب أن تنتظرها الدالة قبل إعادة خطأ أو كما هو الحال هنا، إذ نحتاج إلى معرفة مقدار الزمن بين وقت وآخر.
لنفتح الآن ملف "main.go" ونستخدم التابع Sub
على المتغير firstTime
و secondTime
ونطبع النتائج:
... func main() { firstTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.UTC) fmt.Println("The first time is", firstTime) secondTime := time.Date(2021, 12, 25, 16, 40, 55, 200, time.UTC) fmt.Println("The second time is", secondTime) fmt.Println("Duration between first and second time is", firstTime.Sub(secondTime)) fmt.Println("Duration between second and first time is", secondTime.Sub(firstTime))
شغّل البرنامج باستخدام go run
:
$ go run main.go
سيكون الخرج على النحو التالي:
The first time is 2021-08-15 14:30:45.0000001 +0000 UTC The second time is 2021-12-25 16:40:55.0000002 +0000 UTC Duration between first and second time is -3170h10m10.0000001s Duration between second and first time is 3170h10m10.0000001s
يوضح الناتج أعلاه أن هناك 3170 ساعة و 10 دقائق و 10 ثوان و 100 نانو ثانية بين التاريخين، وهناك بعض الأشياء التي يجب ملاحظتها في الخرج، أولها أن المدة بين المرة الأولى first time
والثانية second time
قيمة سالبة، وهذا يشير إلى أن الوقت الثاني بعد الأول، وسيكون الأمر مماثلًا إذا طرحنا 5 من 0 وحصلنا على 5-. نلاحظ أيضًا أن أكبر وحدة قياس للمدة هي ساعة، لذلك فهي لا تُقسم إلى أيام أو شهور. بما أن عدد الأيام في الشهر غير متسق وقد يكون لكلمة "اليوم" معنى مختلف عند التبديل للتوقيت الصيفي، فإن قياس الساعة هو أدق مقياس، إذ أنه لا يتقلب.
عدّلنا البرنامج خلال هذا القسم مرتين للمقارنة بين الأوقات الزمنية باستخدام ثلاث توابع مختلفة؛ إذ استخدمنا أولًا التابعين Before
و After
لتحديد ما إذا كان الوقت قبل أو بعد وقت آخر؛ ثم استخدمنا Sub
لمعرفة المدة بين وقتين.
ليس الحصول على المدة بين وقتين الطريقة الوحيدة التي تستخدم بها الحزمة الزمنية الدالة time.Duration
، إذ يمكننا أيضًا استخدامها لإضافة وقت أو إزالته من قيمة من النوع time.Time
.
إضافة وطرح الأوقات الزمنية
إحدى العمليات الشائعة عند كتابة التطبيقات التي تستخدم التواريخ والأوقات هي تحديد وقت الماضي أو المستقبل بناءً على وقت آخر مرجعي، فمثلًا يمكن استخدام هذا الوقت المرجعي لتحديد موعد تجديد الاشتراك بخدمة، أو ما إذا كان قد مر قدرٌ معينٌ من الوقت منذ آخر مرة جرى فيها التحقق من أمر معين. توفر حزمة الوقت time
طريقةً لمعالجة الأمر، وذلك من خلال تحديد المدد الزمنية الخاصة بنا باستخدام متغيرات من النوع time.Duration
.
إنشاء قيمة من النوع time.Duration
بسيط جدًا والعمليات الرياضية عليه متاحة كما لو أنه متغير عددي عادي؛ فمثلًا لإنشاء مدة زمنية time.Duration
تُمثّل ساعة أو ساعتين أو ثلاثة ..إلخ، يمكننا استخدام time.Hour
مضروبةً بعدد الساعات التي نريدها:
oneHour := 1 * time.Hour twoHours := 2 * time.Hour tenHours := 10 * time.Hour
نستخدم time.Minute
و time.Second
في حال الدقائق والثواني:
tenMinutes := 10 * time.Minute fiveSeconds := 5 * time.Second
يمكن أيضًا إضافة مدة زمنية إلى مدة أخرى للحصول على مجموعهما. لنفتح ملف main.go
ونطبق ذلك:
... func main() { toAdd := 1 * time.Hour fmt.Println("1:", toAdd) toAdd += 1 * time.Minute fmt.Println("2:", toAdd) toAdd += 1 * time.Second fmt.Println("3:", toAdd) }
شغّل البرنامج باستخدام go run
:
$ go run main.go
سيكون الخرج على النحو التالي:
1: 1h0m0s 2: 1h1m0s 3: 1h1m1s
نلاحظ من الخرج أن المدة الأولى المطبوعة هي ساعة واحدة، وهذا يتوافق مع 1 * time.Hour
في الشيفرة. أضفنا بعد ذلك 1 * time.Minute
إلى القيمة toAdd
أي ساعة ودقيقة واحدة. أخيرًا أضفنا 1 * time.Second
إلى قيمة toAdd
، لينتج لدينا ساعة واحدة ودقيقة واحدة وثانية في المدة الزمنية الممثلة بالنوع time.Duration
.
يمكن أيضًا دمج عمليات إضافة المُدد معًا في عبارة واحدة، أو طرح مدة من أخرى:
oneHourOneMinute := 1 * time.Hour + 1 * time.Minute tenMinutes := 1 * time.Hour - 50 * time.Minute
لنفتح ملف "main.go" ونعدله بحيث نستخدم توليفة من هذه العمليات لطرح دقيقة واحدة وثانية واحدة من toAdd
:
... func main() { ... toAdd += 1 * time.Second fmt.Println("3:", toAdd) toAdd -= 1*time.Minute + 1*time.Second fmt.Println("4:", toAdd) }
شغّل البرنامج باستخدام go run
:
$ go run main.go
سيعطي الخرج التالي:
1: 1h0m0s 2: 1h1m0s 3: 1h1m1s 4: 1h0m0s
يظهر السطر الرابع من الخرج، والذي يُمثّل ناتج طرح المجموع 1*time.Minute + 1*time.Second
من toAdd
أن العملية ناجحة، إذ حصلنا على قيمة ساعة واحدة (طُرحت الثانية والدقيقة).
يتيح لنا استخدام هذه المُدد المقترنة بالتابع Add
للنوع time.Time
حساب المدد الزمنية بين نقطتين زمنيتين إحداهما نقطة مرجعية، مثل حساب المدة منذ أول يوم اشتراك في خدمة معينة حتى اللحظة. لرؤية مثال آخر نفتح ملف "main.go" ونجعل قيمة toAdd
تساوي 24 ساعة أي 24 * time.Hour
، ثم نستخدم التابع Add
على قيمة متغير من النوع time.Time
لمعرفة الوقت الذي سيكون بعد 24 ساعة من تلك النقطة:
... func main() { theTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.UTC) fmt.Println("The time is", theTime) toAdd := 24 * time.Hour fmt.Println("Adding", toAdd) newTime := theTime.Add(toAdd) fmt.Println("The new time is", newTime) }
شغّل البرنامج باستخدام go run
:
$ go run main.go
سيكون الخرج على النحو التالي:
The time is 2021-08-15 14:30:45.0000001 +0000 UTC Adding 24h0m0s The new time is 2021-08-16 14:30:45.0000001 +0000 UTC
نلاحظ أن إضافة 24 ساعة إلى التاريخ 2021-08-15 سينتج عنه التاريخ الجديد 2021-08-16. يمكننا أيضًا استخدام التابع Add
لطرح الوقت، إذ سنستخدم قيمة سالبة ببساطة، فيصبح الأمر كما لو أننا نستخدم التابع Sub
. لنفتح ملف "main.go" ونطبق هذا الكلام، fحيث سنطرح هذه المرة 24 ساعة، أي يجب أن نستخدم قيمة "24-".
... func main() { theTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.UTC) fmt.Println("The time is", theTime) toAdd := -24 * time.Hour fmt.Println("Adding", toAdd) newTime := theTime.Add(toAdd) fmt.Println("The new time is", newTime) }
`
شغّل البرنامج باستخدام go run
:
$ go run main.go
سيكون الخرج كما يلي:
The time is 2021-08-15 14:30:45.0000001 +0000 UTC Adding -24h0m0s The new time is 2021-08-14 14:30:45.0000001 +0000 UTC
نلاحظ من الخرج أنه قد طُرح 24 ساعة من الوقت الأصلي.
استخدمنا خلال هذا القسم التوابع time.Hour
و time.Minute
و time.Second
لإنشاء قيم من النوع time.Duration
. استخدمنا أيضًا قيم النوع time.Duration
مع التابع Add
للحصول على قيمة جديدة لمتغير من النوع time.Time
. سيكون لدينا -من خلال التوابع time.Now
و Add
و Before
و After
- القدرة الكافية على التعامل مع التاريخ والوقت في التطبيقات.
الخاتمة
استخدمنا خلال هذا المقال الدالة time.Now
للحصول على التاريخ والوقت المحلي الحالي على شكل قيم من النوع time.Time
، ثم استخدمنا التوابع Year
و Month
و Hour
..إلخ، للحصول على معلومات محددة من التاريخ والوقت. استخدمنا بعد ذلك التابع Format
لطباعة الوقت بالطريقة التي نريدها وفقًا لتنسيق مخصص نقدمه أو وفقًا لتنسيق جاهز مُعرّف مسبقًا. استخدمنا الدالة time.Parse
لتحويل قيمة سلسلة نصية string
تمثّل بيانات زمنية إلى قيمة من النوع time.Time
لنتمكن من التعامل معها. تعلمنا أيضًا كيفية التبديل من المنطقة الزمنية المحلية إلى منطقة غرينتش UTC باستخدام التابع Local
والعكس. تعلمنا استخدام توابع Sub
و Add
لإجراء عمليات جمع وطرح على البيانات الزمنية، لإيجاد الفرق بين مدتين زمنيتين أو لإضافة مدة زمنية إلى بيانات زمنية محددة.
ترجمة -وبتصرف- للمقال How To Use Dates and Times in Go لصاحبه Kristin Davidson.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.