Skip to content
ansezz.

▸ Free tool

Epoch Converter.

Paste a Unix timestamp and read it back as UTC, your local time, ISO 8601, RFC 2822 and plain English — or go the other way and turn a date into epoch seconds. Nothing leaves your browser.

▸ Seconds or milliseconds?

The unit is guessed from the magnitude: 10 digits reads as seconds, 13 as milliseconds, 16 as microseconds, 19 as nanoseconds. Override it with the dropdown when the guess is wrong.

▸ Current Unix time

ms ·

Your browser reports ·

Timestamp → date

▸ That instant

Relative

UTC

Your local time

ISO 8601 (UTC)

ISO 8601 (local offset)

RFC 2822

Day of week

Day of year / ISO week

Normalised

Date → timestamp

▸ Epoch value

Seconds

Milliseconds

ISO 8601 (UTC)

Same instant, other side

Seconds or milliseconds — the bug that eats a sprint

Unix time is one number: how many seconds have elapsed since 1970-01-01T00:00:00Z, ignoring leap seconds. The trouble is that half the ecosystem decided seconds were too coarse. date +%s, time() in C and PHP, and Postgres EXTRACT(EPOCH FROM ts) give you seconds. Date.now() in JavaScript and System.currentTimeMillis() in Java give you milliseconds. Go's UnixNano() and Prometheus give you nanoseconds. Nothing in the value says which.

So the classic failure is silent: pass milliseconds to a seconds API and your record lands in the year 55,840; pass seconds to a milliseconds API and every row in your table says 20 January 1970. Neither throws. Both pass code review. You find out when a customer asks why their invoice is dated before the moon landing.

The fix is boring and it works: name the variable expiresAtMs, not expiresAt. Put the unit in the column name, the JSON key, and the function signature. This converter counts digits for you, but a digit count is a guess — your schema shouldn't need one.

  Seconds Millis Micros Nanos
Digits today 10 13 16 19
Typical source date +%s Date.now() time.time_ns()/1e3 UnixNano()
1700000000 means 2023-11-14 1970-01-20 1970-01-01 1970-01-01
Fits in int32 until 2038-01-19 1970-01-25 1970-01-01 1970-01-01

2038 is an integer overflow, not a calendar quirk

Historic C implementations typed time_t as a signed 32-bit integer. The largest value it holds is 2,147,483,647 — which as a Unix timestamp is 03:14:07 UTC on 19 January 2038. Add one second and the sign bit flips: the value becomes −2,147,483,648 and the date reads 13 December 1901. Nothing crashes. Sorting inverts, TTLs go negative, and cache entries look infinitely stale.

Y2K was a display problem — two digits instead of four. This is an arithmetic one, which makes it harder to paper over. 64-bit builds of Linux, macOS and Windows already use a 64-bit time_t and are fine for the next 292 billion years. What is not fine: 32-bit embedded firmware, older glibc without _TIME_BITS=64, and MySQL's four-byte TIMESTAMP column, whose documented range stops dead at 2038-01-19 03:14:07 UTC. Use DATETIME or BIGINT there.

The deadline is earlier than 2038 for anything that projects forward. A 20-year certificate, a mortgage amortisation table, or a now + 15 years retention policy overflows today. Click the 2038 preset above and step past it with +1 day to see where the boundary sits.

Store UTC, render local — never the reverse

A Unix timestamp has no timezone. That is its best property: it names an instant, and the timezone is purely a rendering decision made at the edge. Store epoch integers or timestamptz, and convert once, in the UI, using the viewer's IANA zone — which is what the "your local time" row above does with Intl.DateTimeFormat().resolvedOptions().timeZone.

The moment you store wall-clock strings instead, DST eats you. On the spring-forward night one local hour does not exist; on the fall-back night one local hour happens twice. "02:30" is either impossible or ambiguous, and no amount of parsing fixes a value that was never unique. There is one exception worth knowing: for future events where a human picked the wall-clock time ("stand-up at 09:00 in Paris"), store the local time plus the IANA zone id — not the UTC instant and not a fixed +01:00 offset. Governments move DST rules, and when they do, the meeting should follow the clock, not the physics.

