What Happens When You Type a URL and Press Enter
DNS lookups, TCP and TLS handshakes, HTTP requests, servers, and the browser's rendering pipeline. The whole journey of a web page, explained so it finally clicks.
Read →Off-by-one-day birthdays, events that jump an hour twice a year, and reports that disagree between servers. The rules for storing, sending and displaying dates that prevent all of them.
Date bugs are sneaky. They pass every test you run in your own time zone, then a user in another country sees their appointment on the wrong day. Or everything works until daylight saving time changes and a scheduled job runs twice. Here are the rules that prevent almost all of them.
A moment in time, like when an order was placed or a message was sent, should be stored as an unambiguous instant. In practice, that means UTC.
In PostgreSQL, use timestamptz (timestamp with time zone):
created_at timestamptz NOT NULL DEFAULT now()
Despite the name, timestamptz doesn't store a time zone. It converts input to UTC for storage and converts back to the session's time zone on output. Plain timestamp stores whatever you give it with no zone information at all, which is how ambiguity creeps in.
In MySQL, TIMESTAMP converts to UTC for storage, while DATETIME stores the literal value. Whichever you use, make UTC the convention and keep your database and server time zones set to UTC.
Between systems, send dates as ISO 8601 strings with an explicit zone:
2026-03-14T09:30:00Z
2026-03-14T15:00:00+05:30
The Z means UTC. Avoid sending 03/04/2026 (is that March 4th or April 3rd?) or bare 2026-03-14 09:30:00 with no offset. Both are guesses waiting to go wrong.
Keep everything UTC inside your system. Convert to the user's time zone only when displaying, as late as possible:
const formatted = new Intl.DateTimeFormat('en-GB', {
dateStyle: 'medium',
timeStyle: 'short',
timeZone: user.timeZone, // e.g. 'Asia/Kolkata', 'America/New_York'
}).format(new Date(order.createdAt))
Intl.DateTimeFormat is built into every modern browser and Node.js. It handles time zones and locale formats without any library.
+05:30 is an offset. Asia/Kolkata is a time zone. The difference matters because many regions change their offset during the year for daylight saving time, and governments occasionally change the rules.
When you need to know a user's local time, store their IANA time zone name (Europe/London, America/Los_Angeles). The browser can tell you:
Intl.DateTimeFormat().resolvedOptions().timeZone
This is the subtle one. Some values are calendar dates or wall-clock times, not instants:
date type.Ask "is this a moment, a date, or a local time?" every time you add a date column.
The built-in Date has some famous traps:
new Date(2026, 0, 15) is January 15th.new Date('2026-03-14') (date only) is parsed as UTC midnight, while new Date('2026-03-14T00:00') (no offset) is parsed as local midnight. Same day, different instants.date.setDate(...) changes the original object, which surprises code that shares it.toString() uses the machine's zone. Server and browser output will differ.For anything beyond simple formatting, use a library: date-fns (with date-fns-tz), Luxon or Day.js. Also keep an eye on Temporal, the long-awaited modern date API for JavaScript, which is arriving in runtimes and fixes most of these problems by design. Check support before relying on it without a polyfill.
TZ=America/New_York npm test and TZ=Asia/Tokyo npm test.| Situation | Store as |
|---|---|
| When something happened | UTC timestamp (timestamptz) |
| Birthday, due date, holiday | date |
| Recurring local time (opening hours) | Local time + IANA zone name |
| Future local event | Local date-time + IANA zone name |
| User preference | IANA zone name, like Europe/Berlin |
| API payloads | ISO 8601 with Z or offset |
These rules also make APIs more predictable. See more in REST API design best practices.
Store moments (event timestamps) in UTC. Calendar dates like birthdays should be stored as dates, and future local events as local time plus a time zone name.
A date-only value was probably treated as UTC midnight, then displayed in a time zone behind UTC. Store calendar dates as date types and format them without time zone conversion.
timestamptz normalises values to UTC and converts on output, so it represents an unambiguous instant. timestamp stores the literal value with no zone information. For event times, use timestamptz.
DNS lookups, TCP and TLS handshakes, HTTP requests, servers, and the browser's rendering pipeline. The whole journey of a web page, explained so it finally clicks.
Read →There are dozens of HTTP status codes, but about fifteen cover nearly everything. What each one means, when to return it, and the mix-ups (401 vs 403, 301 vs 302) that confuse everyone.
Read →A, AAAA, CNAME, MX, TXT and NS records in plain English, why DNS changes seem slow, and the troubleshooting commands that tell you what is really going on.
Read →