I have multiple date ranges. I want to check if they are overlapping in javascript. When there are only two it is easy, I use:
if(start_times1 <= end_times2 && end_times1 >= start_times2) {}
But what is the formula when there are more than 2 date ranges?
You can use nested for loops with arguments
function dateRangeOverlaps(a_start, a_end, b_start, b_end) {
if (a_start <= b_start && b_start <= a_end) return true; // b starts in a
if (a_start <= b_end && b_end <= a_end) return true; // b ends in a
if (b_start < a_start && a_end < b_end) return true; // a in b
return false;
}
function multipleDateRangeOverlaps() {
var i, j;
if (arguments.length % 2 !== 0)
throw new TypeError('Arguments length must be a multiple of 2');
for (i = 0; i < arguments.length - 2; i += 2) {
for (j = i + 2; j < arguments.length; j += 2) {
if (
dateRangeOverlaps(
arguments[i], arguments[i+1],
arguments[j], arguments[j+1]
)
) return true;
}
}
return false;
}
Here is refined version of what Paul posted:
Added filter and null check to allow any number of entries
Changed the logic so that it can be applied on an array. Eg: [{"from": value, "to": value}]
Adjusted overlap check to allow times having end and start as same
Script:
function dateRangeOverlaps(a_start, a_end, b_start, b_end) {
if (a_start < b_start && b_start < a_end) return true; // b starts in a
if (a_start < b_end && b_end < a_end) return true; // b ends in a
if (b_start < a_start && a_end < b_end) return true; // a in b
return false;
}
function multipleDateRangeOverlaps(timeEntries) {
let i = 0, j = 0;
let timeIntervals = timeEntries.filter(entry => entry.from != null && entry.to != null && entry.from.length === 8 && entry.to.length === 8);
if (timeIntervals != null && timeIntervals.length > 1)
for (i = 0; i < timeIntervals.length - 1; i += 1) {
for (j = i + 1; j < timeIntervals.length; j += 1) {
if (
dateRangeOverlaps(
timeIntervals[i].from.getTime(), timeIntervals[i].to.getTime(),
timeIntervals[j].from.getTime(), timeIntervals[j].to.getTime()
)
) return true;
}
}
return false;
}
Below code comes from my project, maybe it will help you:
function dateRangeOverlaps(startDateA, endDateA, startDateB, endDateB) {
if ((endDateA < startDateB) || (startDateA > endDateB)) {
return null
}
var obj = {};
obj.startDate = startDateA <= startDateB ? startDateB : startDateA;
obj.endDate = endDateA <= endDateB ? endDateA : endDateB;
return obj;
}
//storing existing dates for comparison
public multipleExistingDates=[
{startDate:'02/03/2020 05:00:00',endDate:'02/03/2020 05:30:00'},
{startDate:02/04/2020 05:00:00'',endDate:'02/05/2020 05:00:00'},]
/The date to be compared with existing dates to check if the new date is overlapping with existing dates/
public checkOverlappingDsates(startDate:Date, endDate:Date):boolean{
return this.multipleExistingDates.some((elem)=>{
return( !((moment(endDate).diff(moment(elem.startDate))) < 0 ||
(moment(startDate).diff(moment(elem.endDate))) > 0;})
Note: If the date is overlapping, the function return true else false. Also , you would need to install moment for date comparison.
Why don't we use moment and moment-range, is it not supported across all browsers? 🤔
window['moment-range'].extendMoment(moment);
const events1 = [{
"Date": "05/15/2021",
"EndTime": "17:00",
"StartTime": "16:00"
},
{
"Date": "05/15/2021",
"EndTime": "18:00",
"StartTime": "17:00"
},
{
"Date": "05/15/2021",
"EndTime": "18:45",
"StartTime": "17:45"
}
];
const events2 = [{
"Date": "05/15/2021",
"EndTime": "17:00",
"StartTime": "16:00"
},
{
"Date": "05/15/2021",
"EndTime": "18:00",
"StartTime": "17:00"
},
{
"Date": "05/15/2021",
"EndTime": "19:45",
"StartTime": "18:45"
}
];
function checkOverlap(timeSegments) {
var overlap = timeSegments
.map(r =>
timeSegments.filter(q => q != r).map(q =>
moment.range(
moment(q.Date + " " + q.StartTime),
moment(q.Date + " " + q.EndTime)
).overlaps(
moment.range(
moment(r.Date + " " + r.StartTime),
moment(r.Date + " " + r.EndTime)
)
)
)
);
console.log(overlap.map(x => x.includes(true)).includes(true));
}
checkOverlap(events1);
checkOverlap(events2);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-range/4.0.2/moment-range.js"></script>
Simply use the areIntervalsOverlapping function from date-fns, the "modern JavaScript date utility library".
You just have to pass the two dates as arguments to the function, and it will return true or false depending if the two dates overlaps or not.
Example
Check this example from their documentation:
areIntervalsOverlapping(
{ start: new Date(2014, 0, 10), end: new Date(2014, 0, 20) },
{ start: new Date(2014, 0, 17), end: new Date(2014, 0, 21) }
)
//=> true
This example above returned true because the two dates overlaps. Note that the 0 number (the second argument) in Date(2014, 0, 10) represents the month of January.
You can also use this areIntervalsOverlapping function to check if other time intervals (like hours in the same day) overlaps, because in JavaScript a Date object also considers hours.
Installation
If, for example, you are using Node.js (or any framework that uses it), you just have to install date-fns with
npm install date-fns --save
And then import the desired functions inside your JavaScript code like:
import { areIntervalsOverlapping } from "date-fns";
Of course date-fns is not limited to Node.js. You can use it inside any JavaScript project.
Wouldn't be too hard to do recursively. Make a method overlap which returns the overlapping daterange for two dates. Then in your hasOverlap(list dates) method, if the list is two items, it's simple, else, return hasoverlap(overlap(dates[0], dates[1]), rest of list)
No matter the language, the basic logic to see if two date ranges overlap is:
max(range_1_start, range_2_start) <= min(range_1_end, range_2_end)
In JavaScript syntax, that might look like this:
function doDatesOverlap(start_1,end_1,start_2,end_2){
return Math.max(start_1,start_2) <= Math.min(end_1,end_2);
}
var start_1 = new Date('01/01/2023');
var end_1 = new Date('01/31/2023');
var start_2 = new Date('01/15/2023');
var end_2 = new Date('02/15/2023');
if(doDatesOverlap(start_1,end_1,start_2,end_2)){
console.log('They overlap!');
}
Related
I am trying to implement a method which will return if a date is a holiday or not. For the purpose of this use case, a holiday is naively defined as all sundays, and both second and third saturdays. Here is a snippet of the code,
export const isHoliday = (date: moment.Moment | Date) => {
if (date instanceof Date) {
date = moment(date);
}
const dayOfWeek = date.day();
if (dayOfWeek >= 1 && dayOfWeek < 6) {
// it is a weekday
return false;
}
if (dayOfWeek === 0) {
// it is a sunday
return true;
}
if (dayOfWeek === 6) {
return isSecondSaturday(date) || isThirdSaturday(date);
}
};
And here are the other methods,
const isSecondSaturday = (date: moment.Moment) => {
return isNthSaturday(date, 2);
};
const isThirdSaturday = (date: moment.Moment) => {
return isNthSaturday(date, 3);
};
const isNthSaturday = (today: moment.Moment, n: number) => {
const thisMonth = today.clone().utc().startOf("month");
const firstSaturday = thisMonth.day(6); // <-- culprit?
const nthSaturday = firstSaturday.add(n - 1, "week");
return nthSaturday.date() === today.date();
};
In my testing, I found that the second and third Saturdays are not correctly identified for certain months. I think it could be something minor that I'm missing.
Could you please provide for which month the following implementation does not work? I've put your implementation to the console and checked first four months - it works fine.
function isHoliday(date) {
const dayOfWeek = date.day();
if (date instanceof Date) {
date = moment(date);
}
if (dayOfWeek >= 1 && dayOfWeek < 6) {
// it is a weekday
return false;
}
if (dayOfWeek === 0) {
// it is a sunday
return true;
}
if (dayOfWeek === 6) {
return isSecondSaturday(date) || isThirdSaturday(date);
}
};
function isSecondSaturday(date) {
return isNthSaturday(date, 2);
};
function isThirdSaturday(date) {
return isNthSaturday(date, 3);
};
function isNthSaturday(today, n){
const thisMonth = today.clone().utc().startOf("month");
const firstSaturday = thisMonth.day(6); // <-- culprit?
const nthSaturday = firstSaturday.add(n - 1, "week");
return nthSaturday.date() === today.date();
};
function checkHolidaysInMonth(monthIndex) {
var thisYear = moment().utc().startOf("year");
thisYear.add(monthIndex,'months');
for(var i = 0; i < 31; i++) {
if(isHoliday(thisYear) === true) {
console.log("Is 2nd or 3rd Sat or Sun: " + thisYear.format())
}
thisYear.add(1,'days');
}
}
checkHolidaysInMonth(3);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.27.0/moment.min.js"></script>
Okay, I have figured out the solution. I had been using another method in my code which would find the last working date. This involved some subtraction of date. This should have been isolated to the method, but since momentjs mutates the original moment, it caused a side effect whenever that function was called.
So if ever you are manipulating an argument, please use the clone() method (see docs) on it before you do the operation.
e.g. moment().clone()
I started learning JavaScript and as a part of a small project, I have an array of Date objects, and I am trying to search in it with binary search. I don't know why, but it became an infinite loop.
I know the problem is in the first if (if mid < right), because it works when the content of the if stands alone.
But if I put only the content, the function gets stuck when mid = right.
I don't see what in the if makes it an infinite loop or how to fix it.
I would greatly appreciate it if you could be of assistance.
**timeDiff is a function that returns which of the two dates are greater (later)
EDIT
I will try to describe the code better...
the function of the binary search receives an array of date objects (db = database), and one more date object (objDate), that the search should find.
I want the search function to return if the searched object is in the array (this is the true and false), and what is the index of it (or the index it should be in, if it is not on the array) - this is why I return also the mid.
I know it is not the best thing to return two values, but I need them two, so I put them in two variables when I call the function.
I add here the function timeDiff (that receives two Date objects and returns which of them is greater (later)) and an example for running.
function timeDiff(objDate, currDate) {
var _MS_PER_DAY = 86400000
var day1 = Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate(), objDate.getHours(), objDate.getMinutes());
var day2 = Date.UTC(currDate.getFullYear(), currDate.getMonth(), currDate.getDate(), currDate.getHours(), currDate.getMinutes());
return (day1 - day2) / _MS_PER_DAY ;
function binarySearch(db, objDate){
var left = 0, right = db.length ;
while (left <= right){
var mid = Math.trunc((left + right) / 2) ;
if (mid != right) {
var currDate = new Date(db[mid].date) ;
}
if (timeDiff(objDate, currDate) === 0){
return true, mid ;
}
if (timeDiff(objDate, currDate) < 0){
right = mid - 1 ;
}
if (timeDiff(objDate, currDate) > 0){
left = mid + 1 ;
}
}
(timeDiff(objDate, currDate) > 0) ? mid-- : mid = mid ;
return false, mid ;
}
a running example that should return "true, 0" (but actually does not finish the run) is:
var a = [{ "date": "2018-09-12", "appointments": [ { "subject": "Code review", "start": "09:00", "duration": 1.5 }, { "subject": "JavaScript objects", "start": "10:45", "duration": 2 } ] }] ;
var b = new Date(2018, 09, 12, 8, 0)
binarySearch(a, b)
It does end, it only loops twice.
function timeDiff(objDate, currDate) {
var _MS_PER_DAY = 86400000
var day1 = Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate(), objDate.getHours(), objDate.getMinutes());
var day2 = Date.UTC(currDate.getFullYear(), currDate.getMonth(), currDate.getDate(), currDate.getHours(), currDate.getMinutes());
return (day1 - day2) / _MS_PER_DAY ;
}
function binarySearch(db, objDate){
var left = 0, right = db.length ;
while (left <= right){
var mid = Math.trunc((left + right) / 2) ;
if (mid != right) {
var currDate = new Date(db[mid].date) ;
}
if (timeDiff(objDate, currDate) === 0){
return true, mid ;
}
if (timeDiff(objDate, currDate) < 0){
right = mid - 1 ;
}
if (timeDiff(objDate, currDate) > 0){
left = mid + 1 ;
}
console.log('looping');
}
(timeDiff(objDate, currDate) > 0) ? mid-- : mid = mid ;
return false, mid ;
}
var a = [{ "date": "2018-09-12", "appointments": [ { "subject": "Code review", "start": "09:00", "duration": 1.5 }, { "subject": "JavaScript objects", "start": "10:45", "duration": 2 } ] }] ;
var b = new Date(2018, 09, 12, 8, 0)
binarySearch(a, b)
You could use the find method of array
var a = [{ "date": "2018-09-12", "appointments": [ { "subject": "Code review", "start": "09:00", "duration": 1.5 }, { "subject": "JavaScript objects", "start": "10:45", "duration": 2 } ] }] ;
var b = new Date(2018, 09, 12, 8, 0)
console.log(a.find(item => item.date === `${b.getFullYear()}-${pad(b.getMonth())}-${pad(b.getDate())}`));
function pad(num) {
return num < 10 ? '0' + num : num;
}
I have a function which checks a number(quantity) against an array of quantity range: price
var data = {
"1 - 4": " $4.25 ",
"10 - 24": " $3.25 ",
"25 - 49": " $3.04 ",
"5 - 9": " $3.51 ",
"50+": " $2.84 "
}
function get_price(arr, val) {
var price = Object.keys(arr).reduce((a, c) => {
var s = c.trim();
if (/\d+\-\d+/.test(s)) {
var range = s.split("-");
if (val >= parseInt(range[0]) && val <= parseInt(range[1])) {
a = arr[c];
}
} else {
s = s.replace(/\D/g, "");
if (val >= s) {
a = arr[c];
}
}
return a;
}, 0);
return price;
}
The problem is that if a number is greater then 50 but less then 100 it is calculating correctly, over 100 it is not able to see the 50+ and use that price.
Any help is appreciated!
Use strings as your objects' keys, that will work fine:
const range = {
"1-4": " $4.25 ",
"10-24": " $3.25 ",
"25-49": " $3.04 ",
"5-9": " $3.51 ",
"50+": " $2.84 "
}
More about property accessors:
In this code, property must be a valid JavaScript identifier, i.e. a sequence of alphanumerical characters, also including the underscore ("_") and dollar sign ("$"), that cannot start with a number. For example, object.$1 is valid, while object.1 is not.
- MDN Web docs
Demo:
function get_price(arr, val) {
var price = Object.keys(arr).reduce((a, c) => {
var s = c.trim();
if (/\d+\-\d+/.test(s)) {
var range = s.split("-");
if (val >= parseInt(range[0]) && val <= parseInt(range[1])) {
a = arr[c];
}
} else {
s = s.replace(/\D/g, "");
if (val >= s) {
a = arr[c];
}
}
return a;
}, 0);
return price;
}
const range = {
"1-4": " $4.25 ",
"10-24": " $3.25 ",
"25-49": " $3.04 ",
"5-9": " $3.51 ",
"50+": " $2.84 "
}
console.log(get_price(range, 60))
console.log(get_price(range, 500))
Your code will not work only if you pass strings as the val parameter to the get_price as it will do string comparison between lets say "100" > "50" and it will fail.
Convert to numbers to be sure
if (+val >= +s) {
a = arr[c];
}
and
if (+val >= parseInt(range[0]) && +val <= parseInt(range[1])) {
a = arr[c];
}
I have a somewhat different approach. I'm assuming that the ranges list is likely to get reused. This solution uses a function that turns, say '25 - 49', into {start: 25, end: 49}, and uses it to create an array of objects such as {start: 25, end: 49, value: '$3.04'}. Then I return a function closing over this array that accepts a price and return the value of the first range which includes this price.
This has the (probably minor) advantage of not continually parsing the range strings. More importantly, it breaks the problem into several reusable pieces. makeRange might be reused elsewhere in a project, and so might findByRange. The only part of this specific to the current needs is getPrice, and that becomes fairly declarative.
const makeRange = (desc, match = '') =>
((match = desc.match(/(\d+)\s*\-\s*(\d+)/)) && match && {start: Number(match[1]), end: Number(match[2])})
|| ((match = desc.match(/(\d+)\s*\+/)) && match && {start: Number(match[1]), end: Infinity})
|| ((match = desc.match(/<\s*(\d+)/)) && match && {start: -Infinity, end: Number(match[1]) - 1})
|| {start: NaN, end: NaN}
const findByRange = rangeValues => {
const ranges = Object.keys(rangeValues).map(
key => Object.assign({value: rangeValues[key]}, makeRange(key))
).sort((a, b) => a.start - b.start) // sorting mostly for cleanliness
return val => {
const range = ranges.find(range => range.start <= val && val <= range.end)
return range ? range.value : false // or throw an exception, or use default
}
}
const getPrice = findByRange({
'< 5' : ' $4.25 ', // could also be '1 - 4'
'5-9' : ' $3.51 ',
'10-24' : ' $3.25 ',
'25-49' : ' $3.04 ',
'50+' : ' $2.84 '
});
[3, 7, 16, 42, 57, 143].forEach(
n => console.log(`${n} --> ${getPrice(n)}`)
)
I have an array of time ranges with start & end values.
var timeRanges = [{
start: 120,
end: 140
},{
start: 180,
end: 220
},{
start: 250,
end: 300
}]
Need to check whether the selected range overlap the time range or not. And also the selected range should be in between the timeRange intervals. (Ex: 140-180, 220-250)
var selected = {
start: 150,
end: 170
}
Time interval B 'overlaps' A if:
B starts after A starts but before A finishes.
B starts before A starts and finishes after A starts.
So you can write a function which decides exactly that.
function areOverlapping(A, B) {
if(B.start < A.start) {
return B.finish > A.start;
}
else {
return B.start < A.finish;
}
}
const overlaps = timeRanges.some( range =>
(range.start < selected.start && range.end > selected.start) ||
(range.start < selected.end && range.end > selected.end) ||
(range.start > selected.start && range.end < selected.end)
);
Assuming that your time ranges are sorted, this solution will work. Otherwise, you need to implement time range sorting as well.
function isValidRange(timeRanges, selectedRange) {
var isValid = true;
var minStart = timeRanges[0].start;
var maxEnd = timeRanges[timeRanges.length - 1].end;
if(selectedRange.start < selectedRange.end && selectedRange.start > minStart && selectedRange.end < maxEnd) {
for(var i=0; i<timeRanges.length; i++) {
if((selectedRange.start >= timeRanges[i].start && selectedRange.start <= timeRanges[i].end)
|| (selectedRange.end >= timeRanges[i].start && selectedRange.end <= timeRanges[i].end)) {
isValid = false;
break;
}
else if(i != timeRanges.length - 1) {
if(selectedRange.start > timeRanges[i].end && selectedRange.start < timeRanges[i+1].start) {
if(selectedRange.end < timeRanges[i+1].start) {
break;
}
else {
isValid = false;
break;
}
}
}
}
}
else {
isValid = false;
}
return isValid;
}
var timeRanges = [{
start: 120,
end: 140
},{
start: 180,
end: 220
},{
start: 250,
end: 300
}];
var selected = {
start: 141,
end: 222
};
alert(isValidRange(timeRanges, selected));
Why don't you run your selection through the array and calculate what you need?
timeRanges.forEach(function(aRange, index)) {
if (selected.start > aRange.start && selected.end < aRange.end)
console.log('Selection falls within the item ' + index):
}
I want to be able to test whether a value is within a number range. This is my current code:
if ((year < 2099) && (year > 1990)){
return 'good stuff';
}
Is there a simpler way to do this? For example, is there something like this?
if (1990 < year < 2099){
return 'good stuff';
}
In many languages, the second way will be evaluated from left to right incorrectly with regard to what you want.
In C, for instance, 1990 < year will evaluate to 0 or 1, which then becomes 1 < 2099, which is always true, of course.
Javascript is a quite similar to C: 1990 < year returns true or false, and those boolean expressions seem to numerically compare equal to 0 and 1 respectively.
But in C#, it won't even compile, giving you the error:
error CS0019: Operator '<' cannot be applied to operands of type 'bool' and 'int'
You get a similar error from Ruby, while Haskell tells you that you cannot use < twice in the same infix expression.
Off the top of my head, Python is the only language that I'm sure handles the "between" setup that way:
>>> year = 5
>>> 1990 < year < 2099
False
>>> year = 2000
>>> 1990 < year < 2099
True
The bottom line is that the first way (x < y && y < z) is always your safest bet.
You could make your own method:
// jquery
$(function() {
var myNumber = 100;
try {
if (myNumber.isBetween(50, 150))
alert(myNumber + " is between 50 and 100.");
else
alert(myNumber + " is not between 50 and 100.");
} catch (e) {
alert(e.message());
}
});
// js prototype
if (typeof(Number.prototype.isBetween) === "undefined") {
Number.prototype.isBetween = function(min, max, notBoundaries) {
var between = false;
if (notBoundaries) {
if ((this < max) && (this > min)) between = true;
alert('notBoundaries');
} else {
if ((this <= max) && (this >= min)) between = true;
alert('Boundaries');
}
alert('here');
return between;
}
}
hope this helps.
Max
The fast and simple way to make this is to create a function like this:
function inRange(n, nStart, nEnd)
{
if(n>=nStart && n<=nEnd) return true;
else return false;
}
Then use that as follows:
inRange(500, 200, 1000) => this return true;
Or like this:
inRange(199, 200, 1000) => this return false;
If you don't like the boolean operator, you could always use nested if statements:
if (1990 < year)
{
if( year < 2099)
return 'good stuff';
}
From a similar solution here: http://indisnip.wordpress.com/2010/08/26/quicktip-check-if-a-number-is-between-two-numbers/
$.fn.between = function(a,b){
return (a < b ? this[0] >= a && this[0] <= b : this[0] >= b && this[0] <= a);
}