Skip to content

feat(site): the home's time zone is a stored fact of the site - #1946

Open
frahlg wants to merge 1 commit into
fix/name-copied-localtimefrom
household-time-zone
Open

frahlg wants to merge 1 commit into
fix/name-copied-localtimefrom
household-time-zone

Conversation

@frahlg

@frahlg frahlg commented Oct 31, 2026 •

Copy link
Copy Markdown
Member

Problem

Erik's car goal said 100 % by 17:00 on Sunday. His box aimed at 18:00. It runs in Docker, and Docker runs on UTC, so Core read 17:00 on a UTC clock. Every wall-clock rule had the same gap: car and charger goals, the import hour, daily history and savings. Core read time.Local, which comes from the host.

Fredrik decided on 31 October 2026 that the home's time zone is a stored fact of the site. This replaces passing TZ into Docker.

Stacked on #1945, which makes "UTC" mean UTC. Merge #1945 first; this PR then targets master.

Change

  • One stored value. site.time_zone (IANA name) lives in the settings document Core already keeps in state.db. localzone.Use sets time.Local from it at start, before any goroutine reads the clock, and sets TZ for workers. localzone.Name reports it. Empty keeps the host zone, so native installs run as today.
  • Clients offer it. New op site.time_zone.propose (scope ftw.setup.write). The box takes the offer only while it keeps no zone: nothing stored and a host on UTC. It saves the zone and restarts once to run on it. A box that keeps a zone answers E_PRECONDITION with args.tz, so a phone abroad cannot move a house's clock. The on-box setup writes the browser's zone, and Settings → Site shows the field.
  • A changed zone restarts Core. RestartRequiredFor lists site.time_zone.
  • Agent setup may now write /site/time_zone. The guide no longer says there is no such field.

When the zone changes

State What happens
Instants (history, plans, prices, forecasts) Epoch ms, unchanged.
Daily energy cache (energy_daily_partial, history_daily_quality) Keyed by local date. KeepDailyZone records the zone it was built on and clears it on a new zone. Each day is computed again from the UTC buckets on its next read. The first boot records the zone without clearing, so an unchanged box does not pay the cold recompute.
Savings Cache in memory only. The restart clears it.
Recurring goals Stored as minute of day plus weekday mask. They keep their clock time on the new clock: 17:00 stays 17:00.
One-off "finish at the car's limit" goal Keeps the instant it was saved for (first_deadline_ms). Not recomputed.
Plan in flight, import hour Rebuilt at start. The import hour is seeded from UTC history.
Load model, Rust forecast state Already reset on a zone change (stored zone, site revision).
History older than 90 days Hourly buckets. Exact for whole-hour zones; a :30 or :45 zone cannot split those days exactly.

Decisions for Fredrik

  1. The box restarts itself when it takes a client's zone. Once per box, a few seconds, the same in-process re-exec as a setup commit. Without it, the zone would wait for the next update or reboot.
  2. A host zone that is not UTC wins over the phone. A Pi left on Europe/London stays on it until the owner changes Settings → Site. This keeps "native installs work as today".
  3. The changeset is minor (new stored setting and op). Please approve or ask for patch.

Evidence

  • make verify green on this Mac.
  • New tests that fail on a UTC box before this change: TestABoxOnUTCPlansACarGoalOnTheStoredHouseholdClock (cmd/ftw), TestAStoredZoneIsTheClockOfABoxOnUTC (localzone), TestABoxOnUTCTakesThePhonesZoneAndRestarts and kept-zone cases (api), the op's applied, kept, bad-arg and unavailable answers (appproto), TestANewHouseholdZoneClearsTheCachedDays (state).
  • Docker on this Mac, image built from this branch, container clock UTC. Goal 100 % by 17:00 on Sunday (time_of_day_min 1020, days Sunday):
    • before: household clock zone=Etc/UTC stored=false, target_time 2026-11-01T17:00:00Z (18:00 in Stockholm)
    • stored Europe/Stockholm through Settings, restart: household clock zone=Europe/Stockholm stored=true, household zone changed; daily energy is computed again, target_time 2026-11-01T16:00:00Z (17:00 in Stockholm). date in the container still says UTC. /api/energy/daily and /api/setup/schema report Europe/Stockholm.
  • Browser on the same box: Settings → Site shows the Time zone field with the stored zone, and the setup review reads "clock Europe/Stockholm".

Paired PRs

The registry line is byte-identical in all three.

Open PRs that touch config/config.go or appproto/handler.go (#1792, #1789, #1691, #1051): this PR adds lines and rewrites nothing they change.

🤖 Generated with Claude Code

A Docker box runs on UTC whatever house it is in, so a car goal of 17:00
was met at 18:00 in a Stockholm winter. Core now reads the household clock
from site.time_zone, set once at start, instead of the host's zone.

- localzone.Use sets time.Local (and TZ for workers) from the stored zone
  before any goroutine reads the clock; Name reports it.
- site.time_zone is validated, shown under Settings → Site, written by the
  on-box setup from the browser, and listed as a restart reason.
- site.time_zone.propose lets the app and the web client offer their zone.
  The box takes it only while it keeps none (nothing stored, host on UTC),
  then restarts once. A kept zone answers E_PRECONDITION with args.tz.
- Cached local-day energy is tied to the zone it was computed on; a new
  zone clears it and the days are computed again from UTC buckets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-31T17:17:36.103260Z e3ba1ae PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e3ba1ae786

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +60 to +62
if zone == "" {
household = ""
return nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restore the host TZ when clearing the stored time zone

On the Home Assistant add-on restart path, clearing site.time_zone in Settings does not restore the host's clock. The preceding boot overwrote TZ with the stored zone, and execCurrentProcess in go/cmd/ftw/reexec.go passes os.Environ() into the replacement process. This empty-zone branch therefore inherits the previous household zone as its supposed host zone, leaving schedules and daily boundaries on the cleared setting until the container is fully restarted. Preserve the original host environment across re-execs, or restrict the household TZ override to worker environments.

AGENTS.md reference: AGENTS.md:L20-L21

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant