| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 |
- # -*- coding: utf-8 -*-
- """
- Time Conditions - Zeitbasierte Bedingungen.
- """
- from datetime import datetime, time, date
- from typing import Any
- from trixy_core.scheduler.condition.base import Condition
- from trixy_core.scheduler.form_fields import FieldType, FormField, FormValidationError
- class TimeRangeCondition(Condition):
- """
- Prüft ob aktuelle Zeit in einem Zeitfenster liegt.
- Beispiel:
- - TimeRangeCondition(time(9, 0), time(17, 0)) - 9:00-17:00
- - TimeRangeCondition(time(22, 0), time(6, 0)) - 22:00-06:00 (über Mitternacht)
- """
- def __init__(
- self,
- start_time: time,
- end_time: time,
- name: str | None = None,
- invert: bool = False,
- ):
- """
- Initialisiert die Condition.
- Args:
- start_time: Startzeit
- end_time: Endzeit
- name: Condition-Name
- invert: Ergebnis invertieren
- """
- super().__init__(name, invert)
- self._start_time = start_time
- self._end_time = end_time
- @property
- def start_time(self) -> time:
- """Startzeit."""
- return self._start_time
- @property
- def end_time(self) -> time:
- """Endzeit."""
- return self._end_time
- def _evaluate(self, context: dict) -> bool:
- """Prüft ob aktuelle Zeit im Zeitfenster liegt."""
- now = context.get("current_time", datetime.now())
- if isinstance(now, datetime):
- current_time = now.time()
- else:
- current_time = now
- # Normal: Start < End (z.B. 9:00-17:00)
- if self._start_time <= self._end_time:
- return self._start_time <= current_time <= self._end_time
- # Über Mitternacht: Start > End (z.B. 22:00-06:00)
- return current_time >= self._start_time or current_time <= self._end_time
- def to_dict(self) -> dict:
- """Konvertiert zu Dictionary."""
- return {
- **self._base_dict(),
- "start_time": self._start_time.isoformat(),
- "end_time": self._end_time.isoformat(),
- }
- @classmethod
- def from_dict(cls, data: dict) -> "TimeRangeCondition":
- """Erstellt Condition aus Dictionary."""
- return cls(
- start_time=time.fromisoformat(data["start_time"]),
- end_time=time.fromisoformat(data["end_time"]),
- name=data.get("name"),
- invert=data.get("invert", False),
- )
- @classmethod
- def display_name(cls) -> str:
- """Anzeigename fuer die TUI."""
- return "Zeitfenster"
- @classmethod
- def form_fields(cls) -> list[FormField]:
- """Gibt die Formular-Felder fuer die TUI zurueck."""
- return [
- FormField(
- name="start_time", label="Startzeit", required=True,
- placeholder="09:00:00", help_text="Format: HH:MM:SS",
- ),
- FormField(
- name="end_time", label="Endzeit", required=True,
- placeholder="17:00:00", help_text="Format: HH:MM:SS",
- ),
- FormField(
- name="invert", label="Invertieren",
- field_type=FieldType.CHECKBOX, default=False,
- ),
- ]
- @classmethod
- def validate_form(cls, data: dict) -> list[FormValidationError]:
- """Validiert Formular-Daten. Gibt Liste von Fehlern zurueck."""
- errors: list[FormValidationError] = []
- for field_name in ("start_time", "end_time"):
- val = data.get(field_name, "")
- if not val:
- label = "Startzeit" if field_name == "start_time" else "Endzeit"
- errors.append(FormValidationError(field_name, f"{label} ist erforderlich"))
- else:
- try:
- time.fromisoformat(val)
- except ValueError:
- errors.append(FormValidationError(field_name, "Ungueltiges Zeitformat (HH:MM:SS erwartet)"))
- return errors
- class DayOfWeekCondition(Condition):
- """
- Prüft ob aktueller Wochentag in Liste ist.
- Beispiel:
- - DayOfWeekCondition([0, 1, 2, 3, 4]) - Montag-Freitag
- - DayOfWeekCondition([5, 6]) - Wochenende
- """
- # Aliase für Wochentage (Python: 0=Mo, 6=So)
- DAY_ALIASES = {
- "MON": 0, "MONDAY": 0,
- "TUE": 1, "TUESDAY": 1,
- "WED": 2, "WEDNESDAY": 2,
- "THU": 3, "THURSDAY": 3,
- "FRI": 4, "FRIDAY": 4,
- "SAT": 5, "SATURDAY": 5,
- "SUN": 6, "SUNDAY": 6,
- }
- def __init__(
- self,
- days: list[int | str],
- name: str | None = None,
- invert: bool = False,
- ):
- """
- Initialisiert die Condition.
- Args:
- days: Liste von Wochentagen (0-6 oder Namen)
- name: Condition-Name
- invert: Ergebnis invertieren
- """
- super().__init__(name, invert)
- self._days = self._parse_days(days)
- def _parse_days(self, days: list[int | str]) -> set[int]:
- """Parsed Wochentage."""
- result = set()
- for day in days:
- if isinstance(day, int):
- if 0 <= day <= 6:
- result.add(day)
- elif isinstance(day, str):
- day_upper = day.upper()
- if day_upper in self.DAY_ALIASES:
- result.add(self.DAY_ALIASES[day_upper])
- return result
- @property
- def days(self) -> set[int]:
- """Erlaubte Wochentage."""
- return self._days.copy()
- def _evaluate(self, context: dict) -> bool:
- """Prüft ob aktueller Wochentag erlaubt ist."""
- now = context.get("current_time", datetime.now())
- if isinstance(now, datetime):
- weekday = now.weekday()
- else:
- weekday = datetime.now().weekday()
- return weekday in self._days
- def to_dict(self) -> dict:
- """Konvertiert zu Dictionary."""
- return {
- **self._base_dict(),
- "days": list(self._days),
- }
- @classmethod
- def from_dict(cls, data: dict) -> "DayOfWeekCondition":
- """Erstellt Condition aus Dictionary."""
- return cls(
- days=data["days"],
- name=data.get("name"),
- invert=data.get("invert", False),
- )
- @classmethod
- def display_name(cls) -> str:
- """Anzeigename fuer die TUI."""
- return "Wochentag"
- @classmethod
- def form_fields(cls) -> list[FormField]:
- """Gibt die Formular-Felder fuer die TUI zurueck."""
- return [
- FormField(
- name="days", label="Wochentage",
- field_type=FieldType.WEEKDAYS, required=True,
- help_text="Erlaubte Wochentage",
- ),
- FormField(
- name="invert", label="Invertieren",
- field_type=FieldType.CHECKBOX, default=False,
- ),
- ]
- @classmethod
- def validate_form(cls, data: dict) -> list[FormValidationError]:
- """Validiert Formular-Daten. Gibt Liste von Fehlern zurueck."""
- errors: list[FormValidationError] = []
- days = data.get("days", [])
- if not days:
- errors.append(FormValidationError("days", "Mindestens ein Wochentag muss ausgewaehlt sein"))
- return errors
- @classmethod
- def weekdays(cls, name: str | None = None) -> "DayOfWeekCondition":
- """Erstellt Condition für Werktage (Mo-Fr)."""
- return cls([0, 1, 2, 3, 4], name=name or "Weekdays")
- @classmethod
- def weekends(cls, name: str | None = None) -> "DayOfWeekCondition":
- """Erstellt Condition für Wochenenden (Sa-So)."""
- return cls([5, 6], name=name or "Weekends")
- class DateRangeCondition(Condition):
- """
- Prüft ob aktuelles Datum in einem Bereich liegt.
- Beispiel:
- - DateRangeCondition(date(2024, 12, 1), date(2024, 12, 31)) - Dezember 2024
- """
- def __init__(
- self,
- start_date: date | None = None,
- end_date: date | None = None,
- name: str | None = None,
- invert: bool = False,
- ):
- """
- Initialisiert die Condition.
- Args:
- start_date: Startdatum (None = unbegrenzt)
- end_date: Enddatum (None = unbegrenzt)
- name: Condition-Name
- invert: Ergebnis invertieren
- """
- super().__init__(name, invert)
- self._start_date = start_date
- self._end_date = end_date
- @property
- def start_date(self) -> date | None:
- """Startdatum."""
- return self._start_date
- @property
- def end_date(self) -> date | None:
- """Enddatum."""
- return self._end_date
- def _evaluate(self, context: dict) -> bool:
- """Prüft ob aktuelles Datum im Bereich liegt."""
- now = context.get("current_time", datetime.now())
- if isinstance(now, datetime):
- current_date = now.date()
- elif isinstance(now, date):
- current_date = now
- else:
- current_date = datetime.now().date()
- if self._start_date and current_date < self._start_date:
- return False
- if self._end_date and current_date > self._end_date:
- return False
- return True
- def to_dict(self) -> dict:
- """Konvertiert zu Dictionary."""
- return {
- **self._base_dict(),
- "start_date": self._start_date.isoformat() if self._start_date else None,
- "end_date": self._end_date.isoformat() if self._end_date else None,
- }
- @classmethod
- def from_dict(cls, data: dict) -> "DateRangeCondition":
- """Erstellt Condition aus Dictionary."""
- start = None
- end = None
- if data.get("start_date"):
- start = date.fromisoformat(data["start_date"])
- if data.get("end_date"):
- end = date.fromisoformat(data["end_date"])
- return cls(
- start_date=start,
- end_date=end,
- name=data.get("name"),
- invert=data.get("invert", False),
- )
- @classmethod
- def display_name(cls) -> str:
- """Anzeigename fuer die TUI."""
- return "Datumsbereich"
- @classmethod
- def form_fields(cls) -> list[FormField]:
- """Gibt die Formular-Felder fuer die TUI zurueck."""
- return [
- FormField(
- name="start_date", label="Startdatum",
- field_type=FieldType.DATETIME,
- placeholder="2024-01-01", help_text="Format: YYYY-MM-DD",
- ),
- FormField(
- name="end_date", label="Enddatum",
- field_type=FieldType.DATETIME,
- placeholder="2024-12-31", help_text="Format: YYYY-MM-DD",
- ),
- FormField(
- name="invert", label="Invertieren",
- field_type=FieldType.CHECKBOX, default=False,
- ),
- ]
- @classmethod
- def validate_form(cls, data: dict) -> list[FormValidationError]:
- """Validiert Formular-Daten. Gibt Liste von Fehlern zurueck."""
- errors: list[FormValidationError] = []
- for fn in ("start_date", "end_date"):
- val = data.get(fn, "")
- if val:
- try:
- date.fromisoformat(val)
- except ValueError:
- label = "Startdatum" if fn == "start_date" else "Enddatum"
- errors.append(FormValidationError(fn, f"Ungueltiges {label} (YYYY-MM-DD erwartet)"))
- return errors
|