mirror of
https://github.com/skoelle/icloud-contacts-sync.git
synced 2026-09-17 23:40:24 +00:00
birthday unknown year filter
This commit is contained in:
@@ -24,6 +24,7 @@ from api.auth import get_current_user, resolve_account_for_user
|
|||||||
from api.schemas import ContactListResponse, ContactOut, SyncRunOut
|
from api.schemas import ContactListResponse, ContactOut, SyncRunOut
|
||||||
from config import Config
|
from config import Config
|
||||||
from mailer import build_message, fetch_birthdays_for_date, send_message
|
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")
|
logging.basicConfig(level=Config.LOG_LEVEL, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||||
logger = logging.getLogger("api")
|
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")
|
app.mount("/static", StaticFiles(directory="api/static"), name="static")
|
||||||
templates = Jinja2Templates(directory="api/templates")
|
templates = Jinja2Templates(directory="api/templates")
|
||||||
templates.env.filters["urlquote"] = lambda s: quote_plus(s or "")
|
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:
|
def _fmt_ts(dt) -> str | None:
|
||||||
|
|||||||
@@ -332,7 +332,7 @@
|
|||||||
{% if contact.birthday %}
|
{% if contact.birthday %}
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="birthday-highlight">
|
<div class="birthday-highlight">
|
||||||
🎂 {{ contact.birthday }}
|
🎂 {{ contact.birthday|fmt_birthday }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -367,7 +367,7 @@
|
|||||||
<div class="birthday-organization">{{ b.organization }}</div>
|
<div class="birthday-organization">{{ b.organization }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="birthday-date">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
{% if b.photo_url %}
|
{% if b.photo_url %}
|
||||||
|
|||||||
@@ -198,7 +198,7 @@
|
|||||||
<div class="contact-organization">{{ c.organization }}</div>
|
<div class="contact-organization">{{ c.organization }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if c.birthday %}
|
{% if c.birthday %}
|
||||||
<div class="contact-birthday">{{ c.birthday }}</div>
|
<div class="contact-birthday">{{ c.birthday|fmt_birthday }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div style="display:flex;flex-direction:column;align-items:center;gap:0.3rem">
|
<div style="display:flex;flex-direction:column;align-items:center;gap:0.3rem">
|
||||||
|
|||||||
+9
-6
@@ -13,6 +13,7 @@ from email.message import EmailMessage
|
|||||||
|
|
||||||
import db
|
import db
|
||||||
from config import Config
|
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")
|
logging.basicConfig(level=Config.LOG_LEVEL, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||||
logger = logging.getLogger("mailer")
|
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()}):", ""]
|
plain_lines = [f"Heutige Geburtstage ({today.isoformat()}):", ""]
|
||||||
for b in birthdays:
|
for b in birthdays:
|
||||||
age = today.year - b["birthday"].year
|
date_str = fmt_birthday_short(b["birthday"])
|
||||||
date_str = b["birthday"].strftime("%d.%m.")
|
age = fmt_birthday_age(b["birthday"], today)
|
||||||
line = f"- {b['full_name']} · {date_str} · {age} Jahre"
|
age_str = f" · {age} Jahre" if age is not None else ""
|
||||||
|
line = f"- {b['full_name']} · {date_str}{age_str}"
|
||||||
if b.get("organization"):
|
if b.get("organization"):
|
||||||
line += f" ({b['organization']})"
|
line += f" ({b['organization']})"
|
||||||
if Config.WEB_URL:
|
if Config.WEB_URL:
|
||||||
@@ -82,8 +84,9 @@ def build_message(birthdays: list[dict], target_date: date | None = None) -> Ema
|
|||||||
|
|
||||||
cards_html = ""
|
cards_html = ""
|
||||||
for b in birthdays:
|
for b in birthdays:
|
||||||
age = today.year - b["birthday"].year
|
date_str = fmt_birthday_short(b["birthday"])
|
||||||
date_str = b["birthday"].strftime("%d.%m.")
|
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 ""
|
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"]
|
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
|
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>
|
||||||
<div style="font-size:18px;font-weight:600;color:#222;">{name_html}</div>
|
<div style="font-size:18px;font-weight:600;color:#222;">{name_html}</div>
|
||||||
{org_html}
|
{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>
|
</div>
|
||||||
{photo_html}
|
{photo_html}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user