12

tl;dr: Does cron use the numerical value of an interval compared to the numerical value of the day to determine its time of execution or is it literally "every 3 days" at the prescribed time from creation?

Question:

If I add the following job with crontab -e will it run at midnight tomorrow for the first time or three days from tomorrow? Or is it only on "third" days of the month? Day 1, 4, 7, 10...?

0 0 */3 * * /home/user/script.sh

I put this cron in yesterday and it ran this morning (that might be the answer to my question) but I want to verify that this is correct. Today is the 31st and that interval value does appear to fall into the sequence. If cron starts executing an interval on the 1st of the month, will it run again tomorrow for me?

Additional notes:

There are already some excellent posts and resources about cron in general (it is a common topic I know) however the starting point for a specific interval isn't as clear to me. Multiple sources word it in multiple ways:

  • This unixgeeks.org post states:

    Cron also supports 'step' values. A value of */2 in the dom field would mean the command runs every two days and likewise, */5 in the hours field would mean the command runs every 5 hours.

    • So what is implied really by every two days?
  • This answer states that a cronjob of 0 0 */2 * * would execute "at 00:00 on every odd-numbered day (default range with step 2, i.e. 1,3,5,7,...,31)"

    • Does cron always step from the first day of the month?
    • It appears that the blog states the cron will execute on the 31st and then again on the 1st of the next month (so two days in a row) due to the interval being based on the numeric value of the day.
  • Another example from this blog post

    • 0 1 1 */2 * command to be executed is supposed to execute the first day of month, every two months at 1am
    • Does this imply that the cron will execute months 1,3,5,7,9,11?

It appears that cron is designed to execute interval cronjobs (*/3) based on the numerical value of the interval compared to the numerical value of the day (or second, minute, hour, month). Is this 100% correct?

P.S. This is a very specific question about one particular feature of cron that (I believe) needs some clarification. This should allow Google to tell you, with 100% certainty, when your "every 3 months" cron will run for the first time after it's been added to crontab.

7

The crontab(5) man page use a wording that is pretty clear:

Step values can be used in conjunction with ranges. Following a range with "/number" specifies skips of the number's value through the range. For example, "0-23/2" can be used in the hours field to specify command execution every other hour (the alternative in the V7 standard is "0,2,4,6,8,10,12,14,16,18,20,22"). Steps are also permitted after an asterisk, so if you want to say "every two hours", just use "*/2".

The exact wording (and the example) is "skips of the number's value through the range" - and it is implied that it starts at the first number in the range.

This mean if the range is 1-31 for days, the values returned in the case of 1-31/2 or */2 is 1,3,5,7.. etc. This also means that the range is reset to the start value when it has run through.

So you are also correct that in this case, the cronjob would run both on the 31th and 1st the month after.

Please note that cron has 2 fields that are mutually exclusive - the "day of month" and "day of week". So you have to choose one or the other, when running jobs with an interval of days.

If you want to define a cronjob that runs perfectly every other day, you have to use multiple lines and custom define each month according to the current calendar.

| improve this answer | |
  • Thank you for your answer! So if I program a cronjob with a day */3 interval then it will run on days 1,4,7,10,13,16,19,22,25,28,31 at the prescribed time? Adding the cron on day 30 would yield execution on day 31, then 1, then 4 etc. etc. That appears to be exactly what your answer says (sorry if I'm being thickheaded :P) I had glanced over crontab's man page and wanted to be sure that I was interpreting that correctly :) – Shrout1 Jul 31 at 12:42
  • Yes that is correct! – Artur Meinild Jul 31 at 12:46
  • 1
    Ok, so my problem was crontab is documented in section 1 and 5, and the relevant info is in section 5. Since man crontab opens section 1, you need man 5 crontab. – Quasímodo Jul 31 at 12:46
  • Good clarification. – Artur Meinild Jul 31 at 12:49
  • 8
    For strict daily intervals, an alternative to using different crontab lines per month (and having endless fun with leap years) is to run every day, and have the script check and exit itself. Use date '+%s' to get seconds since the epoch, divide by 86400, and use a modulus (and maybe an offset) to pick your day. – Paul_Pedant Jul 31 at 13:05
6

Today (2020-07-31) is the perfect day to ask this question, because 30 has an awful lot of factors.

My understanding (from memory) is that (a) the * expands to the range 1-31, then (b) the /3 is a skip increment for that list. So if you wrote 3-31/3 it would run on the 3rd, 6th, 9th, .., 27th (in Feb) or 30th (in other months). man -s 5 crontab implies this with ranges, but does not include an example that starts other than at the base value.

I set up a crontab (Linux Mint 18.1) with every skip value:

30 13 */1 * * date > /home/paul/cron.1.log
30 13 */2 * * date > /home/paul/cron.2.log
30 13 */3 * * date > /home/paul/cron.3.log
...
30 13 */30 * * date > /home/paul/cron.30.log
30 13 */31 * * date > /home/paul/cron.31.log

It runs only where the skip is 1, 2, 3, 5, 6, 10, 15 and 30. That looks like all the factors of (31 - 1).

Then I altered each range to be 7-31/, and it fires when the skip is 1, 2, 3, 4, 6, 8, 12 and 24. That is all the factors of (31 - 7).

With range 8-31, only skips 1 and 23 fire, because (31 - 8) is prime.

| improve this answer | |
  • Excellent - thank you for that clarification! – Shrout1 Jul 31 at 13:09
  • 1
    @Shrout1 My mantra: if you don't trust the manual, ask the system! – Paul_Pedant Jul 31 at 13:57
  • Of course, testing how ranges and steps work on the minutes column would have been way smarter and quicker, although days are 1-based and irregular. – Paul_Pedant Jul 31 at 16:07
2

Just for those that would like to look at some code here, this confirms the conclusion in the other answers.

cron.h defines the first and last possible elements of the different types (like HOUR, MONTH, DAY).

#define FIRST_HOUR  0
#define LAST_HOUR   23
#define HOUR_COUNT  (LAST_HOUR - FIRST_HOUR + 1)

#define FIRST_DOM   1
#define LAST_DOM    31
#define DOM_COUNT   (LAST_DOM - FIRST_DOM + 1)

#define FIRST_MONTH 1
#define LAST_MONTH  12
#define MONTH_COUNT (LAST_MONTH - FIRST_MONTH + 1)

In entry.c, '*' is parsed into a range using those limits

if (ch == '*') {
    /* '*' means "first-last" but can still be modified by /step
     */
    num1 = low;
    num2 = high;

Step size (num3) defaults to 1, but can be overriden if present in the crontab

    ch = get_number(&num3, 0, PPC_NULL, ch, file);
    if (ch == EOF)
        return EOF;
} else {
    /* no step.  default==1.
     */
    num3 = 1;

And then all the valid elements are created by iterating from first to last using the given step size. So the first element is always the beginning of the range.

/* range. set all elements from num1 to num2, stepping
 * by num3.  (the step is a downward-compatible extension
 * proposed conceptually by bob@acornrc, syntactically
 * designed then implmented by paul vixie).
 */
for (i = num1;  i <= num2;  i += num3)
    if (EOF == set_element(bits, low, high, i))
        return EOF;
| improve this answer | |
  • Thank you for taking the time to pull that. I think this code will put questions to bed once and for all. To the source!!! :D – Shrout1 Aug 3 at 15:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.