Instant: ما هو، الطابع الزمني والتطبيق في التطوير

المؤلف: IT Sectr نُشر: 2026-07-13 وقت القراءة: 9 دق

Instant — فئة غير قابلة للتغيير من حزمة java.time، تمثل نقطة على الخط الزمني في UTC بدقة نانوثانية. على عكس LocalDateTime، لا يحتوي Instant على التاريخ والوقت بتنسيق قابل للقراءة البشرية — إنه تمثيل آلي للحظة. وفقًا لمواصفات Oracle Java 17 (2024)، تم تصميم Instant للتبادل الآلي للطوابع الزمنية وهو مماثل لـ System.currentTimeMillis()، ولكن بدقة نانوثانية.

النقاط الرئيسية

  • Instant — لحظة زمنية في UTC بدقة نانوثانية، غير قابلة للتغيير وآمنة للخيوط.
  • يخزن الوقت كعدد الثواني من الحقبة (1970-01-01T00:00:00Z) بالإضافة إلى النانوثواني.
  • Instant.now() يعيد اللحظة الحالية في UTC بغض النظر عن المنطقة الزمنية للجهاز.
  • للتحويل إلى تنسيق قابل للقراءة البشرية، يتم استخدام atZone(ZoneId) الذي يعيد ZonedDateTime.
  • Instant هو النوع المفضل لنقل الطوابع الزمنية في APIs والأنظمة الموزعة.

ما هو Instant؟

Instant هي فئة تصمم نقطة واحدة على الخط الزمني. يتكون تمثيلها الداخلي من حقلين: long seconds (عدد الثواني من 1970-01-01T00:00:00Z) و int nanos (النانوثواني داخل الثانية الحالية، من 0 إلى 999999999).

نطاق قيم Instant هو من -31557014167219200 إلى 31556889864403199 ثانية من الحقبة، ويغطي حوالي 292 مليون سنة في كلا الاتجاهين. هذا كافٍ لأي مهام عملية، بما في ذلك الحسابات الفلكية.

وفقًا لـ Baeldung (2024)، Instant هو جسر بين الأنواع القابلة للقراءة البشرية (LocalDateTime، ZonedDateTime) وتنسيقات الآلة (timestamp بالميلي ثانية). يُستخدم Instant للتسجيل والتخزين المؤقت والمزامنة وجميع المهام حيث تكون اللحظة المطلقة في الوقت مهمة.

تنفذ الفئة واجهات Comparable (لمقارنة اللحظات) و Temporal (لاستخدامها في API العامة لـ java.time). Instant غير قابل للتغيير — جميع الطرق تعيد كائنًا جديدًا.

Instant مقابل Date مقابل Long

قبل Java 8، كان يتم استخدام java.util.Date و System.currentTimeMillis() للعمل مع اللحظات الزمنية. كلا النهجين لهما عيوب. Date قابل للتغيير، غير آمن للخيوط، يخزن الوقت بالميلي ثانية من الحقبة، لكن أسماء طرقه قديمة (getYear() يعيد 116 لعام 2016).

Long (طابع زمني بسيط) سريع ومضغوط، لكنه لا يحتوي على دعم مدمج للنانوثواني، ولا يُعرض بتنسيق قابل للقراءة، ويتطلب تحليلاً يدويًا أثناء التصحيح. نهج Long أيضًا لا يميز أنواع البيانات — قد يمرر المطور قيمة غير صحيحة.

Instant يحل كل هذه المشاكل. إنه غير قابل للتغيير، يحتوي على معلومات دقة صريحة (ثواني + نانوثواني)، يتسلسل إلى تنسيق ISO-8601 “2026-07-21T15:00:00Z”، وله API غني للتحويلات. وفقًا لـ SonarSource (2024)، Instant هو البديل الموصى به لـ Date في جميع المشاريع الجديدة.

إنشاء وقراءة Instant

يتم الحصول على اللحظة الحالية عبر Instant.now(). على عكس LocalDateTime.now()، يعيد Instant.now() دائمًا الوقت في UTC، متجاهلاً المنطقة الزمنية للجهاز. هذا يجعله مثاليًا للطوابع الزمنية للخادم.

من القيم الموجودة: Instant.ofEpochSecond(long epochSecond) — من الثواني منذ الحقبة، Instant.ofEpochMilli(long epochMilli) — من الميلي ثانية، Instant.parse(CharSequence) — من سلسلة ISO-8601 (“2026-07-21T15:00:00Z”).

للقراءة: getEpochSecond() — عدد الثواني منذ الحقبة، toEpochMilli() — عدد الميلي ثانية، getNano() — النانوثواني. طريقة toString() تعيد سلسلة بتنسيق ISO-8601.

kotlin
val now = Instant.now()

val fromSeconds = Instant.ofEpochSecond(1784700000)
val fromMillis = Instant.ofEpochMilli(1784700000000)
val parsed = Instant.parse("2026-07-21T15:00:00Z")

