Remove Milliseconds in JavaScript: ISO Text, Dates, and Unix Seconds
- Authors

- Name
- Geeks Kai
- @KaiGeeks
“Remove milliseconds” can mean hiding fractional seconds in a string, creating a Date at a whole second, or converting integer Unix milliseconds to integer Unix seconds. These operations have different output types and units.
| Goal | Result | Changes the input Date? |
|---|---|---|
| ISO text without a fractional part | UTC string ending in Z | No |
| A copy at the preceding whole UTC second | Date; ISO output still has .000Z | No |
| Integer Unix seconds | Number in seconds, not milliseconds | No |
The first two helpers accept a valid Date object. The conversion helper accepts safe integer Unix milliseconds. Supply an explicit offset or Z when constructing a Date from a date-time string; do not guess a numeric timestamp’s unit from its length.
function getValidEpochMilliseconds(date) {
if (!(date instanceof Date)) {
throw new TypeError("Expected a Date object.")
}
const epochMilliseconds = date.getTime()
if (!Number.isFinite(epochMilliseconds)) {
throw new RangeError("Expected a valid Date.")
}
return epochMilliseconds
}
function isoWithoutMilliseconds(date) {
getValidEpochMilliseconds(date)
return date.toISOString().slice(0, -5) + "Z"
}
function atWholeUtcSecond(date) {
const copy = new Date(getValidEpochMilliseconds(date))
copy.setUTCMilliseconds(0)
return copy
}
function unixSecondsFloor(epochMilliseconds) {
if (!Number.isSafeInteger(epochMilliseconds)) {
throw new TypeError("Expected integer Unix milliseconds within the safe integer range.")
}
return Math.floor(epochMilliseconds / 1000)
}
const sourceDate = new Date("2025-01-01T12:00:00.987+02:00")
console.log(isoWithoutMilliseconds(sourceDate)) // 2025-01-01T10:00:00Z
console.log(atWholeUtcSecond(sourceDate).toISOString()) // 2025-01-01T10:00:00.000Z
console.log(sourceDate.toISOString()) // 2025-01-01T10:00:00.987Z: unchanged
console.log(unixSecondsFloor(sourceDate.getTime())) // integer seconds, not milliseconds
console.log(unixSecondsFloor(-1)) // -1: floor to the preceding whole second
console.log(atWholeUtcSecond(new Date(-1)).getTime()) // -1000: still milliseconds
isoWithoutMilliseconds() formats in UTC because toISOString() produces UTC text. Slicing its final five characters removes the .sssZ suffix, then the example restores Z. This operation changes the returned text’s precision, not the source Date. The explicit validation rejects an invalid Date rather than pretending any input string can be formatted.
atWholeUtcSecond() makes a copy and uses setUTCMilliseconds(0). Calling a setter directly on the original Date would mutate it. setMilliseconds(0) is a local-time setter; use the UTC operation when that is the intended boundary. Either way, toISOString() still includes a milliseconds field even when its value is zero.
For the millisecond immediately before the epoch, Math.floor(-1 / 1000) is -1 second. Math.trunc(-1 / 1000) produces negative zero. The example deliberately chooses the preceding whole-second boundary. If your contract requires truncation toward zero, choose that explicitly and decide how to represent negative zero.
Multiplying the floored seconds by 1000 gives an epoch value in milliseconds again; it does not return Unix seconds. Keep unit-bearing names such as epochMilliseconds and unixSeconds at API boundaries.
If a project already uses a date library, its formatting tokens can omit fractional seconds. A format containing a numeric timezone-offset token is not automatically equivalent to UTC text ending in Z; confirm the library’s mode and token rules. Native Date is sufficient for the specific examples above, without adding a dependency.
Discord timestamps use Unix seconds. Use the Discord timestamp generator for that message-format task, and see the timestamp formats guide for examples. Removing .987 from a formatted string is not a substitute for converting milliseconds to seconds.