简体   繁体   中英

python date interval intersection

As a matter of general interest I'm wondering if there's a more elegant/efficient way to do this. I have a function that compares two start/end tuples of dates returning true if they intersect.

from datetime import date
def date_intersection(t1, t2):
    t1start, t1end = t1[0], t1[1]
    t2start, t2end = t2[0], t2[1]

    if t1end < t2start: return False
    if t1end == t2start: return True
    if t1start == t2start: return True
    if t1start < t2start and t2start < t1end: return True
    if t1start > t2start and t1end < t2end: return True
    if t1start < t2start and t1end > t2end: return True
    if t1start < t2end and t1end > t2end: return True
    if t1start > t2start and t1start < t2end: return True
    if t1start == t2end: return True
    if t1end == t2end: return True 
    if t1start > t2end: return False

so if:

d1 = date(2000, 1, 10)
d2 = date(2000, 1, 11)
d3 = date(2000, 1, 12)
d4 = date(2000, 1, 13)

then:

>>> date_intersection((d1,d2),(d3,d4))
False
>>> date_intersection((d1,d2),(d2,d3))
True
>>> date_intersection((d1,d3),(d2,d4))
True

etc.

I'm curious to know if there's a more pythonic/elegant/more efficient/less verbose/generally better, way to do this with maybe mxDateTime or some clever hack with timedelta or set()?

An alternative and useful form would be for the function to return a start/end tuple of the intersection if one is found

Thanks

It's not really more Pythonic, but you can simply the logic to decide on an intersection somewhat. This particular problems crops up a lot:

return (t1start <= t2start <= t1end) or (t2start <= t1start <= t2end)

To see why this works think about the different possible ways that the two intervals can intersect and see that the starting point of one must always be within the range of the other.

An alternative and hopefully more intelligible solution:

def has_overlap(a_start, a_end, b_start, b_end):
    latest_start = max(a_start, b_start)
    earliest_end = min(a_end, b_end)
    return latest_start <= earliest_end

We can get the interval of the overlap easily, it is (latest_start, earliest_end) . Note that latest_start can be equal to earliest_end.

It should be noted that this assumes that a_start <= a_end and b_start <= b_end .

Here's a version that give you the range of intersection. IMHO, it might not be the most optimize # of conditions, but it clearly shows when t2 overlaps with t1. You can modify based on other answers if you just want the true/false.

if (t1start <= t2start <= t2end <= t1end):
    return t2start,t2end
elif (t1start <= t2start <= t1end):
    return t2start,t1end
elif (t1start <= t2end <= t1end):
    return t1start,t2end
elif (t2start <= t1start <= t1end <= t2end):
    return t1start,t1end
else:
    return None
intersection_length = min(t1end, t2end) - max(t1start, t2start)
return intersection_length >= 0 

This will accept an intersection consisting of a single point; otherwise use > 0 .

The intuition is that an intersected interval, if it exists, starts at the highest start and ends at the lowest end. There are two possibilities:

  1. It is a correct interval, possibly of length zero.

  2. The start and end points end up being switched. This means there is a gap between the two intervals, which you might call a "negative intersection".

Final Comparison: start <= other_finish and other_start <= finish

# All of the conditions below result in overlap I have left out the non overlaps

start <= other_start | start <= other_finish | other_start <= finish | finish <= other_finish 

      0                        1                        1                        0                   
      0                        1                        1                        1
      1                        1                        1                        0          
      1                        1                        1                        1

Only the start <= other_finish and other_start <= finish need to be true to return an overlap.

if t1end < t2start or t1start > t2end: return False
if t1start <= t2end or t2start <= t1start: return True
return False

Won't that cover all intersecting sets?

Very late answer, but it seems to me that the simplest and least head-scratching solution is:

def date_intersection(t1, t2):
    t1, t2 = sorted([t1, t2], key=lambda t: t[0])
    t1end = t1[1]
    t2start = t2[0]
    return t1 end <= t2start

I would probably also be looking at expressing the idea of a "time interval" as a class, so I could do something more like:

def date_intersection(interval1, interval2):
    i1, i2 = sorted([interval1, interval2], key=lambda i: i.start)
    return i1.end <= i2.start

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM