How to humanize duration accurately in JavaScript, including weeks, months and years
It is very simple, that you might not need a library, although there are cautions.
That is, using Date
object. Be aware of months and leap years. Don't try to use straight simple approximation of raw milliseconds.
Let me say this first. I still have problem with exactly +1 Day
sometimes (about 0.2% of the cases), and I cannot figure whether it is algorithm problem, or Date object problem.
1) 3mo 3w 2d 17h 46min 40s +/- 50%
duration is precise:
Error: Duration might be miscalculated: 16 / 1,000
at Context.<anonymous> (src/index.spec.ts:96:19)
at processImmediate (internal/timers.js:461:21)
{
since: 2020-07-31T00:16:04.889Z,
calculated: 2020-08-01T00:16:04.889Z,
difference: '+1d',
now: 2020-12-06T10:03:52.130Z,
duration: '+4mo 5d 9h 47min 47s 241ms',
error: '-0.779%'
}
Simplest native methods you need
It can be exemplified here.
export type DurationUnit = "ms" | "s" | "min" | "h" | "d" | "w" | "mo" | "y";
export function addDate(d: Date): Record<DurationUnit, (n: number) => Date> {
return {
ms: (n) => {
d.setMilliseconds(d.getMilliseconds() + n);
return new Date(d);
},
s: (n) => {
d.setSeconds(d.getSeconds() + n);
return new Date(d);
},
min: (n) => {
d.setMinutes(d.getMinutes() + n);
return new Date(d);
},
h: (n) => {
d.setHours(d.getHours() + n);
return new Date(d);
},
d: (n) => {
d.setDate(d.getDate() + n);
return new Date(d);
},
w: (n) => {
d.setDate(d.getDate() + n * 7);
return new Date(d);
},
mo: (n) => {
d.setMonth(d.getMonth() + n);
return new Date(d);
},
y: (n) => {
d.setFullYear(d.getFullYear() + n);
return new Date(d);
},
};
}
Correction of leap years
I cannot tell if I have done it correctly, but it is this.
this.d = this.parse((d) => d.getDate(), {
get: (d) => d.getMonth(),
set: (d, v) => d.setMonth(v),
inc: (d) => {
const y = d.getFullYear();
let isLeapYear = true;
if (y % 4) {
isLeapYear = false;
} else if (y % 100) {
isLeapYear = true;
} else if (y % 400) {
isLeapYear = false;
}
return [
31, // Jan
isLeapYear ? 29 : 28, // Feb
31, // Mar
30, // Apr
31, // May
30, // Jun
31, // Jul
31, // Aug
30, // Sep
31, // Oct
30, // Nov
31, // Dec
][d.getMonth()];
},
});
You can see the discussion on dev.to (if it exists), here.