Дата и время (java.time)
Работа с датами и временем — одна из самых частых задач в реальных приложениях. Логирование событий, расчёт сроков, работа с календарём, планировщики задач, анализ данных — всё это требует точного и удобного API для работы с датой и временем. В этом уроке мы разберём современный пакет java.time (Java 8+), который заменил старые Date и Calendar.
🕰️ Почему java.time?
До Java 8 для работы с датами использовались классы java.util.Date, java.util.Calendar и java.text.SimpleDateFormat. У них было множество проблем:
Date можно изменить после создания (setTime(), setYear()). Это приводит к багам в многопоточном коде. java.time классы — все неизменяемые и потокобезопасные.
В Calendar январь — это месяц 0, декабрь — 11. Легко ошибиться на единицу. В java.time месяцы — честные: январь — Month.JANUARY или значение 1.
SimpleDateFormat нельзя использовать из нескольких потоков без синхронизации. DateTimeFormatter — потокобезопасен, его можно сохранить в константу.
Date представляет и дату, и время одновременно. Нельзя сказать «мне нужна только дата без времени». java.time разделяет: LocalDate, LocalTime, LocalDateTime.
Разработчикам java.time вдохновлялись библиотекой Joda-Time (которая теперь практически не нужна — всё лучшее перешло в стандартную библиотеку). Автор Joda-Time, Стивен Коулборн, участвовал в разработке java.time.
💡 Ключевая идея: java.time разделяет концепции «дата», «время», «дата+время», «момент во времени» на разные классы. Каждый класс отвечает за свою область и делает это хорошо. Никаких больше универсальных Date, которые пытаются быть всем сразу.
📅 LocalDate — только дата
LocalDate представляет дату без времени и часового пояса: год, месяц, день. Используется для дней рождения, праздников, сроков, расписаний — всего, где время не важно.
import java.time.LocalDate;
import java.time.Month;
import java.time.DayOfWeek;
public class LocalDateExample {
public static void main(String[] args) {
// Текущая дата
LocalDate today = LocalDate.now();
System.out.println("Сегодня: " + today); // 2024-12-25
// Создание конкретной даты
LocalDate newYear = LocalDate.of(2025, Month.JANUARY, 1);
LocalDate mayDay = LocalDate.of(2025, 5, 1); // Month можно числом
System.out.println("Новый год: " + newYear);
// Парсинг из строки (ISO-8601: yyyy-MM-dd)
LocalDate parsed = LocalDate.parse("2025-06-15");
System.out.println("Спарсено: " + parsed);
// Получение компонентов
int year = today.getYear();
int month = today.getMonthValue(); // 1-12 (не 0-11!)
Month monthEnum = today.getMonth(); // Month.DECEMBER
int day = today.getDayOfMonth();
DayOfWeek weekday = today.getDayOfWeek(); // DayOfWeek.WEDNESDAY
int dayOfYear = today.getDayOfYear();
System.out.printf("Год: %d, Месяц: %d, День: %d, %s (день %d в году)%n",
year, month, day, weekday, dayOfYear);
}
}
📌 Манипуляции с датами (plus/minus)
LocalDate, как и все классы java.time, неизменяем. Методы plusDays, minusMonths и т.д. возвращают новые объекты, не меняя исходный.
public class DateManipulation {
public static void main(String[] args) {
LocalDate today = LocalDate.now();
// Добавление/вычитание дней, месяцев, лет
LocalDate tomorrow = today.plusDays(1);
LocalDate nextWeek = today.plusWeeks(1);
LocalDate nextMonth = today.plusMonths(1);
LocalDate nextYear = today.plusYears(1);
LocalDate lastWeek = today.minusWeeks(1);
LocalDate lastYear = today.minusYears(5);
// Специальные методы для настройки даты
LocalDate firstDayOfMonth = today.withDayOfMonth(1);
LocalDate lastDayOfMonth = today.withDayOfMonth(
today.lengthOfMonth() // количество дней в текущем месяце
);
LocalDate firstDayOfYear = today.withDayOfYear(1);
// with(TemporalAdjuster) — мощный механизм
LocalDate nextMonday = today.with(
java.time.temporal.TemporalAdjusters.next(DayOfWeek.MONDAY)
);
LocalDate firstOfNextMonth = today.with(
java.time.temporal.TemporalAdjusters.firstDayOfNextMonth()
);
System.out.println("Сегодня: " + today);
System.out.println("Завтра: " + tomorrow);
System.out.println("Следующий понедельник: " + nextMonday);
System.out.println("Дней в этом месяце: " + today.lengthOfMonth());
System.out.println("Високосный? " + today.isLeapYear());
}
}
📌 Сравнение дат
public class DateComparison {
public static void main(String[] args) {
LocalDate d1 = LocalDate.of(2025, 6, 15);
LocalDate d2 = LocalDate.of(2025, 12, 25);
LocalDate d3 = LocalDate.of(2025, 6, 15);
// Методы сравнения
System.out.println(d1.isBefore(d2)); // true
System.out.println(d2.isAfter(d1)); // true
System.out.println(d1.isEqual(d3)); // true
// Через compareTo (Comparable)
System.out.println(d1.compareTo(d2)); // отрицательное (d1 раньше)
System.out.println(d2.compareTo(d1)); // положительное
System.out.println(d1.compareTo(d3)); // 0 — равны
// equals
System.out.println(d1.equals(d3)); // true
}
}
💡 Аналогия: LocalDate — это как отрывной календарь. Вы можете посмотреть сегодняшнюю дату, оторвать листок (получить завтра), вернуться на неделю назад. Но сам календарь не меняется — каждый раз вы получаете новый листок.
⏰ LocalTime — только время
LocalTime представляет время без даты и часового пояса: часы, минуты, секунды, наносекунды. Используется для времени открытия магазина, начала урока, таймера.
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
public class LocalTimeExample {
public static void main(String[] args) {
// Текущее время
LocalTime now = LocalTime.now();
System.out.println("Сейчас: " + now); // 14:30:25.123456789
// Создание времени
LocalTime noon = LocalTime.of(12, 0); // 12:00
LocalTime midnight = LocalTime.of(0, 0, 0); // 00:00:00
LocalTime precise = LocalTime.of(10, 30, 45, 500_000_000); // 10:30:45.500
// Парсинг
LocalTime parsed = LocalTime.parse("15:45:30");
System.out.println("Парс: " + parsed); // 15:45:30
// Получение компонентов
int hour = now.getHour();
int minute = now.getMinute();
int second = now.getSecond();
int nano = now.getNano();
// Манипуляции
LocalTime inTwoHours = now.plusHours(2);
LocalTime thirtyMinAgo = now.minusMinutes(30);
LocalTime roundedToHour = now.truncatedTo(ChronoUnit.HOURS);
// Сравнение
System.out.println("Сейчас до полудня? " + now.isBefore(noon));
System.out.println("Сейчас после полудня? " + now.isAfter(noon));
}
}
💡 Важно: LocalTime может хранить время с точностью до наносекунд (9 знаков после запятой). Но не все часы в реальном мире так точны — системные часы обычно имеют точность до микросекунд или миллисекунд. ChronoUnit — перечисление единиц времени: HOURS, MINUTES, SECONDS, MILLIS, MICROS, NANOS и другие.
📆 LocalDateTime — дата и время вместе
LocalDateTime объединяет LocalDate и LocalTime. Это самый часто используемый класс для хранения момента во времени «без привязки к часовому поясу».
import java.time.LocalDateTime;
import java.time.LocalDate;
import java.time.LocalTime;
public class LocalDateTimeExample {
public static void main(String[] args) {
// Текущие дата и время
LocalDateTime now = LocalDateTime.now();
System.out.println("Сейчас: " + now);
// Из отдельных компонентов
LocalDateTime dt1 = LocalDateTime.of(2025, 6, 15, 14, 30);
LocalDateTime dt2 = LocalDateTime.of(2025, 12, 25, 10, 0, 0);
LocalDateTime dt3 = LocalDateTime.of(
LocalDate.of(2025, 1, 1),
LocalTime.of(0, 0, 0)
);
// Парсинг
LocalDateTime parsed = LocalDateTime.parse("2025-06-15T14:30:00");
System.out.println("Парс: " + parsed);
// Разделение на дату и время
LocalDate datePart = parsed.toLocalDate();
LocalTime timePart = parsed.toLocalTime();
System.out.println("Дата: " + datePart + ", Время: " + timePart);
// Манипуляции
LocalDateTime nextHour = now.plusHours(1);
LocalDateTime nextWeek = now.plusWeeks(1);
LocalDateTime startOfDay = now.toLocalDate().atStartOfDay(); // 00:00
}
}
⚠️ Важно понимать: LocalDateTime не хранит информацию о часовом поясе. Если сейчас 14:30 в Москве и 14:30 во Владивостоке — это разные моменты времени, но LocalDateTime не сможет их различить. Для работы с часовыми поясами есть ZonedDateTime и OffsetDateTime.
🎨 DateTimeFormatter — форматирование и парсинг
DateTimeFormatter — потокобезопасный класс для форматирования дат в строки и парсинга строк в даты. Его можно сохранять в статические константы и использовать из разных потоков без синхронизации.
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Locale;
public class DateTimeFormatterExample {
public static void main(String[] args) {
LocalDateTime now = LocalDateTime.now();
// Предопределённые форматеры
System.out.println("ISO_LOCAL_DATE: " + now.format(DateTimeFormatter.ISO_LOCAL_DATE));
System.out.println("ISO_LOCAL_TIME: " + now.format(DateTimeFormatter.ISO_LOCAL_TIME));
System.out.println("ISO_LOCAL_DATE_TIME: " + now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
// ISO: 2025-06-15T14:30:00
// Форматы с локалью
System.out.println("SHORT: " + now.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)));
System.out.println("MEDIUM: " + now.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)));
System.out.println("LONG: " + now.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG)));
System.out.println("FULL: " + now.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL)));
// Примерно: 15.06.2025, 15 июн. 2025 г., 15 июня 2025 г.
// Свой шаблон
DateTimeFormatter custom = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss");
System.out.println("Кастом: " + now.format(custom)); // 15.06.2025 14:30:00
DateTimeFormatter russian = DateTimeFormatter.ofPattern(
"EEEE, d MMMM yyyy 'г.' HH:mm", new Locale("ru")
);
System.out.println("По-русски: " + now.format(russian));
// воскресенье, 15 июня 2025 г. 14:30
// Парсинг строки обратно в дату
String dateStr = "25.12.2025";
DateTimeFormatter parser = DateTimeFormatter.ofPattern("dd.MM.yyyy");
LocalDate parsed = LocalDate.parse(dateStr, parser);
System.out.println("Спарсено: " + parsed); // 2025-12-25
// Безопасность: если строка не соответствует формату — DateTimeParseException
try {
LocalDate.parse("32.13.2025", parser);
} catch (java.time.format.DateTimeParseException e) {
System.out.println("Ошибка парсинга: " + e.getMessage());
}
}
}
| Символ | Значение | Пример |
|---|---|---|
| y | год | 2025 |
| M | месяц (число или текст) | 6, 06, Jun, June |
| d | день месяца | 15, 05 |
| E | день недели (текст) | пн, понедельник |
| H | час (0-23) | 14 |
| h | час (1-12 AM/PM) | 2 |
| m | минута | 30 |
| s | секунда | 45 |
| a | AM/PM маркер | PM |
⏳ Duration и Period — разница во времени
Для вычисления разницы между датами/временем есть два класса: Duration (для времени — часы, минуты, секунды, наносекунды) и Period (для дат — годы, месяцы, дни).
import java.time.*;
import java.time.temporal.ChronoUnit;
public class DurationPeriodExample {
public static void main(String[] args) {
// Duration — разница во времени (наносекунды)
LocalTime start = LocalTime.of(9, 0);
LocalTime end = LocalTime.of(17, 30);
Duration workDay = Duration.between(start, end);
System.out.println("Рабочий день:");
System.out.println(" часов: " + workDay.toHours()); // 8
System.out.println(" минут: " + workDay.toMinutes()); // 510
System.out.println(" секунд: " + workDay.toSeconds()); // 30600
System.out.println(" в ISO: " + workDay); // PT8H30M
// Duration из чисел
Duration twoHours = Duration.ofHours(2);
Duration thirtyMin = Duration.ofMinutes(30);
Duration oneDay = Duration.of(1, ChronoUnit.DAYS);
// Period — разница между датами
LocalDate startDate = LocalDate.of(2020, 1, 1);
LocalDate endDate = LocalDate.of(2025, 6, 15);
Period period = Period.between(startDate, endDate);
System.out.println("Период:");
System.out.println(" лет: " + period.getYears()); // 5
System.out.println(" месяцев: " + period.getMonths()); // 5
System.out.println(" дней: " + period.getDays()); // 14
System.out.println(" всего месяцев: " + period.toTotalMonths()); // 65
System.out.println(" в ISO: " + period); // P5Y5M14D
// ChronoUnit.between — общее количество единиц
long daysBetween = ChronoUnit.DAYS.between(startDate, endDate);
long monthsBetween = ChronoUnit.MONTHS.between(startDate, endDate);
long weeksBetween = ChronoUnit.WEEKS.between(startDate, endDate);
System.out.println("Между датами:");
System.out.println(" дней: " + daysBetween);
System.out.println(" месяцев: " + monthsBetween);
System.out.println(" недель: " + weeksBetween);
}
}
💡 Разница между Duration и Period простыми словами: Duration измеряет время в секундах/наносекундах — это как секундомер. Period измеряет в днях/месяцах/годах — это как календарь. Duration может сказать, сколько секунд между 9:00 и 17:00. Period скажет, сколько лет и месяцев между 1 января 2020 и 15 июня 2025. ChronoUnit.between — универсальный метод, который работает и с датами, и с временем, возвращая количество в указанных единицах.
🌍 ZonedDateTime — дата и время с часовым поясом
Когда нужно хранить момент во времени с учётом часового пояса (например, время встречи для международных участников), используйте ZonedDateTime или OffsetDateTime. ZoneId представляет часовой пояс.
import java.time.*;
public class ZonedDateTimeExample {
public static void main(String[] args) {
// Все доступные ID часовых поясов
// System.out.println(ZoneId.getAvailableZoneIds()); // ~600 поясов
// Текущее время в конкретном поясе
ZonedDateTime moscowNow = ZonedDateTime.now(ZoneId.of("Europe/Moscow"));
ZonedDateTime newYorkNow = ZonedDateTime.now(ZoneId.of("America/New_York"));
ZonedDateTime tokyoNow = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
System.out.println("Москва: " + moscowNow);
System.out.println("Нью-Йорк: " + newYorkNow);
System.out.println("Токио: " + tokyoNow);
// Конвертация между поясами
ZonedDateTime meetingInMoscow = ZonedDateTime.of(
LocalDate.of(2025, 6, 20),
LocalTime.of(15, 0),
ZoneId.of("Europe/Moscow")
);
ZonedDateTime meetingInNY = meetingInMoscow.withZoneSameInstant(
ZoneId.of("America/New_York")
);
System.out.println("Встреча в Москве: " + meetingInMoscow);
System.out.println("Встреча в Нью-Йорке: " + meetingInNY);
// Москва 15:00 -> Нью-Йорк 08:00 (летом разница -7 часов)
// Instant — момент в UTC (машинное время)
Instant now = Instant.now();
System.out.println("Instant (UTC): " + now);
// Instant — это количество секунд/наносекунд с 1970-01-01T00:00:00Z
// Instant в LocalDateTime с указанием пояса
LocalDateTime moscowTime = LocalDateTime.ofInstant(
now, ZoneId.of("Europe/Moscow")
);
System.out.println("Instant как Московское: " + moscowTime);
}
}
🔑 Правильный выбор класса: Если вам нужно записать «когда это произошло» в глобальном смысле — используйте Instant (UTC). Если нужно локальное время события (например, «концерт в 19:00 по местному времени») — используйте LocalDateTime в паре с ZoneId. ZonedDateTime сочетает и то, и другое, но тяжелее.
⚠️ Старый Date и Calendar (НЕ РЕКОМЕНДУЕТСЯ)
Вы обязательно встретите старый код с java.util.Date и java.util.Calendar. Нужно уметь их читать и конвертировать в java.time. Но писать новый код с ними — плохая практика.
import java.util.Date;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.time.*;
public class OldApiDemo {
public static void main(String[] args) {
// Date — устаревший, но всё ещё часто встречается
Date now = new Date(); // текущий момент
Date specific = new Date(125, 5, 15); // Deprecated! 2025-06-15
// Годы: 1900-based, месяцы: 0-based — постоянные ошибки!
// Calendar — немного лучше, но тоже не подарок
Calendar cal = Calendar.getInstance();
cal.set(2025, Calendar.JUNE, 15, 14, 30, 0);
Date meeting = cal.getTime();
System.out.println("Calendar: " + meeting);
// Конвертация старого API в java.time
// Date -> Instant
Instant instant = now.toInstant();
System.out.println("Instant: " + instant);
// Instant -> LocalDateTime (с указанием пояса)
LocalDateTime ldt = instant.atZone(ZoneId.systemDefault()).toLocalDateTime();
System.out.println("LocalDateTime: " + ldt);
// Calendar -> ZonedDateTime
ZonedDateTime zdt = ((GregorianCalendar) cal).toZonedDateTime();
System.out.println("ZonedDateTime: " + zdt);
// java.time -> Date
Instant backToInstant = ldt.atZone(ZoneId.systemDefault()).toInstant();
Date backToDate = Date.from(backToInstant);
System.out.println("Date обратно: " + backToDate);
}
}
🚫 Почему новый код нельзя писать на Date/Calendar? (1) Месяцы с 0 — январь = 0, декабрь = 11. (2) Мутабельность — Date и Calendar можно изменить после создания. (3) Отсутствует ясность — Date — это и дата, и время. (4) SimpleDateFormat не потокобезопасен. (5) Нет поддержки Period, Duration, TemporalAdjusters. Используйте java.time — это современно, безопасно и удобно.
🏋️ Практические примеры
📌 Пример 1: Возраст по дате рождения
import java.time.*;
import java.time.temporal.ChronoUnit;
public class AgeCalculator {
public static int calculateAge(LocalDate birthDate) {
LocalDate today = LocalDate.now();
return (int) ChronoUnit.YEARS.between(birthDate, today);
}
public static String detailedAge(LocalDate birthDate) {
LocalDate today = LocalDate.now();
Period period = Period.between(birthDate, today);
return String.format("%d лет, %d месяцев, %d дней",
period.getYears(), period.getMonths(), period.getDays());
}
public static void main(String[] args) {
LocalDate birth = LocalDate.of(1990, 5, 15);
System.out.println("Возраст: " + calculateAge(birth) + " лет");
System.out.println("Детально: " + detailedAge(birth));
}
}
📌 Пример 2: Количество рабочих дней между датами
import java.time.*;
import java.time.temporal.ChronoUnit;
public class WorkingDaysCalculator {
public static long countWorkingDays(LocalDate start, LocalDate end) {
long workingDays = 0;
LocalDate current = start;
while (!current.isAfter(end)) {
DayOfWeek day = current.getDayOfWeek();
if (day != DayOfWeek.SATURDAY && day != DayOfWeek.SUNDAY) {
workingDays++;
}
current = current.plusDays(1);
}
return workingDays;
}
public static void main(String[] args) {
LocalDate start = LocalDate.of(2025, 6, 1);
LocalDate end = LocalDate.of(2025, 6, 30);
System.out.println("Рабочих дней в июне: " + countWorkingDays(start, end));
}
}
📌 Пример 3: Планировщик напоминаний
import java.time.*;
import java.time.temporal.TemporalAdjusters;
public class ReminderScheduler {
public static void printReminders() {
LocalDate today = LocalDate.now();
System.out.println("=== Напоминания ===");
System.out.println("Сегодня: " + today);
// Последний день месяца — оплата счетов
LocalDate payDay = today.with(
TemporalAdjusters.lastDayOfMonth()
);
System.out.println("💳 Оплата счетов: " + payDay);
// Следующий понедельник — встреча
LocalDate nextMonday = today.with(
TemporalAdjusters.next(DayOfWeek.MONDAY)
);
System.out.println("📅 Встреча: " + nextMonday);
// Первое число следующего месяца — отчёт
LocalDate reportDay = today.with(
TemporalAdjusters.firstDayOfNextMonth()
);
System.out.println("📊 Отчёт: " + reportDay);
// Разница в днях
long daysToReport = ChronoUnit.DAYS.between(today, reportDay);
System.out.println("До отчёта: " + daysToReport + " дней");
}
public static void main(String[] args) {
printReminders();
}
}
📌 Пример 4: Логирование с временными метками
import java.time.*;
import java.time.format.DateTimeFormatter;
public class Logger {
private static final DateTimeFormatter FORMATTER =
DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss.SSS");
public static void log(String level, String message) {
String timestamp = LocalDateTime.now().format(FORMATTER);
System.out.printf("[%s] %s: %s%n", timestamp, level, message);
}
public static void main(String[] args) {
log("INFO", "Приложение запущено");
log("WARN", "Использование большого количества памяти");
log("ERROR", "Ошибка подключения к базе данных");
}
}
✅ Итоги урока
- java.time — современный API для работы с датой и временем (Java 8+). Неизменяемые, потокобезопасные классы
- LocalDate — дата без времени (год, месяц, день). Методы: now, of, parse, plusDays, minusMonths, withDayOfMonth, isBefore, isLeapYear
- LocalTime — время без даты (часы, минуты, секунды, наносекунды). Методы: now, of, parse, plusHours, minusMinutes, isAfter
- LocalDateTime — дата и время вместе. Создаётся через LocalDateTime.of(date, time) или LocalDateTime.now()
- DateTimeFormatter — потокобезопасный форматтер. Предопределённые (ISO_LOCAL_DATE) и кастомные шаблоны (ofPattern)
- Duration — разница во времени (часы, минуты, секунды). between(start, end), toHours, toMinutes
- Period — разница между датами (годы, месяцы, дни). between(start, end), getYears, getMonths, toTotalMonths
- ChronoUnit — универсальные единицы: DAYS.between, MONTHS.between, WEEKS.between
- ZonedDateTime — дата+время+часовой пояс. ZoneId, withZoneSameInstant
- Instant — момент в UTC (машинное время). Незаменим для хранения меток времени в базах данных
- Старые Date/Calendar — не используйте в новом коде. При необходимости конвертируйте через toInstant(), Date.from()
В следующем уроке разберём работу с файлами — чтение, запись, создание и удаление через java.nio.file. 🚀
Тест: Дата и время (java.time)
10 вопросов