FlowingDev

Epoch Time, Explained: The Clock That Only Ticks Forward

Learn what Unix time (or Epoch time) is: the number of seconds elapsed since January 1st, 1970, used by computers to track time universally.

Try the tool: Epoch Converter

In one sentence

Epoch time is a computer's way of tracking time as a single, ever-increasing number: the total seconds that have passed since midnight UTC on January 1st, 1970.

The problem it solves

Humans and time have a complicated relationship. We have timezones, daylight saving, and formats like MM/DD/YYYY vs. DD/MM/YYYY. We write "October 8th, 2024 at 3:00 PM," but that means something different in Tokyo than it does in Toronto. It’s a mess of ambiguity.

Computers, on the other hand, despise ambiguity. They need a single, universal, mathematically simple way to represent a moment in time. Trying to do math with "October 8th" is a nightmare. But doing math with a plain old number? That's what computers are built for.

This is the problem Unix time (also called Epoch time or POSIX time) was created to solve. Back in the early days of the Unix operating system in the 1970s, its creators needed a straightforward timekeeping system. They decided to pick an arbitrary starting point—an "epoch"—and just... count.

The chosen epoch was 00:00:00 UTC, January 1, 1970. Why then? It was a nice, round number, and it was recent enough for the technology of the day.

From that moment on, every passing second increments a universal counter. So instead of a computer having to parse "3:00 PM on October 8, 2024, in Toronto (which is EDT)," it can just store the number 1728409200. That number represents that exact moment in time, everywhere on Earth, simultaneously. No timezones, no formats, no "is it AM or PM?". Just a number. Problem solved.

How it works under the hood

At its heart, the concept is dead simple. But like all things in tech, the devil is in the details.

The Epoch and the Unit

The whole system is built on two ideas:

  1. The Starting Point (Epoch): This is fixed at 1970-01-01T00:00:00Z. The Z stands for Zulu, a military and aviation term for UTC (Coordinated Universal Time). In the world of Epoch time, this moment is simply 0.
  2. The Unit of Measurement: The standard, official unit is the second.

So, the timestamp 1 represents 1970-01-01T00:00:01Z. The timestamp for the start of the next day, 1970-01-02T00:00:00Z, is 86400 (because there are 60 seconds * 60 minutes * 24 hours = 86,400 seconds in a day).

// A date far in the future
const humanDate = new Date('2035-10-26T10:00:00Z');

// Its corresponding Epoch timestamp in seconds
const epochTimestamp = 2071754400;

When your computer shows you this timestamp as a local time, it’s doing a conversion behind the scenes. It takes the universal UTC timestamp and applies your system's timezone offset to display it in a way that makes sense to you. The underlying number, however, remains pure and universal.

Variations: Milliseconds, Microseconds, Nanoseconds

Sometimes, you need to measure things that happen faster than a second. For this, systems use more precise versions of the Epoch timestamp. The principle is the same, but the unit changes.

Unit Example Value (for the same moment) Common Digit Count Typical Use Case
Seconds 1728409200 10 The POSIX standard; APIs, databases.
Milliseconds 1728409200123 13 JavaScript (Date.now()), modern APIs.
Microseconds 1728409200123456 16 High-performance systems, some databases.
Nanoseconds 1728409200123456789 19 Scientific computing, Go language.

This is the number one source of bugs when working with timestamps. If a system gives you a 13-digit number and you treat it as seconds, you're trying to calculate a date thousands of years in the future. Always check the docs or look at the number of digits to know what you're dealing with.

The "Year 2038 Problem"

Here's a classic piece of computer lore. Many early systems, to save precious memory, stored the Epoch timestamp as a 32-bit signed integer.

A "bit" is a 1 or a 0. "32-bit" means you have 32 slots for 1s and 0s. "Signed" means one of those bits is used to indicate if the number is positive or negative. This leaves 31 bits for the number itself, which can represent a maximum value of 2^31 - 1, or 2,147,483,647.

What happens when the number of seconds since 1970 hits that limit? It will happen on Tuesday, January 19, 2038, at 03:14:07 UTC. At the very next second, the integer will overflow. Like a car's odometer rolling over from 999999 to 000000, the 32-bit timestamp will wrap around to its most negative value (-2,147,483,648). This corresponds to a date in December 1901.

For any 32-bit system that hasn't been patched, this will cause chronological chaos. Think of embedded systems in older cars, industrial equipment, or network routers.

The fix? Use a 64-bit integer. A 64-bit integer can store a number so mind-bogglingly large that it won't overflow for about 292 billion years. By then, the sun will have long since expanded and swallowed the Earth, so we can probably call it a permanent solution. Most modern operating systems and languages have already made the switch.

Leap Seconds: The Fly in the Ointment

The Earth's rotation isn't perfectly regular; it's slowing down slightly. To keep our atomic clocks (which are super regular) in sync with the solar day, international bodies occasionally add a "leap second" to the calendar. This means a minute might have 61 seconds (e.g., 23:59:60).

So how does Epoch time handle this? It doesn't.

