LocalDate, LocalTime и LocalDateTime: какво е това, работа с дата

Автор: IT Sectr Публикувано: 2026-07-13 Време за четене: 12 мин

LocalDate, LocalTime и LocalDateTime — основните класове на пакета java.time, които осигуряват работа с дата и час без връзка с часова зона. Според документацията на Oracle (Java 17, 2024), тези типове са проектирани като immutable и thread-safe, което ги прави безопасни за многонишкови приложения. Те станаха достъпни на Android чрез desugaring от API 26, а за по-стари версии чрез библиотеката ThreeTenABP.

Основни поенти

  • LocalDate — immutable клас за представяне на дата (година, месец, ден) без час и часова зона.
  • LocalTime — immutable клас за представяне на час (час, минута, секунда, наносекунда) без дата и часова зона.
  • LocalDateTime — комбинация на LocalDate и LocalTime, съхраняваща като дата, така и час без връзка със зона.
  • И трите класа поддържат аритметични операции — добавяне и изваждане на дни, месеци, часове чрез методите plus и minus.
  • На Android тези типове са достъпни чрез desugaring (API 26+) или библиотеката ThreeTenABP (API < 26).

Какво са LocalDate, LocalTime и LocalDateTime?

LocalDate — клас, който представя дата в формат година-месец-ден без информация за час и часова зона. Използва се за съхраняване на данни като дата на раждане, дата на събитие или дата на изтичане.

LocalDate съхранява годината в диапазона от -999999999 до +999999999, месеца от 1 до 12 и деня от месеца, като се вземат предвид високосните години. Класът е напълно immutable — всяка операция върща нов обект.

LocalTime представя времето на деня: часове, минути, секунди и наносекунди. Максимална точност — до наносекунда. LocalTime не съдържа информация за дата и часова зона, което го прави удобен за съхраняване на часа на отваряне на магазин или продължителността на процес.

LocalDateTime обединява LocalDate и LocalTime в един обект. Това е най-често използваният тип, когато трябва да съхраняваме и дата, и час, но връзката с часова зона не е необходима. Например, дата и час на концерт в локален формат.

Според Oracle Java Documentation (2024), и трите класа са проектирани на базата на идеи от библиотеката Joda-Time, но с подобрена архитектура и пълна интеграция в стандартната библиотека.

Как е организиран пакетът java.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: работа с дата

Класът LocalDate предоставя много методи за създаване, четене и промяна на дата. Текущата дата може да се получи чрез статичния метод now(). Конкретна дата — чрез метода of(int year, int month, int dayOfMonth).

За четене на компонентите на датата се използват getterи: getYear(), getMonthValue(), getDayOfMonth(), getDayOfWeek(), getDayOfYear(). Методът getMonth() върща enum Month, а getDayOfWeek() — enum DayOfWeek.

LocalDate поддържа проверка на дати. Методите isBefore(), isAfter() и isEqual() позволяват сравнение на дати. Методът isLeapYear() проверява дали годината е високосна. Методът lengthOfMonth() върща броя на дните в месеца, а lengthOfYear() — в годината.

За промяна се използват методите withYear(), withMonth(), withDayOfMonth(), които върщат нов обект с променен компонент. Методите plusDays(), minusMonths() и подобни извършват аритметика на датата.

LocalTime: работа с час

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.

Getterите включват getHour(), getMinute(), getSecond(), getNano(). Методът toSecondOfDay() върща броя на секундите от началото на деня, а toNanoOfDay() — наносекунди. Това е удобно за изчисления на продължителност в рамките на един ден.

LocalTime поддържа същите операции за сравнение и промяна като LocalDate: plusHours(), minusMinutes(), withHour(), withMinute(). Методите isBefore() и isAfter() работят, като вземат предвид цикличността на времето в рамките на деня.

LocalDateTime: комбинация на дата и час

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 осигурява достъп до всички полета на дата и час чрез съответните getterи: toLocalDate() и toLocalTime() върщат отделни компоненти. Методът truncatedTo(TemporalUnit unit) позволява закръгляне на времето до зададена точност — например, до минути.

За преобразуване в часова зона се използва методът atZone(ZoneId zone), който върща ZonedDateTime. Това е единственият начин да се добави часова зона към LocalDateTime.

Как да създаваме обекти на дата и час?

И трите класа използват единен модел за създаване чрез статични фабрични методи. Конструкторите на класовете са обявени като private — не може да се създаде обект директно чрез new.

Основни начини за създаване:

  • now() — текуща дата/час от системния часовник
  • of(...) — от компоненти (година, месец, ден и др.)
  • parse(String) — от низ в формат ISO-8601
  • from(TemporalAccessor) — от друг темпорален обект

Методът of има много презатоваряния. За LocalDate са необходими година, месец и ден. За LocalTime — часове и минути (опционално секунди и наносекунди). За LocalDateTime — година, месец, ден, часове, минути. Месецът може да се предаде като int (1-12) или като enum Month.

kotlin
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).

kotlin
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.

kotlin
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 сравнява стойности, а не референции. Това означава, че два обекта с еднакви полета ще бъдат равни, дори да са различни инстанции.

kotlin
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) приемат тези обекти.

kotlin
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)

Примери на код в Kotlin

Нека разгледаме практически пример: приложение за отчитане на работни смени. Трябва да изчислим продължителността на смената и да определим дали пада през нощно време. Използваме LocalTime за начален и краен час, LocalDate за дата и LocalDateTime за изчисления на смени, които преминават полунощ.

kotlin
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 за дата на раждане и го сравняваме с текущата дата, като вземаме предвид деня и месеца на раждане.

kotlin
fun calculateAge(birthDate: LocalDate): Int {
    val today = LocalDate.now()
    val period = Period.between(birthDate, today)
    return period.years
}

Третият пример — работа с уведомления. LocalDateTime се използва за планиране на напомняния. Проверяваме дали планираното време е настъпило.

kotlin
data class Reminder(
    val id: Long,
    val scheduledAt: LocalDateTime
) {
    fun isDue(): Boolean =
        LocalDateTime.now().isAfter(scheduledAt)
}

Поддръжка в Android: ниво на API и desugaring

Вградената поддръжка за 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 от 14 нататък.

За проекти, които не могат да използват desugaring (например, стари проекти на AGP под 4.0), съществува библиотеката ThreeTenABP — backport на java.time. Тя предоставя същите класове (LocalDate, LocalTime, LocalDateTime), но в пакета org.threeten.bp.

groovy
@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 не съдържа никаква информация за часова зона. Ако трябва да предадете абсолютен момент от времето — използвайте зонални типове. Ако локалното време е достатъчно — локални типове.

Често задавани въпроси

Каква е разликата между LocalDate и Date в Java?

Date съхранява броя милисекунди от 1970-01-01 UTC, докато LocalDate съхранява година, месец и ден без връзка с часова зона. Date е променлив и не е thread-safe, LocalDate — immutable и thread-safe. Date е остарял от Java 8.

Може ли да се използва LocalDateTime в база данни?

Да, 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) преди запазване. Това предотвратява проблеми с закръгляне при сериализация.

Защо LocalDate.now() върща различна дата на различни устройства?

Методът now() използва системния часовник на устройството и часовата зона по подразбиране. Ако устройствата са в различни часови зони, датата може да се различава. За единен времеви печат използвайте Instant.now(), който винаги върща време в UTC.

Обобщение

  • LocalDate — immutable клас за дата без време и часова зона. Използва се за съхраняване на рождени дати, крайни срокове, дати на събития.
  • LocalTime — immutable клас за време на деня с точност до наносекунда. Подходящ за съхраняване на работно време, продължителност на процеси.
  • LocalDateTime — комбинация на дата и час без връзка със зона. Най-гъвкият локален тип, но не е подходящ за разпределени системи.
  • И трите класа поддържат аритметика, сравнение, форматиране и разбиране чрез единен API, базиращ се на DateTimeFormatter.
  • На Android java.time е достъпен чрез вградена поддръжка от API 26 или чрез desugaring за по-стари версии.
  • За глобални времеви печати и данни с часови зони използвайте ZonedDateTime или Instant вместо локални типове.
  • При разбиране на низове винаги предавайте DateTimeFormatter за нестандартни формати и обработвайте DateTimeParseException.

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също