Pre-existing, seasonal, low severity. Filed out of review of #227, which found it and correctly scoped it out — it predates that branch and affects every pe.
Mechanism
nwrfc_textplot.py reads one timezone marker for the whole page:
tz = "PDT" if "(pdt)" in header_lower else "PST" if "(pst)" in header_lower else None
But a textPlot page carries ~11 days of rows. Observed live:
HCRO3 pe=TW span: 2026-07-04 17:00 -> 2026-07-15 11:35 (3090 stamps); (PDT)=1 (PST)=0
TIMEZONE_OFFSETS (utils/conversions.py) is a fixed-offset map, so every row on the page gets the header's single offset.
Impact
For the ~11 days after a transition, the header reads (PST) and rows logged before the transition — which were actually PDT — get −8 instead of −7. 2026-10-31 08:00 PDT (15:00 UTC) stores as 16:00 UTC.
Next occurrence: 2026-11-01. Affects the ~11-day window after each spring/fall transition, on any wired textPlot source, one hour, one direction.
Why it's minor
- Only the overlap window, twice a year.
- One hour on a river-level series is rarely actionable.
- Newer rows — the ones the site actually shows — are stamped correctly; it's the tail of the page that skews.
- Observations upsert on
(source_id, observed_at, data_type), so a skewed row lands on a neighbouring slot rather than corrupting a good one.
Possible fix
Derive the offset per-row rather than per-page: resolve the naive stamp against a real IANA zone (America/Los_Angeles) instead of the page's fixed marker, letting zoneinfo apply the transition. That's a real change to parse_datetime's contract, hence not bundled into #227.
Adjacent, noted while here: if a page ever carried no marker, tz=None makes parse_datetime stamp naive local time as UTC — a silent 7–8h shift that dump_to_db's _localize can't rescue, because the value is already tz-aware by then. No live page does this today (every data-bearing page carries exactly one (PDT)), so it's theoretical — but it's the same root cause and worth fixing together.
— Claude · Opus-4.8(1M) · x-high
Pre-existing, seasonal, low severity. Filed out of review of #227, which found it and correctly scoped it out — it predates that branch and affects every
pe.Mechanism
nwrfc_textplot.pyreads one timezone marker for the whole page:But a textPlot page carries ~11 days of rows. Observed live:
TIMEZONE_OFFSETS(utils/conversions.py) is a fixed-offset map, so every row on the page gets the header's single offset.Impact
For the ~11 days after a transition, the header reads
(PST)and rows logged before the transition — which were actually PDT — get −8 instead of −7.2026-10-31 08:00 PDT(15:00 UTC) stores as 16:00 UTC.Next occurrence: 2026-11-01. Affects the ~11-day window after each spring/fall transition, on any wired textPlot source, one hour, one direction.
Why it's minor
(source_id, observed_at, data_type), so a skewed row lands on a neighbouring slot rather than corrupting a good one.Possible fix
Derive the offset per-row rather than per-page: resolve the naive stamp against a real IANA zone (
America/Los_Angeles) instead of the page's fixed marker, lettingzoneinfoapply the transition. That's a real change toparse_datetime's contract, hence not bundled into #227.Adjacent, noted while here: if a page ever carried no marker,
tz=Nonemakesparse_datetimestamp naive local time as UTC — a silent 7–8h shift thatdump_to_db's_localizecan't rescue, because the value is already tz-aware by then. No live page does this today (every data-bearing page carries exactly one(PDT)), so it's theoretical — but it's the same root cause and worth fixing together.— Claude · Opus-4.8(1M) · x-high