1. Исторический контекст: как работали с датами до Java 8
В далёкой древности (до Java 8) для работы с датами и временем использовали классы java.util.Date, java.util.Calendar и для форматирования — java.text.SimpleDateFormat. Это был тот случай, когда программисты всей планеты дружно вздыхали и, скрипя зубами, писали что-то вроде:
import java.util.Date;
Date now = new Date();
System.out.println(now); // Выведет что-то странное вроде "Wed Jun 05 14:15:22 MSK 2025"
Звучит просто, но на деле всё было не так радужно. Вот лишь некоторые "радости" старого API:
- Date — изменяемый (mutable) объект. Его можно было случайно поменять, и это часто приводило к багам.
- Месяцы в Date и Calendar начинались с нуля (январь — это 0, декабрь — 11), а дни — с единицы.
- SimpleDateFormat не был потокобезопасным: если два потока форматировали дату одновременно, можно было получить неожиданные результаты.
- Огромное количество методов было объявлено как @Deprecated (устаревшие), и IDE постоянно пугала вас жёлтыми предупреждениями.
- Работа с часовыми поясами была настоящей болью: легко было перепутать локальное время и UTC, а про переход на летнее/зимнее время лучше вообще не вспоминать.
Пример боли
import java.util.Date;
Date date = new Date(2025, 5, 1); // 2025 год, 5 месяц (июнь?), 1 число
System.out.println(date); // Не то, что ожидаешь!
2. Появление java.time: новый подход
К 2014 году стало понятно: старый API не просто неудобен — он опасен. Поэтому в Java появился новый пакет — java.time, реализующий спецификацию JSR‑310. Этот API был вдохновлён популярной библиотекой Joda-Time и сразу стал стандартом де‑факто.
Основные пакеты и классы
- java.time — основной пакет, где живут все новые классы для дат и времени.
- java.time.format — для форматирования и парсинга дат и времени.
- java.time.temporal — для более продвинутых временных операций.
- java.time.zone — для работы с часовыми поясами.
Вот главные герои нового API:
| Класс | Для чего? | Пример использования |
|---|---|---|
|
Только дата (год, месяц, день) | День рождения, без времени |
|
Только время (часы, минуты, секунды) | Время встречи, без даты |
|
Дата и время, без часового пояса | Локальное событие |
|
Дата и время с учётом таймзоны | Встреча в Минске по местному времени |
|
Абсолютная точка времени (UTC) | Метка события в журнале |
|
Промежуток времени (часы, минуты, сек.) | Длительность звонка |
|
Период (годы, месяцы, дни) | Стаж работы, возраст |
Пример: создание даты по‑новому
import java.time.LocalDate;
LocalDate today = LocalDate.now();
System.out.println(today); // Например, "2025-06-05"
3. Преимущества нового API
Неизменяемость (immutable)
Все классы из java.time — неизменяемые. Это значит: если вы создали объект LocalDate, изменить его нельзя. Любая операция (например, добавить день) возвращает новый объект.
LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plusDays(1);
System.out.println(today); // 2025-06-05
System.out.println(tomorrow); // 2025-06-06
Явная работа с часовыми поясами
В старом API легко было забыть, в каком часовом поясе находится дата. В java.time всё явно: если нужен часовой пояс — используйте ZonedDateTime, если не нужен — используйте LocalDateTime.
import java.time.ZonedDateTime;
import java.time.ZoneId;
ZonedDateTime MinskTime = ZonedDateTime.now(ZoneId.of("Europe/Minsk"));
System.out.println(MinskTime); // 2025-06-05T14:23:45+03:00[Europe/Minsk]
Удобные методы для вычислений и сравнения
LocalDate today = LocalDate.now();
LocalDate nextMonth = today.plusMonths(1);
boolean isAfter = LocalDate.now().plusDays(1).isAfter(today); // true
Форматирование и парсинг
Форматирование и парсинг — через DateTimeFormatter (подробно — на следующей лекции):
import java.time.format.DateTimeFormatter;
LocalDate today = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
String formatted = today.format(formatter); // "05.08.2025"
4. Совместимость: как жить со старым кодом
В реальном мире очень часто приходится работать с библиотеками или старыми системами, где используются Date и Calendar. К счастью, новый API дружелюбен к наследию: можно конвертировать старые типы в новые и наоборот.
Преобразование Date ↔ Instant
import java.util.Date;
import java.time.Instant;
// Date → Instant
Date legacyDate = new Date();
Instant instant = legacyDate.toInstant();
// Instant → Date
Date dateBack = Date.from(instant);
Преобразование Calendar ↔ ZonedDateTime
import java.util.Calendar;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.util.Date;
// Calendar → ZonedDateTime
Calendar calendar = Calendar.getInstance();
ZonedDateTime zdt = ZonedDateTime.ofInstant(
calendar.toInstant(),
calendar.getTimeZone().toZoneId()
);
// ZonedDateTime → Calendar
Calendar calBack = Calendar.getInstance();
calBack.setTime(Date.from(zdt.toInstant()));
Таблица: сопоставление старых и новых классов
| Старый класс | Новый класс | Комментарий |
|---|---|---|
|
|
Абсолютное время |
|
|
Дата и время с таймзоной |
|
|
Форматирование/парсинг дат |
5. Практика: первые шаги с java.time
Допустим, у вас есть пользователь с датой рождения. Сохраним и выведем эту дату:
import java.time.LocalDate;
public class UserProfile {
private String name;
private LocalDate birthDate;
public UserProfile(String name, LocalDate birthDate) {
this.name = name;
this.birthDate = birthDate;
}
public void printProfile() {
System.out.println("Имя: " + name);
System.out.println("Дата рождения: " + birthDate);
}
}
public class Main {
public static void main(String[] args) {
UserProfile user = new UserProfile("Алиса", LocalDate.of(1998, 12, 25));
user.printProfile();
}
}
Вывод:
Имя: Алиса
Дата рождения: 1998-12-25
6. Сравнение: старый API vs новый API
Пример: добавить неделю к дате рождения
Старый способ (Date/Calendar):
import java.util.Calendar;
Calendar cal = Calendar.getInstance();
cal.set(1998, Calendar.DECEMBER, 25);
cal.add(Calendar.WEEK_OF_YEAR, 1);
System.out.println(cal.getTime()); // Муторно и неочевидно
Новый способ (java.time):
import java.time.LocalDate;
LocalDate birthDate = LocalDate.of(1998, 12, 25);
LocalDate nextWeek = birthDate.plusWeeks(1);
System.out.println(nextWeek); // 1999-01-01
В новом API код короче, проще и безопаснее.
7. Типичные ошибки при работе с java.time
Ошибка №1: забыли, что объекты неизменяемы.
Если вызвать date.plusDays(1); и не сохранить результат, исходная дата останется прежней.
Ошибка №2: путаница между LocalDate и LocalDateTime.
LocalDate хранит только дату (год, месяц, день), а LocalDateTime — ещё и время. Не перепутайте, если нужно обработать и часы, и минуты.
Ошибка №3: использование старых классов в новых проектах.
Если есть возможность — всегда используйте java.time. Старые классы нужны только для совместимости.
Ошибка №4: неправильная работа с часовыми поясами.
Если нужно хранить событие, важное для разных регионов, используйте ZonedDateTime или хотя бы Instant. LocalDateTime не содержит информации о временной зоне!
Ошибка №5: попытка напрямую сравнивать LocalDate и LocalDateTime.
Это разные типы данных, сравнивать их напрямую нельзя. Сначала приведите к одному типу.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