Skip to main content
Dev Utilities Pro

Time & Date

Unix Timestamps Explained — Epoch, Y2K38, and ISO 8601 vs RFC 3339

Updated May 23, 2026 · By Byron Malone

A Unix timestamp is an integer count of seconds elapsed since 1970-01-01T00:00:00Z (the Unix epoch), as defined in POSIX.1-2017 §4.16. Modern APIs frequently return milliseconds instead — auto-detect by threshold: if the value is greater than 1012, it is milliseconds. Convert to ISO 8601 by dividing millisecond timestamps by 1,000 first. Always store timestamps in UTC; convert to local time only at display. Systems storing timestamps in signed 32-bit integers will overflow on 2038-01-19T03:14:07Z — use 64-bit storage.

What is a Unix timestamp and why 1970?

A Unix timestamp is the number of seconds (or milliseconds, in many modern APIs) that have elapsed since the Unix epoch: midnight UTC on January 1, 1970. The epoch is defined in POSIX.1-2017 §4.16 as “the number of seconds elapsed since the Epoch, excluding leap seconds.” That “excluding leap seconds” clause means Unix timestamps are not identical to TAI (International Atomic Time) — they are continuous integers that skip the insertion of leap seconds, making duration arithmetic straightforward without needing to know the current leap-second count.

The 1970 epoch was chosen pragmatically by the Unix developers at Bell Labs (Ken Thompson, Dennis Ritchie, and colleagues) in the late 1960s. It was a recent round date that fit in the available integer sizes on PDP-11 minicomputers, allowed both historical timestamps (negative values for dates before 1970) and future timestamps within the representable range. The 1970 epoch won by becoming the Unix standard — and Unix's dominance made it the de facto global convention, now codified in POSIX.1-2017, ECMAScript 2024, and the Java time API.

Advertisement

Seconds vs. milliseconds: auto-detection and the 10¹² rule

The Unix standard defines timestamps in seconds. JavaScript's Date.now() returns milliseconds — a decision made because browsers need sub-second precision for animation timing, performance measurement, and input event handling. Many modern APIs follow JavaScript's convention. This creates a widespread ambiguity.

The practical detection rule:

if abs(value) >= 1_000_000_000_000:  # >= 10^12
  unit = milliseconds
  unix_seconds = value / 1000
else:
  unit = seconds
  unix_seconds = value

This rule is unambiguous for any timestamp representing a date in the range 1970–33658 AD. The Unix timestamp crossed 109 on September 9, 2001 at 01:46:40 UTC. As of May 2026, all second-resolution timestamps are 10 digits (currently ~1,748,000,000). Millisecond timestamps are 13 digits (currently ~1,748,000,000,000). A second-resolution timestamp will not reach 1012 until November 20, 33658 AD — the threshold is unambiguous in any realistic context.

The rule of thumb for code review: if an API response contains a timestamp field with a value over 1,000,000,000,000 (13+ digits), it is milliseconds. If it is 10 digits, it is seconds. If it is somewhere between 10 and 13 digits, check the API documentation — that range is ambiguous.

ISO 8601 and RFC 3339: the human-readable equivalents

ISO 8601:2004 defines the international standard for date and time representation. A Unix timestamp of 1748000000 corresponds to 2025-05-23T13:33:20Z in ISO 8601 format. RFC 3339 is a strict profile of ISO 8601 for internet timestamps — it requires the T separator (not a space), mandates including seconds, and requires a timezone designator (Z or ±HH:mm). RFC 3339 is what OpenAPI 3.x, RFC 7231 (HTTP), RSS 2.0, and Atom feeds all reference.

Practical guidance:

  • Store as Unix seconds (or milliseconds). Integers are compact, timezone-agnostic, sort correctly, and support direct arithmetic (add 86400 to get tomorrow). Use BIGINTin SQL schemas — not INT (see Y2K38 below).
  • Display as ISO 8601 / RFC 3339. Human-readable, self-documenting, sorts lexicographically in UTC. Always include the timezone designator — a bare 2026-05-23T15:30:00 is ambiguous; always use 2026-05-23T15:30:00Z or 2026-05-23T10:30:00-05:00.
  • Pass between systems as UTC integers. UTC Unix timestamps are the safest inter-system format — no timezone interpretation required. Convert to local time only at the last step, at display time, in the user's timezone.
Advertisement

IANA timezones: use names, not abbreviations

Timezone abbreviations are ambiguous. “EST” could be US Eastern Standard Time (UTC−5), or in a non-US context, Australian Eastern Standard Time (UTC+10). “IST” could be Indian Standard Time (UTC+5:30), Ireland Standard Time (UTC+1), or Israel Standard Time (UTC+2). Using abbreviations in code is a reliability bug.

