
While coding, developers frequently deal with time durations — whether it’s for timeouts, intervals, cache lifecycles, or animations. The common approach is to use plain numbers to represent these durations, often in milliseconds. While this works, it introduces ambiguity and potential bugs.
The Problem with Plain Number
Consider the default JavaScript setTimeout method:
setTimeout(() => doSomething(), 5000);
Quite often plain number are used as a parameter. For correct usage you have to rely on documentation, comments, or debugging to understand the unit.
Furthermore this could lead to variables with comments or constants having big magic numbers.
const TIMEOUT = 1209600000; //two weeks in millis
const TWO_WEEK_MILLIS = 1209600000;
Those constants created multiple times whereever needed. Sometimes developer tries to unmagic those number with patterns containing out the convertion to the next unit .
const TWO_WEEK_MILLIS = 14 * 24 * 60 * 60 * 1000;
Real-World Example
In this example you can see a echart configuration, with such a construct. Maybe you already recognized that maxValueSpan property is milliseconds while the minValueSpan is in seconds. Here you do not get any compile error nor this is obvious while reading.
return {
dataZoom: [
{
maxValueSpan: 3_600 * 24 * 1000 * 365 * 2,
minValueSpan: 3_600,
disabled: true,
moveOnMouseMove: 'shift',
type: 'inside',
zoomOnMouseWheel: 'ctrl',
xAxisIndex: 0,
},
],
Having just these numbers without any semantic as parameters, leads to that we can paste any number into this parameter. 2 weeks or 2 hours, the method will never know.
The Solution: Duration Objects
Instead of primitive numbers we can use duration objects. Good old moment.js does have them (see example), date-fns have it and i am pretty sure there a lot of other libs providing such classes (or you can implement it by your own)
export const weeks = (w: number) => moment.duration(w, 'weeks');
export const hours = (h: number) => moment.duration(h, 'hours');
export const seconds = (s: number) => moment.duration(s, 'seconds');
...
Exporting this function multiple times for the different units makes it easy to create a duration everybody is knowing what he is dealing with (like weeks(2))
Now, instead of passing raw numbers, you pass duration objects:
const myTimeout = seconds(5);
setTimeout(()=> {
doSomething();
}, myTimeout.asMilliseconds());
This approach makes your code more readable and less error-prone. It also allows your functions to be agnostic of the time unit, converting it internally as needed.
Even if a third-party library still consumes a number it becomes visible which target unit a developer wants to pass and you can convert it to the number as late as possible or create own wrapper consuming the duration object.
import moment, { Duration } from 'moment';
function setSafeTimeout(callback: () => void,timeout: Duration) {
setTimeout(callback, timeout.asMilliseconds());
}
setSafeTimeout(seconds(5));
Conclusion
Using duration objects instead of raw numbers is a simple yet powerful way to make your TypeScript code more robust, readable, and maintainable. Whether you use a library like Moment.js or implement your own lightweight duration helpers, the benefits are clear.
This approach can also be applied to other units, but durations are the most common inside code from my perspective.