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.
This commit is contained in:
Emi Griffith 2026-07-24 16:06:52 -07:00
parent 248eeee110
commit f0747bae3a
2 changed files with 8 additions and 3 deletions

View file

@ -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();

View file

@ -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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
// The heading label for a graded response: the resolved place name, or the
// cell-center coordinates while (or if) no name resolves.