There is a class of bug that only shows up in support tickets, weeks later, and always sounds the same: the customer says they booked four o'clock and the business says nobody was there at four o'clock. Both are telling the truth.
It is almost always the same root cause. Somebody stored what the clock said instead of what happened.
The instant is the fact
An appointment is a moment in time. That moment is the same for everybody: the business in Madrid, the customer in BogotΓ‘, and the calendar server in between all refer to one instant.
"16:00" is not that moment. It is a label a viewer applies to it, and different viewers apply different labels to the same instant. 2026-09-14T14:00:00Z is a fact. "14 September at 16:00" is a fact plus an unstated assumption about who is reading.
Everything else follows from taking that seriously:
- Slots travel as instants. Availability is computed in the business's zone, because that is where opening hours live, but what leaves the server is an instant.
- The zone the visitor picks changes only the label. It never changes which instant gets booked. That is what makes offering the full time zone list safe.
- Conversion happens at the edge, once. The moment a local time is written into storage, the fact has been replaced by an opinion.
Where it actually bites: grouping by day
The subtle failure is not displaying a time. It is displaying a day.
A business in Madrid opens at 09:00. That slot is 07:00 UTC. For a customer in Mexico City it is 01:00, the night before. If the calendar groups slots by the day they fall on in the business's zone, that customer sees a slot filed under Tuesday that is, for them, Monday night. They click Tuesday and get nothing, or worse, they book what they think is Tuesday morning and turn up a day late.
So the day key has to be computed in the zone being shown, not the zone the slots were generated in. It is one line, and it is the difference between a calendar that works internationally and one that quietly does not.
The confirmation has to agree with the page
Someone picks 16:00 because that is what the page said. A minute later an email arrives saying 10:00, because the email was written in the business's zone.
Nothing is technically wrong. The instant is the same. But the customer now has two different times for one appointment and no way to tell which is real, and every one of those becomes a support conversation or a no-show.
The zone the visitor was reading has to travel with the booking, like any other fact about it, and the confirmation has to be written in it. It is a field on the submission, not something to infer later.
Read the zone list, do not ship one
Time zone rules change. Governments move DST boundaries, abolish it, and occasionally change offset, usually with a few months' notice. A table of zones bundled into an application is correct on the day it ships and wrong somewhere within a year.
Every modern runtime exposes the platform's own list. Reading it means the application inherits the operating system's updates instead of maintaining a copy that ages.
Where the list is unavailable, the honest fallback is not a guess at the world's zones. It is the only two that matter to the page: the visitor's and the business's.
The one that will surprise you
A note from production, because it costs a day to find and five minutes to fix.
Formatting a time as 24-hour is not the same everywhere. Under some runtimes and locales, midnight formats as hour 24 rather than 00 when the 12-hour clock is disabled. A page that parses its own formatted output, or compares it against a range, will do something strange for exactly one hour a day.
The general lesson is the specific one: formatted time is output, never input. Anything that has to be compared, sorted or stored should be working with the instant, not with the string a formatter produced from it.
Summary
- Store and transmit instants. Render labels.
- Group by day in the zone being displayed, not the zone the data came from.
- Carry the visitor's zone with the booking so the confirmation cannot contradict the page.
- Take the zone list from the platform, and fall back to the two zones the page actually needs.
- Never parse a string a formatter produced. It is output.
If a scheduling system has ever been an hour or a day out for one customer, this is where the answer is.



