From 25cca8ce24e18acaa38abce8209a5b1913add4e0 Mon Sep 17 00:00:00 2001 From: sudacode Date: Sun, 12 Jul 2026 02:09:52 -0700 Subject: [PATCH] fix(sync): rollup-day month check lands in the wrong month at negative UTC offsets rollup_day is a local epoch day, but the copy-vs-recompute guard read its month back via day * 86400 (UTC midnight), which resolves to the previous civil month for the 1st of a month anywhere west of UTC. Anchor at local noon (+43200) instead. Pre-existing on main, surfaced by CodeRabbit review. --- changes/sync-monthly-rollup-timezone.md | 4 ++++ src/core/services/stats-sync/merge-rollups.ts | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 changes/sync-monthly-rollup-timezone.md diff --git a/changes/sync-monthly-rollup-timezone.md b/changes/sync-monthly-rollup-timezone.md new file mode 100644 index 00000000..bc4c7aca --- /dev/null +++ b/changes/sync-monthly-rollup-timezone.md @@ -0,0 +1,4 @@ +type: fixed +area: sync + +- Fixed stats sync copying a remote daily rollup that should have been recomputed when the day was the 1st of a month and the machine was on a negative UTC offset (the Americas), which could leave that day's totals wrong after a sync. The rollup day is now read back at local noon instead of UTC midnight, so it always resolves to the correct civil month. diff --git a/src/core/services/stats-sync/merge-rollups.ts b/src/core/services/stats-sync/merge-rollups.ts index ee91e339..3d007898 100644 --- a/src/core/services/stats-sync/merge-rollups.ts +++ b/src/core/services/stats-sync/merge-rollups.ts @@ -193,10 +193,13 @@ export function copyRemoteOnlyRollups( const localMonthSessions = local.query( `SELECT 1 FROM imm_sessions WHERE video_id = ? AND ${LOCAL_MONTH_EXPR} = ? LIMIT 1`, ); + // rollup_day is a *local* epoch day, so anchor it at local noon (+43200) + // before reading its month back: plain UTC midnight lands in the previous + // civil month for the 1st of a month at any negative UTC offset. const localMonthSessionsForDay = local.query( `SELECT 1 FROM imm_sessions WHERE video_id = ? - AND ${LOCAL_MONTH_EXPR} = CAST(strftime('%Y%m', CAST(? AS INTEGER) * 86400, 'unixepoch', 'localtime') AS INTEGER) + AND ${LOCAL_MONTH_EXPR} = CAST(strftime('%Y%m', CAST(? AS INTEGER) * 86400 + 43200, 'unixepoch', 'localtime') AS INTEGER) LIMIT 1`, ); const insertDaily = local.query(