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(