Show reminder times in the clinic's time zone
Node · Node · intermediate · modification
Reminder texts showed appointment times in UTC, which confused patients (support tickets from the Berlin and New York clinics). This formats the date and time in the clinic's own zone with date-fns's time-zone support: `@date-fns/tz` provides the `tz()` helper and `format` takes it through the `in` option, so daylight-saving changes are handled by the library rather than by offset math. The ` UTC` suffix is dropped, since the time is now the clinic's local time.
Node 20 SMS reminder worker, CommonJS, plain JavaScript (no TypeScript build). `findDueReminders` (existing, unchanged) loads each due appointment with its clinic as `{ id, name, timeZone }` (the `clinics.time_zone` column the booking UI already uses) and calls `reminderText` once per SMS. `package-lock.json` is updated in the same commit and not shown: it resolves `date-fns` to 3.6.0 and `@date-fns/tz` to 1.5.0. `reminderText` has no tests today.
Requirements
- The appointment reminder SMS built by `reminderText(appointment, clinic)` shows the appointment's date and time in the clinic's own time zone instead of UTC: `Reminder: your appointment at <clinic name> is on <EEE d MMM> at <HH:mm>. Reply C to cancel.` For example, an appointment at `2026-10-27T13:30:00Z` at a clinic in `Europe/Berlin` reads `Tue 27 Oct at 14:30`. The ` UTC` suffix goes.
- The time shown is the wall-clock time at the clinic on the appointment's date, daylight-saving changes included, and the date is the clinic's local date (an evening appointment in New York is on the New York day, not the next UTC day).
- The text must not depend on the server's own time zone. The reminder worker runs with `TZ=UTC` today.
- `clinic.timeZone` is an IANA zone name (`Europe/Berlin`, `America/New_York`, `Asia/Kolkata`); it is required and validated against the IANA database when a clinic is saved, so it is always a valid zone. `appointment.startsAt` is always a valid `Date`.
- Dependencies: `date-fns` stays on the 3.x line the service pins (`^3.6.0`, the lockfile resolves 3.6.0). The move to date-fns 4 is a separate, scheduled project and out of scope here. A small time-zone package that supports date-fns 3 may be added for this change.
- Unchanged: the signature and export of `reminderText`, the English weekday and month abbreviations, the 24-hour clock and the rest of the message text.
Files touched
- package.json
- src/reminders/message.js
--- package.json
},
"dependencies": {
+ "@date-fns/tz": "^1.5.0",
"date-fns": "^3.6.0",
"pg": "^8.12.0"
--- src/reminders/message.js
const { format } = require('date-fns');
+const { tz } = require('@date-fns/tz');
// Appointment times come from a timestamptz column as Date instances.
-// The worker runs with TZ=UTC, so this formats in UTC.
-const WHEN_PATTERN = "EEE d MMM 'at' HH:mm 'UTC'";
+// Patients read them in the clinic's own time zone, DST included.
+const WHEN_PATTERN = "EEE d MMM 'at' HH:mm";
function reminderText(appointment, clinic) {
- const when = format(appointment.startsAt, WHEN_PATTERN);
+ const when = format(appointment.startsAt, WHEN_PATTERN, { in: tz(clinic.timeZone) });
return `Reminder: your appointment at ${clinic.name} is on ${when}. Reply C to cancel.`;
}