Python中有效的日期范围重叠计算?


85

我有两个日期范围,每个范围都由开始日期和结束日期确定(显然,datetime.date()实例)。这两个范围可以重叠也可以不重叠。我需要重叠的天数。当然,我可以用两个日期范围内的所有日期预填充两个集合,并执行一个集合交集,但这可能效率不高...除了长距离的if-elif部分覆盖所有情况的解决方案,还有其他更好的方法吗?

Answers:


174
  • 确定两个开始日期中的最晚一个,以及两个结束日期中最早的一个。
  • 通过减去它们来计算时间增量。
  • 如果增量为正,则为重叠天数。

这是一个示例计算:

>>> 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

1
+1非常好的解决方案。不过,这在另一个完全包含的日期中并不起作用。为了简单起见,整数:Range(1,4)和Range(2,3)返回1
无色

3
@darkless实际上,它返回2正确。尝试这些输入r1 = Range(start=datetime(2012, 1, 1), end=datetime(2012, 1, 4)); r2 = Range(start=datetime(2012, 1, 2), end=datetime(2012, 1, 3))。我认为您错过了+1重叠计算中的(这是必要的,因为该间隔在两端都是封闭的)。
Raymond Hettinger 2015年

哦,您绝对正确,看来我已经错过了。谢谢您:)
darkless 2015年

1
如果要计算2次而不是2个日期怎么办?@RaymondHettinger
埃里克

如果将带日期时间的对象与时间一起使用,则可以代替.days来编写.total_seconds()。
ErikXIII

10

函数调用比算术运算更昂贵。

最快的方法包括2次减法和1 min():

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

与需要1个减法,1 min()和max()的下一个最佳方法相比:

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

当然,对于这两个表达式,您仍然需要检查正重叠。


1
此方法不会总是返回正确答案。例如,Range = namedtuple('Range', ['start', 'end']) r1 = Range(start=datetime(2016, 6, 15), end=datetime(2016, 6, 15)) r2 = Range(start=datetime(2016, 6, 11), end=datetime(2016, 6, 18)) print min(r1.end - r2.start, r2.end - r1.start).days + 1将在应该打印1的位置打印4
tkyass

使用第一个方程式时,我得到了一个模棱两可的级数误差。我需要一个特定的图书馆吗?
亚瑟·豪兰

6

我实现了一个TimeRange类,如下所示。

get_overlapped_range首先通过简单的条件取反所有非重叠选项,然后通过考虑所有可能的选项来计算重叠范围。

要获得天数,您需要获取从get_overlapped_range返回的TimeRange值,然后将持续时间除以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]])

@ L.Guthardt同意,但是此解决方案有条理,并且具有更多功能
Elad Sofer

1
好的...更多功能还不错,但是实际上在StackOverflow上,答案应该恰好符合OP的指定需求。因此,没有更多也没有更少。:)
L. Guthardt '18

5

您可以使用datetimerange包:https : //pypi.org/project/DateTimeRange/

from datetimerange import DateTimeRange
time_range1 = DateTimeRange("2015-01-01T00:00:00+0900", "2015-01-04T00:20:00+0900") 
time_range2 = DateTimeRange("2015-01-01T00:00:10+0900", "2015-01-04T00:20:00+0900")
tem3 = time_range1.intersection(time_range2)
if tem3.NOT_A_TIME_STR == 'NaT':  # No overlap
    S_Time = 0
else: # Output the overlap seconds
    S_Time = tem3.timedelta.total_seconds()

DateTimeRange()内部的“ 2015-01-01T00:00:00 + 0900”也可以是日期时间格式,例如Timestamp('2017-08-30 20:36:25')。


1
谢谢,请看一下DateTimeRange软件包的文档,似乎它们支持is_intersection它根据两个日期范围之间是否有交集而本地返回布尔值(True或False)。因此,以您的示例为例:如果它们相交,time_range1.is_intersection(time_range2)则会返回TrueFalse
Deep

3

伪代码:

 1 + max( -1, min( a.dateEnd, b.dateEnd) - max( a.dateStart, b.dateStart) )

0
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

0

好的,我的解决方案有点奇怪,因为我的df使用了所有系列-但可以说您有以下几列,其中两列是固定的,即您的“会计年度”。PoP是“绩效期”,它是您的可变数据:

df['PoP_Start']
df['PoP_End']
df['FY19_Start'] = '10/1/2018'
df['FY19_End'] = '09/30/2019'

假设所有数据均为日期时间格式,即-

df['FY19_Start'] = pd.to_datetime(df['FY19_Start'])
df['FY19_End'] = pd.to_datetime(df['FY19_End'])

尝试使用以下方程式查找重叠天数:

min1 = np.minimum(df['POP_End'], df['FY19_End'])
max2 = np.maximum(df['POP_Start'], df['FY19_Start'])

df['Overlap_2019'] = (min1 - max2) / np.timedelta64(1, 'D')
df['Overlap_2019'] = np.maximum(df['Overlap_2019']+1,0)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.