Skip to content

Add timezone support for backup scheduling #85

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,18 @@ Files are backed up uncompressed by default, on the assumption a snapshotting or
- `bz2`
- `plain` (no compression - the default)

## Timezone support

You can now control the timezone used for scheduled backups by setting the `TZ` environment variable. For example:

```yaml
environment:
- TZ=Europe/Warsaw
- SCHEDULE=0 3 * * *
```

This ensures that the schedule is interpreted in the specified timezone. If `TZ` is not set, the system default timezone is used.

### Example `docker-compose.yml`

```yml
Expand Down
18 changes: 17 additions & 1 deletion db-auto-backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import docker
import pycron
import pytz
import requests
from docker.models.containers import Container
from dotenv import dotenv_values
Expand Down Expand Up @@ -158,6 +159,7 @@ def backup_redis(container: Container) -> str:

BACKUP_DIR = Path(os.environ.get("BACKUP_DIR", "/var/backups"))
SCHEDULE = os.environ.get("SCHEDULE", "0 0 * * *")
TZ = os.environ.get("TZ")
SHOW_PROGRESS = sys.stdout.isatty()
COMPRESSION = os.environ.get("COMPRESSION", "plain")
INCLUDE_LOGS = bool(os.environ.get("INCLUDE_LOGS"))
Expand Down Expand Up @@ -186,10 +188,24 @@ def get_container_names(container: Container) -> Iterable[str]:
return names


def get_localized_now(dt: datetime, tz_name: str) -> datetime:
tz = pytz.timezone(tz_name)
return dt.astimezone(tz)


@pycron.cron(SCHEDULE)
def backup(now: datetime) -> None:
print("Starting backup...")

# Apply timezone if TZ is set
if TZ:
try:
tz = pytz.timezone(TZ)
now = now.astimezone(tz)
except Exception as e:
print(f"Invalid timezone '{TZ}': {e}")
sys.exit(1)

docker_client = docker.from_env()
containers = docker_client.containers.list()

Expand Down Expand Up @@ -253,7 +269,7 @@ def backup(now: datetime) -> None:

if __name__ == "__main__":
if os.environ.get("SCHEDULE"):
print(f"Running backup with schedule '{SCHEDULE}'.")
print(f"Running backup with schedule '{SCHEDULE}' (TZ={TZ}).")
pycron.start()
else:
backup(datetime.now())
1 change: 1 addition & 0 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ black==23.12.1
ruff==0.11.8
mypy==1.15.0
types-requests
types-pytz
pytest==8.3.5
10 changes: 9 additions & 1 deletion tests/tests.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from datetime import datetime
from importlib.machinery import SourceFileLoader
from importlib.util import module_from_spec, spec_from_loader
from pathlib import Path
from typing import Any, Callable
from unittest.mock import MagicMock
from unittest.mock import MagicMock, patch

import pytest
import pytz

BACKUP_DIR = Path.cwd() / "backups"

Expand Down Expand Up @@ -130,3 +132,9 @@ def test_get_backup_provider(container_name: str, name: str) -> None:

assert provider is not None
assert provider.name == name

def test_get_localized_now():
dt = datetime(2025, 5, 27, 12, 0, 0)
localized = db_auto_backup.get_localized_now(dt, "Europe/Warsaw")
assert localized.tzinfo is not None
assert localized.tzinfo.zone == "Europe/Warsaw"