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.