What a Unix timestamp is
A Unix timestamp is a count of seconds elapsed since 00:00:00 UTC on 1 January 1970 — the Unix epoch. That is the entire definition. It is a single integer, and it identifies an absolute instant in time that is the same everywhere on the planet.
= 1,753,027,200 seconds after 1970-01-01T00:00:00Z
= 2025-07-20T16:00:00Z
The design is deliberately minimal. No timezone, no calendar rules, no daylight saving, no locale. Just a number that increments once per second. Every complication people associate with dates — leap years, month lengths, DST transitions, week numbering — is a presentation-layer concern applied when the integer is formatted for a human.
Seconds, milliseconds, or something else
Unix time is conventionally seconds, but plenty of systems use finer units, and mixing them is one of the most common date bugs in production code.
| Unit | Digits (current era) | Used by | If misread as seconds |
|---|---|---|---|
| Seconds | 10 | Unix time(), PHP time(), Python time.time(), most databases | — |
| Milliseconds | 13 | JavaScript Date.now(), Java System.currentTimeMillis(), Kafka | Lands around the year 57,000 |
| Microseconds | 16 | PostgreSQL internals, some tracing systems | Far future, usually overflows |
| Nanoseconds | 19 | Go UnixNano(), InfluxDB | Overflows outright |
The symptom of getting this wrong is unmistakable: a date in 1970 (milliseconds parsed as seconds and divided down, or seconds interpreted as milliseconds) or a date tens of thousands of years in the future. The converter above detects the unit from digit count and tells you what it assumed.
A worked example
Take 1753027200, ten digits, so seconds.
- Whole days: 1,753,027,200 ÷ 86,400 = 20,289 days, remainder 57,600 seconds
- Add the days to the epoch: 1970-01-01 plus 20,289 days = 2025-07-20
- Convert the remainder: 57,600 ÷ 3,600 = 16, with nothing left over
- Result: 2025-07-20T16:00:00Z
Had the division come out exact, with no remainder, the timestamp would land on midnight UTC — 1,752,969,600 is the midnight-UTC value for that same date. Any remainder is simply the time of day: divide by 3,600 for hours, then 60 for minutes. That is genuinely all there is to the conversion. The difficulty in date handling lies entirely in calendars and timezones, never in the epoch arithmetic itself.
The year 2038 problem
Systems storing Unix time in a signed 32-bit integer can represent at most 2,147,483,647 seconds. That limit is reached at 03:14:07 UTC on 19 January 2038. One second later the value overflows to negative and the date wraps to December 1901.
Modern 64-bit systems are unaffected — a signed 64-bit count runs out roughly 292 billion years from now. But embedded devices, older file formats, some database columns and plenty of legacy C code still use 32 bits. The converter flags any timestamp beyond the 32-bit range.
The mirror image is the negative timestamp: dates before 1970 are represented as negative integers. Legal and unambiguous, but many parsers and databases reject them, which is why historical dates are often better stored as calendar dates than epoch values.
Leap seconds and what Unix time ignores
Unix time assumes every day contains exactly 86,400 seconds. Actual UTC does not — leap seconds are occasionally inserted to keep clocks aligned with the Earth's rotation, and 27 have been added since 1972. Unix time simply does not represent them: during a leap second, the timestamp either repeats a value or the system smears the adjustment across several hours.
For almost all application code this is irrelevant. It matters if you are computing precise intervals across a leap second boundary, in which case you want TAI or a monotonic clock rather than wall-clock Unix time.
Reference points
| Timestamp | Date (UTC) | Significance |
|---|---|---|
| 0 | 1970-01-01 00:00:00 | The Unix epoch |
| 1000000000 | 2001-09-09 01:46:40 | One billion seconds |
| 1234567890 | 2009-02-13 23:31:30 | Sequential-digit milestone |
| 1500000000 | 2017-07-14 02:40:00 | 1.5 billion |
| 2000000000 | 2033-05-18 03:33:20 | Two billion |
| 2147483647 | 2038-01-19 03:14:07 | 32-bit signed overflow |
Frequently asked questions
Why does my timestamp show a different time than expected?
What is the difference between UTC and GMT here?
Should I store timestamps or datetime strings?
What is ISO 8601 and why prefer it?
Why does my date-to-timestamp result shift by an hour?
Can timestamps be negative?
How different systems represent time
Knowing which unit and type a system uses saves a great deal of debugging.
| System | Function | Unit |
|---|---|---|
| JavaScript | Date.now() | Milliseconds |
| Python | time.time() | Seconds (float) |
| PHP | time() | Seconds |
| Java | System.currentTimeMillis() | Milliseconds |
| Go | time.Now().Unix() / UnixNano() | Seconds / nanoseconds |
| MySQL | UNIX_TIMESTAMP() | Seconds |
| PostgreSQL | EXTRACT(EPOCH FROM ts) | Seconds (with fraction) |
| Unix shell | date +%s | Seconds |
The JavaScript-to-everything-else boundary is where most bugs occur, because JavaScript is the odd one out in defaulting to milliseconds. A timestamp travelling from a browser to a Python or PHP backend needs dividing by 1,000, and forgetting is the single most common date bug in web development.
Practical rules that prevent time bugs
- Store UTC, display local. Convert only at the presentation layer, never in storage or business logic.
- Use timezone-aware types. PostgreSQL timestamptz rather than timestamp; Python datetime with tzinfo rather than naive.
- Never do date arithmetic on strings. Parse to an instant, compute, then format.
- Do not assume a day is 86,400 seconds when adding to a local time. DST transitions make some local days 23 or 25 hours long, so "same time tomorrow" is a calendar operation, not an addition.
- Use a monotonic clock for durations. Wall-clock time can jump backwards when NTP corrects it, producing negative elapsed times.
- Log in ISO 8601 with an explicit offset. A log line reading 14:32:01 with no zone is close to useless during an incident spanning regions.