If you write code that touches dates, you've hit this decision: store a Unix timestamp (a number like 1769827200) or an ISO 8601 string (like 2026-01-31T12:00:00Z)? Both are valid. Picking wrong β or mixing them carelessly β causes subtle, hard-to-debug bugs.
What is a Unix timestamp?
A Unix timestamp is the number of seconds since January 1, 1970 00:00:00 UTC, ignoring leap seconds. It's a plain integer, which makes it trivially easy to compare, sort, and do arithmetic on.
1769827200 // 2026-01-31T12:00:00Z
What is ISO 8601?
ISO 8601 is a text format for dates and times. The version developers actually use in the wild is usually the "extended" UTC form:
2026-01-31T12:00:00Z
It's human-readable, includes an explicit time zone, and (unlike a bare timestamp) you can tell at a glance when the event happened.
When to use each
Use a Unix timestamp when:
- Storing values in a database column meant for dates
- Comparing or sorting programmatically
- Doing arithmetic (e.g., "add 86400 seconds")
- Building APIs where the consumer is code, not humans
Use ISO 8601 when:
- Serializing data over JSON APIs (JSON has no native date type)
- Logging and debugging β you want to read the timestamp
- Sending dates to another system that will parse them
- Storing a date that should display in a specific zone without client-side guesswork
The common pattern: store as a timestamp or Date, serialize as ISO 8601. Libraries in every language convert between the two losslessly.
The classic pitfalls
1. Millisecond vs second confusion
JavaScript Date.now() returns milliseconds (1769827200000), while most other ecosystems use seconds. Feeding a millisecond value where seconds are expected gives you a date in the year 57,000. Always check your language's convention:
const seconds = Math.floor(Date.now() / 1000);
2. A timestamp without a time zone is meaningless
The number 1769827200 is inherently UTC β good. But if you store local-wall-clock values like "10:00" without a zone, you've made an un-debuggable mess. Store absolute instants; render local times.
3. ISO 8601 without a zone is ambiguous
2026-01-31T12:00:00 with no Z and no offset is local time β but local to whom? Only the server and client if they happen to share a zone. Always append Z (UTC) or an explicit offset like +08:00.
4. Naive vs aware (Python) / Instant vs LocalDateTime (Java)
Most modern languages have types that distinguish "an instant in time" from "a calendar time in some zone." If you store the calendar time, you inherit every daylight-saving and time zone rule downstream. Prefer the instant type.
Converting between them
To convert a Unix timestamp to a readable ISO string:
new Date(1769827200 * 1000).toISOString()
// "2026-01-31T12:00:00.000Z"
To parse an ISO string back to seconds:
Math.floor(new Date("2026-01-31T12:00:00Z").getTime() / 1000)
// 1769827200
Why UTC is the right default
Whether you store a timestamp or an ISO string, store it in UTC. Never store local time and never store a mixed bag of offsets. Convert to a local time zone only at the very edge β when displaying to a user. This is why APIs, logs, and databases overwhelmingly use UTC, and why the "Z" in 2026-01-31T12:00:00Z is your friend.
Frequently asked questions
What about leap seconds? Unix time ignores them. ISO 8601 can represent them with :60, but almost no software uses that. For practical purposes, ignore leap seconds entirely.
Why 1970? It's an arbitrary, widely adopted epoch (a reference point). The same instant can be stored relative to other epochs (like 2000), but sticking with the Unix epoch avoids every interop headache.
How do I display a UTC time in the user's local zone? Let the platform do it β toLocaleString() in JS, strftime/pytz in Python, DateTimeFormatter in Java β always rendering from the UTC instant, never re-parsing.
If you're converting between human time zones for users rather than machines, the time zone converter and UTC/GMT explainer cover the human side.