Skip to content

Commit 94528ed

Browse files
7418claude
andcommitted
fix: use local timezone for date-based features (check-in, usage stats)
Root cause: `new Date().toISOString().slice(0, 10)` returns UTC date, causing date-based features to be off by up to ±1 day for non-UTC users. Check-in / assistant workspace: - Add `getLocalDateString()` utility, replace all UTC date comparisons - Schema v2→v3 migration: normalize lastCheckInDate only when it matches UTC today (ambiguous window); leave past dates untouched so check-in triggers correctly after upgrade - Runtime compat in `needsDailyCheckIn`: accept UTC today as fallback for edge cases the migration cannot catch - `needsDailyCheckIn(state, now?)` accepts optional time for testability Usage stats: - Replace single-offset SQL bucketing (`DATE(datetime(..., '+N minutes'))`) with JS-side per-row aggregation using `getLocalDateString(new Date(utc))` - Date methods account for historical DST offset at each message's timestamp - `getTokenUsageStats(days, now?)` accepts optional time to pin window start - Window boundary via `localDayStartAsUTC()` is already DST-aware Tests (new file: timezone-boundaries.test.ts, 22 tests): - getLocalDateString at UTC+9/UTC-5/UTC boundaries with fixed timestamps - needsDailyCheckIn: compat suppression, clearly-past trigger, local match - v2→v3 migration: targeted normalization, past-date preservation, null safe - localDayStartAsUTC: UTC+8/UTC-5 midnight boundaries - Usage stats bucketing: UTC+9 cross-day, summary window, DST spring-forward - All usage stats tests use pinned `now` — no time-bomb from fixed test data Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4da188b commit 94528ed

File tree

11 files changed

+664
-38
lines changed

11 files changed

+664
-38
lines changed

src/__tests__/unit/assistant-workspace.test.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import assert from 'node:assert/strict';
1616
import path from 'path';
1717
import os from 'os';
1818
import fs from 'fs';
19+
import { getLocalDateString } from '@/lib/utils';
1920

2021
// Set a temp data dir before importing db module
2122
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codepilot-workspace-test-'));
@@ -59,7 +60,7 @@ describe('Assistant Workspace', () => {
5960
const state = JSON.parse(fs.readFileSync(statePath, 'utf-8'));
6061
assert.equal(state.onboardingComplete, false);
6162
assert.equal(state.lastCheckInDate, null);
62-
assert.equal(state.schemaVersion, 2);
63+
assert.equal(state.schemaVersion, 3);
6364
});
6465

6566
it('should create all 4 template files', () => {
@@ -90,7 +91,7 @@ describe('Assistant Workspace', () => {
9091
initializeWorkspace(workDir);
9192
const state = loadState(workDir);
9293
state.onboardingComplete = true;
93-
state.lastCheckInDate = new Date().toISOString().slice(0, 10);
94+
state.lastCheckInDate = getLocalDateString();
9495
saveState(workDir, state);
9596

9697
const reloaded = loadState(workDir);
@@ -133,13 +134,13 @@ describe('Assistant Workspace', () => {
133134
});
134135

135136
it('should not trigger check-in if already done today', () => {
136-
const today = new Date().toISOString().slice(0, 10);
137+
const today = getLocalDateString();
137138
const state = { onboardingComplete: true, lastCheckInDate: today, schemaVersion: 2 };
138139
assert.equal(needsDailyCheckIn(state), false);
139140
});
140141

141142
it('onboarding day should skip daily check-in (lastCheckInDate set)', () => {
142-
const today = new Date().toISOString().slice(0, 10);
143+
const today = getLocalDateString();
143144
const state = { onboardingComplete: true, lastCheckInDate: today, schemaVersion: 2 };
144145
assert.equal(needsDailyCheckIn(state), false);
145146
});
@@ -262,20 +263,20 @@ describe('Assistant Workspace', () => {
262263
migrateStateV1ToV2(workDir);
263264

264265
const state = loadState(workDir);
265-
assert.equal(state.schemaVersion, 2);
266+
assert.equal(state.schemaVersion, 3);
266267
assert.ok(fs.existsSync(path.join(workDir, 'memory', 'daily')));
267268
assert.ok(fs.existsSync(path.join(workDir, 'Inbox')));
268269
});
269270

270-
it('should not re-migrate v2 state', () => {
271+
it('should not re-migrate v3 state', () => {
271272
initializeWorkspace(workDir);
272273
const state = loadState(workDir);
273-
assert.equal(state.schemaVersion, 2);
274+
assert.equal(state.schemaVersion, 3);
274275

275276
// Should not throw or change anything
276277
migrateStateV1ToV2(workDir);
277278
const reloaded = loadState(workDir);
278-
assert.equal(reloaded.schemaVersion, 2);
279+
assert.equal(reloaded.schemaVersion, 3);
279280
});
280281
});
281282

0 commit comments

Comments
 (0)