Search code examples
linuxunixcroncron-task

Set a cron every 10 days starting from 16th January


How to set a cron to execute every 10 days starting from 16th January? Would this suffice?

30 7 16-15/10 * * command >/dev/null

The above starts at 7.30 AM, 16th of every month and ends on next month 15th and repeats every 10 days. I don't think what I have above is correct. Can anyone tell me how to set up the cron so that month ends are taken into account and every 10 days the command is executed starting from 16th January this year 2016?.


Solution

  • As William suggested, cron can't handle this complexity by itself. However, you can run a cron job more frequently, and use something else for the logic. For example;

    30 7 16-31 1 * date '+\%j' | grep -q '0$' && yourcommand
    30 7 *  2-12 * date '+\%j' | grep -q '0$' && yourcommand
    

    This date format string prints the day of the year, from 001 to 365. The grep -q will do a pattern match, NOT print the results, but return a success of a failure on the basis of what it finds. Every 10 days, the day of the year ends in a zero. On those days, yourcommand gets run.

    This has a problem with the year roll-over. A more complex alternative might be to do a similar grep on a product of date '+%s' (the epoch second), but you'll need to do math to turn seconds into days for analysis by grep. This might work (you should test):

    SHELL=/bin/bash
    
    30 7 * * * echo $(( $(date '+%s') / 86400 )) | grep '0$' && yourcommand
    

    (Add your Jan 16th logic too, of course.)

    This relies on the fact that shell arithmetic can only handle integers. The shell simply truncates rather than rounding.


    UPDATE

    In a comment on another answer, you clarified your requirements:

    The command should start executing on January 16th, and continue like on January 26th, February 5th, February 15th and so on – jai

    For this, the epoch-second approach is probably the right direction.

    % date -v1m -v16d -v7H -v30M -v0S '+%s'
    1452947400
    

    (I'm in FreeBSD, hence these arguments to date.)

    SHELL=/bin/bash
    
    30 7 * * * [[ $(( ($(date '+\%s') - 1452947400) \% 864000 )) == 0 ]] && yourcommand
    

    This expression subtracts the epoch second of 7:30AM Jan 16 (my timezone) from the current time, and tests whether the resultant difference is divisible by 10 days. If it is, the expression evaluates true and yourcommand is run. Note that $(( 0 % $x )) evaluates to 0 for any value of $x.

    This may be prone to error if cron is particularly busy and can't get to your job in the one second where the math works out.

    If you want to make this any more complex (and perhaps even if it's this complex), I recommend you move the logic into a separate shell script to handle the date comparison math. Especially if you plan to add a fudge factor to allow for jobs to miss their 1-second window .. that would likely be multiple lines of script, which is awkward to maintain in a single cronjob entry.