duration formatting #1048

Open
mj1856 opened this Issue Sep 4, 2013 · 73 comments

Projects

None yet
@mj1856
Member
mj1856 commented Sep 4, 2013

We've touched on this before in #463 and #879 but never really talked about it directly in its own issue.

We need a method for formatting durations similar to how we format dates. It should be as simple as:

moment.duration(x).format("H:mm:ss")

Note that the formatting tokens would have to carry slightly different meaning, since we're representing an elapsed duration of time, rather than a time-of-day. I suggest the following:

  • hh would mean "hours remainder after accounting for days"
  • h would be the single digit form of hh
  • HH would mean "total whole hours"
  • H would be the single digit form of HH
  • HHH would mean "total hours including decimals" - although that can currently be done with duration.asHours(), so this might not be necessary.

Similar formatting would apply for other units. In this context, the highest unit would be a "day", which would be a standard day consisting of 24 standard hours. It wouldn't make sense to measure years or months with this due to calendaring issues.

Note, this came up recently (again) on StackOverflow in this question. The user was looking for something like duration.format("H:mm:ss"). This is the workaround:

Math.floor(duration.asHours()) + moment.utc(duration.asMilliseconds()).format(":mm:ss")

This works, but it feels hacky. This should be built in.

@sbruchmann

+1

@leo-mck
leo-mck commented Sep 5, 2013

moment.duration(x).format("H:mm:ss") was exactly what I was expecting, before understanding that it would not work. The workaround suggested by Matt worked fine but comparing with the elegance of other things in momentjs it sure seems hacky.

@robinvdvleuten

Isn't it better to do

moment.utc(total.asMilliseconds()).format("HH:mm:ss");

instead of

Math.floor(duration.asHours()) + moment.utc(duration.asMilliseconds()).format(":mm:ss")
@ghost
ghost commented Sep 7, 2013

Code Bounty

@mj1856
Member
mj1856 commented Sep 8, 2013

@RobinvdVleuten - The problem with using the HH in the current formatter is that it represents the hour portion of a full date and time. When dealing with an elapsed time, there could well be 24 hours or more. Try passing 24 hours in, and you'll see that it gets reset to zero.

@robinvdvleuten

I noticed it indeed. I'm now using the calculated milliseconds to format it, but it's still a hacky workaround.

@icambron
Member

Are you thinking there will just be one "level" of remaindering possible--e.g. no way to say "hours after accounting for months"? I think that's a perfectly reasonable limitation, but just to spitball a sec, I could imagine providing an "anchor" argument, like so:

moment.duration(x).format("hh:mm:ss", "M");

Which tells us that the hours should be modulo the month. The anchor is optional and would default to the largest unit plus one size up (in this case, days). Similarly, it could use the string itself to determine that the minutes are modulo the hours and seconds modulo minutes. Whereas if you did this:

moment.duration(x).format("hh:ss");

...the seconds would be modulo the hours.

That's probably all not a great idea--it's not ironed out (where to weeks go in this hierarchy?) and it's probably unnecessary (people are unlikely to want holes like that). But fun to think about.

@icambron
Member

On a more serious note, how are you going to deal with month vs minutes with this lowercase/uppercase notation?

@mj1856
Member
mj1856 commented Sep 16, 2013

I was envisioning that days would be the maximum unit size, so months wouldn't enter the picture. Basically, this is the inverse of how we parse durations coming from asp.net time spans.

@icambron
Member

Makes sense.

@lfnavess

I have been reading a SCORM 2004 RTE (p77) document, and i found that for time interval representation is as follows, i think this came from a standar,

in theory i can pass a value like: P34H
and the parsers should interpret that as 1day and 10 Hours

i hope it could be posible to format the ouput with this in mind
like 'PHM'
and the formatter interpret that as total Hours remaining minutes?
i would like to be able to format the output like HH:MM

i hope this info is helpfull

timeinterval (second, 10,2):The timeinterval (second, 10, 2)denotes that the value for
the data model element timeinterval represents elapsed time with a precision of 0.01
seconds[1]. The SCORM dot-notation binding defines a particular format for a
characterstring to represent a timeinterval.
The format of the characterstring shall be as follows:
P[yY][mM][dD][T[hH][nM][s[.s]S]] where:
• y: The number of years (integer, >= 0, not restricted)
• m: The number of months (integer, >=0, not restricted)
• d: The number of days (integer, >=0, not restricted)
• h: The number of hours (integer, >=0, not restricted)
• n: The number of minutes (integer, >=0, not restricted)
• s: The number of seconds or fraction ofseconds (real or integer, >=0, not
restricted). If fractions of a second are used, SCORM further restricts the string to
a maximum of 2 digits (e.g., 34.45 – valid, 34.45454545 – not valid).
• The character literals designators P, Y, M, D, T, H, Mand Sshall appear if the
corresponding non-zero value is present.
• Zero-padding of the values shall be supported. Zero-padding does not change the
integer value of the number being represented by a set of characters. For
example, PT05H is equivalent to PT5H and PT000005H.
Example:
• P1Y3M2DT3H indicates a period of time of 1 year, 3 months, 2 days and 3 hours
• PT3H5M indicates a period of time of 3 hours and 5 minutes

