← All posts

Epoch Time Explained — What Is Unix Timestamp Zero?

Unix timestamp 0 is January 1, 1970 at 00:00:00 UTC — the moment Unix time begins. This guide explains why 1970 was chosen by the original Unix team at Bell Labs, how negative timestamps represent pre-1970 dates, the evolution from 32-bit signed time_t to 64-bit, how UTC and TAI handle leap seconds, the Year 2038 boundary, and the epochs other systems use (Windows FILETIME, .NET ticks, Mac Classic, Excel, GPS, NTP, J2000).

What Unix timestamp 0 represents

Unix timestamp 0 is the instant 1970-01-01 00:00:00 UTC — the start of the Unix epoch. Every other Unix timestamp is the count of seconds from that instant. Positive values move forward in time; negative values move backward. The number is a single integer (or float in some runtimes), timezone-neutral by construction. Two systems anywhere on Earth that compute 'Unix time' for the same physical instant return the same number, modulo clock-sync drift.

  • 0 = 1970-01-01 00:00:00 UTC (the Unix epoch)
  • 1 = 1970-01-01 00:00:01 UTC
  • 86,400 = 1970-01-02 00:00:00 UTC (one full day)
  • 31,536,000 = 1971-01-01 00:00:00 UTC (one full non-leap year)
  • 1,000,000,000 = 2001-09-09 01:46:40 UTC (the famous 1-billion-second mark)
  • 1,700,000,000 = 2023-11-14 22:13:20 UTC
  • 2,147,483,647 = 2038-01-19 03:14:07 UTC (the Year 2038 signed-32-bit boundary)

Why January 1, 1970?

The choice of 1970-01-01 traces back to the original Unix development at Bell Labs in the late 1960s. The first time specification, drafted in 1971, used 1971-01-01 as the epoch and counted in 1/60-second 'jiffies' — convenient for the 60 Hz US power-line frequency but exhausting a 32-bit unsigned integer in just 2.3 years. The team — Ken Thompson, Dennis Ritchie, Doug McIlroy, and colleagues — switched to 1970-01-01 with 1-second resolution shortly after, extending the range to roughly 136 years on a signed 32-bit type. 1970 was chosen partly because it was a clean recent date, partly because the older epoch had already passed by the time the new system shipped. The decision proved durable enough that more than 50 years later, Unix time still anchors essentially every server, smartphone, browser, and protocol that handles wall-clock time.

  • 1971 spec (rejected): 60 ticks/second × 32-bit unsigned = 2.3 years of range
  • 1972 spec (adopted): 1 second per tick × 32-bit signed = 136 years of range
  • Range with signed 32-bit time_t: 1901-12-13 to 2038-01-19
  • Bell Labs Unix Version 1 (1971): first OS to use the new epoch
  • The clean-date choice avoided the 'why this random instant?' question later programmers ask

Negative timestamps — dates before 1970

Modern Unix time uses a signed integer, so values below zero represent instants earlier than 1970-01-01 UTC. -1 is one second before the epoch (1969-12-31 23:59:59 UTC); -86,400 is exactly one day earlier. Most languages handle negative values transparently — JavaScript Date, Python datetime, Java Instant, Go time.Time — but Excel, some SQL functions, and naive offset-arithmetic code clamp at 1970 or return errors. When you store or transmit potentially-historical timestamps, use a 64-bit signed type (BIGINT, java.lang.Long, Go int64) so the range covers pre-Unix dates without ambiguity.

  • -1 = 1969-12-31 23:59:59 UTC (one second before the epoch)
  • -86,400 = 1969-12-31 00:00:00 UTC (one day before)
  • -2,208,988,800 = 1900-01-01 00:00:00 UTC (the NTP epoch)
  • -11,644,473,600 = 1601-01-01 00:00:00 UTC (the Windows FILETIME epoch)
  • JavaScript: new Date(-86400000).toISOString() returns '1969-12-31T00:00:00.000Z'
  • Python: datetime.fromtimestamp(-86400, tz=timezone.utc) — works without issues
  • Excel limit: dates before 1900-01-01 are unsupported in standard Excel

