Clockhub Logo
BlogContact
All articles
Reference13 min read

Unix Timestamps: From 1970 to Now

Unix time nowLive
0000000000.000
1970-01-01 00:00:00 UTC · Thursday
seconds since 1970-01-01 00:00:00 UTC

Take the number 1738368000. If you know the rule, it is Saturday, 1 February 2025, 00:00:00 UTC. If you don't, it is noise.

That is the whole idea of Unix time: one integer, no time zone, no calendar, no daylight saving, no argument about which February. Everything else in this article is a consequence of that sentence — including the part where the integer runs out.

I. One Integer, Two Readings

The definition is a single line, and every word in it matters:

Unix time is the number of seconds elapsed since 00:00:00 UTC on 1 January 1970, not counting leap seconds.

Four things are packed in there.

It counts from a fixed instant in UTC. Not from your local midnight. Two people in Tokyo and Chicago looking at the same timestamp see the same number, and they convert it to different wall clocks on their own side.

The unit is the second. Early Unix counted in sixtieths of a second; that is a story for the next section, but it is why the number is so small.

Leap seconds are excluded. This is the part almost everyone gets wrong, and it has its own section below.

It can be negative. Dates before 1970 are simply negative numbers, not an error state. -1 is 1969-12-31 23:59:59 UTC.

Here is the same instant written six ways. Only the last three carry a time zone, and only the first two are what a computer actually stores.

RepresentationValue
Unix seconds1738368000
Unix milliseconds1738368000000
ISO 8601, UTC2025-02-01T00:00:00Z
ISO 8601, UTC+82025-02-01T08:00:00+08:00
ISO 8601, UTC−52025-01-31T19:00:00-05:00
RFC 2822Sat, 1 Feb 2025 00:00:00 +0000

Notice that the timestamp column never changes and the date column changes three times. A timestamp is not a date. It is a point on a single global line. The date is what you get when you ask "where is that point relative to this particular patch of the planet."

II. Why 1970 — and Why Not 1971

Nobody sat down in 1969 and chose 1 January 1970 because it was a nice round date. The epoch was moved three times in two years, and 1970 was where it landed by accident.

The 60 Hz problem

The first Unix systems ran on a PDP-11. Its system clock ticked 60 times a second, because it was designed to keep time for a country whose mains power ran at 60 Hz. So the natural thing to store was a count of ticks.

The problem was that the counter was 32 bits wide. At 60 ticks per second, a 32-bit counter wraps after:

2^32 / 60 seconds = 71,582,788 seconds ≈ 2.26 years

Two and a quarter years. The third edition of the Unix Programmer's Manual, dated 15 March 1972, states it with unusual bluntness:

"The time is stored in 32 bits. This guarantees a crisis every 2.26 years."

Three epochs in two years

The obvious fix was to keep moving the starting line forward. The first edition manual, from 3 November 1971, defines time as:

"the time since 00:00:00, Jan. 1, 1971, measured in sixtieths of a second"

The third edition, four months later, had already shifted it to 1 January 1972. Two epochs in under half a year — the counter was running out faster than they could document it.

The real fix was to stop counting ticks and start counting seconds. That buys a factor of sixty: instead of 2.26 years, a 32-bit counter now covers about 136 years. Once the unit changed, the epoch no longer had to be recent, and it was set to 1 January 1970 — by the account in the manuals, chosen arbitrarily, as "a convenient date to work with."

Why it never moved again

Because by the mid-1970s there was already data on disk. Files, tapes, inodes, backup archives — all of them carried timestamps that meant "seconds since 1970." Changing the epoch again would have silently reinterpreted every file on every Unix machine in the world.

So the accidental choice hardened into an interface. Fifty-six years later, every phone, server and database on the planet still counts from that Thursday morning.

A DEC PDP-11/20 minicomputer at the Computer History Museum, with its original 1970 placard

A PDP-11/20, the machine family Unix was written on. The placard reads "Digital Equipment Corporation (DEC), US, 1970" — the same year as the epoch. Image: Don DeBold, CC BY 2.0, via Wikimedia Commons

III. Every Day Is Exactly 86,400 Seconds

