14  الوقت

14.1 مقدمة

الساعات والأيام

تذكر بعض المصادر أن الأولين كانوا يقسمون الوقت على 12 ساعة ليلية (تبدأ بعد غروب الشمس) تتلوها 12 ساعة نهارية (تبدأ بعد شروق الشمس). وهي ساعات متغيرة؛ ففي الصيف تزيد طول ساعة النهار (فوق 60 دقيقة معتدلة) بينما تقصر ساعة الليل (دون 60 دقيقة معتدلة)، ويحصل العكس في الشتاء (فيكون الليل أطول والنهار أقصر). أما عند اعتدال الشمس في أشهر معيَّنة في خط الاستواء فإن ساعات الليل والنهار 60 دقيقة (وهو مقياس الاعتدال). والمرجع في ذلك الساعة الشمسية (المزوَّلة). ولحساب الفترة الزمنية كانوا يستعملون ما يُحرَق كالشمع، أو ثقب في وعاء مدرَّج من ماء أو رمل.

انظر كتاب فقه اللغة للثعالبي:

الفصل السابع عشر “في تَعْدِيدِ سَاعَاتِ النَّهارِ واللَّيل على أربع وعشرين لفظة”.

عن حمزة بن الحسن وعليه عهدتها:

سَاعَاتُ النَّهارِ: الشُرُوقُ. ثُمَّ البكورُ. ثُمَّ الغُدْوَةُ. ثُمَّ الضُّحَى. ثُمَّ الهاجِرَةُ. ثُمَّ الظَهِيرَةُ. ثُمَّ الرَّوَاحُ. ثُمَّ العَصْرُ. ثُمَّ القَصْرُ. ثُمَّ الأصِيلُ. ثُمَّ العَشِيُّ. ثُمَّ الغُروبُ. سَاعَاتُ اللَّيلِ: الشَّفَقُ. ثُمَّ الغَسَقُ. ثُمَّ العَتَمَةُ. ثُمَّ السُّدْفَة. ثُمَّ الفَحْمَةُ. ثُمَّ الزُّلَّةُ. ثُمَّ الزُّلْفةُ. ثُمَّ البُهْرَةُ. ثُمَّ السَّحَرُ. ثُمَّ الفَجْرُ. ثُمَّ الصُّبْحُ. ثُمَّ الصَّباحُ

ثم جاءت الساعات الميكانيكية الثابتة التي تعمل وكأن جميع الساعات 60 دقيقة؛ ولا تعتبر شتاءً ولا صيْفًا، ولا نهارًا ولا ليلاً .. لكنها أصبحت هي المتداولة. وقد مرَّ ضبط الساعة بأطوار من الدقة، فبدأت من الساعة المتأرجحة (1657) إلى الكرونومتر (1762) إلى مذبذب الكوارتز (1927) إلى الساعة الذرية (1949) إلى ساعة شعاع السيزيوم (1955) إلى ساعة نافورة السيزيوم (1993) إلى الساعة الضوئية (Optical Clock: 2006) والتي تحيد بمقدار ثانية بعد كل 30 مليار سنة.

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

ظهر نظام التوقيت العالمي المنسق (UTC: Coordinated Universal Time) حوالي سنة 1967، لتسهيل التعامل بين الدول والتواصل عبر القارات. فهو نظام للوقت يعتمد على خطوط الطول الأرضية، تقسَّم فيه الجغرافيا لمناطق زمنية بحسب بعدها عن خط طول جرينيتش الذي كان هو نقطة الصفر (UTC+00:00) أي: المرجِع: فما يكون شرقيها يكون الفارق فيه بالموجب، وما يكون غربيَّها يكون الفارق فيه بالسالب.

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

خريطة تظهر تقسيم المناطق الزمنية

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

  • مناطق عريضة جغرافيًّا: كالصين التي تمتد لتغطي مجموعة خطوط طول إلا أنها تعتمد توقيتًا موحَّدًا.
  • التوقيت الصيفي: بعض المناطق الزمنية تقدِّم الوقت ساعة عند الربيع (أو نهاية الشتاء) ثُم تعيدُه في الخريف، وذلك لتنظيم جدوَل الناس بحيث يتعرضوا للشمس أكثر.
  • قرارات سياسية: قررت بعض الدوَّل أن تغير منطقتها الزمنية في سنة من السنوات؛ فذلك يجب أن يؤخذ في عين الاعتبار عند حساب الأوقات قبل أو بعد هذه السنة لهذه الدُّوَل.

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

