RegEx pour faire correspondre le modèle de date en Java

RegEx pour faire correspondre le modèle de date en Java

1. introduction

Les expressions régulières sont un outil puissant pour faire correspondre différents types de modèles lorsqu'elles sont utilisées correctement.

Dans cet article, nous utiliserons le packagejava.util.regex pour déterminer si unString donné contient une date valide ou non.

Pour une introduction aux expressions régulières, reportez-vous àour Guide To Java Regular Expressions API.

2. Présentation du format de date

Nous allons définir une date valide par rapport au calendrier grégorien international. Notre format suivra le modèle général:YYYY-MM-DD.

Incluons également le concept d'une annéeleap qui est une année contenant un jour du 29 février. According to the Gregorian calendar, we’ll call a year leap if the year number can be divided evenly by 4 except for those which are divisible by 100 but including those which are divisible by 400.

Dans tous les autres cas, nous appellerons une annéeregular.

Exemples de dates valides:

  • 31/12/2017

  • 2020-02-29

  • 2400-02-29

Exemples de dates non valides:

  • 2017/12/31: délimiteur de jeton incorrect

  • 2018-1-1: zéros non significatifs manquants

  • 2018-04-31: les mauvais jours comptent pour avril

  • 2100-02-29: cette année n'est pas bissextile car la valeur se divise par100, donc février est limité à 28 jours

3. Mettre en œuvre une solution

Puisque nous allons faire correspondre une date à l'aide d'expressions régulières, commençons par esquisser une interfaceDateMatcher, qui fournit une seule méthodematches:

public interface DateMatcher {
    boolean matches(String date);
}

Nous allons présenter la mise en œuvre étape par étape ci-dessous, en vue d'une solution complète à la fin.

3.1. Correspondance au format large

Nous allons commencer par créer un prototype très simple gérant les contraintes de format de notre matcher:

class FormattedDateMatcher implements DateMatcher {

    private static Pattern DATE_PATTERN = Pattern.compile(
      "^\\d{4}-\\d{2}-\\d{2}$");

    @Override
    public boolean matches(String date) {
        return DATE_PATTERN.matcher(date).matches();
    }
}

Ici, nous spécifions quea valid date must consist of three groups of integers separated by a dash. Le premier groupe est composé de quatre entiers, les deux autres groupes ayant chacun deux entiers.

Dates correspondantes:2017-12-31,2018-01-31,0000-00-00,1029-99-72

Dates non concordantes:2018-01,2018-01-XX,2020/02/29

3.2. Correspondance avec le format de date spécifique

Notre deuxième exemple accepte des plages de jetons de date ainsi que notre contrainte de formatage. Pour simplifier, nous avons limité notre intérêt aux années 1900 à 2999.

Maintenant que nous avons réussi à faire correspondre notre format de date général, nous devons limiter cela davantage - pour nous assurer que les dates sont réellement correctes:

^((19|2[0-9])[0-9]{2})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$

Ici, nous avons introduit troisgroups de plages d'entiers qui doivent correspondre:

  • (19|2[0-9])[0-9]{2} couvre une plage limitée d'années en faisant correspondre un nombre qui commence par19 ou2X suivi de quelques chiffres quelconques.

  • 0[1-9]|1[012] correspond à un numéro de mois dans une plage de01-12

  • 0[1-9]|[12][0-9]|3[01] correspond à un numéro de jour dans une plage de01-31

Dates correspondantes:1900-01-01,2205-02-31,2999-12-31

Dates non concordantes:1899-12-31,2018-05-35,2018-13-05,3000-01-01,2018-01-XX

3.3. Match du 29 février

Afin de faire correspondre correctement les années bissextiles, nous devons d'abordidentify when we have encountered a leap year, puis nous assurer que nous acceptons le 29 février comme date valide pour ces années.

Comme le nombre d'années bissextiles dans notre plage restreinte est suffisant, nous devrions utiliser les règles de divisibilité appropriées pour les filtrer:

  • Si le nombre formé par les deux derniers chiffres d'un nombre est divisible par 4, le nombre d'origine est divisible par 4

  • Si les deux derniers chiffres du nombre sont 00, le nombre est divisible par 100

Voici une solution:

