1. Обзор
В этом руководстве вы узнаете, как реорганизовать свой код, чтобы использовать новый API Date Time, представленный в Java 8.
2. Краткий обзор нового API
Раньше работать с датами в Java было сложно. Старая библиотека дат, предоставляемая JDK, включала только три класса: java.util.Date, java.util.Calendar
и java.util.Timezone
.
Они подходили только для самых основных задач. Для чего-то даже отдаленно сложного разработчикам приходилось либо использовать сторонние библиотеки, либо писать тонны пользовательского кода.
В Java 8 представлен совершенно новый API даты и времени ( java.util.time.*
), который частично основан на популярной библиотеке Java под названием JodaTime. Этот новый API значительно упростил обработку даты и времени и устранил многие недостатки старой библиотеки дат.
1.1. Ясность API
Первым преимуществом нового API является ясность — API очень четкий, лаконичный и простой для понимания. В ней нет многих несоответствий, найденных в старой библиотеке, таких как нумерация полей (в календаре месяцы отсчитываются от нуля, а дни недели отсчитываются от единицы).
1.2. Гибкость API
Еще одним преимуществом является гибкость — работа с несколькими представлениями времени . Старая библиотека дат включала только один класс представления времени — java.util.Date
, который, несмотря на свое название, на самом деле является отметкой времени. Он хранит только количество миллисекунд, прошедших с эпохи Unix.
Новый API имеет множество различных представлений времени, каждое из которых подходит для разных вариантов использования:
Мгновенно
— представляет момент времени (отметка времени)LocalDate
— представляет дату (год, месяц, день)LocalDateTime
— то же, что иLocalDate
, но включает время с точностью до наносекунды .OffsetDateTime
— то же, что иLocalDateTime
, но со смещением часового пояса .LocalTime
— время с точностью до наносекунды и без информации о дате .ZonedDateTime
— то же, что иOffsetDateTime
, но включает идентификатор часового пояса.OffsetLocalTime
— то же, что иLocalTime
, но со смещением часового пояса .MonthDay
– месяц и день, без года и времениYearMonth
– месяц и год, без дня и времениПродолжительность
– количество времени, представленное в секундах, минутах и часах. Имеет наносекундную точностьПериод
– количество времени, представленное в днях, месяцах и годах.
1.3. Неизменяемость и потокобезопасность
Еще одно преимущество заключается в том, что все представления времени в Java 8 Date Time API являются неизменяемыми и, следовательно, потокобезопасными.
Все изменяющие методы возвращают новую копию вместо изменения состояния исходного объекта.
Старые классы, такие как java.util.Date
, не были потокобезопасными и могли привести к очень тонким ошибкам параллелизма.
1.4. Цепочка методов
Все мутирующие методы могут быть объединены в цепочку, что позволяет реализовать сложные преобразования в одной строке кода.
ZonedDateTime nextFriday = LocalDateTime.now()
.plusHours(1)
.with(TemporalAdjusters.next(DayOfWeek.FRIDAY))
.atZone(ZoneId.of("PST"));
2. Примеры
В приведенных ниже примерах показано, как выполнять общие задачи как со старым, так и с новым API.
Получение текущего времени
// Old
Date now = new Date();
// New
ZonedDateTime now = ZonedDateTime.now();
Представляя определенное время
// Old
Date birthDay = new GregorianCalendar(1990, Calendar.DECEMBER, 15).getTime();
// New
LocalDate birthDay = LocalDate.of(1990, Month.DECEMBER, 15);
Извлечение определенных полей
// Old
int month = new GregorianCalendar().get(Calendar.MONTH);
// New
Month month = LocalDateTime.now().getMonth();
Добавление и вычитание времени
// Old
GregorianCalendar calendar = new GregorianCalendar();
calendar.add(Calendar.HOUR_OF_DAY, -5);
Date fiveHoursBefore = calendar.getTime();
// New
LocalDateTime fiveHoursBefore = LocalDateTime.now().minusHours(5);
Изменение определенных полей
// Old
GregorianCalendar calendar = new GregorianCalendar();
calendar.set(Calendar.MONTH, Calendar.JUNE);
Date inJune = calendar.getTime();
// New
LocalDateTime inJune = LocalDateTime.now().withMonth(Month.JUNE.getValue());
Усечение
Усечение сбрасывает все поля времени меньше указанного поля. В приведенном ниже примере минуты и все, что ниже, будет установлено на ноль.
// Old
Calendar now = Calendar.getInstance();
now.set(Calendar.MINUTE, 0);
now.set(Calendar.SECOND, 0);
now.set(Calendar.MILLISECOND, 0);
Date truncated = now.getTime();
// New
LocalTime truncated = LocalTime.now().truncatedTo(ChronoUnit.HOURS);
Преобразование часового пояса
// Old
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTimeZone(TimeZone.getTimeZone("CET"));
Date centralEastern = calendar.getTime();
// New
ZonedDateTime centralEastern = LocalDateTime.now().atZone(ZoneId.of("CET"));
Получение промежутка времени между двумя моментами времени
// Old
GregorianCalendar calendar = new GregorianCalendar();
Date now = new Date();
calendar.add(Calendar.HOUR, 1);
Date hourLater = calendar.getTime();
long elapsed = hourLater.getTime() - now.getTime();
// New
LocalDateTime now = LocalDateTime.now();
LocalDateTime hourLater = LocalDateTime.now().plusHours(1);
Duration span = Duration.between(now, hourLater);
Форматирование и разбор времени
DateTimeFormatter — это замена старого SimpleDateFormat, который является потокобезопасным и предоставляет дополнительные функции.
// Old
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date now = new Date();
String formattedDate = dateFormat.format(now);
Date parsedDate = dateFormat.parse(formattedDate);
// New
LocalDate now = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String formattedDate = now.format(formatter);
LocalDate parsedDate = LocalDate.parse(formattedDate, formatter);
Количество дней в месяце
// Old
Calendar calendar = new GregorianCalendar(1990, Calendar.FEBRUARY, 20);
int daysInMonth = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
// New
int daysInMonth = YearMonth.of(1990, 2).lengthOfMonth();
3. Взаимодействие с устаревшим кодом
Во многих случаях пользователю может потребоваться обеспечить совместимость со сторонними библиотеками, которые полагаются на старую библиотеку дат.
В Java 8 старые классы библиотеки дат были расширены методами, которые преобразуют их в соответствующие объекты из нового API дат.
Новые классы предоставляют аналогичные функции.
Instant instantFromCalendar = GregorianCalendar.getInstance().toInstant();
ZonedDateTime zonedDateTimeFromCalendar = new GregorianCalendar().toZonedDateTime();
Date dateFromInstant = Date.from(Instant.now());
GregorianCalendar calendarFromZonedDateTime = GregorianCalendar.from(ZonedDateTime.now());
Instant instantFromDate = new Date().toInstant();
ZoneId zoneIdFromTimeZone = TimeZone.getTimeZone("PST").toZoneId();
4. Вывод
В этой статье мы рассмотрели новый API даты и времени, доступный в Java 8. Мы рассмотрели его преимущества по сравнению с устаревшим API и указали на различия на нескольких примерах.
Обратите внимание, что мы едва коснулись возможностей нового API Date Time. Обязательно прочитайте официальную документацию, чтобы узнать о полном наборе инструментов, предлагаемых новым API.
Примеры кода можно найти в проекте GitHub .