Так уж вышло, что в 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
, где появилось сразу несколько классов для работы с датами
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
- вспомогательный класс, в котором включено много статических методов для работы с датами.
Пояивлся также еще и класс 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-timejava.time.Clock#instant
— запросить текущее время(в наносекундах по Unix-time)
Следующий класс для обзора - это Instant
Объекты этого класса хранят миллисекунды с 01.01.1970
и являются эквивалентом старого java.util.Date
.
По сути это 'правильный' Date
- неизменяемый, с наносекундной точностью и внятным названием, отражающим сущность класса.
Классы связаны - Date
имеет метод toInstant();
, который вернет нам Instant
от Date
. Однако надо понимать, что тут
теряется точность, так как старое API оперирует миллисекундной точностью, в то время как новое - наносекундной.
Т.е сейчас лучше использовать именно новый класс для оперирования моментом времени.
Операции с ним похожи на то, что описано выше:
Instant instant = Instant.now();
instant = instant.plusSeconds(3).minusMillis(2000);
Все это, напоминаю, immutable.
Описание календарной длительности (периода) в виде кортежа (год, месяц, день). Позволяет понятно рассчитать время начиная от какой-то точки в человекочетаемом виде и интерфейсе . Например, можно быстро и просто получить возраст по дате рождения:
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
— описание точной длительности в виде целого количества секунд и долей текущей секунды в виде наносекунд.
Также добавились классы для парсинга и форматирования дат.
В старом 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