I need to write an regular expression that matches Cron time expressions, like these ones:

  • 28 2 7 1 1
  • 28 2 7 1 *
  • 28 2 7 * *
  • 28 2 * * *
  • 28 * * * *
  • * * * * *

To validate cron expressions in general, it'll depend greatly on the specific implementation you're using

Rules

General Format

In general, most adhere to the the following format:

| Field name | Mandatory? | Allowed values | Special characters | | ------------ | ---------- | --------------- | ------------------ | | Seconds | No* | 0-59 | * / , - | | Minutes | Yes | 0-59 | * / , - | | Hours | Yes | 0-23 | * / , - | | Day of month | Yes | 1-31 | * / , - L W | | Month | Yes | 1-12 or JAN-DEC | * / , - | | Day of week | Yes | 0-6 or SUN-SAT | * / , - L # | | Year | No* | 1970–2099 | * / , - |

<sup>*where seconds and years are non-standard and sometimes not included</sup>

Predefined Scheduling Macros:

Some flavors allow predefined time periods like this:

| Entry | Equivalent to | | --------- | --------------- | | @annually | 0 0 0 1 1 * * | | @yearly | 0 0 0 1 1 * * | | @monthly | 0 0 0 1 * * * | | @weekly | 0 0 0 * * 0 * | | @daily | 0 0 0 * * * * | | @hourly | 0 0 * * * * * | | @reboot | |

Intervals

Some flavors allow using the @every <duration> syntax with the following timespan units:

| Unit | <a href="https://en.wikipedia.org/wiki/Orders_of_magnitude_(time)">Definition</a> | | ---------- | --------------------------------------------------------------------------------- | | ns | nanosecond | | us, µs | microsecond | | ms | millisecond | | s | second | | m | minute | | h | hour |

Validating

Predefined Macros Regex

To validate predefined macros, you can use the following regex:

/@(annually|yearly|monthly|weekly|daily|hourly|reboot)/

Which will pass the following test cases:

@daily
@hourly

@every Regex

To validate @every durations, you can use the following regex:

/@every (\d+(ns|us|µs|ms|s|m|h))+/

Which will pass the following test cases:

@every 5s
@every 20h30m

Individual Cron Terms

Validating cron terms in the regular expression is a little trickier since there are so many variations.

Just focusing in on a single term, the following are all valid:

  • Two or more numbers separated by a ,
  • Two numbers separated by a / or -
  • A 1-2 digit number
  • A single *

To validate a single term, we can use the following regex:

/(\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*/

<sup>where \d+ just guarantees you have 1 or more numeric digits</sup>

Which will pass the following test cases:

1,2,3
1,2
1/2
1-2
1
*

Combining Cron Terms

To check the full expression, we can just make sure we have {5,7} terms with the following regex:

/(((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,7}/

If we wanted to distinguish between each term, we'd need to validate numbers within a certain range:

| Allowed values | Regex | | -------------- | --------------------------- | | 0-59 | [1-5]?[0-9] | | 0-23 | 2[0-3]|1[0-9]|[0-9] | | 1-31 | 3[01]|[12][0-9]|[1-9] | | 1-12 | 1[0-2]|[1-9] | | 0-6 | [0-6] | | 1970–2099 | 19[7-9][0-9]|20[0-9][0-9] |

If however, we just want to make sure something looks like a regex expression without worrying about which term is days vs. hours, the expression stays a lot cleaner, so that's what I'll go with for simplicity

Putting it all together

By combining the above statements, we can have a relatively simple sanity check that the term looks regex-y with the following expression:

/(@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(@every (\d+(ns|us|µs|ms|s|m|h))+)|((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,7})/

Additional Resources