10

I have a moment duration of say 14 seconds.

const duration = moment.duration(end.diff(startTime));

If I want to display this duration as a formatted string, I can use .humanize()

duration.humanize(true)
// in a few seconds

This works fine, but is there a way for moment to format this as an exact duration like in 14 seconds or, for a longer duration, in in 2 days and 5 hours instead of in 2 days?

hoan
  • 1,277
  • 3
  • 18
  • 32

5 Answers5

9

You can customize the output of relative time functions like humanize() using relativeTime key of updateLocale and relativeTimeThreshold.

I don't know what is your expected output for a generic duration, but you can use the following code to get in 14 seconds for a duration of 14 seconds:

const startTime = moment();
const end = moment().add(14, 'seconds');
const duration = moment.duration(end.diff(startTime));
console.log( duration.humanize(true) );

moment.relativeTimeThreshold('ss', 60);
moment.updateLocale('en', {
  relativeTime : {
    s: function (number, withoutSuffix, key, isFuture){
      return number + ' seconds';
    }
  }
});

console.log( moment.duration(end.diff(startTime)).humanize(true) );
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>

You can also take a look at this and this similar questions.

VincenzoC
  • 30,117
  • 12
  • 90
  • 112
5

I solved your problem with this snippet of code I made.

function formatDuration(period) {
    let parts = [];
    const duration = moment.duration(period);

    // return nothing when the duration is falsy or not correctly parsed (P0D)
    if(!duration || duration.toISOString() === "P0D") return;

    if(duration.years() >= 1) {
        const years = Math.floor(duration.years());
        parts.push(years+" "+(years > 1 ? "years" : "year"));
    }

    if(duration.months() >= 1) {
        const months = Math.floor(duration.months());
        parts.push(months+" "+(months > 1 ? "months" : "month"));
    }

    if(duration.days() >= 1) {
        const days = Math.floor(duration.days());
        parts.push(days+" "+(days > 1 ? "days" : "day"));
    }

    if(duration.hours() >= 1) {
        const hours = Math.floor(duration.hours());
        parts.push(hours+" "+(hours > 1 ? "hours" : "hour"));
    }

    if(duration.minutes() >= 1) {
        const minutes = Math.floor(duration.minutes());
        parts.push(minutes+" "+(minutes > 1 ? "minutes" : "minute"));
    }

    if(duration.seconds() >= 1) {
        const seconds = Math.floor(duration.seconds());
        parts.push(seconds+" "+(seconds > 1 ? "seconds" : "second"));
    }

    return "in "+parts.join(", ");
}

This function takes a period string (ISO 8601), parses it with Moment (>2.3.0) and then, for every unit of time, pushes a string in the parts array. Then everything inside the parts array gets joined together with ", " as separation string.

You can test it here: https://jsfiddle.net/mvcha2xp/6/

I'm using it as a Vue filter to humanize durations correctly.

SyncroIT
  • 1,510
  • 1
  • 14
  • 26
  • 2
    Thanks so much for this! Exactly what I needed. This should have more up votes. It's a simple solution that doesn't require an additional package. – Nick Falco Jun 04 '21 at 03:52
2

No. The only argument that .humanize() takes is the suffix toggle. If you check the source for the internal function that the humanize method uses, you can see that it always rounds the durations. If you want more specificity, you'll need to implement your own method with the desired behavior.

Hydrothermal
  • 4,851
  • 7
  • 26
  • 45
  • 1
    This is partially correct, momentjs offers [`relativeTimeThreshold`](http://momentjs.com/docs/#/customization/relative-time-threshold/), [`relativeTimeRounding`](http://momentjs.com/docs/#/customization/relative-time-rounding/) and [`relativeTime`](http://momentjs.com/docs/#/customization/relative-time/) to customize output of [`humanize()`](https://momentjs.com/docs/#/durations/humanize/) and other relative time function like [`fromNow()`](https://momentjs.com/docs/#/displaying/fromnow/). – VincenzoC Dec 18 '18 at 09:21
  • 1
    @VincenzoC Thanks, I didn't know about those! Hopefully the OP sees your answer as well. – Hydrothermal Dec 18 '18 at 19:32
2

Possible solution is to use another library, which does this as OP stated.

humanizeDuration(3000)      // '3 seconds'
humanizeDuration(2250)      // '2.25 seconds'
humanizeDuration(97320000)  // '1 day, 3 hours, 2 minutes'
szab.kel
  • 2,356
  • 5
  • 40
  • 74
0

You could also do a simple workaround with moment. Check the example: https://stackblitz.com/edit/typescript-c6e4uh

static humanizeDuration(duration: number, unit: string = 'ms'): string {
    const durationObj: any = moment.duration(duration, (unit as any));
    const durationStringArray = [];
    Object.keys(durationObj._data)
      .filter((key) => durationObj._data[key] > 0)
      .forEach((key) => {
        durationStringArray.push(moment.duration(durationObj._data[key], (key as any)).humanize());
    });
    return durationStringArray.reverse().join(', ');
  }
Dustexe
  • 103
  • 7