نخلص من ذلك أن التعامل مع الوقت له اعتبارات كثيرة بسبب ظروف تاريخية وسياسية وليس خاضعًا لقوانين فيزيائية أو أرضية أو فلكية بحتة. ولذلك ظهرت قاعدة بيانات للمناطق الزمنية (بمجهود جماعي) تتضمن معلومات الدول بشكل يتم تحديثه بشكل مستمر لمتابعة التغيرات، تسمَّى: (tz database).

14.2 الوقت في الحاسب

يظهر وقت التقويم (Calendar Time) على هذا النحو:

  • التاريخ: 2024-11-25
  • الساعات: 08:30:25
  • المنطقة الزمنية: GMT+3 (أي: ثلاث ساعات متقدِّمة عن جرينيتش)

لكن تمثيل الزمن في الحاسب هو: عدد صحيح (int) ويمثِّل عدد الثواني من بزوغ فجر نظام يونكس (Unix Epoch) وهو تاريخ ووقت اعتباطي تمَّ وضعه على أن يكون:

  • تاريخ: 1970-01-01
  • الساعة: 00:00:00
  • المنطقة الزمنية: UTC التي هي GMT+0

مثال: لنشير إلى سنة واحدة تمامًا بعد النقطة الصفرية، أي: الساعة 00:00:00 بتاريخ 1 يناير1971 ، فإن السنة الواحدة فيها 31,536,000 ثانية ، وبالتالي يكون هذا الرقم هو الذي يمثل ذلك الوقت.

ولو أردت الترجمة بالعكس، فتقول إن 1,000,000,000 ثانية منذ النقطة الصفرية يوافق 9 سبتمبر 2001 01:46:40 في وقت التقويم على منطقة UTC.

مشكلة سنة 2038 تحصل في الأنظمة ذات 32-بت؛ إذْ عدد الثواني يصل إلى أقصى مداه عند نقطتين زمنيتين:

  • ففي النزول تستطيع أن تصل إلى: 20:45:52 UTC بتاريخ 1901-12-13 (باختيار عدد سالب)
  • وكذلك في الصعود؛ لا يمكن أن تتعدى: 03:14:07 UTC بتاريخ 2038-01-19

والحل في ذلك بسيط، وهو نقل البرنامج لنظام 64-بت.

14.3 الوقت في بايثون

ننتقل الآن إلى الوقت والتاريخ في بايثون، حيث يوجد لدينا المفاهيم التالية:

أولاً: datetime.date وهو تاريخ مثالي يفترض أن التقويم الغريغوري يمتد إلى ما لا نهاية في المستقبل والماضي (رغم أنه في الحقيقة حل مكان التاريخ الجولياني سنة 1582). سمات هذا الكائن: السنة والشهر واليوم.

ثانيًا: datetime.time هو وقت مثالي يفترض 86,400 ثانية في اليوم (بدون ثوانٍ كبيسة). سمات هذا الكائن: الساعة والدقيقة والثانية والميكروثانية وtzinfo (معلومات المنطقة الزمنية).

ثالثًا: datetime.datetime وهو التاريخ والوقت معًا؛ فلديه سمات كلا الجزئين.

رابعًا: datetime.timedelta وهو فترة زمنية. ولكننا سنستبدله بـ dateutil.relativedelta إذ هي كذلك فترة زمنية إلا أن نطاقها أوسع (تستوعب السنين والأشهر، وتعتبر السنوات الكبيسة في الحسبان).

الوقت الصحيح لا بد له من نسبة إلى منطقة زمنيَّة (كأن تقول الساعة 04:00:00 صباحًا بتوقيت UTC+03)؛ فهذا تسميه بايثون وقت واع (Aware)، وأما الوقت الذي لم تحدد منطقته الزمنية (كما لو قُلت في الساعة 04:00:00 صباحًا) فهذا غير منسوب لمنطقة زمنية وبالتالي فهو ساذج (Naive) على تعبيرهم.

ويرشدنا توثيق المكتبة للتوسع في استعمال قاعدة بيانات المناطق الزمنية، والقدرة على تفسير التواريخ والأوقات بمرونة لاستخدام مكتبة dateutil المتوفرة في PyPI.

لتثبيت المكتبة نستعمل pip على النحو التالي:

pip install python-dateutil

الآن نستورد المكتبة الأساسية datetime ومكتبة dateutil:

from datetime import date, time, datetime, timedelta
from dateutil import tz

نريد الآن معرفة الوقت العالمي والمحلي، وكذلك الوقت في القاهرة، وكذلك الوقت في لندن (هنا قائمة بالأسماء):

