Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Checking if two 'time ranges' overlap with one another

Tags:

python

django

I have an object saved in db with time range (t1): 11:45-00:15. Now I have another time range from request : (t2) 00:05-00:10. What is most optimal way to find whether this new time range t2 overlaps with an already saved object with time ranges t1. This is the case of midnight, that is where the day is changing. For same day, i'm successfully able to find overlapping time ranges. I don't have a datetime field, rather i have time field only, hence i have to made do with what i already have.

In models t1 will be stored as:

start_time = TimeField(null=True, blank=True, db_index=True)
end_time = TimeField(null=True, blank=True, db_index=True)

so 11:45 will be stored in start_time and 00:15 will be stored in end_time

like image 610
Bolshoi Booze Avatar asked Mar 06 '19 13:03

Bolshoi Booze


People also ask

How do you know if two time ranges overlap?

1) Sort all intervals in increasing order of start time. This step takes O(nLogn) time. 2) In the sorted array, if start time of an interval is less than end of previous interval, then there is an overlap.

How do you know if two date ranges overlap in Excel?

To calculate the number of days that overlap in two date ranges, you can use basic date arithmetic, together with the the MIN and MAX functions. Excel dates are just serial numbers, so you can calculate durations by subtracting the earlier date from the later date.

How do you calculate range overlap?