Here is where Unix time stops matching the physical world.

The Earth's rotation is not steady. It slows, it speeds up, it wobbles. Astronomers track this with UT1, a time scale derived from the actual orientation of the planet. Atomic clocks do not care about any of that. The two drift apart.

The fix, invented in 1972, was the leap second: occasionally, at the end of 30 June or 31 December, a second labelled 23:59:60 is inserted into UTC so that the atomic and astronomical scales stay within 0.9 seconds of each other. Twenty-seven have been added so far, the most recent at the end of 2016.

Unix time ignores all of them. The POSIX standard says, in effect, that every day contains exactly 86,400 seconds, no exceptions. So the number is not a count of seconds that physically elapsed. It is a coordinate on a simplified, perfectly uniform grid laid over real time.

What that costs you

Two things, and both are subtle.

Ambiguity. When a positive leap second happens, one Unix value has to be reused. At the end of 1998, 915148800 referred both to 1998-12-31 23:59:60 and to 1999-01-01 00:00:00. The same happened with 1483228800 at the end of 2016. For one second, a timestamp does not identify a unique instant.

A second definition nobody uses. Some Linux systems run in the right configuration, which does count leap seconds by making every day 86,401 seconds long. Since the first leap second in 1972, right time has drifted ahead of ordinary posix time; as of 2017 it is exactly 27 seconds ahead. If you have ever compared two machines and found a 27-second disagreement, this is why.

The mechanism is going away

In November 2022 the 27th General Conference on Weights and Measures decided to discontinue the leap second, with a target of 2035 or earlier — and in October 2026 the 28th conference fixed the date: from 20 May 2027, UTC runs continuously. The reason is not that the drift stopped. It is that the drift has become an engineering liability: a leap second has crashed routers, confused trading systems, and forced every large software company to write special-case code that runs a handful of times a decade and is therefore never properly tested.

After that, UTC will simply be allowed to drift further from the Earth's rotation — and Unix time will become, for the first time, a genuinely uniform count of seconds.

The practical rule

Do not measure durations by subtracting timestamps.

end - start on two Unix values is wrong whenever a leap second falls between them, and wrong again whenever a system clock is adjusted by NTP or by a user. For elapsed time, use a monotonic clock — CLOCK_MONOTONIC in C, performance.now() in a browser, time.monotonic() in Python. Those count real elapsed time and are immune to both problems. Timestamps are for labelling moments; monotonic clocks are for measuring intervals. Mixing them up produces bugs that appear once a year and cannot be reproduced.

Unix time nowLive
0000000000.000
1970-01-01 00:00:00 UTC · Thursday
seconds since 1970-01-01 00:00:00 UTC

IV. The 32-Bit Debt

Back to the counter.

A signed 32-bit integer holds values from −2,147,483,648 to 2,147,483,647. Applied to seconds since 1970, that gives a fixed window:

BoundValueDate
Minimum-21474836481901-12-13 20:45:52 UTC
Epoch01970-01-01 00:00:00 UTC
Maximum21474836472038-01-19 03:14:07 UTC

One second after the maximum, the value overflows and wraps to the minimum. A system that stores time this way will, at 03:14:08 UTC on 19 January 2038, believe it is 13 December 1901.

A timeline from 1900 to 2120 showing the int32 and uint32 ranges, filled to 2026

The int32 range runs from 1901 to 2038; the unsigned range from 1970 to 2106. The solid portion is how much has already been used.

How close are we

The int32 window is 136.1 years long, and 2026 sits at 91.7% of it. This is the part people underestimate. The 2038 problem is not a distant deadline — it is a bar that is nine-tenths full.

The unsigned variant buys another 68 years, to 2106-02-07 06:28:15 UTC. That is where the uint32 row in the diagram comes from, and it is the reason some formats will fail twice.

What actually breaks

