Skip to content

Commit 015b13d

Browse files
committed
Add timezone support for backup scheduling
1 parent 6b0b23e commit 015b13d

File tree

4 files changed

+39
-2
lines changed

4 files changed

+39
-2
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,18 @@ Files are backed up uncompressed by default, on the assumption a snapshotting or
4040
- `bz2`
4141
- `plain` (no compression - the default)
4242

43+
## Timezone support
44+
45+
You can now control the timezone used for scheduled backups by setting the `TZ` environment variable. For example:
46+
47+
```yaml
48+
environment:
49+
- TZ=Europe/Warsaw
50+
- SCHEDULE=0 3 * * *
51+
```
52+
53+
This ensures that the schedule is interpreted in the specified timezone. If `TZ` is not set, the system default timezone is used.
54+
4355
### Example `docker-compose.yml`
4456

4557
```yml

db-auto-backup.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import docker
1515
import pycron
16+
import pytz
1617
import requests
1718
from docker.models.containers import Container
1819
from dotenv import dotenv_values
@@ -158,6 +159,7 @@ def backup_redis(container: Container) -> str:
158159

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

188190

191+
def get_localized_now(dt: datetime, tz_name: str) -> datetime:
192+
tz = pytz.timezone(tz_name)
193+
return dt.astimezone(tz)
194+
195+
189196
@pycron.cron(SCHEDULE)
190197
def backup(now: datetime) -> None:
191198
print("Starting backup...")
192199

200+
# Apply timezone if TZ is set
201+
if TZ:
202+
try:
203+
tz = pytz.timezone(TZ)
204+
now = now.astimezone(tz)
205+
except Exception as e:
206+
print(f"Invalid timezone '{TZ}': {e}")
207+
sys.exit(1)
208+
193209
docker_client = docker.from_env()
194210
containers = docker_client.containers.list()
195211

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

254270
if __name__ == "__main__":
255271
if os.environ.get("SCHEDULE"):
256-
print(f"Running backup with schedule '{SCHEDULE}'.")
272+
print(f"Running backup with schedule '{SCHEDULE}' (TZ={TZ}).")
257273
pycron.start()
258274
else:
259275
backup(datetime.now())

dev-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ black==23.12.1
44
ruff==0.11.8
55
mypy==1.15.0
66
types-requests
7+
types-pytz
78
pytest==8.3.5

tests/tests.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
from datetime import datetime
12
from importlib.machinery import SourceFileLoader
23
from importlib.util import module_from_spec, spec_from_loader
34
from pathlib import Path
45
from typing import Any, Callable
5-
from unittest.mock import MagicMock
6+
from unittest.mock import MagicMock, patch
67

78
import pytest
9+
import pytz
810

911
BACKUP_DIR = Path.cwd() / "backups"
1012

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

131133
assert provider is not None
132134
assert provider.name == name
135+
136+
def test_get_localized_now():
137+
dt = datetime(2025, 5, 27, 12, 0, 0)
138+
localized = db_auto_backup.get_localized_now(dt, "Europe/Warsaw")
139+
assert localized.tzinfo is not None
140+
assert localized.tzinfo.zone == "Europe/Warsaw"

0 commit comments

Comments
 (0)