Дата и время ·
‹ Previous Next ›
⏱ 5 min read Modified: 2026-10-19

Класс LocalDateTime в Java: создание, форматирование и примеры

Вызов LocalDateTime.parse("2024-03-15 10:30") выглядит безобидно, но падает с DateTimeParseException: Text '2024-03-15 10:30' could not be parsed at index 10. На десятой позиции стандартный формат ISO-8601 ждёт букву T, а не пробел: LocalDateTime.parse("2024-03-15T10:30") работает, а для формата с пробелом нужен свой DateTimeFormatter.

LocalDateTime в Java (java.time.LocalDateTime) — неизменяемый потокобезопасный класс, который хранит дату и время без часового пояса: год, месяц, день, часы, минуты, секунды и наносекунды, например 2024-03-15T14:30:45.123. По сути это LocalDate и LocalTime в одном объекте. Класс появился в Java 8 в пакете java.time; текущие дату и время возвращает LocalDateTime.now(), конкретные — LocalDateTime.of().

Как создать LocalDateTime: of(), atTime(), parse()

Публичного конструктора у LocalDateTime нет — объект создают фабричными методами. LocalDateTime.of(год, месяц, день, часы, минуты[, секунды[, наносекунды]]) задаёт значение по частям, LocalDateTime.of(LocalDate, LocalTime) склеивает готовые дату и время, date.atTime() и time.atDate() делают то же со стороны LocalDate и LocalTime, а parse() разбирает строку ISO-8601.

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;

public class LocalDateTimeExample1 {
    public static void main(String[] args) {
        // Секунды и наносекунды равны нулю
        LocalDateTime date1 = LocalDateTime.of(2014, 9, 19, 14, 5);
        // Наносекунды равны нулю
        LocalDateTime date2 = LocalDateTime.of(2014, 9, 19, 14, 5, 20);
        LocalDateTime date3 = LocalDateTime.of(2014, 9, 19, 14, 5, 20, 9);
        LocalDateTime date4 = LocalDateTime.of(2024, Month.MARCH, 15, 14, 30);

        LocalDate date = LocalDate.of(2024, 3, 15);
        LocalTime time = LocalTime.of(9, 15);

        LocalDateTime date5 = date.atTime(14, 30, 59);
        LocalDateTime date6 = time.atDate(date);
        LocalDateTime date7 = LocalDateTime.of(date, time);
        LocalDateTime date8 = date.atStartOfDay();
        LocalDateTime date9 = LocalDateTime.parse("2024-03-15T14:30:45");

        System.out.println(date1);
        System.out.println(date2);
        System.out.println(date3);
        System.out.println(date4);
        System.out.println(date5);
        System.out.println(date6);
        System.out.println(date7);
        System.out.println(date8);
        System.out.println(date9);
    }
}
2014-09-19T14:05
2014-09-19T14:05:20
2014-09-19T14:05:20.000000009
2024-03-15T14:30
2024-03-15T14:30:59
2024-03-15T09:15
2024-03-15T09:15
2024-03-15T00:00
2024-03-15T14:30:45

Обратите внимание на формат вывода: между датой и временем стоит буква T, а нулевые секунды и наносекунды toString() не печатает — поэтому date1 выглядит как 2014-09-19T14:05. Девять наносекунд в date3 выводятся полностью, девятью цифрами.

Все значения проверяются сразу, при создании объекта. Несуществующая дата или время вызывает исключение, а строка без T или без времени не разбирается:

LocalDateTime.of(2024, 2, 30, 10, 0);    // DateTimeException: Invalid date 'FEBRUARY 30'
LocalDateTime.of(2024, 3, 15, 24, 0);    // DateTimeException: Invalid value for HourOfDay (valid values 0 - 23): 24
LocalDateTime.parse("2024-03-15 10:30"); // DateTimeParseException: Text '2024-03-15 10:30' could not be parsed at index 10
LocalDateTime.parse("2024-03-15");       // DateTimeParseException: Text '2024-03-15' could not be parsed at index 10

Строку только с датой превращают в LocalDateTime в два шага: LocalDate.parse("2024-03-15").atStartOfDay(). Как разбирать другие форматы — в разделе Форматирование LocalDateTime.

LocalDateTime.now(): текущие дата и время

Метод LocalDateTime.now() возвращает текущие дату и время по системным часам в часовом поясе JVM по умолчанию. Вариант LocalDateTime.now(ZoneId) возвращает дату и время в указанной зоне, а now(Clock) берёт их из объекта Clock, что удобно для тестов.

import java.time.LocalDateTime;
import java.time.ZoneOffset;

public class LocalDateTimeNowExample {
    public static void main(String[] args) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime utc = LocalDateTime.now(ZoneOffset.UTC);

        System.out.println("Сейчас: " + now);
        System.out.println("В UTC:  " + utc);
    }
}
Сейчас: 2026-10-19T14:35:07.123456   (зависит от момента запуска и зоны JVM, здесь Europe/Moscow)
В UTC:  2026-10-19T11:35:07.123512

Количество знаков после секунд зависит от версии Java и ОС: в Java 8 обычно миллисекунды, начиная с Java 9 — как правило микросекунды. Сам результат now() не помнит, в какой зоне он получен: это просто «показания часов на стене». Почему на сервере now() может отличаться от вашего компьютера — в FAQ урока Дата и время в Java, пример с фиксированным Clock — в уроке LocalTime (для LocalDateTime.now(clock) всё так же).

Получение даты, времени и отдельных полей

Поля LocalDateTime читают геттерами: getYear(), getMonthValue(), getMonth(), getDayOfMonth(), getDayOfYear(), getDayOfWeek(), getHour(), getMinute(), getSecond(), getNano(). Дату целиком возвращает toLocalDate(), время — toLocalTime().

import java.time.LocalDateTime;

public class LocalDateTimeExample2 {
    public static void main(String[] args) {
        LocalDateTime dateTime = LocalDateTime.of(2024, 3, 15, 14, 30, 45);
        int year = dateTime.getYear();
        int dayYear = dateTime.getDayOfYear();
        int hour = dateTime.getHour();
        int minute = dateTime.getMinute();

        System.out.println("Год: " + year);
        System.out.println("Месяц: " + dateTime.getMonth());
        System.out.println("День в году: " + dayYear);
        System.out.println("День недели: " + dateTime.getDayOfWeek());
        System.out.println("Часы: " + hour);
        System.out.println("Минуты: " + minute);
        System.out.println("Дата: " + dateTime.toLocalDate());
        System.out.println("Время: " + dateTime.toLocalTime());
    }
}
Год: 2024
Месяц: MARCH
День в году: 75
День недели: FRIDAY
Часы: 14
Минуты: 30
Дата: 2024-03-15
Время: 14:30:45

Работает и универсальный get(ChronoField): dateTime.get(ChronoField.MINUTE_OF_DAY) вернёт 870. У LocalDateTime есть все поля даты и времени, но нет полей зоны: dateTime.get(ChronoField.OFFSET_SECONDS) выбросит UnsupportedTemporalTypeException: Unsupported field: OffsetSeconds. Подробно о полях ChronoField — в уроках LocalDate и LocalTime.

Изменение, сложение и сравнение: with(), plus(), isAfter()

Методы with...() заменяют одно поле, plus...() и minus...() сдвигают значение на годы, месяцы, недели, дни, часы, минуты, секунды или наносекунды, а isAfter(), isBefore(), isEqual() сравнивают два момента. Объект неизменяем: каждый метод возвращает новый LocalDateTime, исходный остаётся прежним.

import java.time.LocalDateTime;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;

