| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- # -*- 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)
|