Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Duration missing features #463

Closed
llacroix opened this issue Oct 10, 2012 · 193 comments
Closed

Duration missing features #463

llacroix opened this issue Oct 10, 2012 · 193 comments

Comments

@llacroix
Copy link

I'm working on a project and momentjs is really useful for date manipulation so thanks for that.

To give you an example, we're making somekind of store for plane tickets. The duration.humanize() function is way to imprecise for us.

For example when leaving at: 12:00 and landing at 13:30. It will round it to 2 hours. But we need some kind of granularity which is missing from momentjs.

humanize could default to 1 level of precision starting from the highest value.

// 1:30
duration.humanize()
2 hour
duration.humanize({precision: 2})
1 hour 30 minutes
duration.humanize({precision: 3})
1 hour 30 minutes 0 seconds

The second problem is when there is the number 0. Momentjs translates it as "few seconds". I'm not so sure how it could be achieved but it would be cool to have some kind of formatting.

// 3 days and 0 minutes
duration.format('D [and] M')
> 3 days and 0 minutes
duration.format('H [and] M')
> 72 hours and 0 minutes
duration.format('H [and] m')
> 72 hours and few minutes

That way the highest value could be mapped in the format. So even if there is 1 year, the format tells us how to display it correctly. I'd be happy to push a commit for that because it's very useful and having to do it by hand when momentjs already handle localization feels bad.

@bryannielsen
Copy link

+1 on this request

@rockymeza
Copy link
Contributor

@llacroix, would you be interested in writing a pull request for this?

@llacroix
Copy link
Author

Yes probably, i'll try to find time for that. Adding it to momentjs will probably save myself and others time in the long run. Currently this forces me to create some kind of mess everywhere and it's not perfect. Fixing momentjs seems more appropriate.

But we should probably discuss a bit more about what kind of format should be done. For example, should 0 values be displayed or not.

4 hours 20 seconds or 0 days 4 hours 0 minutes 20 seconds

Months would be 30 days, a year 365.

And what formats other than year, month, week, day, minute, second and millisecond should exist.

@sylvainpolletvillard
Copy link

+1 for this
But yes, formatting is a key problem here. Humanize and format are definitely both needed.
Some suggestions for humanize behaviour :
// 1:30
duration.humanize();
1 hour 30 minutes
duration.humanize({round: "hours"})
2 hours
duration.humanize({round: "minutes"})
1 hour 30 minutes
duration.humanize({round: "seconds"})
1 hour 30 minutes 0 seconds

If round is not explicitely defined, I think the highest unity whose value is zero and all the units smaller than it should be omitted.

// 1 hour 0 minutes 45 seconds
duration.humanize() --> 1 hour
// 1 hour 1 minutes 0 seconds
duration.humanize() --> 1 hour and 1 minute
// 1 hour 1 minutes 10 seconds
duration.humanize() --> 1 hour 1 minute and 10 seconds

Also, the "and" separator should be used for the last join, preceeded by whitespaces
2 months 6 days 7 hours and 36 minutes

This is how I imagine that the function should behave by default

@timrwood
Copy link
Member

Here's my suggestion for the signature and implementation.

var duration = moment.duration({
    hours : 1,
    minutes : 0,
    seconds : 20,
    milliseconds : 0
});
duration.countdown(); // 1 hour 0 minutes 20 seconds
duration.countdown(1); // 1 hour
duration.countdown(2); // 1 hour and 0 minutes
duration.countdown(3); // 1 hour 0 minutes and 20 seconds

As @sylvainpolletvillard suggested, we may want to add a parameter for trimming off zeroed values. Maybe something like this.

duration.countdown(3); // 1 hour 0 minutes and 1 second
duration.countdown(3, true); // 1 hour

We may also want to add a parameter for the suffix like moment.fromNow(Boolean).

duration.countdown(3); // 1 hour 0 minutes and 1 second ago
duration.countdown(3, null, true); // 1 hour ago

We can build this somewhat easily for English, but concatenating all these strings correctly in all different languages will be very tricky.

