mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-21 10:15:04 +02:00
Fix: Print Activity heatmap buckets by local date, not UTC date (#1446)
PrintCalendar.tsx had three instances of the same UTC-shortcut anti-pattern:
1. Bucketing input dates via `date.split('T')[0]` — gives the UTC day
while the cell tooltip rendered local via `toLocaleDateString`. Same
data, two renderers, only one was tz-correct. Reporter on CDT (UTC-5)
saw evening prints jump to "tomorrow's" cell.
2. Per-cell lookup key built via `day.toISOString().split('T')[0]`. The
`day` Date objects produced by the calendar-generation loop are
local-tz (constructed via `new Date()` + `setDate`), so `toISOString`
shifted them back to UTC before the lookup — would have re-broken the
join even after the bucketing fix.
3. "Today" ring comparison used `new Date().toISOString().split('T')[0]`
too — at 23:00 local the ring would have moved to UTC-tomorrow's cell.
Fix adds a `localDateKey(input: string | Date): string` helper to
utils/date.ts that wraps parseUTCDate and formats via the local-tz
getters (`getFullYear` / `getMonth` / `getDate` with two-digit padding),
returning a stable comparable YYYY-MM-DD. PrintCalendar.tsx uses it in
all three spots so the buckets, the cell join, and the today ring share
the same local-tz axis as the user's tooltip label.
Backend stays UTC. Bucketing is a presentation concern and the browser
already knows the user's tz.
Stragglers flagged for follow-up: StatsPage.computeDateRange builds
dateFrom/dateTo for backend stats queries using getUTC* getters, so a
"this week" picked at 23:00 local on Sunday in CDT sends UTC-Monday-based
ranges to the backend. Fixing it properly also needs the backend to
filter on a tz-shifted UTC range, and Bambuddy has no user-tz setting
model today. localDateKey is in place for reuse when that work lands.
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { useMemo, useRef, useState, useEffect } from 'react';
|
||||
|
||||
import { localDateKey } from '../utils/date';
|
||||
|
||||
interface PrintCalendarProps {
|
||||
printDates: string[]; // Array of ISO date strings
|
||||
months?: number; // How many months to show (default 3)
|
||||
@@ -24,11 +26,14 @@ export function PrintCalendar({ printDates, months = 3 }: PrintCalendarProps) {
|
||||
}, []);
|
||||
|
||||
const { weeks, monthLabels, printCounts } = useMemo(() => {
|
||||
// Count prints per day
|
||||
// Count prints per day. Bucket by local-tz YYYY-MM-DD so an evening
|
||||
// print in a negative-UTC-offset region (e.g. CDT) doesn't get
|
||||
// attributed to "tomorrow's" UTC cell while its label renders as
|
||||
// today (#1446).
|
||||
const counts: Record<string, number> = {};
|
||||
printDates.forEach((date) => {
|
||||
const day = date.split('T')[0];
|
||||
counts[day] = (counts[day] || 0) + 1;
|
||||
const day = localDateKey(date);
|
||||
if (day) counts[day] = (counts[day] || 0) + 1;
|
||||
});
|
||||
|
||||
// Generate weeks for the last N months
|
||||
@@ -149,9 +154,12 @@ export function PrintCalendar({ printDates, months = 3 }: PrintCalendarProps) {
|
||||
);
|
||||
}
|
||||
|
||||
const dateStr = day.toISOString().split('T')[0];
|
||||
// Keep lookup + "today" comparison on the same local-tz
|
||||
// axis as the buckets — toISOString() would shift these
|
||||
// cells to UTC and break the join (#1446).
|
||||
const dateStr = localDateKey(day);
|
||||
const count = printCounts[dateStr] || 0;
|
||||
const isToday = dateStr === new Date().toISOString().split('T')[0];
|
||||
const isToday = dateStr === localDateKey(new Date());
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user