واجهة التاريخ والوقت

LocalDate وLocalTime وLocalDateTime

15 دقيقة الدرس 2 من 13

LocalDate وLocalTime وLocalDateTime

الفئات الثلاث الأكثر استخدامًا في java.time هي LocalDate وLocalTime وLocalDateTime. كلمة "Local" (محلي) تعني أنها لا تحمل أي معلومات عن المنطقة الزمنية أو الإزاحة — فهي تمثّل تاريخًا أو وقتًا كما يفهمه الإنسان من تقويم أو ساعة حائط، لا كلحظة دقيقة على الخط الزمني العالمي. إتقان متى تستخدم كل فئة، وكيف تنشئ نُسخها وتتعامل معها، هو أساس كل ما سيأتي من دروس في هذه الحزمة.

ما تمثّله كل فئة

  • LocalDate — تاريخ تقويمي: سنة، شهر، يوم. مثال: 2024-03-15. بلا وقت، بلا منطقة زمنية. استخدمها لأعياد الميلاد وتواريخ الفواتير والإجازات الرسمية — أي شيء لا يهم فيه وقت اليوم.
  • LocalTime — وقت من اليوم: ساعة، دقيقة، ثانية، نانوثانية. مثال: 14:30:00. بلا تاريخ، بلا منطقة. استخدمها لمواعيد العمل وإعدادات المنبّه والجداول اليومية المتكرّرة.
  • LocalDateTime — تاريخ ووقت معًا. مثال: 2024-03-15T14:30:00. ما زالت بلا منطقة زمنية. استخدمها حين تحتاج البُعدين معًا لكن المعنى واحد في كل المناطق (كسطر سجل log يُكتب على خادم موحّد في موقع معروف).
غياب المنطقة الزمنية لا يعني UTC. LocalDateTime غامض عمدًا بشأن الخط الزمني العالمي — لا يمكن تحويله إلى Instant (لحظة دقيقة) دون إضافة منطقة زمنية. هذا تصميم مقصود، وليس قصورًا: فهو يمنع التحويلات الصامتة الخاطئة التي كانت تُعاني منها واجهة برمجة java.util.Date القديمة.

إنشاء النُّسخ: now()

المصنع الساكن now() يقرأ ساعة النظام ويُعيد التاريخ أو الوقت الحالي بحسب المنطقة الافتراضية لبيئة JVM. إنه الأسلوب المعتاد للتقاط "الآن" بصيغة بشرية:

import java.time.LocalDate; import java.time.LocalTime; import java.time.LocalDateTime; LocalDate today = LocalDate.now(); // مثلًا: 2024-03-15 LocalTime timeNow = LocalTime.now(); // مثلًا: 14:30:22.548 LocalDateTime dtNow = LocalDateTime.now(); // مثلًا: 2024-03-15T14:30:22.548 System.out.println(today); // 2024-03-15 System.out.println(timeNow); // 14:30:22.548167 System.out.println(dtNow); // 2024-03-15T14:30:22.548167
مرّر Clock لتسهيل الاختبار. الكود الإنتاجي الذي يستدعي LocalDate.now() بلا وسيطات يصعب اختباره لأنه يعتمد على الساعة الحقيقية. بدلًا من ذلك، مرّر java.time.Clock: LocalDate.now(clock). في الاختبارات، استخدم Clock.fixed(...) لتثبيت الوقت عند لحظة معروفة.

إنشاء النُّسخ: of()

of() ينشئ تاريخًا أو وقتًا محددًا من مكوّناته. يتحقق من صحة الوسيطات فورًا — تمرير تركيبة غير صالحة يُطلق DateTimeException في موضع الاستدعاء مباشرةً، لا بالطرح الصامت الذي كانت تُبديه Calendar القديمة:

import java.time.LocalDate; import java.time.LocalTime; import java.time.LocalDateTime; import java.time.Month; // سنة، شهر (رقم 1-12)، يوم LocalDate birthday = LocalDate.of(1990, 6, 15); // باستخدام enum Month — أكثر وضوحًا ويتجنّب خطأ الإزاحة بواحد LocalDate release = LocalDate.of(2024, Month.MARCH, 15); // ساعة، دقيقة LocalTime startTime = LocalTime.of(9, 0); // ساعة، دقيقة، ثانية، نانوثانية (الأصفار الزائدة اختيارية) LocalTime precise = LocalTime.of(14, 30, 45, 500_000_000); // دمج LocalDate مع LocalTime LocalDateTime mtg = LocalDateTime.of(release, startTime); // أو تمرير جميع الحقول مباشرة LocalDateTime launch = LocalDateTime.of(2024, Month.MARCH, 15, 9, 0); System.out.println(birthday); // 1990-06-15 System.out.println(precise); // 14:30:45.500 System.out.println(mtg); // 2024-03-15T09:00

