محوّل طوابع زمن Unix

حوّل طابع زمن Unix إلى تاريخ في أي منطقة زمنية، أو حوّل تاريخًا إلى قيمة epoch: تُكتشف الثواني والميلي ثانية تلقائيًا، مع صيغة ISO 8601 وكم مضى من الوقت.

زمن Unix الحالي
جارٍ التحديد…
الإدخال

ما هو طابع زمن Unix

طابع زمن Unix — ويُسمّى أيضًا زمن epoch أو زمن POSIX — هو رقم واحد يَعُدّ كم ثانية مضت منذ الساعة 00:00:00 بتوقيت UTC من الأول من يناير 1970، وهي لحظة تُعرف باسم epoch الخاصة بـ Unix. ولأنه عدد صحيح واحد، بلا منطقة زمنية ولا تقويم ولا تنسيق، فهو الصيغة التي تلجأ إليها الحواسيب كلما لزم تخزين لحظة زمنية أو ترتيبها أو مقارنتها أو إرسالها عبر الشبكة. وهو ما يقف خلف العمود «created_at» في قاعدة بياناتك، وخلف الحقلين «iat» و«exp» في رمز JWT، وخلف mtime لأي ملف، وخلف الطوابع الزمنية في كل ملف سجل تقريبًا ستقرؤه يومًا.

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

ثوانٍ أم ميلي ثانية — الالتباس الذي يعَضّ

العُرف الأصلي في Unix يَعُدّ ثوانيَ كاملة، وهو ما تحصل عليه من date +%s في الصدفة، ومن time()‎ في PHP، ومن time.time()‎ في بايثون بعد الاقتطاع. أما جافاسكربت وجافا وكثير غيرهما فتَعُدّ الميلي ثانية: فـ Date.now()‎ يُعيد رقمًا أكبر بألف مرة. وكلاهما يُسمّى «طابعًا زمنيًا»، والخلط بينهما من أشيع أخطاء التواريخ على الإطلاق.

والعطل هنا صامت لا صاخب. اقرأ قيمة بالميلي ثانية على أنها ثوانٍ فيهبط تاريخك عشرات آلاف السنين في المستقبل؛ واقرأ قيمة بالثواني على أنها ميلي ثانية فينهار كل شيء إلى يناير 1970. ولا يرفع أيٌّ منهما خطأً — بل تحصل ببساطة على تاريخ خاطئ يبدو كأنه تاريخ حقيقي.

تُخمّن هذه الأداة اعتمادًا على رتبة حجم الرقم، ثم تخبرك بما خمّنته إلى جوار النتيجة مباشرة. فثواني epoch اليوم من عشرة أرقام، وميلي ثوانيها من ثلاثة عشر، ولذلك يكون التخمين صائبًا في الغالب الأعم — غير أن «الغالب» لا يكفي لقيمة توشك أن تلصقها في تقرير خلل، ولهذا تبقى القراءة ظاهرة دائمًا وعلى بُعد نقرة واحدة من التبديل.

المناطق الزمنية وUTC ولماذا «المحلي» زَلِق

لا منطقة زمنية لطابع زمن Unix. فهو يحدّد لحظة: 1716197600 هو 2024-05-20T09:33:20Z، وتلك اللحظة نفسها هي في آنٍ واحد 10:33 في لندن، و12:33 في القدس، و18:33 في طوكيو، لأن لندن والقدس تعملان بالتوقيت الصيفي في ذلك اليوم ولا توقيت صيفي في اليابان إطلاقًا. المنطقة الزمنية ليست جزءًا من القيمة؛ بل هي العدسة التي تنظر بها إليها.

ولهذا تعرض الأداة عدة عدسات في وقت واحد. فـ UTC هو المرجع المحايد الذي تتفق عليه كل الخوادم وكل السجلات. وتوقيتك المحلي هو ما يبلّغ عنه جهازك أنت، ويُعرض مع اسم منطقته (مثل Europe/Berlin) لتعرف دائمًا أي عدسة أنتجته — والاكتشاف يجري داخل متصفحك، فالزائر من برلين يرى توقيت برلين والزائر من طوكيو يرى توقيت طوكيو. أما السطر الثالث فهو أي منطقة تختارها من قاعدة بيانات IANA الكاملة، وهو السطر الذي تحتاجه حين تقرأ سجلّ خادم يقيم في مكان آخر.

  • UTC — المرجع الذي تتفق عليه كل الأنظمة، والصواب في التخزين وتدوين السجلات.
  • المحلي — اللحظة نفسها كما يراها جهازك، موسومة بالمنطقة التي اكتُشفت.
  • منطقة من اختيارك — لقراءة السجلات والتتبّعات من أجهزة في أماكن أخرى.
  • ISO 8601 — صيغة النص المعتمدة للتبادل، مثل 2024-05-20T09:33:20.000Z.
  • نسبي — «قبل 3 ساعات»، لإحساس سريع بمدى حداثة الشيء.

وتُعرض الإزاحات بجانب كل توقيت (‎+03:00 و‎-04:00) لأنها ليست ثابتة: فأغلب المناطق تنزاح ساعةً بسبب التوقيت الصيفي، حتى إن المنطقة الواحدة قد تُنتج إزاحات مختلفة في أوقات مختلفة من السنة. والتاريخ القريب من موعد التحويل هو بالضبط الموضع الذي يخطئ فيه الحساب اليدوي.

التعامل مع زمن epoch عمليًا

لكل لغة ولكل صدفة طريقتها في إنتاج قيم epoch وقراءتها. وهذه تستحق أن تُحفظ:

date +%s                         # الصدفة: الوقت الحالي بالثواني
date -d @1716197600              # الصدفة (GNU): من الثواني إلى تاريخ
Date.now()                       # جافاسكربت: الوقت الحالي بالميلي ثانية
new Date(1716197600 * 1000)      # جافاسكربت: من الثواني إلى Date
time.time()                      # بايثون: ثوانٍ، كعدد عشري
datetime.fromtimestamp(1716197600, tz=timezone.utc)
SELECT EXTRACT(EPOCH FROM now()) -- PostgreSQL: ثوانٍ

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

وثمة أمر آخر يحسن معرفته: عدّاد الثواني ذو 32 بتًا بإشارة ينفد في 19 يناير 2038، وهي «مشكلة عام 2038». الأنظمة الحديثة تستعمل قيمًا من 64 بتًا وهي بمأمن، لكن الأجهزة المضمّنة القديمة وأعمدة قواعد البيانات الموروثة هي بالضبط المواضع التي قد تصادفها فيها بعد.

ما هي epoch، ولماذا لا تبدأ كل الطوابع الزمنية من 1970

epoch، بهذا المعنى، ليست إلا صفرًا مختارًا — اللحظة التي يُعرَّف عدّاد ما بأنه يبدأ منها. وepoch الخاصة بـ Unix هي 00:00:00 بتوقيت UTC من الأول من يناير 1970، وقد اختيرت لأنها تاريخ مستدير مريح لا لأنها مشتقّة من شيء: فليس في الصيغة نفسها ما يوجب ذلك اليوم بعينه. وكانت Unix الأولى تَعُدّ أسداس الثانية من صفر أقرب بكثير، وقد قال دليل الإصدار الثالث صراحةً لماذا لم يكن لذلك أن يدوم — اثنتان وثلاثون بتًا من الأسداس تضمن أزمة كل 2.26 سنة، أي نحو 828 يومًا. أما الثواني الكاملة في الاثنتين والثلاثين بتًا نفسها فتبلغ مئة وستًّا وثلاثين سنة، أو ثمانيًا وستين إن كان العدّاد بإشارة، وهو تاريخ 2038 أعلاه.

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

  • Windows FILETIME — نبضات من مئة نانوثانية منذ الأول من يناير 1601 بتوقيت UTC. اقسم على عشرة ملايين واطرح 11644473600 لتحصل على ثواني Unix؛ فاللحظة 1716197600 المستعملة في هذا الدليل كله هي 133606712000000000 في ذلك النظام.
  • ‎.NET DateTime.Ticks — النبضة نفسها من مئة نانوثانية، لكنها تُعَدّ من بداية السنة 1. وepoch الخاصة بـ Unix نفسها تقع عند 621355968000000000 نبضة، وهو العدد الذي يُطرح قبل القسمة.
  • التاريخ المرجعي لدى Apple — ثوانٍ منذ الأول من يناير 2001 بتوقيت UTC، وتستعمله Cocoa وCore Data. أضف 978307200 لتحصل على ثواني Unix: فـ 1716197600 هي 737890400 هناك.
  • الأرقام التسلسلية في Excel — أيام لا ثوانٍ، وتُعَدّ من 30 ديسمبر 1899، لأن Excel يعامل 1900 على أنها سنة كبيسة ولم تكن كذلك.
  • زمن GPS — ثوانٍ منذ 6 يناير 1980، تُعَدّ دون تخطّي أي ثانية كبيسة قط، فلم يعد زمن GPS وUTC متفقين.

وهذا الأخير يشير إلى أمر يصدق على قيمة Unix نفسها: فهي ليست قراءة ساعة إيقاف. يعرّفها POSIX بأنها قيمة تقارب عدد الثواني المنقضية منذ epoch، ويشترط أن يُحسَب كل يوم بـ 86400 ثانية بالضبط — فالثواني الكبيسة المُدرجة في UTC لا تُعَدّ إطلاقًا. ولذلك فالفرق بين طابعين زمنيين من طوابع Unix هو عدد ثواني التقويم بينهما لا عدد الثواني التي انقضت فعلًا، وأفضل فهم للقيمة أنها ترميز لقراءة تقويمية بتوقيت UTC. وهذا أيضًا سبب أن لا طابع زمني يسمّي ثانية كبيسة قط: فلا خانة لها في هذا الترميز.

من تاريخ إلى قيمة epoch: في الصدفة وجافاسكربت وبايثون

الكتلة أعلاه تأخذ قيمة epoch وتخرج منها شيئًا مقروءًا. أما الاتجاه المعاكس — تاريخ عندك سلفًا، في صورة نص أو مجموعة أعداد، يصير قيمة epoch — فهو الموضع الذي تسكنه علل المناطق الزمنية، لأن كل واحدة من هذه سوف تفترض منطقةً في صمت متى لم يسمِّ النص واحدة. وهذه هي المهمة نفسها بثلاث طرق، على لحظة هذا الدليل 2024-05-20T09:33:20Z. وسطور الصدفة هي date من GNU؛ أما date في BSD وmacOS فيأخذ رايات أخرى.

date -u -d '2024-05-20 09:33:20' +%s     # 1716197600 — الراية -u تقرأ النص بتوقيت UTC
date -d '2024-05-20 09:33:20 UTC' +%s    # المثل نفسه، مع تسمية المنطقة داخل النص
date -d '2024-05-20 09:33:20' +%s        # لا هذا ولا ذاك: منطقة جهازك، لحظة أخرى
Date.parse('2024-05-20T09:33:20Z') / 1000    // 1716197600 — الحرف Z هو ما يجعلها UTC
new Date('2024-05-20T09:33:20Z').getTime()   // اللحظة نفسها بالميلي ثانية
Date.parse('2024-05-20 09:33:20')            // بلا Z: منطقة جهازك، لحظة أخرى
from datetime import datetime, timezone
int(datetime(2024, 5, 20, 9, 33, 20, tzinfo=timezone.utc).timestamp())   # 1716197600 — كائن datetime واعٍ
int(datetime.fromisoformat('2024-05-20T09:33:20+00:00').timestamp())     # المثل نفسه، محلَّلًا من سلسلة نصية
int(datetime(2024, 5, 20, 9, 33, 20).timestamp())                        # ساذج: منطقة جهازك

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

الأسئلة الشائعة

كيف تعرف الأداة إن كان رقمي بالثواني أم بالميلي ثانية؟
من رتبة حجمه: القيم دون 1e11 تقريبًا تُقرأ ثوانيَ، والأكبر منها تُقرأ ميلي ثانية. وهذا اليوم يفصل بوضوح بين الثواني ذات العشرة أرقام والميلي ثواني ذات الثلاثة عشر. والقراءة معروضة بجانب النتيجة ويمكنك تبديلها بنقرة واحدة، فالتخمين لا يُخفى عنك أبدًا.
ما المنطقة الزمنية التي تُعدّ «محلية»؟
المنطقة التي يبلّغ عنها جهازك أنت، تُكتشف في متصفحك وتُعرض باسمها كي لا يبقى شك. إنها منطقتك أنت لا منطقة الموقع: فالزائر من برلين يرى توقيت برلين والزائر من طوكيو يرى توقيت طوكيو.
هل يمكنني تحويل تاريخ إلى طابع زمني؟
نعم. الحقل نفسه يقبل الاتجاهين: اكتب رقمًا فتحصل على تاريخ، واكتب تاريخًا مثل 2024-05-20 أو 2024-05-20T09:33:20Z فتحصل على قيمة epoch. ولا وضع عليك تبديله.
هل يتعامل مع تواريخ ما قبل 1970؟
نعم. الطوابع الزمنية السابقة لـ epoch سالبة ببساطة — فالقيمة ‎-86400 هي 31 ديسمبر 1969 — والقيم السالبة تُقبل وتُحوَّل كالمعتاد.
لماذا يعرض طابعي الزمني تاريخًا غير الذي توقّعته؟
يعود ذلك دائمًا تقريبًا إلى أحد سببين: إما أن قراءة الثواني/الميلي ثانية ليست ما افترضته (انظر إلى الوسم وبدّله)، وإما أنك تقارن قيمة بتوقيت UTC بتوقّع بالتوقيت المحلي. وتعرض الأداة السطرين جنبًا إلى جنب لترى أيّهما الحاصل.
ما مشكلة عام 2038؟
الأنظمة التي تخزّن ثواني epoch في عدد صحيح من 32 بتًا بإشارة تفيض في 19 يناير 2038 وتنقلب إلى رقم سالب، فتعطي تواريخ في عام 1901. وكل ما يستعمل قيمًا من 64 بتًا — أي كل ما هو حديث تقريبًا — غير متأثر، لكن الأنظمة المضمّنة الموروثة وأعمدة قواعد البيانات القديمة قد تظل معرّضة للخطر.
لماذا يبلغ طابعي الزمني ثمانية عشر رقمًا؟
لأنه على الأرجح ليس طابع زمن Unix. فثواني Unix اليوم من عشرة أرقام وميلي ثوانيها من ثلاثة عشر؛ أما ثمانية عشر فهي هيئة نبضة من مئة نانوثانية، وهو ما يَعُدّه Windows FILETIME من 1601 وما يَعُدّه ‎.NET من بداية السنة 1. وستة عشر رقمًا هي الحالة الشائعة الأخرى — اللحظة نفسها بالميكروثانية. والقسم الخاص بـ epoch أعلاه يعطي الثابت الذي يُطرح في كل حالة.
كيف أحصل على طابع زمن Unix من تاريخ في بايثون؟
ابنِ كائن datetime واعيًا — أي كائنًا يحمل tzinfo — ثم استدعِ فيه الدالة timestamp، واقتطع الناتج إلى عدد صحيح. السطر موجود في كتلة الشيفرة أعلاه، والقسم نفسه يعرض المهمة نفسها في الصدفة وفي جافاسكربت. وكل الصعوبة في tzinfo: احذفه فتقرأ بايثون أعدادك على أنها التوقيت المحلي للجهاز وتعيد قيمة مختلفة دون أن تخبرك.
هل يُرسَل الطابع الزمني الذي أُدخله إلى أي جهة؟
لا. التحليل والتحويل والتنسيق تجري كلها داخل متصفحك، اعتمادًا على دعم التواريخ والمناطق الزمنية المدمج في المنصّة نفسها. ولا يغادر شيء مما تكتبه جهازك.

أدوات ذات صلة