Backup rotation schemes are simple in theory: daily, weekly, monthly. But naming the backup files so they rotate properly? That’s where the date math gets interesting.
The rotating backup variables#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # Rotating 5-week cycle (WEEK1-WEEK5)
export WEEK=WEEK$(($(date +%W) % 5 + 1))
# Week of month (WEEKLY0-WEEKLY4, where 0 is days 1-7)
export WEEKLY=WEEKLY$(($(date +%d) / 7))
# Day of week (DAY1-DAY7, Monday=1)
export DAY=DAY$(date +%u)
# Month number (MONTH1-MONTH12)
export MONTH=MONTH$(date +%m)
# Display results
echo "WEEK=$WEEK"
echo "WEEKLY=$WEEKLY"
echo "DAY=$DAY"
echo "MONTH=$MONTH"
|
Example output (for 2026-01-11, Saturday):
1
2
3
4
| WEEK=WEEK3
WEEKLY=WEEKLY1
DAY=DAY6
MONTH=MONTH1
|
What each variable does#
WEEK - 5-week rotation#
WEEK$(($(date +%W) % 5 + 1))
- Uses ISO week number (1-53)
- Modulo 5 creates a rotating cycle: WEEK1, WEEK2, WEEK3, WEEK4, WEEK5
- Useful for keeping 5 weeks of history
- Example: week 1→WEEK2, week 2→WEEK3, …, week 5→WEEK1
WEEKLY - Week of month#
WEEKLY$(($(date +%d) / 7))
- Divides day of month by 7
- Returns 0-4 (for days 1-31)
- Days 1-7 → WEEKLY0
- Days 8-14 → WEEKLY1
- Days 15-21 → WEEKLY2
- Days 22-28 → WEEKLY3
- Days 29-31 → WEEKLY4
DAY - Day of week#
DAY$(date +%u)
- ISO weekday: 1=Monday, 7=Sunday
- DAY1 through DAY7
- For daily backups
MONTH - Month number#
MONTH$(date +%m)
- Simple month number: 01-12
- MONTH1 (January) through MONTH12 (December)
- For monthly backups
Usage in backup scripts#
Daily rotation (7 days)#
1
2
3
4
5
6
| #!/bin/bash
DAY=DAY$(date +%u)
BACKUP_FILE="/backups/daily-${DAY}.tar.gz"
tar czf "$BACKUP_FILE" /data
echo "Backup saved to $BACKUP_FILE"
|
This creates 7 files that rotate:
daily-DAY1.tar.gz (Monday)daily-DAY2.tar.gz (Tuesday)- …
daily-DAY7.tar.gz (Sunday)
Weekly rotation (5 weeks)#
1
2
3
4
5
6
| #!/bin/bash
WEEK=WEEK$(($(date +%W) % 5 + 1))
BACKUP_FILE="/backups/weekly-${WEEK}.tar.gz"
tar czf "$BACKUP_FILE" /data
echo "Weekly backup saved to $BACKUP_FILE"
|
This keeps 5 rotating weekly backups.
Monthly rotation (12 months)#
1
2
3
4
5
6
| #!/bin/bash
MONTH=MONTH$(date +%m)
BACKUP_FILE="/backups/monthly-${MONTH}.tar.gz"
tar czf "$BACKUP_FILE" /data
echo "Monthly backup saved to $BACKUP_FILE"
|
This keeps up to 12 monthly backups.
Complete grandfather-father-son rotation#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| #!/bin/bash
# Date variables for rotation
DAY=DAY$(date +%u)
WEEK=WEEK$(($(date +%W) % 5 + 1))
MONTH=MONTH$(date +%m)
WEEKLY=WEEKLY$(($(date +%d) / 7))
BACKUP_DIR="/backups"
SOURCE="/data"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
# Daily backup (keeps 7 days)
tar czf "$BACKUP_DIR/daily-${DAY}.tar.gz" "$SOURCE"
echo "Daily backup: daily-${DAY}.tar.gz"
# Weekly backup on Sundays (keeps 5 weeks)
if [ "$(date +%u)" = "7" ]; then
tar czf "$BACKUP_DIR/weekly-${WEEK}.tar.gz" "$SOURCE"
echo "Weekly backup: weekly-${WEEK}.tar.gz"
fi
# Monthly backup on 1st of month (keeps 12 months)
if [ "$(date +%d)" = "01" ]; then
tar czf "$BACKUP_DIR/monthly-${MONTH}.tar.gz" "$SOURCE"
echo "Monthly backup: monthly-${MONTH}.tar.gz"
fi
# Optional: timestamped backup for archive
# tar czf "$BACKUP_DIR/archive-${TIMESTAMP}.tar.gz" "$SOURCE"
|
Alternative rotation schemes#
4-week rotation#
1
2
| # Rotate every 4 weeks instead of 5
WEEK=WEEK$(($(date +%W) % 4 + 1))
|
14-day rotation (2 weeks)#
1
2
| # Use day of year modulo 14
DAY14=DAY$(($(date +%j) % 14 + 1))
|
Year variable#
1
2
3
| # For yearly backups
YEAR=YEAR$(date +%Y)
BACKUP_FILE="/backups/yearly-${YEAR}.tar.gz"
|
Custom N-day rotation#
1
2
3
4
| # Rotate every N days (e.g., 10-day rotation)
N=10
DAYN=DAY$(($(date +%j) % N + 1))
BACKUP_FILE="/backups/${N}day-${DAYN}.tar.gz"
|
1
2
3
4
5
6
7
8
9
10
11
| # Quick reference for date formats
date +%u # Day of week (1-7, Monday=1)
date +%w # Day of week (0-6, Sunday=0)
date +%d # Day of month (01-31)
date +%j # Day of year (001-366)
date +%W # Week number, Monday as first day (00-53)
date +%m # Month (01-12)
date +%Y # Year (2026)
date +%H # Hour (00-23)
date +%M # Minute (00-59)
date +%S # Second (00-60)
|
Verify rotation logic#
1
2
3
4
5
6
| # Test what backup files would be created this week
for i in {0..6}; do
TEST_DATE=$(date -d "+$i days" +%Y-%m-%d)
DAY=$(date -d "$TEST_DATE" +%u)
echo "$TEST_DATE ($(date -d "$TEST_DATE" +%A)) → daily-DAY${DAY}.tar.gz"
done
|
Example output:
1
2
3
4
5
| 2026-01-11 (Saturday) → daily-DAY6.tar.gz
2026-01-12 (Sunday) → daily-DAY7.tar.gz
2026-01-13 (Monday) → daily-DAY1.tar.gz
2026-01-14 (Tuesday) → daily-DAY2.tar.gz
...
|
Pro tip: The modulo trick (% 5 + 1) ensures your backups rotate automatically. With WEEK1-WEEK5, you always keep 5 weeks of history without manual cleanup. On week 6, it overwrites WEEK1. Same principle works for any rotation period - just change the modulo divisor. Test your rotation logic with date -d "+N days" before deploying to production.