Implementers should be aware that the format and binding is for the communication of
the data between a SCO and an LMS. Since the format is representing a period of time,
then a duration like PT5M is equivalent to PT300S.
If the data model element, that is of type timeinterval(second,10,2) contains a value, then
• The designator Pshall be present;
• If the value of years, months, days, hours, minutes or seconds is zero, the value
and corresponding character literal designation may be omitted, but at least one
character literal designator and value shall be present in addition to the designator
P;
• The designator Tshall be omitted if all of the time components (hours, minutes
and seconds) are not used. A zero value may be used with any of the time
components (e.g., PT0S).

@quietmint
Contributor

Duration formatting would be an excellent addition. There's no need for two distinct "hour" tokens. Assume a duration of 32 hours. You'd never want to extract just 8 hours without also extracting 1 day.

I think @icambron has a good suggestion. Parse the format string into tokens and express the duration in terms of the largest unit. Parse the tokens one at a time in order (e.g., "DD:hh:ss" meaning number of complete days, number of complete hours after accounting for days, number of complete seconds after accounting for days + hours).

@jsmreese
Contributor

I've posted a moment.duration.format plugin:
https://github.com/jsmreese/moment-duration-format

I think it addresses most of the ideas / use-cases in this thread.

@hotzenklotz

@jsmreese Have you consider filing a pull request that contains your plugin as a core part of momentjs?

@jsmreese
Contributor

@hotzenklotz Yes I have considered filing a pull request.

I haven't done so because of all the reasons @icambron laid out in #1538.

My plugin:

  • depends on Lo-Dash
  • does not look and feel like Moment.js code
  • uses a completely different test setup

I would love for my plugin to become part of Moment.js core... but I'm not going to waste their time with a pull request before those issues are addressed.

@schmod
schmod commented Mar 19, 2014

We would also want to make sure that the code can be internationalized. Fortunately, most of the strings we would need are in the Unicode CLDR, so very little translation work should be necessary.

The CLDR also has locale-specific recommendations on how to format certain kinds of intervals, which could potentially be more useful than arbitrary duration formats. Not sure how that fits in here, but it may be nice to automagically be able to display locale-specific intervals between two specific times.

The CLDR also has information about how to display specific time intervals (rather than durations) which could be useful at some point...

English(US) Calendar Data

@jsmreese
Contributor

Just published a new version of Moment Duration Format that removes the previous dependency on Lo-Dash or Underscore.

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

@nathanhoel

@jsmreese Suits our needs perfectly. Thanks!

@outaTiME

+1

@mhayes14

+1

@rsweetland

+1

@larssondaniel

+1

@mlegenhausen
Contributor

+1

@oznu
oznu commented May 19, 2015

+1

@rhobot
rhobot commented May 22, 2015

+1

@diegoleme

+1

@schmod
schmod commented May 28, 2015