We will probably have to create callbacks for languages to concatenate strings themselves. These rules will probably be very complex (I'm looking at you, hungarian).

I think the best way to pass these values would be something like the following.

var keys    = [     "h",        "mm",          "s"],
    values  = [       1,           0,           20],
    strings = ["1 hour", "0 minutes", "20 seconds"];

lang.countdown(keys, values, strings, addSuffix);

Then the English translation would be something like this:

lang.countdown = function (keys, values, strings, addSuffix) {
    var i, output = "";

    for (i = 0; i < strings.length; i++) {
        if (i === strings.length - 1 && strings.length > 1) {
            output += "and ";
        }
        output += strings[i] + " ";
    }
    if (addSuffix) {
        output += "ago";
    }
}

All in all, this becomes an incredibly complex addition which would require 33 translation functions and a bunch more code in core. Also, I'm not sure how often it would be used, so it's adding all that bloat for everyone else.

Perhaps this would be best moved to a plugin?

@bryannielsen
Copy link

Going back to the discussion on formatting the duration I think it would be great if something like this were possible -

moment.duration(9483000).format('d h m s') // 1 day 2 hours 20 minutes 30 seconds  
moment.duration(9483000).format('h m s') // 26 hours 20 minutes 30 seconds 
//etc...  

Maybe providing a boolean parameter in the format method to determine whether 0 values were shown or not?

@seaneagan
Copy link

CLDR has a list formatting scheme and data, which might be able to be used for many languages, probably many languages would still need custom callbacks though:

http://cldr.unicode.org/development/development-process/design-proposals/list-formatting

@llacroix
Copy link
Author

I'm pretty for the format function. Since implementing translation of multiple languages might be quite hard. Using formats should be quite easy to implement.

Let say you want

moment.duration(9483000).format('d h m s') // 1 day 2 hours 20 minutes and 30 seconds  
// English  => 'd h m [and] s' 1 day 2 hours 20 minutes and 30 seconds
// French   => 'd, h, m [et] s'   1 jour, 2 heures, 20 minutes et 30 secondes
// Russian  => 'd h m [и] s'   1 день 2 часа 30 минут и 30 секунд

No need for special callbacks but just special format strings.

@ejain
Copy link

ejain commented Dec 10, 2012

The proposed duration.countdown() and duration.countdown(1) methods are exactly what I'm looking for (i.e. what I'm doing now in my own code).

Meanwhile, is there a way to extend the Duration prototype?

@timrwood
Copy link
Member

The duration prototype is exposed via moment.duration.fn similar to moment.fn.

@hsablonniere
Copy link
Contributor

I have been working on something similar last year on #143/#192. Tim suggested a plugin but I never took the time to do it.

I was about to do reboot my work. I had to update to new concepts such as duration objects etc... I was this ticket and other ones. Now I'm struggling on wether it is still needed and if yes how should we implement that.

I don't want to step on one's shoes so le met know if I can help. Who is working on that issue right now?

My code was able to format to consecutive units and non consecutive units like that :

test.equal(moment([2012, 0, 1]).diff([2011, 0, 1], 'years\\y'), "1y");
test.equal(moment([2012, 0, 1]).diff([2011, 0, 1], 'months[m]'), "12m");
test.equal(moment([2016, 0, 20]).diff([2011, 10, 1], 'months [months] days [days]'), "50 months 19 days");
test.equal(moment([2016, 0, 20]).diff([2011, 10, 1], 'years \\y months \\M days \\d'), "4 y 2 M 19 d");
test.equal(moment([2016, 0, 20]).diff([2011, 10, 1], 'years[y] days[d]'), "4y 80d");
test.equal(moment([2016, 0, 20]).diff([2011, 10, 1], 'years [years] weeks [weeks]'), "4 years 11 weeks");
test.equal(moment([2016, 0, 20]).diff([2011, 10, 1], 'years\\y weeks\\w [and] days [days]'), "4y 11w and 3 days");
test.equal(moment([2016, 0, 20]).diff([2011, 10, 1], 'days\\d'), "1541d");

