Unix Seconds vs Milliseconds: The Bug That Haunts Every Developer

You paste a timestamp into your code, the result shows January 1, 1970. Or worse — a date 50,000 years in the future. This is the most common timestamp bug in software, and it has one cause: confusing Unix seconds with Unix milliseconds. Here's everything you need to know to never hit it again.

The Core Difference

Unix time counts how much time has passed since January 1, 1970 at 00:00:00 UTC. The question is: in what unit?

Unix Seconds
1,715,429,912
10 digits. Used by: Linux, macOS, most REST APIs, Python, PHP, Go, Rust, PostgreSQL, Redis, JWT.
Unix Milliseconds
1,715,429,912,000
13 digits. Used by: JavaScript's Date.now(), Java, Kotlin, Android, many analytics platforms.

Milliseconds offer 1,000× more precision than seconds. The difference matters for sub-second event ordering, high-frequency systems, and any log that needs to distinguish events happening within the same second.

Why You See January 1970 — The Classic Bug

This happens when you pass Unix seconds to a function that expects milliseconds — or vice versa.

JavaScript — the bug
// ❌ WRONG — passing Unix seconds to new Date() which expects milliseconds
const ts = 1715429912; // Unix seconds from your API
new Date(ts).toISOString();
// → "1970-01-20T20:30:29.912Z"  ← 20 days after epoch, not 2026!

// ✅ CORRECT — multiply by 1000 first
new Date(ts * 1000).toISOString();
// → "2026-05-11T14:38:32.000Z"  ← correct
🐛

The inverse bug also exists. Passing Unix milliseconds where seconds are expected gives you a date in the year 58,000+. This happens often when storing Date.now() in a database that expects Unix seconds.

How to Detect Precision Instantly — The Digit Rule

The simplest rule, which works reliably between 2001 and 2286:

DigitsPrecisionExampleYear range
9Unix seconds999999999September 2001
10Unix seconds17154299122001 – 2286
13Unix milliseconds17154299120002001 – 2286
16Unix microseconds1715429912000000PostgreSQL, ClickHouse
19Unix nanoseconds1715429912000000000Go, Rust, InfluxDB

Quick check: count the digits. 10 = seconds. 13 = milliseconds. If it's neither, paste it into UnixLi — it detects the precision automatically and warns you if the result looks wrong.

Auto-Detection in Code

Here's a reliable detection function you can use in any project:

JavaScript — auto-detect and normalize to milliseconds
/**
 * Normalizes any Unix timestamp to milliseconds.
 * Handles seconds (10 digits), milliseconds (13 digits),
 * and microseconds (16 digits).
 */
function toMilliseconds(ts) {
  const n = Number(ts);
  if (isNaN(n)) throw new Error('Invalid timestamp');

  const digits = Math.abs(n).toString().split('.')[0].length;

  if (digits <= 10) return n * 1000;       // seconds → ms
  if (digits <= 13) return n;               // already ms
  if (digits <= 16) return Math.floor(n / 1000); // microseconds → ms
  return Math.floor(n / 1_000_000);          // nanoseconds → ms
}

// Usage — works with any precision
new Date(toMilliseconds(1715429912));     // seconds     ✅
new Date(toMilliseconds(1715429912000));  // milliseconds ✅
new Date(toMilliseconds("1715429912"));  // string       ✅
Python — normalize to seconds
from datetime import datetime, timezone

def to_seconds(ts: int | float) -> float:
    """Normalize any Unix timestamp to seconds."""
    digits = len(str(abs(int(ts))))
    if digits <= 10: return float(ts)           # already seconds
    if digits <= 13: return ts / 1_000          # ms → seconds
    if digits <= 16: return ts / 1_000_000     # µs → seconds
    return ts / 1_000_000_000                  # ns → seconds

# Usage
dt = datetime.fromtimestamp(to_seconds(1715429912000), tz=timezone.utc)
# → 2026-05-11 14:38:32+00:00  ✅

Language-by-Language Reference

Every language and ecosystem has its own default precision. This table is the definitive reference.

