Перейти к основному содержимому

Введение в API даты/времени Java 8

· 10 мин. чтения

1. Обзор

В Java 8 представлены новые API для даты и времени , чтобы устранить недостатки старых java.util.Date и java.util.Calendar .

В этом руководстве давайте начнем с проблем в существующих API-интерфейсах даты и календаря и обсудим, как их решают новые API -интерфейсы даты и времени Java 8.

Мы также рассмотрим некоторые основные классы нового проекта Java 8, которые являются частью пакета java.time , такие как LocalDate , LocalTime , LocalDateTime , ZonedDateTime , Period , Duration и их поддерживаемые API.

2. Проблемы с существующими API даты / времени

  • Безопасность потоков . Классы Date и Calendar не являются потокобезопасными, поэтому разработчикам приходится сталкиваться с головной болью, связанной с трудными для отладки проблемами параллелизма, и писать дополнительный код для обеспечения безопасности потоков. Напротив, новые API-интерфейсы даты и времени , представленные в Java 8, являются неизменяемыми и потокобезопасными, что избавляет разработчиков от головной боли параллелизма.
  • Дизайн API и простота понимания . API-интерфейсы даты и календаря плохо разработаны с неадекватными методами для выполнения повседневных операций. Новый API даты / времени ориентирован на ISO и следует согласованным моделям предметной области для даты, времени, продолжительности и периодов. Существует множество служебных методов, поддерживающих наиболее распространенные операции.
  • ZonedDate и Time — разработчикам пришлось написать дополнительную логику для обработки логики часовых поясов с помощью старых API, тогда как с новыми API обработка часового пояса может выполняться с помощью API Local и ZonedDate / Time .

3. Использование LocalDate , LocalTime и LocalDateTime

Наиболее часто используемые классы — LocalDate , LocalTime и LocalDateTime . Как видно из их названий, они представляют локальную дату/время из контекста наблюдателя.

В основном мы используем эти классы, когда часовые пояса не требуется явно указывать в контексте. В рамках этого раздела мы рассмотрим наиболее часто используемые API.

3.1. Работа с локальной датой

LocalDate представляет дату в формате ISO (гггг-мм-дд) без времени. Мы можем использовать его для хранения таких дат, как дни рождения и дни выплаты жалованья.

Экземпляр текущей даты может быть создан из системных часов:

LocalDate localDate = LocalDate.now();

И мы можем получить LocalDate , представляющий конкретный день, месяц и год, используя метод of или метод parse .

Например, эти фрагменты кода представляют LocalDate на 20 февраля 2015 г.:

LocalDate.of(2015, 02, 20);

LocalDate.parse("2015-02-20");

LocalDate предоставляет различные служебные методы для получения разнообразной информации. Давайте кратко рассмотрим некоторые из этих методов API.

Следующий фрагмент кода получает текущую локальную дату и добавляет один день:

LocalDate tomorrow = LocalDate.now().plusDays(1);

Этот пример получает текущую дату и вычитает один месяц. Обратите внимание, как он принимает перечисление в качестве единицы времени:

LocalDate previousMonthSameDay = LocalDate.now().minus(1, ChronoUnit.MONTHS);

В следующих двух примерах кода мы анализируем дату «2016-06-12» и получаем день недели и день месяца соответственно. Обратите внимание на возвращаемые значения: первое — это объект, представляющий DayOfWeek , а второе — целое число, представляющее порядковый номер месяца:

DayOfWeek sunday = LocalDate.parse("2016-06-12").getDayOfWeek();

int twelve = LocalDate.parse("2016-06-12").getDayOfMonth();

Мы можем проверить, попадает ли дата в високосный год, например, текущая дата:

boolean leapYear = LocalDate.now().isLeapYear();

Кроме того, связь одной даты с другой может быть определена до или после другой даты:

boolean notBefore = LocalDate.parse("2016-06-12")
.isBefore(LocalDate.parse("2016-06-11"));

boolean isAfter = LocalDate.parse("2016-06-12")
.isAfter(LocalDate.parse("2016-06-11"));

Наконец, границы даты могут быть получены из заданной даты.

В следующих двух примерах мы получаем LocalDateTime , представляющий начало дня (2016-06-12T00:00) заданной даты, и LocalDate , представляющий начало месяца (2016-06-01) соответственно:

LocalDateTime beginningOfDay = LocalDate.parse("2016-06-12").atStartOfDay();
LocalDate firstDayOfMonth = LocalDate.parse("2016-06-12")
.with(TemporalAdjusters.firstDayOfMonth());

Теперь давайте посмотрим, как мы работаем с местным временем.

3.2. Работа с локальным временем

LocalTime представляет время без даты.

Как и в случае с LocalDate , мы можем создать экземпляр LocalTime из системных часов или с помощью синтаксического анализа и методов .