Unix time skips leap seconds on purpose

POSIX defines a timestamp as days_since_epoch × 86400 + seconds_of_day. Every day is exactly 86,400 seconds, which means Unix time is not a count of physical seconds elapsed — 27 leap seconds have been inserted into UTC since 1972 and Unix time contains none of them. During a leap second a system either repeats a timestamp or, more commonly now, smears the extra second across a 24-hour window (Google and AWS both do this on their public NTP).

The practical consequence: subtracting two epoch timestamps gives you calendar elapsed time, not true elapsed time, and it can go backwards when NTP steps the clock. If you are measuring a duration — request latency, a timeout, a rate-limit window — use a monotonic clock: performance.now(), CLOCK_MONOTONIC, or time.monotonic(). Save epoch timestamps for the thing they are good at: naming an instant that two machines can agree on.

What this converter actually does

Digits in, unit inferred by magnitude, multiplied into milliseconds, handed to a Date, then formatted with Intl.DateTimeFormat for UTC and for your detected zone, and Intl.RelativeTimeFormat for the "3 hours ago" line. ISO 8601 and RFC 2822 are built by hand so the output is stable rather than locale-dependent. Two limits are worth knowing: a JavaScript Date tops out at ±8.64e15 ms (about ±273,790 years around 1970), and it stores whole milliseconds — so microsecond and nanosecond input keeps its precision in the normalised row but not in the formatted dates. No request is made at any point; unplug the network and the page still converts.

Questions people ask

How do I know if a timestamp is in seconds or milliseconds?

Count the digits. Right now a seconds timestamp has 10 digits, milliseconds has 13, microseconds 16, and nanoseconds 19. Seconds stay at 10 digits until November 2286, so the digit count is a reliable signal for any timestamp you will realistically meet. This converter applies that rule automatically, and you can override it with the unit dropdown when you know better.

Does this epoch converter handle milliseconds and microseconds?

Yes — seconds, milliseconds, microseconds and nanoseconds, detected from the magnitude of the number or forced with the unit dropdown. Microsecond and nanosecond inputs are converted to a JavaScript Date, which only stores whole milliseconds, so sub-millisecond precision is shown in the normalised row but not in the formatted dates.

What is the 2038 problem and does it still affect me?

A signed 32-bit time_t can only count to 2147483647, which is 03:14:07 UTC on 19 January 2038. One second later it wraps to negative and the date reads December 1901. Modern 64-bit Linux, macOS and Windows builds are fine, but 32-bit embedded targets, old C libraries, and MySQL's four-byte TIMESTAMP column are not. The bug arrives early for anything that computes far-future dates, like a 20-year certificate expiry.

Is a Unix timestamp the same thing as UTC?

Not quite. A Unix timestamp is a count of seconds; UTC is a way of writing a calendar date. The timestamp carries no timezone at all, and it is defined relative to UTC, so converting it to a wall-clock time always requires you to pick a timezone. That is why the same timestamp shows two different clock readings on this page: one in UTC, one in whatever IANA zone your browser reports.

Does Unix time account for leap seconds?

No, and that is deliberate. Unix time is defined as days since the epoch multiplied by 86400 plus seconds in the day, so every day is exactly 86400 seconds long even though 27 leap seconds have been inserted into UTC since 1972. Systems either repeat a second or smear it across a day. If you need true elapsed physical time, use a monotonic clock rather than subtracting two epoch timestamps.

Is my timestamp data sent to a server?

No. Every conversion on this page runs in your browser using the built-in Date and Intl APIs. There is no request, no logging, and no analytics on the values you type. You can open devtools, watch the network tab, and confirm it — or disconnect from the internet and the page still works.

Related