print(datetime.now(tz=tz.tzutc()))
print(datetime.now(tz=tz.tzlocal()))
print(datetime.now(tz=tz.gettz('Africa/Cairo')))
print(datetime.now(tz=tz.gettz('Europe/London')))
2024-12-13 18:09:15.844209+00:00
2024-12-13 21:09:15.844276+03:00
2024-12-13 20:09:15.844794+02:00
2024-12-13 18:09:15.845219+00:00

لاحظ أن شكل الوقت كاملاً على النحو التالي:

    2024-11-19 11:32:35.355104+03:00
    YYYY-MM-DD HH:MM:SS.ssssss+HH:MM

نفكك ذلك:

  • YYYY-MM-DD هو التاريخ (يبدأ بالسنة ثم الشهر ثم اليوم)
  • HH:MM:SS.ssssss هو الوقت (بالساعات والدقائق والثواني والميكروثواني)
  • +HH:MM هو الفرق بين الوقت المحلي لتلك المنطقة الزمنية والوقت العالمي المنسق (UTC).

التفسير: تحويل النص إلى تاريخ ووقت

وقراءة التواريخ أفضل بكثير في مكتبة dateutil بدلاً من استعمال المكتبة الأساسية datetime. وتكثر الحاجة لذلك عند استقبال معلومات من الشبكة أو من ملفات أو من المستخدمين:

from dateutil.parser import parse

هنا نحدد وقتًا افتراضيًّا عند القراءة، بحيث لو لم توجد المعلومة عند القراءة فإنها تستعمل القيم الابتدائية:

DEFAULT = datetime(2003, 9, 25)
parse("Thu Sep 25 10:36:28", default=DEFAULT)
datetime.datetime(2003, 9, 25, 10, 36, 28)

ونرى كيف أن المفسر يحاول معرفة المعلومات ولو كانت ناقصة:

  • دون السنة
  • دون الشهر
  • دون الثواني
  • دون اليوم
print(parse("Thu Sep 10:36:28", default=DEFAULT))
print(parse("Thu 10:36:28", default=DEFAULT))
print(parse("Thu 10:36", default=DEFAULT))
print(parse("10:36", default=DEFAULT))
2003-09-25 10:36:28
2003-09-25 10:36:28
2003-09-25 10:36:00
2003-09-25 10:36:00

الحسابات الزمنية

from dateutil.relativedelta import relativedelta
from dateutil.rrule import MO, TU, WE, TH, FR, SA, SU

حساب الزمن المنقضي

today = datetime.now(tz=tz.tzlocal())
birthday = datetime(1970, 1, 1, tzinfo=tz.tzlocal())
age = relativedelta(today, birthday)
print(f'You are {age.years} years and {age.months} months old')
You are 54 years and 11 months old

حساب الزمن لموعد

today = datetime.now(tz=tz.tzlocal())
exam_date = datetime(2025, 1, 15, tzinfo=tz.tzlocal())
diff = relativedelta(exam_date, today)
print(f'There are {diff.days} days and {diff.hours} hours remaining')
There are 1 days and 2 hours remaining

مقارنة الوقت

now = datetime.now(tz=tz.tzlocal())
exam_date = datetime(2024, 11, 19, hour=9, minute=30, second=0, tzinfo=tz.tzlocal())

if now > exam_date:
    print('The exam has passed')
elif now < exam_date:
    print('The exam is coming')
The exam has passed

مقارنة الفترة

now = datetime.now(tz=tz.tzlocal())
exam_date = datetime(2024, 11, 19, hour=15, minute=30, second=0, tzinfo=tz.tzlocal())

if relativedelta(now, exam_date).hours < 1:
    print('Hurry up!')
elif relativedelta(now, exam_date).hours < 4:
    print(f'Remember you have an exam today at {exam_date:%H:%M}')
else:
    print(f'You have plenty of time to prepare for the exam')
You have plenty of time to prepare for the exam

الجمعة القادمة

today = datetime.now(tz=tz.tzlocal())
next_friday = today + relativedelta(days=+1, weekday=FR)
print(f'The next Friday is {next_friday:%Y-%m-%d}')
The next Friday is 2024-12-20

الجمعة الفائتة

last_friday = today - relativedelta(days=+1, weekday=FR(-1))
print(f'The last Friday is {last_friday:%Y-%m-%d}')
The last Friday is 2024-12-06

حساب الوقت باعتبار منطقتين زمنيتين

لديك اجتماع في وقت محدد بتوقيت لندن، وتريد معرفة وقت الوصول بالطائرة إن كانت الرحلة تستغرق 4 ساعات والإقلاع من القاهرة في الساعة 01:00:00 صباحًا والوجهة لندن:

departure_tz = tz.gettz('Africa/Cairo')
arrival_tz = tz.gettz('Europe/London')

departure_time = datetime(2024, 11, 19, hour=1, tzinfo=departure_tz)

