⚡Store times in UTC, display them local - it's the only way to build voice agents that work reliably across timezones.
Where This Started
UTC didn't always exist. Before atomic clocks, the world had Greenwich Mean Time - GMT - which was good enough when most communication happened by ship or letter. Then we got satellites and nuclear weapons and suddenly "good enough" wasn't good enough anymore. In the 1960s, atomic clocks became standard, and the world needed a compromise.
The English wanted to call it CUT (Coordinated Universal Time). The French wanted TUC (Temps Universel Coordonné). Neither side won. So they split the difference and called it UTC. Both names work. Everyone uses neither.
NATO pilots started calling it Zulu time - Z for the zero timezone offset. The name stuck. Today when someone says "the call is at 1400 Zulu," they mean 2 PM UTC, and every timezone in the world knows exactly when that is.
That simplicity - one reference point everyone agrees on - is the entire reason we use UTC. Not because it's special. Because it's neutral.
Why Voice Agents Need This
Consider an engineering consultancy based in Madrid. A voice agent handles bookings for technical consultations. A client in Sydney says "book me for 3pm tomorrow." That gets converted to a UTC timestamp and stored in Supabase. The Madrid team queries the database and sees the same moment in time. The client's calendar shows 3pm AEDT. The confirmation email shows 3pm AEDT. When the reminder fires, the agent says "Your consultation is at 3pm." Everyone sees their local 3pm, but the database sees one true value that can't be misinterpreted.
Now imagine storing local time instead. The agent stores "3pm" directly. Is that 3pm AEDT or 3pm CET? The Madrid booking system doesn't know. If historical data is pulled after a DST change, what does that "3pm" mean now?
Cal.com learned this the hard way. They store every booking in UTC at the database level. The API expects ISO 8601 format: 2024-08-13T09:00:00Z. The Z means UTC. When they integrated with Office365 and Google Calendar, different calendar sources sent different timezone formats. Some sent UTC offsets, some sent timezone names, some sent both. Until everything was normalized to UTC at storage, they had double-bookings.
The reason wasn't complicated. It's just that a database can't compare times reliably if they're in different reference frames.

How Timezones Get Lost
A voice agent gets deployed. It works in the developer's timezone. Then a user in Tokyo books an appointment. A user in São Paulo books an appointment. The dashboard shows conflicting times. The voice agent confirms the wrong time to one of them. Nobody's fault except the way the times are stored in the database.
Store appointment times as timestamptz (timestamp WITH timezone). PostgreSQL automatically converts local times to UTC for storage. That's what we want.
The timezone offset is discarded after storage. If 2021-07-28T17:00:00+01:00 (5pm, one hour ahead of UTC) goes in, Supabase stores 2021-07-28T16:00:00+00:00 (4pm UTC). The +01:00 is gone.
So the user's timezone needs to be stored separately. In voice agent prompts, we typically specify a timezone like America/New_York or Europe/Madrid - that's an IANA timezone name. Store it in that format, not as an offset like -04:00. Offsets change when daylight saving kicks in. IANA names stay valid.
When times come back from the database, the agent converts them to local time before speaking to the user. In Retell, you'd reference the timezone in your prompt. In a Pydantic AI agent, you'd use Python's datetime library to handle the conversion.
What Happens When Someone Books
A client in Sydney says "book me for 3pm tomorrow."
The voice agent converts: 3pm AEDT → 2026-01-15T04:00:00Z (UTC)
Supabase stores: 2026-01-15T04:00:00Z
The client's calendar shows: 3:00 PM AEDT
The agent confirms: "Your consultation is at 3pm."
The Madrid team queries the same event: It's 2026-01-15T04:00:00Z. They convert to Madrid time (UTC+1 in January): 5:00 AM. Their calendar shows 5am. That's the same moment - just a different local representation.
The user never sees UTC. They say 3pm. They see 3pm. They hear 3pm. UTC is the invisible plumbing that makes it work.
What Breaks When This Is Skipped
In 2015, Chile changed its DST policy. Then changed it again in 2016. Apps that stored local times had a problem: the same database entry - a meeting at "2pm" on a specific date - meant something different after the policy changed. Historical data was reinterpreted by the new rules. Meetings moved. Systems broke.
In 2011, Samoa skipped December 30 entirely when it jumped from UTC-11 to UTC+13. Recurring events couldn't happen that day. Systems that relied on local time calculations just... failed.

And there's this: 68% of double-booking incidents in remote teams involve events missing timezone identifiers. Different systems disagree on what time an event actually is because they're working from different reference points.
The reason these problems exist is that local time is relative. UTC isn't. UTC is the same everywhere, at every moment, forever.
How to Implement This
In Supabase, create the schema with timestamptz:
CREATE TABLE appointments (
id uuid PRIMARY KEY,
user_id uuid NOT NULL REFERENCES users(id),
appointment_time timestamptz NOT NULL,
user_timezone text NOT NULL
);
Store the user's timezone preference in the users table as an IANA format string. When an API receives a booking request with a local time, convert it to UTC before storing. When displaying it, convert from UTC to the user's timezone.
That's the whole pattern. One direction in, one direction out.
The Takeaway
UTC in the database isn't about precision or correctness. It's about agreement. When every system in the stack agrees on what time something actually is, all the other problems - integrations, DST changes, timezone jumps, historical data - become manageable. When they don't agree, the result is multiple competing truths, and one of them will be wrong.
The best part is that once this is set up, it's invisible. Users experience booking in their local time. The voice agent confirms in their local time. Everything works. That's how it should work.

