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
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.
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.
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 ...
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.
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.
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.
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.
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 ...
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)
)
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 :
t1.end_time < t2.start_time < t2.end_time < t1.start_time
t1.start_time < t1.end_time < t2.start_time < t2.end_time
t2.start_time < t2.end_time < t1.start_time < t1.end_time
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)
)
Yes... We've skipped that... There are 3 decisions that we need to make to include them.
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:
.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| 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__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.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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With