Timestamp Conversion: Unix Time, ISO 8601, and the Time Zone Problems Nobody Warns You About
Time zones, daylight saving, leap seconds, the Year 2038 problem. Learn the formats, the gotchas, and the rules that make timestamps reliable across systems.
Most date bugs come from the same handful of mistakes: storing local time, comparing strings instead of numbers, ignoring daylight saving, or treating "the day a user clicked save" as a meaningful global concept. Learning what to store, what to display, and what to compare prevents 90% of them.
Three formats you'll encounter
1. Unix timestamp (epoch time)
Number of seconds since 1970-01-01 00:00:00 UTC. Sometimes in milliseconds (JavaScript), microseconds (some databases), or nanoseconds (Go).
1730000000= Oct 27, 2024 02:13:20 UTC- Best for storing & comparing — single number, no time zone confusion.
- Worst for human reading — opaque without conversion.
2. ISO 8601
Human-readable but unambiguous: 2024-10-27T02:13:20Z. The Z means UTC; an offset like +05:30 indicates a specific time zone.
- Best for APIs and logs — unambiguous, sortable as strings.
- Always include the time zone (Z or offset). Never store as "naive" local time.
3. RFC 2822 / Email Date format
Sun, 27 Oct 2024 02:13:20 GMT. Common in HTTP headers and email. Avoid for new systems.
The cardinal rules
- Store everything in UTC. Convert to local time only at display.
- Use Unix timestamps for storage and comparison; ISO 8601 for transit.
- Never compare or sort time zone-naive local times. "9 AM" means different things in different zones.
- Always specify the time zone when displaying. "3:00 PM ET" is clear; "3:00 PM" is ambiguous.
The time zone is not just an offset
America/New_York is sometimes UTC-5 (EST) and sometimes UTC-4 (EDT) — the offset changes twice a year for daylight saving. Store the IANA time zone name, not the offset, if you need to display future events correctly.
Example: a meeting scheduled for "3 PM in New York on March 15, 2027." If you stored the offset as UTC-5, but DST starts on March 8, 2027, your event would display at the wrong time. Store "America/New_York" and let the time-zone library compute the offset at display time.
The DST gotcha
The JavaScript Date trap
JavaScript's built-in Date object has notorious quirks:
new Date('2024-10-27')is parsed as UTC midnight (ISO interpretation).new Date('2024/10/27')is parsed as local midnight.new Date('10/27/2024')is parsed as US-style local; ambiguous in many locales.- Months are 0-indexed:
new Date(2024, 9, 27)= October 27.
Use a real library for non-trivial work: date-fns (functional, tree-shakable),Luxon (immutable, full TZ support), or Temporal (the upcoming standard).
The Year 2038 problem
32-bit signed integer Unix timestamps overflow on January 19, 2038 at 03:14:07 UTC. Any system using a 32-bit time_t will report dates after this as 1901 or fail outright.
Most modern systems have moved to 64-bit timestamps (good for ~292 billion years). But:
- Embedded systems, legacy databases, and some financial systems still use 32-bit.
- MySQL
TIMESTAMPcolumn is 32-bit. UseDATETIMEfor dates beyond 2038. - Some cookies and HTTP cache headers are still 32-bit.
Microseconds and high-precision time
For high-frequency systems (financial trading, distributed systems, observability), seconds aren't enough. Common precisions:
- Milliseconds: JavaScript's default. Most web APIs.
- Microseconds: PostgreSQL's default for
timestamp. Linuxgettimeofday(). - Nanoseconds: Go's
time.Time. Modernclock_gettime().
When converting between systems, watch for unit mismatches. A "timestamp 1730000000" in seconds is October 2024; in milliseconds, it's January 1970.
Common bugs and their fixes
Bug: dates off by hours
Server stores in UTC, client displays in local time, but conversion code mixes them. Make conversion explicit at the display boundary, not in business logic.
Bug: events "disappearing" on certain days
Storing dates as YYYY-MM-DD strings and parsing them as JavaScript Dates causes them to appear as "previous day" in negative-UTC time zones. Use date-only types (PostgreSQL date, Java LocalDate, etc.) or treat date strings as opaque.
Bug: filtering "today" returns wrong rows
"Today" depends on the time zone. A user in Tokyo and a user in San Francisco are looking at different days for 7+ hours each day. Define "today" in the user's time zone, not the server's.
Bug: scheduled jobs running at the wrong time during DST
Cron schedules in "local" time can skip or repeat hours during DST transitions. Always run cron in UTC (or a fixed-offset zone like UTC+9 for Tokyo) and convert if needed.
Database storage best practices
- PostgreSQL: use
TIMESTAMPTZ, notTIMESTAMP. Stores in UTC, displays in session time zone.TIMESTAMPis "naive" — store at your peril. - MySQL:
DATETIMEfor dates after 2038,TIMESTAMPauto-converts to UTC and back to session zone. Pick one and document. - SQLite: no native timestamp type — use ISO 8601 text or Unix integer.
- MongoDB: Date type stores in UTC milliseconds. Always convert to UTC before insert.
Client-side display
Modern browsers expose the user's time zone via Intl.DateTimeFormat().resolvedOptions().timeZone. Use it to convert UTC server timestamps for display, but consider letting users override it (for shared team contexts where everyone needs to see the same time).
Key Takeaways
- Store in UTC. Convert to local time only at display. Never store time zone-naive local times.
- Use Unix timestamps for comparison/storage; ISO 8601 (with Z or offset) for APIs.
- Time zones aren't fixed offsets — DST changes them. Store IANA names like 'America/New_York' for future events.
- JavaScript Date has known quirks: parse with care, prefer date-fns/Luxon/Temporal for non-trivial work.
- Year 2038 problem still affects legacy systems. MySQL TIMESTAMP overflows; use DATETIME for dates beyond 2038.
Convert any timestamp instantly using our Timestamp Converter.