10

I have a task that needs to be performed on my project schedule (3 weeks).

I'm able to set up cron to do this every week, or (for example) on the 3rd week of every month - but can't find a way to do this every three weeks.

I could hack the script to create temporary files (or similar) so it could work out it was the third time it has been run - but this solution smells.

Can it be done in a clean way?

2
  • 1
    @itj 3 weeks = 21 days so why not put a cron task every 21 days ?
    – Studer
    Feb 19, 2010 at 22:46
  • 1
    @studer I may have missed something, but I didn't think crontab was that flexible
    – itj
    Feb 22, 2010 at 8:09

3 Answers 3

8

The crontab file only lets you specify:

minute (0-59)
hour (0-23)
day of the month (1-31)
month of the year (1-12)
day of the week (0-6 with 0=Sunday)

So it's not possible to specify which weeks should apply.

Writing a wrapper script may be the best option.
You could get the week number in a shell-script by using

date +%U

or

date +%V

depending on whether you like your weeks to start on Sunday or Monday.
So you could use

week=$(date +%V)
check=$(( ($week - 1) % 3 ))

And $check would be 0 in the 1st, 4th, 7th, ... weeks of the year.

3
  • 3
    What happens at the end of the year? Will it run extra times or skip a week or something, since a year doesn't divide evenly into a multiple of 3 weeks?
    – davr
    Feb 19, 2010 at 17:15
  • Thanks for answer. Neater than any idea I had. Its only valid for 1 year - but that should be OK for most projects.
    – itj
    Feb 19, 2010 at 17:17
  • Over multiple years, you'd get two runs spaced 8 or 9 days apart around Christmas & Jan 1st, so that's when you'd need to do it properly and record the "last run date" somewhere so you can calculate a 3-week interval.
    – njd
    Feb 19, 2010 at 17:54
3

Thanks to earlier answers, have been pointed at the epoch options to date -e or formatting as %s

Although a bit painful ((date +%s) / 86400) gives days from the epoch.

Relying on the weekly job being run at the same time, it is then easy to check this against a specific day of a 3 week period ($epoch_day%21 == 13 for instance)

In my case this is fine, as it is a one shot task. If it is missed on the particular day then there is no need to run at the next opportunity.

1

If you can save a timestamp file between runs, you can check the date of it instead of relying solely on the current date.

If your find command supports fractional values for -mtime (or has -mmin) (GNU find has both, POSIX does not seem to require either), you could ‘throttle’ the cron jobs with with find and touch.

Or, if you have a stat command that supports showing file dates as “seconds since the epoch” (e.g. stat from Gnu coreutils, also other implementations), you could make your own comparison using date, stat, and the shell's comparison operators (along with touch to update a timestamp file). You also might be able to use ls instead of stat if it can do the formatting (e.g. ls from GNU fileutils).

Below is a Perl program (I called it n-hours-ago) that updates a timestamp file and exits successfully if the original timestamp was old enough. Its usage text shows how to use it in a crontab entry to throttle a cron job. It also describes adjustments for “daylight savings” and how to handle ‘late’ timestamps from previous runs.

#!/usr/bin/perl
use warnings;
use strict;
sub usage {
    printf STDERR <<EOU, $0;
usage: %s <hours> <file>

    If entry at pathname <file> was modified at least <hours> hours
    ago, update its modification time and exit with an exit code of
    0. Otherwise exit with a non-zero exit code.

    This command can be used to throttle crontab entries to periods
    that are not directly supported by cron.

        34 2 * * * /path/to/n-hours-ago 502.9 /path/to/timestamp && command

    If the period between checks is more than one "day", you might
    want to decrease your <hours> by 1 to account for short "days"
    due "daylight savings". As long as you only attempt to run it at
    most once an hour the adjustment will not affect your schedule.

    If there is a chance that the last successful run might have
    been launched later "than usual" (maybe due to high system
    load), you might want to decrease your <hours> a bit more.
    Subtract 0.1 to account for up to 6m delay. Subtract 0.02 to
    account for up to 1m12s delay. If you want "every other day" you
    might use <hours> of 47.9 or 47.98 instead of 48.

    You will want to combine the two reductions to accomodate the
    situation where the previous successful run was delayed a bit,
    it occured before a "jump forward" event, and the current date
    is after the "jump forward" event.

EOU
}

if (@ARGV != 2) { usage; die "incorrect number of arguments" }
my $hours = shift;
my $file = shift;

if (-e $file) {
    exit 1 if ((-M $file) * 24 < $hours);
} else {
    open my $fh, '>', $file or die "unable to create $file";
    close $fh;
}
utime undef, undef, $file or die "unable to update timestamp of $file";
exit 0;

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .