Click here to Skip to main content
14,973,021 members
Articles / General Programming / Regular Expressions
Tip/Trick
Posted 10 Apr 2021

Stats

2.8K views

Regex for Cron Expressions

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
10 Apr 2021CPOL5 min read
Regex that can be used for parsing Cron Expressions
In this article, you will find an explanation of full regular expressions for both POSIX and extended version of cron expression. It can be used to easily parse cron expressions in any programming language that supports regular expressions.

Introduction

Cron is a time-based job scheduler in Unix-like systems. It executes jobs based on specification in a so-called crontab file, which is edited by using crontab command in Unix.

Each line of this file represents a time schedule for running a specific command. The POSIX standard version of cron expression matches job schedule to minute, hour, day of the month, month and day of the week, respectively. There are also extended versions which can define second, and also use some special characters and forms to achieve more scalability in scheduling.

This article explains the full regular expressions for both POSIX and extended version of cron expression. It can be used to easily parse cron expressions in any programming language that supports regular expressions.

Here is more information on cron:

Using the Code

POSIX Version

^(?#minute)(\*|(?:[0-9]|(?:[1-5][0-9]))(?:(?:\-[0-9]|\-(?:[1-5][0-9]))?|
(?:\,(?:[0-9]|(?:[1-5][0-9])))*)) (?#hour)(\*|(?:[0-9]|1[0-9]|2[0-3])
(?:(?:\-(?:[0-9]|1[0-9]|2[0-3]))?|(?:\,(?:[0-9]|1[0-9]|2[0-3]))*)) 
(?#day_of_month)(\*|(?:[1-9]|(?:[12][0-9])|3[01])(?:(?:\-(?:[1-9]|
(?:[12][0-9])|3[01]))?|(?:\,(?:[1-9]|(?:[12][0-9])|3[01]))*)) (?#month)(\*|
(?:[1-9]|1[012]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(?:(?:\-(?:[1-9]|
1[012]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?|(?:\,(?:[1-9]|1[012]|
JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))*)) (?#day_of_week)(\*|
(?:[0-6]|SUN|MON|TUE|WED|THU|FRI|SAT)(?:(?:\-(?:[0-6]|SUN|MON|TUE|WED|THU|FRI|SAT))?|
(?:\,(?:[0-6]|SUN|MON|TUE|WED|THU|FRI|SAT))*))$

The POSIX version of cron expression looks like this:

# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12)
# │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
# │ │ │ │ │                                   7 is also Sunday on some systems)
# │ │ │ │ │
# │ │ │ │ │
# * * * * * <command to execute>

First, the minute is specified, then hour, day of the month and so on. The POSIX version allows multiple values in each field separated by comma (,) or as a range represented by a dash in between two values. Month can be represented as a number (1-12) or as a 3-character string (JAN-DEC). Day of the week can be represented as a number (0-6) or as a 3-character string (SUN-SAT).

"Minute" Expression

(?#minute)(\*|(?:[0-9]|(?:[1-5][0-9]))(?:(?:\-[0-9]|\-(?:[1-5][0-9]))?|
(?:\,(?:[0-9]|(?:[1-5][0-9])))*))
  • (?#minute) => comment
  • \* => character * literally (any minute)
  • (?:) => non-capturing group
  • (?:[0-9]|(?:[1-5][0-9])) => 0-59
  • (?:\-[0-9]|\-(?:[1-5][0-9]))? => possible dash and again number between 0 and 59
  • (?:\,(?:[0-9]|(?:[1-5][0-9])))* => possible indefinite times comma followed by number between 0 and 59

"Hour" Expression

(?#hour)(\*|(?:[0-9]|1[0-9]|2[0-3])(?:(?:\-(?:[0-9]|1[0-9]|2[0-3]))?|
(?:\,(?:[0-9]|1[0-9]|2[0-3]))*))
  • (?#hour) => comment
  • \* => character * literally (any hour)
  • (?:) => non-capturing group
  • (?:[0-9]|1[0-9]|2[0-3]) => 0-23
  • (?:\-(?:[0-9]|1[0-9]|2[0-3]))? => possible dash and again number between 0 and 23
  • (?:\,(?:[0-9]|1[0-9]|2[0-3]))* => possible indefinite times comma followed by number between 0 and 23

"Day of month" Expression

(?#day_of_month)(\*|(?:[1-9]|(?:[12][0-9])|3[01])(?:(?:\-(?:[1-9]|
(?:[12][0-9])|3[01]))?|(?:\,(?:[1-9]|(?:[12][0-9])|3[01]))*))
  • (?#day_of_month) => comment
  • \* => character * literally (any day of month)
  • (?:) => non-capturing group
  • (?:[1-9]|(?:[12][0-9])|3[01]) => 1-31
  • (?:\-(?:[1-9]|(?:[12][0-9])|3[01]))? => possible dash and again number between 1 and 31
  • (?:\,(?:[1-9]|(?:[12][0-9])|3[01]))* => possible indefinite times comma followed by number between 1 and 31

"Month" Expression

(?#month)(\*|(?:[1-9]|1[012]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)
(?:(?:\-(?:[1-9]|1[012]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?|
(?:\,(?:[1-9]|1[012]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))*))
  • (?#month) => comment
  • \* => character * literally (any day of month)
  • (?:) => non-capturing group
  • (?:[1-9]|1[012]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC) => 1-12 or JAN-DEC
  • (?:\-(?:[1-9]|1[012]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))? => possible dash and again number between 1 and 12 or string JAN - DEC
  • (?:\,(?:[1-9]|1[012]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))* => possible indefinite times comma followed by number between 1 and 12 or string JAN - DEC

"Day of week" Expression

(?#day_of_week)(\*|(?:[0-6]|SUN|MON|TUE|WED|THU|FRI|SAT)
(?:(?:\-(?:[0-6]|SUN|MON|TUE|WED|THU|FRI|SAT))?|(?:\,(?:[0-6]|SUN|MON|TUE|WED|THU|FRI|SAT))*))
  • (?#day_of_week) => comment
  • \* => character * literally (any day of month)
  • (?:) => non-capturing group
  • (?:[0-6]|SUN|MON|TUE|WED|THU|FRI|SAT) => 0-6 or SUN-SAT
  • (?:\-(?:[0-6]|SUN|MON|TUE|WED|THU|FRI|SAT))? => possible dash and again number between 0 and 6 or string SUN - SAT
  • (?:\,(?:[1-9]|1[012]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))* => possible indefinite times comma followed by number between 0 and 6 or string SUN - SAT

Extended Version

^(?#second)(\*|(?:\*|(?:[0-9]|(?:[1-5][0-9])))\/(?:[0-9]|(?:[1-5][0-9]))|
(?:[0-9]|(?:[1-5][0-9]))(?:(?:\-[0-9]|\-(?:[1-5][0-9]))?|(?:\,(?:[0-9]|(?:[1-5][0-9])))*)) 
(?#minute)(\*|(?:\*|(?:[0-9]|(?:[1-5][0-9])))\/(?:[0-9]|(?:[1-5][0-9]))|(?:[0-9]|
(?:[1-5][0-9]))(?:(?:\-[0-9]|\-(?:[1-5][0-9]))?|(?:\,(?:[0-9]|(?:[1-5][0-9])))*)) 
(?#hour)(\*|(?:\*|(?:\*|(?:[0-9]|1[0-9]|2[0-3])))\/(?:[0-9]|1[0-9]|2[0-3])|
(?:[0-9]|1[0-9]|2[0-3])(?:(?:\-(?:[0-9]|1[0-9]|2[0-3]))?|(?:\,(?:[0-9]|1[0-9]|2[0-3]))*)) 
(?#day_of_month)(\*|\?|L(?:W|\-(?:[1-9]|(?:[12][0-9])|3[01]))?|(?:[1-9]|(?:[12][0-9])|
3[01])(?:W|\/(?:[1-9]|(?:[12][0-9])|3[01]))?|(?:[1-9]|(?:[12][0-9])|3[01])
(?:(?:\-(?:[1-9]|(?:[12][0-9])|3[01]))?|(?:\,(?:[1-9]|(?:[12][0-9])|3[01]))*)) 
(?#month)(\*|(?:[1-9]|1[012]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)
(?:(?:\-(?:[1-9]|1[012]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?|
(?:\,(?:[1-9]|1[012]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))*)) 
(?#day_of_week)(\*|\?|[0-6](?:L|\#[1-5])?|(?:[0-6]|SUN|MON|TUE|WED|THU|FRI|SAT)
(?:(?:\-(?:[0-6]|SUN|MON|TUE|WED|THU|FRI|SAT))?|(?:\,(?:[0-6]|SUN|MON|TUE|WED|THU|FRI|SAT))*)) 
(?#year)(\*|(?:[1-9][0-9]{3})(?:(?:\-[1-9][0-9]{3})?|(?:\,[1-9][0-9]{3})*))$

Extended version allows in addition specification of seconds in the first field, and year in the last.

Also, it allows a few additional options:

  • \/ => character / literally - can be used in combination with ranges on seconds, minutes and hours to specify step values, i.e., 10/5 for minutes means execution starting from 5th minute every 5 minutes; it is the same as POSIX longer expression 5,10,15,20,25,30,35,40,45,50,55 ; */5 means execute every 5 mins (same as 0/5)
  • L => used with day of week to specify last in month, i.e., 5L is last Friday in month OR used with day of month to specify the last day in month; optionally, you can use minus sign to specify days in relation to last day, ie. L-5 means the 5th day before last day of month
  • W => used with day of month to specify closest weekday, i.e., 15W is closest weekday to 15th of month
  • ? => in some implementations, this is used to leave out day of month or day of week (they cannot be used at the same time)
  • # => used with day of week to specify which week in month (1-5), i.e., 5#3 is third Friday of the month; can be used only with numeric representation

Remarks

It is important to note that these regular expressions only parse and take care of capturing the different fields of the cron expression. Some logically unallowed combinations can still pass, and they need to be handled within the code after parsing the regular expression. For example:

  • 0 0 0 5 * 5 * - in most implementations, it is not allowed to use both day of month and day of week at the same time, so the proper usage would be to put character ? to either day of month or day of week field (but not both); in POSIX form, one of them needs to be * if the other one is specific value (restricted) - if they are both restricted, they need to both match today, otherwise the expression is faulty.
  • 0 0 0 31 2 ? * - this expression is supposed to run on date 31.02., which of course does not exist
  • 0 55-33 * * ? * - specified to run between minutes 55 and 33; the right-hand value of the range should always be higher than the left-hand value
  • 0 0 0 ? FEB 4#5 2021 - this refers to 00:00:00 on Wednesday of the 5th week of February 2021 - there are no 5 weeks in Feb 21, so this expression would mean that the command will never run
  • 0 */26 * ? * * * - in general, only steps that are divisible with the maximum number can be used; in this case, we want to run command every 26 minutes, which will be legal, but would leave a yield an uneven short period at the end of the time unit - in other words, it would not run every 26 min, but at 0:26, 0:52, 1:26, 1:52, etc.
  • 0 0 0 ? * * 2222 - regular expression allows years from 1000-9999, but most implementations of cron allow only years 1970-2099

History

  • 11th April, 2021: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Marijan Nikic
User Interface Analyst Raiffeisenbank Austria
Croatia Croatia
I acquired Masters degree in computing science at the Faculty of Electrical Engineering and Computing in Zagreb, Croatia in 2009. Following my studies, I got a job in a Croatian branch of Austrian-based CEE Raiffeisen Bank as an MIS (Management information systems) analyst.
I have been working there since 2010, as an IT expert within the Controlling department, maintaining the Oracle's OFSA system, underlying interfaces and databases.
Throughout that time, I have worked with several different technologies, which include SQL & PL/SQL (mostly), Cognos BI, Apparo, Datastage, ODI, Jenkins, ...
I have recently taken a lot of interest in scripting with VBScript and Windows batch scripting. Privately, I am mostly doing Windows Forms and Console apps in Visual Studio, in C#.

Comments and Discussions

 
-- There are no messages in this forum --