There's a bug pattern I've seen in production more times than I can count. It goes like this:
const timestamp = Date.now(); // Returns something like 1742812800000
fetch(`/api/events?since=${timestamp}`);
The backend receives 1742812800000 and interprets it as a Unix timestamp in seconds — which translates to the year 57,212. Cue the confused "why are no events showing up" support ticket.
This is the 10-digit vs 13-digit timestamp problem, and it trips up even experienced developers.
What Is a Unix Timestamp?
A Unix timestamp counts the number of seconds (or milliseconds) elapsed since January 1, 1970, 00:00:00 UTC — also called the Unix epoch.
-
1742812800— 10 digits → seconds since epoch -
1742812800000— 13 digits → milliseconds since epoch
The same moment in time, just different units. The problem is that different languages and APIs use different conventions:
| Language/Platform | Default Unit |
|---|---|
JavaScript Date.now()
|
Milliseconds |
JavaScript new Date().getTime()
|
Milliseconds |
Python time.time()
|
Seconds (float) |
Unix/Linux date +%s
|
Seconds |
| Most REST APIs | Seconds |
Java System.currentTimeMillis()
|
Milliseconds |
Go time.Now().Unix()
|
Seconds |
Go time.Now().UnixMilli()
|
Milliseconds |
See the mismatch? JavaScript defaults to milliseconds, most backend systems default to seconds.
How to Detect Which One You Have
A quick sanity check:
function detectTimestampUnit(ts) {
// Current year is ~2026, which is ~1.74 billion seconds
// or ~1.74 trillion milliseconds from epoch
const SECONDS_IN_2020 = 1577836800;
const SECONDS_IN_2100 = 4102444800;
if (ts >= SECONDS_IN_2020 && ts <= SECONDS_IN_2100) {
return 'seconds';
} else if (ts >= SECONDS_IN_2020 * 1000 && ts <= SECONDS_IN_2100 * 1000) {
return 'milliseconds';
}
return 'unknown';
}
console.log(detectTimestampUnit(1742812800)); // "seconds"
console.log(detectTimestampUnit(1742812800000)); // "milliseconds"
Or more bluntly: 10 digits = seconds, 13 digits = milliseconds. This holds true for any timestamp between 2001 and 2286.
The Conversion You Actually Need
// Seconds → Milliseconds (for JavaScript Date)
const tsSeconds = 1742812800;
const date = new Date(tsSeconds * 1000);
console.log(date.toISOString()); // "2026-03-24T08:00:00.000Z"
// Milliseconds → Seconds (for API calls)
const tsMs = Date.now();
const tsForApi = Math.floor(tsMs / 1000);
console.log(tsForApi); // 1742812800
// Get current timestamp in seconds
const now = Math.floor(Date.now() / 1000);
The Year 2038 Problem (Still Real)
If you're working with 32-bit signed integers to store Unix timestamps (common in legacy C/C++ code and older databases), you have a hard ceiling:
- Max 32-bit signed int:
2,147,483,647 - That's: January 19, 2038, 03:14:07 UTC
After that, the value overflows to negative — typically interpreted as December 13, 1901. If your system will still be running in 2038 (and many legacy systems will), this needs to be addressed. The fix is migrating to 64-bit integer storage.
Common API Pain Points
GitHub API uses ISO 8601 strings, not timestamps. Don't try to send a number.
Stripe uses seconds-based Unix timestamps throughout.
Twilio uses ISO 8601 as well.
Most database created_at columns store as ISO strings or native datetime types — convert before storing.
Quick Conversion Without Code
When debugging, you often just need a quick sanity check: "does this timestamp look right?"
For that, I keep datetimecalculator.app/unix-timestamp bookmarked. Paste in a 10- or 13-digit number, it auto-detects the unit and shows you the UTC and local time. Works the other way too — pick a date and get the timestamp back. Faster than firing up a Node.js REPL.
Defensive Coding Patterns
// Always be explicit in function signatures
function setExpiry(timestampSeconds) { ... }
function setExpiryMs(timestampMilliseconds) { ... }
// Or use a type wrapper (TypeScript)
type Seconds = number & { readonly __brand: "Seconds" };
type Milliseconds = number & { readonly __brand: "Milliseconds" };
function toSeconds(ms: Milliseconds): Seconds {
return Math.floor(ms / 1000) as Seconds;
}
Naming your variables and parameters explicitly (timestampSeconds, expiryMs) eliminates an entire class of conversion bugs.
Summary
- JavaScript gives you milliseconds. Most everything else wants seconds.
- 10 digits = seconds, 13 digits = milliseconds — use this to sanity-check unknown timestamps.
- 32-bit timestamp storage has a 2038 deadline. Migrate now if it applies to you.
- Name your variables explicitly to prevent silent unit mismatches.
Date and time bugs are among the hardest to reproduce in production because they're often time-dependent or timezone-dependent. Getting the fundamentals right upfront saves a lot of pain later.
Hit a timestamp bug in production that cost you hours? I'd love to hear the war story.
Top comments (0)