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:
@@ -443,3 +443,23 @@ export function formatDurationFromHours(hours: number): string {
|
||||
const m = Math.round((hours - h) * 60);
|
||||
return m > 0 ? `${h}h ${m}m` : `${h}h`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a YYYY-MM-DD key for a date, evaluated in the user's local timezone.
|
||||
*
|
||||
* The naive `iso.split('T')[0]` shortcut gives the UTC date, which buckets
|
||||
* an evening print in a negative-UTC-offset region (e.g. CDT) onto the
|
||||
* following day. Stats / heatmap bucketing is a presentation concern and
|
||||
* the browser knows the user's tz, so we format with the local getters
|
||||
* here. Use `toLocaleDateString` for *displaying* a date — this helper is
|
||||
* for *keying* buckets, where the value needs to be a stable comparable
|
||||
* string regardless of locale conventions.
|
||||
*/
|
||||
export function localDateKey(input: string | Date): string {
|
||||
const date = typeof input === 'string' ? parseUTCDate(input) : input;
|
||||
if (!date) return '';
|
||||
const y = date.getFullYear();
|
||||
const m = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const d = String(date.getDate()).padStart(2, '0');
|
||||
return `${y}-${m}-${d}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user