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 :
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)
)
But what about equals?
Yes... We've skipped that... There are 3 decisions that we need to make to include them.
- Can any range have 0 length (start and end equal) and not collide with anything?
- Can any range have 24h length (start and end equal) and collide with everything?
- 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:
- 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
- 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
- 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.