Most of the visible damage will not be in the operating systems. Linux moved to 64-bit time_t years ago, and 64-bit builds of everything mainstream followed. The exposure is in the places that are hard to change:

  • Databases. MySQL's TIMESTAMP column type is a signed 32-bit count of seconds. It stops at 2038-01-19 03:14:07 and there is no way to extend it in place. DATETIME has a much wider range and is the correct choice for anything that must outlive 2038 — but the migration is manual, and every table has to be inspected.
  • File formats. Archives, image metadata, mail stores and backup catalogues with 32-bit timestamp fields will keep writing wrapped dates long after the machines that read them have been replaced.
  • Embedded systems. Industrial controllers, medical devices, car infotainment units, building management systems. These are the ones with a twenty-year service life, no update path, and a manufacturer that no longer exists.
  • 32-bit userspace. Still common on older ARM boards and in containers built from old base images.

Rows of server racks in a data centre

Server racks. The systems that will break in 2038 are mostly older and less visible than these. Image: Carl Lender, CC BY 2.0, via Wikimedia Commons

Why it is worse than Y2K

Y2K was a formatting problem with a fixed, well-understood deadline and a clear test: set the clock forward, see if the date looks right. The 2038 problem is a signed arithmetic problem. An overflowed value does not throw an error. It becomes a valid date in 1901, which then propagates: a certificate appears to have expired 124 years ago, a session token looks ancient, an ordering comparison silently reverses.

Failures will be quiet and partial, which is the worst combination.

V. Seconds Stopped Being Enough

The second was adequate when the fastest thing a computer did took milliseconds. It is not adequate now.

ResolutionTypical storageWhere you meet it
1 secondtime_tFiles, databases, HTTP headers
1 millisecondJavaScript Date, Java InstantBrowsers, JSON APIs, Date.now()
1 microsecondstruct timevalgettimeofday, older C APIs
1 nanosecondstruct timespecclock_gettime, APFS, ext4, modern kernels

This is the single most common source of bugs in code that handles timestamps. A value of 1738368000 and a value of 1738368000000 are the same moment in two different units, and both look like plausible integers. A factor of a thousand is easy to apply in the wrong direction and impossible to spot by eye.

The JavaScript ceiling

Date in JavaScript stores milliseconds in a double-precision float. The specification caps it at ±8.64 × 10¹⁵ milliseconds, which is ±100 million days, or roughly ±273,790 years around the epoch. That is far beyond the int32 window — JavaScript has never had a 2038 problem. But the millisecond unit is why so many APIs return thirteen-digit numbers, and why Date.now() divided by 1000 is such a familiar line of code.

Identifiers that carry time

Modern systems increasingly embed a timestamp in the ID itself, which makes the precision choice a design decision rather than an implementation detail. UUIDv7 puts a 48-bit millisecond count at the front, which gives it a range of about 8,900 years from 1970 and makes the IDs naturally sortable by creation time. Snowflake-style IDs do the same with 41 bits of milliseconds, and pay for it with a range measured in decades.

One trap worth naming

Never store a timestamp in a float32, and be careful with float64 beyond the millisecond range. A 32-bit float has 24 bits of mantissa; at the magnitude of a current Unix timestamp it can only distinguish steps of about 128 seconds. The value is not wrong, it is rounded, which is worse — it looks fine until two events a minute apart get the same timestamp.

VI. Seven Ways It Goes Wrong in Practice

These are the failures that actually show up in bug trackers, roughly in order of frequency.

1. Seconds or milliseconds? The number arrives, you cannot tell from the digits alone, and the wrong choice puts the date in 1970 or in the year 57,000. Sanity check: a plausible current timestamp in seconds is 10 digits; in milliseconds, 13.

2. Local time is not the timestamp. date -d @0 does not print 1970-01-01 00:00:00. It prints midnight UTC expressed in your zone — 1970-01-01 08:00:00 in China, 1969-12-31 19:00:00 in New York. The number is the same; the rendering is local.

3. The timestamp does not contain a time zone. This is not a bug in Unix time, it is the point of it. But it means "2025-02-01 00:00:00" without a zone is not a moment, and any system that stores such strings will eventually disagree with another system about what it meant.

4. Sorting timestamps as text breaks at digit boundaries. When 1000000000 arrived on 9 September 2001, any software that compared timestamps as strings instead of numbers started putting newer items before older ones, because "999999999" sorts after "1000000000". Zero-padding or storing integers fixes it; nothing else does.

