Unix timestamps: seconds, milliseconds, time zones and 2038
A practical guide to epoch time for people who read logs and APIs. Every conversion below can be repeated in the live Timestamp Converter.
What the number counts
A Unix timestamp is the number of seconds since the epoch, 1970-01-01 00:00:00 UTC. POSIX defines it with a fixed formula in which every day is exactly 86,400 seconds long. That has a consequence people rarely notice: leap seconds are not counted. A leap second was inserted at the end of 2016, yet the difference between the timestamps for 2016-12-31T00:00:00Z and 2017-01-01T00:00:00Z is still exactly 86400. For logs and databases this is a feature. Any timestamp maps to one calendar date and time with simple arithmetic, and you never need a leap second table.
A timestamp has no time zone. 1791462013 is the same instant in Tokyo and in São Paulo. Time zones only appear when you turn the number into a wall-clock reading for a person.
Seconds or milliseconds?
Unix tools, PostgreSQL's to_timestamp, and most HTTP APIs use seconds. JavaScript's Date.now() and Java's System.currentTimeMillis() use milliseconds. Mixing them up is the most common timestamp bug, and the digit count is the quickest way to tell them apart:
- Seconds have 10 digits for every date from 2001-09-09T01:46:40Z (
1000000000) to 2286-11-20T17:46:39Z (9999999999). - Milliseconds have 13 digits over the same span.
Fieldkit's converter uses exactly that rule: an integer with 13 or more digits is read as milliseconds, anything shorter as seconds. The mistake shows up as an absurd date. Read the millisecond value 1791462013482 as seconds and you land tens of thousands of years in the future. Read the seconds value 1791462013 as milliseconds and you get 1970-01-21T17:37:42.013Z, three weeks after the epoch. If a dashboard shows January 1970, look for a seconds value being passed to a function that expects milliseconds.
Some systems use even finer units. Python's time.time_ns() and Go's UnixNano() return nanoseconds, which have 19 digits today, and some databases and tracing tools log microseconds, with 16. The converter only understands seconds and milliseconds. A 16-digit microsecond value is read as milliseconds and lands in the year 58739, and a 19-digit nanosecond value is reported as out of range. Drop the last three or six digits first to get milliseconds.
Worked example: matching a bug report to a server log
A user in São Paulo writes: "The export failed at 9:20 this morning, October 8." The server logs in epoch milliseconds, and the closest error line reads ts=1791462013482 level=error msg="export timeout".
- Turn the user's wall-clock time into an instant. In October 2026, São Paulo is at UTC-03:00, so enter
2026-10-08T09:20:00-03:00in the converter. The output shows Unix seconds1791462000and ISO 8601 (UTC)2026-10-08T12:20:00.000Z. - Convert the log value. Enter
1791462013482. Input type reads Unix (ms) and the ISO line is2026-10-08T12:20:13.482Z. - Compare in UTC. The error happened 13.482 seconds after the time the user remembered. That is the same event.
The step that prevents mistakes is writing the offset explicitly. If you type 2026-10-08T09:20:00 without it, the browser reads the value in your time zone, which is only correct if you happen to be in São Paulo too.
Offsets, time zones, and ISO 8601
RFC 3339, the internet profile of ISO 8601, writes an instant as a date, a T, a time, and an offset: 2026-10-08T12:20:13Z or 2026-10-08T09:20:13-03:00. Both strings name the same moment. Z means UTC. RFC 3339 also defines -00:00 for "the time is in UTC, but the local offset is unknown", which is rare in practice but legal.
An offset is not a time zone. America/New_York is a zone; its offset is -05:00 in January and -04:00 in July. When you store a future event that must happen at a local wall-clock time, such as a 9:00 meeting next March, store the zone name next to the local time, because the offset that will apply can change with daylight saving rules. For past events, a UTC timestamp or an RFC 3339 string with an offset is enough.
How JavaScript parses date strings
The converter passes anything that is not an integer to Date.parse, so it inherits JavaScript's rules. The ECMAScript specification has one rule that catches almost everyone:
- A date-only string such as
2026-10-08is read as UTC. The converter shows2026-10-08T00:00:00.000Z. In São Paulo, the Local line therefore reads Wed Oct 07 2026 21:00, the evening before. - A date-time string without an offset, such as
2026-10-08T09:20:00, is read as local time. - A string with
Zor an offset is unambiguous.
Formats outside the standard one, like 10/08/2026 or 2026-10-08 09:20:00 with a space, fall back to implementation-specific parsing. Some engines accept them and some do not, and they may not agree on the result. In code that runs in many browsers, either use the full ISO form with an offset or parse the parts yourself.
JavaScript dates also have a range: plus or minus 8.64 × 1015 milliseconds around the epoch, which ends at +275760-09-13T00:00:00.000Z. Values beyond that make the converter report that the timestamp is out of range.
The year 2038 problem
Many older systems store Unix time in a signed 32-bit integer. The largest value it can hold is 2147483647, which the converter shows as 2038-01-19T03:14:07.000Z. One second later, a 32-bit counter wraps to -2147483648, and that value converts to 1901-12-13T20:45:52.000Z. Software that still keeps time in 32 bits will jump back more than a century.
Most current 64-bit operating systems and languages already use 64-bit time, so the risk sits in specific places: embedded devices, old file formats, database columns declared as a 32-bit INT, and binary protocols with a fixed 4-byte time field. A quick test is to store 2147483648 and read it back. If it comes back negative or as a date in 1901, that path needs a wider type.
Getting the current timestamp
- JavaScript:
Math.floor(Date.now() / 1000)for seconds,Date.now()for milliseconds. - Python:
int(time.time()). To go back,datetime.fromtimestamp(1791462013, tz=timezone.utc)returns2026-10-08 12:20:13+00:00. - GNU
dateon Linux:date +%s, anddate -u -d @1791462013to convert back. - BSD
dateon macOS:date -u -r 1791462013.
The converter's Now button fills in the current time in seconds and converts it, which is handy when you need a value for a test fixture.
Habits that avoid most timestamp bugs
- Store instants in UTC, as an integer or as an RFC 3339 string with
Z. - Put the unit in the field name:
created_at_msorexpires_s. - Convert to local time only at display time, with an explicit zone.
- Never parse a date-time string without an offset unless you know which zone it was written in.
- When two systems disagree about a time, convert both values to UTC before comparing.
Open the live tool
Open Timestamp Converter Related reading: all guides · FAQ.