10/13-Digit Timestamps: Year 1970 Bugs
Passing Unix timestamps into new Date(ts) frequently produces unexpected '1970-01-01' dates or Year 55800 backend overflows. The root cause is confusing seconds (10-digit) with milliseconds (13-digit). This article details unit verification, formatting, and engineering safeguards.
1. Problem: rendering failures from second and millisecond unit confusion
Unix timestamps measure elapsed time since 1970-01-01 00:00:00 UTC. Systems like Python, PHP, and Go return 10-digit second timestamps (1700000000), whereas JavaScript Date.now() and Java System.currentTimeMillis() return 13-digit millisecond timestamps (1700000000000). Passing 10-digit seconds to new Date(1700000000) yields January 20th, 1970!
2. Minimal reproduction: parsing deviations across timestamp units
This comparison illustrates the physical difference between passing 10-digit seconds vs. 13-digit milliseconds into JavaScript Date:
/* Raw second timestamp: 1700000000 (represents 2023-11-14 22:13:20 UTC) */
const timestamp10 = 1700000000;
// Incorrect: passing 10-digit seconds directly to JS Date
const wrongDate = new Date(timestamp10);
console.log(wrongDate.toISOString()); // "1970-01-20T16:13:20.000Z" (Regresses to 1970!)
// Correct: multiply seconds by 1000 to obtain milliseconds
const correctDate = new Date(timestamp10 * 1000);
console.log(correctDate.toISOString()); // "2023-11-14T22:13:20.000Z" (Correct!)3. Root cause: runtime unit differences and fragile digit heuristics
1. Runtime differences: Unix APIs often use seconds, JavaScript Date uses milliseconds, and logs may use microseconds or nanoseconds. 2. Digits are diagnostic only: present-day seconds and milliseconds are commonly 10 and 13 digits, but negatives, historic values, durations, and far-future dates break that rule. 3. Unit is schema data: production code must receive the unit through the schema, field name, or an explicit parameter instead of guessing with 1e11.
4. Recommendation: explicit units and BigInt boundaries
1. Declare seconds, milliseconds, microseconds, or nanoseconds in the API schema. 2. Deliver microsecond and nanosecond values as strings or BigInt so they are not rounded through Number first. 3. Preserve negative timestamps and validate the normalized result against JavaScript Date's valid range.
5. Complete code: timestamp normalization with an explicit unit
This TypeScript code never guesses from digit count. The caller supplies a unit, and integer values are scaled through BigInt before conversion to milliseconds.
type TimestampUnit =
| "seconds"
| "milliseconds"
| "microseconds"
| "nanoseconds";
const MAX_DATE_MS = 8_640_000_000_000_000n;
function normalizeToMs(
input: string | number | bigint,
unit: TimestampUnit,
): number {
if (typeof input === "number" && !Number.isSafeInteger(input)) {
throw new RangeError("Number input must be a safe integer; use a string or BigInt for high precision");
}
if (typeof input === "string" && !/^-?\d+$/.test(input)) {
throw new TypeError("Timestamp strings must contain a decimal integer");
}
const raw = BigInt(input);
const milliseconds = unit === "seconds" ? raw * 1_000n
: unit === "milliseconds" ? raw
: unit === "microseconds" ? raw / 1_000n
: raw / 1_000_000n;
if (milliseconds < -MAX_DATE_MS || milliseconds > MAX_DATE_MS) {
throw new RangeError("Timestamp is outside the JavaScript Date range");
}
return Number(milliseconds);
}
function createDate(input: string | number | bigint, unit: TimestampUnit): Date {
return new Date(normalizeToMs(input, unit));
}
console.log(createDate(1_700_000_000, "seconds").toISOString());
console.log(createDate("1700000000000000000", "nanoseconds").toISOString());
console.log(createDate(-3600, "seconds").toISOString());6. Incorrect approaches
Relying naively on String(ts).length === 10 (unreliable for floats or negative numbers). Blindly multiplying timestamps by 1000 without inspecting source schema units.
7. Boundaries: negatives, truncation, and invalid dates
Pre-1970 timestamps are negative and must not be rejected wholesale. Integer division of microseconds or nanoseconds truncates toward zero; if the domain requires flooring or sub-millisecond precision, define that separately because Date cannot preserve it.
8. Verification
Create equivalent vectors for every unit and cover negative 1969 values, the Unix epoch, micro/nanosecond strings, unsafe Number input, and Date boundaries. Assert exact ISO output and exact failures rather than checking only a broad year range.
9. FAQ
Why does Python time.time() return floats? Python returns seconds as floating-point numbers (e.g., 1700000000.123). Front-ends should multiply by 1000 and apply Math.floor().
Does Date.now() suffer from precision loss? No, Date.now() returns standard Safe Integers; use performance.now() for microsecond timing.
Does the Year 2038 Problem affect JavaScript? 32-bit signed integer overflow impacts older C/C++ backends, but JS Numbers use 64-bit IEEE 754 floats, safely representing timestamps up to the year 275760.
10. Summary
Understanding physical units (seconds vs. milliseconds) is vital to preventing 1970 and Year 55800 rendering bugs. Enforce normalization at API boundaries.
Sources and further reading
Specifications and references for checking the article’s technical details.
- RFC 3339 — Date and Time on the Internet
RFC Editor
- ECMAScript Language Specification
Ecma International
Related articles
Safari Date Parsing: Invalid Dates and ISO Formats
Examine engine differences between Safari JavaScriptCore and Chrome V8 when parsing non-standard dates, providing cross-browser ISO 8601 date parsing.
Best practicesUnix Timestamps: UTC, CST and Daylight Saving Time
Decouple physical timestamps from localized timezone rendering, addressing CST ambiguities and Daylight Saving Time shifts with IANA timezone names.
PitfallsCRLF vs LF: Fix Line Ending Diffs with Git
Analyze how line ending mismatches between Windows (CRLF, \r\n) and Linux/macOS (LF, \n) cause whole-file false diffs, and configure Git line ending normalization.
Keep exploring
Use the related browser tool to test the workflow with your own sample data.
Open related tool