There are two general approaches used to calculate home-range overlap: 1) calculate the percentage overlap at a given isopleth level (this works for geometric and probabilistic home ranges) or 2) calculate an index of similarity between the two utilization distributions (UD; this only works for probabilistic estimators ...

How do you find overlapping time intervals in SQL?

Basically, a period can be represented by a line fragment on time axis which has two boundaries; starttime and endtime. To claim two time periods to be overlapping, they must have common datetime values which is between lower and upper limits of both periods.

How to find if two date ranges can overlap?

One brief answer pointed to the best solution: Instead of trying to query for all the possible ways in which two date ranges could overlap, think of the scenarios in which they do not overlap. Low and behold, there are only two: Date Range A ends before Date Range B begins or Date Range A starts after Date Range B ends.

How to check if two intervals do not overlap?

There are only two cases where the intervals do not overlap, and they are mirror images of each other. Two intervals do not overlap when one ends before the other begins. Because either one can (a priori) be the one that ends first, this requires two checks, as coded here: I changed the name to hasOverlap as being simpler.

When does this function return “overlap”?

This function will return “Overlap” when it is not true that the two date ranges do not overlap. Like a magic trick, the revealed secret looks very simple and mundane.

How to check if any two intervals intersect among a given set?

Check if any two intervals intersects among a given set of intervals 1 Sort all intervals in increasing order of start time. This step takes O (nLogn) time. 2 In the sorted array, if start time of an interval is less than end of previous interval, then there is an overlap. More ...


2 Answers

I'm assuming that in cases when start_time is bigger than end_time, time range should just overlap, as the time was cyclic and we just don't care about date (in other words, those ranges represents events that are happening every day and we want to detect if any event overlaps with any other event)

With this assumption, here is quick answer:

if t2.start_time > t2.end_time:
    condition = (
        Q(start_time__gt=F('end_time')) |
        Q(start_time__lt=t2.end_time) |
        Q(end_time__gt=t2.start_time)
    )
else:
    condition = (
        Q(start_time__gt=F('end_time'), start_time__lt=t2.end_time) |
        Q(start_time__gt=F('end_time'), end_time__gt=t2.start_time) |
        Q(start_time__lt=t2.end_time, end_time__gt=t2.start_time)
    )

Explanation

There are 24 cases of arranging start times and end times of each event (excluding cases when any two dates are equal). From those 24 cases, 20 of them mean that there is an overlap and 4 of them mean that there is no overlap. As no overlap contains far less cases, lets look closer at it, so here are those 4 cases :

  1. t1.end_time < t2.start_time < t2.end_time < t1.start_time
  2. t1.start_time < t1.end_time < t2.start_time < t2.end_time
  3. t2.start_time < t2.end_time < t1.start_time < t1.end_time
  4. t2.end_time < t1.start_time < t1.end_time < t2.start_time

You can represent those cases in python code as follows (redundant whitespaces added to show how we can group them):

(                                t1.start_time > t2.end_time and t2.start_time > t1.end_time and t2.start_time < t2.end_time) or \ # 1.
(t1.start_time < t1.end_time                                 and t2.start_time > t1.end_time and t2.start_time < t2.end_time) or \ # 2.
(t1.start_time < t1.end_time and t1.start_time > t2.end_time                                 and t2.start_time < t2.end_time) or \ # 3.
(t1.start_time < t1.end_time and t1.start_time > t2.end_time and t2.start_time > t1.end_time                                )      # 4. 

As we can see, it can be simplified to tell that items are not overlapping if any 3 of those 4 conditions apply:

(
    t1.start_time < t1.end_time,
    t1.start_time > t2.end_time,
    t2.start_time > t1.end_time,
    t2.start_time < t2.end_time,
)

If we want to find out if 2 items are overlapping, we have to reverse this condition. So items are overlapping if any of 2 of those 4 conditions apply:

(
    t1.start_time > t1.end_time,
    t1.start_time < t2.end_time,
    t2.start_time < t1.end_time,
    t2.start_time > t2.end_time,
)

so full condition looks like:

t1.start_time > t1.end_time and t1.start_time < t2.end_time or \
t1.start_time > t1.end_time and t2.start_time < t1.end_time or \
t1.start_time > t1.end_time and t2.start_time > t2.end_time or \
t1.start_time < t2.end_time and t2.start_time < t1.end_time or \
t1.start_time < t2.end_time and t2.start_time > t2.end_time or \
t2.start_time < t1.end_time and t2.start_time > t2.end_time

Dressing it in django queryset, as we have 1 condition that we can check before executing it (we can check if start_time of t2 is lower than end_time of t2), we can divide it into 2 cases and simplify both of them.

if t2.start_time > t2.end_time:
    condition = (
        Q(start_time__gt=F('end_time')) |
        Q(start_time__lt=t2.end_time) |
        Q(end_time__gt=t2.start_time)
    )
else:
    condition = (
        Q(start_time__gt=F('end_time'), start_time__lt=t2.end_time) |
        Q(start_time__gt=F('end_time'), end_time__gt=t2.start_time) |
        Q(start_time__lt=t2.end_time, end_time__gt=t2.start_time)
    )

But what about equals?

Yes... We've skipped that... There are 3 decisions that we need to make to include them.

  1. Can any range have 0 length (start and end equal) and not collide with anything?
  2. Can any range have 24h length (start and end equal) and collide with everything?
  3. If start of one range is equal to end of another range, are they overlapping?

1st and second condition cannot be both yes. If you answered for all of those questions "no", then you're done, code shown above will work in your case. If you answered yes for any question, apply corresponding modification from list below:

  1. Add .exclude(start_time=F('end_time')) to the final queryset and assume that t2 is not overlapping with anything else if t2.start_time == t2.end_time and just skip the check
  2. Add | Q(start_time=F('end_time') to both conditions and assume that t2 is overlapping with everything else if t2.start_time == t2.end_time and just skip the check
  3. Replace every __gt with __gte and every __lt with __lte in both conditions, when there is t2 in right side of inner condition instead of F function.
like image 84
GwynBleidD Avatar answered Sep 28 '22 03:09

GwynBleidD


This logic should do it:

if (d1.start_time < d2.start_time < d1.end_time) or (d1.start_time < d2.end_time < d1.end_time) or (d2.start_time <= d1.start_time and d2.end_time >= d1.end_time): return True

Explanation:

First, check if the start and end times for d2 overlap w d1's time range. If it does, return True.

Next, see if d2 is either larger or the same as d1. If it is, return True.

Otherwise, these dates do not overlap.

like image 39
kellycup8 Avatar answered Sep 28 '22 03:09

kellycup8