^((2000|2400|2800|(19|2[0-9](0[48]|[2468][048]|[13579][26])))-02-29)$

Le modèle comprend les parties suivantes:

  • 2000|2400|2800 correspond à un ensemble d'années bissextiles avec un diviseur de400 dans une plage restreinte de1900-2999

  • 19|2[0-9](0[48]|[2468][048]|[13579][26])) correspond à toutes les combinaisons d'annéeswhite-list qui ont un diviseur de4 et qui n'ont pas de diviseur de100

  • -02-29 correspond àFebruary 2nd

Dates correspondantes:2020-02-29,2024-02-29,2400-02-29

Dates non concordantes:2019-02-29,2100-02-29,3200-02-29,2020/02/29

3.4. Matching General Days of Février

En plus de correspondre au 29 février pour les années bissextiles,we also need to match all other days of February (1 – 28) in all years:

^(((19|2[0-9])[0-9]{2})-02-(0[1-9]|1[0-9]|2[0-8]))$

Dates correspondantes:2018-02-01,2019-02-13,2020-02-25

Dates non concordantes:2000-02-30,2400-02-62,2018/02/28

3.5. Correspondance des mois de 31 jours

Les mois de janvier, mars, mai, juillet, août, octobre et décembre doivent correspondre pendant 1 à 31 jours:

^(((19|2[0-9])[0-9]{2})-(0[13578]|10|12)-(0[1-9]|[12][0-9]|3[01]))$

Dates correspondantes:2018-01-31,2021-07-31,2022-08-31

Dates non concordantes:2018-01-32,2019-03-64,2018/01/31

3.6. Mois de 30 jours correspondants

Les mois d’avril, juin, septembre et novembre doivent correspondre entre 1 et 30 jours:

^(((19|2[0-9])[0-9]{2})-(0[469]|11)-(0[1-9]|[12][0-9]|30))$

Dates correspondantes:2018-04-30,2019-06-30,2020-09-30

Dates non concordantes:2018-04-31,2019-06-31,2018/04/30

3.7. Date matcher grégorienne

Nous pouvons maintenantcombine all of the patterns above into a single matcher to have a complete GregorianDateMatcher satisfaire toutes les contraintes:

class GregorianDateMatcher implements DateMatcher {

    private static Pattern DATE_PATTERN = Pattern.compile(
      "^((2000|2400|2800|(19|2[0-9](0[48]|[2468][048]|[13579][26])))-02-29)$"
      + "|^(((19|2[0-9])[0-9]{2})-02-(0[1-9]|1[0-9]|2[0-8]))$"
      + "|^(((19|2[0-9])[0-9]{2})-(0[13578]|10|12)-(0[1-9]|[12][0-9]|3[01]))$"
      + "|^(((19|2[0-9])[0-9]{2})-(0[469]|11)-(0[1-9]|[12][0-9]|30))$");

    @Override
    public boolean matches(String date) {
        return DATE_PATTERN.matcher(date).matches();
    }
}

We’ve used an alternation character “|” to match at least one des quatre branches. Ainsi, la date valide de février correspond soit à la première branche du 29 février d'une année bissextile, soit à la deuxième branche de n'importe quel jour de1 à28. Les dates des mois restants correspondent aux troisième et quatrième branches.

Étant donné que nous n’avons pas optimisé ce modèle au profit d’une meilleure lisibilité, n'hésitez pas à en expérimenter une longueur.

En ce moment, nous avons satisfait toutes les contraintes, nous avons introduit au début.

3.8. Remarque sur les performances

Parsing complex regular expressions may significantly affect the performance of the execution flow. L'objectif principal de cet article n'était pas d'apprendre un moyen efficace de tester une chaîne pour son appartenance à un ensemble de toutes les dates possibles.

Envisagez d'utiliser lesLocalDate.parse() fournis par Java8 si une approche fiable et rapide pour valider une date est nécessaire.

4. Conclusion

Dans cet article, nous avons appris à utiliser des expressions régulières pour faire correspondre la date strictement formatée du calendrier grégorien en fournissant également des règles de format, de plage et de longueur de mois.

Tout le code présenté dans cet article est disponibleover on Github. Ceci est un projet basé sur Maven, il devrait donc être facile à importer et à exécuter tel quel.