Practical limits of the format

The Unix timestamp's range depends entirely on the storage type. Legacy 32-bit signed time_t covers from December 1901 through January 2038 — the Year 2038 boundary that's the systemic deadline for any code still using 32-bit time storage. 64-bit signed time_t (now standard on Linux, macOS, modern Windows time APIs) covers from before the Big Bang through the year 292 billion. JavaScript Date stores milliseconds as a 64-bit float, giving ±100 million days (±273,790 years) of range. The Year 2038 problem isn't about the format itself but about systems that still use 32-bit storage — and the migration is largely complete on modern OS kernels, though embedded systems and legacy applications remain at risk.

  • Signed 32-bit time_t: -2,147,483,648 to 2,147,483,647 (1901-12-13 to 2038-01-19)
  • Unsigned 32-bit time_t (rare): 0 to 4,294,967,295 (1970-01-01 to 2106-02-07)
  • Signed 64-bit time_t: ±9.2×10^18 (covers ±292 billion years)
  • JavaScript Date (float64 ms): ±8.64×10^15 ms (±273,790 years)
  • Java Instant: signed 64-bit seconds + 32-bit nanoseconds (≈292 billion years)
  • Linux 5.6+ on 32-bit architectures: full 64-bit time_t via Y2038 fix

Other systems' epochs

Unix isn't the only system that counts from an epoch — most operating systems and many specialized formats pick their own reference date. The choice is usually arbitrary (a clean date around the system's design era) or tied to a domain convention (GPS, NTP, J2000 for astronomy). Converting between any two epochs is just an offset addition; the math below is the basis for /formats and other inter-system converters.

  • Unix / POSIX: 1970-01-01 00:00:00 UTC (the standard for most server software)
  • Windows FILETIME: 1601-01-01 00:00:00 UTC (100-nanosecond intervals)
  • .NET DateTime.Ticks: 0001-01-01 00:00:00 UTC (100-nanosecond intervals)
  • Mac Classic / HFS+: 1904-01-01 00:00:00 UTC (chosen because 1904 was the most recent leap year before 1970)
  • Excel (Windows): 1900-01-01 (with a leap-year bug that double-counts Feb 29, 1900)
  • NTP: 1900-01-01 00:00:00 UTC (32-bit seconds, rolls over in 2036)
  • GPS: 1980-01-06 00:00:00 UTC (no leap seconds; counts TAI seconds)
  • J2000 (astronomy): 2000-01-01 12:00:00 UTC (TT — terrestrial time)
  • Convert: see /formats for an interactive converter between every epoch above

Epoch seconds, milliseconds, microseconds, and nanoseconds

Unix time historically meant seconds — that's how POSIX defines time_t. Modern systems also use sub-second variants: milliseconds (JavaScript, Java, MongoDB BSON Date), microseconds (PostgreSQL, BigQuery, ClickHouse), and nanoseconds (Linux clock_gettime, Go, InfluxDB, OpenTelemetry). All count from the same 1970 epoch; they differ only in resolution. The unit is usually identifiable from the digit count: 10 digits is seconds, 13 is milliseconds, 16 is microseconds, 19 is nanoseconds. Mixing units at a system boundary is the single most common source of timestamp bugs in production.

  • Seconds (10 digits in 2026): POSIX standard; Python time.time(), Go .Unix()
  • Milliseconds (13 digits): JavaScript Date.now(), Java currentTimeMillis(), BSON Date
  • Microseconds (16 digits): PostgreSQL TIMESTAMPTZ, BigQuery TIMESTAMP_MICROS
  • Nanoseconds (19 digits): Linux clock_gettime(CLOCK_REALTIME), Go .UnixNano(), InfluxDB
  • Convert between: divide or multiply by 1000 at each step (s → ms → µs → ns)
  • JavaScript precision limit: ms only (Date is float64); use performance.timeOrigin + performance.now() for sub-ms

