fix(reminders): read a first-due date sent as a date as that day in the user's zone

A reminder's first-due date could only be sent as a date-time, and the
web form sent the browser's local midnight, so a browser set to a zone
other than the profile's could move the day. The API now also takes a
bare calendar date and stores it as that day's midnight in the user's
zone, and the web form sends the date itself. A date-time still means the
instant it names.
This commit is contained in:
Marc-André Bombeck committed 2026-09-26 22:19:49 +02:00
1 parent b6b51fcb5e
commit 9311337997
8 files changed
+101 -20

No files matched your search

@@ -118,3 +118,19 @@ describe("measurementReminderTypeEnum (V3 — completeness)", () => {
expect(parsed.success).toBe(true);
});
});
describe("createMeasurementReminderSchema — first-due date", () => {
const base = { label: "Blood test", intervalDays: 365 };
it.each([
["2026-10-01", true],
["2026-10-01T00:00:00.000Z", true],
["2026-10-01T00:00:00-07:00", true],
["2026-10-01T00:00:00", false],
["01.10.2026", false],
])("anchorDate %s is accepted: %s", (anchorDate, ok) => {
expect(
createMeasurementReminderSchema.safeParse({ ...base, anchorDate })
.success,
).toBe(ok);
});
});
+11 -2
View File
@@ -143,13 +143,22 @@ const cadenceRefinement = (
}
};
/**
* The first-due date: a calendar date (`YYYY-MM-DD`, read as that day in the
* user's zone) or a date-time (the instant it names).
*/
const anchorDateField = z.union([
z.iso.date(),
z.iso.datetime({ offset: true }),
]);
export const createMeasurementReminderSchema = z
.object({
label: z.string().trim().min(1).max(120),
measurementType: measurementReminderTypeEnum.nullish(),
intervalDays: z.number().int().min(1).max(3650).nullish(),
rrule: rruleField.nullish(),
anchorDate: z.iso.datetime({ offset: true }).nullish(),
anchorDate: anchorDateField.nullish(),
notifyHour: z.number().int().min(0).max(23).default(9),
location: z.string().trim().max(200).nullish(),
enabled: z.boolean().default(true),
@@ -177,7 +186,7 @@ export const updateMeasurementReminderSchema = z
measurementType: measurementReminderTypeEnum.nullish(),
intervalDays: z.number().int().min(1).max(3650).nullish(),
rrule: rruleField.nullish(),
anchorDate: z.iso.datetime({ offset: true }).nullish(),
anchorDate: anchorDateField.nullish(),
notifyHour: z.number().int().min(0).max(23).optional(),
location: z.string().trim().max(200).nullish(),
enabled: z.boolean().optional(),