7

I'm looking to find overlapping dates between two dates ranges as the following:

range1 = start(2016-06-01) end (2016-06-20)
range2 = start(2016-06-10) end (2016-06-13)

result here is 4 dates (2016-06-10,2016-06-11,2016-06-12,2016-06-13). another example:

 range1 = start(2016-06-01) end (2016-06-20) range2 = start(2016-06-18)
 end (2016-06-25)

result here is 3 dates (2016-06-18,2016-06-19,2016-06-20). and if no dates overlap then the result is 0 dates.

I found this post its helpful in determining the number of overlapping dates but I was wondering if I can get the actual dates without using long if/else statements?

Thanks in advance!

Community
  • 1
  • 1
tkyass
  • 2,968
  • 8
  • 38
  • 57

3 Answers3

3

I would suggest generating the dates in each of both ranges, then selecting the intersection between the two set. A snipped doing so may look like this:

from datetime import date, timedelta


def f(d1, d2):
    delta = d2 - d1
    return set([d1 + timedelta(days=i) for i in range(delta.days + 1)])

range1 = [date(2016, 6, 1), date(2016, 6, 20)]
range2 = [date(2016, 6, 10), date(2016, 6, 13)]

print f(*range1) & f(*range2)

For performance purposes you can also cast d1 + timedelta(days=i) to str while generating the dates in a given range.

kardaj
  • 1,897
  • 19
  • 19
  • Thanks Kardaj! just wondering about the performance part, why casting to string will improve performance? – tkyass Jun 23 '16 at 21:18
  • Because in the case that you're going to generate a lot of dates, you would prefer storing a less complex data-structure such as a string which for this case, won't lead to information loss. – kardaj Jun 24 '16 at 07:37
2

When you're not working with Python3 yet and thus cannot make use of the memory efficient range objects, you could make a namedtuple as shown in the answer you link to (otherwise you could just as well use the new range objects). From there, all you need to do is use datetime.date.fromordinal on the overlapping daterange:

>>> from datetime import date
>>> from collections import namedtuple
>>> Range = namedtuple('Range', ['start', 'end'])
>>> r1 = Range(start=date(2016, 1, 1), end=date(2016, 2, 5))
>>> r2 = Range(start=date(2016, 1, 28), end=date(2016, 2, 28))
>>> latest_start = max(r1.start, r2.start)
>>> earliest_end = min(r1.end, r2.end)
>>> overlap = (earliest_end - latest_start).days + 1
>>> overlapping_dates = [] # default
>>> if overlap > 0:
...     overlapping_dates = range(latest_start.toordinal(), earliest_end.toordinal() + 1) # as numbers
...     overlapping_dates = [ date.fromordinal(x) for x in overlapping_dates ] # back to datetime.date objects
...
>>> overlapping_dates
[datetime.date(2016, 1, 28),
 datetime.date(2016, 1, 29),
 datetime.date(2016, 1, 30),
 datetime.date(2016, 1, 31),
 datetime.date(2016, 2, 1),
 datetime.date(2016, 2, 2),
 datetime.date(2016, 2, 3),
 datetime.date(2016, 2, 4),
 datetime.date(2016, 2, 5)]

An approach using set will work too (one of those approaches is in this answer's edit history), but is usually less efficient, because it has to have all dates in memory, even those that are not in the intersection.

Oliver W.
  • 13,169
  • 3
  • 37
  • 50
1

I wrote a small function for this recently, which returns whether there's an overlap and then the from-to range

def date_overlap(start1, end1, start2, end2):
    overlaps = start1 <= end2 and end1 >= start2
    if not overlaps:
        return False, None, None
    return True, max(start1, start2), min(end1, end2)

Obviously the output format can be adapted to whatever is needed.

Norms
  • 41
  • 2