From 52895199beb3f1dc61b6b3f99deed817286aeb82 Mon Sep 17 00:00:00 2001 From: dev_agent Date: Wed, 30 Sep 2026 11:13:24 -0400 Subject: [PATCH] Document the manual, admin-set application timezone (task e9aa78f3) (#286) Co-authored-by: dev_agent Co-committed-by: dev_agent --- custom_components/nestquest/binary_sensor.py | 2 +- .../nestquest/core/completion.py | 22 ++++++------- custom_components/nestquest/core/const.py | 8 +++-- .../nestquest/core/dao_instances.py | 24 +++++++------- custom_components/nestquest/core/events.py | 17 +++++----- custom_components/nestquest/core/history.py | 4 +-- .../nestquest/core/materialize.py | 16 +++++----- .../nestquest/core/quest_definitions.py | 8 ++--- custom_components/nestquest/core/settings.py | 13 ++++---- custom_components/nestquest/core/snapshot.py | 4 +-- custom_components/nestquest/core/sweep.py | 13 ++++---- docs/ARCHITECTURE.md | 32 +++++++++++++++++++ tests/conftest.py | 4 +-- tests/test_completion.py | 2 +- tests/test_core_snapshot.py | 2 +- tests/test_materialize.py | 17 +++++----- 16 files changed, 114 insertions(+), 74 deletions(-) diff --git a/custom_components/nestquest/binary_sensor.py b/custom_components/nestquest/binary_sensor.py index 7c7fe10..880a80b 100644 --- a/custom_components/nestquest/binary_sensor.py +++ b/custom_components/nestquest/binary_sensor.py @@ -9,7 +9,7 @@ snapshot (the same ChildDaySnapshot the day sensors read): celebration automation must never fire on an empty day (the day-complete event carries the same rule, Feature 10 task 7). - ``binary_sensor.nestquest__present_today`` — ON when the - presence engine resolves the child present on the HA-local date + presence engine resolves the child present on the household-local date (schedules and overrides, D-004 anchor-date arithmetic). Naming and registry identity follow the day sensors' contract: names diff --git a/custom_components/nestquest/core/completion.py b/custom_components/nestquest/core/completion.py index bb6becb..413d5e2 100644 --- a/custom_components/nestquest/core/completion.py +++ b/custom_components/nestquest/core/completion.py @@ -10,7 +10,7 @@ un-completing (no-op if already open) is :func:`uncomplete_instance`. Instance current state is derived from the latest event via :func:`instance_state` (``open`` / ``done`` / ``missed``); there is no status column. ``missed`` is derived for past-due open instances -(``due_date`` before HA-local ``today``) and is never stored as an +(``due_date`` before household-local ``today``) and is never stored as an event_type. Actor policy matches the schema CHECKs (D-008): ``actor_source`` is @@ -71,10 +71,10 @@ def _completion_moment( now: datetime.datetime | None, today: datetime.date | None, ) -> tuple[datetime.date, datetime.time | None]: - """Return the HA-local completion date and optional wall-clock time. + """Return the household-local completion date and optional wall-clock time. - ``today`` is the HA-local calendar date when threaded (Feature 07 - style). ``now`` is a timezone-aware HA-local datetime; its wall + ``today`` is the household-local calendar date when threaded (Feature 07 + style). ``now`` is a timezone-aware household-local datetime; its wall clock is used as-is and is never converted to UTC. When ``today`` is omitted the date comes from ``now``. When both are omitted the host calendar date is the Feature 07 fallback — never the UTC @@ -98,7 +98,7 @@ def _completion_moment( def _resolve_today(today: datetime.date | None) -> datetime.date: - """Return the threaded HA-local date, or the host calendar date.""" + """Return the threaded household-local date, or the host calendar date.""" if today is None: return datetime.date.today() return today @@ -138,7 +138,7 @@ def derive_state( Thin wrapper over :func:`_derived_state`: ``open``, ``done``, or ``missed`` from the latest event plus the due date (``missed`` is derived, never an event_type). ``today`` is the caller-resolved - HA-local date. + household-local date. """ return _derived_state(instance, latest, today) @@ -345,7 +345,7 @@ async def instance_state( """Return ``open``, ``done``, or ``missed`` from the latest event. No events, or a latest event of uncompleted, is ``open`` unless - ``due_date`` is before ``today`` (HA-local, threaded), in which + ``due_date`` is before ``today`` (household-local, threaded), in which case it is ``missed``. A latest event of completed is ``done`` even when past due. ``missed`` is never stored as an event_type. Unknown ``instance_id`` raises ValueError naming the field. @@ -414,9 +414,9 @@ async def complete_instance( latest-event, and the append run under one connection lock so a concurrent complete cannot double-append. - ``was_on_time`` is computed from the HA-local completion moment + ``was_on_time`` is computed from the household-local completion moment (threaded ``now`` / ``today``) against the instance ``due_date`` - and optional ``due_time``. ``now`` is the timezone-aware HA-local + and optional ``due_time``. ``now`` is the timezone-aware household-local datetime; its wall clock is never converted to UTC. Same-day ``due_time`` comparison requires ``now``. """ @@ -471,7 +471,7 @@ async def uncomplete_instance( """Append an uncompleted event unless the instance is already open. Returns the derived state ``open``, or ``missed`` when the instance - is past due (``due_date`` before HA-local ``today``); ``appended`` + is past due (``due_date`` before household-local ``today``); ``appended`` reports whether THIS call appended (False on an already-open no-op). Unknown ``instance_id`` raises ValueError naming the field. Existence, latest-event, and the append run under one @@ -529,7 +529,7 @@ async def list_missed_for_child( """Return the child's missed instances due in the closed date range. Missed means the latest event state is open and ``due_date`` is - before ``today`` (HA-local, threaded). Uses + before ``today`` (household-local, threaded). Uses :meth:`QuestInstancesDao.list_by_date_range` plus derived state. Never appends a missed event. Instances and latest events are read inside the connection lock so the list is one snapshot. diff --git a/custom_components/nestquest/core/const.py b/custom_components/nestquest/core/const.py index 0ac9c17..6ebfe59 100644 --- a/custom_components/nestquest/core/const.py +++ b/custom_components/nestquest/core/const.py @@ -115,8 +115,12 @@ DEFAULT_AUTOMATION_ENABLED = True # The household's IANA time zone (e.g. ``America/New_York``) the API # service reads its clock in. The empty string is the API host's own # local time. Only an admin sets it (manually, from the admin app); -# nothing sets it automatically. The integration itself always uses -# Home Assistant's ``time_zone`` and ignores this field. +# nothing sets it automatically. The integration relays the API's +# effective application timezone (the coordinator carries it and the +# household sensor publishes it for the panel cards) and never derives +# the application clock from Home Assistant's configuration; the Home +# Assistant / browser zone applies only when the API publishes no zone +# (e.g. an older API). CONF_TIMEZONE = "timezone" DEFAULT_TIMEZONE = "" diff --git a/custom_components/nestquest/core/dao_instances.py b/custom_components/nestquest/core/dao_instances.py index 3d30b61..1ae7b16 100644 --- a/custom_components/nestquest/core/dao_instances.py +++ b/custom_components/nestquest/core/dao_instances.py @@ -68,7 +68,7 @@ def _resolve_today(today: datetime.date | None) -> datetime.date: around midnight, so when the caller has already resolved the local date (``today``) that anchor is authoritative; ``None`` falls back to the host clock (:func:`_today`), which is exactly the previous - behavior for callers with no HA-local date. + behavior for callers with no household-local date. """ return _today() if today is None else today @@ -225,7 +225,7 @@ class QuestInstancesDao: - ``due_date`` must be TODAY or later: instances are never generated in the past (Feature 07). The caller's materialization horizon is future-dated by definition. - ``today`` optionally pins the resolved HA-local date the + ``today`` optionally pins the resolved household-local date the guard compares against (falling back to the host clock when omitted) so a household time zone behind the host around midnight is not misread as "past". @@ -248,7 +248,7 @@ class QuestInstancesDao: # check runs after the lock is acquired, because a caller can # wait on the lock across midnight — a date that was "today" # at call time may be "yesterday" by the time the INSERT runs. - # A caller-supplied ``today`` (HA-local) is used for BOTH + # A caller-supplied ``today`` (household-local) is used for BOTH # checks and never re-read, since it is already the anchor the # batch runs against. today_date = _resolve_today(today) @@ -257,7 +257,7 @@ class QuestInstancesDao: async with self._database.transaction(): # Re-read "today" under the lock: this is the date the # insert actually executes on, so the no-past rule - # holds across midnight rollovers. A pinned HA-local + # holds across midnight rollovers. A pinned household-local # anchor is exempt from the re-read (see above). if today is None: today_date = _today() @@ -364,7 +364,7 @@ class QuestInstancesDao: rather than raising, so the rest of the batch is unaffected. The no-past rule (instances are never generated in the past) is preserved from :meth:`upsert` and still raises ValueError; ``today`` - optionally pins the HA-local anchor it compares against. An + optionally pins the household-local anchor it compares against. An instance that ALREADY has a completion event is also skipped (returns ``None``) rather than raised: it is immutable, so the walk leaves it untouched instead of failing the batch — the @@ -504,7 +504,7 @@ class QuestInstancesDao: append-only contract: an instance that was ever completed is never reported missed, even when later reversed — history keeps both rows and the sweep never mutates any). The watermark is - the HA-local date the sweep last ran, and the window is + the household-local date the sweep last ran, and the window is ``[watermark, today)``: the nightly run entering day D+1 sweeps exactly day D's instances, so every day's quests are announced once — at the midnight after their due date — and a same-night @@ -544,9 +544,10 @@ class QuestInstancesDao: rolling horizon from now), so a caller cannot use this to rewrite past open instances either — the day-rollover missed sweep (Feature 11) handles past-due instances instead. ``today`` - optionally pins the HA-local anchor the clamp compares against - (falling back to the host clock when omitted). Any instance with - a completion event is never touched, regardless of its date. + optionally pins the household-local anchor the clamp compares + against (falling back to the host clock when omitted). Any + instance with a completion event is never touched, regardless of + its date. Returns the number deleted. """ _validate_date(cutoff_date, "cutoff_date") @@ -581,8 +582,9 @@ class QuestInstancesDao: one transaction — an instance that gains a completion event mid-flight cannot be deleted. The cutoff cannot be backdated below today, for the same reason as the definition-scoped method (see its - docstring). ``today`` optionally pins the HA-local anchor the clamp - compares against (falling back to the host clock when omitted). + docstring). ``today`` optionally pins the household-local anchor + the clamp compares against (falling back to the host clock when + omitted). Any instance with a completion event is never touched, regardless of its date. diff --git a/custom_components/nestquest/core/events.py b/custom_components/nestquest/core/events.py index d066d5b..0a46523 100644 --- a/custom_components/nestquest/core/events.py +++ b/custom_components/nestquest/core/events.py @@ -39,10 +39,11 @@ ISO-8601 timestamp. Completion payloads additionally carry ``was_on_time``. No other personal data: names and titles are the household's own records, and nothing beyond them leaks. -Timezone policy: the day-complete check needs the HA-local "today", -which the caller passes in as a plain :class:`datetime.date`. Nothing -here reads ``hass.config.time_zone`` — the integration owns HA -timezone, the core owns the calendar arithmetic. +Timezone policy: the day-complete check needs the household-local +"today", which the caller passes in as a plain :class:`datetime.date`. +Nothing here reads a timezone — the API owns the clock (the +admin-stored application timezone, or the API host's local zone when +none is stored), the core owns the calendar arithmetic. """ from __future__ import annotations @@ -147,10 +148,10 @@ async def build_child_day_complete_event( completion of TODAY'S instance evaluates it: completing a back-dated or future instance must not announce a cleared day. - ``today`` is the HA-local calendar date the day-complete rule is - evaluated against; the integration reads it from - ``hass.config.time_zone`` and passes it in, so this module never - touches HA config. + ``today`` is the household-local calendar date the day-complete + rule is evaluated against; the API resolves it in the application + timezone and passes it in, so this module never reads a clock or a + timezone of its own. """ # Only a completion of TODAY'S instance evaluates the day-complete # rule: completing a back-dated or future instance must not diff --git a/custom_components/nestquest/core/history.py b/custom_components/nestquest/core/history.py index 5208af0..cc149d0 100644 --- a/custom_components/nestquest/core/history.py +++ b/custom_components/nestquest/core/history.py @@ -153,7 +153,7 @@ def _validate_range(start_date: str, end_date: str) -> tuple[str, str]: def _resolve_today(today: datetime.date | None) -> datetime.date: - """Return the threaded HA-local date, or the host calendar date.""" + """Return the threaded household-local date, or the host calendar date.""" if today is None: return datetime.date.today() return today @@ -283,7 +283,7 @@ async def query_history( Rows are ordered deterministically by ``due_date``, then instance, then event id (the per-instance append order, so a completion and - its reversal keep their order). ``today`` pins the HA-local date + its reversal keep their order). ``today`` pins the household-local date the missed derivation compares due dates against (the host calendar date when omitted). The whole read runs under the task-reentrant connection lock — :func:`list_missed_for_child` re-enters it — so diff --git a/custom_components/nestquest/core/materialize.py b/custom_components/nestquest/core/materialize.py index 2f7429b..fe37f2c 100644 --- a/custom_components/nestquest/core/materialize.py +++ b/custom_components/nestquest/core/materialize.py @@ -255,12 +255,12 @@ async def materialize( after a presence change) cannot touch any unrelated child's rows. When omitted (or None) the walk covers every assignee as before. - ``today`` optionally pins the resolved HA-local date the walk's + ``today`` optionally pins the resolved household-local date the walk's no-past guard compares against; it is threaded into every :meth:`~.dao_instances.QuestInstancesDao.upsert_if_valid` write. When omitted the host clock (``datetime.date.today``) is used, which is the - historical default. A caller that computed the horizon in Home - Assistant's configured time zone MUST pass that same date here so a + historical default. A caller that computed the horizon in the + household's application time zone MUST pass that same date here so a time zone behind the host around midnight is not rejected as "past". ``start_date`` is clamped up to the resolved "today" (the same @@ -298,8 +298,8 @@ async def materialize( start = datetime.datetime.strptime(start_date, "%Y-%m-%d").date() end = datetime.datetime.strptime(end_date, "%Y-%m-%d").date() - # Clamp a past start up to today (the HA-local anchor when the caller - # pinned one, else the host clock) so the walk can never emit a + # Clamp a past start up to today (the household-local anchor when the + # caller pinned one, else the host clock) so the walk can never emit a # past-dated instance; the DAO's no-past guard remains the backstop. # A range entirely in the past therefore yields zero instances. today_date = _resolve_today(today) @@ -345,7 +345,7 @@ async def regenerate_for_definition( :func:`~.dao_instances._today` — so the delete cutoff and the re-materialization range share one anchor, and the walk's ``upsert_if_valid`` no-past guard never rejects the regenerated - range. ``today`` optionally pins a caller-resolved HA-local date + range. ``today`` optionally pins a caller-resolved household-local date (threaded into both the delete cutoff and the re-materialization) so a household time zone behind the host around midnight stays consistent. ``horizon_days`` sizes the re-materialization window; @@ -413,9 +413,9 @@ async def regenerate_for_child( :meth:`~.dao_instances.QuestInstancesDao.delete_future_uncompleted_for_child`), so the delete cutoff and the re-materialization window share ONE execution-day anchor even when the call is queued across midnight. - ``today`` optionally pins a caller-resolved HA-local date, threaded + ``today`` optionally pins a caller-resolved household-local date, threaded into the delete cutoff and the re-materialization for the same - host-vs-HA time-zone consistency. ``horizon_days`` sizes the + host-vs-household time-zone consistency. ``horizon_days`` sizes the re-materialization window; when omitted it falls back to :data:`~.const.DEFAULT_HORIZON_DAYS`. A bool, non-int, or sub-1 value raises ValueError BEFORE any delete. Returns the number of instances diff --git a/custom_components/nestquest/core/quest_definitions.py b/custom_components/nestquest/core/quest_definitions.py index b4764a5..f12774d 100644 --- a/custom_components/nestquest/core/quest_definitions.py +++ b/custom_components/nestquest/core/quest_definitions.py @@ -345,7 +345,7 @@ async def edit_quest_definition( future instances only; no ``quest_instances`` or ``completion_events`` row is touched. - ``today`` optionally pins the caller-resolved HA-local date the + ``today`` optionally pins the caller-resolved household-local date the regeneration's no-past guard and horizon are anchored to (threaded into :func:`~.materialize.regenerate_for_definition`); it is a plain ``datetime.date`` with no HA import here — the Feature 09 service @@ -449,7 +449,7 @@ async def assign_child( ``quest_definition_assignees`` link is written; existing instances and completion history are never touched. - ``today`` optionally pins the caller-resolved HA-local date threaded + ``today`` optionally pins the caller-resolved household-local date threaded into the assignment's regeneration (see :func:`edit_quest_definition`). ``horizon_days`` optionally sizes the regeneration's re-materialization window the same way. @@ -491,7 +491,7 @@ async def unassign_child( argument. Only the ``quest_definition_assignees`` link is written; existing instances and completion history are never touched. - ``today`` optionally pins the caller-resolved HA-local date threaded + ``today`` optionally pins the caller-resolved household-local date threaded into the unassignment's regeneration (see :func:`edit_quest_definition`). ``horizon_days`` optionally sizes the regeneration's re-materialization window the same way. @@ -548,7 +548,7 @@ async def set_quest_definition_active( connection-scoped lock, so the returned bundle is a consistent snapshot that a concurrent mutation cannot race. - ``today`` optionally pins the caller-resolved HA-local date threaded + ``today`` optionally pins the caller-resolved household-local date threaded into the activation change's regeneration (see :func:`edit_quest_definition`). ``horizon_days`` optionally sizes the regeneration's re-materialization window the same way. diff --git a/custom_components/nestquest/core/settings.py b/custom_components/nestquest/core/settings.py index edb8a5d..8b3e4b3 100644 --- a/custom_components/nestquest/core/settings.py +++ b/custom_components/nestquest/core/settings.py @@ -316,12 +316,11 @@ class NestQuestSettings: The API service does not run inside Home Assistant, so its one clock read (an aware host-local ``now``) is converted here: with - :attr:`timezone` set — the admin's choice, or else the zone the - integration reports from Home Assistant — the SAME instant is - re-expressed in that zone, so ``.date()`` and ``.time()`` are the - household's wall clock even when the host runs UTC. With - :attr:`timezone` empty the instant is returned unchanged (the - host's local time). + :attr:`timezone` set — only ever by an admin, from the admin + app — the SAME instant is re-expressed in that zone, so + ``.date()`` and ``.time()`` are the household's wall clock even + when the host runs UTC. With :attr:`timezone` empty the instant + is returned unchanged (the host's local time). """ if not self.timezone: return instant @@ -341,7 +340,7 @@ class NestQuestSettings: """Return the rolling horizon window ``[today, today + horizon_days]``. ``today`` is REQUIRED (there is no host-clock default): the - integration passes the HA-local ``hass.config.time_zone`` date so + API passes the household-local date (:meth:`household_now`) so the horizon tracks the household's own day, and a silent host-clock fallback would be a wrong-day footgun (a test or a caller that forgets to pass ``today`` would otherwise materialize diff --git a/custom_components/nestquest/core/snapshot.py b/custom_components/nestquest/core/snapshot.py index 7e91c8b..23ac5d0 100644 --- a/custom_components/nestquest/core/snapshot.py +++ b/custom_components/nestquest/core/snapshot.py @@ -18,7 +18,7 @@ the panel payload shaper (:func:`instance_payload`), and the async service's panel snapshot route share ONE implementation of both the snapshot assembly and the entity-facing attribute payload shape. Nothing here imports :mod:`homeassistant`; the builder takes the -database, the settings object, and the HA-local ``now`` (a single +database, the settings object, and the household-local ``now`` (a single clock read the caller already resolved, from which it derives ``today``) and returns the snapshot, mirroring exactly what the coordinator used to do inline. @@ -187,7 +187,7 @@ async def build_snapshot( :func:`instance_payload` layer — this builder produces the shared shape both consumers read. - ``now`` is the HA-local timezone-aware datetime the caller already + ``now`` is the household-local timezone-aware datetime the caller already resolved (a SINGLE clock read); the overdue check compares ``now.time()`` against the instance's ``due_time`` for today's open instances. The builder performs NO internal clock read — it never diff --git a/custom_components/nestquest/core/sweep.py b/custom_components/nestquest/core/sweep.py index b853cb8..7374476 100644 --- a/custom_components/nestquest/core/sweep.py +++ b/custom_components/nestquest/core/sweep.py @@ -39,9 +39,10 @@ lets exactly one of them win the claim — the loser returns [] and announces nothing. The caller supplies ``today`` as a plain :class:`datetime.date`: the -household-local calendar date, read from ``hass.config.time_zone`` by -the integration or from the configured household timezone by the API. -This module never reads a timezone database or config of its own. +household-local calendar date, which the API reads in the +admin-stored application timezone (the API host's local zone when none +is stored). This module never reads a timezone database or config of +its own. """ from __future__ import annotations @@ -97,9 +98,9 @@ async def run_missed_sweep( """Build one missed event per unswept past-due open instance. ``today`` is the household-local calendar date the sweep runs for - (the caller owns the clock: the integration reads it from - ``hass.config.time_zone``, the API from the configured household - timezone, falling back to its host's local time when unset). + (the caller owns the clock: the API reads it in the admin-stored + application timezone, falling back to its host's local time when + unset). Returns the ``(event_type, payload)`` tuples to announce, EMPTY when the watermark already covers ``today`` (a same-night rerun) — the caller decides how to publish them and announces nothing on diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 0f78653..c6c9b57 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -99,6 +99,38 @@ A lone `home` pattern answers exactly as the old schedule did, so no child's presence changes. The drop is one-way and 0.7.1 refuses a version-9 database; see the rollback step in `deploy/RUNBOOK.md` §11. +### Application timezone (manual, admin-set) + +The application timezone is set **manually by an admin from the admin +app** — the Settings screen's timezone picker, which reads and writes the +`timezone` field of `GET`/`PATCH /api/v1/admin/settings`. Nothing sets it +automatically: there is no Home Assistant zone adoption, no browser-zone +auto-detect, and no auto-save when a screen loads. Only an admin's `PATCH` +ever changes the stored value. + +The stored value is the **single source of local-time truth for the whole +application.** The API reads its one clock and re-expresses it in that zone +(`NestQuestSettings.household_now`), so every local date and time comes +from it: + +- the panel snapshot's today and overdue state, and both panel cards' + visible dates and clocks (the snapshot publishes the zone as `timezone`, + which the integration exposes on the household rollup sensor); +- completion timestamps (the local completion date and the on-time check); +- the missed-sweep scheduler's household rollover; +- every admin read, and the admin app's local-time formatting. + +**Empty means the API host's local zone.** While `timezone` is empty the +API reads its clock in the host's local time, and publishes that zone as +the effective zone (`api/host_zone.py`: the `effective_timezone` field of +the admin settings response and the snapshot's `timezone`) until an admin +sets one. No code fills the empty value in. + +**A save re-plans the sweep.** An admin save that changes `timezone` or +`day_rollover_time` notifies the running missed-sweep scheduler +(`MissedSweepScheduler.settings_changed`, under its settings lock), which +re-plans its next sweep for the new household rollover. + ## 4. Authentication: two planes The kid panel and the admin PWA have opposite trust levels, so they use diff --git a/tests/conftest.py b/tests/conftest.py index 2204796..4ff6a74 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1017,8 +1017,8 @@ def make_hass() -> tuple: hass.bus = EventBus() hass.http = HttpRegistry() hass.extra_js_urls: list[str] = [] - # A valid IANA time zone (the day-rollover listener reads it to - # compute "today" in HA local time). + # A valid IANA time zone for the fake Home Assistant config (the + # NestQuest application timezone is the admin-set setting, not this). hass.config.time_zone = "UTC" # The minimal entity registry: entities self-register through diff --git a/tests/test_completion.py b/tests/test_completion.py index d8b82a0..e094dc4 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -834,7 +834,7 @@ def test_complete_instance_no_due_time_after_due_date_is_late( def test_complete_instance_ha_local_wall_clock_not_utc(tmp_path) -> None: - """HA-local 11:00 with due_time 12:00 is on time even when UTC is 19:00. + """Household-local 11:00, due 12:00, is on time even when UTC is 19:00. The helper's zone is UTC-8, so 11:00 local is 19:00 UTC. Converting to UTC before comparing would mark this late. diff --git a/tests/test_core_snapshot.py b/tests/test_core_snapshot.py index 9e7c23d..6e9d87b 100644 --- a/tests/test_core_snapshot.py +++ b/tests/test_core_snapshot.py @@ -136,7 +136,7 @@ async def test_build_snapshot_explicit_values(hass, make_entry, tmp_path) -> Non # Single clock read for the seed/snapshot anchor (no midnight skew); # the coordinator no longer owns a clock read (the API serves the - # snapshot), so the test resolves the HA-local zone directly. + # snapshot), so the test resolves its own local zone directly. time_zone = ZoneInfo(hass.config.time_zone) today = datetime.datetime.now(time_zone).date() # Pin the build instant at 12:00 local: past Ada's 10:00 due time diff --git a/tests/test_materialize.py b/tests/test_materialize.py index 436198a..98725be 100644 --- a/tests/test_materialize.py +++ b/tests/test_materialize.py @@ -940,14 +940,15 @@ def test_materialize_rejects_malformed_or_inverted_bounds(tmp_path) -> None: def test_materialize_accepts_ha_local_today_behind_host(tmp_path) -> None: - """The clamp and no-past guard compare against a caller-supplied HA-local today. + """The clamp and no-past guard compare against a caller-supplied today. A household time zone behind the host clock around midnight resolves a - HA-local "today" one calendar day earlier than the host's + household-local "today" one calendar day earlier than the host's ``date.today()``. The walk clamps its start to that anchor: without - a pinned HA-local date the effective start is the host's today (one - day after ha_today), so the now-past day is skipped rather than - rejected, and only the host-today-onward days materialize. + a pinned household-local date the effective start is the host's + today (one day after ha_today), so the now-past day is skipped + rather than rejected, and only the host-today-onward days + materialize. """ async def _body(database): children = ChildrenDao(database) @@ -963,9 +964,9 @@ def test_materialize_accepts_ha_local_today_behind_host(tmp_path) -> None: ["morning"], ) - # Without a pinned HA-local today, the walk clamps its start to the - # host's today (one day after ha_today); the past day is dropped and - # the 14 host-today-onward days materialize. + # Without a pinned household-local today, the walk clamps its start + # to the host's today (one day after ha_today); the past day is + # dropped and the 14 host-today-onward days materialize. without_pin = await materialize(database, start_iso, end_iso) assert without_pin == 14