-
-
Save ultrasonex/e1fdb8354408a56df91aa4902d17aa6a to your computer and use it in GitHub Desktop.
# 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") | |
Hi @ultrasonex, I was looking to use this to validate AWS EventBridge Cron Expressions, but I'm not sure if your validator is for a different AWS cron format. If it is for EventBridge, I found a couple of things in your validator:
- It doesn't handle the
W
andL
options for Day-of-month and Day-of-week.- Old years may not match, but are valid. Especially for ranges ( e.g.
2017-2100
is a valid range and will match for many decades). In my application expressions will be saved for an unknown period, so an expression that is valid now should remain valid if validated in the future.2017
may never match again, but it does conform to the allowed 1970-2199 years, so is valid.Anyway, thank you for you great work.
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 @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.
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
andL
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!! 👍
Hi @ultrasonex, thanks for sharing it
As mentioned above some of the regex have issues. I fixed some of theme. Here is a fixed version: