Dates and Time Zones in Apps: How Not to Get Them Wrong
Reminders sent an hour late, bookings on the wrong day, 'yesterday' that's actually today. Why dates are hard, the golden rule (store UTC, display local), ISO 8601, time zones vs offsets, daylight saving traps, and how to check your AI-built app handles them.
Dates look simple and aren't. Almost every app eventually gets a bug report like: "My reminder came an hour late," "The booking shows the wrong day," or "Everything broke on the night the clocks changed." The good news is that a few rules prevent most of these.
Why dates are hard
- The same moment is a different local time in different places: 9:00 in London is 10:00 in Paris and 04:00 in New York.
- Daylight saving time shifts clocks by an hour, on different dates in different countries — and some places don't use it at all.
- Time zone rules change, by government decision.
- Your server, your database, and each user's device may all be in different time zones.
- Some things are moments ("the payment happened at…") and some are calendar concepts ("my birthday," "every day at 9am") — and they need different handling.
The golden rule: store UTC, display local
UTC (Coordinated Universal Time) is the world's reference time. It has no daylight saving and never changes.
- Store moments in UTC. Every timestamp in your database — created, updated, paid, sent — in UTC.
- Convert to the user's local time only when displaying.
That way, the stored data is unambiguous, sorting and comparing work, and each user sees times in their own zone.
In PostgreSQL, use the timestamptz type ("timestamp with time zone") for moments. Despite the name, it stores a precise moment and converts for display. Plain timestamp has no zone information, and is a common source of hour-off bugs. (What Is a Database?.)
ISO 8601: the one format to use
When sending dates between systems — in APIs, JSON, logs — use ISO 8601:
2026-09-24T14:30:00Z ← the Z means UTC
2026-09-24T16:30:00+02:00 ← same moment, with a +2 hour offset
2026-09-24 ← a date with no time
It's unambiguous and sorts correctly as text. Never send 09/10/2026 — Americans read that as 10 September, most of the world as 9 October. (What Is JSON?.)
In JavaScript, new Date().toISOString() gives you ISO 8601 in UTC.
Time zones vs offsets
An offset like +02:00 is a fixed difference from UTC. A time zone like Europe/Paris is a rule set: +01:00 in winter, +02:00 in summer, with the switch on specific dates.
For anything in the future or recurring, store the time zone name, not the offset:
- A user in Paris sets a daily reminder at 09:00. Store "09:00, Europe/Paris" — not "07:00 UTC," which will be wrong for half the year.
- Time zone names come from the standard IANA time zone database:
America/New_York,Asia/Tokyo,Australia/Sydney. Browsers can tell you the user's:Intl.DateTimeFormat().resolvedOptions().timeZone.
Moments vs calendar dates
Some dates aren't moments at all:
- Birthdays, holidays, due dates — "24 September" is 24 September wherever you are. Store as a date only (PostgreSQL
date), with no time and no zone. Storing a birthday as midnight UTC is how people's birthdays end up displayed a day early. - Opening hours, recurring schedules — "every weekday 09:00–17:00 local." Store the local time and the time zone.
- Events that happened — payments, logins, messages. Store the moment in UTC.
The daylight saving traps
- Some local times don't exist. When clocks spring forward, 02:30 is skipped in many zones. A reminder scheduled for then must go somewhere sensible.
- Some local times happen twice. When clocks fall back, 01:30 occurs twice.
- Not all days are 24 hours. Adding "24 hours" is not the same as adding "one day" across a daylight saving change.
- Scheduled jobs on a server set to local time can run twice or not at all on changeover nights. Run servers and cron jobs in UTC. (Background Jobs and Cron.)
Use a library, and the platform
Date maths is a classic place for subtle bugs, especially in AI-generated code that hand-rolls conversions. Use well-tested tools:
Intl.DateTimeFormat(built into JavaScript) for displaying dates in the user's language and zone.- date-fns with its time zone add-on, or Luxon, for arithmetic and zone conversion.
- The
TemporalAPI — JavaScript's modern replacement forDate— handles zones, calendar dates, and durations properly, and is arriving in browsers and runtimes; check support for your targets before relying on it.
new Intl.DateTimeFormat("en-GB", {
dateStyle: "medium",
timeStyle: "short",
timeZone: "Europe/Paris",
}).format(new Date("2026-09-24T14:30:00Z"))
// "24 Sept 2026, 16:30"
Checking your app
Ask your AI tool to audit date handling, and test yourself:
- Database timestamps use
timestamptz(or equivalent) and are stored in UTC - Birthdays and due dates are stored as dates, not timestamps
- Recurring schedules store a time zone name, not an offset
- APIs send ISO 8601
- Dates are displayed in the user's time zone
- Server and scheduled jobs run in UTC
- Tests cover a daylight saving change and a user in a far-away zone
To test, change your computer's time zone to somewhere far away (like Pacific/Auckland) and use your app. Things that shift a day are bugs.
EasySpawn gives Claude Code a persistent workspace with a real Postgres database, so date handling can be tested against real timestamptz columns and real scheduled jobs — not assumptions. See how it works or join the waitlist.
Related: Design Your First Database · SQL for Beginners · Debugging for Beginners
Keep reading
How to Write a Good Bug Report (for Humans and AI Tools)
'It's broken' can't be fixed. 'On /checkout, clicking Pay with an empty cart returns a 500' can. The five parts of a useful bug report, how to find reproduction steps, what evidence to attach, and a template you can paste into GitHub issues or your AI coding tool.
Why Is My Website Slow? A Beginner's Guide to Finding Out
Slow sites lose visitors. How to measure speed properly, what Core Web Vitals mean, and the usual culprits in AI-built apps — huge images, too much JavaScript, slow database queries, waterfalls of API calls, and a server far from your users — with a fix for each.