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

Нарекания к классам для работы с датой и временем из стандартной библиотеки Java были давно. Некоторые, особо разозленные программисты, даже говорили, что это позор Java. Но выбор был небольшой – либо пользуйся стандартной библиотекой, либо можешь попытать счастья используя сторонние библиотеки, которые тоже не были лишены недостатков. Из основных недостатков стандартной библиотеки можно было отметить низкую точность (1 миллисекунда), изменяемость (muttable), в связи с этим усложнялась работа с ними в многопоточном режиме, отсутствие классов для работы с периодами и т.д. Поэтому в Java 8 Oracle полностью пересмотрел работу с датами и временем. Старый код по-прежнему работает, но новый подход во много раз удобнее, о нем и пойдет речь. Дата и время в Java 8 представлена четырьмя классами. Для того, чтобы начать работу с новыми классами нужно их импортировать:

Рассмотрим три класса для работы c датами и временем:
LocalDate. Позволяет работать только с датами, без времени.
LocalTime. Позволяет работать только со временем, без дат.
LocalDateTime. Позволяет работать и с датами, и с временем.

Давайте попробуем получить информацию о текущем времени:

Вывод:
2016-10-25
10:49:45.690
2016-10-25T10:49:45.690

Все три класса имеют статические методы now(), которые возвращают соответствующие объекты содержащие текущую дату и время. Первая строчка вывода содержит только дату, вторая только время и третья дату и время(буква T используется для разделения даты и времени). Давайте теперь попробуем создать объект не с текущей датой, а какой-то определенной, например 01.01.2016:

Вывод:
2016-01-01

Метод of можно вызвать по-другому:

Вывод:
2016-01-01

Если обратиться к документации Oracle то можно посмотреть в чем именно заключается отличие в этих двух вызовах:

В первом случае для месяца используется переменная с типом int, во втором константа перечисляемого типа. Обратите внимание, что нумерация месяцев начинается с единицы, а не нуля. Использования нуля в качестве параметра для месяца сгенерирует исключение класса DateTimeException.

Настало время попробовать создать объект, содержащий только время:

Вывод:
08:23

Как и для класса LocalDate метод of для класса LocalTime тоже перегружен, например, добавим к нашему времени секунды:

Вывод:
08:23:15

Добавим наносекунды:

Вывод:
08:23:15.000000150

Напоследок создадим объект класса LocalDateTime, который содержит в себе как дату, так и время:

Вывод:
2016-01-01T12:30:45

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

Вывод:
2016-01-01T12:30:45

Как видите, вывод абсолютно идентичен. Вариаций на тему «как создать объект LocalDateTime» много и описываться в рамках этой статьи они не будут, как правило, это различные комбинации из даты и времени.

нумерация месяцев в java date time api начинается с 1

Учтите, что все три класса LocalDate, LocalTime, LocalDateTime имеют приватные конструкторы и создать экземпляры этих классов напрямую не получится. Следующий код вызовет ошибку компиляции:



Работа с датой и временем.

Работать с классами даты и времени так же просто, как и их создавать, только следует учитывать один нюанс – так же, как и класс String классы даты и времени неизменяемые (Immutable). Рассмотрим пару примеров, создадим объект класса LocalDate:

Прибавим к созданной дате один день:

Как видите все очень просто, название метода plusDays() говорит само за себя. Для добавления недели можно передать в метод plusDays() целочисленную константу 7, а можно сделать проще:

метод plusWeeks() удобно использовать, если необходимо добавить несколько недель. Теперь месяцы:

И года:

А теперь попробуем вернуться в прошлое:

Рассмотрим более интересный пример:

Можно предположить, что в результате выполнения этого кода будет сгенерировано исключение, но это не так. Java прибавит один день к 31 октября и переменная date будет содержать дату 1 ноября 2016. Ещё один интересный пример, касающийся дат:

В 2015 году в феврале было 28 дней, но об этом беспокоиться вам не надо, Java сделает все за вас, в результате выполнения этого кода получим 2015-02-28.

Методы для работы с классом LocalTime схожи с теми, которые мы рассматривали выше:

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

Можно предположить, что первоначально объект LocalTime содержит в себе только часы и минуты, и при добавлении секунд будет сгенерировано исключение, но нет, в результате выполнения кода получим: 01:15:30

Класс LocalDateTime содержит в себе и дату и время, поэтому к нему можно применить все выше описанные методы классов LocalDate и LocalTime:

Приведенный выше код можно записать в три строчки:

Хотелось бы еще раз напомнить о том, что классы LocalDate, LocalTime и LocalDateTime неизменяемые. Рассмотрим пример:

Вывод:
2016-11-25

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

Работа с временными зонами.

Не смотря, на то, что Oracle настоятельно рекомендует избегать работы с временными зонами (или часовыми поясами) и применять их в случаях крайней нужды, ознакомиться с ними в любом случае нужно. Настало время к рассмотренным ранее трем классам для работы с датой и временем добавить четвертый:
ZonedDateTime – содержит в себе дату, время и временную зону.

Пример:

Вывод:
2016-12-19T13:31:26.103+12:00[Asia/Anadyr]

Как можно заметить часовой пояс был добавлен в конце сообщения +12:00[Asia/Anadyr], иначе говоря, я нахожусь в 12 часовых поясах от Гринвича. Как и с датами объект класса ZonedDateTime можно создать несколькими способами:

Пример:

Вывод:
2016-12-30T12:30+03:00[Europe/Moscow]

Класс ZonedDateTime учитывает не только временные зоны, но и летнее/зимнее время. В России от переходов на летнее и зимнее время отказались, а вот, к примеру, в США нет. Давайте посмотрим, как это работает. В США переход на зимнее время осуществляется в ноябре в 1:59 отсчет времени начинает вновь с 1:00, иначе говоря от времени отнимается один час:

Как видите, при первом добавлении часа, время не меняется. Давайте посмотрим, как дела обстоят с переходом на летнее время. В США переход на летнее время происходит в марте после 1:59 наступает 3:00, другими словами прибавляется один час.

Час из жизни американцев был бессовестно украден.



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

Для работы с временными периодами в Java присутствует замечательный класс Period. Давайте создадим экземпляр класса и более рассмотрим его методы:

Так же, как классы даты и времени, рассмотренные выше, периоды имеют приватные конструкторы, а для создания экземпляров класса используются статичные методы. В примере первой строчкой мы создали период из одного дня, второй строчкой – из одной недели, третьей – из одного месяца, четвертной — из одного года и последней – из двух лет, трех месяцев и одного дня. Период можно создать и по-другому:

В примере мы создали период из одного дня использовав экземпляры класса LocalDate в качестве начальной и конечной даты нашего периода. При создании методов можно использовать цепочку вызовов (method chaining), но делать этого не стоит и сейчас мы рассмотрим почему:

Логично предположить, что в примере был создан период из одного месяца и одного дня, но это не так. При использовании цепочки вызовов для периодов учитывается только последний вызов – в наше случае ofMonths. Иначе говоря выше приведенный код эквивалентен:

Первой строчкой мы создаем период в один день, а второй заменяем его на период в один месяц.

сцепка методов на периодах не работает, выполняется только последний метод

Рассмотрим теперь работу с периодами с практичной стороны:

Первыми двумя строчками мы создаем дату 1-03-2016 и период в 10 дней, третьей строчкой с помощью метода plus мы прибавляем к созданной дате период в 10 дней и получаем 11-03-2016. Абсолютно так же периоды работают и с классом LocalDatetime:

Разница лишь только в выводе дополнительной информации о времени. При попытке прибавить период в десять дней к объекту класса LocalTime будет сгенерировано исключение:

Исключение генерируется, потому что объект LocalTime не содержит в себе никакой информации о дате, только время.

Работа с промежутками.

Как вы могли заметить периоды, не работают с классами времени и создать период из 10 минут не получится, в таких ситуациях на помощь нам придут промежутки (Duration, если переводить этот термин «в лоб», то дословно получится продолжительность/длительность, но мне больше нравится термин промежуток). Работа с промежутками очень схожа с тем, что мы делали с периодами, за исключением того, что промежутки работают со временем и не работают с датами:

