10

I have e.g. an array with 2 objects (myObject1 and myObject2 like ). Now when I add an third object I will check if time range overlaps. Actually I don't know how I can do this in a performant way.

var myObjectArray = [];
var myObject1 = {};
myObject1.startTime = '08:00';
myObject1.endTime = '12:30';
...

var myObject2 = {};
myObject2.startTime = '11:20';
myObject2.endTime = '18:30';
...

myObjectArray.push(myObject1);
myObjectArray.push(myObject2);
GillesC
  • 10,647
  • 3
  • 40
  • 55
quma
  • 5,233
  • 26
  • 80
  • 146

7 Answers7

12

Let assume we have some intervals

const INTERVALS = [
  ['14:00', '15:00'],
  ['08:00', '12:30'],
  ['12:35', '12:36'],
  ['13:35', '13:50'],
];

If we want to add new interval to this list we should check if new interval is not overlapping with some of them.

You can loop trough intervals and check if the new one is overlapping with others. Note that when comparing intervals you do not need Date object if you are sure it is the same day as you can convert time to number:

function convertTimeToNumber(time) {
  const hours = Number(time.split(':')[0]);
  const minutes = Number(time.split(':')[1]) / 60;
  return hours + minutes;
}

There are two cases where intervals are NOT overlapping:

  1. Before (a < c && a < d) && (b < c && b <d):
a          b
|----------|
             c          d
             |----------| 
  1. After where (a > c && a > d) && (b > c && b > d):
             a          b
             |----------|
c          d
|----------| 

Because always c < d, it is enough to say that condition for NOT overlapping intervals is (a < c && b < c) || (a > d && b > d) and because always a < b, it is enough to say that this condition is equivalent to:

b < c || a > d

Negation of this condition should give us a condition for overlapping intervals. Base on De Morgan's laws it is:

b >= c && a <= d

Note that in both cases, intervals can not "touch" each other which means 5:00-8:00 and 8:00-9:00 will overlap. If you want to allow it the condition should be:

b > c && a < d

There are at least 5 situation of overlapping intervals to consider:

a          b
|----------|
      c          d
      |----------| 
      a          b
      |----------|
c          d
|----------| 
      a          b
      |----------|
c                    d
|--------------------|
a                    b
|--------------------|
      c          d
      |----------|
a          b
|----------|
c          d
|----------|

Full code with extra add and sort intervals functions is below:

    const INTERVALS = [
      ['14:00', '15:00'],
      ['08:00', '12:30'],
      ['12:35', '12:36'],
      ['13:35', '13:50'],
    ];


    function convertTimeToNumber(time) {
      const hours = Number(time.split(':')[0]);
      const minutes = Number(time.split(':')[1]) / 60;
      return hours + minutes;
    }

    // assuming current intervals do not overlap
    function sortIntervals(intervals) {
      return intervals.sort((intA, intB) => {
        const startA = convertTimeToNumber(intA[0]);
        const endA = convertTimeToNumber(intA[1]);

        const startB = convertTimeToNumber(intB[0]);
        const endB = convertTimeToNumber(intB[1]);

        if (startA > endB) {
          return 1
        }

        if (startB > endA) {
          return -1
        }

        return 0;
      })
    }


    function isOverlapping(intervals, newInterval) {
      const a = convertTimeToNumber(newInterval[0]);
      const b = convertTimeToNumber(newInterval[1]);

      for (const interval of intervals) {
        const c = convertTimeToNumber(interval[0]);
        const d = convertTimeToNumber(interval[1]);

        if (a < d && b > c) {
          console.log('This one overlap: ', newInterval);
          console.log('with interval: ', interval);
          console.log('----');
          return true;
        }
      }

      return false;
    }

    function isGoodInterval(interval) {
      let good = false;

      if (interval.length === 2) {
        // If you want you can also do extra check if this is the same day
        const start = convertTimeToNumber(interval[0]);
        const end = convertTimeToNumber(interval[1]);

        if (start < end) {
          good = true;
        }
      }

      return good;
    }

    function addInterval(interval) {
      if (!isGoodInterval(interval)) {
        console.log('This is not an interval');
        return;
      }

      if (!isOverlapping(INTERVALS, interval)) {
        INTERVALS.push(interval);

        // you may also want to keep those intervals sorted
        const sortedIntervals = sortIntervals(INTERVALS);
        console.log('Sorted intervals', sortedIntervals);
      }
    }


    // --------------------------------------
    const goodIntervals = [
      ['05:31', '06:32'],
      ['16:00', '17:00'],
      ['12:31', '12:34']
    ];

    let goodCount = 0;
    for (const goodInterval of goodIntervals) {
      if (!isOverlapping(INTERVALS, goodInterval)) {
        goodCount += 1
      }
    }

    console.log('Check good intervals: ', goodCount === goodIntervals.length);

    // --------------------------------------
    const ovelappingIntervals = [
      ['09:30', '12:40'],
      ['05:36', '08:50'],
      ['13:36', '13:37'],
      ['06:00', '20:00'],
      ['14:00', '15:00']
    ]

    let badCount = 0;
    for (const badInterval of ovelappingIntervals) {
      if (isOverlapping(INTERVALS, badInterval)) {
        badCount += 1
      }
    }

    console.log('Check bad intervals: ', badCount === ovelappingIntervals.length);

    // --------------------------------------
    addInterval(goodIntervals[0])
