Instant — فئة غير قابلة للتغيير من حزمة java.time، تمثل نقطة على الخط الزمني في UTC بدقة نانوثانية. على عكس LocalDateTime، لا يحتوي Instant على التاريخ والوقت بتنسيق قابل للقراءة البشرية — إنه تمثيل آلي للحظة. وفقًا لمواصفات Oracle Java 17 (2024)، تم تصميم Instant للتبادل الآلي للطوابع الزمنية وهو مماثل لـ System.currentTimeMillis()، ولكن بدقة نانوثانية.
النقاط الرئيسية
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 غير قابل للتغيير — جميع الطرق تعيد كائنًا جديدًا.
قبل 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.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.
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 إلى 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) مع دعم النانوثواني.
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.now() يعيد نفس النتيجة على أي جهاز في أي مكان في العالم. يتم تحقيق ذلك عن طريق تثبيت الوقت في UTC.
المنطقة الزمنية مطلوبة فقط لعرض Instant للإنسان. لهذا، يتم استخدام atZone(ZoneId). ZoneId.systemDefault() يعيد المنطقة الزمنية للجهاز المضبوطة في نظام التشغيل. ZoneOffset.UTC هو الثابت لـ UTC.
في الأنظمة الموزعة، يُوصى بتخزين ونقل جميع الطوابع الزمنية في Instant (أو OffsetDateTime مع ZoneOffset.UTC). يتم التحويل إلى الوقت المحلي فقط على العميل قبل العرض للمستخدم. هذا يمنع الالتباس مع المناطق الزمنية.
في تطبيقات Android الموزعة، مزامنة الوقت ضرورية للتخزين المؤقت الصحيح والإشعارات والتحرير التعاوني. Instant هو الخيار الطبيعي لهذه المهمة بفضل ارتباطه بـ UTC.
عند مقارنة الطوابع الزمنية من أجهزة مختلفة، يجب مراعاة أن ساعات النظام قد تتباعد. يُوصى باستخدام وقت الخادم كمرجع. يعيد الخادم Instant في UTC، ويقارنه العميل مع Instant المحلي فقط للحسابات النسبية.
لحساب الفرق بين لحظتين، استخدم Duration.between(Instant start, Instant end). تعيد هذه الطريقة Duration يمكن تحويلها إلى ساعات ودقائق وثوان. تسمح طريقا isAfter() و isBefore() بمقارنة اللحظات.
fun isCacheExpired(
cachedAt: Instant,
ttlMinutes: Long
): Boolean {
val elapsed = Duration.between(cachedAt, Instant.now())
return elapsed.toMinutes() >= ttlMinutes
}
المثال الأول هو تسجيل الأحداث بطابع زمني. Instant يُحفظ في قاعدة بيانات Room ويُرسل إلى الخادم. يتم تسجيل الطابع الزمني في UTC لتفسير لا لبس فيه.
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 دقائق”، “منذ ساعتين” — تنسيق شائع في المراسلات والشبكات الاجتماعية.
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 لتتبع وقت آخر تحديث.
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 الخادم كمصدر للحقيقة.
الأسئلة الشائعة
System.currentTimeMillis() يعيد long — عدد الميلي ثانية من الحقبة دون ربط بالمنطقة الزمنية. Instant يوفر نفس الوظيفة ولكن بدقة نانوثانية وAPI غني للتحويلات والمقارنات والتوافق مع java.time.
Room لا يدعم Instant مباشرة. استخدم TypeConverter الذي يحول Instant إلى Long (toEpochMilli) والعكس (Instant.ofEpochMilli). لدقة النانوثواني، احفظ حقلين: الحقبة-ثواني والنانوثواني.
نعم، Instant غير قابل للتغيير وينفذ equals() و hashCode() بشكل صحيح. اثنان Instant بنفس القيمة سيكونان متساويين. هذا يجعله مفتاحًا موثوقًا لـ HashMap والمجموعات الأخرى، على عكس java.util.Date القابل للتغيير.
استخدم Duration.between(start, end) للحصول على Duration أو ChronoUnit.SECONDS.between(start, end) للفرق بالثواني (long). Duration يوفر طرق toMinutes() و toHours() و toDays() و toNanos().
Instant مصمم كنقطة مطلقة على الخط الزمني. بدون تحديد منطقة زمنية أو UTC، التحليل مستحيل لأن Instant لا يحتوي على معلومات تقويمية. اللاحقة “Z” تدل على إزاحة صفرية (UTC) وهي إلزامية لتنسيق ISO-8601.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.