5. TIMESTAMP in MySQL. Covered above, but it deserves its own line because it is the most common 2038 exposure in ordinary web applications.

6. Before 1970. Negative values are legal and are often rejected by validators, database column types and API schemas that assume "timestamp = positive integer." Birth dates, historical records and astronomical data all live on the wrong side of the epoch.

7. Subtracting wall clocks. Already mentioned, repeated here because it is the one that produces intermittent, unreproducible production incidents. Use a monotonic clock for durations.

VII. The Timestamps People Celebrate

Unix time has a small culture of round numbers. These are the ones that get noticed.

TimestampDate (UTC)Why it mattered
01970-01-01 00:00:00The epoch itself
−11969-12-31 23:59:59Proof that the range extends backwards
1197310171973-10-17 18:36:57The first timestamp used as a self-describing reference
10000000002001-09-09 01:46:40The first ten-digit timestamp — string sorting breaks here
10737418242004-01-10 13:37:042³⁰ — the "1337" timestamp
12345678902009-02-13 23:31:30Google marked it with a doodle
21474836472038-01-19 03:14:07The last representable second
42949672952106-02-07 06:28:15The unsigned ceiling

The 2001 case is the interesting one, because it is a preview of 2038 in miniature: a number that was perfectly valid, that every system accepted, and that quietly broke a comparison.

VIII. After 2038

The fix is not complicated. A signed 64-bit time_t covers about 292 billion years in each direction — roughly twenty times the age of the universe. There is no second 2038 problem waiting at the end of it, and no plausible software system will ever need to think about the limit again.

Almost every current 64-bit platform has already made the switch. The remaining work is not in the kernel; it is in the databases, the file formats, the firmware and the twenty-year-old devices.

The other rollovers

Two more dates are worth knowing about, because they are less famous and equally real.

2036-02-07 06:28:16 UTC — NTP era 0 ends. The Network Time Protocol counts seconds from 1 January 1900 in an unsigned 32-bit field. It runs out in 2036, and unlike the Unix case there is no room to widen the field without breaking the packet format. The protocol has a defined extension mechanism, but it requires both sides to be updated, which for NTP means every device that has ever been told to sync.

2106-02-07 — the unsigned 32-bit ceiling. Any format that used uint32 for seconds instead of int32 gets 68 extra years. Some file formats and embedded protocols did exactly that, which is why the diagram has two bars instead of one.

What to do about it

If you maintain a system that will still be running in 2038:

  • Audit every place a timestamp is stored in a fixed-width integer, and every database column typed TIMESTAMP.
  • Prefer DATETIME, 64-bit integers, or ISO 8601 strings for anything with a long life.
  • Write a test that sets the system clock past 2038-01-19 and exercises the full read/write path. It is a cheap test and almost nobody has one.
  • Treat any timestamp comparison that is done on strings as a latent bug, not a style preference.

Closing: 1970 Is a Decision

The number 1738368000 looks like a fact about the universe. It is not. It is the result of a 1971 decision to count ticks, a 1972 decision to count seconds instead, and an arbitrary choice of starting line made when the alternative — moving the epoch every couple of years — had become untenable.

Fifty-six years later, that choice is embedded in every filesystem, every database, every log line and every API on the internet. It will outlive the people who made it, the machines it was made on, and probably the leap second that was invented to patch the same underlying problem.

There is something clarifying about that. A timestamp is not time. It is a convention — a very good one, precise enough to be useful and simple enough to fit in a single integer — and like every convention, it has a history, an expiry date, and a maintenance cost.

Knowing where the number comes from is what lets you tell, immediately, whether a value is seconds or milliseconds, whether a date is UTC or local, and whether the system in front of you is going to be fine in 2038.


Need to check a specific value? The timestamp converter handles seconds, milliseconds and ISO 8601 in both directions, with time zone support — useful when you have a number and no idea which unit it is in.

Unix time nowLive
0000000000.000
1970-01-01 00:00:00 UTC · Thursday
seconds since 1970-01-01 00:00:00 UTC