formaterTools Logo Formater Tools

Unix Timestamps Explained: Seconds, Milliseconds, and the Year 2038 Problem

A Unix timestamp looks like the least ambiguous thing in software - it's just a number. No timezone string to misparse, no format string to get wrong, no locale differences between "March 4th" and "4th of March." And yet timestamp bugs are some of the most persistent, recurring mistakes in programming, precisely because the number being unambiguous doesn't mean everyone agrees on what unit it's counting, or protects you from a hard numerical ceiling baked into decades of legacy code.

What the Number Actually Counts

A Unix timestamp is the number of seconds that have elapsed since midnight, January 1, 1970, UTC - a moment universally referred to as "the epoch." That's the entire definition. There's no calendar logic baked into the number itself, no leap-year handling to think about, no daylight saving adjustment - it's a single running count of elapsed seconds, which is exactly why it's so convenient for computers to store, compare, and sort: subtracting one timestamp from another gives you an elapsed duration with no calendar arithmetic required.

The Seconds-vs-Milliseconds Trap

The single most common timestamp bug isn't a timezone mistake at all - it's mixing up units. Most Unix tools, PHP's time(), and most server logs express timestamps in seconds. JavaScript's Date.now(), by contrast, returns milliseconds since the epoch - a deliberate choice for sub-second timing precision in the browser. Feed a millisecond value into code that expects seconds and you get a date roughly 1000 times too far in the future; feed a seconds value into code expecting milliseconds and you get a date collapsed down to sometime in early 1970, since the number is 1000 times too small to represent a modern date in milliseconds.

There's a quick visual tell: a seconds-based timestamp for any date between the 2000s and the 2030s is 10 digits long (1705353600), while the equivalent millisecond timestamp is 13 digits (1705353600000). The Epoch to Date Converter on this site automates that check with a single numeric threshold: any value below 10,000,000,000 is treated as seconds and multiplied by 1000 before conversion; anything at or above that is assumed to already be milliseconds. That threshold corresponds to roughly the year 2286, so it correctly auto-detects any realistic modern timestamp for the next 260 years without you having to count digits yourself.

That heuristic does have one genuine edge case worth knowing, precisely because it illustrates the seconds-vs- milliseconds confusion from the opposite direction. A millisecond timestamp very close to the epoch itself - say, 5000000, which as milliseconds represents January 1, 1970, 01:23:20 UTC, barely an hour and a half after the epoch - is numerically small enough to fall under the 10-billion threshold and get mistakenly multiplied by 1000, landing the converted date somewhere in 1970 turned into 1970-plus-a-thousand-times-the-value instead of the intended near-epoch moment. In practice this only affects timestamps from the first few months after the epoch, which essentially never show up in real logs, APIs, or databases - nobody's production system is emitting millisecond timestamps from January 1970 - but it's a clean demonstration of why a purely numeric heuristic for unit detection can never be airtight: the number alone genuinely is ambiguous at the boundary, and any auto-detection scheme has to pick a threshold and accept that some theoretical input near the edges will be read the wrong way.

"Wrong Timezone" Bugs Aren't the Timestamp's Fault

A Unix timestamp itself has no timezone - it represents one single, universal instant, the same instant no matter where on Earth you read it. There's no such thing as "this timestamp in Pacific time" versus "this timestamp in UTC"; the number is identical either way. What varies by timezone is entirely the display step - converting that raw instant into a human-readable string like "Tuesday, 3:00 PM." When an app shows a user the wrong time, the timestamp was correct all along; the formatting code applied the wrong timezone offset when rendering it, which is a completely different bug living in a completely different piece of code.

That distinction is baked directly into how the converter above is built. Its "UTC Components" breakdown - year, month, day, hour, day-of-week - is computed exclusively with getUTC*() methods, so it always shows the same values regardless of what timezone your browser is set to; a separate "Local Time" box shows the same instant reformatted for your machine's timezone instead. Both boxes describe the identical moment - that's not a bug if they display different clock times, it's the entire point of having both. The reverse direction has its own timezone wrinkle worth knowing: the "Date to Epoch" input on that page is a plain HTML datetime-local field with no timezone selector, so whatever date and time you type is interpreted in your browser's local timezone by default - if you need the epoch value for a specific UTC instant rather than your own local time, you have to do that conversion yourself before typing it in.

The Year 2038 Problem Is Still a Real, Unsolved Legacy Issue

Many older systems store Unix timestamps as a signed 32-bit integer - a data type that can represent whole numbers only up to 2,147,483,647 before it overflows. Counting in seconds from the 1970 epoch, that ceiling is reached at exactly 03:14:07 UTC on January 19, 2038. The instant after that, a system still using a 32-bit signed timestamp doesn't gracefully cap out - the value wraps around to a large negative number, which most software then interprets as a date back in December 1901, since a negative Unix timestamp represents a moment before the epoch.

This isn't a historical curiosity or an already-patched issue - it's specifically a problem for any code path still using a 32-bit time type, and there's a surprising amount of that still running: older embedded systems, firmware, industrial control systems, some database column types, and 32-bit builds of older software that never got recompiled against a 64-bit time representation. Modern 64-bit systems and most contemporary languages have already moved to 64-bit timestamps, which push the overflow date out past the point the sun is expected to become a red giant - effectively solved for new code. The risk is concentrated entirely in long-lived legacy systems that are hard to update or replace, which is exactly the profile of software that tends to still be running critical infrastructure well past its expected lifetime. The overflow date being a specific, known number - January 19, 2038 - rather than a vague future problem is exactly why it's still worth taking seriously today instead of filing it away as already solved.

Where This Comes Up in Practice

In day-to-day debugging, timestamps show up constantly as raw integers you need to make sense of quickly: reading a created_at or exp field straight out of a JSON API response or a decoded JWT payload, figuring out whether a log line's timestamp column is seconds or milliseconds before writing a query against it, or sanity-checking whether a session or token expiry has already passed. Pasting the raw number into the Epoch to Date Converter turns that guesswork into an immediate, readable answer in both GMT and your local time, with the unit-detection caveats above in mind for the rare edge cases where the heuristic needs a human double-check.