2.x. datetime
datetime
Материалы
Дата и время
Пакет java.time, введённый в Java 8, предоставляет современный API для работы с датами и временем. Все основные классы этого пакета неизменяемы (immutable) и потокобезопасны.
Обзор основных классов
| Класс | Описание | Пример |
|---|---|---|
LocalDate | Дата без времени и часового пояса | 2025-01-26 |
LocalTime | Время без даты и часового пояса | 14:30:15.123 |
LocalDateTime | Дата и время без часового пояса | 2025-01-26T14:30:15 |
ZonedDateTime | Дата и время с часовым поясом | 2025-01-26T14:30:15+03:00[Europe/Moscow] |
OffsetDateTime | Дата и время со смещением от UTC | 2025-01-26T14:30:15+03:00 |
Instant | Момент времени на временной шкале (timestamp) | 2025-01-26T11:30:15Z |
Duration | Интервал времени (часы, минуты, секунды) | PT2H30M |
Period | Интервал дат (годы, месяцы, дни) | P1Y2M10D |
LocalDate — дата без времени
LocalDate представляет дату в формате год-месяц-день без привязки к времени или часовому поясу. Используйте его для дней рождения, праздников, дедлайнов.
import java.time.LocalDate;
import java.time.Month;
// Текущая дата
LocalDate today = LocalDate.now();
// Конкретная дата
LocalDate birthday = LocalDate.of(1990, Month.MARCH, 15);
LocalDate christmas = LocalDate.of(2025, 12, 25);
// Парсинг из строки
LocalDate parsed = LocalDate.parse("2025-01-26");
Получение компонентов даты:
LocalDate date = LocalDate.of(2025, 1, 26);
int year = date.getYear(); // 2025
Month month = date.getMonth(); // JANUARY
int monthValue = date.getMonthValue(); // 1
int dayOfMonth = date.getDayOfMonth(); // 26
DayOfWeek dayOfWeek = date.getDayOfWeek(); // SUNDAY
int dayOfYear = date.getDayOfYear(); // 26
Арифметика с датами:
LocalDate date = LocalDate.of(2025, 1, 26);
LocalDate tomorrow = date.plusDays(1); // 2025-01-27
LocalDate nextWeek = date.plusWeeks(1); // 2025-02-02
LocalDate nextMonth = date.plusMonths(1); // 2025-02-26
LocalDate nextYear = date.plusYears(1); // 2026-01-26
LocalDate yesterday = date.minusDays(1); // 2025-01-25
Сравнение дат:
LocalDate date1 = LocalDate.of(2025, 1, 26);
LocalDate date2 = LocalDate.of(2025, 2, 15);
boolean isBefore = date1.isBefore(date2); // true
boolean isAfter = date1.isAfter(date2); // false
boolean isEqual = date1.isEqual(date1); // true
LocalTime — время без даты
LocalTime представляет время с точностью до наносекунд без привязки к дате или часовому поясу. Используйте его для времени открытия магазина, будильника, расписания.
import java.time.LocalTime;
// Текущее время
LocalTime now = LocalTime.now();
// Конкретное время
LocalTime morning = LocalTime.of(8, 30); // 08:30
LocalTime precise = LocalTime.of(14, 30, 15); // 14:30:15
LocalTime nanos = LocalTime.of(14, 30, 15, 123456789); // 14:30:15.123456789
// Специальные константы
LocalTime midnight = LocalTime.MIDNIGHT; // 00:00
LocalTime noon = LocalTime.NOON; // 12:00
LocalTime max = LocalTime.MAX; // 23:59:59.999999999
// Парсинг
LocalTime parsed = LocalTime.parse("14:30:15");
Получение компонентов и арифметика:
LocalTime time = LocalTime.of(14, 30, 15);
int hour = time.getHour(); // 14
int minute = time.getMinute(); // 30
int second = time.getSecond(); // 15
LocalTime later = time.plusHours(2).plusMinutes(15); // 16:45:15
LocalTime earlier = time.minusMinutes(45); // 13:45:15
LocalDateTime — дата и время
LocalDateTime объединяет дату и время, но не содержит информации о часовом поясе. Это описание даты и времени, как на настенных часах, без привязки к конкретной точке на временной шкале.
import java.time.LocalDateTime;
// Текущая дата и время
LocalDateTime now = LocalDateTime.now();
// Конкретные дата и время
LocalDateTime meeting = LocalDateTime.of(2025, Month.JANUARY, 26, 14, 30);
LocalDateTime precise = LocalDateTime.of(2025, 1, 26, 14, 30, 15, 123456789);
// Из LocalDate и LocalTime
LocalDate date = LocalDate.of(2025, 1, 26);
LocalTime time = LocalTime.of(14, 30);
LocalDateTime combined = LocalDateTime.of(date, time);
LocalDateTime atTime = date.atTime(14, 30);
LocalDateTime atDate = time.atDate(date);
// Парсинг
LocalDateTime parsed = LocalDateTime.parse("2025-01-26T14:30:15");
Извлечение компонентов:
LocalDateTime dt = LocalDateTime.of(2025, 1, 26, 14, 30, 15);
LocalDate date = dt.toLocalDate(); // 2025-01-26
LocalTime time = dt.toLocalTime(); // 14:30:15
int year = dt.getYear();
Month month = dt.getMonth();
int day = dt.getDayOfMonth();
int hour = dt.getHour();
int minute = dt.getMinute();
Важно:
LocalDateTimeне представляет момент на временной шкале. Он не может быть преобразован вInstantбез указания часового пояса. ИспользуйтеZonedDateTimeилиOffsetDateTime, когда нужна конкретная точка во времени.
Instant — момент времени
Instant представляет точку на временной шкале — количество секунд (и наносекунд) с Unix-эпохи (1 января 1970 года, 00:00:00 UTC). Это машинное представление времени, идеальное для логирования, timestamps и вычислений.
import java.time.Instant;
// Текущий момент
Instant now = Instant.now();
// Из Unix timestamp (секунды)
Instant fromEpoch = Instant.ofEpochSecond(1706270400L);
// Из миллисекунд
Instant fromMillis = Instant.ofEpochMilli(1706270400000L);
// Парсинг ISO-8601 (всегда UTC)
Instant parsed = Instant.parse("2025-01-26T11:30:00Z");
Получение значений:
Instant instant = Instant.now();
long epochSecond = instant.getEpochSecond(); // Секунды с эпохи
int nano = instant.getNano(); // Наносекунды (0-999999999)
long epochMilli = instant.toEpochMilli(); // Миллисекунды с эпохи
Арифметика:
Instant instant = Instant.parse("2025-01-26T12:00:00Z");
Instant later = instant.plusSeconds(3600); // +1 час
Instant earlier = instant.minusMillis(500); // -500 мс
Instant muchLater = instant.plus(Duration.ofHours(5)); // +5 часов
Ограничение:
Instantне поддерживает операции с днями, месяцами, годами, так как они зависят от часового пояса и календаря. Для таких операций используйтеZonedDateTime.
Часовые пояса: ZoneId и ZoneOffset
ZoneId — географический часовой пояс
ZoneId представляет часовой пояс в формате “регион/город” (например, Europe/Moscow). Он учитывает правила перехода на летнее время.
import java.time.ZoneId;
// Системный часовой пояс
ZoneId systemZone = ZoneId.systemDefault();
// Конкретный часовой пояс
ZoneId moscow = ZoneId.of("Europe/Moscow");
ZoneId tokyo = ZoneId.of("Asia/Tokyo");
ZoneId newYork = ZoneId.of("America/New_York");
ZoneId utc = ZoneId.of("UTC");
// Все доступные зоны
Set<String> zones = ZoneId.getAvailableZoneIds();
ZoneOffset — фиксированное смещение
ZoneOffset представляет фиксированное смещение от UTC, например +03:00. Он не знает о летнем времени.
import java.time.ZoneOffset;
ZoneOffset plusThree = ZoneOffset.of("+03:00");
ZoneOffset minusFive = ZoneOffset.of("-05:00");
ZoneOffset utc = ZoneOffset.UTC; // +00:00
ZoneOffset hours = ZoneOffset.ofHours(3);
ZoneOffset hoursMinutes = ZoneOffset.ofHoursMinutes(5, 30);
ZonedDateTime — дата и время с часовым поясом
ZonedDateTime — это полное представление даты и времени с часовым поясом. Он учитывает переход на летнее время и другие правила часовых поясов.
import java.time.ZonedDateTime;
import java.time.ZoneId;
// Текущее время в системном часовом поясе
ZonedDateTime now = ZonedDateTime.now();
// Текущее время в конкретном поясе
ZonedDateTime nowInTokyo = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
// Конкретная дата/время в поясе
ZonedDateTime meeting = ZonedDateTime.of(
2025, 1, 26, 14, 30, 0, 0,
ZoneId.of("Europe/Moscow")
);
// Из LocalDateTime
LocalDateTime local = LocalDateTime.of(2025, 1, 26, 14, 30);
ZonedDateTime zoned = local.atZone(ZoneId.of("Europe/Moscow"));
// Парсинг
ZonedDateTime parsed = ZonedDateTime.parse("2025-01-26T14:30:00+03:00[Europe/Moscow]");
Конвертация между часовыми поясами:
ZonedDateTime moscowTime = ZonedDateTime.of(
2025, 1, 26, 14, 30, 0, 0,
ZoneId.of("Europe/Moscow")
);
// Тот же момент в другом поясе (время изменится)
ZonedDateTime tokyoTime = moscowTime.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
// 2025-01-26T20:30+09:00[Asia/Tokyo]
// То же локальное время в другом поясе (момент изменится)
ZonedDateTime sameClock = moscowTime.withZoneSameLocal(ZoneId.of("Asia/Tokyo"));
// 2025-01-26T14:30+09:00[Asia/Tokyo]
Проблемы перехода на летнее время
При переходе на летнее время возникают gap (пропуск) и overlap (наложение):
// Gap: при переходе на летнее время 2:00 -> 3:00
// Время 2:30 не существует!
ZonedDateTime gapTime = ZonedDateTime.of(
LocalDateTime.of(2024, 3, 31, 2, 30),
ZoneId.of("Europe/Berlin")
);
// Java автоматически сдвинет на 3:30
// Overlap: при переходе на зимнее время 3:00 -> 2:00
// Время 2:30 существует дважды
ZonedDateTime overlapTime = ZonedDateTime.of(
LocalDateTime.of(2024, 10, 27, 2, 30),
ZoneId.of("Europe/Berlin")
);
// Java выберет более раннее смещение
// Для контроля используйте:
ZonedDateTime withLaterOffset = overlapTime.withLaterOffsetAtOverlap();
OffsetDateTime — дата и время со смещением
OffsetDateTime хранит дату, время и фиксированное смещение от UTC, но без информации о часовом поясе. Используйте его для сериализации, хранения в базе данных, передачи по сети.
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
// Текущее время со смещением
OffsetDateTime now = OffsetDateTime.now();
// С конкретным смещением
OffsetDateTime withOffset = OffsetDateTime.now(ZoneOffset.of("+03:00"));
// Из LocalDateTime
LocalDateTime local = LocalDateTime.of(2025, 1, 26, 14, 30);
OffsetDateTime offset = local.atOffset(ZoneOffset.of("+03:00"));
// Парсинг
OffsetDateTime parsed = OffsetDateTime.parse("2025-01-26T14:30:00+03:00");
Когда использовать ZonedDateTime vs OffsetDateTime
Используйте ZonedDateTime | Используйте OffsetDateTime |
|---|---|
| Для UI и отображения пользователю | Для хранения в базе данных |
| Когда важны правила перехода времени | Для сериализации в JSON/XML |
| Для планирования событий в будущем | Для логирования |
| Для расчётов с учётом часового пояса | Когда достаточно фиксированного смещения |
Duration — интервал времени
Duration представляет интервал времени в секундах и наносекундах. Используйте его для измерения времени между моментами.
import java.time.Duration;
import java.time.temporal.ChronoUnit;
// Создание
Duration tenSeconds = Duration.ofSeconds(10);
Duration fiveMinutes = Duration.ofMinutes(5);
Duration twoHours = Duration.ofHours(2);
Duration oneDay = Duration.ofDays(1); // 24 часа ровно
Duration complex = Duration.ofHours(2).plusMinutes(30).plusSeconds(15);
// Парсинг ISO-8601
Duration parsed = Duration.parse("PT2H30M15S"); // 2 часа 30 минут 15 секунд
// Между двумя моментами
Instant start = Instant.now();
// ... выполнение кода ...
Instant end = Instant.now();
Duration elapsed = Duration.between(start, end);
// Между LocalDateTime
LocalDateTime dt1 = LocalDateTime.of(2025, 1, 26, 10, 0);
LocalDateTime dt2 = LocalDateTime.of(2025, 1, 26, 14, 30);
Duration between = Duration.between(dt1, dt2); // PT4H30M
Получение компонентов:
Duration duration = Duration.parse("PT2H30M15.5S");
long seconds = duration.getSeconds(); // 9015
int nanos = duration.getNano(); // 500000000
long totalMinutes = duration.toMinutes(); // 150
long totalHours = duration.toHours(); // 2
long totalMillis = duration.toMillis(); // 9015500
Арифметика:
Duration duration = Duration.ofHours(2);
Duration doubled = duration.multipliedBy(2); // PT4H
Duration halved = duration.dividedBy(2); // PT1H
Duration negated = duration.negated(); // PT-2H
Duration longer = duration.plusMinutes(30); // PT2H30M
Duration shorter = duration.minusSeconds(15); // PT1H59M45S
Period — интервал дат
Period представляет интервал в годах, месяцах и днях. Используйте его для календарных вычислений.
import java.time.Period;
// Создание
Period tenDays = Period.ofDays(10);
Period twoMonths = Period.ofMonths(2);
Period oneYear = Period.ofYears(1);
Period complex = Period.of(1, 2, 10); // 1 год 2 месяца 10 дней
// Парсинг ISO-8601
Period parsed = Period.parse("P1Y2M10D");
// Между двумя датами
LocalDate start = LocalDate.of(2024, 1, 1);
LocalDate end = LocalDate.of(2025, 3, 15);
Period between = Period.between(start, end); // P1Y2M14D
Получение компонентов:
Period period = Period.between(
LocalDate.of(2024, 1, 1),
LocalDate.of(2025, 3, 15)
);
int years = period.getYears(); // 1
int months = period.getMonths(); // 2
int days = period.getDays(); // 14
// Общее количество месяцев
long totalMonths = period.toTotalMonths(); // 14
Применение к датам:
LocalDate date = LocalDate.of(2025, 1, 26);
Period period = Period.of(1, 2, 10);
LocalDate future = date.plus(period); // 2026-04-05
LocalDate past = date.minus(period); // 2023-11-16
Разница между Duration и Period:
Durationпредставляет точное количество времени (1 день = ровно 24 часа).Periodпредставляет календарный интервал (1 месяц может быть 28, 29, 30 или 31 день).
ChronoUnit — единицы измерения
ChronoUnit позволяет измерять расстояние между датами/временем в конкретных единицах:
import java.time.temporal.ChronoUnit;
LocalDate date1 = LocalDate.of(2024, 1, 1);
LocalDate date2 = LocalDate.of(2025, 6, 15);
long days = ChronoUnit.DAYS.between(date1, date2); // 531
long weeks = ChronoUnit.WEEKS.between(date1, date2); // 75
long months = ChronoUnit.MONTHS.between(date1, date2); // 17
long years = ChronoUnit.YEARS.between(date1, date2); // 1
LocalDateTime dt1 = LocalDateTime.of(2025, 1, 26, 10, 0);
LocalDateTime dt2 = LocalDateTime.of(2025, 1, 26, 14, 30);
long hours = ChronoUnit.HOURS.between(dt1, dt2); // 4
long minutes = ChronoUnit.MINUTES.between(dt1, dt2); // 270
DateTimeFormatter — форматирование и парсинг
DateTimeFormatter используется для преобразования дат в строки и обратно.
Предопределённые форматтеры
import java.time.format.DateTimeFormatter;
LocalDateTime dt = LocalDateTime.of(2025, 1, 26, 14, 30, 15);
// ISO форматы
String iso = dt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
// "2025-01-26T14:30:15"
String isoDate = dt.format(DateTimeFormatter.ISO_LOCAL_DATE);
// "2025-01-26"
String isoTime = dt.format(DateTimeFormatter.ISO_LOCAL_TIME);
// "14:30:15"
Пользовательские паттерны
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");
LocalDateTime dt = LocalDateTime.of(2025, 1, 26, 14, 30);
// Форматирование
String formatted = dt.format(formatter); // "26.01.2025 14:30"
// Парсинг
LocalDateTime parsed = LocalDateTime.parse("26.01.2025 14:30", formatter);
Основные символы паттернов:
| Символ | Значение | Пример |
|---|---|---|
y | Год | 2025 |
M | Месяц | 1, 01, Jan, January |
d | День месяца | 26 |
E | День недели | Mon, Monday |
H | Час (0-23) | 14 |
h | Час (1-12) | 2 |
m | Минуты | 30 |
s | Секунды | 15 |
S | Доли секунды | 123 |
a | AM/PM | PM |
z | Название часового пояса | MSK |
Z | Смещение | +0300 |
X | Смещение с Z для UTC | +03 или Z |
Примеры паттернов:
// dd.MM.yyyy -> 26.01.2025
// yyyy-MM-dd -> 2025-01-26
// d MMMM yyyy -> 26 января 2025
// EEE, MMM d, yyyy -> Sun, Jan 26, 2025
// HH:mm:ss -> 14:30:15
// hh:mm a -> 02:30 PM
// yyyy-MM-dd'T'HH:mm:ss.SSSXXX -> 2025-01-26T14:30:15.000+03:00
Локализованные форматы
import java.time.format.FormatStyle;
import java.util.Locale;
LocalDateTime dt = LocalDateTime.of(2025, 1, 26, 14, 30);
// С текущей локалью
DateTimeFormatter full = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL);
DateTimeFormatter medium = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);
// С конкретной локалью
DateTimeFormatter russian = DateTimeFormatter
.ofLocalizedDate(FormatStyle.LONG)
.withLocale(new Locale("ru", "RU"));
String formatted = dt.toLocalDate().format(russian); // "26 января 2025 г."
TemporalAdjusters — корректировщики дат
TemporalAdjusters предоставляет готовые методы для сложных операций с датами:
import java.time.temporal.TemporalAdjusters;
LocalDate date = LocalDate.of(2025, 1, 26);
// Первый/последний день месяца
LocalDate firstDay = date.with(TemporalAdjusters.firstDayOfMonth()); // 2025-01-01
LocalDate lastDay = date.with(TemporalAdjusters.lastDayOfMonth()); // 2025-01-31
// Первый/последний день года
LocalDate firstOfYear = date.with(TemporalAdjusters.firstDayOfYear()); // 2025-01-01
LocalDate lastOfYear = date.with(TemporalAdjusters.lastDayOfYear()); // 2025-12-31
// Следующий/предыдущий день недели
LocalDate nextMonday = date.with(TemporalAdjusters.next(DayOfWeek.MONDAY)); // 2025-01-27
LocalDate prevFriday = date.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY)); // 2025-01-24
// Первый конкретный день недели в месяце
LocalDate firstMondayOfMonth = date.with(
TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)
); // 2025-01-06
// Последний конкретный день недели в месяце
LocalDate lastFridayOfMonth = date.with(
TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY)
); // 2025-01-31
Конвертация из java.util.Date
При работе с legacy-кодом часто требуется конвертация между старым java.util.Date и новыми классами.
Date → новые классы
import java.util.Date;
import java.time.*;
Date legacyDate = new Date();
// Date -> Instant
Instant instant = legacyDate.toInstant();
// Date -> LocalDate
LocalDate localDate = legacyDate.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDate();
// Date -> LocalDateTime
LocalDateTime localDateTime = legacyDate.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime();
// Date -> ZonedDateTime
ZonedDateTime zonedDateTime = legacyDate.toInstant()
.atZone(ZoneId.systemDefault());
Новые классы → Date
// Instant -> Date
Instant instant = Instant.now();
Date fromInstant = Date.from(instant);
// LocalDate -> Date
LocalDate localDate = LocalDate.now();
Date fromLocalDate = Date.from(
localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()
);
// LocalDateTime -> Date
LocalDateTime localDateTime = LocalDateTime.now();
Date fromLocalDateTime = Date.from(
localDateTime.atZone(ZoneId.systemDefault()).toInstant()
);
// ZonedDateTime -> Date
ZonedDateTime zonedDateTime = ZonedDateTime.now();
Date fromZoned = Date.from(zonedDateTime.toInstant());
Пример: планировщик событий
Рассмотрим практический пример — расчёт времени прибытия авиарейса:
import java.time.*;
import java.time.format.DateTimeFormatter;
public class FlightScheduler {
public static void main(String[] args) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d MMM yyyy HH:mm z");
// Вылет из Москвы 26 января в 14:30
LocalDateTime departureLocal = LocalDateTime.of(2025, 1, 26, 14, 30);
ZoneId moscowZone = ZoneId.of("Europe/Moscow");
ZonedDateTime departure = ZonedDateTime.of(departureLocal, moscowZone);
System.out.println("Вылет: " + departure.format(formatter));
// Вылет: 26 янв. 2025 14:30 MSK
// Время полёта: 9 часов 45 минут
Duration flightDuration = Duration.ofHours(9).plusMinutes(45);
// Прибытие в Токио
ZoneId tokyoZone = ZoneId.of("Asia/Tokyo");
ZonedDateTime arrival = departure
.plus(flightDuration)
.withZoneSameInstant(tokyoZone);
System.out.println("Прибытие: " + arrival.format(formatter));
// Прибытие: 27 янв. 2025 06:15 JST
// Местное время в Москве при прибытии
ZonedDateTime arrivalMoscowTime = arrival.withZoneSameInstant(moscowZone);
System.out.println("По Москве: " + arrivalMoscowTime.format(formatter));
// По Москве: 27 янв. 2025 00:15 MSK
}
}
Рекомендации по выбору класса
Нужно хранить момент времени (timestamp)?
├── Да → Instant
└── Нет, нужна читаемая дата/время
├── Нужен часовой пояс?
│ ├── Да, с учётом летнего времени → ZonedDateTime
│ └── Да, только смещение → OffsetDateTime
└── Нет, достаточно локального значения
├── Только дата → LocalDate
├── Только время → LocalTime
└── Дата и время → LocalDateTime
Резюме
Пакет java.time предоставляет мощный и выразительный API для работы с датами и временем:
- Используйте
LocalDate,LocalTime,LocalDateTimeдля дат/времени без часовых поясов - Используйте
Instantдля машинных timestamps - Используйте
ZonedDateTimeдля полной поддержки часовых поясов с правилами перехода времени - Используйте
OffsetDateTimeдля сериализации и хранения в базах данных - Используйте
Durationдля временных интервалов иPeriodдля календарных - Используйте
DateTimeFormatterдля форматирования и парсинга - Все классы неизменяемы и потокобезопасны