Konrad Grzyb
  • 1,561
  • 16
  • 12
  • the validate method is not correct : isGoodInterval(['23:00','01:31']); show false – Gogo Nov 02 '21 at 15:59
  • if you can change the method with this one for someone that search the correct unswer: function isGoodInterval(interval) { let good = false; if (interval.length === 2) { good = Date.parse("1-1-2000 " + interval[0]) > Date.parse("1-1-2000 " + interval[1]); } return good; } – Gogo Nov 02 '21 at 16:31
  • 2
    It is because it was suppose to be for the same day. Interval 23-01 is for another day. – Konrad Grzyb Nov 06 '21 at 06:51
9

You can try something like this:

var timeList = [];

function addTime() {
  var startTime = document.getElementById("startTime").value;
  var endTime = document.getElementById("endTime").value;

  if (validate(startTime, endTime)){
    timeList.push({
      startTime: startTime,
      endTime: endTime
    });
    print(timeList);
    document.getElementById("error").innerHTML = "";
    }
  else
    document.getElementById("error").innerHTML = "Please select valid time";
}

function validate(sTime, eTime) {
  if (+getDate(sTime) < +getDate(eTime)) {
    var len = timeList.length;
    return len>0?(+getDate(timeList[len - 1].endTime) < +getDate(sTime) ):true;
  } else {
    return false;
  }
}

function getDate(time) {
  var today = new Date();
  var _t = time.split(":");
  today.setHours(_t[0], _t[1], 0, 0);
  return today;
}
function print(data){
  document.getElementById("content").innerHTML = "<pre>" + JSON.stringify(data, 0, 4) + "</pre>";
}
<input type="text" id="startTime" />
<input type="text" id="endTime" />
<button onclick="addTime()">Add Time</button>
<p id="error"></p>

<div id="content"></div>
Rajesh
  • 24,354
  • 5
  • 48
  • 79
6

Use moment-js with moment-range (broken reference)

Tested example:

const range1 = moment.range(a, c);
const range2 = moment.range(b, d);
range1.overlaps(range2); // true

See more examples in https://github.com/rotaready/moment-range#overlaps

Note, for the above code to work maybe you first do:

<script src="moment.js"></script>
<script src="moment-range.js"></script>

window['moment-range'].extendMoment(moment);

HTML code

<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.12.0/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-range/2.2.0/moment-range.min.js"></script>

JavaScript code

var range  = moment.range(new Date(year, month, day, hours, minutes), new Date(year, month, day, hours, minutes));
var range2 = moment.range(new Date(year, month, day, hours, minutes), new Date(year, month, day, hours, minutes));
range.overlaps(range2); // true or flase

Pretty neat solution and momentjs comes with tons of date and time utilities.

David Silva-Barrera
  • 1,006
  • 8
  • 12
Daniel Wardin
  • 1,840
  • 26
  • 48
  • Thanks a lot for your answer. My problem is that the frameworks to use are defined an d moment-js is not in it. Is there any other smart solution doing this? Thanks! – quma Mar 15 '16 at 13:05
  • 1
    Hmm.. Uncaught TypeError: moment.range is not a function How to get it to work?.. UPD.: ups, I've forgot 'var'. All works – Gennady G Oct 19 '17 at 13:22
2

Use JavaScript Date() object to store time and then compare them if ending time of object1 is greater than starting time of object2 then they are overlapping. You can compare them using > operator.

date1.getTime() > date2.getTime()

Demonstration given here

Usage of Date object

Community
  • 1
  • 1
Rehan Haider
  • 893
  • 11
  • 26
0

Here's something that might work.

// check if time overlaps with existing times
for (var j = 0; j < times.length; j++) {
        let existing_start_time = moment(this.parseDateTime(this.times[j].start_time)).format();
        let existing_end_time = moment(this.parseDateTime(this.times[j].end_time)).format();

        // check if start time is between start and end time of other times
        if (moment(start_time).isBetween(existing_start_time, existing_end_time)) {
            times[i].error = 'Time overlaps with another time';
            return false;
        }

        // check if end time is between start and end time of other times
        if (moment(end_time).isBetween(existing_start_time, existing_end_time)) {
            times[i].error = 'Time overlaps with another time';
            return false;
        }

}

https://momentjs.com/

Skykid Felix
  • 177
  • 2
  • 14
0

You can check if there is an overlap by trying to merge a time range to the existing time ranges, if the total count of time ranges decrease after merge, then there is an overlap.

I found following articles which might help on handle merging ranges

  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Nov 02 '21 at 07:03
0

To determine whether the time range overlaps other time ranges you can utilize both moment.js and moment-range libraries.

First install moment-js and moment-range Given you have an INTERVALS array that contains example objects:

const INTERVALS = [
    { START: 0, END: 10 },
    { START: 12, END: 30 },
    ...
]

You can use a function below:

const validateIntervalOverlaps = () => {
if (INTERVAL_START && INTERVAL__END) {
  const timeInterval = moment.range(moment(INTERVAL_START), moment(INTERVAL_ENDS))

  const overlappingInterval = INTERVALS.find(intervalItem => {
    const interval = moment.range(moment(intervalItem.START), moment(intervalItem.END))
    return timeInterval.overlaps(interval)
  })

  return overlappingInterval
}

}

Next, you can do what you need to do with overlappingInterval :) F.e. determine if it exists or use it in any other way. Good luck!