From bd2f982c489b15ce1165c051fdedf35af979f784 Mon Sep 17 00:00:00 2001 From: Martin Kagamino Lehoux Date: Tue, 8 Sep 2026 17:06:51 +0200 Subject: Migrate date handling from Date to Temporal API Routine scheduling, health series, service stamps, session expiry, and date formatting move to native Temporal (Instant / ZonedDateTime in UTC), with ISO strings kept at DB and chart boundaries. Ambient types in src/temporal-ambient.d.ts cover the native runtime; no polyfill. Observable behavior and stored formats are unchanged. --- src/lib/server/services/categories.test.ts | 4 +++- src/lib/server/services/groceries.test.ts | 20 +++++++++++++----- src/lib/server/services/groceries.ts | 4 +++- src/lib/server/services/references.test.ts | 12 ++++++++--- src/lib/server/services/routine-actions.ts | 2 +- src/lib/server/services/routines.test.ts | 33 ++++++++++++++++++++---------- src/lib/server/services/routines.ts | 12 +++++++---- 7 files changed, 61 insertions(+), 26 deletions(-) (limited to 'src/lib/server/services') diff --git a/src/lib/server/services/categories.test.ts b/src/lib/server/services/categories.test.ts index a89b816..dbb21fb 100644 --- a/src/lib/server/services/categories.test.ts +++ b/src/lib/server/services/categories.test.ts @@ -31,7 +31,9 @@ describe('categories service', () => { .insert(categories) .values({ name: 'Dairy' }) .returning(); - const now = new Date().toISOString(); + const now = Temporal.Now.instant().toString({ + smallestUnit: 'millisecond', + }); await db.insert(groceries).values({ name: 'milk', checked: false, diff --git a/src/lib/server/services/groceries.test.ts b/src/lib/server/services/groceries.test.ts index a704b57..4d9aa2f 100644 --- a/src/lib/server/services/groceries.test.ts +++ b/src/lib/server/services/groceries.test.ts @@ -18,7 +18,9 @@ describe('groceries service', () => { }); it('loadUnchecked returns only unchecked items', async () => { - const now = new Date().toISOString(); + const now = Temporal.Now.instant().toString({ + smallestUnit: 'millisecond', + }); await db.insert(groceries).values([ { name: 'milk', checked: false, createdAt: now }, { name: 'bread', checked: true, createdAt: now }, @@ -30,7 +32,9 @@ describe('groceries service', () => { }); it('loadAll returns all items', async () => { - const now = new Date().toISOString(); + const now = Temporal.Now.instant().toString({ + smallestUnit: 'millisecond', + }); await db.insert(groceries).values([ { name: 'milk', checked: false, createdAt: now }, { name: 'bread', checked: true, createdAt: now }, @@ -41,7 +45,9 @@ describe('groceries service', () => { }); it('toggle flips checked state', async () => { - const now = new Date().toISOString(); + const now = Temporal.Now.instant().toString({ + smallestUnit: 'millisecond', + }); await db .insert(groceries) .values({ name: 'milk', checked: false, createdAt: now }); @@ -67,7 +73,9 @@ describe('groceries service', () => { }); it('addOrUncheck unchecks an existing checked item', async () => { - const now = new Date().toISOString(); + const now = Temporal.Now.instant().toString({ + smallestUnit: 'millisecond', + }); await db .insert(groceries) .values({ name: 'milk', checked: true, createdAt: now }); @@ -83,7 +91,9 @@ describe('groceries service', () => { }); it('deleteGrocery removes the item', async () => { - const now = new Date().toISOString(); + const now = Temporal.Now.instant().toString({ + smallestUnit: 'millisecond', + }); await db .insert(groceries) .values({ name: 'milk', checked: false, createdAt: now }); diff --git a/src/lib/server/services/groceries.ts b/src/lib/server/services/groceries.ts index 8ffcde3..9aa8102 100644 --- a/src/lib/server/services/groceries.ts +++ b/src/lib/server/services/groceries.ts @@ -96,7 +96,9 @@ export async function addOrUncheck( .values({ name, categoryId, - createdAt: new Date().toISOString(), + createdAt: Temporal.Now.instant().toString({ + smallestUnit: 'millisecond', + }), }) .returning(); diff --git a/src/lib/server/services/references.test.ts b/src/lib/server/services/references.test.ts index 1ec3fdb..49c5c94 100644 --- a/src/lib/server/services/references.test.ts +++ b/src/lib/server/services/references.test.ts @@ -12,7 +12,9 @@ describe('references service', () => { }); it('loadForGrocery returns refs for the given grocery', async () => { - const now = new Date().toISOString(); + const now = Temporal.Now.instant().toString({ + smallestUnit: 'millisecond', + }); const [g1] = await db .insert(groceries) .values({ name: 'coffee', checked: false, createdAt: now }) @@ -33,7 +35,9 @@ describe('references service', () => { }); it('add creates a reference', async () => { - const now = new Date().toISOString(); + const now = Temporal.Now.instant().toString({ + smallestUnit: 'millisecond', + }); const [g] = await db .insert(groceries) .values({ name: 'pasta', checked: false, createdAt: now }) @@ -51,7 +55,9 @@ describe('references service', () => { }); it('deleteReference removes the ref', async () => { - const now = new Date().toISOString(); + const now = Temporal.Now.instant().toString({ + smallestUnit: 'millisecond', + }); const [g] = await db .insert(groceries) .values({ name: 'rice', checked: false, createdAt: now }) diff --git a/src/lib/server/services/routine-actions.ts b/src/lib/server/services/routine-actions.ts index b546ec7..64d0a1b 100644 --- a/src/lib/server/services/routine-actions.ts +++ b/src/lib/server/services/routine-actions.ts @@ -68,7 +68,7 @@ export async function completeRoutineAction(data: FormData, routineId: number) { await complete( routineId, parseFormString(data.get('note')), - new Date().toISOString(), + Temporal.Now.instant().toString({ smallestUnit: 'millisecond' }), ); } catch { return fail(400, { error: 'Routine introuvable' }); diff --git a/src/lib/server/services/routines.test.ts b/src/lib/server/services/routines.test.ts index 22b49ff..eda8824 100644 --- a/src/lib/server/services/routines.test.ts +++ b/src/lib/server/services/routines.test.ts @@ -12,8 +12,6 @@ import { loadOverview, } from './routines'; -const DAY_MS = 24 * 60 * 60 * 1000; - describe('routines service', () => { let db: Awaited>['db']; let projectId: number; @@ -84,7 +82,11 @@ describe('routines service', () => { expect(versions[0].archived).toBe(false); expect(versions[1].archived).toBe(true); - const dashboard = await loadProjectDashboard(projectId, Date.now(), db); + const dashboard = await loadProjectDashboard( + projectId, + Temporal.Now.instant(), + db, + ); expect(dashboard.routines).toHaveLength(0); expect(dashboard.healthScore).toBe(100); }); @@ -116,15 +118,19 @@ describe('routines service', () => { const archived = await loadArchived(projectId, db); expect(archived).toHaveLength(2); // Descending archive date; equal timestamps keep insertion order. - expect(new Date(archived[0].archivedAt).getTime()).toBeGreaterThanOrEqual( - new Date(archived[1].archivedAt).getTime(), + expect( + Temporal.Instant.from(archived[0].archivedAt).epochMilliseconds, + ).toBeGreaterThanOrEqual( + Temporal.Instant.from(archived[1].archivedAt).epochMilliseconds, ); }); it('complete records the snapshot version it was made against', async () => { const r = await add(projectId, 'clean kitchen', 7, db); await edit(r.id, { frequencyDays: 5 }, db); - const doneAt = new Date().toISOString(); + const doneAt = Temporal.Now.instant().toString({ + smallestUnit: 'millisecond', + }); const completion = await complete(r.id, 'with vinegar', doneAt, db); expect(completion.routineVersion).toBe(2); expect(completion.note).toBe('with vinegar'); @@ -132,16 +138,21 @@ describe('routines service', () => { it('dashboard computes urgency and health from completions', async () => { const r = await add(projectId, 'clean kitchen', 7, db); - const createdAt = Date.parse(r.createdAt); - await complete(r.id, null, new Date(createdAt).toISOString(), db); + const created = Temporal.Instant.from(r.createdAt); + await complete( + r.id, + null, + created.toString({ smallestUnit: 'millisecond' }), + db, + ); // 22 days after completion with a 7-day routine => two periods overdue - const now = createdAt + 22 * DAY_MS; + const now = created.toZonedDateTimeISO('UTC').add({ days: 22 }).toInstant(); const dashboard = await loadProjectDashboard(projectId, now, db); expect(dashboard.routines).toHaveLength(1); expect(dashboard.routines[0].status).toBe('veryLate'); expect(dashboard.healthScore).toBe(0); const last = dashboard.healthSeries[dashboard.healthSeries.length - 1]; - expect(last.at).toBe(now); + expect(last.at).toBe(now.epochMilliseconds); expect(last.health).toBe(dashboard.healthScore); }); @@ -150,7 +161,7 @@ describe('routines service', () => { const growth = await addProject('Personal growth', db); await add(growth.id, 'read 30 minutes', 1, db); - const now = Date.now(); + const now = Temporal.Now.instant(); const overview = await loadOverview(now, db); expect(overview.projects).toHaveLength(2); expect(overview.globalScore).toBe( diff --git a/src/lib/server/services/routines.ts b/src/lib/server/services/routines.ts index ec03c50..a29872a 100644 --- a/src/lib/server/services/routines.ts +++ b/src/lib/server/services/routines.ts @@ -36,7 +36,9 @@ export async function add( projectId, name, frequencyDays, - createdAt: new Date().toISOString(), + createdAt: Temporal.Now.instant().toString({ + smallestUnit: 'millisecond', + }), }) .returning(); return inserted; @@ -91,7 +93,9 @@ export async function archive(id: number, db = defaultDb): Promise { ...current, version: current.version + 1, archived: true, - archivedAt: new Date().toISOString(), + archivedAt: Temporal.Now.instant().toString({ + smallestUnit: 'millisecond', + }), }); } @@ -206,7 +210,7 @@ export interface ProjectDashboard { export async function loadProjectDashboard( projectId: number, - now: number, + now: Temporal.Instant, db = defaultDb, ): Promise { const versions = await db @@ -249,7 +253,7 @@ export interface ProjectsOverview { } export async function loadOverview( - now: number, + now: Temporal.Instant, db = defaultDb, ): Promise { const activeProjects = await db -- cgit v1.3