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.