قيمة time.Time الافتراضية في Go: أخطاء قاتلة في الكود

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: ذلك الخطأ الذي وقع فيه الجميع

\n\n

هل سبق لك أن رأيت في سجلات الأخطاء تاريخًا مثل 0001-01-01 00:00:00 +0000 UTC وتساءلت: "من أين أتى هذا؟ لم أقم بتهيئته!"؟ تهانينا، لقد واجهت القيمة الافتراضية لـ time.Time في Go. هذا فخ كلاسيكي يجذب آلاف المشاهدات على Stack Overflow ومئات الساعات من التصحيح حول العالم.

\n\n

في هذه المقالة، سنشرح ما هي القيمة الافتراضية (zero value) لـ time.Time، ولماذا تتصرف بشكل غير متوقع، وكيف تفرق بين التاريخ "الفارغ" والتاريخ الحقيقي، والأهم من ذلك — كيف تتجنب هذه المشاكل في بيئة الإنتاج. إذا كنت تكتب كودًا بلغة Go وتتعامل مع التواريخ، فهذا المحتوى سينقذك من الصداع.

\n\n

ما هي القيمة الافتراضية في Go ولماذا time.Time حالة خاصة

\n\n

في Go، لكل نوع قيمة افتراضية (zero value). للأرقام هي 0، للنصوص هي ""، للمؤشرات هي nil. المنطق بسيط: إذا أعلنت متغيرًا دون تهيئة، فإنه يأخذ قيمة "فارغة". لكن مع time.Time الأمور أكثر تعقيدًا.

\n\n

القيمة الافتراضية لـ time.Time ليست Unix timestamp 0 (1970-01-01) كما قد تتوقع. إنها 0001-01-01 00:00:00 +0000 UTC. لماذا؟ لأن time.Time داخليًا يخزن النانو ثانية منذ 1 يناير من العام 1 (حسب التقويم الميلادي). الصفر في هذا التمثيل يعطي العام 1.

\n\n
package main\n\nimport (\n    "fmt"\n    "time"\n)\n\nfunc main() {\n    var t time.Time\n    fmt.Println(t) // 0001-01-01 00:00:00 +0000 UTC\n    fmt.Println(t.Unix()) // -62135596800 (رقم سالب!)\n}\n
\n\n

هل ترى المشكلة بالفعل؟ إذا حاولت التحقق بـ if t == 0 أو المقارنة مع Unix zero، فسيتعطل كودك. دعنا نستعرض أهم الأخطاء مع القيمة الافتراضية لـ time.Time وطرق تجنبها.

\n\n

أهم 5 أخطاء مع القيمة الافتراضية لـ time.Time

\n\n

1. المقارنة مع Unix timestamp 0

\n\n

الخطأ الأكثر شيوعًا هو محاولة التحقق من أن الوقت "فارغ" بمقارنته مع time.Unix(0, 0). هذا لن يعمل لأن القيمة الافتراضية ليست عام 1970.

\n\n
// ❌ خطأ: لن يكون true أبدًا\nvar t time.Time\nif t == time.Unix(0, 0) {\n    fmt.Println("الوقت فارغ")\n}\n\n// ✅ الصحيح: استخدم دالة IsZero()\nif t.IsZero() {\n    fmt.Println("الوقت فارغ")\n}\n
\n\n

دالة IsZero() هي أداتك الرئيسية. إنها ترجع true فقط للقيمة الافتراضية، وليس لأي تاريخ "قديم".

\n\n

2. التسلسل إلى JSON: null غير متوقع

\n\n

عند تسلسل هيكل بيانات يحتوي على time.Time إلى JSON، تتحول القيمة الافتراضية إلى النص "0001-01-01T00:00:00Z". هذا يعطل API والواجهة الأمامية وقواعد البيانات. يرى العملاء تاريخًا غريبًا بدلاً من null.

\n\n
type Event struct {\n    Name      string    `json:"name"`\n    StartTime time.Time `json:"start_time"`\n}\n\nfunc main() {\n    e := Event{Name: "test"}\n    data, _ := json.Marshal(e)\n    fmt.Println(string(data))\n    // {"name":"test","start_time":"0001-01-01T00:00:00Z"}\n    // ❌ يجب أن يكون null أو الحقل غائبًا\n}\n
\n\n

الحل: استخدم مؤشرًا لـ time.Time (*time.Time) أو نوعًا مخصصًا مع منطق تسلسل.

\n\n
// ✅ الخيار باستخدام المؤشر\ntype Event struct {\n    Name      string     `json:"name"`\n    StartTime *time.Time `json:"start_time,omitempty"`\n}\n\n// ✅ أو نوع مخصص مع omitempty\ntype CustomTime struct {\n    time.Time\n}\n\nfunc (ct CustomTime) MarshalJSON() ([]byte, error) {\n    if ct.IsZero() {\n        return []byte("null"), nil\n    }\n    return json.Marshal(ct.Time)\n}\n
\n\n

3. مشاكل مع قواعد البيانات (SQL)

\n\n

عند إدراج قيمة time.Time افتراضية في PostgreSQL أو MySQL، ستحصل على التاريخ 0001-01-01، مما يسبب خطأ في نطاق القيم المسموح بها (العام 1 خارج النطاق).

المدونات

توصيات الكتب