การคำนวณช่วงวันที่ที่มีประสิทธิภาพใน python?


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
จะเป็นอย่างไรถ้าคุณต้องการคำนวณ 2 ครั้งแทนที่จะเป็น 2 วันที่? @RaymondHettinger
Eric

1
หากคุณใช้ออบเจ็กต์วันที่และเวลาที่คุณทำได้แทนที่จะเป็น. วันให้เขียน. total_seconds ()
ErikXIII

10

การเรียกใช้ฟังก์ชันมีราคาแพงกว่าการคำนวณทางคณิตศาสตร์

วิธีที่เร็วที่สุดในการทำเช่นนี้คือการลบ 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

แน่นอนด้วยนิพจน์ทั้งสองคุณยังคงต้องตรวจสอบการทับซ้อนเชิงบวก


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จะพิมพ์ 4 ที่รองรับการพิมพ์ 1
tkyass

ฉันได้รับข้อผิดพลาดของอนุกรมที่ไม่ชัดเจนโดยใช้สมการแรก ฉันต้องการห้องสมุดเฉพาะหรือไม่?
Arthur D. Howland

6

ฉันใช้คลาส 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]])

@ L.Guthardt เห็นด้วย แต่โซลูชันนี้ได้รับการจัดระเบียบและมาพร้อมกับฟังก์ชันการทำงานที่มากขึ้น
Elad Sofer

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

"2015-01-01T00: 00: 00 + 0900" ภายใน DateTimeRange () ยังสามารถเป็นรูปแบบวันที่และเวลาเช่น Timestamp ('2017-08-30 20:36:25')


1
ขอบคุณเพิ่งดูเอกสารประกอบสำหรับDateTimeRangeแพ็กเกจและดูเหมือนว่าพวกเขาสนับสนุนis_intersectionซึ่งจะส่งคืนค่าบูลีน (จริงหรือเท็จ) ขึ้นอยู่กับว่ามีจุดตัดระหว่างสองช่วงวันที่หรือไม่ ดังนั้นตัวอย่างของคุณ: time_range1.is_intersection(time_range2)จะกลับมาTrueถ้ามันตัดกันFalse
ลึก


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 คือ "ช่วงเวลาการแสดง" ซึ่งเป็นข้อมูลตัวแปรของคุณ:

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)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.