LocalDate, LocalTime και LocalDateTime — οι κύριες κλάσεις του πακέτου java.time, που παρέχουν εργασία με ημερομηνία και ώρα χωρίς σύνδεση με τη ζώνη ώρας. Σύμφωνα με την τεκμηρίωση της Oracle (Java 17, 2024), αυτοί οι τύποι είναι σχεδιασμένοι ως immutable και thread-safe, γεγονός που τους καθιστά ασφαλείς για πολυνηματικές εφαρμογές. Έγιναν διαθέσιμοι στο Android μέσω desugaring ξεκινώντας από το API 26, και για παλαιότερες εκδόσεις — μέσω της βιβλιοθήκης ThreeTenABP.
Τα βασικά σημεία
LocalDate — η κλάση που αναπαριστά ημερομηνία σε μορφή έτος-μήνας-ημέρα χωρίς πληροφορία για ώρα και ζώνη ώρας. Χρησιμοποιείται για την αποθήκευση δεδομένων όπως γενέθλια, ημερομηνία εκδήλωσης ή προθεσμία λήξης.
Η LocalDate αποθηκεύει το έτος σε εύρος από -999999999 έως +999999999, τον μήνα από 1 έως 12 και την ημέρα του μήνα λαμβάνοντας υπόψη τα δίσεκτα έτη. Η κλάση είναι πλήρως immutable — κάθε πράξη επιστρέφει ένα νέο αντικείμενο.
LocalTime αναπαριστά την ώρα της ημέρας: ώρες, λεπτά, δευτερόλεπτα και νανοδευτερόλεπτα. Η μέγιστη ακρίβεια είναι έως το νανοδευτερόλεπτο. Η LocalTime δεν περιέχει πληροφορία για ημερομηνία και ζώνη ώρας, γεγονός που την καθιστά βολική για την αποθήκευση της ώρας ανοίγματος ενός καταστήματος ή της διάρκειας μιας διαδικασίας.
LocalDateTime συνδυάζει τις LocalDate και LocalTime σε ένα αντικείμενο. Είναι ο πιο συχνά χρησιμοποιούμενος τύπος όταν χρειάζεται να αποθηκευτούν και ημερομηνία και ώρα, αλλά δεν απαιτείται σύνδεση με ζώνη ώρας. Για παράδειγμα, η ημερομηνία και η ώρα μιας συναυλίας σε τοπική μορφή.
Σύμφωνα με Oracle Java Documentation (2024), και οι τρεις κλάσεις είναι σχεδιασμένες με βάση ιδέες από τη βιβλιοθήκη Joda-Time, αλλά με βελτιωμένη αρχιτεκτονική και πλήρη ενσωμάτωση στην τυπική βιβλιοθήκη.
Το πακέτο java.time εμφανίστηκε στην Java 8 ως αντικατάσταση των παλαιωμένων κλάσεων Date, Calendar και SimpleDateFormat. Η αρχιτεκτονική του βασίζεται στις αρχές των immutable αντικειμένων και του fluent-interface.
Το βασικό χαρακτηριστικό — όλες οι κύριες κλάσεις είναι value-based. Αυτό σημαίνει ότι τα στιγμιότυπά τους συγκρίνονται με βάση την τιμή και όχι την αναφορά, και δεν μπορούν να κληρονομηθούν. Για τη σύγκριση δύο αντικειμένων χρησιμοποιείται η μέθοδος equals, όχι ο τελεστής ==.
Το πακέτο χωρίζεται σε διάφορες κατηγορίες. Τύποι χωρίς ζώνη ώρας — LocalDate, LocalTime, LocalDateTime — χρησιμοποιούνται για τοπικές ημερομηνίες και ώρες. Τύποι με ζώνη ώρας — ZonedDateTime, OffsetDateTime, OffsetTime — προσθέτουν πληροφορία σχετικά με μετατόπιση ή ζώνη. Στιγμιαίοι τύποι — Instant — αναπαριστούν ένα σημείο στο χρονοδιάγραμμα σε UTC.
Αυτός ο διαχωρισμός λύνει το πρόβλημα που χαρακτήριζε το παλιό API: ο προγραμματιστής ποτέ δεν ήξερε αν το αντικείμενο Date περιέχει πληροφορία για ζώνη ώρας ή όχι. Στο java.time κάθε τύπος δηλώνει ρητά τη σημασιολογία του.
Η κλάση LocalDate παρέχει πολλές μεθόδους για δημιουργία, ανάγνωση και τροποποίηση ημερομηνίας. Την τρέχουσα ημερομηνία μπορείτε να την λάβετε μέσω της στατικής μεθόδου now(). Μια συγκεκριμένη ημερομηνία — μέσω της μεθόδου of(int year, int month, int dayOfMonth).
Για την ανάγνωση των στοιχείων της ημερομηνίας χρησιμοποιούνται τα getters: getYear(), getMonthValue(), getDayOfMonth(), getDayOfWeek(), getDayOfYear(). Η μέθοδος getMonth() επιστρέφει το enum Month, και η getDayOfWeek() — το enum DayOfWeek.
Η LocalDate υποστηρίζει έλεγχο ημερομηνιών. Οι μέθοδοι isBefore(), isAfter() και isEqual() επιτρέπουν τη σύγκριση ημερομηνιών. Η μέθοδος isLeapYear() ελέγχει αν το έτος είναι δίσεκτο. Η μέθοδος lengthOfMonth() επιστρέφει τον αριθμό των ημερών του μήνα, και η lengthOfYear() — του έτους.
Για τροποποίηση χρησιμοποιούνται οι μέθοδοι withYear(), withMonth(), withDayOfMonth(), οι οποίες επιστρέφουν ένα νέο αντικείμενο με το τροποποιημένο στοιχείο. Οι μέθοδοι plusDays(), minusMonths() και παρόμοιες εκτελούν αριθμητική ημερομηνίας.
LocalTime αναπαριστά την ώρα της ημέρας με ακρίβεια έως το νανοδευτερόλεπτο. Η τυπική μορφή είναι ISO-8601 (HH:mm:ss.nnnnnnnnn). Η ελάχιστη τιμή είναι 00:00, η μέγιστη — 23:59:59.999999999.
Μπορείτε να δημιουργήσετε ένα αντικείμενο LocalTime μέσω της now() για την τρέχουσα ώρα, της of(int hour, int minute), της of(int hour, int minute, int second) ή της of(int hour, int minute, int second, int nanoOfSecond). Η μέθοδος parse(CharSequence text) αναλύει μια συμβολοσειρά σε μορφή ISO-8601.
Τα getters περιλαμβάνουν getHour(), getMinute(), getSecond(), getNano(). Η μέθοδος toSecondOfDay() επιστρέφει τον αριθμό των δευτερολέπτων από την αρχή της ημέρας, και η toNanoOfDay() — τα νανοδευτερόλεπτα. Αυτό είναι βολικό για υπολογισμούς διάρκειας εντός μιας ημέρας.
Η LocalTime υποστηρίζει τις ίδιες πράξεις σύγκρισης και τροποποίησης όπως και η LocalDate: plusHours(), minusMinutes(), withHour(), withMinute(). Οι μέθοδοι isBefore() και isAfter() λειτουργούν λαμβάνοντας υπόψη ότι η ώρα είναι κυκλική εντός των ορίων της ημέρας.
LocalDateTime συνδυάζει τις δυνατότητες των LocalDate και LocalTime σε μία κλάση. Αποθηκεύει και ημερομηνία και ώρα, αλλά χωρίς ζώνη ώρας. Είναι ο πιο ευέλικτος τοπικός τύπος, αλλά απαιτεί προσοχή κατά τη χρήση σε κατανεμημένα συστήματα.
Μπορείτε να δημιουργήσετε μια LocalDateTime μέσω των στατικών μεθόδων now(), of(LocalDate date, LocalTime time), of(int year, Month month, int dayOfMonth, int hour, int minute) και των υπερφορτώσεών τους. Επίσης, μπορείτε να συνδυάσετε LocalDate και LocalTime μέσω της μεθόδου atTime().
Η LocalDateTime παρέχει πρόσβαση σε όλα τα πεδία ημερομηνίας και ώρας μέσω των αντίστοιχων getters: οι toLocalDate() και toLocalTime() επιστρέφουν τα ξεχωριστά στοιχεία. Η μέθοδος truncatedTo(TemporalUnit unit) επιτρέπει τη στρογγυλοποίηση της ώρας σε καθορισμένη ακρίβεια — για παράδειγμα, έως τα λεπτά.
Για μετατροπή σε ζώνη ώρας χρησιμοποιείται η μέθοδος atZone(ZoneId zone), η οποία επιστρέφει ZonedDateTime. Αυτός είναι ο μόνος τρόπος για να προσθέσετε ζώνη ώρας σε μια LocalDateTime.
Και οι τρεις κλάσεις χρησιμοποιούν ένα ενιαίο πρότυπο δημιουργίας μέσω στατικών εργοστασιακών μεθόδων. Οι κατασκευαστές των κλάσεων δηλώνονται ως private — δεν μπορείτε να δημιουργήσετε αντικείμενο απευθείας μέσω new.
Οι βασικοί τρόποι δημιουργίας:
Η μέθοδος of έχει πολλές υπερφορτώσεις. Για LocalDate χρειάζονται έτος, μήνας και ημέρα. Για LocalTime — ώρες και λεπτά (προαιρετικά δευτερόλεπτα και νανοδευτερόλεπτα). Για LocalDateTime — έτος, μήνας, ημέρα, ώρες, λεπτά. Ο μήνας μπορεί να μεταβιβαστεί ως int (1-12) ή ως enum Month.
val today = LocalDate.now()
val specificDate = LocalDate.of(2026, Month.JULY, 21)
val parsedDate = LocalDate.parse("2026-07-21")
val currentTime = LocalTime.now()
val lunchTime = LocalTime.of(13, 30, 0)
val parsedTime = LocalTime.parse("13:30:00")
val now = LocalDateTime.now()
val meeting = LocalDateTime.of(2026, 7, 21, 15, 0)
Οι κλάσεις java.time έχουν σχεδιαστεί για εύκολη μετατροπή μεταξύ τους. Η LocalDate μπορεί να μετατραπεί σε LocalDateTime μέσω της μεθόδου atTime(LocalTime) ή atStartOfDay(). Η LocalTime — μέσω της atDate(LocalDate).
LocalDateTime μπορεί να μετατραπεί πίσω σε LocalDate μέσω της toLocalDate() και σε LocalTime μέσω της toLocalTime(). Για μετατροπή σε ZonedDateTime χρησιμοποιείται η μέθοδος atZone(ZoneId).
Η μετατροπή σε java.util.Date (για συμβατότητα με παλιό κώδικα) απαιτεί ενδιάμεσο βήμα μέσω Instant και ζώνης ώρας. Σύμφωνα με Baeldung (2024), αυτή η πράξη εκτελείται μέσω του Date.from(instant).
val date = LocalDate.of(2026, 7, 21)
val dateTime = date.atTime(LocalTime.of(10, 30))
val time = LocalTime.of(14, 0)
val dateTimeFromTime = time.atDate(date)
val extractedDate = dateTime.toLocalDate()
val extractedTime = dateTime.toLocalTime()
val zoned = dateTime.atZone(ZoneId.of("Europe/Moscow"))
Για μορφοποίηση και parsing χρησιμοποιείται η κλάση DateTimeFormatter. Παρέχει προκαθορισμένες μορφές μέσω σταθερών (ISO_LOCAL_DATE, ISO_LOCAL_TIME, ISO_LOCAL_DATE_TIME) και τη δυνατότητα δημιουργίας δικών σας μέσω pattern-συμβολοσειρών.
Τα πρότυπα μορφοποίησης χρησιμοποιούν σύμβολα: yyyy — έτος, MM — μήνας (διψήφιος), dd — ημέρα, HH — ώρα (0-23), mm — λεπτό, ss — δευτερόλεπτο. Η μέθοδος format() καλείται στο αντικείμενο ημερομηνίας-ώρας ή μέσω DateTimeFormatter.
Η DateTimeFormatter υποστηρίζει επίσης το localization μέσω των στατικών μεθόδων ofLocalizedDate(FormatStyle), ofLocalizedTime(FormatStyle) και ofLocalizedDateTime(FormatStyle). Διαθέσιμα στυλ: SHORT, MEDIUM, LONG και FULL.
val formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
val formatted = LocalDateTime.now().format(formatter)
val parsed = LocalDate.parse(
"21.07.2026",
DateTimeFormatter.ofPattern("dd.MM.yyyy")
)
Και οι τρεις κλάσεις υλοποιούν τη διεπαφή Comparable, γεγονός που επιτρέπει τη φυσική σύγκρισή τους. Η μέθοδος compareTo() επιστρέφει αρνητικό αριθμό, μηδέν ή θετικό ανάλογα με τη σειρά. Οι μέθοδοι isBefore(), isAfter() και isEqual() επιστρέφουν boolean.
Για LocalDate η σύγκριση γίνεται χρονολογικά — η παλαιότερη ημερομηνία θεωρείται μικρότερη. Για LocalTime — κατά την ώρα της ημέρας. Για LocalDateTime — πρώτα κατά ημερομηνία, έπειτα κατά ώρα. Όλες οι συγκρίσεις λαμβάνουν σωστά υπόψη τα δίσεκτα έτη και τον αριθμό των ημερών στους μήνες.
Σημαντική διαφορά από το παλιό API: η equals() για LocalDate, LocalTime και LocalDateTime συγκρίνει τιμές, όχι αναφορές. Αυτό σημαίνει ότι δύο αντικείμενα με τα ίδια πεδία θα είναι ίσα, ακόμα κι αν είναι διαφορετικά στιγμιότυπα.
val d1 = LocalDate.of(2026, 7, 21)
val d2 = LocalDate.of(2026, 12, 25)
if (d1.isBefore(d2)) {
Log.d("Ημερομηνία", "το d1 είναι πριν από το d2")
}
val sortedDates = listOf(d2, d1).sorted()
Και οι τρεις κλάσεις υποστηρίζουν αριθμητικές πράξεις μέσω των μεθόδων plus και minus. Για LocalDate είναι διαθέσιμες plusDays(), plusWeeks(), plusMonths(), plusYears() και οι αντίστοιχες minus-μέθοδοι. Η LocalTime υποστηρίζει plusHours(), plusMinutes(), plusSeconds(), plusNanos().
Η LocalDateTime κληρονομεί όλες τις αριθμητικές πράξεις και των δύο τύπων. Χαρακτηριστικό της LocalDate: κατά την πρόσθεση μήνα τα αποτελέσματα επεξεργάζονται σωστά το διαφορετικό μήκος των μηνών. Για παράδειγμα, 31 Ιανουαρίου + 1 μήνας = 28 (29 σε δίσεκτο έτος) Φεβρουαρίου.
Για πιο σύνθετες πράξεις υπάρχει η κλάση Period (για ημερομηνίες) και Duration (για ώρα). Οι μέθοδοι plus(TemporalAmount) και minus(TemporalAmount) δέχονται αυτά τα αντικείμενα.
val today = LocalDate.now()
val nextWeek = today.plusDays(7)
val nextMonth = today.plusMonths(1)
val lastYear = today.minusYears(1)
val now = LocalTime.now()
val inTwoHours = now.plusHours(2)
val halfHourAgo = now.minusMinutes(30)
Ας δούμε ένα πρακτικό παράδειγμα: μια εφαρμογή καταγραφής βάρδιων εργασίας. Πρέπει να υπολογίσουμε τη διάρκεια της βάρδιας και να προσδιορίσουμε αν πέφτει σε νυχτερινή ώρα. Χρησιμοποιούμε LocalTime για την ώρα έναρξης και λήξης, LocalDate για την ημερομηνία και LocalDateTime για υπολογισμούς βαρδιών που διασχίζουν τα μεσάνυχτα.
data class Shift(
val startTime: LocalTime,
val endTime: LocalTime,
val date: LocalDate
) {
fun isOvernight(): Boolean = endTime.isBefore(startTime)
fun durationInMinutes(): Long {
val start = LocalDateTime.of(date, startTime)
val end = LocalDateTime.of(
if (isOvernight()) date.plusDays(1) else date,
endTime
)
return Duration.between(start, end).toMinutes()
}
}
Το δεύτερο παράδειγμα — ο υπολογισμός της ηλικίας ενός χρήστη. Χρησιμοποιούμε LocalDate για την ημερομηνία γέννησης και τη συγκρίνουμε με την τρέχουσα ημερομηνία, λαμβάνοντας υπόψη την ημέρα και τον μήνα γέννησης.
fun calculateAge(birthDate: LocalDate): Int {
val today = LocalDate.now()
val period = Period.between(birthDate, today)
return period.years
}
Το τρίτο παράδειγμα — εργασία με ειδοποιήσεις. Η LocalDateTime χρησιμοποιείται για τον προγραμματισμό υπενθυμίσεων. Ελέγχουμε αν έχει φτάσει η προγραμματισμένη ώρα.
data class Reminder(
val id: Long,
val scheduledAt: LocalDateTime
) {
fun isDue(): Boolean =
LocalDateTime.now().isAfter(scheduledAt)
}
Η ενσωματωμένη υποστήριξη του java.time εμφανίστηκε στο Android ξεκινώντας από το API 26 (Android 8.0 Oreo). Για συσκευές με παλαιότερες εκδόσεις Android είναι απαραίτητο να χρησιμοποιήσετε desugaring — μηχανισμό που προσθέτει υποστήριξη για νέα API της Java σε παλαιότερες εκδόσεις.
Το desugaring στο Android Gradle Plugin ρυθμίζεται μέσω των compileOptions στο build.gradle. Αρκεί να ορίσετε isCoreLibraryDesugaringEnabled = true και να προσθέσετε τη βιβλιοθήκη desugar_jdk_libs. Μετά από αυτό, το java.time γίνεται διαθέσιμο για όλα τα API level, ξεκινώντας από το 14.
Για έργα που δεν μπορούν να χρησιμοποιήσουν desugaring (για παράδειγμα, legacy έργα σε AGP χαμηλότερο από 4.0), υπάρχει η βιβλιοθήκη ThreeTenABP — το backport του java.time. Παρέχει τις ίδιες κλάσεις (LocalDate, LocalTime, LocalDateTime), αλλά στο πακέτο org.threeten.bp.
@Suppress("UnstableApiUsage")
android {
compileOptions {
isCoreLibraryDesugaringEnabled = true
}
}
dependencies {
"coreLibraryDesugaring"("com.android.tools:desugar_jdk_libs:2.1.4")
}
Το πρώτο συνηθισμένο σφάλμα — η χρήση της LocalDateTime σε κατανεμημένα συστήματα χωρίς να λαμβάνεται υπόψη η ζώνη ώρας. Αν ο διακομιστής βρίσκεται στο Europe/Moscow και ο πελάτης στο Asia/Tokyo, η LocalDateTime θα ερμηνευτεί διαφορετικά. Λύση: χρησιμοποιήστε Instant ή ZonedDateTime για παγκόσμια δεδομένα.
Το δεύτερο σφάλμα — λανθασμένο parsing συμβολοσειρών. Από προεπιλογή, η LocalDate.parse() αναμένει μορφή ISO-8601 (yyyy-MM-dd). Αν η συμβολοσειρά είναι σε άλλη μορφή, πρέπει να μεταβιβάσετε ρητά DateTimeFormatter. Επίσης, αξίζει να διαχειρίζεστε την DateTimeParseException, ώστε η εφαρμογή να μην καταρρεύσει σε μη έγκυρη είσοδο.
Το τρίτο σφάλμα — αγνόηση της null-ασφάλειας. Οι LocalDate, LocalTime και LocalDateTime είναι αντικείμενα που μπορούν να είναι null. Στο Kotlin συνιστάται η χρήση nullable-τύπων με ρητό έλεγχο ή με τον τελεστή Elvis. Στη Java — ο έλεγχος για null πριν από την κλήση μεθόδων.
Το τέταρτο σφάλμα — σύγχυση μεταξύ LocalDateTime και ZonedDateTime. Η LocalDateTime δεν περιέχει καμία πληροφορία για ζώνη ώρας. Αν χρειάζεται να μεταβιβαστεί μια απόλυτη στιγμή χρόνου — χρησιμοποιήστε ζωνικούς τύπους. Αν αρκεί ο τοπικός χρόνος — τοπικούς.
Συχνές ερωτήσεις
Η Date αποθηκεύει τον αριθμό των χιλιοστών του δευτερολέπτου από την 1970-01-01 UTC, ενώ η LocalDate αποθηκεύει έτος, μήνα και ημέρα χωρίς σύνδεση με ζώνη ώρας. Η Date είναι μεταβλητή και όχι thread-safe, η LocalDate είναι immutable και thread-safe. Η Date έχει παλαιωθεί ξεκινώντας από την Java 8.
Ναι, η LocalDateTime αντιστοιχίζεται καλά στον τύπο SQL TIMESTAMP WITHOUT TIME ZONE. Η JPA και το Room την υποστηρίζουν μέσω TypeConverter. Για TIMESTAMP WITH TIME ZONE χρησιμοποιήστε ZonedDateTime ή OffsetDateTime.
Χρησιμοποιήστε ChronoUnit.DAYS.between(startDate, endDate). Αυτή η μέθοδος επιστρέφει long — τη διαφορά σε ημέρες. Για πιο λεπτομερή υπολογισμό χρησιμοποιήστε την Period.between(), η οποία επιστρέφει Period με έτη, μήνες και ημέρες.
Η LocalTime υποστηρίζει ακρίβεια έως τα νανοδευτερόλεπτα (9 δεκαδικά ψηφία). Αν χρειάζεται ακρίβεια έως τα χιλιοστά, χρησιμοποιήστε truncateTo(ChronoUnit.MILLIS) πριν από την αποθήκευση. Αυτό αποτρέπει προβλήματα στρογγυλοποίησης κατά τη σειριοποίηση.
Η μέθοδος now() χρησιμοποιεί το ρολόι συστήματος της συσκευής και την προεπιλεγμένη ζώνη ώρας. Αν οι συσκευές βρίσκονται σε διαφορετικές ζώνες ώρας, η ημερομηνία μπορεί να διαφέρει. Για μια ενιαία χρονική σήμανση χρησιμοποιήστε Instant.now(), η οποία επιστρέφει πάντα την ώρα σε UTC.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης