How Unix time works and how to turn a timestamp into a readable date
What a Unix timestamp is, how to tell seconds from milliseconds apart, why it carries no time zone, and how to convert one in JavaScript, Python and SQL.
Published September 27, 2026 · By the CalcOak team
A Unix timestamp is a single integer that names one instant: the number of seconds that have passed since midnight at the start of 1 January 1970 in UTC. Because it is just a count, it is easy to store, compare and subtract, and it means the same moment on every machine in the world. This guide covers the rules you need to read one by eye, convert it correctly, and avoid the handful of mistakes that produce dates that are off by hours, by a factor of a thousand, or by 68 years.
What the number actually counts
The starting point, called the epoch, is 1970-01-01T00:00:00Z. Zero is that instant, 86,400 is one day later, and negative values reach back before 1970: -86400 is 31 December 1969. Every day is treated as exactly 86,400 seconds long, so converting a timestamp is plain arithmetic with no calendar tables involved until you translate the day count into a year, month and day.
Two things are deliberately left out. The first is any notion of a time zone; the count is anchored to UTC and nothing else. The second is leap seconds. Twenty-seven of them have been inserted since 1972, but Unix time pretends they did not happen, so a timestamp and a true UTC clock can disagree by one second during the leap itself. For anything short of satellite navigation this does not matter, and it is why Date, time.time() and UNIX_TIMESTAMP() all agree with each other exactly.
Seconds, milliseconds or microseconds
Different systems store the same instant at different resolutions, and nothing in the number tells you which one you are looking at except its size. For any date from September 2001 to November 2286, the digit count is a reliable signal.
| Unit | Digits today | Example for the same instant | Typical source |
|---|---|---|---|
| Seconds | 10 | 1700000000 | Unix tools, PHP, Python time.time() (integer part), most APIs |
| Milliseconds | 13 | 1700000000000 | JavaScript Date.now(), Java, Kafka, MongoDB |
| Microseconds | 16 | 1700000000000000 | PostgreSQL internals, some logging libraries |
| Nanoseconds | 19 | 1700000000000000000 | Go UnixNano(), Prometheus, high-resolution tracing |
If you see a 13-digit value and treat it as seconds, you will get a date some 50,000 years in the future; feed a 10-digit value to a function expecting milliseconds and you land in January 1970, a few hours after the epoch. The Unix timestamp converter guesses the unit from the digit count and shows the guess next to the input, which is a quick way to confirm which one a log file is using.
Why there is no time zone, and how local time appears
A timestamp identifies an instant, not a clock reading. The clock reading you display depends on the zone you choose to render it in. Converting to local time means two steps: turn the count into a UTC date, then apply the offset for the target zone at that particular date. The second step is the one that changes with daylight saving, so the same zone can subtract five hours in January and four in July.
This is also the rule to remember when you compare timestamps with a scheduler. Cron expressions are written in wall-clock time in whatever zone the server runs in, usually UTC on cloud platforms. If a job is set to fire at 02:30 and you want to know the timestamp of the next run, you need the zone, and the cron expression builder lists upcoming run times for the zone you pick so you can check.
Worked example: 1,800,000,000
Take the round number 1,800,000,000 seconds and convert it by hand.
- Divide by 86,400 to get whole days: 1,800,000,000 ÷ 86,400 = 20,833 days with a remainder of 28,800 seconds.
- Convert the remainder: 28,800 ÷ 3,600 = 8 hours exactly, so the time of day is 08:00:00.
- Count 20,833 days forward from 1 January 1970. Fifty-seven years take you to 1 January 2027 (that span contains 14 leap days, giving 20,819 days), leaving 14 more days.
- Fourteen days after 1 January is 15 January.
Result: 2027-01-15T08:00:00Z. In Node, new Date(1800000000 * 1000).toISOString() prints exactly that. Rendered in New York the same instant reads 03:00 on 15 January; in Tokyo it is 17:00. The instant did not change, only the label.
Converting in code
The pattern is the same everywhere: get the number, mind the unit, and be explicit about UTC.
JavaScript works in milliseconds, so multiply seconds by 1,000 on the way in and divide on the way out:
const d = new Date(1800000000 * 1000);
d.toISOString(); // "2027-01-15T08:00:00.000Z"
Math.floor(Date.now() / 1000); // current time in seconds
Python’s standard library expects seconds and, unless told otherwise, gives you local time. Ask for UTC by name:
from datetime import datetime, timezone
datetime.fromtimestamp(1800000000, tz=timezone.utc)
# 2027-01-15 08:00:00+00:00
int(datetime.now(timezone.utc).timestamp())
In SQL the function names differ by engine. MySQL uses FROM_UNIXTIME, which returns a value in the session time zone, and UNIX_TIMESTAMP for the reverse. PostgreSQL uses to_timestamp and extracts the epoch from a timestamp:
-- PostgreSQL
SELECT to_timestamp(1800000000) AT TIME ZONE 'UTC';
SELECT extract(epoch FROM now());
-- MySQL
SELECT FROM_UNIXTIME(1800000000);
SELECT UNIX_TIMESTAMP(NOW());
Whichever language you use, store timestamps as integers or as UTC datetimes, and convert to a local zone only at the point of display.
The 2038 problem
Many older systems keep the count in a signed 32-bit integer, whose largest value is 2,147,483,647. That corresponds to 19 January 2038 at 03:14:07 UTC. One second later the integer overflows to -2,147,483,648, and the date jumps back to 13 December 1901. Modern operating systems, languages and databases use 64-bit values that will not overflow for about 292 billion years, so the risk sits in embedded controllers, old binary file formats, database columns declared as a 32-bit INT, and any protocol that fixes the field width. If you write expiry dates or certificate lifetimes decades ahead, check that every hop along the way is 64-bit.
Common mistakes
Watch for these patterns; they account for almost every wrong date traced back to a timestamp.
- Off by a factor of 1,000: mixing seconds and milliseconds. Count the digits before you convert.
- Wrong by a few hours: formatting in the server’s local zone when you meant UTC, or the other way round.
- Wrong by exactly one hour twice a year: applying a fixed offset instead of a zone name, which ignores daylight saving.
- A date-only string such as “2027-01-15” being parsed as local midnight in one place and UTC midnight in another. Always attach a time and a zone, or use a timestamp.
- Dates in 1901 or 1970: an overflow or a zero default, respectively, not a real event.
- Leap seconds counted by hand. Unix time ignores them, so do not add 27 to anything.