diff --git a/update.py b/update.py index cf72c7b..f96fc41 100755 --- a/update.py +++ b/update.py @@ -21,6 +21,7 @@ import agenda.mail import agenda.thespacedevs import agenda.types import agenda.uk_holiday +from agenda.event import Event from agenda.types import StrDict from web_view import app @@ -295,7 +296,11 @@ def update_gandi(config: flask.config.Config) -> None: def check_birthday_reminders(config: flask.config.Config) -> None: - """Check for upcoming birthdays and send email reminders.""" + """Send at most one grouped birthday reminder email per day. + + Collects birthdays in the next 7 days, groups them into sections + (Today/Tomorrow/In N days), and sends a single email. + """ today = date.today() data_dir = config["PERSONAL_DATA"] entities_file = os.path.join(data_dir, "entities.yaml") @@ -303,55 +308,65 @@ def check_birthday_reminders(config: flask.config.Config) -> None: if not os.path.exists(entities_file): return - # Get upcoming birthdays (next 7 days) birthdays = agenda.birthday.get_birthdays(today, entities_file) - upcoming_birthdays = [] - for birthday in birthdays: - days_until = (birthday.date - today).days + # Collect next 7 days into a dict keyed by days-until + by_days: dict[int, list[Event]] = {} + for ev in birthdays: + days_until = (ev.as_date - today).days if 0 <= days_until <= 7: - upcoming_birthdays.append((birthday, days_until)) + by_days.setdefault(days_until, []).append(ev) - if not upcoming_birthdays: + if not by_days: return - # Group birthdays by days until - birthday_groups = {} - for birthday, days_until in upcoming_birthdays: - if days_until not in birthday_groups: - birthday_groups[days_until] = [] - birthday_groups[days_until].append(birthday) + # Build subject + headings: list[str] = [] + if 0 in by_days: + headings.append("today") + if 1 in by_days: + headings.append("tomorrow") + others = sum(1 for k in by_days.keys() if k not in (0, 1)) + if others: + plural = "s" if others != 1 else "" + headings.append(f"{others} other{plural}") + subject = ( + f"🎂 Birthday reminders ({', '.join(headings)})" + if headings + else "🎂 Birthday reminders" + ) - # Send reminder emails - for days_until, birthdays in birthday_groups.items(): - if days_until == 0: - subject = "🎂 Birthday Today!" - elif days_until == 1: - subject = "🎂 Birthday Tomorrow!" + # Build body (UK style dates) + lines: list[str] = ["Upcoming birthdays (next 7 days):", ""] + for delta in sorted(by_days.keys()): + if delta == 0: + lines.append("Today") + elif delta == 1: + lines.append("Tomorrow") else: - subject = f"🎂 Birthday in {days_until} days" + lines.append(f"In {delta} days") - body_lines = ["Birthday Reminder:\n"] - for birthday in birthdays: - date_str = birthday.date.strftime("%A, %B %d, %Y") - if days_until == 0: - body_lines.append(f"Today: {birthday.title} - {date_str}") - elif days_until == 1: - body_lines.append(f"Tomorrow: {birthday.title} - {date_str}") - else: - body_lines.append(f"{date_str}: {birthday.title}") - - body_lines.append( - f"\nView all birthdays: https://edwardbetts.com/agenda/birthdays" + entries = sorted( + by_days[delta], + key=lambda e: (e.as_date, (e.title or e.name or "")), ) - body = "\n".join(body_lines) + for ev in entries: + d = ev.as_date + # Portable UK-style date: weekday, D Month YYYY + date_str = f"{d:%A}, {d.day} {d:%B %Y}" + label = ev.title or ev.name + lines.append(f" • {label} — {date_str}") - if sys.stdin.isatty(): - print(f"Birthday reminder: {subject}") - print(body) + lines.append("") - agenda.mail.send_mail(config, subject, body) + lines.append("View all birthdays: https://edwardbetts.com/agenda/birthdays") + body = "\n".join(lines) + + if sys.stdin.isatty(): + print(f"Birthday reminder: {subject}\n{body}") + + agenda.mail.send_mail(config, subject, body) def main() -> None: