PostgreSQL: الفرق بين timestamp و timestamptz — ما المُخزّن فعلًا تحت الغطاء؟
يحتفظ PostgreSQL بكل من timestamp وtimestamptz كعدد صحيح واحد بـ 64 بت: عدد المايكروثانية منذ 1970-01-01 00:00:00 UTC. يظهر الفرق فقط عند تنسيق البيانات للاستهلاك البشري.
لماذا يُربك هذا الناس؟
- عمودان، تاريخ واحد… نتيجتا استعلام مختلفتان
- تطبيقك يُدرج
2025-07-29 10:00، لكن فريق آخر يرى02:00 - الواجهة الأمامية تعرض سلسلة ISO نصية لا تتطابق مع سجل الخادم الخلفي
علبتا خوخ: واحدة عادية وواحدة بملصق
| نوع البيانات | الاسم الرسمي | القيمة المخزنة | ما يحدث عند SELECT |
|---|---|---|---|
timestamp | timestamp بدون منطقة زمنية | عدد المايكروثانية الخام | يُرسل بدون تغيير — Postgres لا يخمّن المنطقة الزمنية أبدًا |
timestamptz | timestamp مع منطقة زمنية | نفس عدد المايكروثانية | يطبّق Postgres إعداد TimeZone للجلسة قبل إرسال النص |
تشبيه
timestamp= علبة خوخ بدون ملصق مصدر. تعرف أنه فاكهة، لكن لا تعرف أين عُلّب.timestamptz= علبة مختومة بفخر “صُنع في UTC+8.” أي شخص يفتحها يمكنه تحويل لوحة المعلومات الغذائية.
تحت الغطاء: مجرد رقم كبير
2000-01-01 00:00:00 UTC → 0
2000-01-01 00:00:01 UTC → 1 000 000
- الوحدة: مايكروثانية (جزء من المليون من الثانية)
- النطاق: 4713 ق.م – 294276 م — مناسب لإنديانا جونز
- تخزين
timestampوtimestamptzمتطابق؛ التفسير هو ما يختلف
عرض في 15 ثانية
-- Client thinks in Shanghai time
SET TimeZone = 'Asia/Shanghai';
CREATE TABLE demo (
created_ts timestamp,
created_tz timestamptz
);
INSERT INTO demo VALUES ('2025-07-29 10:00', '2025-07-29 10:00');
| الاستعلام | النتيجة | السبب |
|---|---|---|
SELECT created_ts FROM demo; | 2025-07-29 10:00:00 | القيمة الخام، بدون حساب منطقة زمنية |
SELECT created_tz FROM demo; | 2025-07-29 10:00:00+08 | العلامة مُطبّقة عند الإخراج |
SET TimeZone = 'UTC'; ثم select | 2025-07-29 02:00:00+00 | نفس اللحظة، عدسة جديدة |
حساب الطوابع الزمنية والفترات
أحد أكثر الجوانب العملية لطوابع PostgreSQL الزمنية هو حساب الفترات. لأن كلا النوعين يخزنان عدد المايكروثانية، يمكنك جمع وطرح الفترات مباشرة:
-- Add 3 hours and 30 minutes
SELECT '2025-07-29 10:00'::timestamptz + INTERVAL '3 hours 30 minutes';
-- → 2025-07-29 13:30:00+08
-- Find the difference between two timestamps
SELECT '2025-07-30 09:00'::timestamptz - '2025-07-29 10:00'::timestamptz;
-- → 23:00:00 (an interval)
-- Extract specific fields
SELECT EXTRACT(EPOCH FROM '2025-07-29 10:00:00+08'::timestamptz);
-- → 1753768800 (Unix timestamp in seconds)
-- Truncate to day boundary (useful for daily aggregations)
SELECT date_trunc('day', '2025-07-29 15:42:19+08'::timestamptz);
-- → 2025-07-29 00:00:00+08
دالة EXTRACT(EPOCH FROM ...) مفيدة بشكل خاص عندما تحتاج لتمرير الطوابع الزمنية لأنظمة خارجية تتوقع ثوانٍ منذ حقبة Unix. وبالعكس، يمكنك تحويل حقبة إلى طابع زمني:
SELECT to_timestamp(1753768800);
-- → 2025-07-29 10:00:00+08 (in Asia/Shanghai session)
نقطة دقيقة لكن مهمة: حساب الفترات مع timestamp (بدون منطقة زمنية) يتجاهل انتقالات التوقيت الصيفي تمامًا، بينما timestamptz يحترمها. هذا يعني أن إضافة INTERVAL '1 day' لقيمة timestamptz تعبر حدود التوقيت الصيفي ستعيد بشكل صحيح نفس وقت الساعة الحائطية — وليس بالضبط 24 ساعة لاحقًا.
اعتبارات الفهرسة والأداء
يُخزّن كل من timestamp وtimestamptz كأعداد صحيحة بـ 8 بايت، لذا لا يوجد فرق في الأداء بينهما للتخزين أو الفهرسة. تعمل فهارس B-tree بشكل متطابق على كلا النوعين لأن المقارنة الأساسية مجرد مقارنة أعداد صحيحة.
ومع ذلك، هناك بعض الاعتبارات العملية:
- استعلامات النطاق:
WHERE created_at > '2025-07-01'تعمل بكفاءة مع فهرس على أي نوع. معtimestamptz، يحوّل PostgreSQL القيمة الحرفية إلى UTC قبل المقارنة، لذا يُستخدم الفهرس. - مفاتيح التقسيم: عند استخدام تقسيم النطاق على أعمدة الطوابع الزمنية،
timestamptzأكثر أمانًا عمومًا لأن حدود التقسيم لا لبس فيها (دائمًا UTC). معtimestamp، حد مثل'2025-07-01 00:00'قد يعني أشياء مختلفة لجلسات مختلفة. - الفهارس الوظيفية: إذا كنت تستعلم كثيرًا بالتاريخ فقط (متجاهلًا الوقت)، فكّر في فهرس على
date_trunc('day', created_at)لتسريع استعلامات التجميع اليومي.
المزالق الشائعة والإصلاحات السريعة
1. مستخدمون مختلفون، ساعات مختلفة
- السبب: العملاء يستخدمون إعدادات
TimeZoneمختلفة معtimestamptz - الإصلاح: إما أبقِ كل شيء
timestamp+ اتفق على منطقة واحدة، أو أفرضSET TimeZone = 'UTC'عند تهيئة الاتصال
نمط شائع في كود التطبيق هو ضبط المنطقة الزمنية مرة عند تهيئة مجمّع الاتصالات:
-- In your connection setup (e.g., pg pool config)
SET timezone = 'UTC';
هذا يضمن أن جميع الجلسات ترى نفس تمثيل UTC، وطبقة التطبيق تتعامل مع التحويل إلى التوقيت المحلي للعرض.
2. تخزين “وقت الحائط” لكن اخترت النوع الخطأ
- تقاويم الأعمال (ساعات العمل، المواعيد النهائية) يجب أن تستخدم
timestamp - سير العمل عبر الحدود (الطلبات، السجلات) يجب أن تخزّن UTC في
timestamptz
الاختبار بسيط: إذا كان السؤال “في أي لحظة حدث هذا؟” استخدم timestamptz. إذا كان السؤال “ماذا تقول ساعة الحائط؟” استخدم timestamp.
3. واجهات API المنزلقة
- أرسل دائمًا
timestamptzكسلاسل ISO-8601 مع الفارق (Zأو+08:00) - دع واجهة المستخدم تنسّق محليًا
4. مقارنة الطوابع الزمنية عبر الأنواع
خلط timestamp وtimestamptz في المقارنات أو الربطات مصدر شائع للأخطاء الدقيقة:
-- Dangerous: implicit cast applies session timezone
SELECT * FROM orders o
JOIN schedules s ON o.created_tz = s.start_ts;
-- PostgreSQL casts s.start_ts to timestamptz using session timezone
-- Different sessions can get different join results!
الإصلاح: حوّل بشكل صريح دائمًا عند المقارنة بين الأنواع، أو وحّد على نوع واحد لكل مجال.
5. مزالق إعدادات ORM الافتراضية
العديد من أنظمة ORM (Django، SQLAlchemy، ActiveRecord) تستخدم افتراضيًا timestamp بدون منطقة زمنية. تحقق من ملفات الترحيل — إذا كان تطبيقك يخدم مستخدمين عبر مناطق زمنية، تجاوز الافتراضي إلى timestamptz. في Django، اضبط USE_TZ = True في الإعدادات. في SQLAlchemy، استخدم DateTime(timezone=True).
ورقة غش: أي نوع أستخدم؟
تقويم محلي فقط → timestamp
أي شيء عالمي → timestamptz (خزّن UTC)
- التقارير المالية، جداول الحصص →
timestamp - سجلات التدقيق، طلبات التجارة الإلكترونية →
timestamptz
تحقق في ثوانٍ مع Go Tools
| الحاجة | الأداة | كيف |
|---|---|---|
| فحص قيمة الحقبة من SQL | محوّل الحقبة | الصق 1690622400، اضغط تحويل |
| مقارنة منطقتين زمنيتين بنظرة | محوّل المناطق الزمنية | أدخل 10:00 Asia/Shanghai |
| ترتيب JSON مجمّع بحقول زمنية | منسّق JSON | ألقِ الحمولة، نسّق وافحص |
جميع الأدوات تعمل بالكامل في متصفحك — لا تغادر البيانات جهازك أبدًا.
الأسئلة الشائعة
ما الفرق بين timestamp و timestamptz في PostgreSQL؟
timestamp (بدون منطقة زمنية) يخزّن قيمة تاريخ-وقت كما هي، بدون سياق منطقة زمنية. timestamptz (مع منطقة زمنية) يحوّل المدخل إلى UTC للتخزين ويحوّل مجددًا إلى المنطقة الزمنية للجلسة عند الاسترجاع. استخدم timestamptz لجميع الحالات تقريبًا — يمنع أخطاء المنطقة الزمنية عبر الأنظمة الموزعة.
هل يخزّن PostgreSQL فعلًا المنطقة الزمنية في timestamptz؟
لا — رغم الاسم، لا يخزّن PostgreSQL المنطقة الزمنية نفسها. يحوّل المدخل إلى UTC ويخزّن فقط قيمة UTC (عدد المايكروثانية من 2000-01-01). عند الاسترجاع، يحوّل من UTC إلى أي منطقة زمنية يحددها إعداد timezone لجلستك. معلومات المنطقة الزمنية الأصلية تُحذف.
كيف أغيّر المنطقة الزمنية لجلسة PostgreSQL؟
شغّل SET timezone = 'America/New_York'; لتغيير المنطقة الزمنية للجلسة. هذا يؤثر على كيفية عرض وتفسير قيم timestamptz. للإعدادات الافتراضية على مستوى الخادم، اضبط timezone في postgresql.conf. استخدم دائمًا أسماء مناطق IANA الزمنية (مثل Asia/Shanghai) بدلًا من الاختصارات (مثل CST) لتجنب الغموض.
هل أستخدم timestamp أم timestamptz لتخزين أوقات الأحداث؟
استخدم timestamptz لكل شيء تقريبًا — إجراءات المستخدم، استدعاءات API، سجلات التدقيق، والأحداث المجدولة. استخدم timestamp (بدون منطقة زمنية) فقط للأوقات المجردة غير المرتبطة بلحظة محددة، مثل “المتجر يفتح الساعة 09:00” التي تعني 9 صباحًا بأي منطقة زمنية محلية، وليس لحظة UTC محددة.
كيف يتعامل PostgreSQL مع التوقيت الصيفي مع timestamptz؟
يتعامل PostgreSQL مع التوقيت الصيفي بشكل صحيح عند استخدام timestamptz لأنه يخزّن كل شيء بتوقيت UTC داخليًا. عند استرجاع قيمة، يحوّل PostgreSQL من UTC باستخدام قواعد التوقيت الصيفي الحالية لمنطقتك الزمنية في الجلسة. هذا يعني أن نفس لحظة UTC المخزنة تعرض بشكل صحيح أوقاتًا محلية مختلفة قبل وبعد انتقال التوقيت الصيفي.
للحصول على دليل شامل حول طوابع Unix الزمنية — بما في ذلك التعامل مع الدقة وأفضل ممارسات المناطق الزمنية وأمثلة برمجية بلغات JavaScript وPython وGo — راجع دليل طابع Unix الزمني.
خلاصة
- كلا نوعي الوقت في Postgres هما عدادات مايكروثانية؛ الملصق هو الفرق الوحيد
- اختيار النوع الخطأ يعني طوابع زمنية محيرة وحسابات مكسورة
- اختبر وحوّل وتحقق من السلامة بالأدوات المناسبة لتوفير ساعات من تصحيح الأخطاء