public class LocalDateTimeExample3 {
    public static void main(String[] args) {
        LocalDateTime base = LocalDateTime.of(2024, 3, 15, 14, 30);

        LocalDateTime date1 = base.with(ChronoField.HOUR_OF_DAY, 10);
        LocalDateTime date2 = base.withMonth(8);
        LocalDateTime date3 = base.withYear(2013).withMinute(0);

        // Сравнение
        boolean after = base.isAfter(date1);
        boolean before = base.isBefore(date1);
        boolean equal = base.equals(date1);

        // Добавление
        LocalDateTime date4 = base.plusYears(4);
        LocalDateTime date5 = base.plusWeeks(3);
        LocalDateTime date6 = date1.plus(2, ChronoUnit.HOURS);

        // Вычитание
        LocalDateTime date7 = base.minusMonths(2);
        LocalDateTime date8 = base.minusNanos(1);
        LocalDateTime date9 = base.minus(10, ChronoUnit.SECONDS);

        System.out.println("date1: " + date1);
        System.out.println("date2: " + date2);
        System.out.println("date3: " + date3);
        System.out.println("after: " + after + ", before: " + before + ", equal: " + equal);
        System.out.println("date4: " + date4);
        System.out.println("date5: " + date5);
        System.out.println("date6: " + date6);
        System.out.println("date7: " + date7);
        System.out.println("date8: " + date8);
        System.out.println("date9: " + date9);
        System.out.println("base не изменился: " + base);
    }
}
date1: 2024-03-15T10:30
date2: 2024-08-15T14:30
date3: 2013-03-15T14:00
after: true, before: false, equal: false
date4: 2028-03-15T14:30
date5: 2024-04-05T14:30
date6: 2024-03-15T12:30
date7: 2024-01-15T14:30
date8: 2024-03-15T14:29:59.999999999
date9: 2024-03-15T14:29:50
base не изменился: 2024-03-15T14:30

В отличие от LocalTime, время здесь не «заворачивается» внутри суток: LocalDateTime.of(2024, 3, 15, 23, 30).plusMinutes(45) даст 2024-03-16T00:15 — дата сдвинется на следующий день. Для сортировки используйте compareTo() (класс реализует Comparable) и сравнивайте результат с нулём, а не с -1: сначала сравниваются даты, и разница может быть любым числом.

Чтобы отбросить лишнюю точность, есть truncatedTo(): dateTime.truncatedTo(ChronoUnit.DAYS) даёт полночь того же дня, truncatedTo(ChronoUnit.MINUTES) — значение без секунд. Единицы больше суток не поддерживаются: truncatedTo(ChronoUnit.MONTHS) выбросит UnsupportedTemporalTypeException: Unit is too large to be used for truncation.

Форматирование LocalDateTime: format() и parse()

Чтобы вывести LocalDateTime в нужном виде, например 15.03.2024 14:30, вызовите dateTime.format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")). Обратная операция — LocalDateTime.parse(текст, formatter). Без форматтера toString() и parse() работают только с ISO-8601: 2024-03-15T14:30.

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class FormatLocalDateTimeExample {
    public static void main(String[] args) {
        LocalDateTime dateTime = LocalDateTime.of(2024, 3, 15, 14, 30);

        DateTimeFormatter ru = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");
        DateTimeFormatter sql = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

        System.out.println(dateTime);              // toString(), ISO-8601
        System.out.println(dateTime.format(ru));
        System.out.println(dateTime.format(sql));

        // Строка -> LocalDateTime
        LocalDateTime parsed = LocalDateTime.parse("15.03.2024 14:30", ru);
        LocalDateTime withSpace = LocalDateTime.parse("2024-03-15 10:30:00", sql);
        System.out.println(parsed);
        System.out.println(withSpace);
    }
}
2024-03-15T14:30
15.03.2024 14:30
2024-03-15 14:30:00
2024-03-15T14:30
2024-03-15T10:30

Два частых сбоя при форматировании. Если в шаблоне нет времени, а разбираете вы в LocalDateTime, например LocalDateTime.parse("15.03.2024", DateTimeFormatter.ofPattern("dd.MM.yyyy")), получите DateTimeParseException: ... Unable to obtain LocalDateTime from TemporalAccessor — разбирайте в LocalDate и вызывайте atStartOfDay(). А шаблон со смещением зоны (XXX, Z) на LocalDateTime выбросит UnsupportedTemporalTypeException: Unsupported field: OffsetSeconds: зоны в объекте нет, сначала нужен atZone(). Все символы шаблона и работа с Locale — в уроке Класс DateTimeFormatter.

