Pybites Logo

Learn to handle cron schedule expressions

Level: Advanced (score: 5)

Let your mind wander and tell us what you think about this expression:

0 0,12 1 */2 *

And? What did you came up with? The gibberish of a four-year-old? Your account movements? Or maybe hidden coordinates to a legendary treasure? There is no correct answer, of course, but if, by any chance, you might have thought "At minute 0 past hour 0 and 12 on day-of-month 1 in every 2nd month," you would be on to something! 

Cron Schedule Expressions

Indeed, what you have just seen is known as a cron schedule expression and this site can be used to generate and explain such expressions. Cron is a computer program that can be used to make a computer do tasks at specific time intervals. The name of the cron computer program is from the word cron, which is from the Greek word for time (chronos, thanks Wikipedia!). crontab (cron table) is a file that lists the specific tasks for the computer to run and when the tasks should be run.

A cron schedule expression normally has five parts, separated by white space: minute, hour, day, month, and day of week.

Each part can be specified by a single number, a star *, or multiple numbers chained with syntax rules using -, , and /.

Your task

Your task is to write a class CrontabScheduler that accepts a cron schedule expression expr and a datetime now as reference. An instance of that class can be used as an iterator to retrieve the next scheduled datetime according to the cron schedule expression.

To make things a little easier (and no, this bite should not be easy, at least, we hope not!), you have to implement only a subset of the cron schedule expression syntax:

- You only have to support cron schedule expressions with four parts, meaning we omit the day of week part for this bite.

- You only have to support one operator per part, so -, ,, and / only appear one per part.

- For the step operator / only a star * appears in front of it.

You can try to think about what changes to the code are necessary to support the full syntax, but it will complicate things here and there, so try to keep these simplifications in mind.

So here are a few examples:

- expr = "* * * *", meaning "At every minute," so with now = datetime(2022, 6, 1, 0, 0) the next datetime would be datetime(2022, 6, 1, 0, 1)

- expr = "* * 1 *", meaning "At every minute on day-of-month 1,", so with now = datetime(2022, 6, 1, 0, 0) the next datetime would be datetime(2022, 6, 1, 0, 1), which is the same as before in this case.

- expr = "0 0 1,15 3", meaning "At 00:00 on day-of-month 1 and 15 in March,", so with now = datetime(2022, 6, 1, 0, 0) the next datetime would be datetime(2023, 3, 1, 0, 0)

You will find a lot more examples with expected answers in the tests, so make sure to study them!

Your implementation of the CrontabScheduler class should support creating an iterator with iter(CrontabScheduler()) and to retrieve the next value by calling next(it) or next(CrontabScheduler()).

Make sure you handle invalid values by raising a proper ValueError exception.

Hints

- Try to solve this bite step by step, starting with the most simple tests and work your way up the complexity ladder

- Try to handle the different operators one by one, see the tests for examples

- If you get stuck finding a way to parse the cron expression try to divide-and-conquer the problem. Given an arbitrary datetime, how can you answer if it is valid according to the cron schedule expression? If you find a way to answer that you can systematically go forward in time starting by now to a point that fulfills all the requirements from the cron schedule expression

- Or you find a completely different way than I have, kudos to you!

Keep calm and coding!