refactor(tz): take the medication and reminder trees off the zone literal

The medication and measurement-reminder code now imports DEFAULT_TIMEZONE
from the tz module like every other fallback, so the lint rule no longer
exempts those paths.
This commit is contained in:
Marc-André Bombeck committed 2026-09-26 17:03:19 +02:00
1 parent e2814336e5
commit 314e9c1aa3
13 files changed
+29 -42

No files matched your search

@@ -30,11 +30,10 @@ import { annotate } from "@/lib/logging/context";
import { satisfyReminder } from "@/lib/measurement-reminders/satisfy";
import { toMeasurementReminderDto } from "@/lib/measurement-reminders/dto";
import { isScreeningReminderType } from "@/lib/validations/measurement-reminders";
import { DEFAULT_TIMEZONE } from "@/lib/tz/format";
type RouteParams = { params: Promise<{ id: string }> };
const DEFAULT_TIMEZONE = "Europe/Berlin";
export const POST = apiHandler(
async (request: NextRequest, { params }: RouteParams) => {
// v1.37.0 — MANAGE. Marking a reminder satisfied re-anchors a schedule the level
@@ -33,13 +33,11 @@ import {
} from "@/lib/measurement-reminders/scheduling";
import { toMeasurementReminderDto } from "@/lib/measurement-reminders/dto";
import { wallClockInTz, zonedWallClockToUtc } from "@/lib/tz/wall-clock";
import { userDayKey } from "@/lib/tz/format";
import { DEFAULT_TIMEZONE, userDayKey } from "@/lib/tz/format";
import { holdsOpenAfterReminder } from "@/lib/measurement-reminders/holds-open";
type RouteParams = { params: Promise<{ id: string }> };
const DEFAULT_TIMEZONE = "Europe/Berlin";
async function resolveTimezone(userId: string): Promise<string> {
const row = await prisma.user.findUnique({
where: { id: userId },
@@ -16,11 +16,10 @@ import { annotate } from "@/lib/logging/context";
import { satisfyReminder } from "@/lib/measurement-reminders/satisfy";
import { toMeasurementReminderDto } from "@/lib/measurement-reminders/dto";
import { isScreeningReminderType } from "@/lib/validations/measurement-reminders";
import { DEFAULT_TIMEZONE } from "@/lib/tz/format";
type RouteParams = { params: Promise<{ id: string }> };
const DEFAULT_TIMEZONE = "Europe/Berlin";
export const POST = apiHandler(
async (request: NextRequest, { params }: RouteParams) => {
// v1.37.0 — MANAGE. The same primitive from the checklist side re-anchors a schedule the level
@@ -22,11 +22,10 @@ import { apiSuccess, apiError, getClientIp } from "@/lib/api-response";
import { annotate } from "@/lib/logging/context";
import { skipReminder } from "@/lib/measurement-reminders/satisfy";
import { toMeasurementReminderDto } from "@/lib/measurement-reminders/dto";
import { DEFAULT_TIMEZONE } from "@/lib/tz/format";
type RouteParams = { params: Promise<{ id: string }> };
const DEFAULT_TIMEZONE = "Europe/Berlin";
export const POST = apiHandler(
async (request: NextRequest, { params }: RouteParams) => {
// MANAGE — the same level as satisfy/complete: deciding to let a cycle
@@ -29,11 +29,10 @@ import { annotate } from "@/lib/logging/context";
import { toMeasurementReminderDto } from "@/lib/measurement-reminders/dto";
import { snoozeMeasurementReminderSchema } from "@/lib/validations/measurement-reminders";
import { zonedWallClockToUtc } from "@/lib/tz/wall-clock";
import { DEFAULT_TIMEZONE } from "@/lib/tz/format";
type RouteParams = { params: Promise<{ id: string }> };
const DEFAULT_TIMEZONE = "Europe/Berlin";
/** Furthest a snooze may reach: five years from now. */
const SNOOZE_CAP_MS = 5 * 365.25 * 24 * 60 * 60 * 1000;
+1 -2
View File
@@ -25,8 +25,7 @@ import {
} from "@/lib/measurement-reminders/scheduling";
import { toMeasurementReminderDto } from "@/lib/measurement-reminders/dto";
import { listMeasurementReminders } from "@/lib/measurement-reminders/list-read";
const DEFAULT_TIMEZONE = "Europe/Berlin";
import { DEFAULT_TIMEZONE } from "@/lib/tz/format";
async function resolveTimezone(userId: string): Promise<string> {
const row = await prisma.user.findUnique({
+4 -3
View File
@@ -59,6 +59,7 @@ import { satisfyReminder } from "@/lib/measurement-reminders/satisfy";
import { evaluateCoachContextReminders } from "@/lib/ai/coach/context-reminders";
import { calendarDaysUntil } from "@/lib/measurement-reminders/due-day";
import { holdsOpenAfterReminder } from "@/lib/measurement-reminders/holds-open";
import { DEFAULT_TIMEZONE } from "@/lib/tz/format";
/**
* v1.18.0 — map a reminder's `measurementType` to the toggleable module
@@ -231,7 +232,7 @@ export function evaluateMeasurementReminderDue(
if (!reminder.enabled || reminder.nextDueAt === null) {
return { fire: false, inHourWindow: false, isDue: false };
}
const tz = timezone || "Europe/Berlin";
const tz = timezone || DEFAULT_TIMEZONE;
const isDue = now.getTime() >= reminder.nextDueAt.getTime();
const parts = wallClockInTz(now, tz);
const inHourWindow = parts.hour === reminderSlotHour(reminder, tz);
@@ -326,7 +327,7 @@ export async function runMeasurementReminderTick(
for (const reminder of reminders) {
summary.candidatesScanned += 1;
try {
const timezone = reminder.user.timezone || "Europe/Berlin";
const timezone = reminder.user.timezone || DEFAULT_TIMEZONE;
// v1.39.2 — a reminder already sent for its current slot stays in
// this scan every tick until it is satisfied. Outside its notify hour
@@ -587,7 +588,7 @@ export async function runReminderSatisfyForUser(
where: { id: userId },
select: { timezone: true },
});
const timezone = user?.timezone || "Europe/Berlin";
const timezone = user?.timezone || DEFAULT_TIMEZONE;
const reminders = await prisma.measurementReminder.findMany({
where: { userId, deletedAt: null, enabled: true },
+2 -1
View File
@@ -30,6 +30,7 @@ import {
nextOccurrenceAfter,
} from "@/lib/medications/scheduling/recurrence";
import { wallClockInTz } from "@/lib/tz/wall-clock";
import { DEFAULT_TIMEZONE } from "@/lib/tz/format";
/**
* The reminder fields this module reads. A subset of the Prisma row so
@@ -133,7 +134,7 @@ export function buildReminderRecurrence(
// `anchorDate` / `endsOn` are instants instead (the form sends local
// midnight, a Coach course sends `now + days`), so hand the engine the
// calendar day each instant falls on in the user's zone.
const tz = timeZone || "Europe/Berlin";
const tz = timeZone || DEFAULT_TIMEZONE;
const startsOn = calendarDayInZone(
reminder.anchorDate ?? reminder.createdAt,
tz,
+1 -2
View File
@@ -2,6 +2,7 @@ import {
reduceCurrentWindowStatus,
type ScheduleWindowInput,
} from "@/lib/medications/window-status";
import { DEFAULT_TIMEZONE } from "@/lib/tz/format";
export interface DefaultMedicationOption {
id: string;
@@ -24,8 +25,6 @@ const DEFAULT_THRESHOLDS: MedicationDueThresholds = {
missedMinutes: 240,
};
const DEFAULT_TIMEZONE = "Europe/Berlin";
/**
* Pick the medication that should be pre-selected when an intake form opens.
* Current, late, and overdue schedule windows win; otherwise the fallback is
+2 -1
View File
@@ -35,6 +35,7 @@ import {
} from "@/lib/medications/intake-tracking";
import { liveEraStartsByMedication } from "@/lib/medications/scheduling/live-era";
import { floorWhole } from "@/lib/medications/units-per-dose";
import { DEFAULT_TIMEZONE } from "@/lib/tz/format";
export type MedicationsListResult = Array<Record<string, unknown>>;
@@ -327,7 +328,7 @@ export async function readMedicationsListCached(user: {
id: string;
timezone: string | null;
}): Promise<MedicationsListResult> {
const userTz = user.timezone || "Europe/Berlin";
const userTz = user.timezone || DEFAULT_TIMEZONE;
return cachedSwr(
caches.medications as ServerCache<MedicationsListResult>,
user.id,
+2 -4
View File
@@ -48,6 +48,7 @@ import {
wallClockInTz,
type WallClockParts,
} from "@/lib/tz/wall-clock";
import { DEFAULT_TIMEZONE } from "@/lib/tz/format";
/** One explicit per-dose on-time window as the API serialises it. */
export interface DoseWindowEntryInput {
@@ -123,9 +124,6 @@ export interface NextDueGate {
overdue: boolean;
}
/** Fallback timezone until every call site threads the user's own. */
const DEFAULT_TZ = "Europe/Berlin";
/**
* Minute-of-day an observer in the target zone reads off the wall clock.
* Every band comparison below is minute-of-day arithmetic, so this is the
@@ -516,7 +514,7 @@ export function reduceCurrentWindowStatus<
active,
lastTakenAt,
todayEventCount,
tz = DEFAULT_TZ,
tz = DEFAULT_TIMEZONE,
nextDue,
} = options;
const nowParts = wallClockInTz(now, tz);