Вы, конечно, можете хранить год, месяц и день месяца в int-ах. Однако скоро вы столкнётесь с тем, что посчитать количество дней между 20 января и 16 апреля – не такая очевидная операция. Как минимум, ввиду разного количества дней в месяцах.
В общем, гораздо удобнее использовать уже готовые классы, заточенные под хранение и вычисление даты. Аналогично со временем. Давайте посмотрим, что приготовил для нас пакет java.time.
Содержание:
Дата
Для оперирования датой без времени подходит класс LocalDate. Создать его объект можно через, например, LocalDate.now() для получения текущей даты. Или с помощью LocalDate.of() для задания года, месяца и дня.
1 2 |
LocalDate currentDate = LocalDate.now(); LocalDate specificDate = LocalDate.of(2022, 4, 15); |
Как результат, видим в консоли два представления даты, сформированных переопределённым методом toString() у LocalDate. То есть написать System.out.println(currentDate) то же самое, что System.out.println(currentDate.toString()).
Первым идёт год, затем идёт месяц, а цепочку закрывает день.
В свою очередь, получать отдельные части – год, месяц и день (но не только) – можно так:
1 2 3 4 5 6 |
currentDate.getYear(); currentDate.getMonthValue(); currentDate.getMonth(); currentDate.getDayOfMonth(); currentDate.getDayOfWeek(); currentDate.getDayOfYear(); |
Выведем полученные значения в консоль. Посмотрите: мы можем получить не только численные значения года, месяца и дня в месяце. Мы можем получить название месяца (enum Month), название дня недели (enum DayOfWeek) и порядковый номер дня в году. Сегодня вторник, 27 февраля 2024 года. В январе был 31 день, плюс 27 в феврале – как раз 58!
Можно сравнивать даты при помощи методов isBefore(), isAfter() и isEqual():
1 2 3 |
currentDate.isAfter(specificDate); currentDate.isBefore(specificDate); currentDate.isEqual(specificDate); |
Получается, currentDate (2024-02-27) находится после specificDate (2022-04-15), не находится до и не равна. Всё верно, так и есть. 27 февраля 2024 года – это после 15 апреля 2022 года.
Помимо получений характеристик определённой даты и сохранения их в переменные, нужно уметь оперировать интервалами между датами. Например, можно производить всяческие операции и вычисления: к выбранной дате прибавлять или отнимать дни, недели, месяцы, годы:
1 2 3 4 5 6 7 8 9 |
LocalDate dateDayAfter = currentDate.plusDays(1); LocalDate dateWeekAfter = currentDate.plusWeeks(1); LocalDate dateMonthAfter = currentDate.plusMonths(1); LocalDate dateYearAfter = currentDate.plusYears(1); LocalDate dateDayBefore = currentDate.minusDays(1); LocalDate dateWeekBefore = currentDate.minusWeeks(1); LocalDate dateMonthBefore = currentDate.minusMonths(1); LocalDate dateYearBefore = currentDate.minusYears(1); |
Посмотрим на вывод в консоль:
Я думаю, это отличная возможность для управления датами. Например, вам требуется выяснить, прошла ли неделя с некоторого момента. Это звучит очевидно до тех пор, пока ваша неделя не стоит на пересечении двух месяцев, у которых ещё и количество дней может быть разным. Нечего тратить время на то, что уже реализовано и отлично работает.
Таким образом, если нам нужно получить дату перед или после текущей, чтобы она отличалась на несколько дней, недель, месяцев или лет – это легко сделать при помощи методов вроде plusDays() и minusDays().
Также полезная функция – нахождение интервала времени между двумя заданными датами. Его можно находить разными способами.
Первый из них – при использовании класса Period. Это класс, представляющий собой промежуток в годах, месяцах и днях.
1 2 3 4 5 |
Period periodBetween = Period.between(specificDate, currentDate); periodBetween.getYears(); periodBetween.getMonths(); periodBetween.getDays(); periodBetween.toTotalMonths(); |
У класса Period есть всякие возможности. Однако нет одной очень важной – общее количество дней. Есть только общее количество месяцев. А также частное количество лет, месяцев и дней. А ведь общее количество дней бывает жизненно необходимым.
Поэтому можно воспользоваться другим способом, а именно – методом until() с указанием, что рассчитывать нужно именно дни (не месяцы и не годы). Таким образом:
1 |
specificDate.until(currentDate, ChronoUnit.DAYS); |
Итак, подсчёты говорят, что от одной даты до другой ровно 683 дня.
Есть и третий способ – воспользоваться методом datesUntil(). Он возвращает stream дат между двумя датами. А метод count() у этого stream-а вернёт количество этих дат – то есть дней между датами.
1 2 |
specificDate.datesUntil(currentDate); specificDate.datesUntil(currentDate).count(); |
Как видим, count() у stream-а вернул действительно ровно столько же дней (683), как и предыдущий способ подсчёта. Далее начинается пересчёт всех дат между этими двумя датами. Они идут подряд с разницей в один день каждая. Кто знает, может, и такое кому-нибудь пригодится.
Время
Класс LocalTime предоставляет функционал по управлению временем в Java. Вот как можно создавать время:
1 2 3 4 5 |
LocalTime currentTime = LocalTime.now(); LocalTime midnight = LocalTime.MIDNIGHT; LocalTime noon = LocalTime.NOON; LocalTime firstTime = LocalTime.of(12, 30, 14); LocalTime secondTime = LocalTime.of(16, 17, 5); |
В значение времени можно задавать часы и минуты. А можно дополнительно указать секунды. Более того, можно указать и наносекунды. В currentTime, то есть LocalTime.now() после 34 и точки как раз идут доли этой секунды.
Теперь получим данные из объекта времени firstTime – часы, минуты, секунды, наносекунды:
1 2 3 4 5 |
firstTime.getHour(); firstTime.getMinute(); firstTime.getSecond(); firstTime.getNano(); firstTime.toSecondOfDay(); |
Итак, время 12:30:14 нам удалось разбить на отдельные составляющие – часы, минуты, секунды и наносекунды, коих 0. Кроме того, мы сумели получить порядковую секунду дня – и на момент времени 12:30:14 это 45014-ая секунда. Вполне неплохо: всего в дне 86400, а мы установили чуть позже полудня. Вроде сходится.
Как и даты, время можно сравнивать. Делается это такими же методами isAfter() и isBefore(). Правда, isEqual() нету, можно использовать вместо него equals().
1 2 3 |
firstTime.isAfter(secondTime); firstTime.isBefore(secondTime); firstTime.equals(secondTime); |
Да, 12:30 действительно раньше, чем 16:17. В свою очередь, 16:17 не раньше, чем 12:30. И уж точно они не равны.
Ещё аналогичным образом, как и с датой, можно прибавлять и отнимать время:
1 2 3 |
firstTime.plusHours(2); firstTime.minusMinutes(48); firstTime.minusSeconds(1230); |
Удобно, не правда ли? Это очень упрощает жизнь. Также есть методы для наносекунд, кстати – plusNanos() и minusNanos().
Для нахождения продолжительности времени между двумя LocalTime, используется класс Duration.
1 2 3 4 5 |
Duration duration = Duration.between(firstTime, secondTime); duration.toMillis(); duration.toSeconds(); duration.toMinutes(); duration.toHours(); |
Видно, что между 12:30:14 и 16:17:05 разница во времени – 3 часа 46 минут 51 секунда. Всего это 13611000 миллисекунд, 13611 секунд, 226 минут или 3 часа. Причём при получении общего количества минут отбрасываются секунды, а при получении общего количества часов отбрасываются минуты.
И дата, и время
Если вам нужно управлять и датой, и временем, вам подойдёт класс LocalDateTime. По сути, это просто комбинация данных из LocalDate и LocalTime. Вот так можно создать объект LocalDateTime:
1 2 3 |
LocalDateTime currentDateTime = LocalDateTime.now(); LocalDateTime firstDateTime = LocalDateTime.of(2022, 4, 15, 12, 30, 14); LocalDateTime secondDateTime = LocalDateTime.of(2024, 2, 27, 16, 17, 5); |
Для создания объекта с произвольной датой и временем нужно внести 6 значений: год, месяц, день, час, минута, секунда. Также можно опустить секунды или добавить наносекунды.
В целом, все остальные методы схожи с LocalDate и LocalTime. Ведь LocalDateTime может точно так же возвращать значения своих характеристик. Может он и прибавлять/отнимать дни, недели, месяцы, годы (а также часы, минуты, секунды). Он может и находить разницу между датами и временем. И, конечно, определять, какие дата и время были до, а какие после. Даже называются методы точно так же.
Часовые пояса
В настоящих проектах локальной датой и временем обычно дело не обходится. Ведь пользователи могут быть из разных часовых поясов, а время согласовать каким-то образом надо. Поэтому применяется класс ZonedDateTime, который хранит не только время и дату, но и часовой пояс. Таким образом, можно будет сравнивать хронологию временных меток пользователей даже из разных стран.
Чтобы создать ZonedDateTime, можно вводить много int-ов характеристик даты и времени. А можно создать объект из готового LocalDateTime, просто указав часовой пояс:
1 2 3 4 5 6 7 |
LocalDateTime currentDateTime = LocalDateTime.now(); LocalDateTime firstDateTime = LocalDateTime.of(2022, 4, 15, 12, 30, 14); LocalDateTime secondDateTime = LocalDateTime.of(2024, 2, 27, 16, 17, 5); ZonedDateTime currentZonedDateTime = ZonedDateTime.of(currentDateTime, ZoneId.systemDefault()); ZonedDateTime firstZonedDateTime = ZonedDateTime.of(firstDateTime, ZoneId.of("America/New_York")); ZonedDateTime secondZonedDateTime = ZonedDateTime.of(secondDateTime, ZoneId.of("Europe/Paris")); |
Выводится много цифр и букв. Как видно в консоли, на моём компьютере стоит часовой пояс UTC+3. В Нью-Йорке он UTC+4. А в Париже – UTC+1.
В общем-то и всё. Ведь ZonedDateTime, как и LocalDateTime, реализует те же методы, что есть в LocalDate и LocalTime.
В итоге, в этой статье мы рассмотрели 4 класса по работе с датой и временем. Шучу, на самом деле 6: ещё Period и Duration. И 3 enum-а (Month, DayOfWeek, ChronoUnit). Что ж, они позволяют создавать, форматировать, преобразовывать и выполнять операции с датой и временем в Java с учетом временной зоны или без неё. И это удобно.