我有两个日期范围,其中每个范围由开始日期和结束日期确定(显然,
datetime.date
实例)。两个范围可以重叠也可以不重叠。我需要重叠的天数。当然,我可以用两个范围内的所有日期预先填充两个集合,并执行集合交集,但这可能效率低下...除了使用长 if
-elif
部分覆盖的另一个解决方案之外,还有更好的方法吗所有情况?
这是一个计算示例:
>>> 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
函数调用比算术运算更昂贵。
最快的方法是进行 2 次减法和 1 分钟():
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
当然,对于这两个表达式,您仍然需要检查正重叠。
我实现了一个 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')。
建立在 @Raymond Hettinger 的解决方案之上,从 python 3.6 开始,您现在可以使用
NamedTuple
模块中的 typing
。
from datetime import datetime
from typing import NamedTuple
class Range(NamedTuple):
start: datetime
end: datetime
>>> 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 + max( -1, (min( a.dateEnd, b.dateEnd) - max( a.dateStart, b.dateStart)).days )
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
好吧,我的解决方案有点奇怪,因为我的 df 使用所有系列 - 但假设您有以下列,其中 2 列是固定的,即您的“财政年度”。 PoP 是“Period of Performance”,这是您的变量数据:
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)
另一种解决方案是先升序对源数组进行排序,然后循环并比较日期,如下所示:
date_ranges = sorted(
date_ranges,
key=lambda item: item['start_date'],
)
for i in range(len(date_ranges)-1):
if date_ranges[i]['end_date'] > date_ranges[i+1]['start_date']:
raise Exception('Overlap'})
我在我的一个应用程序中使用的是创建日期差异列表,并查询日期范围内的日期差异建议保存。
如果所有旧日期范围的日期差值列表(以天为单位)是:
dateDiffOld = [2920753, 2920746, 2920698, 2920387, 2920360, 2920296]
新的日期范围是:
dateDiffNew = 2920360
然后使用:
if dateDiffNew in dateDiffOld:
# do something
我寻求具有类似要求的可能解决方案,这让我找到了 SO 上提供的各种答案,但不知何故(对于我的用例)我发现这是有效的(到目前为止,有大量记录)。还没有机会在其他地方使用它。
注意:我描述的即时应用程序是使用Django创建的。
注2:请各位会员就此方法可能存在的陷阱(我目前尚未遇到)提出建议。