It didn't handle units that weren't in order like 'days years'. It also didn't handle removing zero values.

@bryannielsen
Copy link

That looks awesome, exactly the kind of thing I was hoping for!

@viettienn
Copy link

This is definitely a needed feature for everyone.

@surjikal
Copy link

surjikal commented Feb 2, 2013

Need this!

@icambron
Copy link
Member

icambron commented Apr 8, 2013

As a possible stopgap for some of you, I've created a simple plugin that allows you to use countdown.js directly from Moment:

moment("1982-5-25").countdown().toString(); // => '30 years, 10 months, 14 days, 2 hours, 23 minutes, and 50 seconds'

It passes through any Countdown options you pass it, like what units to use and with how much precision (you can look at the Countdown docs). Anyway, the plugin is here: https://github.com/icambron/moment-countdown

@sciotta
Copy link

sciotta commented Apr 19, 2013

@icambron thanks for your countribuiton! It's very helpful!

@stralytic
Copy link

I've just started using moment.js and pretty quickly came across this exact problem. This is the code I used to solve it:

moment.duration.fn.format = function (input) {
    var output = input;
    var milliseconds = this.asMilliseconds();
    var totalMilliseconds = 0;
    var replaceRegexps = {
        years: /Y(?!Y)/g,
        months: /M(?!M)/g,
        weeks: /W(?!W)/g,
        days: /D(?!D)/g,
        hours: /H(?!H)/g,
        minutes: /m(?!m)/g,
        seconds: /s(?!s)/g,
        milliseconds: /S(?!S)/g
    }
    var matchRegexps = {
        years: /Y/g,
        months: /M/g,
        weeks: /W/g,
        days: /D/g,
        hours: /H/g,
        minutes: /m/g,
        seconds: /s/g,
        milliseconds: /S/g
    }
    for (var r in replaceRegexps) {
        if (replaceRegexps[r].test(output)) {
            var as = 'as'+r.charAt(0).toUpperCase() + r.slice(1);
            var value = new String(Math.floor(moment.duration(milliseconds - totalMilliseconds)[as]()));
            var replacements = output.match(matchRegexps[r]).length - value.length;
            output = output.replace(replaceRegexps[r], value);

            while (replacements > 0 && replaceRegexps[r].test(output)) {
                output = output.replace(replaceRegexps[r], '0');
                replacements--;
            }
            output = output.replace(matchRegexps[r], '');

            var temp = {};
            temp[r] = value;
            totalMilliseconds += moment.duration(temp).asMilliseconds();
        }
    }
    return output;
}

Features of this code:

d=moment.duration({hours:1,minutes:1,seconds:1});
d.format('HHH:mm:ss');
"001:01:01"
  • if you omit a character in your input template, and the value of the subsequent character is larger than it's usual maximum, it adds the previous to it, eg:
d=moment.duration({days:1, hours:1, minutes: 1});
d.format('H:mm:ss');
"25:01:00"

Possible problems:

  • uses Math.floor on values obtained from asXxxx, eg:
Math.floor(moment.duration(milliseconds)).asHours()
  • this will mean that if there is for example, values for minutes or seconds in the duration, but you ask the format function for hours, then it won't round up, it will always round down.

@Maxwell2022
Copy link

+1 for this request

Personally I think it makes more sense to implement .format() method than having some fancy rules for .humanize() or to create a .countdown() method. 95% of the issues can be solved with .format().

I agree that .humanize() should offer more precision but it should be a different feature.

Formatting duration is a must have. It should be easy to translate seconds in formated string.

@mancausoft
Copy link

I try the stralytic code but:
d =moment.duration({days:1, hours:1, minutes: 1}); d.format('D H:mm:ss');
"1 -215:01:00"

@kumarharsh
Copy link
Contributor

+1 for format()

@kumarharsh
Copy link
Contributor

