Guides · 6 min read
Seconds vs milliseconds vs microseconds: when each is right
A 10-digit number is probably seconds. A 13-digit number is probably milliseconds. But it depends on what you are doing — here is how to pick the right resolution and avoid the off-by-1000 bugs that come with mixing them up.
Published 22 April 2026
Every developer who has worked across more than one language has, at some point, been off by exactly a factor of 1,000 on a timestamp. You log a value, it shows up as the year 53,991, you say “ah, milliseconds”, and divide. Or you show it as 1972 and you multiply. It is one of the most reliable bugs in our field.
This guide is about why it happens — different ecosystems pick different default units — and how to choose what to store in your own systems.
The four units, and how to spot each one
A “now” timestamp, written in 2026, looks like this in each unit:
| Unit | Magnitude in 2026 | Digit count |
|---|---|---|
| seconds | ~1.78 × 10⁹ | 10 |
| milliseconds | ~1.78 × 10¹² | 13 |
| microseconds | ~1.78 × 10¹⁵ | 16 |
| nanoseconds | ~1.78 × 10¹⁸ | 19 |
If you are staring at a number and trying to guess the unit, count the digits and use this table. It only fails for timestamps in the very distant past or future, neither of which usually shows up in production data.
You can also drop the value into the Unixdates converter, which auto-detects based on the magnitude and shows you the resulting date.
Why ecosystems disagree
The lazy answer is: history.
The Unix C API uses seconds. time_t is seconds. time(NULL) returns seconds. Almost every shell tool — date, find -mtime, cron, at — speaks seconds. Anything that descends directly from the C standard library is seconds.
JavaScript uses milliseconds, because in 1995 Brendan Eich looked at the Java Date class and copied its API. Java was using milliseconds because in 1995 Sun thought that was a good resolution for “user-visible time”. JavaScript’s Date.now() and the Date constructor have been milliseconds ever since, and so have all the JSON APIs that grew up around them.
Java is milliseconds, but with a caveat: System.currentTimeMillis() is milliseconds, while Instant.now() returns nanosecond-resolution. Most Java code in practice uses milliseconds.
Go is nanoseconds. time.Now().UnixNano() gives you nanoseconds since 1970, and the time.Time type internally stores nanosecond precision. time.Now().Unix() truncates to seconds for output, but the source of truth is nanos.
Postgres stores microseconds internally for timestamp and timestamptz columns. The wire format, EXTRACT(EPOCH FROM ...), gives you fractional seconds.
MySQL is seconds for UNIX_TIMESTAMP() returning an integer, but you can opt into fractional seconds with UNIX_TIMESTAMP(now(6)) to get microseconds.
Operating system clocks typically run at nanosecond resolution but get truncated by whatever API you use to read them. clock_gettime(CLOCK_REALTIME, ...) is nanos.
So when JavaScript and Postgres talk to each other, you have three different resolutions in play in a single round trip.
What you should actually store
Here are the practical defaults I recommend for new systems:
For database columns: use the database’s native timestamp type with timezone. TIMESTAMPTZ in Postgres, TIMESTAMP WITH TIME ZONE in standard SQL, DATETIME(6) in MySQL. You get sortability, indexability, type safety, and the database manages the unit for you. The internal representation is microseconds in Postgres and you don’t have to think about it.
For API request and response bodies: ISO 8601 strings are the right answer for human-readable APIs. "2026-04-22T08:00:00Z" is unambiguous, sorts correctly when stringified, and does not require a unit conversation. If you must use a number — for very high-volume APIs where the extra bytes matter — pick milliseconds. Most modern client libraries assume milliseconds, and JavaScript treats them as native.
For inter-service messages on a binary protocol: nanoseconds in a 64-bit integer. Protobuf’s Timestamp is seconds + nanos precisely so you don’t have to pick. gRPC, Cloud Spanner, BigQuery, and OpenTelemetry all use nanos. If you don’t have a strong reason otherwise, follow that convention.
For logs and metrics: nanoseconds. OpenTelemetry standardised on nanoseconds, and the entire observability stack downstream — Tempo, Honeycomb, Datadog, Grafana — handles them natively. Lower precision is lossy when you are correlating distributed traces across services.
For “what time did this physical event happen”: nanoseconds in a monotonic clock if you can, or microseconds if you can’t. You don’t actually want Unix time for measuring durations — see the timezone guide for why.
The “I just want a number” cheat sheet
If you don’t care about any of the above and just want to ship something:
// JavaScript / TypeScript
const now = Date.now(); // milliseconds
# Python
import time
now = int(time.time() * 1000) # milliseconds, integer
// Go
now := time.Now().UnixMilli() // milliseconds, since Go 1.17
# Shell (GNU date)
date +%s%3N
Picking milliseconds as your default unit is a fine choice. It’s one number, it’s the JSON-and-JavaScript standard, and you have ~285,000 years of headroom before a 64-bit number runs out.
Detecting the unit at runtime
If you are reading data from a source you don’t fully control, the safe pattern is to detect the unit by magnitude. The threshold-based approach is:
function toMilliseconds(n: number): number {
const abs = Math.abs(n);
if (abs >= 1e16) return n / 1000; // microseconds
if (abs >= 1e13) return n; // milliseconds
if (abs >= 1e10) return n; // already milliseconds
return n * 1000; // seconds
}
This is exactly what the Unixdates auto-detect does. It is not airtight — a “seconds” value far in the future could collide with a “milliseconds” value today — but for any real timestamp in the last 50 years and the next 50 years, it works.
The bug to watch for
The single most common timestamp bug in JavaScript is forgetting to divide by 1000 when handing a value to a server expecting seconds, or forgetting to multiply by 1000 when receiving a value the server thinks is in seconds. Any time your code crosses an ecosystem boundary, write down on paper what unit each side uses, and check.
You will save yourself a 2 a.m. debugging session.
Need to convert a timestamp right now? Try the Unixdates converter — auto-detects seconds, milliseconds and microseconds.