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"

Date format reference

 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.