Java 8 дата и время

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

Java уже довольно много лет, и попыток создать классы для обработки дат было несколько. Здесь я лишь в общих чертах опишу три способа:

    • java.util.Date — самый древний способ. Большая часть методов отмечена устаревшими, но сам класс до сих пор используется довольно часто.
    • java.util.Calendar — обычно используется совместно с java.util.Date.
    • Пакет java.time — новый способ, сильно превосходит предыдущие по удобству и функционалу, но используется пока не так часто (по крайней мере мне редко приходилось сталкиваться.

java.util.Date

Самый древний способ. Если вы посмотрите на описание методов java.util.Date, то вы заметите, что большая часть методов указана устаревшими. Реально полезного и работающего всего чуть-чуть.

Создание экземпляров:

 

Полезные методы класса java.util.Date:

 

Возвращает true, если дата объекта находится позже указанной.

 

Возвращает true, если дата объекта находится перед указанной.

 

Сравнение дат. Возвращает 0, если даты равны. Возвращает -1, если дата объекта находится до anotherDate. Возвращает 1, если дата объекта позже anotherDate.

 

Используются для конвертации в Instant из пакета java.time и обратно.

 

Возвращает и устанавливает значение java.util.Date в количестве миллисекунд, прошедших с 1 января 1970 года 00:00 GMT.

 

java.util.Calendar

Класс java.util.Calendar является абстрактным классом. Единственная реализация — java.util.GregorianCalendar. Новый экземпляр создаётся фабричным методом:

Calendar предоставляет методы установки значений различных полей вроде YEAR, MONTH, DAY_OF_MONTH, HOUR и т. д, манипуляции этими полями, например добавление дня или месяца. Дата и время представляется количеством миллисекунд, прошедших с 1 января 1970 года 00:00:00.000 GMT. Обычно класс java.util.Calendarиспользуется для создания экземпляров java.util.Date, представляющих необходимую дату и время.

Calendar имеет два режима интерпретации полей: мягкий (lenient) и жёсткий (non-lenient). Когда Calendar в мягком (lenient) режиме, то он принимает больший диапазон значений полей, чем возвращает. Когда Calendar пересчитывает значения полей, возвращаемых get(), то все поля нормализуются. Например, мягкий GregorianCalendarинтерпретирует MONTH == JANUARY, DAY_OF_MONTH == 32 как 1 февраля.

Когда Calendar в жёстком (non-lenient) режиме, то он бросает исключение в подобных случаях. Например, GregorianCalendar всегда возвращает значение поля DAY_OF_MONTH в диапазоне от 1 до длины месяца. Жёсткий GregorianCalendar бросает исключение во время выполнения вычислений своего времени или поля, если хоть одно поле выходит за допустимые границы.

Полезные методы:

Устанавливает новое значения поля.

 

Возвращает значение поля. Для мягкого режима значения полей нормализуются, а в случае жёсткого режима и выхода значения какого-либо поля за допустимый диапазон бросается исключение

 

Добавляет amount (может быть отрицательным) к указанному полю.

 

Конвертация в java.util.Date и обратно.

 

Устанавливает мягкий (lenient) или жёсткий (non-lenient) режим.

 

Конвертирует в Instant из пакета java.time.

 

Позволяет установить часовой пояс. По умолчанию используется часовой пояс системы, на которой запущена программа. С помощью этого метода можно указать другой часовой пояс. Например: calender.setTimeZone(TimeZone.getTimeZone(«America/Los_Angeles»));

 

Возвращает часовой пояс, ассоциированный с данным экземпляром Calendar.

java.time

Наиболее удобный и современный способ работы с датой и временем. Берёт своё начало от библиотеки Joda-Time.

Есть два базовых способа представления времени. Один способ представляет время в терминах человека, таких как год, месяц, день, час, минуты и секунды. Второй способ представляет машинное время, измеряя время непрерывно с начала, называемого эпохой, в наносекундах. Пакет Date-Time содержит большое количество классов, представляющий дату и время. Некоторые классы в Date-Time API представляют машинное время, некоторые человеческое.

Сначала определите, какие аспекты даты и времени вам нужны, затем выберите класс или классы, которые подходят под ваши нужды.

Например, вы можете выбрать java.time.LocalDate для хранения даты рождения, так как многие люди празднуют день рождения в тот же день, независимо от того, находятся ли они в месте рождения или на другом конце Земли. Если вам нужно астрологическое время, то вы можете использовать java.time.LocalDateTime, чтобы показать дату и день рождения, либо java.time.ZonedDateTime, который дополнительно содержит часовой пояс. Если вы создаёте временную отметку, то вероятнее всего вы захотите использовать java.time.Instant, который позволяет сравнивать одну временную отметку с другой.

Суммарная таблица классов пакета java.time:

Класс или перечисление Год Месяц День Часы Минуты Секунды* Смещ. врем. зоны Zone ID toString Output
Instant
x
2013-08-20T15:16:26.355Z
LocalDate
x
x
x
2013-08-20
LocalDateTime
x
x
x
x
x
x
2013-08-20T08:16:26.937
ZonedDateTime
x
x
x
x
x
x
x
x
2013-08-21T00:16:26.941+09:00[Asia/Tokyo]
LocalTime
x
x
x
08:16:26.943
MonthDay
x
x
--08-20
Year
x
2013
YearMonth
x
x
2013-08
Month
x
AUGUST
OffsetDateTime
x
x
x
x
x
x
x
2013-08-20T08:16:26.954-07:00
OffsetTime
x
x
x
x
08:16:26.957-07:00
Duration ** ** **
x
PT20H (20 hours)
Period
x
x
x
*** *** P10D (10 days)

* секунды считаются с точностью до наносекунд
** Этот класс не сохраняет эту информацию, но имеет методы для получения времени в них.
*** Когда Period  добавляется к ZonedDateTime , то учитывается переход на зимнее/летнее время и отличие локального времени.

java.time.DayOfWeek

Перечисление java.time.DayOfWeek состоит из семи констант, описывающих дни недели. Целочисленные значения для констант начинаются с 1 (Понедельник) и заканчиваются 7 (Воскресенье).

Список констант:

MONDAY  (понедельник, 1)
TUESDAY  (вторник, 2)
WEDNESDAY  (среда, 3)
THURSDAY  (четверг, 4)
FRIDAY  (пятница, 5)
SATURDAY  (суббота, 6)
SUNDAY  (воскресенье, 7)

Вы можете использовать метод public String getDisplayName(TextStyle style, Localelocale)  для получения названий дней недели в соответствии с региональными настройками пользователя. Перечислениеjava.time.format.TextStyle  позволяет указать тип строки: FULL, NARROW  (обычно одна буква), SHORT  (аббревиатура).

Пример:

java.time.Month

Перечисление java.time.Month  содержит константы для двенадцати месяцев, пронумерованных от 1 до 12:
JANUARY  (январь, 1)
FEBRUARY  (февраль, 2)
MARCH  (март, 3)
APRIL  (апрель, 4)
MAY  (май, 5)
JUNE  (июнь, 6)
JULY  (июль, 7)
AUGUST  (август, 8)
SEPTEMBER  (сентябрь, 9)
OCTOBER  (октябрь, 10)
NOVEMBER  (ноябрь, 11)
DECEMBER  (декабрь, 12)

Перечисление java.time.Month  содержит несколько полезных методов. Например, метод maxLength()  возвращает максимально возможное количество дней в месяце:

System.out.printf(«%d%n», Month.FEBRUARY.maxLength());

Также есть метод public String getDisplayName(TextStyle style, Locale locale), позволяющий получить текстовое название месяца в соответствии с указанной локалью:

java.time.LocalDate

Класс java.time.LocalDate хранит год, месяц и день. Он используется для хранения и обработки даты без времени. Примеры создания:

В дополнение к обычным методам класс java.time.LocalDate  содержит методы для получения информации о дате. Метод getDayOfWeek  возвращает день недели. Например, следующий код вернёт MONDAY:

Следующий пример использует TemporalAdjuster, чтобы следующую среду от указанной даты:

java.time.YearMonth

Класс java.time.YearMonth представляет месяц с годом. Следующие примеры используют YearMonth.lengthOfMonth(), чтобы определить количество дней в конкретном годе и месяце:

Этот код выведет в консоль:

java.time.MonthDay

Класс java.time.MonthDay содержит день с месяцем. Следующий пример использует MonthDay.isValidYear, чтобы определить, является ли 29 февраля корректной датой для 2010 года. Этот вызов вернёт false, так как 2010 год не является високосным.

java.time.Year

Класс java.time.Year хранит год. Следующий пример использует метод Year.isLeap , чтобы определить, является ли год високосным. Этот вызов вернёт true, так как 2012 год високосный.

java.time.LocalTime

Класс java.time.LocalTime оперирует только временем. Он полезен  для хранения времени открытия/закрытия магазина и т. д. Пример:

Класс LocalTime  не сохраняет информацию о часовой поясе и летнем/зимнем времени.

java.time.LocalDateTime

Класс java.time.LocalDateTime хранит дату и время. Он является нечтом вроде комбинации LocalDate  и LocalTime. В дополнение к методу now(), который есть у каждого временного класса, классLocalDateTime  содержит большое количество методов of, которые позволяют создать экземпляры LocalDateTime. Метод from  конвертирует экземпляр другого класса в LocalDateTime. Также есть методы для добавления и вычитания часов, минут, дней и недель Пример:

Этот код выведет в консоль:

java.time.ZoneId и java.time.ZoneOffset

Часовой пояс — это участок земной поверхности, на котором используется одно и то же стандартное время. Каждый часовой пояс определяется идентификатором, имеющим формат регион/город (Asia/Tokyo), и смещением от Гринвича/UTC. Например, смещение для Токио +9:00.

Date-Time API содержит два класса для указания часового пояса или смещения:

  • java.time.ZoneId указывает идентификатор часового пояса и поставляет правила для конвертирования java.time.Instant  вjava.time.LocalDateTime.
  • java.time.ZoneOffset указывает смещение часового пояса от Гринвича/UTC.

Смещения от Гринвича/UTC обычно определяются в полных часах, но есть исключения. Следующий код выводит в консоль все часовые пояса, которые используют смещение от Гринвича/UTC, указанные не в полных часах.

Этот пример выведет в консоль:

java.time.ZonedDateTime

Класс java.time.ZonedDateTime можно рассматривать как комбинацию java.time.LocalDateTime  и java.time.ZoneId. Он представляет собой полную дату, время и часовой пояс.

Следующий код определяет время вылета из Сан-Франциско в Токио как java.time.ZonedDateTime  в часовом поясе America/Los Angeles. Для получения экземпляра java.time.ZonedDateTime, содержащего время прибытия в Токио после 650 минут полёта, используются методыwithZoneSameInstant  и plusMinutes. Метод ZoneRules.isDaylightSavings  определяет, используется ли летнее время по прибытии в Токио.

Для форматированного вывода java.time.ZonedDateTime  используется объект java.time.format.DateTimeFormatter.

Этот код выведет в консоль:

java.time.OffsetDateTime

Класс java.time.OffsetDateTime можно рассматривать как комбинацию java.time.LocalDateTime  и java.time.ZoneOffset . Он содержит полную дату, время и смещение от Гринвича/UTC (+/-часы:минуты).

Следующий пример использует класс java.time.OffsetDateTime  и методTemporalAdjuster.lastDay, чтобы найти последний четверг в июле 2013 года.

Этот код выведет в консоль следующее:

java.time.OffsetTime

Класс java.time.OffsetTime можно рассматривать как комбинациюjava.time.LocalTime  и java.time.ZoneOffset . Он содержит время и смещение от Гринвича/UTC (+/-часы:минуты).

java.time.Instant

Класс java.time.Instant — это один из самых основных классов Date-Time API, который представляет наносекунды от 1 января 1970 года 00:00:00 по UTC. Класс java.time.Instant  может содержать отрицательное значение, если он представляет время до 1 января 1970 года.

Пример создания:

Класс содержит полезные константы: Instant.EPOCH  (1 января 1970 00:00:00Z), Instant.MIN  (самое прошлое время из возможных),Instant.MAX  (самое будущее время из возможных).

Класс java.time.Instant  содержит большое количество методов для манипулирования  Instant. Методы plus  и minus  используются для добавления и вычитания времени. Следующий код добавляет один час к текущему времени:

Есть методы для сравнения экземпляров Instant, например isAfter и isBefore. Метод until возвращает время между двумя экземплярамиInstant. Следующий код сообщает количество секунд, прошедщих с начала эпохи:

Класс java.time.Instant  работает с машинным представлением времени. Он не работает с годами, месяцами, часами и т. д. Если вам нужно работать с ними, то вам нужно преобразовать его вjava.time.Instant  в другой класс, например java.time.LocalDateTime  илиjava.time.ZonedDateTime, связав java.time.Instant  с часовым поясом. Пример:

Этот код выводит в консоль что-то вроде этого:

Классы java.time.ZonedDateTime  и java.time.OffsetDateTime  могут быть преобразованы в экземпляр java.time.Instant, так как они указывают на конкретный момент во времени. Однако для обратного преобразования нужно указать часовой пояс или смещение.

Форматирование и преобразование из строки

Временные классы из Date-Time API содержит методы parse  и format  для разбора даты и/или времени из строки и форматированного вывода в строку. Эти методы принимают экземпляр класса java.time.format.DateTimeFormatter. Класс DateTimeFormatter  содержит большое количество предопределённых форматировщиков, вы также можете определить свой.

Методы parse  и format  бросают исключение, если во время конвертации возникает какая-нибудь проблема. Ваш код разбора строки должен отлавливать исключение java.time.format.DateTimeParseException, а форматирующий код должен отлавливать java.time.format.DateTimeException.Эти исключения непроверяемые, так что компилятор не будет требовать их обязательной обработки.

Класс java.time.format.DateTimeFormatter  неизменяемый и потокобезопасный. Его можно присвоить константе, если нужно.

Классы из java.time  можно использовать и в java.util.Formatter  и вString.format, как и старые классы java.util.Date  и java.util.Calendar.

java.time.temporal.TemporalAdjuster

Интерфейс java.time.temporal.TemporalAdjuster содержит методы, которые принимают значение времени и возвращает его выровненное по какому-либо принципу значение.

Класс java.time.temporal.TemporalAdjusters содержит предопределённые выравниватели даты и времени для поиска первого или последнего дня в месяце, первого или последнего дня года, последней среды месяца и т. д.

Это выведет в консоль:

java.time.temporal.TemporalQuery

Интерфейс java.time.temporal.TemporalQuery может использоваться для получения информации об объекте времени.

Класс java.time.temporal.TemporalQueries содержит предопределённые TemporalQuery. Например, в следующем примере получаем наименьшую java.time.temporal.ChronoUnit, которая может быть возвращена объектом времени:

Вывод будет следующим:

java.time.Duration

Класс java.time.Duration используется для измерения промежутка времени между двумя машинными временными объектами, например Instant-ами.

Примеры:

Duration  не связана с датой и временем, он не хранит информацию о зонах и переходе на летнее/зимнее время. Добавление Duration, эквивалентного 1 дню, к ZonedDateTime  добавит ровно 24 часа, независимо от часового пояса и зимнего/летнего времени.

java.time.temporal.ChronoUnit

Перечисление java.time.temporal.ChronoUnit объявляет единицы измерения для времени. Метод ChronoUnit.between  используется, когда вам нужно измерить количество времени только в днях, только в секундах или только в других одних единицах измерения времени. Метод between  работает с объектами дат и времени, но возвращает только количество в определённых единицах. Пример:

java.time.Period

Класс java.time.Period определяет период времени в человеческих единицах: месяцах, днях, годах. Класс java.time.Period содержит методы getMonths, getDays, getYears, которые используются для получения соответствующих единиц времени из периода.

Общий период времени представляется суммой всех трёх частей: месяцев, дней, годов. Чтобы получить период только в одной единице времени, используйте ChronoUnit.between.

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

Код выводит в консоль следующее:

Следующий пример считает, сколько осталось до следующего дня рождения:

Вывод будет примерно таким:

Эти вычисления не учитывают разницу в часовых поясах. Например, если вы родились в Австралии, а сейчас живёте в Бангалоре, то это может немного повлиять на вычисление вашего возраста. В подобных ситуациях используйте Period  вместе сZonedDateTime.

java.time.Clock

Многие классы с датой и временем содержат метод now(), который создаёт объект с текущей датой и временем, используя системные часы и часовой пояс по умолчанию.  Эти же объекты также содержат метод now(Clock), который позволяет передать другойjava.time.Clock.

Текущая дата и время зависят от часового пояса и для глобальных приложений необходим Сlock, чтобы гарантировать создание даты/времени с корректным часовым поясом. Несмотря на то что использование java.time.Clock  необязательно, но эта возможность помогает тестировать ваш код с другими часовыми зонами или с фиксированным временем.

Класс Clock  абстрактный, поэтому вы не можете создать его экземпляров. Следующие фабричные методы могут быть полезны для тестирования:

  • Clock.offset(Clock, Duration) возвращает clock, который является смещением на указанный Duration.
  • Clock.systemUTC() возвращает clock, содержащий часовой пояс Гринвич/UTC.
  • Clock.fixed(Instant, ZoneId) всегда возвращает один и тот же Instant. Для этого Clock  время не идёт, оно остановилось.