One number, one instant, three units
A Unix timestamp counts the time elapsed since 1970-01-01T00:00:00Z. That number is the most portable way to move an instant between a database, an API and a log line, and it is unreadable to a human. Paste one in and it comes back in UTC and in your own offset at once; type a date and it goes the other way.
The same instant is written three ways in practice: seconds, the classic Unix form most APIs return; milliseconds, what JavaScript and Java hand you; and microseconds, which turns up in tracing and in Postgres internals. All three describe one moment, and only the multiplier changes.
Reading the unit off the size of the number
Nothing in a bare integer says which unit it is, but its magnitude gives it away, so the unit is guessed here and the guess is stated rather than applied silently. Under 11 digits is read as seconds, 12 to 14 digits as milliseconds, and 15 or more as microseconds.
The reason those thresholds work is that the wrong reading is absurd rather than merely inaccurate. Take 1767225600, which is midnight UTC on 1 January 2026: read as milliseconds it becomes 21 January 1970. Take 1767225600000, the same instant in milliseconds: read as seconds it lands in the year 57971. A value that reads as a plausible recent date in one unit is nonsense in the other, and the everyday failure — milliseconds pasted into a seconds field — surfaces as a date in the first weeks of 1970.
There is no such thing as a local timestamp
Because the count starts from a moment defined in UTC, a timestamp carries no zone at all. What varies is the rendering. The value 1767225600 is 2026-01-01T00:00:00Z, and that is 19:00 on 31 December 2025 in New York and 09:00 on 1 January in Tokyo — one instant, three ways of writing it down, none of them a different timestamp.
This is why the offset beside your local reading is the entire difference between the two lines, and why converting a date requires you to say which clock you meant: read 2026-08-15 09:30 as UTC and you get one number, as local time another, the gap being your offset on that date rather than today.
It also explains a common logging bug. A line stamped only with local wall-clock time is ambiguous for one hour every autumn, when 01:30 happens twice. A line stamped with an epoch value never is.
Where the range runs out
A signed 32-bit counter of seconds stops at 2147483647, which is 03:14:07 UTC on 19 January 2038. One second later it wraps to a large negative number, meaning December 1901. That is the 2038 problem, and it is not hypothetical for embedded firmware, old file formats or any schema still holding a date in a 32-bit integer. A value past the limit is flagged here rather than quietly rendered.
The modern answer is a 64-bit counter, which pushes the ceiling past any date worth worrying about. Browsers have their own bound: a JavaScript date holds milliseconds in a double, valid to about ±8.64e15 of them, or 275,760 years either side of 1970. Negative values are perfectly legal and are how pre-1970 dates are written — a birthday in 1965 is simply a timestamp below zero.
1970, milliseconds and leap seconds
My date came back as January 1970. What went wrong?
You almost certainly fed milliseconds to something expecting seconds, which divides your instant by a thousand and lands you a few weeks after the epoch. The reverse mistake sends you tens of thousands of years into the future. Set the unit explicitly here and the reading will tell you which of the two happened.
Should a new system store seconds or milliseconds?
Store whatever your database has a real type for, and be explicit about it in the column name. Seconds are enough for anything a person schedules; milliseconds matter for ordering events and for anything a browser produces. Mixing the two in one table is the failure this tool exists to diagnose.
Do timestamps count leap seconds?
No. POSIX time defines every day as exactly 86,400 seconds, so a leap second is either repeated or absorbed depending on the platform. That keeps epoch arithmetic simple and means the count is not a true elapsed-seconds total — it runs about 29 seconds short, which matters only for TAI conversions.
Is a timestamp of 0 the same as an empty field?
It is not, and treating it as one is a classic bug. Zero is a real instant: midnight UTC on 1 January 1970. Any code using 0 as a sentinel for absent will render it as that date, which is why so many profiles show a join date of 1 January 1970.
Why does my database show a different time than my application?
They are rendering the same stored instant through different session time zones, so compare the epoch values rather than the printed strings. Matching numbers mean the data is fine and only the display convention differs.
How do I get the current timestamp on the command line?
date +%s prints seconds on Linux and macOS, and GNU date will add milliseconds with date +%s%3N. In JavaScript, Date.now() gives milliseconds and Math.floor(Date.now() / 1000) gives seconds.
Is 1970 the epoch for every system?
No, and the alternatives cause dates that are wrong by decades. Windows FILETIME counts 100-nanosecond intervals from 1601, NTP counts seconds from 1900, GPS time from 1980, and Excel serial dates from 1900 on Windows or 1904 in older Mac files. Only the Unix count starts in 1970.
Should I store a future appointment as a timestamp?
Usually not. A meeting at nine in the morning is a promise about a wall clock, and if the zone rules change before the date arrives — governments move daylight saving with a few months of notice — the stored instant is now the wrong hour. Keep the local date, the time and the zone name, and resolve to an instant when you need one.
Can a timestamp be negative?
Yes. Anything before 1970 is a negative count, so −86400 is midnight on 31 December 1969, and a birthday in the 1950s is an ordinary value a few hundred million below zero.