파이썬에서 효율적인 날짜 범위 중복 계산?


85

각 범위가 시작 및 종료 날짜에 의해 결정되는 두 개의 날짜 범위가 있습니다 (분명히 datetime.date () 인스턴스). 두 범위는 겹칠 수도 있고 그렇지 않을 수도 있습니다. 중복되는 일수가 필요합니다. 물론 두 범위 내의 모든 날짜로 두 세트를 미리 채울 수 있고 세트 교차를 수행 할 수 있지만 이것은 비효율적 일 수 있습니다 ... 모든 경우를 다루는 긴 if-elif 섹션을 사용하는 다른 솔루션과 다른 더 나은 방법이 있습니까?

답변:


175
  • 두 시작 날짜 중 가장 늦은 날짜와 두 종료 날짜 중 가장 빠른 날짜를 결정합니다.
  • 타임 델타를 빼서 계산합니다.
  • 델타가 양수이면 겹치는 일 수입니다.

다음은 계산의 예입니다.

>>> 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을 반환합니다.
darkless

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

오, 당신이 절대적으로 옳습니다. 내가 그것을 놓친 것 같습니다. 감사합니다 :)
darkless

1
두 개의 날짜 대신 두 번 계산하려면 어떻게해야합니까? @RaymondHettinger
에릭

1
시간과 함께 datetime 객체를 사용하는 경우 .days 대신 .total_seconds ()를 작성할 수 있습니다.
ErikXIII

10

함수 호출은 산술 연산보다 비쌉니다.

이를 수행하는 가장 빠른 방법은 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

물론 두 표현 모두 긍정적 인 겹침을 확인해야합니다.


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 인쇄 supposded 곳 (4)를 인쇄합니다
tkyass

첫 번째 방정식을 사용하여 모호한 시리즈 오류가 발생합니다. 특정 라이브러리가 필요합니까?
Arthur D. Howland

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

1
좋아 ... 더 많은 기능이 좋지만 실제로 StackOverflow에서는 답변이 OP의 지정된 요구 사항에 맞아야합니다. 그래서 더도 덜도 아닙니다. :)
L. Guthardt

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')와 같은 datetime 형식 일 수도 있습니다.


1
감사합니다. DateTimeRange패키지 에 대한 설명서를 살펴 보았고 is_intersection두 날짜 범위 사이에 교차가 있는지 여부에 따라 기본적으로 부울 값 (True 또는 False)을 반환하는 기능 을 지원 하는 것 같습니다 . 그래서, 당신의 예를 들어 : 그들이 교차하면 time_range1.is_intersection(time_range2)반환 True합니다False
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는 모든 시리즈를 사용하기 때문에 내 솔루션은 약간 불안정합니다. 그러나 다음 열이 있다고 가정 해 보겠습니다.이 중 2 개는 "회계 연도"입니다. PoP는 가변 데이터 인 "Period of performance"입니다.

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

모든 데이터가 datetime 형식이라고 가정합니다.

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)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.