Skip to content

Instantly share code, notes, and snippets.

@ultrasonex
Last active January 23, 2024 09:25
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save ultrasonex/e1fdb8354408a56df91aa4902d17aa6a to your computer and use it in GitHub Desktop.
Save ultrasonex/e1fdb8354408a56df91aa4902d17aa6a to your computer and use it in GitHub Desktop.
AWS Cron Validator : Schedule expression
# Author : Niloy Chakraborty
# AWS Schedule Expression cron expression validator
import re
from voluptuous import Invalid
import datetime
minute_regex = r"^([*]|([0]?[0-5]?[0-9]?)|(([0]?[0-5]?[0-9]?)(\/|\-)([0]?[0-5]?[0-9]?))|" \
"(([0]?[0-5]?[0-9]?)((\,)([0]?[0-5]?[0-9]?))*))$"
hour_regex = r"^([*]|[01]?[0-9]|2[0-3]|(([01]?[0-9]|2[0-3]?)(\/|\-)([01]?[0-9]|2[0-3]?))|" \
"(([01]?[0-9]|2[0-3]?)((\,)([01]?[0-9]|2[0-3]?))*))$"
d_m_regex = r"^([*]|[?]|([0-2]?[0-9]|3[0-1])|(([0-2]?[0-9]|3[0-1])(\/|\-)([0-2]?[0-9]|3[0-1]))|" \
"(([0-2]?[0-9]|3[0-1])((\,)([0-2]?[0-9]|3[0-1]))*))$"
month_regex = r"^([*]|([0]?[0-9]|1[0-2])|(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)|" \
"((([0]?[0-9]|1[0-2])|(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))(\/|\-)(([0]?[0-9]|1[0-2])|" \
"(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)))|((([0]?[0-9]|1[0-2])|" \
"(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))((\,)(([0]?[0-9]|1[0-2])|" \
"(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)))*))$"
d_w_regex = r"^([*]|[?]|([0]?[1-7])|(SUN|MON|TUE|WED|THU|FRI|SAT)|((([0]?[1-7])|(SUN|MON|TUE|WED|" \
"THU|FRI|SAT))(\/|\-|\,|\#)(([0]?[1-7])|(SUN|MON|TUE|WED|THU|FRI|SAT)))|((([0]?[1-7])|(SUN|MON|TUE|WED|" \
"THU|FRI|SAT))((\,)(([0]?[1-7])|(SUN|MON|TUE|WED|THU|FRI|SAT)))*))$"
year_regex = r"^([*]|([1-2][01][0-9][0-9])|(([1-2][01][0-9][0-9])(\/|\-)([1-2][01][0-9][0-9]))|" \
"(([1-2][01][0-9][0-9])((\,)([1-2][01][0-9][0-9]))*))$"
def validate_aws_regex(regex_val):
regex_splits = regex_val.split(" ")
if len(regex_splits) < 6:
raise Invalid(
"Schedule parameter should have 6 field minute,hour,day_of_the_month,month,day_of_the_week,year.Please check the value")
minute_val = regex_splits[0]
hour_val = regex_splits[1]
d_m_val = regex_splits[2]
month_val = regex_splits[3]
d_w_val = regex_splits[4]
year_val = regex_splits[5]
print(
"Values of Min is %s , hour is %s , day_of_month is %s , month is %s ,day of the week is %s and year is %s" % (
minute_val, hour_val, d_m_val, month_val, d_w_val, year_val))
if not ((d_m_val == '?' and d_w_val != '?') or (d_m_val != '?' and d_w_val == '?')):
raise Invalid("Either day-of-month or day-of-week values must be a question mark (?)")
minute_match = re.fullmatch(minute_regex, minute_val)
hour_match = re.fullmatch(hour_regex, hour_val)
d_m_match = re.fullmatch(d_m_regex, d_m_val)
month_match = re.fullmatch(month_regex, month_val)
d_w_match = re.fullmatch(d_w_regex, d_w_val)
year_match = re.fullmatch(year_regex, year_val)
if not minute_match:
raise Invalid("Schedule expression has an invalid minute column value")
if not hour_match:
raise Invalid("Schedule expression has an invalid hour column value")
if not d_m_match:
raise Invalid("Schedule expression has an invalid day of the month column value")
if not month_match:
raise Invalid("Schedule expression has an invalid month column value")
if not d_w_match:
raise Invalid("Schedule expression has an invalid day of the week column value")
if not year_match:
raise Invalid("Schedule expression has an invalid year column value")
if '#' in d_w_val:
nd = int(d_w_val.split('#')[1])
if nd > 5:
raise Invalid(
"Schedule expression has an invalid day of the week column value. Nth day of week cannot be more than 5")
current_year = datetime.datetime.today().year
year_split_comma_val = year_val.split(",")
year_split_dash_val = year_val.split("-")
if "," in year_val:
if any(yr_val for yr_val in year_split_comma_val if int(yr_val) < current_year):
raise \
Invalid("Schedule expression has an invalid year column value. " \
"Year value[s] should be greater or equal to %s and less than 2199 " % current_year)
if "-" in year_val:
if int(year_split_dash_val[1]) < current_year:
raise \
Invalid("Schedule expression has an invalid year column value. " \
"Year value[s] should be greater or equal to %s and less than 2199 " % current_year)
return regex_val
# Valid
assert validate_aws_regex("0 18 ? * MON-FRI *") == "0 18 ? * MON-FRI *"
assert validate_aws_regex("0 10 * * ? *") == "0 10 * * ? *"
assert validate_aws_regex("15 12 * * ? *") == "15 12 * * ? *"
assert validate_aws_regex("0 8 1 * ? *") == "0 8 1 * ? *"
assert validate_aws_regex("0/5 8-17 ? * MON-FRI *") == "0/5 8-17 ? * MON-FRI *"
assert validate_aws_regex("0 9 ? * 2#1 *") == "0 9 ? * 2#1 *"
assert validate_aws_regex("0 07/12 ? * * *") == "0 07/12 ? * * *"
assert validate_aws_regex("10,20,30,40 07/12 ? * * *") == "10,20,30,40 07/12 ? * * *"
assert validate_aws_regex("10 10,15,20,23 ? * * *") == "10 10,15,20,23 ? * * *"
assert validate_aws_regex("10 10 15,30,31 * ? *") == "10 10 15,30,31 * ? *"
assert validate_aws_regex("10 10 15 JAN,JUL,DEC ? *") == "10 10 15 JAN,JUL,DEC ? *"
assert validate_aws_regex("10 10 31 04,09,12 ? *") == "10 10 31 04,09,12 ? *"
assert validate_aws_regex("0,5 07/12 ? * 01,05,7 *") == "0,5 07/12 ? * 01,05,7 *"
assert validate_aws_regex("0,5 07/12 ? * 01,05,7 2020,2021,2028,2199") == "0,5 07/12 ? * 01,05,7 2020,2021,2028,2199"
assert validate_aws_regex("0,5 07/12 ? * 01,05,7 2020,2021,2028,2199")
# Invalid
# validate_aws_regex("0 18 ? * MON-FRI")
# validate_aws_regex("0 18 * * * *")
# validate_aws_regex("0 65 * * ? *")
# validate_aws_regex("89 10 * * ? *")
# validate_aws_regex("15/65 10 * * ? *")
# validate_aws_regex("15/30 10 * * ? 2400")
# validate_aws_regex("0 9 ? * 2#6 *")
# validate_aws_regex("0 9 ? * ? *")
# validate_aws_regex("10 10 31 04,09,13 ? *")
# validate_aws_regex("0,5 07/12 ? * 01,05,8 *")
# validate_aws_regex("0,5 07/12 ? * 01,05,7 2020,2021,2028,1111")
#validate_aws_regex("0,5 07/12 ? * 01,05,7 2020,2021,2028,2017")
#validate_aws_regex("0,5 07/12 ? * 01,05,7 2017-2100")
@ultrasonex
Copy link
Author

Hi @grumBit , I am glad you were able to use it. Yes at the time of writing, we had a requirement to expose a UI which did not deal with AWS like cron but more of a standard cron so that did not require W or L, which is why I kind of left that part. I will try to update it to include them.

Hi @ultrasonex, I took inspiration from your work and added support for W and L in a new project over here. I decided to generate the regex strings as they were getting very long and difficult to read, added some testing and packaged it up to PyPi. I'm not sure if it's of any use to you at this stage 🤷 .

Many thanks, Graham.

Thanks a lot!! 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment