我有两个日期范围,每个范围都由开始日期和结束日期确定(显然,datetime.date()实例)。这两个范围可以重叠也可以不重叠。我需要重叠的天数。当然,我可以用两个日期范围内的所有日期预填充两个集合,并执行一个集合交集,但这可能效率不高...除了长距离的if-elif部分覆盖所有情况的解决方案,还有其他更好的方法吗?
Answers:
这是一个示例计算:
>>> 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
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
重叠计算中的(这是必要的,因为该间隔在两端都是封闭的)。
函数调用比算术运算更昂贵。
最快的方法包括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
当然,对于这两个表达式,您仍然需要检查正重叠。
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
我实现了一个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]])
您可以使用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')。
DateTimeRange
软件包的文档,似乎它们支持is_intersection
它根据两个日期范围之间是否有交集而本地返回布尔值(True或False)。因此,以您的示例为例:如果它们相交,time_range1.is_intersection(time_range2)
则会返回True
False
好的,我的解决方案有点奇怪,因为我的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)