Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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Дата и время со смещением от UTC2025-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
aAM/PMPM
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 для форматирования и парсинга
  • Все классы неизменяемы и потокобезопасны