Разница между двумя LocalDateTime

Разницу между двумя LocalDateTime считают через Duration.between(start, end) — результат в часах, минутах и секундах — или через ChronoUnit.X.between(start, end), который возвращает long в нужных единицах. Учитываются только полные единицы: неполный день или час отбрасывается.

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.temporal.ChronoUnit;

public class BetweenLocalDateTimeExample {
    public static void main(String[] args) {
        LocalDateTime start = LocalDateTime.of(2024, 3, 15, 22, 0);
        LocalDateTime end = LocalDateTime.of(2024, 3, 16, 6, 30);

        System.out.println(Duration.between(start, end));
        System.out.println(ChronoUnit.MINUTES.between(start, end));
        System.out.println(ChronoUnit.DAYS.between(start, end));
        System.out.println(Period.between(start.toLocalDate(), end.toLocalDate()));
    }
}
PT8H30M
510
0
P1D

Последние две строки отвечают на разные вопросы. ChronoUnit.DAYS.between считает полные сутки (прошло 8,5 часа — значит, 0 дней), а Period.between по датам видит, что календарный день сменился. Сам Period.between(start, end) с аргументами LocalDateTime не скомпилируется: он принимает только LocalDate. Подробнее — в уроках Класс Duration и Класс Period.

LocalDateTime vs Instant: что выбрать

LocalDateTime — это дата и время «на часах», без зоны, поэтому одно и то же значение в Москве и в Берлине соответствует разным моментам. Instant — точный момент на шкале UTC, одинаковый для всех. Для меток событий (создание заказа, логи) выбирайте Instant, для локальных значений вроде «приём у врача 15 марта в 10:30» — LocalDateTime.

Признак LocalDateTime Instant
Что хранит Год, месяц, день, часы, минуты, секунды, наносекунды Секунды и наносекунды от 1970-01-01T00:00:00Z
Часовой пояс Нет и не подразумевается Нет, но значение всегда в UTC
Пример toString() 2024-03-15T10:30 2024-03-15T07:30:00Z
Определяет момент времени? Нет, пока не указана зона Да
Перевод часов на летнее время Не учитывает Не касается: шкала UTC непрерывна
Поля «год», «месяц», plusMonths() Есть Нет: только секунды, миллисекунды, наносекунды (и дни как 86400 секунд)
Когда выбирать Расписание, дата и время в чеке, поле формы «дата и время» Метки времени в логах и БД, обмен между сервисами

Что выведет этот код? Отсутствие зоны у LocalDateTime хорошо видно на переводе часов:

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;

public class DstExample {
    public static void main(String[] args) {
        ZoneId berlin = ZoneId.of("Europe/Berlin");

        // 31 марта 2024: в Берлине часы переводят с 02:00 сразу на 03:00
        LocalDateTime gap = LocalDateTime.of(2024, 3, 31, 2, 30);
        System.out.println(gap.atZone(berlin));

        // 27 октября 2024: с 03:00 обратно на 02:00, ночь длиннее на час
        LocalDateTime start = LocalDateTime.of(2024, 10, 27, 0, 0);
        LocalDateTime end = LocalDateTime.of(2024, 10, 27, 6, 0);
        System.out.println(ChronoUnit.HOURS.between(start, end));
        System.out.println(ChronoUnit.HOURS.between(start.atZone(berlin), end.atZone(berlin)));
    }
}
2024-03-31T03:30+02:00[Europe/Berlin]
6
7

Время 02:30 31 марта в Берлине не существует, но LocalDateTime об этом не знает — при привязке к зоне оно молча сдвигается на 03:30. А в ночь обратного перевода между 00:00 и 06:00 по часам прошло 6 часов, хотя на самом деле — 7. Если считаете длительность смен, тарифицируете звонки или храните моменты событий, работайте с ZonedDateTime или Instant.

