Skip to content
$ epochly

Milliseconds to Date

Thirteen digits, straight out of JavaScript's Date.now() or Java's System.currentTimeMillis(). Paste the whole thing — no dividing by 1000 first. The field below is preloaded with a millisecond value so you can see the shape of the answer immediately.

~/epochly — ms decode··········
unit

detected: milliseconds (ms)
13 digits — 12 to 14 digits is read as milliseconds. A 13-digit value is the classic JavaScript Date.now() / Java System.currentTimeMillis() shape.

UTC · ISO 86012023-11-14T22:13:20.000Z
UTC · RFC 2822Tue, 14 Nov 2023 22:13:20 +0000
UTC · readableTuesday, 14 November 2023, 22:13:20 UTC
Local timeresolving your time zone…
Local · ISOresolving your time zone…
Relativestarting clock…Months and years are approximated as 30 and 365 days, so this is a distance, not a measurement.
Epoch · seconds1700000000
Epoch · milliseconds1700000000000
try

Why milliseconds exist at all

Whole seconds are too coarse for anything that measures itself. A web request, a database query and a garbage-collection pause all finish inside one second, so a seconds-resolution clock reports them as taking zero time. Two ecosystems solved this by making milliseconds the base unit of their clock rather than a decimal on top of it: JavaScript, where Date is internally a millisecond count, and Java, where System.currentTimeMillis() has been the standard call since version 1.0.

Almost everything else kept seconds and added a fraction. Python's time.time() returns a float; PostgreSQL's EXTRACT(EPOCH …) returns a numeric with decimals; Go added UnixMilli() as a separate call alongside Unix(). So the boundary between a JavaScript or Java service and anything else is a factor of 1000 — and because both sides just see an integer, nothing complains.

The same digits, read both ways

This is the table to bookmark. Each row takes one literal integer of the kind you actually find in logs and shows what it means under both readings, so the cost of guessing wrong is a date rather than a warning.

Every date derived at build time with BigInt arithmetic, then asserted in the test suite. "Read as seconds" and "read as milliseconds" are the same digits fed to the two interpretations; the highlighted one is correct for that source.
Literal Digits Comes from Read as seconds Read as milliseconds
1700000000 10
→ seconds
Python time.time(), Go time.Now().Unix(), PHP time(), date +%s 2023-11-14T22:13:20.000Z 1970-01-20T16:13:20.000Z
1000000000 10
→ seconds
the first ever 10-digit timestamp — common in test fixtures 2001-09-09T01:46:40.000Z 1970-01-12T13:46:40.000Z
1700000000000 13
→ milliseconds
JavaScript Date.now(), Java System.currentTimeMillis() +055840-11-08T22:13:20.000Z 2023-11-14T22:13:20.000Z
1709164800000 13
→ milliseconds
a Kafka record timestamp on the 2024 leap day +056131-04-12T00:00:00.000Z 2024-02-29T00:00:00.000Z
1700000000000000 16
→ microseconds
Python datetime microseconds, Chrome trace events, PostgreSQL internals not a representable date +055840-11-08T22:13:20.000Z

Three patterns fall out of that table, and between them they identify almost every unit bug you will meet:

  • A date in January 1970 means a current seconds value was read as milliseconds. 1700000000 becomes 1970-01-20T16:13:20.000Z; the sibling literal 1000000000 becomes 1970-01-12T13:46:40.000Z. Anything in the first four weeks of 1970 is this and only this.
  • A five- or six-digit year means a millisecond value was read as seconds. 1700000000000 becomes +055840-11-08T22:13:20.000Z — ISO 8601 requires the leading + for years outside 0000–9999, so the plus sign itself is the tell.
  • "Invalid Date" or a thrown error often means a microsecond value was read as seconds. 1700000000000000 is not a representable date — 1.7×1018 milliseconds is far outside the ±8.64×1015 range a JavaScript Date holds.

Worked example: the leap-day Kafka record

A consumer logs a record timestamp of 1709164800000 and the alert says the message is from the future. Thirteen digits, so milliseconds, so the instant is 2024-02-29T00:00:00.000Z — midnight UTC on 29 February 2024, a real leap day. Nothing is from the future. What made it look wrong was a downstream service dividing by 1000 with integer maths and then formatting in a local zone, which shifted the date across midnight and landed it on 28 February — a day that, in a leap year, invites exactly this kind of second-guessing.

The diagnostic sequence is worth internalising because it takes seconds: count the digits, decode the raw value here, compare against what the failing system displayed. If Epochly agrees with the producer, the consumer is broken. If Epochly agrees with the consumer, the producer wrote the wrong unit and every stored row is affected, not just the one you noticed.

Converting milliseconds in code, safely

The scaling itself is trivial; the discipline is in never letting a bare integer cross a boundary without its unit attached. Three habits that eliminate most of these bugs:

  1. Put the unit in the name. updated_at_ms and updated_at_seconds cost nothing and make the mistake visible in code review, where it is free to fix. A column called timestamp tells the next person nothing.
  2. Prefer ISO 8601 strings at API boundaries. 2023-11-14T22:13:20.000Z cannot be misread as either unit, sorts correctly as text, and costs a few bytes. Keep epoch integers for storage and internal maths where the unit is enforced by a schema.
  3. Assert the digit count on ingest. A three-line guard that rejects a 10-digit value in a field documented as milliseconds catches the whole class of bug at the edge, before it reaches a database and becomes a migration.

Related conversions

Milliseconds to date FAQ

01 What date is the 13-digit timestamp 1700000000000?
2023-11-14T22:13:20.000Z in UTC — Tuesday, 14 November 2023, 22:13:20 UTC. Thirteen digits means milliseconds, so the value is 1,700,000,000 seconds plus zero milliseconds. Read as seconds by mistake it would be +055840-11-08T22:13:20.000Z, which is the year 55840.
02 Do I have to divide by 1000 before pasting?
No. Paste all 13 digits. The converter counts them, reports "milliseconds" in the detection line, and does the scaling internally. Dividing first only risks introducing the error you are trying to avoid — and if the value has a non-zero millisecond part, integer division silently discards it.
03 Why does JavaScript use milliseconds when everything else uses seconds?
Because Date stores an integer count of milliseconds, and Date.now() simply exposes it. Java made the same choice with System.currentTimeMillis(). The practical consequence is that these two ecosystems are a factor of 1000 away from Python, Go, PHP, Rust, MySQL, PostgreSQL and date +%s by default — see the ten-language table on the main converter page.
04 My date came out as 20 January 1970. What did I do?
You passed seconds to a milliseconds API. 1700000000 read as milliseconds is 1970-01-20T16:13:20.000Z. Any date in the first few weeks of 1970 is this bug: 1970-01-12, 1970-01-20 and 1970-01-25 are all current-era timestamps that lost their ×1000.
05 What about 16-digit values?
Those are microseconds, and they behave the same way — paste them whole. Interestingly the microsecond mistake is the loudest of all: 1700000000000000 read as seconds is not merely wrong, it is not a representable date at all, because it exceeds the ±8.64×1015 millisecond range a JavaScript Date can hold. Some libraries throw; some print "Invalid Date".
06 Does converting to milliseconds lose precision?
Not for milliseconds and microseconds. A 13-digit millisecond value is far below Number.MAX_SAFE_INTEGER (9007199254740991), so it is exact in a double. Nanosecond values with 19 digits do exceed that, so the last few digits cannot survive a float — this page and its tables use BigInt arithmetic for anything past 16 digits, and the tool reports the instant to millisecond resolution rather than pretending to nanosecond accuracy it cannot carry.

Related tools