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 · 186 comments
Closed

Duration missing features #463

llacroix opened this issue Oct 10, 2012 · 186 comments

Comments

@llacroix
Copy link

@llacroix llacroix commented Oct 10, 2012

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

@bryannielsen bryannielsen commented Oct 16, 2012

+1 on this request

@rockymeza
Copy link
Contributor

@rockymeza rockymeza commented Oct 16, 2012

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

@llacroix
Copy link
Author

@llacroix llacroix commented Oct 19, 2012

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

@sylvainpolletvillard sylvainpolletvillard commented Oct 24, 2012

+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

@timrwood timrwood commented Oct 24, 2012

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

@bryannielsen bryannielsen commented Oct 24, 2012

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

@seaneagan seaneagan commented Oct 24, 2012

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

@llacroix llacroix commented Oct 27, 2012

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 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

@timrwood timrwood commented Dec 10, 2012

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

@hsablonniere
Copy link
Contributor

@hsablonniere hsablonniere commented Jan 5, 2013

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

@bryannielsen bryannielsen commented Jan 5, 2013

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

@viettienn
Copy link

@viettienn viettienn commented Jan 14, 2013

This is definitely a needed feature for everyone.

@surjikal
Copy link

@surjikal surjikal commented Feb 2, 2013

Need this!

@icambron
Copy link
Member

@icambron 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

@thiagog3
Copy link

@thiagog3 thiagog3 commented Apr 19, 2013

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

@stralytic
Copy link

@stralytic stralytic commented Apr 20, 2013

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

@Maxwell2022 Maxwell2022 commented May 21, 2013

+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

@mancausoft mancausoft commented May 28, 2013

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

@kumarharsh kumarharsh commented May 30, 2013

+1 for format()

@kumarharsh
Copy link
Contributor

@kumarharsh kumarharsh commented Jun 11, 2013

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

@1updesign 1updesign commented Jul 18, 2013

+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
    }
@denl95
Copy link

@denl95 denl95 commented Feb 8, 2018

+1

@aguynamedben
Copy link

@aguynamedben aguynamedben commented Feb 17, 2018

+1 hard to debug durations

@zaplachinsky
Copy link

@zaplachinsky zaplachinsky commented Feb 20, 2018

+1

@Kovbo
Copy link

@Kovbo Kovbo commented Mar 4, 2018

You can easily do something like:

    var duration = moment().add(5, 'days') -  moment().local();
    var s = Math.floor( (duration/1000) % 60 );
    var m = Math.floor( (duration/1000/60) % 60 );
    var h = Math.floor( (duration/(1000*60*60)) % 24 );
    var d = Math.floor( duration/(1000*60*60*24) );
    return d + ' days ' + h + ' hours ' + m + ' minutes ' + s  + ' seconds before the deadline.';   
    // 4 days 23 hours 59 minutes 59 seconds before the deadline.
@ToonLadyboy
Copy link

@ToonLadyboy ToonLadyboy commented Mar 24, 2018

Request one user twitter @Toon_Ladyboy

@alfalfarias
Copy link

@alfalfarias alfalfarias commented Nov 23, 2018

You can easily do something like:

    var duration = moment().add(5, 'days') -  moment().local();
    var s = Math.floor( (duration/1000) % 60 );
    var m = Math.floor( (duration/1000/60) % 60 );
    var h = Math.floor( (duration/(1000*60*60)) % 24 );
    var d = Math.floor( duration/(1000*60*60*24) );
    return d + ' days ' + h + ' hours ' + m + ' minutes ' + s  + ' seconds before the deadline.';   
    // 4 days 23 hours 59 minutes 59 seconds before the deadline.

Uggly, but effective.

@danmo
Copy link

@danmo danmo commented Jan 28, 2019

2019, still in need of this feature.

@gkatsanos
Copy link

@gkatsanos gkatsanos commented Mar 22, 2019

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

@drydenwilliams drydenwilliams commented Apr 30, 2019

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 louwers commented Jun 15, 2019

Can this be reopened? This is very basic functionality.

@schnetzi
Copy link

@schnetzi 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

@elhakeem elhakeem commented Jul 28, 2019

July 2019 and this feature still not available.

@gaycookie
Copy link

@gaycookie gaycookie commented Aug 28, 2019

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 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

@laggingreflex laggingreflex commented Sep 26, 2019

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

@kmjungersen
Copy link

@kmjungersen kmjungersen commented Nov 4, 2019

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 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

@justrealmilk justrealmilk commented Feb 13, 2020

2020

@vjfuenzalida
Copy link

@vjfuenzalida 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 only to format durations between 00:00 and 23:59)

@GuitooStephan
Copy link

@GuitooStephan GuitooStephan commented May 20, 2020

Still need this

@dazoliveira
Copy link

@dazoliveira dazoliveira commented May 20, 2020

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

@elisherer elisherer commented Aug 6, 2020

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

@Bassadin Bassadin commented Sep 10, 2020

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 Muskos commented Oct 9, 2020

The game is over :)

@dazoliveira
Copy link

@dazoliveira dazoliveira commented Oct 13, 2020

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

Successfully merging a pull request may close this issue.

None yet