The IANA Time Zone Database (tzdata, maintained at iana.org/time-zones) provides unambiguous named timezones: America/Chicago, Asia/Kolkata, Europe/Dublin, Asia/Jerusalem. These names resolve to the correct UTC offset for any given timestamp, including DST transitions, using the historical rules encoded in tzdata. JavaScript's Intl.DateTimeFormat API and Java's java.timepackage both accept IANA names directly. Python's zoneinfo module (Python 3.9+) uses IANA names via ZoneInfo('America/Chicago').

DST transitions are handled automatically when you use IANA timezone names — the tzdata historical rules know the exact UTC second when each transition occurs. The two transition edge cases to be aware of:

  • Spring-forward gap: In America/New_York, the local time 2:00am–2:59am does not exist on the second Sunday in March. If you create a timestamp for 2026-03-08T02:30:00 in New York, libraries vary on how they resolve it — some advance to 3:30am, some throw an error. The robust pattern is to work in UTC and convert at display.
  • Fall-back ambiguity: In America/New_York, 1:30am occurs twice on the first Sunday in November. A local time string like 2026-11-01T01:30:00 in New York maps to two different UTC timestamps (6:30 UTC and 7:30 UTC). The converter shows both candidate timestamps and lets you select the intended one.

Y2K38: the 2038 problem and how to avoid it

The Year 2038 problem affects systems that store Unix timestamps in signed 32-bit integers. The maximum value of a signed 32-bit integer is 2,147,483,647 — corresponding to 2038-01-19T03:14:07Z. The next second (2,147,483,648) overflows to −2,147,483,648, representing December 13, 1901 at 20:45:52 UTC. This is not a theoretical concern for new systems: code written today that stores timestamps in 32-bit columns will fail in under 12 years.

Systems and scenarios to audit for Y2K38 exposure:

  • MySQL TIMESTAMP columns — 32-bit storage, affected. MySQL DATETIME columns — 64-bit storage, not affected. Audit all MySQL schemas for TIMESTAMP columns used to store dates beyond 2038.
  • PostgreSQL TIMESTAMP columns — 64-bit storage since PostgreSQL 7.3, not affected.
  • 32-bit Linux systemstime_t is 32-bit on 32-bit kernels, affected. All 64-bit Linux systems use 64-bit time_t, not affected.
  • File system timestamps — FAT32 file systems store timestamps in a custom 32-bit format with a different range limit (2107 for FAT32). ext4, APFS, and NTFS are not affected.
  • Embedded systems — IoT devices running 32-bit RTOS with hardcoded 32-bit timestamp fields are the highest-risk category. Review any embedded system that stores timestamps for future-dated content (warranty expiry, certificate validity, subscription end dates).

The fix is straightforward: use 64-bit integer storage for timestamps everywhere. A signed 64-bit timestamp can represent dates up to approximately 292 billion years from now — a sufficient planning horizon.

JavaScript timestamp patterns

Common JavaScript patterns for correct timestamp handling:

// Current time as Unix seconds
const unixSeconds = Math.floor(Date.now() / 1000);

// Current time as Unix milliseconds (ECMAScript native)
const unixMs = Date.now();

// Convert Unix seconds to Date object
const date = new Date(unixSeconds * 1000);

// Format as ISO 8601 UTC
date.toISOString();  // "2026-05-23T15:30:00.000Z"

// Format in a specific IANA timezone
new Intl.DateTimeFormat('en-US', {
  timeZone: 'America/Chicago',
  dateStyle: 'full',
  timeStyle: 'long',
}).format(date);
// "Saturday, May 23, 2026 at 10:30:00 AM CDT"

// Auto-detect seconds vs milliseconds
function toUnixSeconds(value: number): number {
  return Math.abs(value) >= 1e12 ? value / 1000 : value;
}

The Date.parse() function has historically inconsistent behavior across browsers for non-ISO date strings. Always use ISO 8601 format with an explicit timezone designator, or pass the numeric Unix milliseconds to new Date() directly — the numeric constructor is unambiguous in all environments per ECMAScript 2024 §21.4.

Advertisement

Convert a timestamp now

Paste any Unix timestamp (seconds or milliseconds — auto-detected) into the converter. Get the ISO 8601 UTC representation, RFC 3339 with timezone offset, human-readable date in any IANA timezone, relative time from now, and a Y2K38 warning if applicable.

Open timestamp converter

By Last updated

Founder, Bedrocka Tools

Primary source: POSIX.1-2017 §4.16 — Seconds Since the Epoch. See the full timestamp formats methodology for algorithm documentation.