val epochSecond = now.getEpochSecond()
val epochMilli = now.toEpochMilli()
val nanos = now.getNano()

تحويل Instant إلى تنسيقات أخرى

يتم تحويل Instant إلى ZonedDateTime عبر atZone(ZoneId). على سبيل المثال، Instant.now().atZone(ZoneId.of(“Europe/Moscow”)) يعيد ZonedDateTime لموسكو. بدون منطقة، التحويل مستحيل — Instant لا يحتوي على معلومات تقويمية.

لتحويل Instant إلى LocalDateTime: atZone(ZoneId).toLocalDateTime(). هذا النهج صريح ولا يفقد المعلومات. التحويل العكسي: LocalDateTime.atZone(ZoneId).toInstant().

للتوافق مع java.util.Date: Date.from(instant) و date.toInstant(). هذا تحويل ثنائي الاتجاه يحافظ على الدقة حتى الميلي ثانية (Date لا يدعم النانوثواني). لـ java.sql.Timestamp، استخدم Timestamp.from(instant) مع دعم النانوثواني.

kotlin
val instant = Instant.now()

val zoned = instant.atZone(ZoneId.of("Europe/Moscow"))
val localDateTime = instant
    .atZone(ZoneId.systemDefault())
    .toLocalDateTime()

val oldDate = Date.from(instant)
val backToInstant = oldDate.toInstant()

Instant والمناطق الزمنية

الميزة الرئيسية لـ Instant هي أنه مستقل تمامًا عن المناطق الزمنية. Instant.now() يعيد نفس النتيجة على أي جهاز في أي مكان في العالم. يتم تحقيق ذلك عن طريق تثبيت الوقت في UTC.

المنطقة الزمنية مطلوبة فقط لعرض Instant للإنسان. لهذا، يتم استخدام atZone(ZoneId). ZoneId.systemDefault() يعيد المنطقة الزمنية للجهاز المضبوطة في نظام التشغيل. ZoneOffset.UTC هو الثابت لـ UTC.

في الأنظمة الموزعة، يُوصى بتخزين ونقل جميع الطوابع الزمنية في Instant (أو OffsetDateTime مع ZoneOffset.UTC). يتم التحويل إلى الوقت المحلي فقط على العميل قبل العرض للمستخدم. هذا يمنع الالتباس مع المناطق الزمنية.

مزامنة الوقت عبر Instant

في تطبيقات Android الموزعة، مزامنة الوقت ضرورية للتخزين المؤقت الصحيح والإشعارات والتحرير التعاوني. Instant هو الخيار الطبيعي لهذه المهمة بفضل ارتباطه بـ UTC.

عند مقارنة الطوابع الزمنية من أجهزة مختلفة، يجب مراعاة أن ساعات النظام قد تتباعد. يُوصى باستخدام وقت الخادم كمرجع. يعيد الخادم Instant في UTC، ويقارنه العميل مع Instant المحلي فقط للحسابات النسبية.

لحساب الفرق بين لحظتين، استخدم Duration.between(Instant start, Instant end). تعيد هذه الطريقة Duration يمكن تحويلها إلى ساعات ودقائق وثوان. تسمح طريقا isAfter() و isBefore() بمقارنة اللحظات.

kotlin
fun isCacheExpired(
    cachedAt: Instant,
    ttlMinutes: Long
): Boolean {
    val elapsed = Duration.between(cachedAt, Instant.now())
    return elapsed.toMinutes() >= ttlMinutes
}

أمثلة عملية في Android

المثال الأول هو تسجيل الأحداث بطابع زمني. Instant يُحفظ في قاعدة بيانات Room ويُرسل إلى الخادم. يتم تسجيل الطابع الزمني في UTC لتفسير لا لبس فيه.

kotlin
data class EventLog(
    val id: Long = 0,
    val eventName: String,
    val timestamp: Instant
)

class Converters {
    @TypeConverter
    fun fromInstant(value: Instant?): Long? {
        return value?.toEpochMilli()
    }

    @TypeConverter
    fun toInstant(value: Long?): Instant? {
        return value?.let { Instant.ofEpochMilli(it) }
    }
}

المثال الثاني هو تحديد الوقت المنقضي منذ حدث. نستخدم Duration.between لعرض “منذ 5 دقائق”، “منذ ساعتين” — تنسيق شائع في المراسلات والشبكات الاجتماعية.

kotlin
fun timeAgo(instant: Instant): String {
    val duration = Duration.between(instant, Instant.now())
    return when {
        duration.toMinutes() < 1 -> "just now"
        duration.toHours() < 1 -> "${duration.toMinutes()} min ago"
        duration.toDays() < 1 -> "${duration.toHours()} h ago"
        else -> "${duration.toDays()} d ago"
    }
}

