How to Configure Cron to Run on Alternate Sundays Using Week Number Calculation


2 views

Many developers mistakenly believe that 0 0 * * 0/2 in cron will run every other Sunday. This actually means "run every Sunday where the day-of-week (0-6) is divisible by 2". Since Sunday is day 0 in cron, this expression will run every Sunday (0 divided by 2 equals 0).

Here are three robust methods to achieve true alternate Sunday execution:

1. Week Number Modulo Method

The most reliable approach uses the ISO week number:

0 0 * * 0 [ $(date +\%V) \% 2 -eq 0 ] && /path/to/command

Or for the opposite weeks:

0 0 * * 0 [ $(date +\%V) \% 2 -ne 0 ] && /path/to/command

2. Day of Year Calculation

Alternative using day of year:

0 0 * * 0 [ $(( $(date +\%j) / 7 \% 2 )) -eq 0 ] && /path/to/command

3. External Script Wrapper

For complex scheduling needs, create a wrapper script:

#!/bin/bash
WEEK=$(date +\%V)
if [ $(( WEEK % 2 )) -eq 1 ]; then
    /path/to/actual_script.sh
fi

Then call it from cron:

0 0 * * 0 /path/to/wrapper_script.sh
  • Always test with date +\%V to verify week numbering in your environment
  • The \% escape is required in crontab for percent signs
  • Consider timezone implications for global systems
  • Document which Sundays (odd/even weeks) will trigger execution

For year transitions where week numbering might behave unexpectedly:

0 0 * * 0 [ $(( 10#$(date +\%V) \% 2 )) -eq 0 ] && /path/to/command

The 10# prefix ensures decimal interpretation if week numbers start with zero.


Many developers assume 0 0 * * 0/2 in cron means "every other Sunday," but this is a common misconception. In reality, this syntax checks if the day of the week (0-6, where 0=Sunday) is divisible by 2, which will always evaluate to true for Sunday (0%2=0). The job will therefore run every Sunday.

Since cron alone can't handle this specific scheduling requirement, we need to add logic in the executed command. Here's a reliable bash solution:

0 0 * * 0 [ $(( $(date +\%W) \% 2 )) -eq 1 ] && /path/to/your/script.sh

The magic happens in $(date +\%W) which returns the week number of the year (00-53). By checking \% 2, we determine if it's an odd week. The backslash before % is required for cron to interpret it correctly.

For systems where week numbering might behave differently, consider these alternatives:

1. Day of Month Calculation

0 0 * * 0 [ $(( $(date +\%d) / 7 \% 2 )) -eq 1 ] && /path/to/script.sh

2. Using a State File

0 0 * * 0 /usr/bin/flock -n /tmp/cron.lock /bin/bash -c 'if [ -f /var/tmp/last_run ]; then rm /var/tmp/last_run; else touch /var/tmp/last_run && /path/to/script.sh; fi'

Be aware that different systems may have different interpretations of week numbers. For maximum reliability:

  • ISO week numbers (Monday as first day) vs US week numbers (Sunday as first day)
  • First week of year calculations may vary
  • Consider using date +\%V for ISO week numbers

For more complex scheduling needs, you might want to use Python:

0 0 * * 0 /usr/bin/python3 -c "import datetime; import sys; sys.exit(0 if (datetime.datetime.now().isocalendar()[1] % 2) == 0 else 1)" || /path/to/script.sh

This uses Python's isocalendar() which returns (year, week number, weekday) according to ISO standards.

Before deploying, verify your chosen method with this test command:

for i in {1..52}; do date -d "2023-01-01 + $i weeks" +"Week %W: %Y-%m-%d"; done | grep -E "Week [0-9]*[13579]:"

This will list all odd-numbered weeks in 2023, helping you confirm your implementation matches your requirements.