Officially, the POSIX standard ignores leap seconds. It assumes every day has exactly 86,400 seconds. When a leap second occurs, systems handle it in a few ways, but a common one is to effectively repeat the previous second. The timestamp for 23:59:59 might occur twice. This maintains the continuous, unbroken count of seconds but means that a Unix timestamp doesn't always map perfectly back to real-world UTC. For 99.9% of applications, this is a non-issue. For high-frequency trading or scientific measurement, it's a huge headache.

Real-world stories

The Case of the Time-Traveling Cache

A dev team was launching a new feature backed by a caching system. To improve performance, they'd cache data for one hour. The logic was simple: expiration_time = current_time() + 3600. They deployed the code across their server fleet.

Suddenly, weird bugs flooded in. Data was disappearing from the cache almost instantly. After hours of frantic debugging, they found the culprit. One of the new servers in the fleet had its system clock set incorrectly—it was running five minutes behind all the other servers.

When a user's request hit a correct server, it would cache the data with an expiration time of, say, 1678886400 (12:00 PM). If a subsequent request for that same data hit the "slow" server, its clock read 11:55 AM. When it checked the cache, it saw an expiration time of 12:00 PM and correctly served the data. But if the first request hit the slow server, it would set an expiration time of 1678882800 (11:00 AM, its own time, plus one hour which is 12:00 PM). But it was 11:55 AM. Wait, that's not right.

Let's try again. Server A's time is 12:00. It sets a cache expiration for 12:00 + 1 hour = 13:00. Server B's clock is slow; it thinks it's 11:55. When Server B needs to write to the cache, it sets an expiration of 11:55 + 1 hour = 12:55. Now, if Server A sees an item that expires at 12:55, it will think it has 55 minutes left, while Server B thinks it has a full hour. This leads to inconsistencies.

The real chaos starts when the clocks are way off. If Server B's clock was set an hour behind (thinks it's 11:00 when it's 12:00), it would set an expiration time of 11:00 + 1 hour = 12:00. From Server A's perspective, this new cache item expires the very second it was created. The data effectively vanished.

Lesson: Unix timestamps are absolute, but they are generated from system clocks that might not be. In distributed systems, keeping clocks synchronized (usually with Network Time Protocol, or NTP) is not just good practice; it's critical.

The API That Spoke in Milliseconds

A frontend developer was building a dashboard to display user activity. The backend API provided a last_login field with a timestamp, like 1678886400. The developer used a JavaScript library to show this: new Date(1678886400).

The result was bizarre. Every user's last login was displayed as "January 20, 1970." What was going on?

The developer spent an hour blaming the library, their code, and the phase of the moon. Finally, they tried integrating a different endpoint from the same API. This time, the timestamp was 1678886400123. It had 13 digits! Suddenly, it clicked. JavaScript's Date object constructor expects a timestamp in milliseconds, not seconds.

The backend was sending a standard 10-digit second-based timestamp. The frontend was interpreting 1,678,886,400 as the number of milliseconds since the epoch, which is a date just a few weeks after the epoch began in 1970. The fix was simple: new Date(1678886400 * 1000).

Lesson: Always, always, always verify the precision of a timestamp. A difference of three zeroes is the difference between today and 1970.

Common mistakes and traps

  • Forgetting the timezone. Unix timestamps are always, without exception, in UTC. When you convert one into a human-readable date, your programming language or tool will almost always use your computer's local timezone. This can cause massive confusion if you don't account for it. 1728409200 is one exact moment in time, but it will display as 06:00 in New York and 19:00 in Tokyo. The number is the truth; the display is an interpretation.

  • Mixing up seconds and milliseconds. This is the classic "off-by-1000" error. It's the single most common bug when working with timestamps. As a rule of thumb: 10 digits is seconds, 13 digits is milliseconds. If you see something else, be very suspicious.

  • Ignoring the 2038 problem. If you're building a standard web app in a modern language, you're probably fine. But if you're writing C code for an embedded IoT device, a car's infotainment system, or maintaining a legacy 32-bit system, the Y2038 bug is a very real, ticking time bomb.

  • Using ambiguous strings to generate timestamps. Creating a timestamp from a string like "March 15, 2025 10:00 PM" is asking for trouble. Is that in your local timezone? The server's timezone? UTC? Always generate timestamps from timezone-aware objects or use explicit UTC strings (like the ISO 8601 format: 2025-03-15T22:00:00Z).

Why it belongs on your radar

You simply cannot be a modern developer and not understand Epoch time. It's the lingua franca of time in computing. You will encounter it everywhere:

  • APIs: JSON payloads use it constantly for fields like createdAt, updatedAt, and expires_at.
  • JWTs: The exp (expiration), iat (issued at), and nbf (not before) claims are all standard Unix timestamps.
  • Databases: Storing time as a single integer is often more efficient for indexing and storage than using a complex DATETIME type.
  • Log Files: Using numeric timestamps makes it trivial to correlate events across dozens of different servers and services, even if they're in different timezones.
  • File Systems: Most file systems store file creation and modification dates as Unix timestamps.

Understanding how it works lets you debug a whole class of tricky, time-related bugs with confidence. It allows you to cut through the messy human world of timezones and calendars and think about time the way a computer does: as a simple, orderly line of numbers.

Go deeper

Theory done. Time to get your hands dirty — 100% in your browser.

Try the tool: Epoch Converter