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

Для читання компонентів дати використовуються геттери: 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.

Геттери включають 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 надає доступ до всіх полів дати та часу через відповідні геттери: toLocalDate() та toLocalTime() повертають окремі компоненти. Метод truncatedTo(TemporalUnit unit) дозволяє округлити час до заданої точності — наприклад, до хвилин.

Для перетворення в часовий пояс використовується метод atZone(ZoneId zone), який повертає ZonedDateTime. Це єдиний спосіб додати часовий пояс до LocalDateTime.

Як створювати об’єкти дати та часу?

Всі три класи використовують єдиний шаблон створення через статичні фабричні методи. Конструктори класів оголошені як private — створити об’єкт напряму через new не можна.

Основні способи створення:

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

Метод 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) і можливість створювати свої через pattern-рядки.

Шаблони форматування використовують символи: 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 level і 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 level, починаючи з 14.

Для проєктів, які не можуть використовувати desugaring (наприклад, legacy проєкти на 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також