لماذا الوقت مشكلة كبيرة (وكيف يحلها Kotlin)
\n\nإذا سبق لك كتابة تطبيق يخدم مستخدمين من مناطق زمنية مختلفة، فأنت تعرف هذه المعاناة. يصل الطلب مع طابع زمني، في قاعدة البيانات مخزّن بصيغة UTC، والمستخدم في نيويورك يشكو من أن الإشعار وصل في الثالثة صباحًا، بينما في قاعدة البيانات الساعة الثامنة صباحًا. هل هذا مألوف؟
\n\nالتعامل مع المناطق الزمنية هو مجال في البرمجة تبدو بساطته خادعة، لكنها تسبب عشرات الأخطاء. لكن Kotlin، الذي ورث أفضل ممارسات Java، يمنحنا ترسانة قوية لمكافحة الفوضى. في هذا المقال، سنشرح كيفية التعامل الصحيح مع المناطق الزمنية في Kotlin، وتجنب الأخطاء الشائعة، وكتابة كود لا ينهار عند التحول إلى التوقيت الصيفي.
\n\nنبذة سريعة: ما هي المنطقة الزمنية؟
\n\nقبل الغوص في الكود، دعونا نتفق على المصطلحات. المنطقة الزمنية (time zone) ليست مجرد فرق توقيت عن UTC. بل هي أيضًا مجموعة قواعد للتحول إلى التوقيت الصيفي/الشتوي (DST). على سبيل المثال، Europe/Moscow ليس مجرد +3 ساعات، بل يشمل أيضًا تغييرات تاريخية (في 2014 توقفت روسيا عن تغيير الساعة).
في عالم Java/Kotlin، هناك مفهومان رئيسيان مسؤولان عن ذلك:
\n\n- \n
- ZoneId — معرف المنطقة الزمنية (مثل "America/New_York"). يحتوي على كل تاريخ DST. \n
- ZoneOffset — مجرد فرق توقيت (مثل +05:00). بدون قواعد تحول. \n
الفرق جوهري: إذا استخدمت ZoneOffset بدلاً من ZoneId، سينهار كودك في يوم تغيير الساعة. تذكر هذا جيدًا.
الفئات الأساسية لـ java.time في Kotlin
\n\nيدعم Kotlin بالكامل حزمة java.time (Java 8+). إذا كنت تكتب لتطبيق Android — لا تنسَ إضافة desugaring أو استخدم ThreeTenABP. بالنسبة لـ Kotlin على الخادم، لا توجد مشاكل.
Instant — لحظة زمنية
\n\nInstant هي لحظة مطلقة على الخط الزمني. غير مرتبطة بمنطقة زمنية. هذا هو ما يجب تخزينه في قاعدة البيانات.
import java.time.Instant\n\nfun main() {\n val now = Instant.now()\n println(now) // 2025-03-15T10:30:00.123Z\n}\n\nلاحظ الحرف Z في النهاية — وهو توقيت زولو، أي UTC. خزّن كل شيء بصيغة UTC — هذه هي القاعدة الذهبية.
ZonedDateTime — الوقت مع مراعاة المنطقة
\n\nعندما تريد عرض الوقت للمستخدم، استخدم ZonedDateTime. يخزن التاريخ والوقت وفرق التوقيت ومعرف المنطقة.
import java.time.ZonedDateTime\nimport java.time.ZoneId\n\nfun main() {\n val moscowTime = ZonedDateTime.now(ZoneId.of("Europe/Moscow"))\n println(moscowTime) // 2025-03-15T13:30:00.123+03:00[Europe/Moscow]\n}\n\nهل ترى الأقواس المربعة؟ هذا هو ZoneId الذي يسمح للنظام بمعالجة DST بشكل صحيح.
OffsetDateTime — الوقت مع فرق التوقيت
\n\nإذا كنت لا تحتاج معلومات عن المنطقة، بل فقط فرق التوقيت — استخدم OffsetDateTime. إنه أخف وأسرع.
import java.time.OffsetDateTime\nimport java.time.ZoneOffset\n\nfun main() {\n val offsetTime = OffsetDateTime.now(ZoneOffset.ofHours(5))\n println(offsetTime) // 2025-03-15T15:30:00.123+05:00\n}\n\nلكن تذكر: إذا حفظت OffsetDateTime لنيويورك، ثم تحولت الولايات المتحدة إلى التوقيت الصيفي — سيصبح فرق التوقيت قديمًا.
التحويل بين المناطق الزمنية في Kotlin
\n\nالمهمة الأكثر شيوعًا هي أخذ الوقت من قاعدة البيانات (UTC) وعرضه في المنطقة الزمنية للمستخدم. دعنا نشرح كيفية القيام بذلك بشكل صحيح.
\n\nمن UTC إلى المنطقة المحلية
\n\nimport java.time.Instant\nimport java.time.ZoneId\nimport java.time.ZonedDateTime\n\nfun convertUtcToLocal(utcInstant: Instant, targetZone: String): ZonedDateTime {\n return utcInstant.atZone(ZoneId.of(targetZone))\n}\n\nfun main() {\n val utcNow =