Moment: Control over isBetween's exclusivity/exclusivity behavior

Created on 16 Jan 2015  ·  28Comments  ·  Source: moment/moment

The isBetween method checks if a moment is between two others, but not if it's equal to either of the comparison times. I think we need an inclusive argument, although with the third argument currently being the units, it may be best to create a separate isBetweenInclusive method.

Thanks

Enhancement Up-For-Grabs

Most helpful comment

This was a great feature and absolutely needed!!!!

All 28 comments

I would also like this.

I agree, this would be an incredible useful feature. Being able to choose between inclusive and exclusive isBetween, or pass inclusion as an boolean argument would be fantastic.

There's actually a bit more thought that needs to go into this function than what is currently offered or what is being proposed.

In real-world scenarios, the starting value is always inclusive, but the ending value is either inclusive or exclusive _depending on the granularity_. Specifically, if the value includes a time component, the end value should be exclusive. If it does not include a time component, then it should be _inclusive_.

Think about it this way. If I ask you how many days are there between Jan 1st and Jan 3rd - the answer is three days. But if I ask you how many hours there are between 1:00 and 3:00, the answer is two hours. Humans naturally do this. It has an impact on how we measure durations between intervals (such as with moment#diff), and how we test a value to see if its in range (such as with moment#inBetween).

The current behavior of moment#isBetween is fully exclusive, regardless of the granularity. This isn't very useful.

The current behavior of moment#diff is inclusive on the start, and exclusive on the end, again, regardless of granularity. This is valid for time, bug invalid for days. Indeed, the example in the docs is:

var a = moment([2007, 0, 29]);
var b = moment([2007, 0, 28]);
a.diff(b, 'days') // 1

When in reality, most people would expect the result to be 2 days.

The ultimate problem is that a moment object is a discreet unit of time, but in so many cases we try to treat it like a date on a calendar, and this is where the side effects start to become visible.

Think about it this way. If I ask you how many days are there between Jan 1st and Jan 3rd - the answer is three days. But if I ask you how many hours there are between 1:00 and 3:00, the answer is two hours.

I think the logical (and for me, expected) answer is "2" when asking a date library the amount of days between "Jan 1st" and "Jan 3rd". The reason being that without a time specified, I would expect it to be working out between "Jan 1st 00:00" and "Jan 3rd at 00:00". How humans work out the difference in the real world really depends on the context but I don't think that has anything to do with how things work in the programming world.

Here's how it's done for PHP's Carbon and I've never found this to be a problem: https://github.com/briannesbitt/Carbon/blob/master/src/Carbon/Carbon.php#L1070-1092

The second we merged isBetween I knew some people will want to specify different behavior for intervals, but I'm yet to see a good proposal. Ultimately both ends would be separately configurable (whether they are inclusive/exclusive).

lt should also be backwards compatible with the current version.

// proposal 1
m.isBetween(a, b, "()"); // both excluded
m.isBetween(a, b, "[)"); // start included, end excluded

// proposal 2
m.isBetween(a, b, "+"); // both included
m.isBetween(a, b, "+-"); // start included, end excluded

I can't think of a real-world scenario for excluding the start, so it could just be a boolean flag for whether or not to exclude the end.

Though - I kind of like proposal 1, as it is close to proper ISO 31-11 interval notation.. I'd want to support both options on either side if we go with this.

// these are essential
m.isBetween(a, b, "[]"); // both included
m.isBetween(a, b, "[)"); // start included, end excluded

// these would be rarely used, but complete the syntax
m.isBetween(a, b, "()"); // both excluded
m.isBetween(a, b, "(]"); // start excluded, end included

I don't really like proposal 2. (no offense)

:+1: for proposal one.

I honestly can't see any scenarios where you would explicitly want to exclude either end but not both. Even if you were trying to see "up until this day", you could use moment( date ).endOf( "day" ) as the end date, which would give you 11:59.59 PM.

I think there should just be a boolean flag to make it inclusive/exclusive, just like a "normal" range in most programming languages. Keep it simple to prevent confusion - if you want a time _just before_ an end time, there are other ways to get it.

@mckinnsb - That approach is usually avoided for two reasons:

  1. The precision becomes important. In JavaScript, the last possible time of a standard day would be 23:59:59.999, but in other languages it could be 23:59:59, or 23:59:59.9999999. We interact with values from other sources via string parsing and formatting, so this is important.
  2. If you define a range from 00:00:00.000 to 23:59:59.999 - you can't easily determine the duration of the range. Instead of just duration = end - start, you now have to do something like duration = end - start + epsilon, where epsilon is the minimal precision, as discussed above.

I'll also reject your statement that there is a "normal" range in most programming languages. In reality, many programming ranges do not have built-in range types. When they do, their behavior is language specific. There is no "normal".

See also this quora post about the range function in Python.

+1 for proposal 1, makes things flexible.
My first expectation was the same that the upper and lower limits would be included, just like I would do in SQL, X Between A AND B,
Currently working it around by this x.isBetween(a, b) || x.isSame(a) || x.isSame(b)

+1

+1

+1 proposal 1

+1

+1

+1

@mj1856 Isn't the default behavior '()'?

Is Between 2.9.0+
"Check if a moment is between two other moments, optionally looking at unit scale (minutes, hours, days, etc). **The match is exclusive.**"

isBetween already has a 3rd optional parameter. Currently the method is defined as function isBetween (from, to, units) where units is optional. This proposal results in a 4th optional parameter, so an implementation will have to handle two possible third options (exclusive/inclusive vs units) or all four.

Some workarounds (not tested):

() = x.isBetween(start, end) //fully exclusive - the default implementation at this time
(] = x.isAfter(start) && x.isSameOrBefore(end) //left exclusive, right inclusive
[) = x.isSameOrAfter(start) && x.isBefore(end) //left inclusive, right exclusive
[] = !(x.isBefore(a) || x.isAfter(b)) //fully inclusive

Maybe easier to read workarounds go like this:

() = x.isBetween(a,b)
(] = x.isBetween(a,b) || x.isSame(b)
[) = x.isSame(a) || x.isBetween(a,b)
[] = x.isBetween(a, b) || x.isSame(a) || x.isSame(b) // same as !(x.isBefore(a) || x.isAfter(b))

Since the 3rd and 4th parameters will both be optional, it might be good to pass in an object

var options = {
   units: 'milliseconds', // 'year', 'month', etc.
   inclusive: '{)' // '{}', '()', '(}', '{)'
}
m.isBetween(start, end, options)

Where the default units are milliseconds and the default inclusive is ().

Crap.. you guys are right. Sorry. Rewording...

Marking this up for grabs. The expected usage would be as proposal 1 above, which should allow a _fourth_ optional parameter passed to isBetween, containing one of '[]', '[)', '()', '(]'. It should include tests for all four. The default when not passed should be the same as '()', which is also the current behavior.

WRT the options - I really don't have a preference. One could just as well pass null in the third parameter to get at the fourth, or allow either item to be passed as the third parameter or fourth parameter, since we're limited to known values. The options object is fine as well. Whatever the implementer thinks is easiest - or heck, you could do all of the above.

Unless, of course, someone else has a strong opinion about that. :)

