Open any backend log, database column, or API response that includes time, and you'll likely see a number like "1735689600" or "1735689600000." That's a Unix timestamp — the seconds since January 1, 1970, UTC. Why this format dominates the technical world is a story of pragmatism over elegance.

The basic idea

A Unix timestamp is a single integer counting seconds since a specific instant: midnight on January 1, 1970, UTC (Coordinated Universal Time). This instant is called the "Unix epoch."

  • 0 = 1970-01-01 00:00:00 UTC
  • 1 = 1970-01-01 00:00:01 UTC
  • 1735689600 = 2025-01-01 00:00:00 UTC
  • 2147483647 = 2038-01-19 03:14:07 UTC (the famous Y2038 limit for 32-bit signed)

To convert to a human-readable date: just add seconds to 1970-01-01.

Why seconds since 1970?

Unix (the operating system) was developed in the late 1960s and early 1970s. The team needed a simple way to represent time. They picked the start of 1970 as a clean round date close to the system's birth, and "seconds since" as the simplest possible counter.

It's lasted because:

  • It's a single integer — easy to store, compare, and arithmetic-on.
  • It's UTC, so no time zone confusion.
  • It's monotonic — every second has a unique value.
  • It works in any programming language identically.

Negative timestamps for pre-1970

Times before the Unix epoch are negative numbers:

  • −86400 = 1969-12-31 00:00:00 UTC
  • −31536000 = 1969-01-01 00:00:00 UTC
  • −2208988800 = 1900-01-01 00:00:00 UTC

This works for any historical date. Java, Python, JavaScript all handle negative timestamps.

Seconds vs milliseconds

Two flavors:

  • Seconds: traditional Unix timestamp (e.g., 1735689600). Used in C, Postgres, Redis, Bash.
  • Milliseconds: 1735689600000. Used in JavaScript (Date.now()), MongoDB, Java's epoch milliseconds.

Distinguish them by magnitude: seconds are 10 digits (in 2026); milliseconds are 13 digits.

Convert: seconds × 1000 = milliseconds. Or seconds × 1,000,000 = microseconds (used in some scientific applications).

Why UTC and not local time?

If timestamps used local time, you'd need to know which time zone created the timestamp to interpret it. UTC has none of these problems:

  • Same value worldwide — no DST changes, no zone offsets.
  • Easy comparisons across distributed systems.
  • Database simplifications.

To display in user's local time, the application converts UTC → user's zone at render time. Storing in local time is a recipe for bugs (DST changes, multi-region users).

The Y2038 problem

32-bit signed integers can hold values up to 2,147,483,647. As Unix timestamps, that's 2038-01-19 03:14:07 UTC. After this moment, 32-bit signed timestamps overflow to a large negative number (representing a date in 1901).

This is the Y2038 problem — analogous to Y2K but harder to fix.

Modern systems use 64-bit integers, which won't overflow until 292 billion years from now (which is fine).

Vulnerable systems in 2026:

  • Old embedded systems (microcontrollers running Linux from 2010).
  • Some 32-bit databases not upgraded.
  • Old C programs using time_t without 64-bit explicit type.
  • Firmware in industrial equipment.

Most internet-connected systems are fine; the long tail of legacy embedded firmware is the real concern.

How databases handle timestamps

  • Postgres: stores in UTC internally, displays in session zone.
  • MySQL: TIMESTAMP type stores Unix epoch seconds; DATETIME stores a literal date.
  • MongoDB: uses BSON Date type — milliseconds since epoch.
  • Redis: stores values as strings; convention is seconds-since-epoch.
  • SQLite: typically text ISO 8601 or seconds since epoch (Julian day works too).

Date formats vs Unix timestamps

Two main approaches to storing time:

Unix timestamp: 1735689600. Number, sortable, math-friendly.

ISO 8601: "2025-01-01T00:00:00Z". Human-readable, sortable as text, includes time zone explicitly.

Both work. ISO 8601 is preferred for APIs (more readable for humans debugging). Unix timestamp is more efficient for indexing and arithmetic.

Common timestamp tasks

Get current Unix timestamp:

  • JavaScript: Date.now() / 1000 (for seconds)
  • Python: time.time() or int(time.time())
  • Bash: date +%s
  • Postgres: extract(epoch from now())

Convert to date string:

  • JavaScript: new Date(timestamp * 1000).toISOString()
  • Python: datetime.utcfromtimestamp(timestamp).isoformat()
  • Bash: date -d @1735689600 (GNU) or date -r 1735689600 (BSD)

What about leap seconds?

Earth's rotation occasionally adds an extra second to the day (leap second). Unix time pretends these don't exist — it counts as if every day has exactly 86,400 seconds.

Effect: during a leap second, the same Unix timestamp can correspond to two different real-world moments. Most systems "smear" this — slowing the clock for a few hours around the leap second to absorb the extra second smoothly.

For typical apps, leap seconds are invisible and harmless. For high-precision (financial, scientific), specialized handling matters.

Time zone display vs storage

Pattern for any database storing time:

  1. Storage: UTC Unix timestamp. Always.
  2. Display: convert to user's local time at render.
  3. API: exchange in UTC (Unix or ISO 8601 with Z suffix).

Never store in local time. Never assume any default time zone. Make UTC explicit.

Convert quickly

Our Unix timestamp converter handles both seconds and milliseconds in either direction. Useful for translating database values to readable dates, or generating timestamps for testing.