Fix Forecast.Solar timezone misalignment in non-UTC timezones#2856
Closed
Copilot wants to merge 3 commits into
Closed
Fix Forecast.Solar timezone misalignment in non-UTC timezones#2856Copilot wants to merge 3 commits into
Copilot wants to merge 3 commits into
Conversation
Co-authored-by: springfall2008 <48591903+springfall2008@users.noreply.github.com>
Co-authored-by: springfall2008 <48591903+springfall2008@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Fix forecast solar method timezone alignment
Fix Forecast.Solar timezone misalignment in non-UTC timezones
Nov 2, 2025
Owner
|
On main now |
tieskuh
added a commit
to tieskuh/batpred
that referenced
this pull request
May 30, 2026
…mezones ## Summary `solcast.py` line 508 incorrectly applies `.replace(tzinfo=pytz.utc)` to `self.midnight_utc`, which is already a timezone-aware datetime in `local_tz` (not UTC, despite the misleading name). This produces a final `period_start_stamp` that is shifted forward by the local UTC offset. For users east of UTC (CET +1, CEST +2, Adelaide +10:30, Auckland +13), Forecast.solar data appears later in the plan than it should — and west of UTC, earlier. This is the root cause of the symptoms reported in springfall2008#2821 (Australia/Adelaide, +10:30) and springfall2008#2911 (Pacific/Auckland, +13) and is still reproducible in v8.39.7 in Europe/Amsterdam (+2 CEST) where the smaller offset makes the bug look like "the PV tail extends past sunset" rather than complete day inversion. ## Root cause In `predbat.py:738-747`: ```python self.now_utc_real = datetime.now(self.local_tz) # tz-aware in local_tz now_utc = self.now_utc_real + timedelta(minutes=skew) # still tz-aware in local_tz ... self.now_utc = now_utc # name says UTC, value is local_tz self.midnight_utc = now_utc.replace(hour=0, ...) # local midnight in local_tz ``` So `self.midnight_utc` is the start of the local day, *as a timezone-aware datetime in local_tz* — not UTC midnight. The name is misleading but the value is correct for the way it's used elsewhere (e.g. `(period_end_stamp - self.midnight_utc)` works correctly because Python normalizes tz-aware subtraction to absolute time). The bug at `solcast.py:508`: ```python period_start_stamp = self.midnight_utc.replace(tzinfo=pytz.utc) + timedelta(minutes=minute) ``` `.replace(tzinfo=...)` on a tz-aware datetime **keeps the wall-clock time and swaps the label**. So `00:00 CEST` becomes `00:00 UTC` — a different absolute moment, 2 hours later. Every subsequent `period_start_stamp` is shifted by that local UTC offset. The other use of `.replace(tzinfo=pytz.utc)` on line 376 (Open-Meteo path) is correct because the stamp there is genuinely naive (from `strptime(ts, "%Y-%m-%dT%H:%M")` without `%z`). The bug only affects the Forecast.solar code path. ## Reproduction (Europe/Amsterdam, 30 May 2026) Forecast.solar API returns sunset at `"2026-05-30T19:44:12+00:00"` = 21:44 CEST. With current code: ``` midnight_utc: 2026-05-30 00:00:00+02:00 (local midnight, in CEST) sunset stamp: 2026-05-30 19:44:12+00:00 (UTC from cache) minutes_end: 1304.2 min (21h44m since 00:00 CEST) period_start_stamp (BUG): 2026-05-30 21:44:00+00:00 (00:00 + 1304 min, but tz forced to UTC) -> displayed as 23:44 CEST in plan ← WRONG, sunset is 21:44 ``` User-visible: Predbat plan shows non-zero PV until 23:00-23:30 local, ~2 hours after astronomical sunset. Same data via the Home Assistant Forecast.solar integration sensor (`sensor.energy_production_today`) renders correctly. ## Fix Drop the `.replace(tzinfo=pytz.utc)`. `self.midnight_utc` is already tz-aware in `local_tz`; adding a `timedelta` preserves the timezone: ```diff --- a/apps/predbat/solcast.py +++ b/apps/predbat/solcast.py @@ -505,7 +505,7 @@ class SolarAPI(ComponentBase): for offset in range(0, self.plan_interval_minutes, 1): pv50 += dp4(forecast_watt_data.get(minute + offset, 0) / 1000.0) pv50 /= 60 - period_start_stamp = self.midnight_utc.replace(tzinfo=pytz.utc) + timedelta(minutes=minute) + period_start_stamp = self.midnight_utc + timedelta(minutes=minute) data_item = {"period_start": period_start_stamp.strftime(TIME_FORMAT), "pv_estimate": pv50} if period_start_stamp in period_data: period_data[period_start_stamp]["pv_estimate"] += pv50 ``` After fix: ``` period_start_stamp (FIX): 2026-05-30 21:44:00+02:00 (00:00 + 1304 min in CEST) -> displayed as 21:44 CEST in plan ← CORRECT ``` ## Regression check For users in UTC the fix is a no-op. Both old and new code produce `2026-05-30 19:44:00+00:00` for UTC midnight + 1184 min. Tested with the same reproduction script using `tzinfo=pytz.utc` for `midnight_utc`. Verified delta = 0 seconds. ## Related issues - springfall2008#2821 — Australia/Adelaide, +10:30 offset, plan showed PV during night hours - springfall2008#2911 — Pacific/Auckland, +13 offset, plan entirely shifted by 13h Both were partially addressed by adding `?time=utc` to the URL and the `strptime` change in springfall2008#2856 / springfall2008#2911, but the underlying `.replace(tzinfo=pytz.utc)` on the local-tz `midnight_utc` was kept and is the remaining root cause.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Forecast.Solar data displayed at incorrect local times in non-UTC timezones, showing solar production during nighttime hours (e.g., 17:00-01:00 in Adelaide UTC+10:30).
Root Cause
SolarAPI.fetch_pv_forecast()calculatedmidnight_utcas UTC midnight instead of using the user's local midnight frombase.midnight_utc. This caused forecast timestamps to be offset by the timezone difference when computing minute offsets.Changes
apps/predbat/solcast.py:863-866
base.now_utcandbase.midnight_utcinstead of creating new datetime objects.total_seconds()→.secondsfor timezone-aware datetime differencesForecast.Solar returns timestamps in location's local timezone. Line 289 converts to UTC correctly, but minute offset calculation (line 298) required user's local midnight as reference, not UTC midnight.
Original prompt
This section details on the original issue you should resolve
<issue_title>Forecast.Solar Timezone issue</issue_title>
<issue_description>Describe the bug
Using the forecast solar method in apps.yaml PV production time alignment does not match the local timezone or solar day for lat/long
Expected behaviour
Given this input, I expected the solar forecast to align to the local Adelaide time ACDT, which the planning portion does, but is showing PV production for nighttime hours
Predbat version
Screenshots
If appl...
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.