Skip to main content

Timestamp Converter

Switch between Unix seconds, milliseconds, and human-readable dates in either direction.

Current Time

Unix Timestamp
1791611213
ISO Format
2026-10-10T05:46:53.363Z

Convert Timestamp

Supported formats:

  • Timestamp: Unix timestamp in seconds (1640995200) or milliseconds (1640995200000)
  • Date/Time: ISO format (2022-01-01T00:00:00Z), readable format (Jan 1, 2022), or any valid date string

Convert epoch values to calendar dates and back. Almost none of the bugs in this area are conversion errors: they are unit errors, range errors, and the assumption that a UTC offset is the same thing as a time zone. This page covers the ones that reach production.

Reading the unit off the magnitude

A bare integer does not say whether it counts seconds, milliseconds, microseconds or nanoseconds, so count the digits. A present-day timestamp is 10 digits in seconds, 13 in milliseconds, 16 in microseconds and 19 in nanoseconds. The seconds form crossed 1,000,000,000 on 9 September 2001 and reaches 2,000,000,000 on 18 May 2033, so the digit-count heuristic stays reliable for a long time yet.

Guessing wrong is loud in one direction and silent in the other. Milliseconds fed to a seconds parser land tens of thousands of years in the future, which somebody notices. Seconds fed to a millisecond parser land a few weeks after the epoch, which often does not get noticed at all: new Date(1750000000) in JavaScript, where the constructor takes milliseconds, returns 21 January 1970 rather than June 2025.

Nanoseconds break JavaScript before they break anything else. Number.MAX_SAFE_INTEGER is 9,007,199,254,740,991, while a nanosecond timestamp today sits near 1.79 × 1018 — about two hundred times larger. Run a Go or Prometheus nanosecond value through JSON.parse and the low digits are silently rounded away; use a string or a BigInt instead.

A specific case worth checking twice: the exp, iat and nbf claims in a JWT are NumericDate values, which RFC 7519 defines as seconds. Writing milliseconds there produces a token that appears valid for the next fifty thousand years, and most libraries will not complain. The JWT decoder renders those claims as dates so the mistake is visible.

The ranges that end sooner than you expect

A signed 32-bit time_t overflows at 03:14:07 UTC on 19 January 2038, and the following second wraps to December 1901. This is still live in embedded firmware, in 32-bit builds, and in binary file formats that reserved four bytes for a timestamp and cannot be widened without breaking readers.

MySQL inherits the limit directly: the documented range of TIMESTAMP is 1970-01-01 00:00:01 to 2038-01-19 03:14:07 UTC, while DATETIME runs from 1000-01-01 to 9999-12-31 but stores a wall-clock value with no zone attached. A thirty-year mortgage maturity date is already outside the TIMESTAMP range today, which is the usual way teams discover this.

Other ceilings sit further out but still exist. Go documents time.Time.UnixNano() as undefined outside the years 1678 to 2262, because int64 nanoseconds cannot represent more. JavaScript's Date is capped at ±8,640,000,000,000,000 milliseconds from the epoch, roughly ±273,790 years, and anything beyond returns Invalid Date instead of throwing.

The floor matters as much as the ceiling. Zero is a real timestamp, not a null, so a row displaying 1970-01-01T00:00:00Z nearly always means an unset field that was serialised anyway. By the same logic, 0001-01-01 is .NET's DateTime.MinValue and Go's zero time.Time — both indicate a struct that was never populated.

An offset is not a time zone

-05:00 is an offset: a fixed number of minutes from UTC, valid at one instant. America/New_York is a time zone: a body of rules stating which offset applies when. The same -05:00 describes New York in January, Bogotá all year, and Lima all year, and those three do not stay in agreement.

