aboutsummaryrefslogtreecommitdiff
path: root/src/lib/shared/routine-metrics.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/shared/routine-metrics.ts')
-rw-r--r--src/lib/shared/routine-metrics.ts14
1 files changed, 12 insertions, 2 deletions
diff --git a/src/lib/shared/routine-metrics.ts b/src/lib/shared/routine-metrics.ts
index 99e5c56..59413c2 100644
--- a/src/lib/shared/routine-metrics.ts
+++ b/src/lib/shared/routine-metrics.ts
@@ -1,8 +1,16 @@
// Pure routine metrics, shared by server services and UI. No db access.
// All timestamps are ISO strings; all math is done in ms then converted to days.
+// Due dates anchor on UTC calendar days, not exact 24h intervals: a daily
+// routine done yesterday is due today, whatever the time of day.
const DAY_MS = 24 * 60 * 60 * 1000;
+/** Midnight UTC starting the calendar day containing ms. */
+function startOfUtcDay(ms: number): number {
+ const d = new Date(ms);
+ return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());
+}
+
export type RoutineStatus = 'ok' | 'due' | 'late' | 'veryLate';
export interface RoutineInput {
@@ -48,7 +56,9 @@ export function computeRoutineState(
const referenceMs =
lastDoneMs ?? (Number.isFinite(createdMs) ? createdMs : now);
const periodMs = routine.frequencyDays * DAY_MS;
- const dueMs = referenceMs + periodMs;
+ // Calendar-day anchor: the period starts at midnight of the reference day,
+ // so any new calendar day past the frequency flips the routine to due.
+ const dueMs = startOfUtcDay(referenceMs) + periodMs;
const rawRatio = (now - dueMs) / periodMs;
const overdueRatio = Math.max(0, rawRatio);
// Buckets on overdue time in units of the routine's own frequency:
@@ -127,7 +137,7 @@ export function computeHealthSeries(
refMs = completionMs[nextCompletion];
nextCompletion++;
}
- const dueMs = refMs + periodMs;
+ const dueMs = startOfUtcDay(refMs) + periodMs;
const rawRatio = (t - dueMs) / periodMs;
return Math.max(0, Math.round(100 - Math.max(0, rawRatio) * 100));
};