Good start in PR #2943. We'll track this in that PR. Thanks!

+1

I would have REALLY liked to have used the functionality in @darrenjennings commit today.

Are you talking about this made available in v2.13? @rbreier
https://momentjs.com/docs/#/query/is-between/

That is exactly what I was looking for. Thank you so much. I updated and this works perfectly for me.

This was a great feature and absolutely needed!!!!

The second we merged isBetween I knew some people will want to specify different behavior for intervals, but I'm yet to see a good proposal. Ultimately both ends would be separately configurable (whether they are inclusive/exclusive).

lt should also be backwards compatible with the current version.

// proposal 1
m.isBetween(a, b, "()"); // both excluded
m.isBetween(a, b, "[)"); // start included, end excluded

// proposal 2
m.isBetween(a, b, "+"); // both included
m.isBetween(a, b, "+-"); // start included, end excluded

console.log('isBetweenFlag', moment('2010-10-19').isBetween('2010-10-19', '2010-10-25',"+"));

when i am using above condition, it gets me first error and then condition is gonna be failed. i am using angular 6

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dbshwang picture dbshwang  ·  3Comments

dogukankotan picture dogukankotan  ·  3Comments

alvarotrigo picture alvarotrigo  ·  3Comments

nikocraft picture nikocraft  ·  3Comments

slavafomin picture slavafomin  ·  3Comments