Конвертация в Instant, ZonedDateTime и миллисекунды

Чтобы превратить LocalDateTime в Instant или миллисекунды от эпохи, обязательно укажите зону: ldt.atZone(zone).toInstant() или смещение: ldt.toInstant(ZoneOffset.UTC). Обратная операция — LocalDateTime.ofInstant(instant, zone). Без зоны преобразование невозможно, поэтому прямого метода toInstant() без аргументов у класса нет.

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;

public class ConvertLocalDateTimeExample {
    public static void main(String[] args) {
        LocalDateTime meeting = LocalDateTime.of(2024, 3, 15, 10, 30);
        ZoneId moscow = ZoneId.of("Europe/Moscow");

        ZonedDateTime zoned = meeting.atZone(moscow);
        Instant inMoscow = zoned.toInstant();
        Instant inBerlin = meeting.atZone(ZoneId.of("Europe/Berlin")).toInstant();
        Instant asUtc = meeting.toInstant(ZoneOffset.UTC);
        long millis = inMoscow.toEpochMilli();

        LocalDateTime inTokyo = LocalDateTime.ofInstant(inMoscow, ZoneId.of("Asia/Tokyo"));

        System.out.println(zoned);
        System.out.println(inMoscow);
        System.out.println(inBerlin);
        System.out.println(asUtc);
        System.out.println(millis);
        System.out.println(inTokyo);
        System.out.println(zoned.toLocalDateTime());
    }
}
2024-03-15T10:30+03:00[Europe/Moscow]
2024-03-15T07:30:00Z
2024-03-15T09:30:00Z
2024-03-15T10:30:00Z
1710487800000
2024-03-15T16:30
2024-03-15T10:30

Строки 2–4 показывают главное: одна и та же запись «15 марта, 10:30» даёт три разных момента в зависимости от зоны. Миллисекунды без объекта Instant можно получить и короче — meeting.toEpochSecond(ZoneOffset.ofHours(3)) вернёт секунды. Конвертация в старый java.util.Date идёт через тот же Instant: Date.from(ldt.atZone(zone).toInstant()); полная таблица переходов со старого API — в уроке Дата и время в Java.

ZoneId.systemDefault() — скрытая зависимость

Код ldt.atZone(ZoneId.systemDefault()) на ноутбуке разработчика в Москве и на сервере в UTC даст моменты с разницей в 3 часа. Если значение пришло от пользователя, берите его зону (из профиля или браузера), а не зону JVM. Если зона неизвестна, сохраните LocalDateTime как есть и не превращайте его в Instant наугад.

Шпаргалка по методам LocalDateTime

Методы LocalDateTime делятся на группы: создание, чтение полей, изменение копии, сравнение, форматирование и привязка к зоне. Все методы, которые «меняют» значение, возвращают новый объект.

Метод Что делает Граничный случай / ошибка
of(y, m, d, h, min[, s[, n]]), of(LocalDate, LocalTime) Дата и время из частей of(2024, 2, 30, 10, 0) — DateTimeException
now(), now(ZoneId), now(Clock) Текущие дата и время Без аргумента берётся зона JVM по умолчанию
parse(text), parse(text, formatter) Разбор строки Пробел вместо T — DateTimeParseException at index 10
getYear() … getNano(), toLocalDate(), toLocalTime() Поля, отдельно дата или время get(ChronoField.OFFSET_SECONDS) — UnsupportedTemporalTypeException
withYear() … withNano(), with(ChronoField, value) Копия с заменённым полем withHour(24) — DateTimeException
plusYears() … plusNanos(), minus...(), plus(n, ChronoUnit) Сдвиг на N единиц Переход через полночь меняет дату
isBefore(), isAfter(), isEqual(), compareTo() Сравнение compareTo() сравнивайте с нулём, не с -1
truncatedTo(unit) Обнуляет поля младше единицы MONTHS и больше — UnsupportedTemporalTypeException
format(formatter) Строка по шаблону Шаблон с XXX — Unsupported field: OffsetSeconds
atZone(ZoneId), atOffset(ZoneOffset) Привязка к зоне: ZonedDateTime / OffsetDateTime Время в «дыре» перевода часов сдвигается вперёд
toInstant(ZoneOffset), toEpochSecond(ZoneOffset), ofInstant(Instant, ZoneId) Переход к моменту времени и обратно Без зоны или смещения не вызвать