Теперь мы кратко рассмотрим некоторые из часто используемых API.

Экземпляр текущего LocalTime может быть создан из системных часов:

LocalTime now = LocalTime.now();

Мы можем создать LocalTime , представляющий 6:30 утра, проанализировав строковое представление:

LocalTime sixThirty = LocalTime.parse("06:30");

Фабричный метод также можно использовать для создания LocalTime . Этот код создает LocalTime, представляющий 6:30 утра, используя фабричный метод:

LocalTime sixThirty = LocalTime.of(6, 30);

Давайте создадим LocalTime , проанализировав строку и добавив к ней час, используя «плюс» API. Результатом будет LocalTime , представляющий 7:30 утра:

LocalTime sevenThirty = LocalTime.parse("06:30").plus(1, ChronoUnit.HOURS);

Доступны различные методы получения, которые можно использовать для получения определенных единиц времени, таких как часы, минуты и секунды:

int six = LocalTime.parse("06:30").getHour();

Мы также можем проверить, является ли определенное время раньше или позже другого определенного времени. В этом примере кода сравниваются два значения LocalTime, для которых результат будет верным:

boolean isbefore = LocalTime.parse("06:30").isBefore(LocalTime.parse("07:30"));

Наконец, максимальное, минимальное и полуденное время суток можно получить с помощью констант в классе LocalTime . Это очень полезно при выполнении запросов к базе данных для поиска записей в течение заданного промежутка времени.

Например, приведенный ниже код представляет 23:59:59,99:

LocalTime maxTime = LocalTime.MAX

Теперь давайте погрузимся в LocalDateTime .

3.3. Работа с локальной датой и временем

LocalDateTime используется для представления комбинации даты и времени. Это наиболее часто используемый класс, когда нам нужна комбинация даты и времени.

Класс предлагает множество API. Здесь мы рассмотрим некоторые из наиболее часто используемых.

Экземпляр LocalDateTime можно получить из системных часов аналогично LocalDate и LocalTime :

LocalDateTime.now();

В приведенных ниже примерах кода объясняется, как создать экземпляр с помощью фабричных методов «of» и «parse». Результатом будет экземпляр LocalDateTime , представляющий 20 февраля 2015 г., 6:30 утра:

LocalDateTime.of(2015, Month.FEBRUARY, 20, 06, 30);
LocalDateTime.parse("2015-02-20T06:30:00");

Существуют служебные API для поддержки сложения и вычитания определенных единиц времени, таких как дни, месяцы, годы и минуты.

В приведенном ниже коде показаны методы «плюс» и «минус». Эти API ведут себя точно так же, как их аналоги в LocalDate и LocalTime :

localDateTime.plusDays(1);
localDateTime.minusHours(2);

Методы получения также доступны для извлечения определенных единиц, подобных классам даты и времени. Учитывая приведенный выше экземпляр LocalDateTime , этот пример кода вернет месяц февраль:

localDateTime.getMonth();

4. Использование ZonedDateTime API

Java 8 provides ZonedDateTime when we need to deal with time-zone-specific date and time. The ZoneId is an identifier used to represent different zones. There are about 40 different time zones, and the ZoneId represents them as follows.

Here, we create a Zone for Paris:

ZoneId zoneId = ZoneId.of("Europe/Paris");

И мы можем получить набор всех идентификаторов зон:

Set<String> allZoneIds = ZoneId.getAvailableZoneIds();

LocalDateTime можно преобразовать в определенную зону:

ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, zoneId);

ZonedDateTime предоставляет метод синтаксического анализа для получения даты и времени, зависящего от часового пояса : ``

ZonedDateTime.parse("2015-05-03T10:15:30+01:00[Europe/Paris]");

Другой способ работы с часовым поясом — использование OffsetDateTime . OffsetDateTime — это неизменное представление даты и времени со смещением. Этот класс хранит все поля даты и времени с точностью до наносекунд, а также смещение от UTC/Greenwich.

Экземпляр OffSetDateTime можно создать с помощью ZoneOffset . Здесь мы создаем LocalDateTime , представляющий 6:30 утра 20 февраля 2015 года:

LocalDateTime localDateTime = LocalDateTime.of(2015, Month.FEBRUARY, 20, 06, 30);

Затем мы добавляем ко времени два часа, создавая ZoneOffset и настройку для экземпляра localDateTime :

ZoneOffset offset = ZoneOffset.of("+02:00");

OffsetDateTime offSetByTwo = OffsetDateTime
.of(localDateTime, offset);

Теперь у нас есть localDateTime 20.02.2015 06:30 +02:00.

Теперь давайте перейдем к тому, как изменять значения даты и времени с помощью классов Period и Duration .

5. Использование периода и продолжительности

Класс Period представляет количество времени в годах, месяцах и днях, а класс Duration представляет количество времени в секундах и наносекундах.

