From b4f317e3d51bd626853d28e0407c7f933351b9af Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 25 Jul 2026 03:27:47 +0000 Subject: [PATCH] Fix 7-day forecast showing yesterday: UTC date-parsing bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit new Date("YYYY-MM-DD") parses bare date strings as UTC midnight per spec, then toLocaleDateString() rendered that in the browser's local timezone — for any zone behind UTC (all of the Americas), that rolls the displayed date back a full day, making "today" look like it already passed. Added parseDateOnly() (year/month/day constructor, builds the date in local time instead of round-tripping through UTC) and use it for the daily forecast headings. Also switched the heading format to "Friday July, 24th, 2026" per request, which needed the row layout to drop its fixed 40px width for the old "Thu" abbreviation. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_014c1L8ghNBFjfiH64UMViP8 --- frontend/src/lib/format.ts | 30 ++++++++++++++++++++++++ frontend/src/routes/weather/+page.svelte | 12 ++++++---- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/frontend/src/lib/format.ts b/frontend/src/lib/format.ts index 99675e1..bccf078 100644 --- a/frontend/src/lib/format.ts +++ b/frontend/src/lib/format.ts @@ -21,6 +21,36 @@ export function exactTime(iso: string): string { return `${mm}/${dd}/${yyyy} - ${String(hours).padStart(2, '0')}:${minutes} ${ampm}`; } +/** + * Open-Meteo's daily forecast returns bare "YYYY-MM-DD" dates (no time component) meaning + * that calendar day in the forecast location's own timezone. `new Date("YYYY-MM-DD")` parses + * that as UTC midnight per the ECMAScript spec — displaying it in the browser's local + * timezone (anything behind UTC, i.e. all of the Americas) then rolls it back to the + * *previous* day, making "today" look like it already passed. Parsing via the (year, month, + * day) constructor instead builds the date in local time, avoiding the UTC round-trip. + */ +export function parseDateOnly(dateOnly: string): Date { + const [year, month, day] = dateOnly.split('-').map(Number); + return new Date(year, month - 1, day); +} + +function ordinal(n: number): string { + const lastDigit = n % 10; + const lastTwoDigits = n % 100; + if (lastDigit === 1 && lastTwoDigits !== 11) return `${n}st`; + if (lastDigit === 2 && lastTwoDigits !== 12) return `${n}nd`; + if (lastDigit === 3 && lastTwoDigits !== 13) return `${n}rd`; + return `${n}th`; +} + +/** "Friday July, 24th, 2026" — full weekday/month name with an ordinal day, for the 7-day forecast headings. */ +export function formatDayHeading(dateOnly: string): string { + const d = parseDateOnly(dateOnly); + const weekday = d.toLocaleDateString([], { weekday: 'long' }); + const month = d.toLocaleDateString([], { month: 'long' }); + return `${weekday} ${month}, ${ordinal(d.getDate())}, ${d.getFullYear()}`; +} + export function slugify(name: string): string { return name .toLowerCase() diff --git a/frontend/src/routes/weather/+page.svelte b/frontend/src/routes/weather/+page.svelte index 1cf32a7..0dfc063 100644 --- a/frontend/src/routes/weather/+page.svelte +++ b/frontend/src/routes/weather/+page.svelte @@ -1,6 +1,6 @@