arrival_time = departure_time + relativedelta(hours=4)

print(f'You leave at {departure_time.astimezone(departure_tz)} in Cairo time')
print(f'You arrive at {arrival_time.astimezone(arrival_tz)} in London time')
print(f'which corresponds to {arrival_time.astimezone(departure_tz)} in Cairo time')
You leave at 2024-11-19 01:00:00+02:00 in Cairo time
You arrive at 2024-11-19 03:00:00+00:00 in London time
which corresponds to 2024-11-19 05:00:00+02:00 in Cairo time

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

وانظر الجدول لتنسيق مظهر التاريخ والوقت:

print(f'Departure time: {departure_time.astimezone(departure_tz):%d %b, %X %Z} in Cairo time')
print(f'Arrival time: {arrival_time.astimezone(arrival_tz):%d %b, %X %Z} in London time')
print(f'which corresponds to {arrival_time.astimezone(departure_tz):%d %b, %X %Z} in Cairo time')
Departure time: 19 Nov, 01:00:00 EET in Cairo time
Arrival time: 19 Nov, 03:00:00 GMT in London time
which corresponds to 19 Nov, 05:00:00 EET in Cairo time

التكرار

تكرار التواريخ يتم في هذه المكتبة باستعمال rrule ويحدد على النحو التالي:

from dateutil.rrule import rrule
from dateutil.rrule import DAILY, WEEKLY, MONTHLY, YEARLY, HOURLY, MINUTELY, SECONDLY
from dateutil.rrule import MO, TU, WE, TH, FR, SA, SU

يوم ويوم

تكرار أوقات بأخذ يوم وترك يوم، ابتداءً من وقت معين في الساعة العاشرة صباحًا:

list(
    rrule(DAILY, interval=2, count=10, dtstart=datetime(2024, 11, 19, hour=10))
)
[datetime.datetime(2024, 11, 19, 10, 0),
 datetime.datetime(2024, 11, 21, 10, 0),
 datetime.datetime(2024, 11, 23, 10, 0),
 datetime.datetime(2024, 11, 25, 10, 0),
 datetime.datetime(2024, 11, 27, 10, 0),
 datetime.datetime(2024, 11, 29, 10, 0),
 datetime.datetime(2024, 12, 1, 10, 0),
 datetime.datetime(2024, 12, 3, 10, 0),
 datetime.datetime(2024, 12, 5, 10, 0),
 datetime.datetime(2024, 12, 7, 10, 0)]

أسبوعي

list(
    rrule(WEEKLY, interval=1, count=4, dtstart=datetime(2024, 11, 19, hour=10))
)
[datetime.datetime(2024, 11, 19, 10, 0),
 datetime.datetime(2024, 11, 26, 10, 0),
 datetime.datetime(2024, 12, 3, 10, 0),
 datetime.datetime(2024, 12, 10, 10, 0)]

شهريًّا إلى وقت محدد

list(
    rrule(MONTHLY, interval=1,
        dtstart=datetime(2024, 8, 1),
        until=datetime(2025, 4, 1),
    )
)
[datetime.datetime(2024, 8, 1, 0, 0),
 datetime.datetime(2024, 9, 1, 0, 0),
 datetime.datetime(2024, 10, 1, 0, 0),
 datetime.datetime(2024, 11, 1, 0, 0),
 datetime.datetime(2024, 12, 1, 0, 0),
 datetime.datetime(2025, 1, 1, 0, 0),
 datetime.datetime(2025, 2, 1, 0, 0),
 datetime.datetime(2025, 3, 1, 0, 0),
 datetime.datetime(2025, 4, 1, 0, 0)]

كل 15 دقيقة لمدة 6 مرات

list(
    rrule(MINUTELY, interval=15, count=6, dtstart=datetime(2024, 11, 19, hour=10))
)
[datetime.datetime(2024, 11, 19, 10, 0),
 datetime.datetime(2024, 11, 19, 10, 15),
 datetime.datetime(2024, 11, 19, 10, 30),
 datetime.datetime(2024, 11, 19, 10, 45),
 datetime.datetime(2024, 11, 19, 11, 0),
 datetime.datetime(2024, 11, 19, 11, 15)]

وندعوك للاطلاع على المزيد من الأمثلة على مكتبة dateutil.

14.4 التاريخ الهجري في بايثون

توفر مكتبة hijridate التعامل مع التاريخ الهجري والتحويل بينه وبين الجريجوري (الميلادي):

from hijridate import Hijri, Gregorian

g = Hijri(1446, 5, 17).to_gregorian()
h = Gregorian(2024, 11, 19).to_hijri()

print(g)
print(h)