What Cron Can't Do and How To Do It Anyway
Nobody’s perfect
We’ve written extensively of Cron’s many features, even going as far as reviewing some of the most unique and creative ways in which our customers applied Cron to their business. We’ve even written a Cron tutorial for Cron newbies. But what of the things Cron can’t do? As in life, so too in Cron — understanding your limitations may help you prevent future complications and misfortunes. So let’s inject ourselves with a healthy dose of humility and go through a few cases in which Cron just won’t be able to cut it.
The trouble with intervals
One of Cron’s most glaring shortcomings is its inability to schedule jobs according to certain types of intervals. Technically speaking, what Cron actually can’t do is utilize intervals which can’t serve as divisors without remainder for hours, days, months and years. Cron will work well if you want to run your job every 10 or 20 minutes (because 60/10 and 60/20 has 0 remainder), but not so much with a 40 minute interval. Which begs the question: What are the intervals that Cron can’t figure out? We’re here to break it down.
Scheduling week-sized intervals
Unfortunately, Cron can’t schedule jobs in intervals determined by weeks. If you were to try to schedule Cron to run a job every other Sunday or once every three weeks, then you would find out, much to your dismay, that there is actually no way to configure such a Cron Job.
This isn’t to say that you can’t schedule jobs to occur once per a week in Cron; that can be accomplished by scheduling a job to take place at a certain day and at a certain time, for example: Every Sunday at 4PM. To summarize:
- Cron can: Schedule weekly jobs (i.e. once per a week)
- Cron can’t: Schedule intervals which are made up of weeks (i.e. every couple or more weeks).
Possible solution:
Have a Cronjob run on a certain day, let’s say, Sunday, every week, by typing in the Cron expression: 0 0 * * 0
(At 0:00 on Sunday). For the sake of clarity, we’ll say that this job ran on the 29th of November for the first time.
Next, prefix your command with another bash command, such as the following:
[[ $(("( $(date +%s) - $(date +%s --date=20201129) ) / 86400 % 14")) -eq 0 ]] &&
This will compare the difference between the 29th of November and the date in which the command is currently running. If said difference is divisible by 14 (i.e. 2 weeks), then the following command (which is what you actually want to run once every 2 weeks) will be executed. Make sure to replace `20201129` with your first execution date, and `14` with the interval of days you want the job to run at.
Is there a 31st this month?
Another thing that Cron can’t do is schedule jobs to occur at the end of every month. The reason for this is twofold:
- Cron has no “end of the month” option. The closest alternative Cron has to offer is scheduling jobs on specific dates, such as the 30th or the 31st.
- The asymmetrical character of the calendar months - the fact that there are an irregular amount of days in each month means that Cron has no set date to schedule the job in. You could schedule a job every 30th and 31st, but that would result in Cron running the same job twice in every month which has a 31st: once on the 30th and once again on the 31st.
Possible Solutions:
- Create 3 different Cron Jobs that follow these Cron expressions:
-0 0 31 1,3,5,7,8,10,12 *
(midnight on 31-Jan, 31-Mar, 31-May, 31-Jul, 31-Aug, 31-Oct, 31-Dec). This will cover all the months that end on the 31st.
-0 0 30 4,6,9,11 *
(midnight on 30-Apr, 30-Jun, 30-Sep, 30-Nov)
-0 0 28 2 *
(midnight on 28-Feb - this doesn’t account for leap years) - Schedule a job to run on every single possible last day of the month (i.e. the 29th, the 30th, and the 31st) with the following Cron expression:
0 0 28,29,30,31 * *
. Prefix the command with another bash command:
[[ "$(date --date=tomorrow +\%d)" == "01" ]] &&
This command will get tomorrow’s date. If it finds that tomorrow is the first day of the month, then it will allow the next command, the actual job, to run.
Out of the loop
Cron is unable to schedule a job in intervals which don’t fit neatly into an hour, a day, or a week. To make this a bit clearer, let’s see what happens if we were to try to schedule a job every 40 minutes:
A job occurring every 40 minutes would run once before the hour would end, thus resetting the job. In this case, the job wouldn’t run every 40 minutes, but once per an hour.
This is true not only for other intervals which don’t fit into an hour without remainder (such as: every 7 minutes, every 17 minutes, and so on), but also for intervals which don’t precisely fill out a day or a week, i.e. every 17 hours or every 3 days.
Possible Solution:
Run a job in lower resolution that fits well within the hour so it’ll run every time you want the job to run and then some… For example, to run a job every 40 minutes, run a job every 20 minutes, and prefix the command with a command to check whether the difference between current execution and epoch (Jan 1, 1970 at midnight) is 40 minutes: [[ $(($(date +'%s / 60 % 40 '))) -eq 0 ]] &&
Happy holidays!
If you’re like us and also completely missed out on Thanksgiving, it might be because you scheduled Cron to remind you of it on Thanksgiving day. Doing so would naturally backfire, since Cron isn’t built to figure out the exact date of Thanksgiving every year, seeing as there is no way to program a task to run on the 4th Thursday of November. Likewise, there is no way to schedule a job to run on any combination of the xth day of the month.
Solution:
Create a job that runs every Thursday on November utilizing the Cron expression: 0 0 * 11 4
. Then, prefix your command with a bash test that checks whether your current date is in the 4th week of the month through the following code:
[[ $((($(date +%-d)-1)/7+1)) -eq 4 ]] &&
Going beyond intervals
Intervals aren’t, however, the only source of Cron’s imperfections. Cron has close to no monitoring built into it. Cron’s logs and job histories are mostly inaccessible, and whatever is accessible is for the most part convoluted and intertwined with other, less useful, logs. The user, in turn, doesn’t know when exactly his job succeeded, failed, or if it even actually ran.
There is also no way to set a timeout to jobs. This may lead to an unfortunate scenario in which a job that should’ve finished taking place is actually going on undetected, thereby draining processing time or causing harm where a job shouldn’t run more than once concurrently.
Furthermore, Cron cannot confirm that there are no overlapping job executions. Your tasks may be intertwining and interfering with each other without your knowledge.
The Cron is always greener
It was these shortcomings that we had in mind when we created Cron To Go — a reliable cloud-based scheduler available on Heroku. It boasts such features which are absent from vanilla Cron, such as exhaustive logs and job histories, manual timeouts to jobs, and email and Webhook status notifications. So, if you feel like upgrading your Cron, check out Cron To Go!