Epoch time vs UTC vs local time

Unix time and UTC are related but not identical. Unix time is a single integer; UTC is a wall-clock representation with year, month, day, hour, minute, second, and (since 1972) occasional leap seconds. A Unix timestamp can be formatted as UTC, as any IANA-named timezone, or — by default in most languages — as the system's local timezone. The conversion is purely cosmetic: the underlying instant doesn't change. The bug pattern to watch for is when one part of a system formats with a different timezone than another and the resulting strings can't be compared as instants.

  • Unix time → UTC: pure decoding, deterministic
  • Unix time → local time: depends on the system timezone or an explicit IANA name
  • UTC ↔ local: depends on the IANA name and the date (DST changes the offset)
  • Display rule: keep storage in UTC (or Unix integer), display in the user's tz
  • Comparison rule: convert to Unix integer first, never compare formatted strings

Leap seconds, TAI, and the POSIX idiosyncrasy

POSIX defines time_t as having exactly 86,400 seconds per day. But Earth's rotation is irregular, and UTC inserts occasional leap seconds — extra seconds added (or theoretically subtracted) to keep wall-clock time aligned with astronomical observations. When a positive leap second occurs, Unix time repeats: the second 23:59:60 UTC shares a Unix timestamp with the previous 23:59:59. TAI (International Atomic Time) is the underlying counter without leap seconds — used internally by GPS, financial trading systems, and high-precision timekeeping. The two diverge by an integer number of seconds (37 as of 2026); the international standards body voted in 2026 to abolish leap seconds by 2035, after which UTC and TAI will drift apart but no application code will see leap-second repeats.

  • TAI: monotonic, leap-second-free, the underlying atomic time count
  • UTC: TAI minus the current leap-second offset (37 seconds as of 2026)
  • Unix time: POSIX-defined as UTC seconds; repeats on positive leap seconds
  • GPS time: similar to TAI, no leap seconds — leads UTC by 18 seconds
  • Abolition: voted at CGPM in 2022, reaffirmed at WRC-23 (2023), effective by 2035

Multics, POSIX vs Unix, and other software epochs

Unix isn't the only operating system tradition that influenced modern timekeeping. Multics — Unix's direct ancestor — used a 1900-based 71.6-bit timekeeping format that strongly influenced Unix's design choices, though Unix simplified the encoding significantly. POSIX (IEEE 1003.1) is the formal standard that documented and stabilized the Unix interface, including the time API. 'POSIX time' and 'Unix time' refer to the same number; POSIX is the spec name, Unix is the historical tradition. RFC 868 defines an internet time protocol with a 1900-based epoch; NTP and the Time Protocol (port 37) inherited that choice.

  • Multics (1965–): used a 71.6-bit time word with 1900 epoch, microsecond precision
  • Unix V1 (1971): seconds since 1970, signed 32-bit
  • POSIX (IEEE 1003.1, 1988): formalized Unix interfaces including time_t
  • ISO 8601 (1988): wall-clock representation standard (orthogonal to Unix time)
  • RFC 868 (1983): network Time Protocol used 1900-based epoch
  • RFC 5905 (2010): NTP v4 — modern internet time sync, still 1900-based

How epoch time connects to the rest of the cluster

This page is the conceptual entry point for the unixepochtime.com Unix-timestamp cluster — every other guide on the site builds on the 1970 epoch as its anchor. The conversion guides (Unix time to date, date to epoch) translate epoch integers to and from human-readable forms. The language-specific guides (JavaScript Date.now, epoch milliseconds to date) show how each runtime accesses the epoch. The bug catalog and storage guide show what goes wrong when the epoch's invariants — unit, signedness, timezone-neutrality — are violated. Use this article as the link target when someone first asks 'what does epoch even mean?'.

  • Conversion — epoch to date: see /blog/unix-time-to-date
  • Conversion — date to epoch: see /blog/date-time-to-epoch
  • JavaScript specifically: see /blog/javascript-date-timestamp
  • Current 'now' in every language: see /blog/current-unix-timestamp
  • Storage in databases: see /blog/unix-timestamp-databases
  • Bugs to avoid: see /blog/timestamp-bugs
  • The Year 2038 boundary: see /guides/year-2038-problem

FAQ

What does epoch mean in Unix time?
The epoch is the reference instant from which Unix time is counted: 1970-01-01 00:00:00 UTC. A Unix timestamp is the number of seconds elapsed from that instant. Positive values are later, negative values are earlier.
Is epoch time always in seconds?
Historically yes — POSIX defines time_t as seconds since the epoch. In practice modern systems also use milliseconds (JavaScript, Java), microseconds (PostgreSQL, BigQuery), and nanoseconds (Go, InfluxDB, Linux clock_gettime). All count from the same 1970 epoch; they differ only in resolution.
Can Unix timestamps represent dates before 1970?
Yes. time_t is a signed integer in modern systems, so values below 0 represent dates before 1970-01-01 UTC. -1 is 1969-12-31 23:59:59 UTC. The legacy 32-bit signed type covers from December 1901 (Y2038 backward) through January 2038 (Y2038 forward).
Does a Unix timestamp include a timezone?
No. A Unix timestamp is a timezone-neutral count — the same number represents the same instant everywhere on Earth. Timezones are applied only when you format the timestamp for human display.
What is the maximum Unix timestamp?
Depends on the storage type. Signed 32-bit time_t (legacy): 2,147,483,647 = 2038-01-19 03:14:07 UTC (the Year 2038 boundary). Signed 64-bit time_t: 9,223,372,036,854,775,807 = year 292,277,026,596. JavaScript Date stores milliseconds as float64 — range is ±100,000,000 days (±273,790 years).
Why was 1970-01-01 chosen as the Unix epoch?
When Unix was being designed in the late 1960s, the first specification used 1971-01-01 as the epoch in 1/60-second ticks, which only spanned ~2.3 years in 32-bit. The team — Ken Thompson, Dennis Ritchie, and colleagues at Bell Labs — switched to 1970-01-01 in 1-second ticks to extend the range to roughly 136 years. 1970 was chosen because it was a clean, recent date that gave the system room to count forward for decades.
What's the difference between Unix time and POSIX time?
They're effectively synonyms. POSIX is the IEEE standard (1003.1) that formalized Unix system interfaces; the time interface specifies seconds since 1970-01-01 00:00:00 UTC. 'Unix time' and 'POSIX time' refer to the same number; POSIX time is the formal name when accuracy matters in a spec.
Does Unix time count leap seconds?
No — and this is a known idiosyncrasy. POSIX defines time_t as 86,400 seconds per day, but UTC inserts occasional leap seconds. During a positive leap second, Unix time briefly repeats — the second 23:59:60 UTC is represented by the same Unix timestamp as 23:59:59 of the next day. TAI time (atomic time, no leap seconds) is what most precision timing systems actually use internally.
How is Unix time different from .NET DateTime.Ticks?
.NET Ticks are 100-nanosecond intervals since 0001-01-01 00:00:00 UTC — a much earlier epoch with finer resolution. To convert: (ticks - 621355968000000000) / 10000000 = Unix seconds. .NET's modern DateTimeOffset.ToUnixTimeSeconds() does the conversion for you.
Is Unix time the same on every operating system?
Yes when the OS implements POSIX correctly — Linux, macOS, BSD, modern Windows time APIs. Some 'epoch-like' systems use different reference dates (Windows FILETIME starts at 1601, .NET DateTime at year 1, Mac Classic at 1904), but the conversion is just an offset addition.