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.

No Comments Yet