1

I have a collection of time ranges in the form of Date objects. I need to compare those time ranges against a new time range to make sure I don't add a time range that overlaps or duplicates an existing time range.

Here's an example of the collection.

var timeSlots = [
    {
        BeginTime: new Date("10/25/2015 8:00 AM"),
        EndTime: new Date("10/25/2015 8:45 AM")
    },
    {
        BeginTime: new Date("10/25/2015 9:00 AM"),
        EndTime: new Date("10/25/2015 9:45 AM")
    },
    {
        BeginTime: new Date("10/25/2015 9:50 AM"),
        EndTime: new Date("10/25/2015 10:30 AM")
    }
];

In the previous example, the date part of the dates don't matter. The times do.

If I have an new time range object that begins at 9:30 AM, I need to be able to detect that.

This is inside of an AngularJS app and it has access to both MomentJS and Angular Moment.

How can I take a new time slot object and ensure that it won't conflict with the others that exist?

// building off of the previous example
var newTimeSlot = newDate("10/25/2015 9:00 AM");
if (!conflictingTime(newTimeSlot)) {
    // then run my code
}

function conflictingTime(time) {
    // how do I do this?
}
Will Strohl
  • 1,646
  • 2
  • 15
  • 32
  • 1
    Convert the stamps to _Date_ then you can treat them as integers, look here for more http://stackoverflow.com/a/22785208/1615483 – Paul S. Oct 25 '15 at 22:05
  • Those time stamps really are date objects. I've updated the question. – Will Strohl Oct 25 '15 at 22:19
  • 3
    moment.js has comparison methods ... read the docs. Then use array methods to check what alredy exists – charlietfl Oct 25 '15 at 22:19
  • http://momentjs.com/docs/#/query/is-between/ – Mark C. Oct 25 '15 at 22:23
  • If you are using a date library, then you should be using that to parse the strings, not *Date.parse*. There is no reason to expect an ambiguous string like "10/25/2015 8:00 AM" will be parsed correctly otherwise. – RobG Oct 26 '15 at 00:46

3 Answers3

0

Since this is for an open source project, I was hoping that someone here likes SO points enough to come up with a more "best practice" or more elegant solution than what I did. I guess everyone in the comments hates SO points though.

So here's my solution.

angular.forEach($scope.timeSlots, function (timeSlot, index) {
    var beginTimeHasIssue = ($scope.timeSlot.BeginTime >= timeSlot.BeginTime && $scope.timeSlot.BeginTime <= timeSlot.EndTime);
    var endTimeHasIssue = ($scope.timeSlot.EndTime >= timeSlot.BeginTime && $scope.timeSlot.EndTime <= timeSlot.EndTime);

    if (beginTimeHasIssue || endTimeHasIssue) {
        hasError = true;
        $scope.HasTimeSlotError = true;
        return;
    } 
});
Will Strohl
  • 1,646
  • 2
  • 15
  • 32
0

The way you're doing it is as suggested, however it doesn't seem robust, e.g. if there are no current slots the forEach won't do much. Also, it goes all the way to the end rather than stopping once it's found a slot. Consider the following:

var timeSlots = [
  {BeginTime: new Date("10/25/2015 8:00 AM"),
   EndTime: new Date("10/25/2015 8:45 AM")},
  {BeginTime: new Date("10/25/2015 9:00 AM"),
   EndTime: new Date("10/25/2015 9:45 AM")},
  {BeginTime: new Date("10/25/2015 9:50 AM"),
   EndTime: new Date("10/25/2015 10:30 AM")}
];

/*  @param   {Object} slot - object with BeginTime and EndTime date objects
 **  @returns {Boolean} true if slot fits, otherwise false
 */
function slotFits(slot) {
  if (timeSlots.length == 0) return true; // If no slots, must fit
  return timeSlots.some(function(s, i) {
    return (i == 0 && slot.EndTime <= s.BeginTime) || // slot is before all others
      (s.EndTime <= slot.BeginTime && !timeSlots[i + 1]) || // slot is after all others
      (s.EndTime <= slot.BeginTime && timeSlots[i + 1].BeginTime >= slot.EndTime) // Slot between others
  });
}

[{BeginTime: new Date("10/25/2015 7:00 AM"),  // before all, true
  EndTime: new Date("10/25/2015 8:00 AM")},
 {BeginTime: new Date("10/25/2015 10:30 AM"),  // after all, true
  EndTime: new Date("10/25/2015 11:00 AM")},
 {BeginTime: new Date("10/25/2015 8:45 AM"),  // between 0 and 1, true
  EndTime: new Date("10/25/2015 9:00 AM")},
 {BeginTime: new Date("10/25/2015 8:45 AM"),  // Overlap 1, false
  EndTime: new Date("10/25/2015 9:15 AM")}
].forEach(function(slot, i) {
  console.log('Slot ' + i + ': ' + slotFits(slot));
});

This could also be optimised to stop once it's passed all chance of finding a slot, requires two more lines of code. You should be able to adapt this to your angular code.

PS. I would not usually create Dates this way using a string (i.e. using the built-in parser for non-standard strings), but it's sufficient for this example. The Dates really should be created using something like:

var date = moment('10/25/2015 9:15 AM', 'MM/DD/YYYY h:mm Z').toDate();

console.log(date.toString());
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>
RobG
  • 142,382
  • 31
  • 172
  • 209
  • `timeSlots[i+1].BeginTime` is undefined. – user1027620 Jun 02 '18 at 21:20
  • @user1027620 - if that's the case, it’s testing the last range and the previous line applies and would have returned *true*. If any test returns *true*, the function returns *true*, otherwise it returns *false*. – RobG Jun 03 '18 at 04:30
  • So even with the error, the function still works properly? – user1027620 Jun 03 '18 at 08:09
  • @user1027620—there is no error. `timeSlots[i + 1]` is tested before `timeSlots[i + 1].BeginTime` is reached, so if it's *undefined*, the next test isn't executed. And even if it is, `timeSlots[i + 1].BeginTime` would resolve to *undefined* and `timeSlots[i + 1].BeginTime >= slot.EndTime` would resolve to *false*, which is consistent with the logic and certainly not an error. The test could be more specific, e.g. `typeof timeSlots[i + 1] == 'undefined'`, but I can't see the benefit of that. Simple type conversion to boolean in condition expressions is extremely common in javascript. – RobG Jun 03 '18 at 12:06
0
   addTimeSlot=function(newtimeSlot){
        var beginTimeHasIssue = false;
        var endTimeHasIssue = false;
        timeSlots.forEach(function (timeSlot, index) {
            if( newtimeSlot.BeginTime >= timeSlot.BeginTime &&  newtimeSlot.EndTime <= timeSlot.EndTime ) {
                 console.log('falls inbetween');
                endTimeHasIssue = true;
            } 
            if( newtimeSlot.BeginTime <= timeSlot.BeginTime &&  newtimeSlot.EndTime >= timeSlot.EndTime) {
                 console.log('falls inbetween');
                beginTimeHasIssue = true;
            }      
        });
        if (beginTimeHasIssue || endTimeHasIssue) {
            console.log('error');
            return;
        } else {
            timeSlots.push(newtimeSlot)
            console.log('saved');
        }
        if (timeSlots.length == 0) {
          timeSlots.push(newtimeSlot);
        }
    }