Rexact
Cheatsheet

Regex cron expression

The reference question's top-voted answer (80 votes) gives a regex that doesn't distinguish field position: a comment points out, with a concrete example, that it validates "* * 30 * * *" as a correct cron expression even though that sixth field has no business being there. Another answer (16 votes) does check each field's range, but a comment shows it rejects comma-separated lists like "0 3,15 * * *". Here's a pattern that combines per-position ranges AND lists/steps, compared against both of those attempts and a third - a community synthesis of earlier answers that nonetheless inherits the same position-blindness as the first.

/^(?:\*|[0-5]?[0-9](?:-[0-5]?[0-9])?)(?:/[0-9]+)?(?:,(?:\*|[0-5]?[0-9](?:-[0-5]?[0-9])?)(?:/[0-9]+)?)* (?:\*|(?:[01]?[0-9]|2[0-3])(?:-(?:[01]?[0-9]|2[0-3]))?)(?:/[0-9]+)?(?:,(?:\*|(?:[01]?[0-9]|2[0-3])(?:-(?:[01]?[0-9]|2[0-3]))?)(?:/[0-9]+)?)* (?:\*|(?:[1-9]|[12][0-9]|3[01])(?:-(?:[1-9]|[12][0-9]|3[01]))?)(?:/[0-9]+)?(?:,(?:\*|(?:[1-9]|[12][0-9]|3[01])(?:-(?:[1-9]|[12][0-9]|3[01]))?)(?:/[0-9]+)?)* (?:\*|(?:[1-9]|1[0-2])(?:-(?:[1-9]|1[0-2]))?)(?:/[0-9]+)?(?:,(?:\*|(?:[1-9]|1[0-2])(?:-(?:[1-9]|1[0-2]))?)(?:/[0-9]+)?)* (?:\*|[0-6](?:-[0-6])?)(?:/[0-9]+)?(?:,(?:\*|[0-6](?:-[0-6])?)(?:/[0-9]+)?)*$/
passes 21/21 cases

Comparison of existing patterns

SourcepatternScore on the corpusReDoS
Stack Overflow — réponse la plus votée (80 votes), ne distingue pas la position des champs (accepte « * * 30 * * * », bug signalé en commentaire)^(((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,7}$13 / 21undetermined
Stack Overflow — réponse à 16 votes, plages correctes par position mais rejette les listes séparées par virgules (bug signalé en commentaire sur « 0 3,15 * * * »)^(\*|([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])|\*\/([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])) (\*|([0-9]|1[0-9]|2[0-3])|\*\/([0-9]|1[0-9]|2[0-3])) (\*|([1-9]|1[0-9]|2[0-9]|3[0-1])|\*\/([1-9]|1[0-9]|2[0-9]|3[0-1])) (\*|([1-9]|1[0-2])|\*\/([1-9]|1[0-2])) (\*|([0-6])|\*\/([0-6]))$18 / 21safe
GitHub Gist — synthèse communautaire des réponses du même fil (« credit to Juarez and Slava »), hérite du même défaut de position que la réponse à 80 votes^((((\d+,)+\d+|(\d+(\/|-|#)\d+)|\d+L?|\*(\/\d+)?|L(-\d+)?|\?|[A-Z]{3}(-[A-Z]{3})?) ?){5,7})$14 / 21undetermined

Implementation

const re = new RegExp("^(?:\\*|[0-5]?[0-9](?:-[0-5]?[0-9])?)(?:/[0-9]+)?(?:,(?:\\*|[0-5]?[0-9](?:-[0-5]?[0-9])?)(?:/[0-9]+)?)* (?:\\*|(?:[01]?[0-9]|2[0-3])(?:-(?:[01]?[0-9]|2[0-3]))?)(?:/[0-9]+)?(?:,(?:\\*|(?:[01]?[0-9]|2[0-3])(?:-(?:[01]?[0-9]|2[0-3]))?)(?:/[0-9]+)?)* (?:\\*|(?:[1-9]|[12][0-9]|3[01])(?:-(?:[1-9]|[12][0-9]|3[01]))?)(?:/[0-9]+)?(?:,(?:\\*|(?:[1-9]|[12][0-9]|3[01])(?:-(?:[1-9]|[12][0-9]|3[01]))?)(?:/[0-9]+)?)* (?:\\*|(?:[1-9]|1[0-2])(?:-(?:[1-9]|1[0-2]))?)(?:/[0-9]+)?(?:,(?:\\*|(?:[1-9]|1[0-2])(?:-(?:[1-9]|1[0-2]))?)(?:/[0-9]+)?)* (?:\\*|[0-6](?:-[0-6])?)(?:/[0-9]+)?(?:,(?:\\*|[0-6](?:-[0-6])?)(?:/[0-9]+)?)*$", "");
re.test(input);

Limits

  • Ne couvre que la forme classique à 5 champs (minute heure jour-du-mois mois jour-de-semaine) : ni le champ « secondes » optionnel de certains ordonnanceurs (Quartz, Spring), ni le champ « année » final, ne sont pris en charge.
  • Ne reconnaît pas les noms de mois (JAN-DEC) ni de jours (SUN-SAT), acceptés par de nombreuses implémentations réelles de cron en plus des valeurs numériques : seule la forme numérique est couverte ici.
  • Ne reconnaît pas les macros (« @daily », « @hourly »...) ni la syntaxe d'intervalle (« @every 5m ») que certains ordonnanceurs (notamment Go/Kubernetes CronJob) acceptent en remplacement d'une expression à 5 champs.
  • N'impose pas la cohérence entre jour-du-mois et jour-de-semaine (les deux champs sont interprétés en OU logique par cron quand ils sont tous deux restreints, une subtilité sémantique qu'une regex de forme ne peut pas exprimer), et ne vérifie pas qu'une date comme le 30 février ne peut jamais se produire.