Language / System Default precision How to get Unix seconds How to get Unix ms
JavaScript Milliseconds Math.floor(Date.now() / 1000) Date.now()
Python Seconds (float) int(time.time()) time.time_ns() // 1_000_000
PHP Seconds time() (int)(microtime(true) * 1000)
Go Seconds (via .Unix()) time.Now().Unix() time.Now().UnixMilli()
Rust Seconds + nanoseconds now.as_secs() now.as_millis()
Java / Kotlin Milliseconds System.currentTimeMillis() / 1000 System.currentTimeMillis()
PostgreSQL Seconds (float) EXTRACT(EPOCH FROM NOW())::BIGINT (EXTRACT(EPOCH FROM NOW()) * 1000)::BIGINT
MongoDB Milliseconds (ISODate) Math.floor(new Date().getTime() / 1000) new Date().getTime()
Redis Seconds TIME command (returns seconds + microseconds) Combine TIME fields
JWT (exp, iat) Seconds Always seconds per RFC 7519 N/A — spec mandates seconds
Discord timestamps Seconds <t:TIMESTAMP:R> uses seconds N/A
⚠️

JWT gotcha: RFC 7519 mandates that exp, iat, and nbf are always Unix seconds — never milliseconds. If you store Date.now() as exp, your token will appear valid for 500 years.

Identifying Precision in API Responses

When you receive a timestamp from a third-party API and the documentation is unclear, apply this flowchart mentally:

JavaScript — what does this API timestamp mean?
function diagnoseTimestamp(ts) {
  const asSeconds = new Date(ts * 1000);
  const asMs      = new Date(ts);

  const nowYear = new Date().getUTCFullYear();

  const secYear = asSeconds.getUTCFullYear();
  const msYear  = asMs.getUTCFullYear();

  console.log(`As seconds: ${asSeconds.toISOString()} (${secYear})`);
  console.log(`As ms:      ${asMs.toISOString()} (${msYear})`);

  // Whichever year is closest to now is the right interpretation
  const secDiff = Math.abs(secYear - nowYear);
  const msDiff  = Math.abs(msYear  - nowYear);

  return secDiff < msDiff ? 'seconds' : 'milliseconds';
}

diagnoseTimestamp(1715429912);
// As seconds: 2026-05-11T14:38:32.000Z (2026)  ← closest to now
// As ms:      1970-01-20T20:30:29.912Z (1970)
// → "seconds"

Best Practices — Never Hit This Bug Again

  • Always be explicit in variable names. Name your variables createdAtSec or createdAtMs, never just createdAt. Your future self and teammates will thank you.
  • Use TypeScript branded types. type UnixSeconds = number & {'{'} _brand: 'UnixSeconds' {'}'} makes it a compile error to mix them. See the JavaScript guide for implementation.
  • Document your API contracts explicitly. In your OpenAPI / Swagger spec, always specify whether a timestamp field is seconds or milliseconds. Never leave it implicit.
  • Validate on input. When receiving timestamps from external sources, run the digit-count check and reject values that resolve to implausible dates (before 2000 or after 2100 for most applications).
  • Pick one and stick to it. Within a single codebase, standardize on Unix seconds (simpler, universally supported) or milliseconds (JavaScript-native). Never mix both without explicit conversion functions.

Not sure which one your timestamp is?

Paste it into UnixLi — it auto-detects precision in milliseconds, warns you if the result looks wrong, and shows you the conversion in every format at once.

Detect precision instantly →

Quick Reference

GoalJavaScriptPython
Now in secondsMath.floor(Date.now()/1000)int(time.time())
Now in millisecondsDate.now()time.time_ns()//1_000_000
Seconds → msts * 1000ts * 1000
Ms → secondsMath.floor(ts / 1000)ts / 1000
Seconds → Datenew Date(ts * 1000)datetime.fromtimestamp(ts, utc)
Ms → Datenew Date(ts)datetime.fromtimestamp(ts/1000, utc)
Detect precisionts.toString().length <= 10 ? 'sec' : 'ms'len(str(ts)) <= 10

Summary

The rule is simple: count the digits. 10 digits = Unix seconds. 13 digits = Unix milliseconds. Any function that takes milliseconds (like JavaScript's new Date()) needs seconds multiplied by 1000. Any function that takes seconds (like Python's datetime.fromtimestamp() or JWT's exp) needs milliseconds divided by 1000.

Write the toMilliseconds() normalization helper once, put it in your utils, and never debug a 1970 date again.

Next up: the complete guide to Unix timestamps in JavaScript — formatting, timezones, TypeScript types, and every conversion you'll need.