5.1. Работа с периодом

Класс Period широко используется для изменения значений заданной даты или для получения разницы между двумя датами:

LocalDate initialDate = LocalDate.parse("2007-05-10");

Мы можем манипулировать Date с помощью Period :

LocalDate finalDate = initialDate.plus(Period.ofDays(5));

Класс Period имеет различные методы получения, такие как getYears , getMonths и getDays , для получения значений из объекта Period .

Например, это возвращает значение int , равное 5, поскольку мы пытаемся получить разницу в днях :

int five = Period.between(initialDate, finalDate).getDays();

Мы можем получить период между двумя датами в определенных единицах измерения, таких как дни, месяцы или годы, используя ChronoUnit.between :

long five = ChronoUnit.DAYS.between(initialDate, finalDate);

Этот пример кода возвращает пять дней.

Давайте продолжим, взглянув на класс Duration .

5.2. Работа с длительностью

Подобно периоду, класс Duration используется для работы со временем.

Давайте создадим LocalTime для 6:30 утра, а затем добавим продолжительность 30 секунд, чтобы сделать LocalTime равным 6:30:30:

LocalTime initialTime = LocalTime.of(6, 30, 0);

LocalTime finalTime = initialTime.plus(Duration.ofSeconds(30));

Мы можем получить Продолжительность между двумя моментами либо как Продолжительность , либо как конкретную единицу.

Во-первых, мы используем метод between() класса Duration , чтобы найти разницу во времени между finalTime и initialTime и вернуть разницу в секундах:

long thirty = Duration.between(initialTime, finalTime).getSeconds();

Во втором примере мы используем метод between() класса ChronoUnit для выполнения той же операции:

long thirty = ChronoUnit.SECONDS.between(initialTime, finalTime);

Теперь мы рассмотрим, как преобразовать существующие Date и Calendar в новые Date / Time .

6. Совместимость с датой и календарем

В Java 8 добавлен метод toInstant() , который помогает преобразовать существующий экземпляр даты и календаря в новый API даты и времени:

LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
LocalDateTime.ofInstant(calendar.toInstant(), ZoneId.systemDefault());

LocalDateTime может быть создан из секунд эпохи . Результатом приведенного ниже кода будет LocalDateTime , представляющий 2016-06-13T11:34:50:

LocalDateTime.ofEpochSecond(1465817690, 0, ZoneOffset.UTC);

Теперь давайте перейдем к форматированию даты и времени .

7. Форматирование даты и времени

Java 8 предоставляет API для простого форматирования даты и времени : ``

LocalDateTime localDateTime = LocalDateTime.of(2015, Month.JANUARY, 25, 6, 30);

Этот код передает формат даты ISO для форматирования локальной даты с результатом 2015-01-25:

String localDateString = localDateTime.format(DateTimeFormatter.ISO_DATE);

DateTimeFormatter предоставляет различные стандартные параметры форматирования.

Пользовательские шаблоны также могут быть предоставлены методу формата, который здесь возвращает LocalDate как 2015/01/25:

localDateTime.format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));

Мы можем передать стиль форматирования как SHORT , LONG или MEDIUM как часть опции форматирования.

Например, это даст вывод, представляющий LocalDateTime 25 января 2015 года, 06:30:00:

localDateTime
.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
.withLocale(Locale.UK));

Давайте взглянем на альтернативы, доступные для Java 8 Core Date / Time API.

8. Бэкпорт и альтернативные варианты

8.1. Использование проекта ThreeTen

Для организаций, которые находятся на пути перехода на Java 8 с Java 7 или Java 6 и хотят использовать API даты и времени, проект ThreeTen предоставляет возможность обратного переноса.

Разработчики могут использовать классы, доступные в этом проекте, для достижения той же функциональности, что и у новых API-интерфейсов даты и времени Java 8 . И как только они перейдут на Java 8, пакеты можно будет переключать.

Артефакт для проекта ThreeTen можно найти в Maven Central Repository :

<dependency>
<groupId>org.threeten</groupId>
<artifactId>threetenbp</artifactId>
<version>1.3.1</version>
</dependency>

8.2. Библиотека Джода-Время

Другой альтернативой библиотеке даты и времени Java 8 является библиотека Joda-Time . На самом деле Java 8 Date / Time API был разработан совместно автором библиотеки Joda-Time (Стивен Коулборн) и Oracle. Эта библиотека предоставляет почти все возможности, которые поддерживаются в проекте Java 8 Date / Time .

Артефакт можно найти в Maven Central , включив в наш проект следующую зависимость pom:

<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.4</version>
</dependency>

9. Заключение

Java 8 предоставляет богатый набор API-интерфейсов с единообразным дизайном API для упрощения разработки.

Примеры кода для приведенной выше статьи можно найти в репозитории Java 8 Date/Time git.