Milliseconds vs Seconds: The Unit Confusion That Breaks Every App

The single most common timestamp bug is passing milliseconds where seconds are expected, or vice versa. Learn the 10-digit vs 13-digit rule, language defaults, database implications, and safe conversion patterns.

Why there are two standards

Unix time was originally defined in seconds — a whole integer felt natural for a system that incremented once per clock tick. JavaScript's Date object was introduced in 1995 and chose milliseconds to support sub-second event timing in the browser. Many databases and backend languages kept Unix seconds as the default. Now both standards coexist in every codebase that crosses the JavaScript-to-server boundary, which is why a value can look valid and still represent a date tens of thousands of years away.

Which unit each language uses

The safest way to remember the split is: browser Date APIs usually want milliseconds, Unix-style server APIs usually expose seconds, and newer time libraries often provide both. Do not infer the unit from the language alone when reading third-party API documentation; check the field description and examples.

  • Milliseconds: JavaScript Date.now(), Java System.currentTimeMillis(), Java Instant.toEpochMilli(), .NET ToUnixTimeMilliseconds()
  • Seconds: Python time.time() (float), PHP time(), Go time.Now().Unix(), Ruby Time.now.to_i, C time(NULL), PostgreSQL EXTRACT(EPOCH)
  • Both: Rust — duration.as_secs() for seconds, duration.as_millis() for milliseconds
  • Python note: time.time() returns a float, so milliseconds are available as int(time.time() * 1000)

Auto-detecting the unit from the value

A reliable heuristic for modern dates: a 10-digit number is seconds; a 13-digit number is milliseconds. Current Unix seconds are around 1.7–2.1 billion (10 digits) and will not reach 13 digits until the year 33658. Current Unix milliseconds are already 13 digits. The heuristic is strongest for dates from the 2000s through the next several thousand years; for historical dates, negative dates, or compact test fixtures, use explicit units instead of guessing.

  • 10-digit value → seconds (e.g. 1700000000 = 2023-11-14 UTC)
  • 13-digit value → milliseconds (e.g. 1700000000000 = 2023-11-14 UTC)
  • 16-digit value → microseconds (divide by 1,000,000 for seconds)
  • Negative value → date before January 1, 1970 (seconds or milliseconds)

Safe conversion patterns

Conversion itself is simple arithmetic; the hard part is choosing where conversion should happen. Convert at the system boundary, name the converted value, and avoid passing ambiguous raw numbers through multiple layers.

  • Seconds to milliseconds: seconds * 1000
  • Milliseconds to whole seconds — JavaScript: Math.floor(ms / 1000)
  • Milliseconds to whole seconds — Python: ms // 1000
  • Milliseconds to whole seconds — Go: ms / 1000 (integer division)
  • Universal guard in JavaScript: const toMs = ts => ts < 1e11 ? ts * 1000 : ts

How unit bugs show up in production

A seconds-vs-milliseconds bug often survives validation because both values are just numbers. It usually appears later as an impossible date: January 1970 in JavaScript when seconds were treated as milliseconds, or a far-future year when milliseconds were treated as seconds by a backend.

  • 1970 date in JavaScript UI → seconds were passed to new Date() without multiplying by 1000
  • Year 50000+ in Python, Go, or PHP → milliseconds were passed to an API expecting seconds
  • Expired tokens that never expire → expiration timestamp stored in the wrong unit
  • Cache entries that vanish immediately → milliseconds divided twice or seconds multiplied twice
  • Analytics charts with empty ranges → query bounds use a different unit than stored event timestamps

API and database naming conventions

A tiny naming convention prevents most of these bugs. Never publish an API field called timestamp unless the documentation is unusually clear. Prefer field names that include both meaning and unit.

  • createdAtMs — Unix milliseconds, best for JavaScript clients
  • createdAtSeconds — Unix seconds, common for backend services
  • createdAtIso — ISO 8601 string, useful for human-readable API responses
  • expiresAtUnixSeconds — explicit enough for auth tokens and signed URLs
  • event_time TIMESTAMPTZ — native database time, with conversion handled by the database

Milliseconds vs seconds FAQ

Is a 13-digit timestamp always milliseconds?
For modern real-world Unix timestamps, yes: 13 digits usually means milliseconds. Very far-future seconds timestamps can also reach 13 digits, so critical systems should still carry explicit unit metadata.
Should I store seconds or milliseconds?
Store the unit your system naturally uses, but document it and keep it consistent. JavaScript-heavy systems often use milliseconds; Unix-style backends and many databases commonly use seconds or native datetime columns.
Why use Math.floor(ms / 1000) instead of ms / 1000?
Unix seconds are usually whole seconds. Math.floor removes the fractional part so APIs expecting integer seconds do not receive a decimal value.
How do I convert milliseconds to seconds?
Divide by 1000 and drop the fraction: Math.floor(ms / 1000) in JavaScript, ms // 1000 in Python, or ms / 1000 with integer division in Go. To go the other way, multiply seconds by 1000.