From f0747bae3ab56f9c548b20953f033f298be82528 Mon Sep 17 00:00:00 2001 From: Emi Griffith Date: Fri, 24 Jul 2026 16:06:52 -0700 Subject: [PATCH] Compute "today" and date-picker bounds in local time, not UTC todayISO() and day.js's stepDay() formatted dates via toISOString(), which is UTC. For UTC+ viewers past local midnight this rolled the date a day early: "today", the date-input max, the next-day disabled guard, and the Weekly "Today" button all referred to the wrong day, and prev/next navigation stepped off-by-one. Route both through the existing local isoOfDate() so every view (Weekly, Day Detail, Calendar) agrees on the viewer's local day. --- frontend/static/day.js | 6 ++++-- frontend/static/shared.js | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/frontend/static/day.js b/frontend/static/day.js index c07aec3..53d271a 100644 --- a/frontend/static/day.js +++ b/frontend/static/day.js @@ -7,7 +7,7 @@ import { uv } from "./account.js"; // header sign-in entry + notification bell import { getJSON, TTL, prefetchViews } from "./cache.js"; import { initFindButton, setFindLabel } from "./mappicker.js"; import { TIER_COLORS, PRECIP_COLORS, DRY_COLOR, pctOrd, fmtPrecip, fmtWind, fmtHumid, - todayISO, weatherType, placeLabel } from "./shared.js"; + todayISO, isoOfDate, weatherType, placeLabel } from "./shared.js"; // Color a tier the same way the calendar cell would be colored for it. const tierColor = (c) => (c === "dry" ? DRY_COLOR : TIER_COLORS[c] || PRECIP_COLORS[c] || ""); @@ -44,7 +44,9 @@ function stepDay(delta) { if (!curDate) return; const d = new Date(curDate + "T00:00:00"); d.setDate(d.getDate() + delta); - const iso = d.toISOString().slice(0, 10); + // Format in LOCAL time — curDate was parsed as local midnight, so toISOString() + // (UTC) would shift the result a day for UTC± viewers and desync the today guard. + const iso = isoOfDate(d); if (iso > todayISO()) return; // don't step past today curDate = iso; fetchDay(); diff --git a/frontend/static/shared.js b/frontend/static/shared.js index 423c5ea..b9afe5a 100644 --- a/frontend/static/shared.js +++ b/frontend/static/shared.js @@ -257,7 +257,10 @@ export const pctOrd = (n) => { if (!Number.isFinite(v)) return "—"; return ord(Math.min(99, Math.max(1, Math.round(v)))); }; -export const todayISO = () => new Date().toISOString().slice(0, 10); +// Today in the viewer's LOCAL zone (see isoOfDate below). A UTC date rolls a day +// early/late for UTC± viewers past local midnight, putting "today" and the +// date-picker max out of reach of the day they're actually living in. +export const todayISO = () => isoOfDate(new Date()); export const esc = (s) => String(s).replace(/&/g, "&").replace(//g, ">"); // The heading label for a graded response: the resolved place name, or the // cell-center coordinates while (or if) no name resolves.