المستوى المطلوب: مبتدئ. المقال ده لأي حد بيبني تطبيق فيه مواعيد أو تواريخ، حتى لو لسه بادئ. مش محتاج تكون خبير قواعد بيانات، بس محتاج تكون كتبت كود بيخزّن تاريخ أو وقت قبل كده.
ليه ميعاد الاجتماع بيظهر بالساعة الغلط؟ سرّ تخزين الوقت بالـ UTC
لو خزّنت وقت اجتماع ولقيته بيظهر مضبوط عندك وغلط عند مستخدم في بلد تانية، المشكلة مش في السيرفر ولا في المستخدم. المشكلة إنك خزّنت الوقت بالتوقيت المحلي بدل ما تخزّنه موحّد. المقال ده هيوريك أفضل طريقة تتعامل بيها مع الوقت في أي تطبيق: خزّن بالـ UTC، وحوّل وقت العرض بس.
المشكلة باختصار
عندك تطبيق مواعيد. مستخدم في القاهرة حجز اجتماع الساعة 3 عصرًا. التطبيق خزّن الرقم "15:00" في الداتابيز من غير ما يسجّل إن ده توقيت القاهرة. زميله في لندن فتح نفس الاجتماع، والتطبيق عرض "15:00" برضه. النتيجة: زميل لندن حضر الساعة 3 بتوقيت لندن، واللي هو أصلًا 5 عصرًا في القاهرة. الاجتماع ضاع، والسبب سطر تخزين واحد.
المفهوم الأساسي بمثال بسيط
تخيّل إنك اتفقت مع صحابك تتقابلوا "بعد ما الشمس تغرب". المشكلة إن الشمس بتغرب في وقت مختلف في كل مدينة. الجملة دي غامضة لأنها مربوطة بمكان كل واحد. لكن لو اتفقتوا "نتقابل لما ساعة مكة تدق 8"، بقى عندكم مرجع واحد ثابت الكل بيرجعله، وكل واحد يحسب الفرق بينه وبين مكة.
الـ UTC هو بالظبط "ساعة مكة" دي بس للعالم كله. هو توقيت مرجعي واحد، ثابت، مالوش صيف ولا شتا، وكل مناطق العالم الزمنية بتتحسب كإزاحة منه: القاهرة UTC+2 أو UTC+3 في الصيف، لندن UTC+0 أو UTC+1، اليابان UTC+9. لو خزّنت كل حاجة بالـ UTC، بتبقى ماسك المرجع الثابت، والتحويل لأي مدينة بقى مجرد جمع أو طرح.
المفهوم علميًا وبدقة
الـ UTC اختصار لـ Coordinated Universal Time، وهو معيار التوقيت العالمي اللي كل الأنظمة بتزامن عليه. اللحظة الزمنية الحقيقية (instant) حاجة واحدة موجودة في الكون كله في نفس اللأنية. اللي بيختلف هو تمثيلها على ساعة الحائط في كل مكان. لما تخزّن "15:00" من غير منطقة زمنية، انت خزّنت تمثيل بدون مرجعه، فبقى الرقم بلا معنى مؤكد.
القاعدة الذهبية: خزّن اللحظة الزمنية بالـ UTC، اعرض بالتوقيت المحلي، احسب بالـ UTC. التحويل للتوقيت المحلي يحصل في آخر خطوة قبل ما تعرض للمستخدم بس، ومايتخزّنش أبدًا في الداتابيز كقيمة أساسية.
الحل بالكود
الخطأ الشائع في بايثون هو استخدام datetime.now() اللي بترجّع وقت "ساذج" (naive) من غير منطقة زمنية. البديل الصح هو وقت "واعي" (aware) مربوط بالـ UTC. الكود ده يوريك الفرق والحل:
from datetime import datetime, timezone
from zoneinfo import ZoneInfo # مكتبة قياسية من Python 3.9
# غلط: وقت ساذج، مالوش منطقة زمنية، بيسبب المشكلة
wrong = datetime.now()
print(wrong) # 2026-08-11 15:00:00 (15:00 بأي توقيت؟ مفيش إجابة)
# صح: خزّن اللحظة بالـ UTC
now_utc = datetime.now(timezone.utc)
print(now_utc) # 2026-08-11 13:00:00+00:00
# وقت العرض: حوّل لتوقيت المستخدم من نفس اللحظة
cairo = now_utc.astimezone(ZoneInfo("Africa/Cairo"))
london = now_utc.astimezone(ZoneInfo("Europe/London"))
print(cairo.strftime("%H:%M")) # 15:00 للمستخدم المصري
print(london.strftime("%H:%M")) # 14:00 لزميله في لندن
ركّز في النقطة المهمة: اللحظة المخزّنة واحدة (13:00 UTC)، بس كل مستخدم بيشوفها بساعته الصح. مفيش تخمين، ومفيش اجتماع بيضيع.
سيناريو واقعي بالأرقام
افترض تطبيق حجوزات بـ 50,000 مستخدم موزّعين على 12 منطقة زمنية. لو بتخزّن بالتوقيت المحلي، كل تحويل بيحتاج تخمين للمنطقة، ومع تغيّر التوقيت الصيفي مرتين في السنة بتتولّد مواعيد مكرّرة أو ناقصة في ساعة الانتقال. فرق ساعة واحدة في ميعاد طبي أو رحلة طيران معناه شكوى عميل فعلية. لما تخزّن بالـ UTC، عدد حالات الوقت الغلط بينزل من مئات الشكاوى شهريًا لـ صفر تقريبًا، لأن مصدر الحقيقة بقى واحد.
الـ trade-offs وما يجب الانتباه له
الطريقة دي مش مجانية بالكامل. بتكسب: مصدر حقيقة واحد، حسابات فروق زمنية سهلة، ومناعة ضد أخطاء التوقيت الصيفي. بتخسر: خطوة تحويل زيادة وقت كل عرض، وإنك لازم تخزّن منطقة المستخدم الزمنية بشكل منفصل (زي Africa/Cairo) عشان تعرض صح.
الافتراض المهم: خزّن اسم المنطقة من قاعدة بيانات IANA (زي Africa/Cairo) مش الإزاحة الرقمية (زي +02:00). ليه؟ لأن الإزاحة بتتغيّر مع التوقيت الصيفي، بس اسم المنطقة ثابت والنظام بيحسب الإزاحة الصح تلقائيًا حسب التاريخ.
متى لا تستخدم هذه الطريقة
مش كل وقت لحظة زمنية. لو بتخزّن "عيد ميلاد" أو "ميعاد صلاة الجمعة المحلي" أو تاريخ ميلاد في بطاقة هوية، دي أوقات مدنية مقصودة محليًا، ومالهاش علاقة بالـ UTC. حوّلها لـ UTC هنا هيبوّظ المعنى: عيد ميلادك يوم 5 مايو في أي مكان في الدنيا. القاعدة: الـ UTC للحظات المطلقة (اجتماع، معاملة، لوج)، مش للتواريخ المدنية المجرّدة.
الخطوة التالية
افتح أي جدول في الداتابيز بتاعتك فيه عمود وقت. لو نوعه بيخزّن توقيت بدون منطقة زمنية (زي timestamp without time zone أو DATETIME ساذج)، ده مصدر باج جاهز. حوّله لتخزين UTC (timestamptz في PostgreSQL)، وضيف عمود منفصل لمنطقة المستخدم الزمنية. جرّب سيناريو المثال فوق بمستخدمين في مدينتين، ولو الساعة ظهرت صح للاتنين، يبقى إنت قفلت باب مشاكل كتير قبل ما تبدأ.
المصادر
- توثيق Python الرسمي لوحدة
datetime(الفرق بين naive وaware): docs.python.org/3/library/datetime.html - توثيق وحدة
zoneinfoالقياسية (PEP 615): docs.python.org/3/library/zoneinfo.html - قاعدة بيانات المناطق الزمنية IANA (Time Zone Database): iana.org/time-zones
- توثيق PostgreSQL عن أنواع التاريخ والوقت و
timestamptz: postgresql.org/docs/current/datatype-datetime.html