# -*- coding: utf-8 -*- """ Datums-/Zeit-Utilities fuer das Calendar-Plugin. Parsing von deutschen Datums-/Zeitangaben und TTS-Formatierung. """ import re from datetime import date, datetime, time, timedelta from plugins.calendar.models import CalendarEvent # ========================================================================= # Zeit-Parsing # ========================================================================= def parse_time_string(s: str) -> tuple[int, int] | None: """ Parst eine deutsche Zeitangabe. Beispiele: "15 Uhr" -> (15, 0) "9:30" -> (9, 30) "15:00 Uhr" -> (15, 0) "halb drei" -> (14, 30) Args: s: Zeitangabe als String Returns: (stunde, minute) oder None bei Fehler """ if not s: return None s = s.strip().lower() # "15:00 Uhr" oder "15:00" oder "9:30" m = re.match(r"(\d{1,2}):(\d{2})\s*(?:uhr)?$", s) if m: h, mi = int(m.group(1)), int(m.group(2)) if 0 <= h <= 23 and 0 <= mi <= 59: return (h, mi) # "15 Uhr" oder "15uhr" m = re.match(r"(\d{1,2})\s*uhr$", s) if m: h = int(m.group(1)) if 0 <= h <= 23: return (h, 0) # "15 Uhr 30" m = re.match(r"(\d{1,2})\s*uhr\s*(\d{1,2})$", s) if m: h, mi = int(m.group(1)), int(m.group(2)) if 0 <= h <= 23 and 0 <= mi <= 59: return (h, mi) # Nur Zahl (als Stunde interpretieren) m = re.match(r"(\d{1,2})$", s) if m: h = int(m.group(1)) if 0 <= h <= 23: return (h, 0) return None # ========================================================================= # Datum-Parsing # ========================================================================= _WEEKDAY_MAP = { "montag": 0, "dienstag": 1, "mittwoch": 2, "donnerstag": 3, "freitag": 4, "samstag": 5, "sonntag": 6, } def parse_date_string(s: str) -> date | None: """ Parst eine deutsche Datumsangabe. Beispiele: "heute" -> today "morgen" -> tomorrow "uebermorgen" -> day after tomorrow "Montag" -> naechster Montag "15.03." -> 15. Maerz (aktuelles Jahr) "15.03.2026" -> 15. Maerz 2026 Args: s: Datumsangabe als String Returns: date oder None bei Fehler """ if not s: return None s = s.strip().lower() today = date.today() # Relative Angaben if s in ("heute", "today"): return today if s in ("morgen", "tomorrow"): return today + timedelta(days=1) if s in ("uebermorgen", "übermorgen"): return today + timedelta(days=2) # Wochentag for day_name, day_idx in _WEEKDAY_MAP.items(): if s == day_name: current_idx = today.weekday() diff = day_idx - current_idx if diff <= 0: diff += 7 return today + timedelta(days=diff) # "15.03." oder "15.3." m = re.match(r"(\d{1,2})\.(\d{1,2})\.$", s) if m: day, month = int(m.group(1)), int(m.group(2)) try: target = date(today.year, month, day) # Falls in Vergangenheit, naechstes Jahr if target < today: target = date(today.year + 1, month, day) return target except ValueError: return None # "15.03.2026" m = re.match(r"(\d{1,2})\.(\d{1,2})\.(\d{4})$", s) if m: day, month, year = int(m.group(1)), int(m.group(2)), int(m.group(3)) try: return date(year, month, day) except ValueError: return None return None # ========================================================================= # TTS-Formatierung # ========================================================================= _MONTH_NAMES = [ "", "Januar", "Februar", "Maerz", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember", ] _WEEKDAY_NAMES = [ "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag", ] def format_event_time(event: CalendarEvent) -> str: """ Formatiert die Uhrzeit eines Events fuer TTS. Beispiele: "um 15 Uhr" "von 15 bis 16 Uhr" "ganztaegig" Args: event: Kalender-Event Returns: Formatierter Zeitstring """ if event.all_day: return "ganztaegig" start_h = event.start.hour start_m = event.start.minute end_h = event.end.hour end_m = event.end.minute start_str = _format_clock_time(start_h, start_m) end_str = _format_clock_time(end_h, end_m) if start_str == end_str: return f"um {start_str}" return f"von {start_str} bis {end_str}" def _format_clock_time(h: int, m: int) -> str: """Formatiert Stunde:Minute als TTS-String.""" if m == 0: return f"{h} Uhr" return f"{h} Uhr {m:02d}" def format_event_date(event: CalendarEvent) -> str: """ Formatiert das Datum eines Events fuer TTS. Beispiele: "heute" "morgen" "am Freitag" "am 15. Maerz" Args: event: Kalender-Event Returns: Formatierter Datumsstring """ event_date = event.start.date() today = date.today() diff = (event_date - today).days if diff == 0: return "heute" if diff == 1: return "morgen" if diff == 2: return "uebermorgen" if 0 < diff <= 6: return f"am {_WEEKDAY_NAMES[event_date.weekday()]}" day = event_date.day month = _MONTH_NAMES[event_date.month] return f"am {day}. {month}" def format_event_for_tts(event: CalendarEvent) -> str: """ Formatiert einen kompletten Termin fuer TTS. Beispiel: "Stand-Up heute von 14 bis 15 Uhr" Args: event: Kalender-Event Returns: Kompletter TTS-String """ date_str = format_event_date(event) time_str = format_event_time(event) if event.all_day: return f"{event.title} {date_str}, {time_str}" return f"{event.title} {date_str} {time_str}" def format_attendees_for_tts(attendees: list[str]) -> str: """ Formatiert eine Teilnehmerliste fuer TTS. Beispiel: "André, Max und Lisa" Args: attendees: Liste von Teilnehmernamen Returns: Formatierter String """ if not attendees: return "" if len(attendees) == 1: return attendees[0] return ", ".join(attendees[:-1]) + " und " + attendees[-1] def get_day_range(target_date: date) -> tuple[datetime, datetime]: """ Gibt den Zeitbereich eines Tages zurueck. Args: target_date: Zieldatum Returns: (Tagesanfang 00:00, Tagesende 23:59:59) """ start = datetime.combine(target_date, time.min) end = datetime.combine(target_date, time.max) return (start, end)