From 9f75e5e66b533ab6e8048946b59c1ac189ff967e Mon Sep 17 00:00:00 2001 From: Thomas Oettli Date: Thu, 22 Jan 2026 23:56:07 +0100 Subject: [PATCH] Switch to UTC timestamps, output in local time --- src/ddns_service/__init__.py | 11 +++++++++-- src/ddns_service/cleanup.py | 5 +++-- src/ddns_service/cli.py | 14 +++++++------- src/ddns_service/models.py | 4 ++-- src/ddns_service/server.py | 5 ++--- 5 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/ddns_service/__init__.py b/src/ddns_service/__init__.py index 04e7062..1a25a1e 100644 --- a/src/ddns_service/__init__.py +++ b/src/ddns_service/__init__.py @@ -27,11 +27,18 @@ __all__ = [ DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S %Z" + def datetime_str(dt, utc=False): if not isinstance(dt, datetime.datetime): return "Never" + aware_dt = dt.replace(tzinfo=datetime.UTC) if not dt.tzinfo else dt + if utc: - return timestamp.strftime(DATETIME_FORMAT) + return aware_dt.strftime(DATETIME_FORMAT) else: - return timestamp.astimezone().strftime(DATETIME_FORMAT) + return aware_dt.astimezone().strftime(DATETIME_FORMAT) + + +def utc_now(): + return datetime.datetime.now(datetime.UTC).replace(tzinfo=None) diff --git a/src/ddns_service/cleanup.py b/src/ddns_service/cleanup.py index 5a67750..577ddc2 100644 --- a/src/ddns_service/cleanup.py +++ b/src/ddns_service/cleanup.py @@ -2,9 +2,10 @@ import logging import threading -from datetime import datetime, timedelta +from . import utc_now from .models import Hostname, User +from datetime import timedelta def cleanup_expired(app): @@ -17,7 +18,7 @@ def cleanup_expired(app): Returns: Number of expired hostnames processed. """ - now = datetime.now(datetime.timezone.utc) + now = utc_now() expired_count = 0 for hostname in Hostname.select().join(User).where( diff --git a/src/ddns_service/cli.py b/src/ddns_service/cli.py index 8955209..fd277fc 100644 --- a/src/ddns_service/cli.py +++ b/src/ddns_service/cli.py @@ -23,7 +23,7 @@ def cmd_user_list(args, app): return 0 print(f"\n{'Username':<20} {'Email':<30} {'Hostnames':<10} {'Created'}") - print("-" * 82) + print("-" * 86) for user in users: hostname_count = Hostname.select().where( Hostname.user == user @@ -150,15 +150,15 @@ def cmd_hostname_list(args, app): print( f"{'Hostname':<35} {'User':<15} {'Zone':<20} " - f"{'DNS-TTL':<8} {'Exp-TTL':<8} {'Last-Update IPv4':<21} {'Last-Update IPv6'}" + f"{'DNS-TTL':<8} {'Exp-TTL':<8} {'Last-Update IPv4':<25} {'Last-Update IPv6'}" ) - print("-" * 132) + print("-" * 140) for h in hostnames: - last_ipv4_update = datetime_str(h.last_ipv4_update) if h.last_ipv4_update else "Never" - last_ipv6_update = datetime_str(h.last_ipv6_update) if h.last_ipv6_update else "Never" + last_ipv4_update = datetime_str(h.last_ipv4_update) + last_ipv6_update = datetime_str(h.last_ipv6_update) print( f"{h.hostname:<35} {h.user.username:<15} {h.zone:<20} " - f"{h.dns_ttl:<8} {h.expiry_ttl:<8} {last_ipv4_update:<21} {last_ipv6_update}" + f"{h.dns_ttl:<8} {h.expiry_ttl:<8} {last_ipv4_update:<25} {last_ipv6_update}" ) return 0 @@ -252,7 +252,7 @@ def cmd_hostname_delete(args, app): logging.warning(f"DNS delete failed: type=AAAA error={e}") hostname.delete_instance() - print(f"Hostname '{hostname_str}' deleted.") + print(f"Hostname '{hostname_str}' in zone '{zone}' deleted.") return 0 diff --git a/src/ddns_service/models.py b/src/ddns_service/models.py index ab9d856..fa6ac8c 100644 --- a/src/ddns_service/models.py +++ b/src/ddns_service/models.py @@ -2,8 +2,8 @@ import logging import os -from datetime import datetime +from . import utc_now from peewee import ( AutoField, CharField, @@ -56,7 +56,7 @@ class User(BaseModel): username = CharField(max_length=64, unique=True) password_hash = CharField(max_length=128) email = CharField(max_length=255) - created_at = DateTimeField(default=datetime.now) + created_at = DateTimeField(default=utc_now) class Meta: table_name = "users" diff --git a/src/ddns_service/server.py b/src/ddns_service/server.py index 33f93b5..50e1f51 100644 --- a/src/ddns_service/server.py +++ b/src/ddns_service/server.py @@ -8,13 +8,12 @@ import json import logging import signal import ssl -from datetime import datetime from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from urllib.parse import parse_qs, urlparse import argon2 -from . import datetime_str +from . import datetime_str, utc_now from .cleanup import ExpiredRecordsCleanupThread, RateLimitCleanupThread from .logging import clear_txn_id, set_txn_id from .models import DoesNotExist, get_hostname_for_user, get_user @@ -316,7 +315,7 @@ class DDNSRequestHandler(BaseHTTPRequestHandler): except ValueError: return (400, "badip") - now = datetime.now(datetime.timezone.utc) + now = utc_now() changed = False if ipv4: