Дата и время в Java: java.time API и текущая дата
Попробуйте создать дату 15 февраля 2024 года старым способом: new Date(2024, 1, 15). Код скомпилируется (с предупреждением deprecation), но объект будет хранить 15 февраля 3924 года: к году прибавляется 1900, а месяцы считаются с нуля. В java.time та же дата записывается как LocalDate.of(2024, 2, 15) — и означает ровно то, что написано.
Java Date Time API (пакет java.time, JSR-310) — стандартная библиотека для работы с датой и временем, добавленная в Java 8 на замену java.util.Date и java.util.Calendar. Её основные классы — LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Instant, Period и Duration — неизменяемы (immutable) и потокобезопасны. Текущая дата получается вызовом LocalDate.now(), текущие дата и время — LocalDateTime.now().
Как получить текущую дату и время в Java
Текущую дату и время в Java получают статическим методом now() нужного класса из пакета java.time: LocalDate.now() возвращает только дату, LocalTime.now() — только время, LocalDateTime.now() — дату и время, ZonedDateTime.now() — дату и время с часовым поясом, Instant.now() — момент времени в UTC.
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class CurrentDateTimeExample {
public static void main(String[] args) {
LocalDate date = LocalDate.now(); // текущая дата
LocalTime time = LocalTime.now(); // текущее время
LocalDateTime dateTime = LocalDateTime.now(); // текущие дата и время
ZonedDateTime zoned = ZonedDateTime.now(); // + часовой пояс JVM
Instant instant = Instant.now(); // момент в UTC
LocalDateTime tokyo = LocalDateTime.now(ZoneId.of("Asia/Tokyo"));
System.out.println("Дата: " + date);
System.out.println("Время: " + time);
System.out.println("Дата и время: " + dateTime);
System.out.println("С поясом: " + zoned);
System.out.println("Instant: " + instant);
System.out.println("В Токио: " + tokyo);
}
} Пример вывода при запуске 17 октября 2026 года в 14:35 по Москве (Java 17, часовой пояс JVM — Europe/Moscow):
Дата: 2026-10-17
Время: 14:35:07.123456
Дата и время: 2026-10-17T14:35:07.123456
С поясом: 2026-10-17T14:35:07.123456+03:00[Europe/Moscow]
Instant: 2026-10-17T11:35:07.123456Z
В Токио: 2026-10-17T20:35:07.123456 Обратите внимание: Instant всегда печатается в UTC (суффикс Z), поэтому отстаёт от московского времени на 3 часа. Количество знаков после секунд зависит от версии Java и ОС: в Java 8 системные часы дают миллисекунды (14:35:07.123), начиная с Java 9 — обычно микросекунды.
Чтобы вывести текущую дату в привычном формате dd.MM.yyyy, используйте DateTimeFormatter:
String today = LocalDate.now().format(DateTimeFormatter.ofPattern("dd.MM.yyyy"));
System.out.println(today); // Вывод: 17.10.2026 Символы шаблонов и разбор строки в дату — в уроке Класс DateTimeFormatter. Получение отдельно года, месяца и дня недели из текущей даты разобрано в уроке Класс LocalDate.
Классы java.time: какой выбрать
Класс из java.time выбирают по тому, что нужно хранить: дату без времени — LocalDate, время суток — LocalTime, дату и время без пояса — LocalDateTime, дату и время в конкретном поясе — ZonedDateTime, точный момент для логов и БД — Instant, промежуток в днях — Period, в секундах — Duration.
| Класс | Что хранит | Пример toString() | Когда использовать |
|---|---|---|---|
LocalDate | Год, месяц, день | 2026-10-17 | Дата рождения, дата платежа, праздники |
LocalTime | Часы, минуты, секунды, наносекунды | 14:35:07 | Время открытия магазина, будильник |
LocalDateTime | Дата и время без часового пояса | 2026-10-17T14:35:07 | Время встречи в одном городе, время в чеке |
ZonedDateTime | Дата, время и часовой пояс (ZoneId) | 2026-10-17T14:35:07+03:00[Europe/Moscow] | Рейсы, созвоны между странами, переход на летнее время |
Instant | Секунды и наносекунды от 1970-01-01T00:00:00Z | 2026-10-17T11:35:07Z | Метки времени в логах, БД, между сервисами |
Period | Количество лет, месяцев, дней | P1Y2M3D | Возраст, срок договора, «через 2 месяца» |
Duration | Секунды и наносекунды | PT1H30M | Тайм-ауты, длительность операции |
Классы, описывающие точку на шкале времени (LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Instant), реализуют интерфейс java.time.temporal.Temporal. Классы-промежутки Period и Duration реализуют java.time.temporal.TemporalAmount — поэтому любой промежуток можно прибавить к дате общим методом plus(TemporalAmount).
Неочевидный момент
Классы с префиксом Local не хранят часовой пояс, но и Instant его не хранит: это просто момент на шкале UTC. Часовой пояс есть только у ZonedDateTime (зона вида Europe/Moscow) и OffsetDateTime (смещение вида +03:00). Получить «местное» время из Instant можно лишь указав зону: instant.atZone(ZoneId.of("Europe/Moscow")).
Чем java.time лучше Date и Calendar
До Java 8 с датами работали через java.util.Date и java.util.Calendar. Эти классы изменяемы и не потокобезопасны, месяцы в них нумеруются с нуля, а некорректные значения молча «перекатываются» в следующий месяц. В java.time все эти проблемы устранены: объекты неизменяемы, месяцы идут с 1, а ошибочная дата сразу вызывает исключение.
| Признак | Date / Calendar | java.time |
|---|---|---|
| Изменяемость | Изменяемые: setTime(), set(), add() меняют объект | Неизменяемые: plusDays() и др. возвращают новый объект |
| Потокобезопасность | Нет; SimpleDateFormat тоже не потокобезопасен | Да, включая DateTimeFormatter |
| Нумерация месяцев | С 0: январь = 0, декабрь = 11 | С 1, плюс перечисление Month |
Год в new Date(y, m, d) | Смещение от 1900 (конструктор deprecated) | Обычный год: LocalDate.of(2024, 2, 15) |
| Некорректная дата | Молча переносится (29.02.2023 → 01.03.2023) | DateTimeException |
| Часовой пояс | Date хранит миллисекунды от эпохи, а при выводе подставляет пояс JVM по умолчанию | Есть отдельные классы с поясом и без: LocalDateTime, ZonedDateTime, Instant |
| Разделение понятий | Date одновременно и дата, и время, и момент | Отдельный класс на каждую задачу (см. таблицу выше) |
Что выведет этот код? Многие ждут исключения, но Calendar по умолчанию работает в «мягком» (lenient) режиме:
import java.time.LocalDate;
import java.util.Calendar;
public class LenientExample {
public static void main(String[] args) {
Calendar c = Calendar.getInstance();
c.clear();
c.set(2023, Calendar.FEBRUARY, 29); // 2023 - не високосный год
System.out.println(c.getTime());
// Вывод (пояс JVM Europe/Moscow): Wed Mar 01 00:00:00 MSK 2023
LocalDate date = LocalDate.of(2023, 2, 29);
// Exception in thread "main" java.time.DateTimeException:
// Invalid date 'February 29' as '2023' is not a leap year
}
} Старый API «исправил» ошибку и выдал 1 марта — такой баг легко пропустить до продакшена. LocalDate.of() сообщает о проблеме сразу, в строке, где она возникла.
Как перейти со старого API: конвертация Date и Calendar
Перевести java.util.Date в классы java.time можно через Instant: date.toInstant(), затем atZone(ZoneId) и toLocalDateTime() или toLocalDate(). Обратно — Date.from(instant). Эти методы появились в Java 8 и нужны, когда библиотека или старый код всё ещё принимает Date.
| Из | В | Как |
|---|---|---|
Date | Instant | date.toInstant() |
Instant | Date | Date.from(instant) |
Date | LocalDateTime | date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime() |
LocalDateTime | Date | Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant()) |
GregorianCalendar | ZonedDateTime | calendar.toZonedDateTime() / обратно GregorianCalendar.from(zdt) |
java.sql.Date | LocalDate | sqlDate.toLocalDate() / обратно java.sql.Date.valueOf(localDate) |
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
public class ConvertExample {
public static void main(String[] args) {
Date legacy = new Date(0L); // 1970-01-01T00:00:00Z
Instant instant = legacy.toInstant();
LocalDateTime moscow = instant.atZone(ZoneId.of("Europe/Moscow"))
.toLocalDateTime();
Date back = Date.from(instant);
System.out.println(instant); // Вывод: 1970-01-01T00:00:00Z
System.out.println(moscow); // Вывод: 1970-01-01T03:00
System.out.println(back.getTime()); // Вывод: 0
}
} На чём чаще всего ошибаются
Самая частая ошибка новичков в java.time — вызвать plusDays() или withYear() и не сохранить результат: объект неизменяем, и исходная дата остаётся прежней. Кроме того, неожиданности дают сложение месяцев в конце месяца и сокращённый вывод toString().
import java.time.LocalDate;
import java.time.LocalTime;
public class MistakesExample {
public static void main(String[] args) {
LocalDate date = LocalDate.of(2024, 1, 31);
date.plusDays(1); // результат потерян
System.out.println(date); // Вывод: 2024-01-31
date = date.plusDays(1); // правильно
System.out.println(date); // Вывод: 2024-02-01
LocalDate endOfJan = LocalDate.of(2024, 1, 31);
System.out.println(endOfJan.plusMonths(1)); // Вывод: 2024-02-29
System.out.println(LocalTime.of(14, 0)); // Вывод: 14:00
System.out.println(LocalTime.of(14, 0, 5)); // Вывод: 14:00:05
}
} - Потерянный результат. Методы
plus*(),minus*(),with*()возвращают новый объект — присваивайте его переменной. - Конец месяца.
plusMonths(1)от 31 января даёт последний существующий день февраля (29-е в високосном 2024 году), а не 2 или 3 марта. - Короткий
toString(). Нулевые секунды и наносекунды не печатаются, поэтомуLocalTime.of(14, 0)выводится как14:00. Для стабильного формата (отчёты, сравнение строк) используйтеDateTimeFormatter. LocalDateTimeдля меток событий. Без пояса нельзя понять, когда событие произошло на самом деле, если серверы стоят в разных зонах. Для логов и БД хранитеInstant.
Презентация и видео на Patreon →
Часто задаваемые вопросы
Почему LocalDateTime.now() на сервере показывает не то время, что на моём компьютере?
Методы now() без аргументов берут часовой пояс JVM по умолчанию (ZoneId.systemDefault()), а он берётся из настроек ОС сервера. Если сервер работает в UTC, а вы в Москве, разница составит 3 часа. Передавайте зону явно — LocalDateTime.now(ZoneId.of("Europe/Moscow")), задайте параметр JVM -Duser.timezone=Europe/Moscow или храните моменты в Instant и переводите в нужную зону только при выводе.
Чем Instant.now() отличается от System.currentTimeMillis()?
Оба возвращают текущий момент относительно эпохи 1970-01-01T00:00:00Z. System.currentTimeMillis() отдаёт long в миллисекундах, а Instant.now() — объект с секундами и наносекундами, который можно сравнивать, складывать с Duration и переводить в ZonedDateTime. Для замера длительности выполнения кода берите System.nanoTime(): он не зависит от перевода системных часов.
Как подменить текущую дату в unit-тестах?
У каждого метода now() есть перегрузка now(Clock). В рабочем коде передавайте Clock.systemDefaultZone(), а в тесте — Clock.fixed(Instant.parse("2024-02-29T10:00:00Z"), ZoneOffset.UTC). Тогда LocalDate.now(clock) всегда вернёт 2024-02-29, и тест не зависит от дня запуска.
Нужно ли переписывать старый код с java.util.Date на java.time?
Срочно — нет: класс Date не удалён, устаревшими помечены только его конструкторы и методы с годом, месяцем и днём. В новом коде используйте java.time, а на границе со старым API конвертируйте значения — таблица методов в разделе Как перейти со старого API. JDBC 4.2 и Hibernate 5+ работают с LocalDate, LocalDateTime и Instant напрямую.
Video Explanation
Prefer video format? Watch this lesson with examples and explanations.
Comments