Storing an offset and calling it the user's time zone means their 09:00 reminder fires at 10:00 after the March transition. Zone rules are political and change with very little notice: Brazil abolished daylight saving in 2019, Mexico ended it for most of the country in 2022, Egypt reinstated it in 2023, and Lebanon postponed its 2023 transition with a few days of warning. The IANA time zone database ships several releases a year, and a runtime pinned to an old copy computes the wrong offset for anything after the change.

The rule that follows is about tense. For something that already happened, store the instant — an epoch value or a UTC timestamp — because the offset is now history and cannot be revised. For something scheduled in the future, store the local wall time together with the IANA zone name, because if the zone's rules change before the date arrives, the correct instant changes with them and a pre-computed UTC value becomes wrong.

PostgreSQL is a useful illustration: timestamptz stores a UTC instant in eight bytes and discards whatever zone you supplied, and timestamp stores a wall clock with no zone at all. Despite the name, neither type stores a time zone. In the browser, Intl.DateTimeFormat().resolvedOptions().timeZone returns the IANA identifier you want, while getTimezoneOffset() returns a single offset at a single instant.

ISO 8601, RFC 3339, and what Date.parse actually does

RFC 3339 is a profile of ISO 8601, not a synonym for it. RFC 3339 requires a complete date and time with an offset, so 2026-03-15T09:00:00Z qualifies while 2026-03-15 does not. ISO 8601 additionally permits ordinal dates (2026-074), week dates (2026-W11-7), the basic format without separators (20260315T090000Z), durations such as P3Y6M4D, and intervals — none of which an RFC 3339 parser is obliged to accept.

The differences run both ways. RFC 3339 distinguishes +00:00, meaning UTC, from -00:00, meaning the offset is unknown; ISO 8601 has no negative zero offset. RFC 3339 also allows 60 in the seconds field so a leap second can be written down, which most language parsers will reject outright.

ECMAScript carries an inconsistency that produces off-by-one-day bugs every year. A date-only string is parsed as UTC, but a date-and-time string with no offset is parsed as local time. Date.parse("2026-03-15") and Date.parse("2026-03-15T00:00:00") therefore differ by your offset, and west of Greenwich the first one renders as 14 March. Append Z or an explicit offset to every string you parse.

RFC 9557, published in 2024, extends RFC 3339 with a bracketed zone suffix — 2026-03-15T09:00:00-04:00[America/New_York] — so a single string can finally carry both the instant and the zone that produced it. It is the serialisation format for the Temporal API's ZonedDateTime, and it is the cleanest answer to the future-event problem above.

Unix time skips the leap seconds that actually happened

POSIX defines seconds since the epoch with a formula that assumes exactly 86,400 seconds in every day. That is a definition rather than an approximation: leap seconds are not counted, by design, so that converting an epoch value to a calendar date never requires a lookup table.

Twenty-seven leap seconds have been inserted since 1972, the most recent on 31 December 2016, which is why TAI currently runs 37 seconds ahead of UTC. Subtract two epoch timestamps that straddle those insertions and the elapsed physical time is short by up to 27 seconds. For most applications that is irrelevant; for anything reconciling against an instrument clock or a GPS timescale it is not.

Because the daily count has to stay at 86,400, something has to give at the moment of insertion — either a second is repeated or the clock steps. Google and AWS avoid the discontinuity by smearing the extra second across a 24-hour window, which means their clocks are deliberately up to half a second away from true UTC for a day and will disagree with an unsmeared NTP source during it. Mixing smeared and unsmeared time sources inside one cluster produces exactly the kind of negative interval that crashes a duration calculation.

In 2022 the CGPM resolved to stop inserting leap seconds by 2035, so this becomes a legacy concern rather than a recurring one — but timestamps already recorded keep their gaps. The durable lesson is simpler: never measure elapsed time by subtracting wall-clock values. Use a monotonic clock such as performance.now(), CLOCK_MONOTONIC or System.nanoTime(), which is immune to leap seconds, NTP corrections, and a user resetting the system clock mid-operation.

ThenCatch is a free developer toolkit. Learn more about us.