From dd71977bb91c34ec5f02b425579fccca8db56fe7 Mon Sep 17 00:00:00 2001 From: codeX-james Date: Wed, 29 Jul 2026 23:33:06 +0100 Subject: [PATCH] fix(stats): parse hourly buckets as UTC to fix timezone offset bug SQLite's strftime() returns naive datetime strings ("YYYY-MM-DD HH:00:00") with no timezone marker, but the values are computed in UTC since createdAt is stored as UTC ISO strings. JS's `new Date(...)` parses that space-separated, zone-less format as local time, so on any host not running in UTC the hourly bucket keys drift and fail to match the UTC-based series timestamps, leaving tasksLast24h/xlmLast24h values at 0. Append a literal "Z" to the strftime format so the returned string is a valid UTC ISO8601 string that Date parses correctly regardless of the host timezone. Closes #164 --- backend/src/db/stats.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/db/stats.ts b/backend/src/db/stats.ts index 738f9a2..33d4eea 100644 --- a/backend/src/db/stats.ts +++ b/backend/src/db/stats.ts @@ -62,7 +62,7 @@ async function getUptimePercent(db: DbClient, since: Date): Promise { async function getTasksHourlyCounts(db: DbClient, since: Date): Promise> { const rows = db.prepare( - "SELECT strftime('%Y-%m-%d %H:00:00', \"createdAt\") AS hour, COUNT(*) AS count FROM tasks WHERE \"createdAt\" >= ? GROUP BY hour ORDER BY hour" + "SELECT strftime('%Y-%m-%d %H:00:00Z', \"createdAt\") AS hour, COUNT(*) AS count FROM tasks WHERE \"createdAt\" >= ? GROUP BY hour ORDER BY hour" ).all(since.toISOString()) as Array<{ hour: string; count: string | number }>; return rows.map((row) => ({ hour: row.hour, value: Number(row.count ?? 0) })); @@ -70,7 +70,7 @@ async function getTasksHourlyCounts(db: DbClient, since: Date): Promise> { const rows = db.prepare( - "SELECT strftime('%Y-%m-%d %H:00:00', \"createdAt\") AS hour, COALESCE(SUM(amount), 0) AS sum FROM payments WHERE status = 'released' AND \"createdAt\" >= ? GROUP BY hour ORDER BY hour" + "SELECT strftime('%Y-%m-%d %H:00:00Z', \"createdAt\") AS hour, COALESCE(SUM(amount), 0) AS sum FROM payments WHERE status = 'released' AND \"createdAt\" >= ? GROUP BY hour ORDER BY hour" ).all(since.toISOString()) as Array<{ hour: string; sum: string | number }>; return rows.map((row) => ({ hour: row.hour, value: normalizeDecimal(Number(row.sum ?? 0) / STROOP_FACTOR) }));