Seconds or milliseconds
Unix timestamps are defined in seconds, and a present-day one is ten digits. JavaScript, Java and several databases use milliseconds instead, giving thirteen digits. Mixing the two is the single most common timestamp bug and it has an obvious signature: a thirteen-digit millisecond value read as seconds lands tens of thousands of years in the future, while a ten-digit seconds value read as milliseconds lands in January 1970.
Digit count is therefore a reliable first check. Ten digits is seconds, thirteen is milliseconds, and sixteen is microseconds, which some databases and tracing systems use. Nineteen digits is nanoseconds, the Go and Kubernetes convention.
The reason this bug survives code review is that both values are plausible integers, and neither produces an error. It surfaces as a date that is wrong by decades, which is easy to spot once you look at it and easy to miss in a field nobody reads.
A timestamp has no timezone
The integer itself is unambiguous: it is a count of seconds from a fixed instant in UTC. Two systems anywhere in the world recording the same moment record the same number. Timezone enters only when the number is formatted for a human.
This means a timestamp shown as a date is always a timestamp plus a timezone choice, and the choice is usually invisible. The same value legitimately displays as 23:30 on Tuesday in London and 18:30 on Tuesday in New York. Neither is wrong, and a bug report saying the timestamp is "off by five hours" is almost always a display-timezone difference rather than a wrong value.
The practical discipline is to store and transmit UTC, convert to local time only for display, and label every displayed time with its zone. A log file with unlabelled local times from servers in several regions cannot be ordered reliably afterwards.
Why store a timestamp rather than a date string
A date string requires a format agreement and is easy to misparse. `01/02/2026` is 1 February in most of the world and 2 January in the United States, and nothing in the string says which. An integer has no such failure mode.
Timestamps also sort and compare as integers, which makes range queries and ordering trivial and fast. Arithmetic is subtraction: the difference between two timestamps is a duration in seconds, with no calendar logic and no month-length edge cases.
The trade-off is legibility, nobody reads 1767225600 at a glance, and the loss of information a timestamp cannot carry. It records an instant, not an intention: a recurring 09:00 meeting in a timezone that observes daylight saving is not a fixed instant, and storing it as one makes it shift by an hour twice a year. Wall-clock intentions need a local time plus a zone identifier, not a timestamp.
ISO 8601, the readable interchange format
ISO 8601 is the format to use when a time has to be both human-readable and unambiguous: `2026-01-01T00:00:00Z`, where the trailing `Z` means UTC. It sorts correctly as text, which is a genuinely useful property, and every mainstream language parses it.
An offset can appear instead of `Z`, as in `+01:00`. An offset is not a timezone: it records what the offset was at that instant but not which zone produced it, so it cannot tell you what the offset will be at some other date. For future events in a specific place, store the zone identifier, `Europe/Oslo`, alongside the local time.
A string with no `Z` and no offset is the dangerous case. It is a local time with no way to know whose local time, and different parsers resolve it differently, some assume UTC, some assume the machine timezone. If a date is off by exactly your offset from UTC, a missing `Z` is the likely cause.
The edges: 2038, leap seconds, negatives
A signed 32-bit integer overflows on 19 January 2038, wrapping to 1901. This is not hypothetical for systems with 32-bit `time_t`, and it already bites today on expiry dates far in the future, a 20-year certificate or a long-lived token issued now can exceed the limit. 64-bit timestamps push the problem past the age of the universe.
Unix time deliberately ignores leap seconds: every day is exactly 86,400 seconds, even the days that really had 86,401. A Unix timestamp is therefore not a true count of elapsed SI seconds since the epoch, which matters for precision timekeeping and for almost nothing else.
Negative timestamps represent dates before 1970 and are valid, but support is inconsistent, some libraries and databases reject or mishandle them. For historical dates, a date type is usually the better choice than a timestamp.