إنشاء النُّسخ: parse()

حين تصل بيانات التاريخ/الوقت نصًا — من واجهة REST أو ملف CSV أو حقل VARCHAR في قاعدة البيانات — استخدم parse(). افتراضيًا يتوقع صيغة ISO-8601 التي تستخدمها الحزمة كتنسيق قياسي:

import java.time.LocalDate; import java.time.LocalTime; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; // الصيغ الافتراضية ISO-8601 LocalDate d = LocalDate.parse("2024-03-15"); LocalTime t = LocalTime.parse("14:30:00"); LocalDateTime dt = LocalDateTime.parse("2024-03-15T14:30:00"); // نمط مخصص — استخدم DateTimeFormatter DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd/MM/yyyy"); LocalDate invoice = LocalDate.parse("15/03/2024", fmt); System.out.println(d); // 2024-03-15 System.out.println(invoice); // 2024-03-15
parse() يُطلق DateTimeParseException عند إدخال خاطئ. هذا RuntimeException. عند تحليل بيانات يُدخلها المستخدم أو بيانات خارجية، احتِط دومًا بـ try-catch (أو تحقّق من الصحة أولًا) وأرسل رسالة خطأ واضحة. سلسلة نصية فارغة أو مشوّهة ستُوقف البرنامج في وقت التشغيل إن تُركت دون حماية.

الوصول إلى الحقول

تعرض الفئات الثلاث موصِّلات مكتوبة النوع. يمكنك أيضًا استخدام get(ChronoField) العام إن احتجت إلى معالجة الحقول ديناميكيًا:

LocalDate d = LocalDate.of(2024, Month.MARCH, 15); int year = d.getYear(); // 2024 Month month = d.getMonth(); // MARCH int monthNum = d.getMonthValue(); // 3 int day = d.getDayOfMonth(); // 15 int dayOfYear = d.getDayOfYear(); // 75 DayOfWeek dow = d.getDayOfWeek(); // FRIDAY LocalTime t = LocalTime.of(14, 30, 45, 500_000_000); int hour = t.getHour(); // 14 int minute = t.getMinute(); // 30 int second = t.getSecond(); // 45 int nano = t.getNano(); // 500000000

اللاتغييرية ونمط with()

كل فئات java.time غير قابلة للتغيير (immutable). الدوال مثل withYear() وwithMonth() والدالة العامة with(TemporalField, long) تُعيد نسخة جديدة — الأصل يبقى دون تعديل. المبدأ نفسه ينطبق على String:

LocalDate original = LocalDate.of(2024, 3, 15); // "تعديل" حقل — الأصل يظل كما هو LocalDate nextYear = original.withYear(2025); LocalDate firstDay = original.withDayOfMonth(1); System.out.println(original); // 2024-03-15 System.out.println(nextYear); // 2025-03-15 System.out.println(firstDay); // 2024-03-01
لماذا تهم اللاتغييرية عمليًا؟ مراجع LocalDate المشتركة التي تُمرَّر لعدة دوال أو تُخزَّن في مجموعات آمنة بطبيعتها في بيئات متعددة الخيوط — دون الحاجة إلى نسخ دفاعية. هذا يُزيل فئة كاملة من أخطاء التزامن التي كانت تُعاني منها Calendar القابلة للتغيير.

دمج LocalDateTime وتفكيكه

يمكنك التنقل بحرية بين LocalDateTime ومكوّناته:

LocalDateTime dt = LocalDateTime.of(2024, 3, 15, 14, 30); // استخراج المكوّنات LocalDate datePart = dt.toLocalDate(); // 2024-03-15 LocalTime timePart = dt.toLocalTime(); // 14:30 // إعادة التركيب مع وقت مختلف LocalDateTime rescheduled = dt.with(LocalTime.of(10, 0)); System.out.println(rescheduled); // 2024-03-15T10:00

الخلاصة

استخدم LocalDate للتواريخ التقويمية، وLocalTime لأوقات اليوم، وLocalDateTime لقيم التاريخ والوقت معًا — وكلها بلا منطقة زمنية أو إزاحة. أنشئ النُّسخ بـ now() (من الساعة)، أو of() (من مكوّنات معروفة)، أو parse() (من نص). الفئات الثلاث غير قابلة للتغيير؛ تعديل حقل يُنتج كائنًا جديدًا. في الدرس التالي نتناول Instant — النظير المدرك للمنطقة الزمنية الذي يمثّل لحظة دقيقة على الخط الزمني العالمي.