المثال الثالث هو مزامنة البيانات بين الخادم والعميل. نستخدم Instant لتتبع وقت آخر تحديث.

kotlin
class SyncManager {
    private var lastSyncAt: Instant? = null

    fun sync() {
        val syncStart = Instant.now()
        // server request with lastSyncAt
        lastSyncAt = syncStart
    }

    fun shouldSync(intervalMinutes: Long): Boolean {
        val last = lastSyncAt ?: return true
        return Duration.between(last, Instant.now())
            .toMinutes() >= intervalMinutes
    }
}

الأخطاء الشائعة

الخطأ الأول هو استخدام Instant.now().toString() للعرض للمستخدم. Instant يُخرج بتنسيق UTC “2026-07-21T15:00:00Z”، وهو غير قابل للقراءة للبشر. قم دائمًا بتحويل Instant عبر atZone() إلى المنطقة الزمنية المحلية قبل العرض.

الخطأ الثاني هو فقدان النانوثواني عند التحويل إلى java.util.Date. Date يدعم الميلي ثانية فقط. إذا كان Instant يحتوي على نانوثواني، فسيتم تجاهلها في Date.from(instant). استخدم Instant.truncatedTo(ChronoUnit.MILLIS) لتحديد الدقة صراحة.

الخطأ الثالث هو الخلط بين toEpochMilli() و getEpochSecond(). toEpochMilli() يعيد عدد الميلي ثانية من الحقبة (long)، بينما getEpochSecond() يعيد عدد الثواني (long). الخلط بين هاتين الطريقتين يمكن أن يؤدي إلى خطأ بمقدار 1000 مرة.

الخطأ الرابع هو افتراض أن Instant.now() متزامن عبر جميع الأجهزة. ساعات النظام قد تختلف بدقائق أو حتى ساعات. للعمليات الحساسة للوقت (المصادقة، المدفوعات)، استخدم Instant الخادم كمصدر للحقيقة.

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

كيف يختلف Instant عن System.currentTimeMillis()؟

System.currentTimeMillis() يعيد long — عدد الميلي ثانية من الحقبة دون ربط بالمنطقة الزمنية. Instant يوفر نفس الوظيفة ولكن بدقة نانوثانية وAPI غني للتحويلات والمقارنات والتوافق مع java.time.

كيفية حفظ Instant في Room Database؟

Room لا يدعم Instant مباشرة. استخدم TypeConverter الذي يحول Instant إلى Long (toEpochMilli) والعكس (Instant.ofEpochMilli). لدقة النانوثواني، احفظ حقلين: الحقبة-ثواني والنانوثواني.

هل يمكن استخدام Instant كمفتاح في HashMap؟

نعم، Instant غير قابل للتغيير وينفذ equals() و hashCode() بشكل صحيح. اثنان Instant بنفس القيمة سيكونان متساويين. هذا يجعله مفتاحًا موثوقًا لـ HashMap والمجموعات الأخرى، على عكس java.util.Date القابل للتغيير.

كيفية الحصول على الفرق بين اثنين Instant؟

استخدم Duration.between(start, end) للحصول على Duration أو ChronoUnit.SECONDS.between(start, end) للفرق بالثواني (long). Duration يوفر طرق toMinutes() و toHours() و toDays() و toNanos().

لماذا يتطلب Instant.parse() لاحقة Z أو إزاحة؟

Instant مصمم كنقطة مطلقة على الخط الزمني. بدون تحديد منطقة زمنية أو UTC، التحليل مستحيل لأن Instant لا يحتوي على معلومات تقويمية. اللاحقة “Z” تدل على إزاحة صفرية (UTC) وهي إلزامية لتنسيق ISO-8601.

الخلاصة

  • Instant — فئة غير قابلة للتغيير للحظة زمنية مطلقة في UTC بدقة نانوثانية، مفضلة للطوابع الزمنية في الأنظمة الموزعة.
  • على عكس java.util.Date، Instant غير قابل للتغيير وآمن للخيوط وله دقة نانوثانية. على عكس Long — فهو مُنمط صراحة ولا يسمح بالخلط مع القيم الرقمية الأخرى.
  • للعرض للمستخدم، يتم تحويل Instant عبر atZone(ZoneId) إلى ZonedDateTime، وإلا سيتم عرض الوقت في UTC.
  • للنقل في API، استخدم Instant.toString() (ISO-8601) أو toEpochMilli() للتوافق مع التنسيقات القديمة.
  • عند العمل مع Room، احفظ Instant كـ Long عبر TypeConverter باستخدام طريقة toEpochMilli().
  • لحساب الوقت المنقضي، استخدم Duration.between() الذي يتعامل بشكل صحيح مع النانوثواني.
  • لا تعتمد على مزامنة ساعة الجهاز — للعمليات الحرجة، استخدم Instant الخادم كمصدر للحقيقة.

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا