.humanize isn't really accurate when it comes to seconds.
moment.duration(40, 's').humanize() === "a few seconds" // expected "40 seconds"
moment.duration(45, 's').humanize() === "a minute" // expected "45 seconds"
I understand that we don't often want this level of accuracy but sometimes we do want the exact value in a human readable way.
I suggest a change to the API (which seems backwards compatible from what I actually see) to support this feature:
moment.duration(40, 's').humanize() === "a few seconds"
moment.duration(40, 's').humanize(true) === "in a few seconds"
moment.duration(40, 's').humanize({precise:true}) === "40 seconds"
moment.duration(40, 's').humanize({suffix:true, precise:true}) === "in 40 seconds"
moment.duration(40, 's').humanize({precise:false, suffix:true}) === "in a few seconds"
What do you think ?
@FGRibreau, this issue came up in #232, and the OP over there seemed to be OK not adding that to core.
You could write a language definition called precise-english
or something for when you want to more precise relative times. With the new instance lang feature coming out in 1.7.0, it could be pretty useful. Right now any language definition will inherit all non-specified values from English, so you would just have to change the relativeTime dictionary. Unfortunately, the inheritance doesn't inherit within dictionaries, so you have to specify the whole dictionary.
Please note: the method described in #232 will not work anymore in 1.7.0, because of the changes in #332.
moment.lang('precise-en', {
relativeTime : {
future : "in %s",
past : "%s ago",
s : "%d seconds", //see https://github.com/timrwood/moment/pull/232#issuecomment-4699806
m : "a minute",
mm : "%d minutes",
h : "an hour",
hh : "%d hours",
d : "a day",
dd : "%d days",
M : "a month",
MM : "%d months",
y : "a year",
yy : "%d years"
}
});
// one way
moment.lang('precise-en');
moment.duration({s: 40}).humanize();
// other way
moment.duration({s: 40}).lang('precise-en').humanize();
One thing that would make this easier would be for the inheritance to work so that you only have to specify just one value inside of the relativeTime
object. @timrwood, do you think we need _smarter_ inheritance?
If we add in smarter inheritance, I would want to possibly do something like CLDR does and have each sub-language inherit from the master language. So fr_CA
would inherit from fr
and en_GB
would inherit from en
.
I don't think we need to do deep extending to change out only 'relativeTime.s' though.
@FGRibreau did something along the lines of a precise-en
language definition work for you?
Closing this as @rockymeza's solution is the preferred way of handling this.
This applies to more than just seconds. The same applies to hours, years, etc...
Yeap, and the preferred solution is preferred only for english-speaking auditory. In other langs you have to bother yourself with noun inflections (declensions).
I add weight for this issue. When devising a countdown, the precision is needed.
So it's OK to have "10 hours ago" when looking at past things, but if eagerly waiting for an event, I want "in 10 hours 3 minutes 8 seconds"
Don't tell me that I have to write a custom language to obtain that ?
moment.duration(183, "minutes").humanize() // 3 hours
Would be nice to have de precise output like: 3 hours and 3 seconds.
I want this feature too.
I also need a precise humanize version!
I also think it would be nice for Moment's humanize to be (optionally) more precise, but for anyone who came here looking for a solution right now, I've found that HumanizeDuration.js works pretty well:
var
moment = require('moment'),
humanizeDuration = require('humanize-duration');
console.log(humanizeDuration(moment.duration('PT1H15M').asMilliseconds())); // '1 hour, 15 minutes'
// whereas:
console.log(moment.duration('PT1H15M').humanize()); // 'an hour'
Would love to see an "accurate humanize" option, having to use an additional library on top of moment is a little clunky..
+1 for precise humanize!
Really disappointed to see that there is no option to get the precise humanized duration. :|
There is some plugin called Precise Range referenced in the docs. Have anyone tried it?
Precise Range technically works, but it seems to return a string instead of a moment object, so I can't pick out specifically what date parts I might like.
The language (imo) is a bit off too, one result was - 'an hour 19 minutes a few seconds' . I'd prefer to see something like 1 hour 19 minutes and a few seconds. Mixing words and numbers has always been a bit of a grammatical no-no (as far as I know).
+1 for precise moment humanize
@Envek Precise Range seems nice, doesn't support any i18n/l10n though...
Official support by moment.js would be nice as I needed this in at least two or three of my projects.
@topaxi +1
+1
@doingweb I confirm that the HumanizeDuration.js module works pretty well !
+1 for precise moment humanize
+1 for more precise moment humanize
+1
+1
+1
+1
+1
+1
+1
Why is this getting closed all the time? This is the most obvious feature that must be implemented in moment.js. I was actually shocked that it isn't in already. Humanize is plain useless anywhere other than some vague time display like in posts on forums without it.
+1
+1
+1
-1. I like humanize how it works now. It's supposed to give a "human friendly" version of a duration.
"Hey Frank, when did you send that email?"
"About a minute ago."
vs
"Hey Frank, when did you send that email?"
"Forty three seconds ago."
Which seems more "human?"
More accurate version wasn't supposed to replace the current one. It's a request for new feature.
+1, @mattgrande whilst what we have now is great for describing the duration of something in the past, it's not appropriate for describing, say for example the length of an appointment, e.g. "1 hour, 30 minutes".
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1 for this from me. Making a new language definition, or modifying an existing one, does not really work. It looks like there are 108 languages currently supported by Moment, so we either have to modify 108 definitions manually (and hope we get them all grammatically correct), or just forget about any users who don't speak English? Neither of those sounds like a good solution...
I am using this:
````javascript
var humanizeDuration = function(eventDuration){
eventMDuration = moment.duration(eventDuration, 'seconds');
eventDurationString = ""
if (eventMDuration.days() > 0)
eventDurationString += " " + moment.duration(eventMDuration.days(), 'days').humanize()
if (eventMDuration.hours() > 0)
eventDurationString += " " + moment.duration(eventMDuration.hours(), 'hours').humanize()
if (eventMDuration.minutes() > 0)
eventDurationString += " " + moment.duration(eventMDuration.minutes(), 'minutes').humanize()
return eventDurationString.trim()
}
````
+1
+1 without this feature, time tracking is not possible.
Why issue is closed?
+1
+1
+1
The ability to optionally add a more precise, while still 'human' option would be super valuable.
+1
+1
+1
+1
+1. Working on accountancy application that requires to display a duration accurately but in a human format. "1 year, 2 months, 3 days" is better than "398 days", however "1 year" isn't really acceptable.
The current implementation is so human as to be basically useless.
+1
duration.humanize
has thresholds which define when a unit is considered a minute, an hour and so on. For example, by default more than 45 seconds is considered a minute, more than 22 hours is considered a day and so on. To change those cutoffs usemoment.relativeTimeThreshold(unit, limit)
where unit is one ofss
,s
,m
,h
,d
,M
.
https://momentjs.com/docs/#/customization/relative-time-threshold/
+1
+1
+1, please reconsider opening this issue 😍
+1
+1
+1
+1
+1
+1
I use something like this:
function stringifyDuration(duration) {
// Return a human readable string representing the given moment duration
// Split the duration into parts, and drop the most significant parts with a value of 0
const durationParts = _.dropWhile([
{ value: duration.years(), unit: 'year' },
{ value: duration.months(), unit: 'month' },
{ value: duration.days(), unit: 'day' },
{ value: duration.hours(), unit: 'hour' },
{ value: duration.minutes(), unit: 'minute' },
{ value: duration.seconds(), unit: 'second' },
{ value: duration.milliseconds(), unit: 'millisecond' },
], (part) => part.value === 0);
// Display up to two of the most significant remaining parts
return _.take(durationParts, 2).map((part) => (
`${part.value} ${pluralize(part.unit, part.value)}`
)).join(', ');
}
> stringifyDuration(moment.duration(26, 'hours'))
"1 day, 2 hours"
@timrwood a case for implementing this:
Hey Frank, I'm going to grab lunch, how much time do I have till next meeting?
an hour
Now, let's see what that "hour" can mean in `moment`:
```js
moment.duration({hours: 0, minutes: 45}).humanize() === "an hour"
moment.duration({hours: 1, minutes: 29}).humanize() === "an hour"
```
This means that if we understand things differently I could be ~45 minutes early or late for that meeting.
Thanks to @sproot for calling out RTFM
moment.relativeTimeThreshold('m', 60);
moment.duration({hours: 0, minutes: 45}).humanize() === "45 minutes"
moment.duration({hours: 0, minutes: 59}).humanize() === "59 minutes"
moment.duration({hours: 0, minutes: 60}).humanize() === "an hour"
moment.duration({hours: 1, minutes: 29}).humanize() === "an hour"
It's a bit better, but still problematic when the break length is longer than 60 minutes. That ~30 minute difference can mean I can go home, eat and come back, or I have to grab something nearby.
Workarounds of @Nerian https://github.com/moment/moment/issues/348#issuecomment-279819773 and @cogwirrel https://github.com/moment/moment/issues/348#issuecomment-346233713 combined with @sproot's tip gets a "good enough" implementation without needing an additional library (_ parts can be implemented using plain JS), but it's hack for something that could be supported directly:
var el = document.createElement('script');
el.src = "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js";
document.head.appendChild(el)
moment.relativeTimeThreshold('s', 60);
moment.relativeTimeThreshold('ss', 0); // must be after 's', disables "few seconds"
moment.relativeTimeThreshold('m', 60);
moment.relativeTimeThreshold('h', 24);
moment.relativeTimeThreshold('d', 31);
moment.relativeTimeThreshold('M', 12);
/**
* Return a precize human readable string representing the given moment duration.
*
* @param {Moment.Duration} duration
* @param {{mostPreciseUnit: string, numberOfSignificantParts: integer}} options
*/
moment.duration.fn.humanizePrecisely = function(options = {}) {
// Split the duration into parts to be able to filter out unwanted ones
const allParts = [
{ value: this.years(), unit: 'years' },
{ value: this.months(), unit: 'months' },
{ value: this.days(), unit: 'days' },
{ value: this.hours(), unit: 'hours' },
{ value: this.minutes(), unit: 'minutes' },
{ value: this.seconds(), unit: 'seconds' },
// cannot format with moment.humanize()
//{ value: duration.milliseconds(), unit: 'milliseconds' },
];
return _(allParts)
// only use the first parts until the most precise unit wanted
.take(_.findIndex(allParts, {unit: options.mostPreciseUnit || 'seconds'}) + 1)
// drop the most significant parts with a value of 0
.dropWhile((part) => part.value === 0)
// skip other zeroes in the middle (moment.humanize() can't format them)
.reject((part) => part.value === 0)
// use only the significant parts requested
.take(options.numberOfSignificantParts || allParts.length)
// format each part
.map((part) => moment.duration(part.value, part.unit).locale(this.locale()).humanize())
.join(' ');
}
moment.duration({hours: 2, minutes: 3, seconds: 4}).locale('de').humanizePrecisely()
// === "2 Stunden 3 Minuten 4 seconds"
(Note: There is something wrong with seconds formatting and locales, that'll be fixed by #3981 in #4183.)
For what it's worth, I just found this from the official documentation:
https://momentjs.com/docs/#/plugins/preciserange/
My own code looks like this -- and it works perfectly:
var d1 = new Date()
var d2 = new Date(durationInMinutes * 60 * 1000)
return moment.preciseDiff(d1 - d2)
I guess this effectively closes this issue...?
@mercmobily for AU, US (CA), UK: yes, ROW: not really; maybe when https://github.com/codebox/moment-precise-range/issues/6 is fixed.
(Sidenote: using moment
and *60*1000
looks a bit strange to me, have you considered moment().add(durationInMinutes, 'minutes')
?)
Ah, I am not very well versed in Moment! What does moment().add(durationInMinutes, 'minutes')
add to?
I think once that issue is closed, that's "the" solution to this issue...
moment()
is "now" (similar to new Date()
), and add
increases that by n
minutes. Any moment object can be used though, not limited to "now". (In order to keep this already long discussion focused, let this be the last comment related to this sidenote ;)
+1
+2
+1
+1
+1
Please reopen this. My software would like to show any sort of time interval correctly, and changing "every 12 seconds" to "every few seconds" or changing "every 54 seconds" to "every minute" is not acceptable.
+1
+1
I'm doing:
moment.duration('PT5S').humanize();
And I would like it to say: '5 seconds' but instead it says 'a few seconds'. When can we add this support?
+1
+1
Here's a small utility function based on @cogwirrel's that doesn't use lodash
import { pluralize } from '...'
export function humanize (duration) {
const durationComponents = [
{ value: duration.years(), unit: 'year' },
{ value: duration.months(), unit: 'month' },
{ value: duration.days(), unit: 'day' },
{ value: duration.hours(), unit: 'hour' },
{ value: duration.minutes(), unit: 'minute' },
{ value: duration.seconds(), unit: 'second' },
{ value: duration.milliseconds(), unit: 'millisecond' }
]
return durationComponents
.filter(({ value }) => value !== 0)
.slice(0, 3)
.map(({ unit, value }) => pluralize(value, unit))
.join(', ')
}
+1
(I'd argue that if you're going to have a humanize() method, then converting "1.51 hours" to "2 hours" is not particularly human-y).
+1 😞
Other libraries does not support such a big variety of languages like Moment.js does. Please reconsider!
+1
+1
+1
+1
P.S. unbelievable it's not yet implemented ;(
+1 really, this still isn't here?!
I'm finally unsubscribing (it's been roughly 7 years already, but I think I finally had it with all those +1s)
I found a package which solves my problems 🎉
https://github.com/EvanHahn/HumanizeDuration.js
Maybe it is useful for anyone else as well.
+1
+1
+1
+1
My take on that problem:
const units: Array<{unit: moment.unitOfTime.Base, key: moment.RelativeTimeKey}> = [
{unit: 'y', key: 'yy'},
{unit: 'M', key: 'MM'},
{unit: 'd', key: 'dd'},
{unit: 'h', key: 'hh'},
{unit: 'm', key: 'mm'},
{unit: 's', key: 'ss'},
];
function accurateHumanize(duration: moment.Duration, accuracy: number = 2): string {
let beginFilter = false;
let componentCount = 0;
return units
.map(({unit, key}) => ({value: duration.get(unit), key}))
.filter(({value, key}) => {
if (beginFilter === false) {
if (value === 0) {
return false;
}
beginFilter = true;
}
componentCount++;
return value !== 0 && componentCount <= accuracy;
})
.map(({value, key}) => ({value: value, key: value === 1 ? key[0] as moment.RelativeTimeKey : key}))
.map(({value, key}) => moment.localeData().relativeTime(value, true, key, true))
.join(', ');
}
This makes a lot of sense, would love to see a precise formatting of a duration.
Would love the option to humanize without rounding
+1
+1 on this
+1
+1
+1
+1
Just in case anyone lands here after googling, this config/workaround was sufficient in my case:
moment.relativeTimeRounding((t) => {
const DIGITS = 2; // like: 2.56 minutes
return Math.round(t * Math.pow(10, DIGITS)) / Math.pow(10, DIGITS);
});
moment.relativeTimeThreshold('y', 365);
moment.relativeTimeThreshold('M', 12);
moment.relativeTimeThreshold('w', 4);
moment.relativeTimeThreshold('d', 31);
moment.relativeTimeThreshold('h', 24);
moment.relativeTimeThreshold('m', 60);
moment.relativeTimeThreshold('s', 60);
moment.relativeTimeThreshold('ss', 0);
console.log(moment.duration(89, 's'));
// Output: "1.48 minutes"
// … without configuring the moment.relative*(), output was: "a minute"
because accuracy in my particular use case is much more important than a nice looking text.
Ans this is a comparison before and after configuring/invoking moment.relative*()
methods:
| duration | before | after |
| --------- | --------------- | -------------- |
| 3s
| a few seconds
| 3 seconds
|
| 44s
| a few seconds
| 44 seconds
|
| 45s
| a minute
| 45 seconds
|
| 1m 29s
| a minute
| 1.48 minutes
|
| 1m 30s
| 2 minutes
| 1.5 minutes
|
| 1m 59s
| 2 minutes
| 1.98 minutes
|
| 44m
| 44 minutes
| 44 minutes
|
| 45m
| an hour
| 45 minutes
|
| 1h 29m
| an hour
| 1.48 hours
|
| 1h 30m
| 2 hours
| 1.5 hours
|
| 21h
| 21 hours
| 21 hours
|
| 22h
| a day
| 22 hours
|
| 35h
| a day
| 35 hours
|
| 35h 30m
| a day
| 35.5 hours
|
| 36h
| 2 days
| 36 hours
|
Most helpful comment
Would be nice to have de precise output like: 3 hours and 3 seconds.