timezone config

This commit is contained in:
2026-08-07 13:06:48 +02:00
parent 8eaac47ef6
commit 04f464c7bb
7 changed files with 42 additions and 25 deletions
+20 -11
View File
@@ -10,7 +10,7 @@ Benutzernamen im Remote-User-Header mitschickt."""
import json
import logging
import secrets
from datetime import date
from datetime import date, datetime
from urllib.parse import quote_plus
from fastapi import Depends, FastAPI, Query, Request
@@ -22,7 +22,7 @@ from starlette.middleware.sessions import SessionMiddleware
import db
from api.auth import get_current_user, resolve_account_for_user
from api.schemas import ContactListResponse, ContactOut, SyncRunOut
from config import Config
from config import TIMEZONE, Config
from mailer import build_message, fetch_birthdays_for_date, send_message
logging.basicConfig(level=Config.LOG_LEVEL, format="%(asctime)s [%(levelname)s] %(message)s")
@@ -35,6 +35,15 @@ templates = Jinja2Templates(directory="api/templates")
templates.env.filters["urlquote"] = lambda s: quote_plus(s or "")
def _fmt_ts(dt) -> str | None:
if dt is None:
return None
if dt.tzinfo is None:
from datetime import timezone
dt = dt.replace(tzinfo=timezone.utc)
return dt.astimezone(TIMEZONE).strftime("%d.%m.%Y %H:%M:%S")
def _row_to_contact_out(row: dict) -> dict:
row = dict(row)
for field in ["emails", "phones", "addresses", "urls", "social_profiles", "categories"]:
@@ -42,7 +51,7 @@ def _row_to_contact_out(row: dict) -> dict:
row[field] = json.loads(raw) if raw else []
if not row.get("full_name"):
row["full_name"] = db._build_full_name(row)
row["updated_at"] = str(row["updated_at"])
row["updated_at"] = _fmt_ts(row["updated_at"])
return row
@@ -128,7 +137,7 @@ def get_contact(contact_id: int, current_user: str = Depends(get_current_user)):
@app.get("/api/contacts/birthdays/today", response_model=list[ContactOut])
def birthdays_today(current_user: str = Depends(get_current_user)):
account_name, is_admin = resolve_account_for_user(current_user)
today = date.today()
today = datetime.now(TIMEZONE).date()
with db.get_connection() as conn:
where_clause, params = _account_filter_clause(account_name)
@@ -184,8 +193,8 @@ def list_sync_runs(current_user: str = Depends(get_current_user)):
rows = cur.fetchall()
for r in rows:
r["started_at"] = str(r["started_at"])
r["finished_at"] = str(r["finished_at"]) if r["finished_at"] else None
r["started_at"] = _fmt_ts(r["started_at"])
r["finished_at"] = _fmt_ts(r["finished_at"])
return rows
@@ -224,11 +233,11 @@ def web_dashboard(
last_sync_with_changes = cur.fetchone()
if last_sync:
last_sync["started_at"] = str(last_sync["started_at"])
last_sync["finished_at"] = str(last_sync["finished_at"]) if last_sync["finished_at"] else None
last_sync["started_at"] = _fmt_ts(last_sync["started_at"])
last_sync["finished_at"] = _fmt_ts(last_sync["finished_at"])
if last_sync_with_changes:
last_sync_with_changes["started_at"] = str(last_sync_with_changes["started_at"])
last_sync_with_changes["started_at"] = _fmt_ts(last_sync_with_changes["started_at"])
if last_sync and last_sync["started_at"] == last_sync_with_changes["started_at"]:
last_sync_with_changes = None
@@ -244,8 +253,8 @@ def web_dashboard(
"upcoming_birthdays": upcoming_birthdays,
"last_sync": last_sync,
"last_sync_with_changes": last_sync_with_changes,
"current_year": date.today().year,
"today": date.today(),
"current_year": datetime.now(TIMEZONE).date().year,
"today": datetime.now(TIMEZONE).date(),
},
)
+1
View File
@@ -1,6 +1,7 @@
# Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
# Licensed under the MIT License. See LICENSE file in project root for details.
from datetime import date
from pydantic import BaseModel
+3
View File
@@ -2,6 +2,7 @@
# Licensed under the MIT License. See LICENSE file in project root for details.
import json
import os
from zoneinfo import ZoneInfo
from dotenv import load_dotenv
@@ -29,6 +30,8 @@ class Config:
ACCOUNTS_CONFIG_PATH = os.environ.get("ACCOUNTS_CONFIG_PATH", "/app/config/accounts.json")
TIMEZONE = ZoneInfo(os.environ.get("TIMEZONE", "Europe/Berlin"))
LOG_LEVEL = os.environ.get("LOG_LEVEL", "INFO")
SOURCE_NAME = "icloud"
+4 -4
View File
@@ -5,12 +5,12 @@ import logging
import os
import uuid
from contextlib import contextmanager
from datetime import date, datetime
from datetime import datetime, timezone
import pymysql
from pymysql.cursors import DictCursor
from config import Config
from config import TIMEZONE, Config
logger = logging.getLogger(__name__)
@@ -95,7 +95,7 @@ def finish_sync_run(conn, run_id: str, status: str, upserted: int = None, delete
def upsert_contacts(conn, contacts: list[dict], run_id: str):
if not contacts:
return
now = datetime.now()
now = datetime.now(timezone.utc)
with conn.cursor() as cur:
for c in contacts:
c["sync_run_id"] = run_id
@@ -219,7 +219,7 @@ def search_contacts_without_social(conn, account: str | None) -> list[dict]:
def get_upcoming_birthdays(conn, account: str | None, days: int = 7) -> list[dict]:
where_clause, params = _account_filter_clause(account)
today = date.today()
today = datetime.now(TIMEZONE).date()
with conn.cursor() as cur:
cur.execute(
f"""SELECT id, full_name, given_name, middle_name, family_name,
+6 -6
View File
@@ -8,11 +8,11 @@ die Tabelle birthday_mail_log."""
import logging
import smtplib
import sys
from datetime import date
from datetime import date, datetime
from email.message import EmailMessage
import db
from config import Config
from config import TIMEZONE, Config
logging.basicConfig(level=Config.LOG_LEVEL, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger("mailer")
@@ -33,18 +33,18 @@ def fetch_birthdays_for_date(conn, target_date: date) -> list[dict]:
def fetch_todays_birthdays(conn) -> list[dict]:
return fetch_birthdays_for_date(conn, date.today())
return fetch_birthdays_for_date(conn, datetime.now(TIMEZONE).date())
def already_sent_today(conn) -> bool:
today = date.today()
today = datetime.now(TIMEZONE).date()
with conn.cursor() as cur:
cur.execute("SELECT 1 FROM birthday_mail_log WHERE sent_date = %s", (today,))
return cur.fetchone() is not None
def log_sent(conn, count: int):
today = date.today()
today = datetime.now(TIMEZONE).date()
with conn.cursor() as cur:
cur.execute(
"INSERT INTO birthday_mail_log (sent_date, contacts_count) VALUES (%s, %s)",
@@ -54,7 +54,7 @@ def log_sent(conn, count: int):
def build_message(birthdays: list[dict], target_date: date | None = None) -> EmailMessage:
today = target_date or date.today()
today = target_date or datetime.now(TIMEZONE).date()
msg = EmailMessage()
msg["From"] = Config.MAIL_FROM
msg["To"] = Config.MAIL_TO
+5 -4
View File
@@ -19,6 +19,7 @@ import time
from datetime import datetime, timedelta
import db
from config import TIMEZONE
logging.basicConfig(
level=os.environ.get("LOG_LEVEL", "INFO"),
@@ -62,7 +63,7 @@ def run_sync():
run_script("Sync", "/app/sync.py")
try:
with open("/tmp/last_sync_ok", "w") as f:
f.write(datetime.now().isoformat())
f.write(datetime.now(TIMEZONE).isoformat())
except OSError:
pass
@@ -74,7 +75,7 @@ def run_mailer():
def next_run_time(hour: int) -> datetime:
now = datetime.now()
now = datetime.now(TIMEZONE)
target = now.replace(hour=hour, minute=0, second=0, microsecond=0)
if target <= now:
target += timedelta(days=1)
@@ -103,11 +104,11 @@ def main():
logger.info("Starte ersten Sync-Lauf...")
run_sync()
last_sync = datetime.now()
last_sync = datetime.now(TIMEZONE)
next_mailer = next_run_time(MAIL_SEND_HOUR)
while not _shutdown:
now = datetime.now()
now = datetime.now(TIMEZONE)
if now >= last_sync + timedelta(minutes=SYNC_INTERVAL_MINUTES):
run_sync()