Runner: Kalender am Vorabend fuer den folgenden Tag pruefen

Mit next_day = 1 gelten Wochentage, Ferien und Feiertage fuer morgen
(gemeinter_tag). kalender() merkt sich dafuer heute und morgen und haelt
einen fehlgeschlagenen Abruf nicht mehr den ganzen Tag fest. Uhrzeit,
Zeitfenster und once_per_day bleiben beim heutigen Tag. vorabend.sql legt
die Spalte an.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 09:33:59 +02:00
co-authored by Claude Opus 5
parent 8c62455b2c
commit 8340e8356e
3 changed files with 97 additions and 13 deletions
+43 -13
View File
@@ -515,9 +515,30 @@ def im_zeitfenster(jetzt, von, bis):
NIE, EGAL, ZUSAETZLICH = 0, 1, 2
def tag_passt(automatik, jetzt, kalender):
def gemeinter_tag(automatik, jetzt):
"""
Faellt der heutige Tag in den Rahmen der Automatik?
Fuer welchen Tag gelten Wochentage, Ferien und Feiertage?
Normalerweise fuer heute. Mit `next_day` fuer morgen - die Vorabend-Form:
"Kinderrollos zu, wenn morgen Schule ist" heisst Mo-Fr, Ferien nie,
Feiertage nie, und der Runner schaut dafuer auf den folgenden Tag. Mit
dem heutigen Tag liess sich das nur annaehern (So-Do, heute keine
Ferien), und das ging am letzten Ferientag, am Abend vor einem Feiertag
und am Abend eines Feiertags daneben.
Nur der Rahmen verschiebt sich. Uhrzeit, Zeitfenster und "einmal am Tag"
bleiben beim heutigen Tag - die Automatik laeuft ja heute Abend. Ein
Zeitfenster ueber Mitternacht meint nach Mitternacht deshalb schon den
uebernaechsten Tag; fuer eine Vorabend-Regel ist das kein sinnvoller Fall.
"""
heute = jetzt.date()
return heute + timedelta(days=1) if automatik.get("next_day") else heute
def tag_passt(automatik, tag, kalender):
"""
Faellt der Tag in den Rahmen der Automatik? `tag` ist heute oder morgen,
siehe gemeinter_tag(); `kalender` gehoert zu genau diesem Tag.
Die Wochentage sind eine Maske, Ferien und Feiertage haben je drei
Zustaende. Zwei davon gab es immer: EGAL (der Tag aendert nichts, die
@@ -536,7 +557,7 @@ def tag_passt(automatik, jetzt, kalender):
den Ferien nicht - ein Verbot schlaegt eine Erweiterung. Anders herum
liesse sich "nie" nicht mehr verlassen.
"""
passt = bool(automatik["weekdays"] & (1 << jetzt.weekday()))
passt = bool(automatik["weekdays"] & (1 << tag.weekday()))
if kalender["feiertag"] and automatik["on_holiday"] == ZUSAETZLICH:
passt = True
if kalender["ferien"] and automatik["on_vacation"] == ZUSAETZLICH:
@@ -718,7 +739,7 @@ class Runner:
self.war_aktiv = {} # automation_id -> war im Zeitfenster
self.lief_im_fenster = {} # automation_id -> hat im Fenster ausgeloest
self._sonne = (None, "00:00", "00:00") # (datum, aufgang, untergang)
self._kalender = (None, {"feiertag": False, "ferien": False})
self._kalender = {} # datum -> {"feiertag": .., "ferien": ..}
self._letzte_saeuberung = None
self.reihenfolge = [] # automation_id, Ausloeser vor Nachfolger
self.ausloeser_states = {} # automation_id -> state_id des Ausloesers
@@ -991,21 +1012,30 @@ class Runner:
return "%02d:%02d" % (minute // 60 % 24, minute % 60)
return str(wert)[:5]
def kalender(self):
"""Ferien und Feiertage von heute, einmal je Tag geholt."""
heute = date.today()
if self._kalender[0] == heute:
return self._kalender[1]
def kalender(self, tag):
"""
Ferien und Feiertage eines Tages, je Tag einmal geholt.
Gefragt wird nach heute und - fuer Vorabend-Regeln - nach morgen.
Gemerkt werden deshalb beide; was vor heute liegt, fliegt raus.
"""
if tag in self._kalender:
return self._kalender[tag]
stand = {"feiertag": False, "ferien": False}
try:
with self.db.cursor() as c:
c.execute("SELECT holiday, vacation FROM calendar_days WHERE date = %s", (heute,))
c.execute("SELECT holiday, vacation FROM calendar_days WHERE date = %s", (tag,))
zeile = c.fetchone()
if zeile:
stand = {"feiertag": bool(zeile["holiday"]), "ferien": bool(zeile["vacation"])}
except Exception as fehler:
# Nicht merken: beim naechsten Durchlauf noch einmal fragen,
# statt den ganzen Tag mit "kein Feiertag" weiterzurechnen.
logger.warning("Kalender nicht lesbar: %r", fehler)
self._kalender = (heute, stand)
return stand
heute = date.today()
self._kalender = {t: s for t, s in self._kalender.items() if t >= heute}
self._kalender[tag] = stand
return stand
# --- Werte -----------------------------------------------------------
@@ -1163,13 +1193,13 @@ class Runner:
def durchlauf(self, fenster=NACHHOLFENSTER):
jetzt = datetime.now()
kalender = self.kalender()
for automation_id in self.reihenfolge:
automatik = self.regelwerk.automatiken.get(automation_id)
if automatik is None:
continue
aktiv = (tag_passt(automatik, jetzt, kalender)
tag = gemeinter_tag(automatik, jetzt)
aktiv = (tag_passt(automatik, tag, self.kalender(tag))
and im_zeitfenster(jetzt, automatik["window_from"], automatik["window_to"]))
vorher_aktiv = self.war_aktiv.get(automatik["id"], aktiv)