Calculer les plages de dates et d’heures dans Groovy
Utilisez la date et l'heure Groovy pour découvrir et afficher les incréments de temps.
De temps en temps, je dois faire des calculs liés aux dates. Il y a quelques jours, un collègue m'a demandé de mettre en place une nouvelle définition de projet dans notre système de gestion de projet (open source bien sûr !). Ce projet doit démarrer le 1er août et se terminer le 31 décembre. Le service à fournir est budgété à 10 heures par semaine.
Donc, oui, j'ai dû déterminer combien de semaines entre le 01/08/2021 et le 31/12/2021 inclus.
C'est le genre de problème parfait à résoudre avec un petit script Groovy.
Installer Groovy sur Linux
Groovy est basé sur Java, il nécessite donc une installation Java. Une version récente et décente de Java et Groovy pourrait se trouver dans les référentiels de votre distribution Linux. Alternativement, vous pouvez installer Groovy en suivant les instructions sur groovy-lang.org.
Une alternative intéressante pour les utilisateurs de Linux est SDKMan, qui peut être utilisé pour obtenir plusieurs versions de Java, Groovy et de nombreux autres outils associés. Pour cet article, j'utilise la version OpenJDK11 de ma distribution et la dernière version Groovy de SDKMan.
Résoudre le problème avec Groovy
Depuis Java 8, les calculs d'heure et de date ont été intégrés dans un nouveau package appelé java.time, et Groovy y donne accès. Voici le script :
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"
Copiez ce code dans un fichier appelé wb.groovy et exécutez-le sur la ligne de commande pour voir les résultats :
$ groovy wb.groovy
21 weeks between 2021-08-01 and 2022-01-01
Passons en revue ce qui se passe.
Date et l'heure
La classe java.time.LocalDate fournit de nombreuses méthodes statiques utiles (comme parse() présentée ci-dessus, qui nous permet de convertir une chaîne en un LocalDate ). instance selon un modèle, en l'occurrence 'aaaa-MM-jj'). Les caractères de format sont expliqués à de nombreux endroits, par exemple dans la documentation de java.time.format.DateTimeFormat. Notez que M représente « mois », et non m, qui représente « minute ». " Ainsi, ce modèle définit une date formatée comme une année à quatre chiffres, suivie d'un trait d'union, suivi d'un numéro de mois à deux chiffres (1-12), suivi d'un autre trait d'union, suivi d'un jour du mois à deux chiffres. numéro (1-31).
Notez également qu'en Java, parse() nécessite une instance de DateTimeFormat :
parse(CharSequence text, DateTimeFormatter formatter)
En conséquence, l'analyse devient une opération en deux étapes, alors que Groovy fournit une version supplémentaire de parse() qui accepte la chaîne de format directement à la place de l'instance DateTimeFormat.
La classe java.time.temporal.ChronoUnit, en fait une Enum, fournit plusieurs constantes Enum, comme WEEKS ( ou DAYS, ou SIECLES...) qui à leur tour fournissent la méthode between() qui nous permet de calculer l'intervalle de ces unités entre deux LocalDates (ou d'autres types de données de date ou d'heure similaires). Notez que j'ai utilisé le 1er janvier 2022 comme valeur pour end ; en effet, between() s'étend sur la période commençant à la première date indiquée jusqu'à la deuxième date indiquée, mais sans l'inclure.
Plus d'arithmétique de date
De temps en temps, j'ai besoin de savoir combien de jours ouvrables il y a dans une période spécifique (comme, disons, un mois). Ce script pratique calculera cela pour moi :
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"
Copiez ce code dans un fichier appelé wdb.groovy et exécutez-le depuis la ligne de commande pour voir les résultats :
$ groovy wdb.groovy
22 working day(s) between 2021-08-01 and 2021-09-01
Passons en revue cela.
Tout d’abord, je crée un ensemble de dates de vacances (ce sont les « jours feriados » du Chili pour 2021, au cas où vous vous poseriez la question) appelé HolidaySet. Notez que le modèle par défaut pour LocalDate.parse() est « aaaa-MM-jj », j'ai donc laissé le modèle ici. Notez également que j'utilise le raccourci Groovy [a,b,c] pour créer une Liste, puis la forcer à un Ensemble .
Ensuite, je souhaite ignorer les samedis et les dimanches, je crée donc un autre ensemble incorporant deux valeurs enum de java.time.DayOfWeek–SATURDAY et DIMANCHE.
Ensuite je définis une méthode calcWorkingDays() qui prend comme arguments la date de début, la date de fin (qui suivant l'exemple précédent de between() est la première valeur en dehors de la plage Je veux prendre en compte), l'ensemble des jours fériés et l'ensemble des jours de week-end. Ligne par ligne, cette méthode :
Définit une plage entre start et end, ouverte à la end (c'est ce que signifie
) et exécute l'argument de fermeture passé à la méthode inject() (inject() implémente le 'reduce' opération sur Liste dans Groovy) sur les éléments successifs d de la plage : - Tant que d n'est ni dans le holidaySet ni dans le weekendDaySet, incrémente le sous-total de 1
Ensuite, je définis les dates de début et de fin entre lesquelles je souhaite calculer les jours ouvrés.
Enfin, j'appelle println en utilisant un Groovy GString pour évaluer la méthode calcWorkingDays() et afficher le résultat.
Notez que j'aurais pu utiliser la fermeture each au lieu de inject, ou même une boucle for. J'aurais également pu utiliser des flux Java plutôt que des plages, des listes et des fermetures Groovy. Beaucoup d'options.
Mais pourquoi ne pas utiliser groovy.Date ?
Certains d'entre vous, les anciens utilisateurs de Groovy, se demandent peut-être pourquoi je n'utilise pas le bon vieux groovy.Date. La réponse est que je pourrais l’utiliser. Mais Groovy Date est basé sur Java Date, et il y a de bonnes raisons de passer à java.time, même si Groovy Date a ajouté pas mal de choses intéressantes à Java Date.
Pour moi, la raison principale est qu'il y a des décisions de conception pas si géniales enfouies dans l'implémentation de Java Date, la pire étant qu'il est inutilement modifiable. J'ai passé un certain temps à traquer un bug étrange résultant de ma mauvaise compréhension de la méthode clearTime() sur Groovy Date. J'ai appris qu'il efface en fait le champ horaire de l'instance de date, plutôt que de renvoyer la valeur de date avec la partie heure définie sur « 00:00:00 ».
Les instances de date ne sont pas non plus thread-safe, ce qui peut être un peu difficile pour les applications multithread.
Enfin, avoir à la fois la date et l’heure dans un seul champ n’est pas toujours pratique et peut conduire à d’étranges contorsions dans la modélisation des données. Pensez, par exemple, à un jour au cours duquel plusieurs événements se produisent : idéalement, le champ date serait sur le jour et le champ heure serait sur chaque événement ; mais ce n’est pas facile à faire avec Groovy Date.
Groovy est génial
Groovy est un projet Apache et fournit une syntaxe simplifiée pour Java afin que vous puissiez l'utiliser pour des scripts simples et rapides en plus des applications complexes. Vous conservez la puissance de Java, mais vous y accédez avec un ensemble d'outils efficaces. Essayez-le bientôt et voyez si vous trouvez votre rythme avec Groovy.