Просмотр исходного кода

Use naive local datetimes for room-local time comparisons, restore boundary tests

ZiB 4 месяцев назад
Родитель
Сommit
720816bd6f
2 измененных файлов с 18 добавлено и 15 удалено
  1. 6 10
      custom_components/room_cal_ha/coordinator.py
  2. 12 5
      tests/test_coordinator.py

+ 6 - 10
custom_components/room_cal_ha/coordinator.py

@@ -128,13 +128,11 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
     if not self._is_mode_sync_on():
     if not self._is_mode_sync_on():
       return
       return
 
 
-    now = dt_util.now()
+    now = dt_util.now().replace(tzinfo=None)
 
 
     # At next reservation's check-in: promote next → current, then activate guest mode.
     # 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 and nxt.check_in_time:
-      check_in = dt_util.as_local(
-        datetime.combine(nxt.check_in_date, nxt.check_in_time)
-      )
+      check_in = datetime.combine(nxt.check_in_date, nxt.check_in_time)
       delay = (check_in - now).total_seconds()
       delay = (check_in - now).total_seconds()
       if delay > 0:
       if delay > 0:
         self._mode_sync_handles.append(
         self._mode_sync_handles.append(
@@ -143,9 +141,7 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
 
 
     # At current reservation's check-out: clear current, then deactivate guest mode.
     # 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 and current.check_out_time:
-      check_out = dt_util.as_local(
-        datetime.combine(current.check_out_date, current.check_out_time)
-      )
+      check_out = datetime.combine(current.check_out_date, current.check_out_time)
       delay = (check_out - now).total_seconds()
       delay = (check_out - now).total_seconds()
       if delay > 0:
       if delay > 0:
         self._mode_sync_handles.append(
         self._mode_sync_handles.append(
@@ -157,12 +153,12 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
     Returns 'active' if now is within check_in..check_out, 'elapsed' if past check_out,
     Returns 'active' if now is within check_in..check_out, 'elapsed' if past check_out,
     'pending' if before check_in, or None if the reservation has no datetime info.
     'pending' if before check_in, or None if the reservation has no datetime info.
     """
     """
-    now = dt_util.now()
+    now = dt_util.now().replace(tzinfo=None)
     check_in = check_out = None
     check_in = check_out = None
     if res.check_in_date and res.check_in_time:
     if res.check_in_date and res.check_in_time:
-      check_in = dt_util.as_local(datetime.combine(res.check_in_date, res.check_in_time))
+      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 and res.check_out_time:
-      check_out = dt_util.as_local(datetime.combine(res.check_out_date, res.check_out_time))
+      check_out = datetime.combine(res.check_out_date, res.check_out_time)
     if check_out and now >= check_out:
     if check_out and now >= check_out:
       return "elapsed"
       return "elapsed"
     if check_in and now >= check_in:
     if check_in and now >= check_in:

+ 12 - 5
tests/test_coordinator.py

@@ -1,4 +1,4 @@
-from datetime import date, datetime, time, timezone
+from datetime import date, datetime, time
 from unittest.mock import AsyncMock, MagicMock, patch
 from unittest.mock import AsyncMock, MagicMock, patch
 
 
 import pytest
 import pytest
@@ -243,8 +243,6 @@ async def test_store_persists_across_reload(hass: HomeAssistant, mock_entry):
 # _reservation_window_state
 # _reservation_window_state
 # ---------------------------------------------------------------------------
 # ---------------------------------------------------------------------------
 
 
-_UTC = timezone.utc
-
 DATED = ReservationData(
 DATED = ReservationData(
   check_in_date=date(2026, 7, 1),
   check_in_date=date(2026, 7, 1),
   check_in_time=time(14, 0),
   check_in_time=time(14, 0),
@@ -254,8 +252,9 @@ DATED = ReservationData(
 
 
 
 
 def _state_at(coordinator, res, dt_str):
 def _state_at(coordinator, res, dt_str):
-  """Call _reservation_window_state with dt_util.now() frozen to dt_str (local naive → UTC)."""
-  frozen = datetime.fromisoformat(dt_str).replace(tzinfo=_UTC)
+  """Freeze dt_util.now() to a naive local datetime and call _reservation_window_state."""
+  frozen = MagicMock()
+  frozen.replace.return_value = datetime.fromisoformat(dt_str)  # naive, no TZ
   with patch("custom_components.room_cal_ha.coordinator.dt_util.now", return_value=frozen):
   with patch("custom_components.room_cal_ha.coordinator.dt_util.now", return_value=frozen):
     return coordinator._reservation_window_state(res)
     return coordinator._reservation_window_state(res)
 
 
@@ -272,6 +271,14 @@ async def test_window_state_elapsed(coordinator):
   assert _state_at(coordinator, DATED, "2026-07-10T12:00:00") == "elapsed"
   assert _state_at(coordinator, DATED, "2026-07-10T12:00:00") == "elapsed"
 
 
 
 
+async def test_window_state_at_check_in_is_active(coordinator):
+  assert _state_at(coordinator, DATED, "2026-07-01T14:00:00") == "active"
+
+
+async def test_window_state_at_check_out_is_elapsed(coordinator):
+  assert _state_at(coordinator, DATED, "2026-07-05T12:00:00") == "elapsed"
+
+
 async def test_window_state_no_dates_returns_none(coordinator):
 async def test_window_state_no_dates_returns_none(coordinator):
   assert coordinator._reservation_window_state(ReservationData()) is None
   assert coordinator._reservation_window_state(ReservationData()) is None