Где чаще всего спотыкаются

Ошибки с LocalDateTime чаще всего связаны с форматом строки (буква T), забытым присваиванием результата и ожиданием, что у значения есть часовой пояс. Ниже — типичные случаи и исправления.

Ошибка Что происходит Как правильно
dt.plusHours(1); без присваивания Результат потерян, dt не меняется dt = dt.plusHours(1);
LocalDateTime.parse("2024-03-15 10:30") DateTimeParseException at index 10 "2024-03-15T10:30" или ofPattern("yyyy-MM-dd HH:mm")
createdAt = LocalDateTime.now() на серверах в разных зонах Время событий нельзя сравнить и упорядочить Instant.now() или OffsetDateTime
Period.between(ldt1, ldt2) Не компилируется: нужен LocalDate Duration.between(ldt1, ldt2) или Period.between(ldt1.toLocalDate(), ldt2.toLocalDate())
ChronoUnit.DAYS.between для «сменилось ли число» 22:00 → 06:30 следующего дня даёт 0 Сравнивайте toLocalDate()
dt1 == dt2 Сравниваются ссылки, а не значения dt1.isEqual(dt2) или dt1.equals(dt2)
date.atTime(14, 30, 59, 999999) Получится 14:30:59.000999999: четвёртый аргумент — наносекунды 999_999_999; подробнее — в уроке LocalTime

Часто задаваемые вопросы

Как сохранить LocalDateTime в базу данных?

Начиная с JDBC 4.2 передавайте объект напрямую: ps.setObject(1, dateTime) и rs.getObject("created", LocalDateTime.class); подходящий тип столбца — TIMESTAMP (без часового пояса). Hibernate 5.2+ и JPA 2.2 отображают LocalDateTime сами. В старом коде используется Timestamp.valueOf(dateTime) и timestamp.toLocalDateTime(). Если в столбце должен храниться момент события, выбирайте TIMESTAMP WITH TIME ZONE и OffsetDateTime или Instant.

Как получить начало и конец дня в LocalDateTime?

Начало дня — date.atStartOfDay() (00:00), конец — date.atTime(LocalTime.MAX) (23:59:59.999999999). Для выборок за день надёжнее полуоткрытый интервал: created >= date.atStartOfDay() и created < date.plusDays(1).atStartOfDay() — так не потеряются записи с долями секунды, если база хранит другую точность.

Почему Jackson не сериализует LocalDateTime в JSON?

Поддержка java.time вынесена в модуль jackson-datatype-jsr310. Без него Jackson 2.12+ выбрасывает InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime` not supported by default. Зарегистрируйте JavaTimeModule и отключите SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, иначе значение запишется массивом чисел вроде [2024,3,15,14,30], а не строкой ISO-8601. Spring Boot делает обе настройки автоматически; свой формат задают аннотацией @JsonFormat(pattern = "dd.MM.yyyy HH:mm").

Как отсортировать список объектов по полю LocalDateTime?

LocalDateTime реализует Comparable, поэтому достаточно orders.sort(Comparator.comparing(Order::getCreatedAt)); новые сверху — добавьте .reversed(). Если поле может быть null, обычный компаратор упадёт с NullPointerException — используйте Comparator.comparing(Order::getCreatedAt, Comparator.nullsLast(Comparator.naturalOrder())).

Practice

Reinforce this lesson with hands-on tasks, each with a level and a time estimate.

All module tasks →

Video Explanation

Prefer video format? Watch this lesson with examples and explanations.

Comments

Please log in or register to have a possibility to add comment.