There is currently no way to determine whether a duration parse was successful, even by inspecting the fields.
I agree. We need an isValid method as a start.
I think invalid durations should throw exceptions and the current handling of invalid durations is a bug not an enhancement. Consider these cases:
`var wrong = moment.duration(3,'mintues');`
What will the result be? The docs don't define what happens on bad input.
This has a cascading effect, as duration
is used instead of add()
and subtract()
, so these have undefined behavior as well:
var hmm = moment().subtract(3,'mintues').toDate();
var uhoh = moment().add(3,'mintues').toDate();
From manual testing I can tell you what happens: moment
"succeeds" by using a duration of zero.
If moment()
had simply thrown an exception on bad input, these issue would have been identified immediately. Since it silently "succeeded", a class Garbage-In, Garbage-Out bug persisted.
Since the behavior of typo'ed duration is currently undefined, starting to throw exceptions on bad strings would be backwards compatible.
If a string is being used that is not trusted that may have a typo, try/catch
can be used explicitly when testing if the string can be used to describe a valid duration.
+1 @markstos.
What about supporting a strict
option (like moment itself for dates) that throws? Returning a duration of zero when parsing fails is quite dangerous.
A strict
option would be better than no change, but I don't think parsing invalid dates as zero duration is good default behavior in the first place.
Agreed. Would a possible change only land with [email protected]?
Why not use the same pattern as the rest of moment? Have an _isValid field and an isValid() method and set a duration as invalid if the parsing fails.
For reference, here are the other documented places where isValid() is used:
https://github.com/moment/momentjs.com/search?utf8=%E2%9C%93&q=isValid
Linking to documentation not the code was intentional, though the code mentions are relevant too.
+1
An ability to find out whether parsing was successful or not is very needed.
If backward compatibility is a concern then there could be possible to introduce a method Duration.parse(input: string, strict?: boolean = true): Duration
which would throw if input is incorrect and strict
argument was specified.
The current behavior when we're getting a Duration with all zeros for arbitrary data is very weird.
+1
Having an isValid() method on a duration (to see if parsing was successful) would be handy
Hi all, Moment 2.18.0 now has an .isValid
method for durations. However, it's pretty lenient. @markstos @theazureshadow and others, do try it out and let us know if you have suggestions.
@marwahaha,
As an initial test, I copy/pasted one of the examples above. Instead of returning true or false as expected, it throws an exception (with moment 2.18.0):
moment.duration(3,'mintues').isValid();
(Created the invalid duration still "succeeds" without throwing an exception, but now checking to see if it's valid throws an exception! )
Although it appears there aren't docs for the new method yet, so perhaps this isn't the intended use.
@markstos I just ran that code in the console of Momentjs.com - which has 2.18.0 - without an issue. Can we get more details on that?
Perhaps, that code should be invalid because minutes is spelled wrong.
On Mar 21, 2017 2:35 PM, "Maggie Pint" notifications@github.com wrote:
@markstos https://github.com/markstos I just ran that code in the
console of Momentjs.com - which has 2.18.0 - without an issue. Can we get
more details on that?—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/moment/moment/issues/1805#issuecomment-288176838, or mute
the thread
https://github.com/notifications/unsubscribe-auth/ACbGmWIf24KlRs8oiS2wTn206iJNMod7ks5roBhfgaJpZM4CRuVM
.
@maggiepint @marwahaha Yes, the misspelling of "minutes" IS why the duration should be invalid.
@markstos I can't reproduce that exception in the console of Momentjs.com either. (That's 2.18.1 now, though.) If you can still reproduce the exception when calling isValid, can we get more details?
(Obviously Mark wanted isValid
to return false
in this situation, but that's a separate issue from the exception. It's not 100% obvious to me how we should handle parsing validity with object inputs.)
I can't reproduce the exception either with 2.18.1. Perhaps it was a false alarm. I can reproduce @butterflyhug's result of having this kind of invalid duration returning "true" for "isValid()".
Shouldn't parsing fail in this case instead of returning "0 minutes" as a wrongly-valid output as the result?
Should is a different question, but IIRC what it does is interpret that
value as milliseconds because the unit isn't found in the unit hashtable.
Should it be invalid? IMO probably.
On Mar 22, 2017 8:42 AM, "Mark Stosberg" notifications@github.com wrote:
I can't reproduce the exception either with 2.18.1. Perhaps it was a false
alarm. I can reproduce @butterflyhug https://github.com/butterflyhug's
result of having this kind of invalid duration returning "true" for
"isValid()".Shouldn't parsing fail in this case instead of returning "0 minutes" as a
wrongly-valid output as the result?—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/moment/moment/issues/1805#issuecomment-288440827, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AFxi0nZAz8gwAJM8fVx5rYPyIWjeEfHMks5roUF4gaJpZM4CRuVM
.
There appears to be a bug here:
An assumption is made that there the string is valid and appears in the "duration" map. It seems like the fix is to add a check here to confirm that the string is a valid value to set. If not, set _isValid:false
This behavior is consistent with the standard isValid
method:
moment({'mintues': 3}).isValid()
> true
The source of this is normalizeObjectUnits
https://github.com/moment/moment/blob/497f918515ae6ab900f008f19523b1e4ae5e2450/src/lib/units/aliases.js#L14-L29 which only adds valid attributes (and drops the offending ones).
@maggiepint @ichernev should this behavior change? We are doing a breaking release soon...
Converting '3 mintues' to '0 minutes' and declaring the result "valid" is a bug. It may be a be a breaking change to fix it, but it's still a bug fix.
I agree that this validation behavior isn't ideal, but I disagree that this is necessarily a bug.
Consider the case where you parse something like moment({'minutes': 3, '$cacheKey': 92619502}).isValid()
. I strongly expect that we have users who appreciate having that parse as a valid moment, and I don't see a principled distinction between this example and the bug that you wrote.
The API we are discussing the is "object parser" for the moment
constructor, which is documented here:
http://momentjs.com/docs/#/parsing/object/
The documentation is currently silent on the treatment of passing unknown or additional arguments.
Right now, the behavior of Moment here can be described as "Garbage In, Garbage out". Garbage input is silently accepted and the result is "Garbage out"-- misspelled dates being converted to other dates and being considered valid!
These updates would allow a developer find out as soon as possible that they've made a mistake:
As long as Moment continues to accept "Garbage In" as valid, it will continue to be a disservice to developers and users are left with Moment wrongly reporting that broken dates are "valid".
This improvement seems well worth a "breaking" change.
I was just bitten by this: moment().subtract('1 day')
... this is not a valid way to express a duration. I would not expect it to be equivalent to .subtract(0)
though. IMO, the least-suprising thing to do in this case is to throw.
@johnvh I agree completely.
You know it's not a "valid way to express a duration" yet you write that code anyway? What do you expect? I assume you worked out it was not a valid way to express a duration from the excellent documentation momentjs authors provide?
On 25/04/2017, at 08:29, Mark Stosberg notifications@github.com wrote:
@johnvh I agree completely.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub, or mute the thread.
@simonfox developers are human. We make mistakes. That's why half our code base is tests. The Moment documentation /is/ excellent, but it doesn't help if you believe you've remembered the API correctly and don't need to reference the documentation. That's why it's helpful to not only have great documentation but also code that fails on invalid input instead of silently converting invalid input into broken "valid" input. If @johnvh had immediately received a validation failure when his code ran, he could have checked the excellent documentation to see what the problem might be. Unfortunately, Moment treated invalid input as valid and raised to no alarm.
@markstos exactly. I agree 100%.
This was a bug that had to be tracked down. We didn't write it that way knowing it was invalid of course. It looks right, as the moment manipulation methods are very liberal in their supported signatures. We also use agenda in the same project, which uses human-interval for converting natural language into durations. So we'll have something like this only lines apart:
moment().subtract('1 day');
agenda.processEvery('1 day');
The latter works as intended, the former does not. If the former raised an exception, it would have alerted us to our mistake.
Also, the fact that Moment /has/ an isValid() method implies that it validates values as either valid or invalid. In cases where invalid values are classified as valid, the isValid()
method appears not to work as documented.
Hi all, we'd love to review any PRs in this area!
I ran into this bug, or something similar to it. If I run moment.duration('3', 'minutes').asMinutes()
using Moment 2.18.1, I get a result of 0. I think that should either return 3 or throw an exception. At the very least, moment.duration('3', 'minutes').isValid()
should return false
.
The way Moment behaves now can cause a lot of trouble by causing bugs that may be hard to detect and diagnose, as several people here have mentioned.
Has this been addressed?
@TomJSmith A quick test shows the bug still exists. This code:
moment().subtract('1 day');
"succeeds", subtracting zero days instead of one day as expected. You can call .isValid()
on the resulting date and get a true
response back indicating the result is valid when it is not.
Current behaviour could lead to bad consequences in some use cases and should be clearly documented with a warning:
Use case
I want to delete resources based on a retention duration:
const duration = moment.duration('invalid');
const currentDate = moment();
const removeBeforeDate = moment().subtract(duration);
console.log(`currentDate : ${currentDate}`);
console.log(`removeBeforeDate : ${removeBeforeDate}`);
Result:
currentDate : Fri Apr 13 2018 13:15:04 GMT+0200
removeBeforeDate : Fri Apr 13 2018 13:15:04 GMT+0200
Consequence:
Every resources up to the current date are deleted...
So far, the only way I found to workaround this if to test the value after the parsing:
const duration = moment.duration('invalid');
if (duration.toISOString() === 'P0D') {
// throw an Error
}
Yes, this bug is bad and has been open since 2014. This is something that the "date-fns" projects gets right. They actually tell you if invalid input is invalid. https://date-fns.org/ Unfortunately, date-fns doesn't handle time zone conversions, but perhaps there's a way to handle those with an external library.
For the Moment project to fix this, they have to quit accepting garbage input and treating it as a valid date. While this seems like a better behavior, it's technically a "backwards breaking behavior" change, so it seems there's resistance to make the change. But given the project has not taken action in the last nearly four years, I wouldn't expect a fix soon.
Are there any updates on this?
With moment 2.23.0
the isValid()
function also return true
when trying to create a duration from an invalid ISO8601 string.
Example:
const mom = moment.duration('asdf')
console.log(mom.isValid()) // This returns true, expected would be false
Its kinda annoying imo. and completely defeats the purpose of the isValid()
function on Duration
objects (is there even a case, when isValid()
returns false
since moment does interpret even an invalid input?)
@robbiecloset The update is since my last post in April, the "date-fns" project has gotten better at time zone support. There are now "date-fns-timezone" and "date-fns-tz" projects to help with this.
I would consider using it if it's feasible to switch.
I use moment.duration(value).toISOString() === value
, that value is read from a configuration.
I use moment.duration(value).toISOString() === value, that value is read from a configuration.
@bors-ltd I think part of the problem is that moment might be able to decode two values that produce the same encoded value.
For example: moment.duration('PT0M0S').toISOString() === moment.duration('PT0S').toISOString()
.
If you look at the tests, it seems like invalid values are actually apart of the specification:
https://github.com/moment/moment/blob/2.24.0/src/test/moment/duration.js#L419-L420
Patterns that do not match the ISO duration standard are being tested as being "0" seconds.
Hello, we also had the same problem with the isValid()
method and end up doing it manually.
const isValidDuration = duration => {
return !!duration.match(
/^(-?)P(?=\d|T\d)(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)([DW]))?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?$/
);
};
Hope it could help anyone.
See https://momentjs.com/docs/#/-project-status/
Thanks for all the discussion here.
A hacky fix is to replace the isValid method with a method that assures that the sum of the data parts is > 0 (or at least likely invalid)
Most helpful comment
Are there any updates on this?
With
moment 2.23.0
theisValid()
function also returntrue
when trying to create a duration from an invalid ISO8601 string.Example:
Its kinda annoying imo. and completely defeats the purpose of the
isValid()
function onDuration
objects (is there even a case, whenisValid()
returnsfalse
since moment does interpret even an invalid input?)