Эффективный расчет перекрытия диапазона дат в Python? - PullRequest
68 голосов
/ 28 января 2012

У меня есть два диапазона дат, где каждый диапазон определяется начальной и конечной датой (очевидно, экземплярами datetime.date ()).Два диапазона могут перекрываться или нет.Мне нужно количество дней наложения.Конечно, я могу предварительно заполнить два набора всеми датами в обоих диапазонах и выполнить пересечение наборов, но это, возможно, неэффективно ... Есть ли лучший путь помимо другого решения, использующего длинный раздел if-elif, охватывающий все случаи?

Ответы [ 5 ]

144 голосов
/ 28 января 2012
  • Определите самую позднюю из двух дат начала и самую раннюю из двух дат окончания.
  • Вычислите временную дельту, вычитая их.
  • Если дельта положительна, этоколичество дней перекрытия.

Вот пример расчета:

>>> from datetime import datetime
>>> from collections import namedtuple
>>> Range = namedtuple('Range', ['start', 'end'])

>>> r1 = Range(start=datetime(2012, 1, 15), end=datetime(2012, 5, 10))
>>> r2 = Range(start=datetime(2012, 3, 20), end=datetime(2012, 9, 15))
>>> latest_start = max(r1.start, r2.start)
>>> earliest_end = min(r1.end, r2.end)
>>> delta = (earliest_end - latest_start).days + 1
>>> overlap = max(0, delta)
>>> overlap
52
8 голосов
/ 28 января 2012

Вызовы функций дороже, чем арифметические операции.

Самый быстрый способ сделать это включает 2 вычитания и 1 мин ():

min(r1.end - r2.start, r2.end - r1.start).days + 1

по сравнению со следующим лучшим, который требует 1 вычитания, 1 мин () и макс ():

(min(r1.end, r2.end) - max(r1.start, r2.start)).days + 1

Конечно, с обоими выражениями вам все равно нужно проверить на положительное совпадение.

4 голосов
/ 15 января 2018

Я реализовал класс TimeRange, как вы можете видеть ниже.

get_overlapped_range сначала отменяет все неперекрывающиеся опции простым условием, а затем вычисляет перекрывающийся диапазон, рассматривая все возможные опции.

чтобы получить количество дней, в течение которых вам нужно взять значение TimeRange, которое было возвращено из get_overlapped_range, и разделить длительность на 60 * 60 * 24 (:

class TimeRange (object):

def __init__(self, start, end):
    self.start = start
    self.end = end
    self.duration = self.end - self.start

def is_overlapped(self, time_range):
    if max(self.start, time_range.start) < min(self.end, time_range.end):
        return True
    else:
        return False

def get_overlapped_range(self, time_range):
    if not self.is_overlapped(time_range):
        return

    if time_range.start >= self.start:
        if self.end >= time_range.end:
            return TimeRange(time_range.start, time_range.end)
        else:
            return TimeRange(time_range.start, self.end)
    elif time_range.start < self.start:
        if time_range.end >= self.end:
            return TimeRange(self.start, self.end)
        else:
            return TimeRange(self.start, time_range.end)

def __repr__(self):
    return '{0} ------> {1}'.format(*[time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(d))
                                      for d in [self.start, self.end]])
3 голосов
/ 28 января 2012

Псевдокод:

 1 + max( -1, min( a.dateEnd, b.dateEnd) - max( a.dateStart, b.dateStart) )
0 голосов
/ 01 сентября 2014
def get_overlap(r1,r2):
    latest_start=max(r1[0],r2[0])
    earliest_end=min(r1[1],r2[1])
    delta=(earliest_end-latest_start).days
    if delta>0:
        return delta+1
    else:
        return 0
...