DST Is Where Scheduling Systems Go to Die
FHIR's permissive timestamp types let scheduling bugs hide until daylight saving lands. The fix is to store wall-clock time with its IANA timezone, not an offset.
The bug that ships in every scheduling system
Most digital health backends end up with a scheduling component. The first one we built was a medication reminder. The next one was a biweekly questionnaire. The one after that was a remote monitoring check-in, then a follow-up workflow, then a clinical-trial timepoint engine. The pattern repeats because clinical care is itself scheduled work: doses are due at specific times, labs are repeated on a cadence, follow-ups happen weeks after a procedure, programs run for the duration of an enrollment.
Every one of those backends had the same bug at least once. The scheduler ran fine for months. A daylight saving transition landed. Reminders fired an hour early, an hour late, or twice. The on-call engineer learned that the system was never timezone-aware in the way the team thought it was; it was only correct as long as the offset that happened to be valid when each timestamp was written stayed valid forever.
DST is where scheduling systems go to die. The bug pattern exists well outside FHIR, and engineers reintroduce it on many projects. FHIR makes it especially easy: the timestamp surface is flexible enough that the bug can hide in resources for months between transitions, with no warning until the wall clock disagrees with the data.
What scheduling does in a clinical backend
Any clinical product needs a scheduling workflow primitive. A CarePlan running a biweekly PHQ-9 produces a questionnaire occurrence every two weeks. A medication regimen lands a dose at the right hour. A remote monitoring program raises a check-in on a cadence. A clinical trial drives visits and assessments against a protocol calendar. An appointment booking workflow turns slots into bookings. A hospital workflow engine routes Tasks to the next clinician on shift.
Underneath each of these is the same piece of work: at some moment in the future, the system has to decide that a particular thing is now due and emit an action. A notification fires. A Task transitions to ready. An Appointment is marked active. A FHIR Subscription sends an event to a downstream consumer.
In FHIR the resources that carry these schedules are widespread: CarePlan, Task, Appointment, Slot, Schedule, MedicationRequest, ServiceRequest, Encounter, and the Timing data type that recurs across all of them. Each of these has at least one timestamp the server will interpret to decide when something happens. Any clinical backend that does anything time-driven, and most do, is reading those fields and acting on them. The storage shape of those fields is one of the more consequential decisions the backend makes, and it is rarely one anyone budgets for.
The FHIR timestamp surface, briefly
FHIR’s primitive time types are intentionally flexible. dateTime accepts a year, a year-and-month, a full date, or a full date-and-time. instant is the stricter sibling: a precise moment with an offset, used for fields like Provenance.recorded or AuditEvent.recorded. The Timing data type carries repeat.timeOfDay (a time primitive: hours, minutes, seconds, no timezone), repeat.dayOfWeek, repeat.boundsPeriod, and other recurrence fields.
Two consequences follow from this surface. The first is that the same field can carry timestamps that mean very different things. 2026-11-01T02:30:00-04:00 is a precise moment. 2026-11-01 is a date with no time. 09:00:00 in Timing.repeat.timeOfDay is a time of day with no timezone at all.
The second is that the only timezone information FHIR carries by default is an offset. An offset is not a timezone. An offset says this is some moment N hours from UTC at the moment it was produced. A timezone says this is the rule the local clock follows over time, including when it changes. The two answer different questions, and a scheduling system needs the second.
Storing a future schedule in offset form is the same shape as the .NET DateTime problems Jon Skeet wrote up in What’s wrong with DateTime anyway?. The type is technically populated; the meaning depends on the system’s behavior at later moments the type cannot describe.
Two failure modes, both routine
The first failure mode is partial precision. A scheduling rule lands in the data as Timing.repeat.timeOfDay = "09:00:00" plus a boundsPeriod and a recurrence cadence. The Timing element by itself carries no timezone. The application is fine in development because the developer’s clock matches the test patient’s clock. The first patient enrolled from a different timezone, or the first deployment in a country with DST, is when the system fires reminders at the wrong time of day. The pattern is hard to detect because the data does not lie about itself; it does not carry enough information to be interpreted correctly. A dateTime stored as a date alone (2026-11-01) has the same problem at coarser granularity: midnight of which day, where?
The second failure mode is offset without timezone. A team writes a future Appointment as 2026-11-01T09:00:00-04:00 because it is October 30th and -04:00 is the correct offset for the patient’s location today. The appointment looks correct in any system that displays it. On November 1st the patient’s wall clock falls back; UTC -04:00 is now an hour ahead of the patient’s actual local time. The instant fires at 8 AM local instead of 9 AM. The data was correct on the day it was written and wrong on the day it was needed.
Neither failure mode is an edge case. Both show up at every spring-forward and fall-back, on any patient whose timezone observes the transition, on any recurring schedule that crosses one. The system’s regular behavior is the bug.
What an offset cannot tell you
An offset on its own cannot answer the questions a scheduling system asks at use time.
A future Appointment is stored as 2026-11-01T09:00:00-04:00 on the day it is created. Six months later, what time was the patient meant to attend? The local component (09:00:00) gives one answer. The offset converted to UTC gives another. The offset interpreted as Eastern Daylight Time on a date when Eastern is in Eastern Standard Time gives a third. Each of these readings is consistent with the data, and they disagree by an hour. Nothing in the resource tells a downstream consumer which reading is the intended one.
The same ambiguity applies to historical records. A Provenance recorded as 2025-03-09T02:30:00-05:00 claims a moment that does not exist in the New York time zone, since the spring-forward skip jumps from 2 AM to 3 AM. Was the audit clock running in EST while the rest of the system was already in EDT? Was the timestamp computed by combining a local time with a stale offset somewhere upstream? The data alone cannot say, and the divergence is a question of forensic significance, not a display bug. Audit records and clinical timelines that need to be reconstructed years after the fact (for an inspection, a litigation hold, or a study readout) are exactly where this matters.
An offset describes one instant. A scheduling system asks about many instants in the future, in a timezone whose rules can change. Governments adjust DST policies on short notice. The IANA timezone database is updated several times a year. The offset that was valid on the day a future timestamp was written is not necessarily the offset that will be valid on the day the schedule fires.
A worked example
A patient enrolls in a biweekly PHQ-9 program on October 4th, 2026, with the schedule anchored to 9 AM local in their timezone. An application that records each occurrence as a frozen dateTime writes 2026-10-04T09:00:00-04:00, then 2026-10-18T09:00:00-04:00, then 2026-11-01T09:00:00-04:00, then 2026-11-15T09:00:00-04:00, and so on through the program.
The first two occurrences are correct. From November 1st onwards the patient’s clock has shifted to -05:00. Every remaining instant in the series fires at 8 AM local. The patient gets a notification an hour before the questionnaire was meant to land, sees an inconsistency, and either ignores it (and the response rate drops) or files a support ticket (and the team learns about the bug from the ticket). Each subsequent biweekly occurrence carries the same offset error.
The corrected implementation stores each occurrence as a local time (2026-11-01T09:00:00) plus the IANA timezone identifier America/New_York, attached through the tz-code extension. The server resolves the local time against the current IANA database when the occurrence is materialized and again when the notification fires. The 9 AM occurrence is 9 AM local on every date, before and after every transition, regardless of when the IANA data was last updated.
Both implementations are about the same amount of code. The naive one produces a scheduling drift twice a year for the life of the program. The corrected one produces none.
The fix is to make the timezone part of the data
Scheduling that survives DST never depends on a frozen offset. It stores the wall-clock time the patient or clinician actually means, and it stores the IANA timezone the wall clock follows. The UTC instant is computed at use time, against the current timezone database, the way modern calendar applications do.
In FHIR this works through the tz-code extension, which carries an IANA timezone identifier (Europe/Berlin, America/New_York) on a dateTime or instant element. A future Appointment.start carries both its local time and the patient’s IANA timezone. A CarePlan carries the patient’s timezone for the duration of the program, so that every Timing element resolved against it inherits the same anchor. A Timing.repeat.timeOfDay of 09:00:00 becomes an unambiguous instruction once the surrounding plan tells the server which timezone 9 AM refers to.
For historical records the rule is the symmetric one. Store the UTC instant for global comparability and store the IANA timezone alongside it for local interpretation. The audit log can then answer when did this happen, globally? from the instant and what time did the user see, locally? from the timezone, and the two readings stay consistent across DST transitions, IANA database updates, and policy changes that retroactively redefine offsets.
Jon Skeet’s article makes the same point from a different angle: there is no single date and time type that means one thing. There is a wall-clock value, an instant, a timezone, and a set of conversions between them. A system that builds these as separate concepts can be correct. A system that stores one and pretends it is the other cannot.
What this looks like at the backend layer
Timezone-correct scheduling cannot be retrofitted onto a backend that stores future timestamps as bare offsets. The fix has to live where future occurrences are materialized, where notifications are emitted, and where audit records are written. It has to live at the data layer for the same reason the access boundary does: every read and every write flows through, and any consumer that asks the data the wrong question gets the wrong answer.
A FHIR backend that ships scheduling as a primitive can hold this end of the contract for the application. The patient’s timezone is stored as data on the Patient or the CarePlan and propagates to every scheduled occurrence. Each upcoming occurrence is anchored to that timezone. The server materializes the rolling window of occurrences against the current IANA database, transitions each one to a due state at the right local time, and emits the notification through the configured channel. DST is a non-event because the system was never relying on a frozen offset. The CarePlan scheduling feature on Fire Arrow Server uses this storage shape, and the remote patient monitoring and patient questionnaire workflows depend on it.
The pattern survives only when the timezone is part of the schedule’s data, not a property of whichever process happens to be doing the conversion. A scheduler that asks the OS for now, a notification service that asks the database for 9 AM, and an audit logger that writes Z because that is the default are three independent answers. They will disagree. The agreement has to be enforced by storing the timezone with the time, where the data is, where any consumer can read it.
The cost of getting this wrong is rarely a regulatory finding. It is a slow erosion of trust in the system: medication reminders the patient stops believing, follow-up notifications that arrive at the wrong hour, audit timestamps that an investigator cannot align with what the user actually saw. The fix is structural and well-known, and it lives in the data layer where the schedule itself lives.
What to read next
For more depth on how scheduling shows up as a clinical primitive, see the CarePlan scheduling feature page and the CarePlan in FHIR glossary entry. The notification side of the same primitive is covered on the FHIR Subscriptions feature page and the FHIR Subscription glossary entry. For applied use cases, ePRO on FHIR, chronic disease management, and remote patient monitoring on FHIR all rely on the timezone-correct pattern above. For the conceptual problem the date-and-time community converged on a decade ago, Jon Skeet’s What’s wrong with DateTime anyway? is still the clearest write-up.