birthday unknown year filter

This commit is contained in:
2026-08-07 13:34:18 +02:00
parent 5cdefff6f5
commit 34860e8ae4
6 changed files with 37 additions and 9 deletions
+3
View File
@@ -24,6 +24,7 @@ from api.auth import get_current_user, resolve_account_for_user
from api.schemas import ContactListResponse, ContactOut, SyncRunOut
from config import Config
from mailer import build_message, fetch_birthdays_for_date, send_message
from utils import fmt_birthday_age, fmt_birthday_short
logging.basicConfig(level=Config.LOG_LEVEL, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger("api")
@@ -33,6 +34,8 @@ app.add_middleware(SessionMiddleware, secret_key=secrets.token_hex(32), session_
app.mount("/static", StaticFiles(directory="api/static"), name="static")
templates = Jinja2Templates(directory="api/templates")
templates.env.filters["urlquote"] = lambda s: quote_plus(s or "")
templates.env.filters["fmt_birthday"] = fmt_birthday_short
templates.env.filters["fmt_age"] = fmt_birthday_age
def _fmt_ts(dt) -> str | None:
+1 -1
View File
@@ -332,7 +332,7 @@
{% if contact.birthday %}
<div class="section">
<div class="birthday-highlight">
🎂 {{ contact.birthday }}
🎂 {{ contact.birthday|fmt_birthday }}
</div>
</div>
{% endif %}
+1 -1
View File
@@ -367,7 +367,7 @@
<div class="birthday-organization">{{ b.organization }}</div>
{% endif %}
<div class="birthday-date">
{{ b.birthday.strftime('%d.%m.') }}{% if b.birthday.year and b.birthday.year < current_year %} · {{ current_year - b.birthday.year }} Jahre{% endif %}
{{ b.birthday|fmt_birthday }}{% if b.birthday|fmt_age %} · {{ b.birthday|fmt_age(current_year) }} Jahre{% endif %}
</div>
</div>
{% if b.photo_url %}
+1 -1
View File
@@ -198,7 +198,7 @@
<div class="contact-organization">{{ c.organization }}</div>
{% endif %}
{% if c.birthday %}
<div class="contact-birthday">{{ c.birthday }}</div>
<div class="contact-birthday">{{ c.birthday|fmt_birthday }}</div>
{% endif %}
</div>
<div style="display:flex;flex-direction:column;align-items:center;gap:0.3rem">
+9 -6
View File
@@ -13,6 +13,7 @@ from email.message import EmailMessage
import db
from config import Config
from utils import fmt_birthday_age, fmt_birthday_short
logging.basicConfig(level=Config.LOG_LEVEL, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger("mailer")
@@ -68,9 +69,10 @@ def build_message(birthdays: list[dict], target_date: date | None = None) -> Ema
plain_lines = [f"Heutige Geburtstage ({today.isoformat()}):", ""]
for b in birthdays:
age = today.year - b["birthday"].year
date_str = b["birthday"].strftime("%d.%m.")
line = f"- {b['full_name']} · {date_str} · {age} Jahre"
date_str = fmt_birthday_short(b["birthday"])
age = fmt_birthday_age(b["birthday"], today)
age_str = f" · {age} Jahre" if age is not None else ""
line = f"- {b['full_name']} · {date_str}{age_str}"
if b.get("organization"):
line += f" ({b['organization']})"
if Config.WEB_URL:
@@ -82,8 +84,9 @@ def build_message(birthdays: list[dict], target_date: date | None = None) -> Ema
cards_html = ""
for b in birthdays:
age = today.year - b["birthday"].year
date_str = b["birthday"].strftime("%d.%m.")
date_str = fmt_birthday_short(b["birthday"])
age = fmt_birthday_age(b["birthday"], today)
age_str = f" · {age} Jahre" if age is not None else ""
contact_link = f"{Config.WEB_URL.rstrip('/')}/contacts/{b['id']}" if Config.WEB_URL else ""
name_html = f'<a href="{contact_link}" style="color:#222;text-decoration:none;">{b["full_name"]}</a>' if contact_link else b["full_name"]
is_today = b["birthday"].month == today.month and b["birthday"].day == today.day
@@ -99,7 +102,7 @@ def build_message(birthdays: list[dict], target_date: date | None = None) -> Ema
<div>
<div style="font-size:18px;font-weight:600;color:#222;">{name_html}</div>
{org_html}
<div style="font-size:14px;color:#888;margin-top:4px;">🎂 {date_str} · {age} Jahre</div>
<div style="font-size:14px;color:#888;margin-top:4px;">🎂 {date_str}{age_str}</div>
</div>
{photo_html}
</div>
+22
View File
@@ -0,0 +1,22 @@
# 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
UNKNOWN_YEARS = frozenset({0, 1604, 1900})
def is_unknown_year(birthday: date) -> bool:
return birthday.year in UNKNOWN_YEARS
def fmt_birthday_short(birthday: date) -> str:
if is_unknown_year(birthday):
return birthday.strftime("%d.%m.")
return birthday.strftime("%d.%m.%Y")
def fmt_birthday_age(birthday: date, reference) -> int | None:
if is_unknown_year(birthday):
return None
ref_year = reference if isinstance(reference, int) else reference.year
return ref_year - birthday.year