Guide

How to convert a Unix timestamp

A Unix timestamp counts seconds since midnight UTC on 1 January 1970. It is a single integer with no timezone, no format and no ambiguity, which is why nearly every system stores time this way and converts to something human-readable only at the edges.

Converting one is arithmetic, and the mistakes are not in the arithmetic. They are in the unit, the timezone applied at display time, and the assumption that a timestamp describes a moment a person would recognise as that moment.

Open the Unix Timestamp Converter

Use this when


  • A log line or database row has a numeric timestamp you need to read
  • Checking when a JWT was issued or expires
  • Debugging why a scheduled job fired at the wrong local time
  • Comparing times from systems in different timezones
  • Working out how long ago an event recorded in an API response happened

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.

Common problems


Each of these is something that actually happens, with the cause rather than a generic suggestion to check your input.

The date lands in January 1970

Cause
A seconds value was interpreted as milliseconds, making a present-day instant appear close to the epoch.
Fix
Select Seconds for a seconds-based input, or multiply by 1000 when passing it to a milliseconds-based API.

The date is thousands of years in the future

Cause
A millisecond value was interpreted as seconds, effectively multiplying its date offset by 1000.
Fix
Select Milliseconds for that input, or divide by 1000 before passing it to a seconds-based API.

The time is off by a whole number of hours

Cause
A timezone difference at display time, not a wrong timestamp.
Fix
Compare the UTC rendering at both ends. Label displayed times with their zone so the difference is visible rather than inferred.

A parsed date string is off by exactly your UTC offset

Cause
The string had no `Z` and no offset, so the parser guessed a timezone.
Fix
Always include `Z` or an explicit offset in any timestamp you serialise.

A recurring event shifts by an hour twice a year

Cause
A wall-clock intention was stored as a fixed instant. 09:00 local is not a constant timestamp across a daylight-saving change.
Fix
Store the local time plus a zone identifier such as `Europe/Oslo`, and compute the instant per occurrence.

Questions


How do I tell seconds from milliseconds?
Count the digits. A current timestamp is ten digits in seconds and thirteen in milliseconds. Sixteen is microseconds and nineteen is nanoseconds.
Does a Unix timestamp have a timezone?
No. It is a count of seconds from a fixed instant in UTC, identical everywhere. A timezone is applied only when it is formatted for display, which is where apparent discrepancies come from.
What is the 2038 problem?
A signed 32-bit timestamp overflows on 19 January 2038 and wraps to 1901. It already affects far-future dates such as long-lived certificates and tokens. 64-bit timestamps do not have the limit.
Should I store timestamps or date strings?
Timestamps for instants. They are unambiguous, sort as integers and subtract into durations. Use a local time plus a zone identifier for wall-clock intentions such as recurring meetings, which are not fixed instants.