As a usecase, Durations used in timing exams (3 hours) can be formatted easily as hh:mm(:ss) or so, which would be so, so much more easier. Right now, it is quite tough to do the same in Moment until I dabble in vanilla js :)

@1updesign
Copy link

+1 duration format!

here's the quick fix I used:

moment.duration.fn.format = function(){
    str = ""
    if(this.days() > 1) str = str + Math.floor(this.days()) + "d "
    if(this.hours() > 1) str = str + Math.floor(this.hours()) + "h "
    if(this.minutes() > 1) str = str + Math.floor(this.minutes()) + "m "
    if(this.seconds() > 1) str = str + Math.floor(this.seconds()) + "s "
    return str
    }

@danmo
Copy link

danmo commented Jan 28, 2019

2019, still in need of this feature.

@gkatsanos
Copy link

A simple parameter in humanize, .e.g humanize(precise: true) , which would circumvent all the rounding would be enough to make everyone happy. Besides, rounding is the hard part to implement. The rest is just simple converting a duration like any other formatting done with format().

@drydenwilliams
Copy link

There is a great moment method called fromNow() that will return the time from a specific time in nice human readable form, like this:

moment('2019-04-30T07:30:53.000Z').fromNow() // an hour ago || a day ago || 10 days ago

Or if you want that between two specific dates you can use:

var a = moment([2007, 0, 28]);
var b = moment([2007, 0, 29]);
a.from(b); // "a day ago"

Taken from the Docs:

@louwers
Copy link

louwers commented Jun 15, 2019

Can this be reopened? This is very basic functionality.

@schnetzi
Copy link

schnetzi commented Jul 22, 2019

In my case I found a package which solved my problems:
https://github.com/EvanHahn/HumanizeDuration.js

Maybe it is useful for someone else as well. :)

@elhakeem
Copy link

July 2019 and this feature still not available.

@gaycookie
Copy link

July 2019 and this feature still not available.

Haven't tried it yet, but I just noticed this!
npm install moment-duration-format

https://github.com/jsmreese/moment-duration-format

@Thaina
Copy link

Thaina commented Sep 16, 2019

September 2019 still officially miss this feature

d.format('H:mm:ss');
"1:01:01"

Also wish that it could have this feature

d.format('D-H:mm:ss'); // existence of D will mod hours to 24
"999-23:59:59"

@laggingreflex
Copy link

In case anyone was wondering why this was closed: #463 (comment)

@kmjungersen
Copy link

This is by no means ideal, but I ended up doing something like this as a workaround. I had a duration object with a countdown for logging the user out after inactivity, and wanted to format the time remaining.

// What I did:
private getTimeString(duration: moment.Duration): string {
    const time = moment()
      .seconds(duration.seconds())
      .minutes(duration.minutes());

    return time.format('mm:ss');
}

// What I'd rather do (as many others have mentioned)...
private getTimeString(duration: moment.Duration): string {
    return duration.format('mm:ss');
}

@jimmy-e
Copy link

jimmy-e commented Nov 11, 2019

A hacky solution of mine:

import moment from 'moment';

const formatInt = (int: number): string => {
  if (int < 10) {
    return `0${int}`;
  }
  return `${int}`;
};

export const formatDuration = (time: string): string => {
  const seconds = moment.duration(time).seconds();
  const minutes = moment.duration(time).minutes();
  const hours = moment.duration(time).hours();
  if (hours > 0) {
    return `${formatInt(hours)}:${formatInt(minutes)}:${formatInt(seconds)}`;
  }
  if (minutes > 0) {
    return `${formatInt(minutes)}:${formatInt(seconds)}`;
  }
  return `00:${formatInt(seconds)}`;
};

@justrealmilk
Copy link

2020

@vjfuenzalida
Copy link

vjfuenzalida commented Apr 29, 2020

I use the following method, it may be useful to someone else :)

function formatDuration(duration, format) {
  const date = moment().startOf('day');
  return date.add(duration).format(format);
}

(I use it only to format durations between 00:00 and 23:59)

@GuitooStephan
Copy link

Still need this

@dazoliveira
Copy link

These 'get' helps to format in way u want:

  duration.get('years')
  duration.get('months')
  duration.get('days')
  duration.get('hours')
  duration.get('minutes')
  duration.get('seconds')

@elisherer
Copy link

What I work with:

const formatDuration = ms => {
  const days = Math.floor(ms / 8.64e7);
  const msOnLastDay = ms - days * 8.64e7;
  return (days < 10 ? "0" + days : days) + ":" + moment.utc(msOnLastDay).format("HH:mm:ss.SSS");
};
formatDuration(5)
"00:00:00:00.005"
formatDuration(500)
"00:00:00:00.500"
formatDuration(50000)
"00:00:00:50.000"
formatDuration(5000000)
"00:01:23:20.000"
formatDuration(500000000)
"05:18:53:20.000"
// for reference
JSON.stringify(moment.duration(500000000)._data, null, 2)
"{
  "milliseconds": 0,
  "seconds": 20,
  "minutes": 53,
  "hours": 18,
  "days": 5,
  "months": 0,
  "years": 0
}"

@Bassadin
Copy link

July 2019 and this feature still not available.

Haven't tried it yet, but I just noticed this!
npm install moment-duration-format

https://github.com/jsmreese/moment-duration-format

This works perfectly fine :)

@Muskos
Copy link

Muskos commented Oct 9, 2020

The game is over :)

@dazoliveira
Copy link

dazoliveira commented Oct 13, 2020 via email

@ilhamarrouf
Copy link

2021, still in need of this feature.

@ghost
Copy link

ghost commented Jan 26, 2022

2022, still in need of this.

@Phecda
Copy link

Phecda commented Feb 25, 2022

My solution for a simple HH:mm:ss is

export function formatDuration(duration: moment.Duration) {
  // convert 'P1D' to 'PT24H'
  const isoString = moment.duration(duration.asMilliseconds()).toISOString();

  // const [, , h = '', , m = '', , s = '', , ms = ''] =
  const [, , h = '', , m = '', , s = ''] =
    isoString.match(/T((\d+)H)?((\d+)M)?(([\d]+)(\.(\d+))?S)?/) ?? [];

  // return `${h.padStart(2, '0')}:${m.padStart(2, '0')}:${s.padStart(2, '0')}.${ms.padStart(3, '0')}`;
  return `${h.padStart(2, '0')}:${m.padStart(2, '0')}:${s.padStart(2, '0')}`;
}

see https://momentjs.com/docs/#/durations/as-iso-string/

@TravnikovDev
Copy link

Summer 2022, we still in need of this feature...

@Yuniac
Copy link

Yuniac commented Sep 15, 2022

I'm surprised people have been asking for this feature for years and it's still not supported lol.

@schmod
Copy link

schmod commented Sep 15, 2022

As has been repeatedly discussed in this thread, internationalization concerns make it difficult-to-impossible to implement this feature with any sort of reasonable API.

If you'd like to implement a library that implements this feature for a single locale, you're free to do so (there are numerous examples of this referenced above). However, working around the grammars of every locale that Moment supports (and gathering the relevant translation data) is a much more difficult proposition.

If you've figured out how to build an i18n-friendly version of this API, you're also free to build that yourself. (Note that you'll need to fork Moment, as the project is in "maintenance mode", and the maintainers are not considering major new features.)

Given Moment's deprecated status, you may want to consider making this a proper TC39 proposal as an extension of the Temporal API. I'm sure that many would consider this to be a valuable addition to the ECMAScript standard library.

@ghost
Copy link

ghost commented Sep 16, 2022

@schmod Partial support exists, but if you never start to implement it because it is too hard to do full support, then you will never complete it. I guess there are many people here, who know different languages, so adding a language would be just starting an enchancement issue and sumbitting some simple code.
The temporal API appears to have a Duration, so idk. what's the problem here: https://tc39.es/proposal-temporal/docs/duration.html I did not follow this in the last years though.

@foo-bar-dr
Copy link

Summer 2023, still in need of this feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests