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 интерфејса.
Кључна карактеристика — све главне класе су value-based. То значи да се њихове инстанце пореде по вредности, а не по референци, и не могу се насљеђивати. За поређење два објеката користи се метода equals, а не оператор ==.
Пакет је подељен на више категорија. Типови без временске зоне — LocalDate, LocalTime, LocalDateTime — користе се за локалне датуме и време. Типови са временском зоном — ZonedDateTime, OffsetDateTime, OffsetTime — додају информације о померу или зони. Тренутни типови — Instant — представљају тачку на временској оси у UTC.
Оваква подела решава проблем карактеристичан за стари API: програмер никада није знао да ли објекат Date садржи информације о временској зони или не. У java.time сваки тип јасно декларише своју семантику.
Класа LocalDate пружа бројне методе за креирање, читање и измену датума. Текући датум се може добити кроз статичку методу now(). Одређећи датум — кроз методу of(int year, int month, int dayOfMonth).
За читање компонената датума користе се гетери: 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 формату.
Гетери укључују 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 пружа приступ свим пољима датума и времена кроз одговарајуће гетере: 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"))
За форматирање и парсирање користи се класа DateTimeFormatter. Она пружа преддефинисане формате кроз константе (ISO_LOCAL_DATE, ISO_LOCAL_TIME, ISO_LOCAL_DATE_TIME) и могућност да се креирају сопствени формати помоћу образаца.
Обрасци форматирања користе симболе: yyyy — година, MM — месец (двоцифрени), dd — дан, HH — сат (0-23), mm — минут, ss — секунда. Метода format() се позива на објекту датум-време или кроз DateTimeFormatter.
DateTimeFormatter такође подржава локализацију кроз статичке методе 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 — механизам који додаје подршку за нове Java API-је у ранијим верзијама.
Desugaring у Android Gradle Plugin-у се конфигурише кроз compileOptions у build.gradle. Довољно је поставити isCoreLibraryDesugaringEnabled = true и додати библиотеку desugar_jdk_libs. Након тога java.time постаје доступан за све API нивое од 14.
За пројекте који не могу да користе desugaring (на примјер, легаци пројекти на 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 за глобалне податке.
Друга грешка — неправилно парсирање низова. 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. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође