Skip to content

Latest commit

 

History

History
158 lines (131 loc) · 11.6 KB

java_8_time_api.md

File metadata and controls

158 lines (131 loc) · 11.6 KB

Time API in Java

Так уж вышло, что в Java работа с датами не так тривиальна, как могло показаться. Ранее наиболее часто используемый класс для работы с датой - java.util.Date. И честно сказать - он не слишком удобен. Появившись еще в Java 1.0 он был не слишком удачно спроектирован. Почему?

  • Mutable - Date изменяемый, что порождает много ошибок, потоконебезопасно.
  • Нет поддержки часовых поясов.
  • Имя класса не отображает сути.
  • Сложное использование класса - не совсем очевидные архитектурные решения.

Дабы не быть голословным вот вам пример:

Date date = new Date(2016, 12, 13, 14, 49);

Кажется, что все нормально? Однако тут сразу два подводных камня, первый из которых в том, что на месте, где стоит 2016 конструктор класса java.util.Date ждет отступ от 1900 года. К тому уже, нумерация месяцев тут начинается с 0, поэтому месяца 12 нет.

Вдобавок к этому в java.util.Date сейчас почти все методы помечены как Deprecated. Поэтому, можно расценивать его как просто обертка над long, в котором хранится количество миллисекунд с 1 января 1970 года.

По сути java.util.Date - это даже не дата, а некоторая точка на временной оси и не более, поэтому название класса не совсем точно отображает его суть.

После этого ввели класс java.util.Calendar. Он был предназначен для операций со временем.

Однако минусы все же были:

  • Mutable - те же грабли
  • Напрямую практически невозможно было управлять датами, например, вычесть одну из другой.
  • Нет прямой связи с java.util.Date.

Тут уже счет месяцам начинается с 1, но первый день - это воскресенье. Дни, кстати, нумеруются с 1. Т.е использовать дату с календарем просто так не получится - необходимо было исхитрятся и преобразовывать как-то самому.

Теперь добавим еще про форматирование дат:

java.text.DateFormat и наследники существуют для представления дат, самый известный из них - это SimpleDateFormat. Тут надо помнить, что он потоконебезопасен!! А еще и у SimpleDateFormat и у Calendar желательно проставлять таймзону.

Разумеется, это крайне неудобно, поэтому создавались сторонние библиотеки, такие как Joda-time, имеющие свои плюсы.

Почему она мне не нравится:

  • Не хочется тащить в проект еще какую-то библиотеку, если уже есть встроенные классы для работы со временем.
  • Она использует свою временную базу и не использует стандартную из JVM, а это значит, что при изменениях в законах надо не только обновлять временную базу в JDK, но и обновлять отдельно для библиотеки.

Java Time

Тепреь же есть еще и пакет java.time, где появилось сразу несколько классов для работы с датами

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

По сути LocalDate и LocalTime хранят кортежи (yyyy,MM,dd) и (HH,mm,dd) соответственно, а LocalDateTime хранит оба кортежа.

Примеры:

LocalDate localDate = LocalDate.of(2016, Month.NOVEMBER, 23);
LocalTime localTime = LocalTime.of(19, 00, 20);

//With zones
ZonedDateTime zdt = ZonedDateTime.of(2016, 11, 23, 19, 15, 36, 55, ZonedId.of("Etc/GMT+3"));
ZonedDateTime zdt2 = ZonedDateTime.now(ZonedId.of("Europe/London"));
//Subtraction hours
zdt.getHour() - zdt2.getHour(); 

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

Стоит отметить, что объекты здесь являются уже immutable - т.е аккуратнее изменяем их в циклах. Кстати про поменять:

localDate = localDate.minusYears(1) 
.plusMonths(12) 
.minusDays(365) 
.plusYears(1); 

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

localDate = localDate.with(TemporalAdjusters.next(DayOfWeek.MONDAY)); 

TemporalAdjusters - вспомогательный класс, в котором включено много статических методов для работы с датами.

Clock

Пояивлся также еще и класс java.time.Clock - экземпляр такого класса можно создать только фабричными методами. Относится к нему можно как к часам в реальной жизни. Можно также переопределить java.time.Clock и создать такие часы, какие нужны вам, даже которые выдают каждый раз случайное время или спешат.

  • java.time.Clock#systemDefaultZone — метод создает системные часы во временной зоне по-умолчанию.
  • java.time.Clock#systemUTC — метод создает системные часы во временной зоне UTC.
  • java.time.Clock#system — метод создает системные часы в указанной временной зоне.
  • java.time.Clock#fixed — метод создает часы константного времени, то есть часы не идут, а стоят на месте.
  • java.time.Clock#offset — метод создает прокси над указанными часами, который смещает время на указанную величину.
  • java.time.Clock#tickSeconds — метод создает системные часы в указанной временной зоне, значение которых округлено до целых секунд.
  • java.time.Clock#tickMinutes — метод создает системные часы в указанной временной зоне, значение которых округлено до целых минут.
  • java.time.Clock#withZone — метод создает копию текущих часов в другой временной зоне.

Работаем с объектом часов: У объекта java.time.Clock всего три рабочих метода:

  • java.time.Clock#getZone — запросить временную зону в которой работают часы.
  • java.time.Clock#millis — запросить текущее время в миллисекундах по Unix-time
  • java.time.Clock#instant — запросить текущее время(в наносекундах по Unix-time)

Следующий класс для обзора - это Instant

Instant

Объекты этого класса хранят миллисекунды с 01.01.1970 и являются эквивалентом старого java.util.Date. По сути это 'правильный' Date - неизменяемый, с наносекундной точностью и внятным названием, отражающим сущность класса. Классы связаны - Date имеет метод toInstant();, который вернет нам Instant от Date. Однако надо понимать, что тут теряется точность, так как старое API оперирует миллисекундной точностью, в то время как новое - наносекундной. Т.е сейчас лучше использовать именно новый класс для оперирования моментом времени. Операции с ним похожи на то, что описано выше:

Instant instant = Instant.now();
instant = instant.plusSeconds(3).minusMillis(2000);

Все это, напоминаю, immutable.

Period

Описание календарной длительности (периода) в виде кортежа (год, месяц, день). Позволяет понятно рассчитать время начиная от какой-то точки в человекочетаемом виде и интерфейсе . Например, можно быстро и просто получить возраст по дате рождения:

LocalDate today = LocalDate.now(); 
LocalDate myBirthday = LocalDate.of(1990, Month.DECEMBER, 20); 
 
Period period = Period.between(myBirthday, today); 
//How old
period.getYears();
//total days
long myDays = ChronoUnit.DAYS.between(myBirthday, today); 

Похожий класс - java.time.Duration — описание точной длительности в виде целого количества секунд и долей текущей секунды в виде наносекунд.

Parsing and Formatting

Также добавились классы для парсинга и форматирования дат. В старом API java.text.SimpleDateFormat не являлся потоко-безопасным! И это странно, а также создавало ошибки. В новом API для этого используется класс DateTimeFormatter - он содежит как нелохой набор предопределенных форматтеров, так и возможность кастомизировать свои форматы.

Пример:

LocalDate.now().format(DateTimeFormatter.ISO_DATE);
//print 2016-11-20
//custom
LocalDate.now().format(DateTimeFormatter.ofPattern("MM/DD/yyyy"));
//print 11/20/2016