Răsfoiți Sursa

Fix midnight-boundary bugs in time field guards and CalDAV date search

time(0, 0, 0) is falsy in Python, so `if check_out_time:` guards silently
treated midnight as "no time set" — breaking is_window_active, checkout
scheduling, and window-state detection for midnight checkouts. All guards
changed to `is not None`.

CalDAV date_search now starts 24h before now so in-progress reservations
are always returned; RFC 4791 excludes events with DTEND <= search start,
which dropped a checkout-at-midnight event on a midnight poll.

CalDAV selection changed from `<= check_out` to `< check_out` to match the
exclusive-checkout invariant used everywhere else, and the `or` fallback for
missing check_out_time is replaced with an `is not None` guard so a midnight
checkout time isn't silently promoted to end-of-day.
ZiB 4 luni în urmă
părinte
comite
a3f5e8f73c

+ 9 - 4
custom_components/room_cal_ha/backends/caldav.py

@@ -68,8 +68,11 @@ class CalDavBackend(ReservationBackend):
 
     now_aware = dt_util.now()
     now = now_aware.replace(tzinfo=None)
+    # Search from 24h ago so in-progress reservations are always included.
+    # RFC 4791 excludes events with DTEND <= search start, which would drop a
+    # checkout-at-midnight event on a midnight poll.
     events = calendar.date_search(
-      start=now_aware,
+      start=now_aware - timedelta(days=1),
       end=now_aware + timedelta(days=90),
       expand=True,
     )
@@ -86,9 +89,11 @@ class CalDavBackend(ReservationBackend):
 
     for res in parsed:
       if res.check_in_date and res.check_out_date:
-        check_in = datetime.combine(res.check_in_date, res.check_in_time or datetime.min.time())
-        check_out = datetime.combine(res.check_out_date, res.check_out_time or datetime.max.time().replace(microsecond=0))
-        if check_in <= now <= check_out:
+        check_in_t = res.check_in_time if res.check_in_time is not None else datetime.min.time()
+        check_out_t = res.check_out_time if res.check_out_time is not None else datetime.max.time().replace(microsecond=0)
+        check_in = datetime.combine(res.check_in_date, check_in_t)
+        check_out = datetime.combine(res.check_out_date, check_out_t)
+        if check_in <= now < check_out:
           current = res
         elif check_in > now and nxt is None:
           nxt = res

+ 4 - 4
custom_components/room_cal_ha/coordinator.py

@@ -153,7 +153,7 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
     now = dt_util.now().replace(tzinfo=None)
 
     # At next reservation's check-in: promote next → current, then activate guest mode.
-    if nxt.check_in_date and nxt.check_in_time:
+    if nxt.check_in_date is not None and nxt.check_in_time is not None:
       check_in = datetime.combine(nxt.check_in_date, nxt.check_in_time)
       delay = (check_in - now).total_seconds()
       if delay > 0:
@@ -162,7 +162,7 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
         )
 
     # At current reservation's check-out: clear current, then deactivate guest mode.
-    if current.check_out_date and current.check_out_time:
+    if current.check_out_date is not None and current.check_out_time is not None:
       check_out = datetime.combine(current.check_out_date, current.check_out_time)
       delay = (check_out - now).total_seconds()
       if delay > 0:
@@ -177,9 +177,9 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
     """
     now = dt_util.now().replace(tzinfo=None)
     check_in = check_out = None
-    if res.check_in_date and res.check_in_time:
+    if res.check_in_date is not None and res.check_in_time is not None:
       check_in = datetime.combine(res.check_in_date, res.check_in_time)
-    if res.check_out_date and res.check_out_time:
+    if res.check_out_date is not None and res.check_out_time is not None:
       check_out = datetime.combine(res.check_out_date, res.check_out_time)
     if check_out and now >= check_out:
       return "elapsed"

+ 2 - 2
custom_components/room_cal_ha/models.py

@@ -131,11 +131,11 @@ def is_window_active(res: ReservationData | None, now: datetime) -> bool:
   Matches _reservation_window_state semantics: active once check_in is reached,
   until check_out (if present) is reached.
   """
-  if not res or not res.check_in_date or not res.check_in_time:
+  if not res or res.check_in_date is None or res.check_in_time is None:
     return False
   if datetime.combine(res.check_in_date, res.check_in_time) > now:
     return False
-  if res.check_out_date and res.check_out_time:
+  if res.check_out_date is not None and res.check_out_time is not None:
     if datetime.combine(res.check_out_date, res.check_out_time) <= now:
       return False
   return True