I probably sound like a broken record on this issue, but I would like to ensure that any solution follows existing conventions wherever possible, and can easily be internationalized (which includes adding symbols similar to LLLL for commonly-used duration formats.

Somewhat annoyingly, the CLDR doesn't provide many specific guidelines for formatting durations, although there are extensive relative duration and interval guidelines which could be of use to moment elsewhere. However, they do provide some minimal guidelines and translations for duration units, which would allow you to concatenate the relevant units when implementing something like humanize().

The hh / h syntax described above feels like a significant departure from the formatting tokens used by ISO8601, CLDR, and Apache, and I would prefer to avoid it if at all possible.

A better proposal might be to infer to use the most significant unit in the formatting pattern as the modulo, so h:mm:ss would display a "normal" time, but count hours past 24 (eg 26:30:00). It's unclear how one would even compute a pattern like "HH:MM:ss", or what the use case for that would be. Allowing developers to override this behavior also seems like it could easily become a source of bugs.

Keeping in the spirit of "i18n everywhere," the CLDR defines duration formats for:

  • Hours + minutes ( h:mm in EN-US)
  • Hours + minutes + second (h:mm:ss in EN-US)
  • Minutes + seconds (m:ss in EN-US)
    and it might make sense to provide locale-specific constants (similar to the LLL date formats) for these time durations.

Unfortunately, formatting durations with units greater than hours is really difficult to express via a single formatting string (thanks to the pluralization rules that you'd need to consider), and I wasn't able to find any library in any language that allows for easy, i18n-friendly formatting of durations longer than 24 hours. The best that you can do would be to extend duration.humanize() to take some extra parameters, effectively implementing the original proposal in #463.

In short, it might not be a good idea to implement duration.format(), as I see any potential solution having considerable pitfalls. Your best bet would be to improve duration.humanize(), or implement a scaled-back duration.format() that only understands hours, minutes, and seconds.

@pracucci

+1

In case you need a quick function to add padding:

function padWithZero(input, length) {
    // Cast input to string
    input = "" + input;

    let paddingSize = Math.max(0, length - input.length);
    return new Array(paddingSize > 0 ? paddingSize + 1 : 0).join("0") + input;
}
@bsr203
bsr203 commented Jun 4, 2015

+1

@FourwingsY

+1

@mj1856 mj1856 referenced this issue in moment/momentjs.com Jun 17, 2015
Closed

List moment-duration-format plugin #204

@thomasplevy

+1

@g1eb
g1eb commented Jul 6, 2015

+1

@prusswan
prusswan commented Jul 7, 2015

+1

@khajavi
khajavi commented Jul 13, 2015

+1

@thure
thure commented Jul 23, 2015

+1

@cosimaradu

+1

@jpogorman

+1

@AidasK
AidasK commented Sep 2, 2015

+1

@larikitty

+1

@josx
josx commented Sep 4, 2015

👍

@johnculviner

👍 surprised moment doesn't do this, usually it's never failed me!

@moses-seeq

+1 I have used moment-duration-format, noted above, but it is not internationalized. I am trying to output days and am likely to need months as well, which really need labels.

@AlesJiranek

Is there any progress with this issue?

@fdidron
fdidron commented Oct 14, 2015

+1

@mkhahani

+1

@Slind14
Slind14 commented Oct 28, 2015

👍

@evil-shrike

+1
why is it still not in the core?

@dzendras

+1 +1 +1 +1 +1

@jimutt
jimutt commented Nov 22, 2015

+1

@pfarnach

+2

@rumeshwick

@jsmreese does ur plugin support i18n?

@jsmreese
Contributor

@rumeshwick: maybe? That really depends on how you're doing i18n and what you're expecting from my plugin.

@nathancahill

+1

@vanthome

I found out that it's possible in this kind of hacky way:

  var dur = moment.duration('PT90M');
  moment(dur._data).format('[it\'s] D [days and] h [hour]');

This yields:

"it's 1 days and 1 hour"

You can not, however, print something like "it's 1 days and 90 Minutes".
For my purposes this is enough.
It does not include i18n but in my case I don't want this to be solved by moment.js.

Hi have an Ember.JS helper over here for this:

https://github.com/ember-building-blocks/ember-hbs-date-helpers

@niemyjski

+1

@goodseller

+1 for the hacky way of @vanthome 👍

@mj1856
Member
mj1856 commented Jan 13, 2016

@jsmreese - would you be interested in merging your plugin into moment core? It would be very useful, and much appreciated.

It would need to be reformatted to fit the new ES6 implementation, and have some redundant functions replaced with moment's equivalent, but overall I think it would be fairly easy.

@jsmreese
Contributor

@mj1856 definitely interested. I'll ping you over email with a few questions.

@larikitty

Yay! Way to go, @jsmreese and @mj1856!!! 👏 👏 👏

@gauravagarwalr

I came here just to propose to merge @jsmreese's plugin to moment js.

@thomasInes

+1

@manakor
manakor commented Mar 4, 2016

+1

@mj1856 mj1856 added Up-For-Grabs and removed todo labels Mar 8, 2016
@mj1856
Member
mj1856 commented Mar 8, 2016

Since it looks like @jsmreese is short on time, I'm marking this as Up For Grabs. Essentially, the proposed PR should implement all of the functionality of @jsmreese's moment-duration-format plugin, but should conform to the ES2015 style now being used in moment.js, and should re-use as much of the existing moment.js functionality to minimize code size.

@vinyoliver

+1

@rkunnamp

One feature that @jsmreese's moment-duration-format plugin lacks, is the ability to construct back a duration object from the formatted string.

@joshrowley

hey @mj1856 , i'm interested in tackling integrating the format plugin into moment. how does contributing work here with the Up-For-Grabs label, should I just work on it and submit the PR to the develop branch, or has someone else claimed it?

@maggiepint
Member

@joshrowley it's not claimed, but you would be a hero if you got it done. Go ahead and take it, and when you're ready submit a pull. This one will be a little trickier than other up for grabs, so if you want to, feel free to submit a pull request for review before you are done - we'll keep an eye out for it. We will be picky about not exploding the size of the library with this one - it's already bigger than we would like. Please be mindful of that.

@miloszsobczak

Maybe I'll handle this as well (or instead), but first need to fit into whole moment dev structure.

@butterflyhug
Member

Work in progress PR: #3308

@marwahaha

Hi folks, I opened a PR at #3615 and would love some eyes on it!

@dshamim
dshamim commented Jan 3, 2017

perhaps this is related - issues encountered:
moment.duration(3500000).format("hh:mm", { forceLength: true })
which displays result : 58, rather than 00:58

http://stackoverflow.com/questions/41443233/using-moment-js-to-display-duration-in-format-00xx-when-duration-is-less-than-a

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