How NOT to Break Time (or Your App)
There are two certainties in life: death… and date bugs. And one of those — with some luck — you can actually prevent.
If you’ve been coding long enough, at some point you thought: “storing a date can’t be that hard.”
Wrong.
It absolutely can. And when it blows up, it doesn’t happen on a Tuesday at 11 AM while you’re sipping coffee. It happens in production, silently… usually on a Sunday at 2:00 AM, right when time itself decides to get weird.
The classic mistake
Most systems start the same way: “I’ll just store the date as it comes in.”
So they save something like:
2026-04-09 10:00
And that’s where the disaster begins.
For a user in Houston, maybe that looks right. For someone in New York, it’s already wrong. For another system consuming that data, it’s not even clear what that value actually means.
The database isn’t “understanding” a human intention; it’s just storing something ambiguous… with way too much confidence.
The cardinal sins of date handling
1. Storing local time
Storing local time feels convenient… until you have multiple cities, remote users, or integrations.
Suddenly that “10:00 AM” could mean anything depending on who saved it.
2. Using fixed offsets like GMT-5
This looks practical but ignores daylight saving time and real-world rules for each region.
Houston is GMT-6 in winter and GMT-5 in summer. Using a fixed offset is basically telling time: “hold still.”
It doesn’t work.
3. Splitting date and time into separate columns
date = '2026-04-09'
time = '10:00'
This works… until it doesn’t.
What timezone applies? What happens if there’s a time change on that exact day?
Two columns, zero context.
4. Ignoring timezone in both frontend and backend
If you don’t send or preserve timezone context, no one knows what the user actually meant.
Not you. Not your backend. Not your DBA at 3 AM trying to make sense of the logs.
The rule that saves lives
The correct strategy for most SaaS applications is simple:
- Store the instant in UTC → source of truth
- Preserve the timezone → human context
- Convert only at the edges → input and output
Practical example
A user creates an appointment for 10:00 AM in Houston.
The frontend sends:
- local datetime:
2026-04-09 10:00 - timezone:
America/Chicago
The backend converts to UTC and stores:
2026-04-09 15:00:00 UTC
Then, when displaying:
- Houston sees it as 10:00 AM
- New York sees it as 11:00 AM
And everyone stays friends. Until someone stores the time in local time again 😄
What about Unix timestamps?
Sure, you can store dates as Unix timestamps.
It technically works. Just like a lot of things you can do in software… that you don’t necessarily want to maintain for years.
A Unix timestamp represents an instant in UTC as a number:
1712674800
Pros
- Compact
- Easy to compare
- Timezone-neutral
Cons
- Not human-readable
- Makes debugging harder
- In logs it looks like your app is sending encrypted messages
- Doesn’t preserve user context or intent
For systems with appointments, bookings, or time-sensitive business logic, I prefer native date types from the database engine — like TIMESTAMPTZ in PostgreSQL — and storing the timezone separately when needed.
JavaScript example with Luxon
import { DateTime } from "luxon";
// User in Houston creates a 10:00 AM appointment
const local = DateTime.fromISO("2026-04-09T10:00", {
zone: "America/Chicago",
});
// Convert to UTC for storage
const utc = local.toUTC();
// → 2026-04-09T15:00:00.000Z
// Display in New York
const displayInNewYork = utc.setZone("America/New_York");
// → 2026-04-09T11:00:00.000-04:00
My takeaway
Time isn’t complicated. 👉 We make it complicated the moment we assume it’s simple.
If you’re building a scheduling system, a booking platform, a home services app, or any multi-user product with time-sensitive operations, it’s worth getting this right from the start.
My personal rule:
- UTC as the single source of truth
- Timezone as context
- Conversion only at input and output
That prevents bugs, pointless arguments… and 2 AM Sunday phone calls.
What I didn’t cover (yet)
This post covers the fundamentals, but there are a couple of rabbit holes I intentionally left out:
- DST transitions — that magical moment when the clock jumps forward or falls back, and your 2:30 AM appointment either doesn’t exist or happens twice. That’s where the real fun begins.
- Recurring events — “every Tuesday at 10:00 AM” sounds simple until the user moves to a different timezone, or DST shifts the whole series by an hour. UTC alone doesn’t solve this — you need to store the original local time and the timezone, then recalculate each occurrence.
- Temporal API — JavaScript’s new native date/time API is on its way and will eventually replace the need for libraries like Luxon. But browser support isn’t universal yet, so for now, Luxon (or
date-fns-tz) remains the practical choice. - MySQL vs PostgreSQL — I mentioned
TIMESTAMPTZin PostgreSQL, but MySQL has its own nightmare withDATETIMEvsTIMESTAMP. Different engines, different traps.
Each of these deserves its own post. Stay tuned.
This is the same kind of engineering thinking I apply when building real products: solve the hard foundational problems early, so the system can scale with fewer surprises… and fewer fires in production.
If this topic resonated — or if you’ve ever fought with dates in production — feel free to reach out or drop a comment.
I’d love to hear how other teams are solving this problem.
Loading comments...