A timestamp can be a perfectly valid number and still produce the wrong date. The useful questions are: what unit does it use, how is it displayed, and what precision must survive the conversion?
Here are three small checks you can reproduce in JavaScript. The examples use Date because it is common in existing API integrations.
1. Passing Unix seconds to a millisecond constructor
Start with this value:
const unixSeconds = 1711972800;
console.log(new Date(unixSeconds).toISOString());
// 1970-01-20T19:32:52.800Z
console.log(new Date(unixSeconds * 1000).toISOString());
// 2024-04-01T12:00:00.000Z
JavaScript's numeric Date constructor expects milliseconds. Both calls produce valid dates, so an Invalid Date check alone will not catch the first mistake. See the Date reference.
For an API you control, put the unit in the contract. A field such as createdAtMs is easier to review than time. If clients can send more than one unit, require a separate unit field.
This helper intentionally accepts only integer seconds or milliseconds:
function epochToDate(value, unit) {
if (!Number.isSafeInteger(value)) {
throw new TypeError('Expected a safe integer');
}
if (unit !== 's' && unit !== 'ms') {
throw new TypeError('Expected unit s or ms');
}
const ms = unit === 's' ? value * 1000 : value;
if (!Number.isSafeInteger(ms) || Math.abs(ms) > 8.64e15) {
throw new RangeError('Outside the supported Date range');
}
return new Date(ms);
}
That is a deliberately narrow contract, not a universal timestamp parser. Fractional seconds need a different contract, and input from query strings needs validation before this function.
Counting digits can help a person inspect a value, but it should not silently decide an API's contract. Small millisecond values and dates before 1970 make that heuristic unreliable.
2. Treating different offsets as different instants
These two strings describe the same instant:
const utc = Date.parse('2024-04-01T12:00:00Z');
const withOffset = Date.parse('2024-04-01T14:00:00+02:00');
console.log(utc === withOffset); // true
console.log(new Date(withOffset).toISOString());
// 2024-04-01T12:00:00.000Z
When investigating a two-hour discrepancy, compare the numeric timestamps before changing the stored value. The difference may be in presentation.
An offset-free date-time such as 2024-04-01T12:00:00 is interpreted in the host's local time zone by Date.parse. A server and a laptop may therefore read it differently. See Date.parse.
For an event that represents an instant, require an explicit offset or UTC in string inputs. Scheduling β09:00 every Monday in Parisβ is a separate problem: preserve the named time zone and the scheduling rule instead of storing a fixed offset forever.
3. Losing nanoseconds before conversion even starts
A large timestamp can lose precision as soon as it becomes a JavaScript Number:
const text = '1711972800123456789';
const exact = BigInt(text);
const rounded = BigInt(Number(text));
console.log(exact === rounded); // false
If the source uses nanoseconds, transport the value as a decimal string and parse it with BigInt. Converting a rounded Number to BigInt later cannot recover the missing digits.
For display with Date, reduce the precision explicitly. This example handles non-negative values only:
const nanoseconds = BigInt('1711972800123456789');
const milliseconds = nanoseconds / 1_000_000n;
const remainder = nanoseconds % 1_000_000n;
console.log(new Date(Number(milliseconds)).toISOString());
// 2024-04-01T12:00:00.123Z
console.log(remainder.toString());
// 456789
Keep the original value if the submillisecond fraction matters. For arbitrary input, validate the range before converting to Number. Negative values also need an explicit rounding policy: BigInt division truncates toward zero, which is different from rounding down.
A small regression checklist
For a conversion endpoint, include examples with:
- Explicit seconds and milliseconds representing the same instant.
- Zero and a date before 1970.
- An offset-bearing string crossing midnight in UTC.
- An unsupported unit and an out-of-range value.
- A nanosecond string whose precision must be retained.
These checks make useful code-review questions even if your date library handles the actual conversion.
An interactive companion
Disclosure: this article is published on behalf of Timestampinfo. Its timestamp tools let you inspect UTC, local display, and explicit units; the batch converter also accepts up to 1,000 lines and exports CSV. Conversion inputs are processed in the browser. The site separately uses Matomo for page-view measurement, with details and an opt-out on its privacy page.
The tools and guides are available in English, French, Spanish, and Italian, without an account.
Editorial note: this article was drafted by an AI assistant for Timestampinfo. The JavaScript examples were executed and their outputs checked before publication.
Top comments (0)