В примере выше, как вы можете заметить, самый большой промежуток — это день, который измеряется в часах. Конечно никто вам не помешает создать промежуток из 168 часов, что будет эквивалентно недели, но лучше всё-таки для этой цели воспользоваться периодами. Так же обратите внимание на то как представлена информация в потоке вывода, если в периодах перед выводом стояла буква P, в промежутках указывается PT (Period of Time – период времени).

Промежутки можно создать и другим способом:

Результат работы этой программы абсолютно идентичен предыдущему, только в этот раз мы воспользовались классом перечислений ChronoUnit. Этот класс может быть полезен если вы хотите задать какой-нибудь нестандартный промежуток, например:

Как уже говорилось работа с промежутками абсолютно идентична работе с периодами:

Еще раз повторюсь, промежутки не работают с датами, поэтому в последней строчке было сгенерировано исключение. Подытожим рассмотрение классов периода и промежутка следующей таблицей:
Дата и время в Java 8

Работа с моментами.

Да в Java присутствует и такое 🙂 В оригинале – Instant. Класс Instant представляет собой какой-то конкретный момент во времени и, как правило, используется для запуска какого-либо счетчика:

В этом примере используется метод sleep() для остановки основного потока на 10 секунд, что мы и видим создав промежуток между двумя моментами до и после остановки потока. Если у вас есть объект класса ZonedDateTime вы его можете с легкостью конвертировать в Instant:

Последние две строчки кода выводят один и тот же момент во времени, только переменная Instant содержит время в формате GMT. Напоследок хотелось бы отметить одну отличительную особенность класса Instant – он не умеет работать с неделями:



Форматируемый вывод.

До этого момента для получения информации о дате или времени мы пользовались только стандартным методом println() в качестве параметра, которому передавали ссылку на экземпляр класса даты и времени, но классы пакета java.time предоставляют гораздо больше возможностей для получения информации и ее гибкого форматирования для последующего вывода на экран. Рассмотрим следующий пример:

Каждый из методов гэттеров возвращает определенную информацию. Первый – день месяца, второй – день недели, третий – количество дней, прошедших сначала года, четвертый – номер месяца и последний – год. Используя эти методы можно сформировать вывод информации о дате в удобном нам формате, но есть способ проще – использование класса DateTimeFormatter. Класс DateTimeFormatter хранится в пакете java.time.format. Рассмотрим подробнее как его использовать:

В примере выше был использован международный стандарт ISO для отображения дат и времени, для преобразования выводимой информации в этот стандарт был использован метод format() в качестве параметра, которому была передана константа с указанием нужного нам формата. Если стандарт ISO нас не устраивает в Java есть несколько предустановленных форматов, рассмотрим один из них:

Как Вы можете заметить для того, чтобы воспользоваться предустановленным форматом сначала нужно создать экземпляр класса DateTimeFormatter и вызывать метод format() непосредственно этого экземпляра. Последняя строчка кода генерирует исключение, потому что время не может быть отформатирована как дата, а при создании объекта класса DateTimeFormatter мы использовали метод ofLocalizedDate, вместо этого метода можно было бы воспользоваться методом ofLocalizedDateTime и код бы выполнился без каких-либо проблем. Если Вас не устраивает ни формат ISO ни предустановленные форматы Вы можете разработать свой, делается это очень просто:

Для того чтобы сделать свой шаблон вывода был использован метод ofPattern(), который в качестве параметра принимает переменную типа String. Остановимся по подробнее на самой строке шаблона:
dd обозначает день месяца, если бы мы воспользовались одной буквой d, то получили бы дату без нуля впереди (1 марта)
MMMM используется для отображения месяца, чем больше букв M используется, тем информативнее будет вывод, к примеру одна буква M отобразит 3, MM – 03, MMM – мар, MMMM – март
yyyy отображает год, yy – отобразит две последних цифры года (16), yyyy – отобразит год полностью (2016)
Запятая после года, отображается в выводимой информации и использовалась нами для отделения даты и времени
hh отображает часы, если будет одна буква h – часы будут отображаться с одной цифрой без нуля впереди, где это возможно
: использовались нами для отделения часов и минут
mm – отображают минуты.

Рассмотренный шаблон можно так же применить для преобразования строк в дату и время. Рассмотрим пример:

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

Site Footer