Рассчитать диапазоны дат и времени в Groovy
Используйте дату и время Groovy для обнаружения и отображения приращений времени.
Время от времени мне нужно выполнять некоторые вычисления, связанные с датами. Несколько дней назад коллега попросил меня настроить новое определение проекта в нашей (конечно, с открытым исходным кодом!) системе управления проектами. Этот проект начнется 1 августа и завершится 31 декабря. Предоставление услуг запланировано в бюджете на 10 часов в неделю.
Итак, да, мне нужно было выяснить, сколько недель между 01.08.2021 и 31.12.2021 включительно.
Это идеальная задача, которую можно решить с помощью крошечного сценария Groovy.
Установите Groovy в Linux
Groovy основан на Java, поэтому требует установки Java. В репозиториях вашего дистрибутива Linux могут находиться как последние, так и достойные версии Java и Groovy. Альтернативно вы можете установить Groovy, следуя инструкциям на сайте groovy-lang.org.
Хорошей альтернативой для пользователей Linux является SDKMan, который можно использовать для получения нескольких версий Java, Groovy и многих других связанных инструментов. Для этой статьи я использую версию OpenJDK11 моего дистрибутива и последнюю версию Groovy SDKMan.
Решение проблемы с Groovy
Начиная с Java 8, вычисления времени и даты были объединены в новый пакет под названием java.time, и Groovy предоставляет к нему доступ. Вот сценарий:
import java.time.*
import java.time.temporal.*
def start = LocalDate.parse('2021-08-01','yyyy-MM-dd')
def end = LocalDate.parse('2022-01-01','yyyy-MM-dd')
println "${ChronoUnit.WEEKS.between(start,end)} weeks between $start and $end"
Скопируйте этот код в файл с именем wb.groovy и запустите его в командной строке, чтобы увидеть результаты:
$ groovy wb.groovy
21 weeks between 2021-08-01 and 2022-01-01
Давайте рассмотрим, что происходит.
Дата и время
Класс java.time.LocalDate предоставляет множество полезных статических методов (например, parse(), показанный выше, который позволяет нам преобразовывать строку в LocalDate. > экземпляр по шаблону, в данном случае 'гггг-ММ-дд'). Символы формата объясняются во многих местах, например, в документации по java.time.format.DateTimeFormat. Обратите внимание, что M означает «месяц», а не m, который означает «минуту». Таким образом, этот шаблон определяет дату в формате четырехзначного года, за которым следует дефис, за которым следует двузначный номер месяца (1–12), за которым следует еще один дефис, за которым следует двухзначный день месяца. номер (1-31).
Также обратите внимание, что в Java для parse() требуется экземпляр DateTimeFormat:
parse(CharSequence text, DateTimeFormatter formatter)
В результате синтаксический анализ становится двухэтапной операцией, тогда как Groovy предоставляет дополнительную версию parse(), которая принимает строку формата непосредственно вместо экземпляра DateTimeFormat.
Класс java.time.temporal.ChronoUnit, фактически являющийся Enum, предоставляет несколько констант Enum, например WEEKS ( или DAYS, или CENTURIES...), которые, в свою очередь, предоставляют метод between(), который позволяет нам вычислить интервал этих единиц между два LocalDates (или другие подобные типы данных даты или времени). Обратите внимание, что в качестве значения end я использовал 1 января 2022 г.; это связано с тем, что between() охватывает период времени, начиная с первой указанной даты и заканчивая второй указанной датой, но не включая ее.
Дополнительная арифметика дат
Время от времени мне нужно знать, сколько рабочих дней приходится на определенный период времени (например, месяц). Этот удобный скрипт рассчитает это для меня:
import java.time.*
def holidaySet = [LocalDate.parse('2021-01-01'), LocalDate.parse('2021-04-02'),
LocalDate.parse('2021-04-03'), LocalDate.parse('2021-05-01'),
LocalDate.parse('2021-05-15'), LocalDate.parse('2021-05-16'),
LocalDate.parse('2021-05-21'), LocalDate.parse('2021-06-13'),
LocalDate.parse('2021-06-21'), LocalDate.parse('2021-06-28'),
LocalDate.parse('2021-06-16'), LocalDate.parse('2021-06-18'),
LocalDate.parse('2021-08-15'), LocalDate.parse('2021-09-17'),
LocalDate.parse('2021-09-18'), LocalDate.parse('2021-09-19'),
LocalDate.parse('2021-10-11'), LocalDate.parse('2021-10-31'),
LocalDate.parse('2021-11-01'), LocalDate.parse('2021-11-21'),
LocalDate.parse('2021-12-08'), LocalDate.parse('2021-12-19'),
LocalDate.parse('2021-12-25')] as Set
def weekendDaySet = [DayOfWeek.SATURDAY,DayOfWeek.SUNDAY] as Set
int calcWorkingDays(start, end, holidaySet, weekendDaySet) {
(start..<end).inject(0) { subtotal, d ->
if (!(d in holidaySet || DayOfWeek.from(d) in weekendDaySet))
subtotal + 1
else
subtotal
}
}
def start = LocalDate.parse('2021-08-01')
def end = LocalDate.parse('2021-09-01')
println "${calcWorkingDays(start,end,holidaySet,weekendDaySet)} working day(s) between $start and $end"
Скопируйте этот код в файл с именем wdb.groovy и запустите его из командной строки, чтобы увидеть результаты:
$ groovy wdb.groovy
22 working day(s) between 2021-08-01 and 2021-09-01
Давайте рассмотрим это.
Сначала я создаю набор праздничных дат (если вам интересно, это чилийские дни праздников на 2021 год) под названием HolidaySet. Обратите внимание, что шаблон по умолчанию для LocalDate.parse() — «гггг-ММ-дд», поэтому я оставил этот шаблон здесь. Также обратите внимание, что я использую сокращение Groovy [a,b,c] для создания List, а затем привожу его к Set .
Далее я хочу пропустить субботу и воскресенье, поэтому создаю еще один набор, включающий два значения enum: java.time.DayOfWeek–SATURDAY и ВОСКРЕСЕНЬЕ.
Затем я определяю метод calcWorkingDays() , который принимает в качестве аргументов дату начала и дату окончания (которая, согласно предыдущему примеру between(), является первым значением вне диапазона Хочу рассмотреть), праздничный набор, и набор выходного дня. Построчно, этот метод:
Определяет диапазон между start и end, открывается в end (это то, что означает
) и выполняется аргумент закрытия, передаваемый методу inject() (inject(), реализует метод "сокращение" операция над List в Groovy) для последовательных элементов d в диапазоне: - Пока d не находится ни в holidaySet, ни в weekendDaySet, промежуточный итог увеличивается на 1.
Затем я определяю даты начала и окончания, между которыми я хочу рассчитать рабочие дни.
Наконец, я вызываю println, используя Groovy GString, чтобы оценить метод calcWorkingDays() и отобразить результат.
Обратите внимание, что я мог бы использовать замыкание each вместо inject или даже цикл for. Я мог бы также использовать Java Streams вместо диапазонов, списков и замыканий Groovy. Множество вариантов.
Но почему бы не использовать groovy.Date?
Некоторые из вас, старых пользователей Groovy, могут задаться вопросом, почему я не использую старый добрый groovy.Date. Ответ таков: я мог бы это использовать. Но Groovy Date основан на Java Date, и есть несколько веских причин для перехода на java.time, даже несмотря на то, что Groovy Date добавил немало приятных вещей в Java Date.
На мой взгляд, основная причина заключается в том, что в реализации Java Date скрыты некоторые не очень хорошие дизайнерские решения, худшее из которых заключается в том, что она излишне изменчива. Я потратил некоторое время на поиск странной ошибки, возникшей из-за моего плохого понимания метода clearTime() в Groovy Date. Я узнал, что на самом деле он очищает поле времени экземпляра даты, а не возвращает значение даты с частью времени, установленной на «00:00:00».
Экземпляры Date также не являются потокобезопасными, что может быть непросто для многопоточных приложений.
Наконец, объединение даты и времени в одно поле не всегда удобно и может привести к некоторым странным искажениям при моделировании данных. Представьте себе, например, день, в который происходит несколько событий: в идеале поле дата должно указывать день, а поле время — каждое событие; но это непросто сделать с Groovy Date.
классный есть классный
Groovy — это проект Apache, который предоставляет упрощенный синтаксис для Java, поэтому вы можете использовать его для быстрых и простых сценариев в дополнение к сложным приложениям. Вы сохраняете возможности Java, но получаете к ним доступ с помощью эффективного набора инструментов. Попробуйте скорее и посмотрите, найдете ли вы свой вкус с Groovy.