Python'da verimli tarih aralığı çakışması hesaplaması?


85

Her aralığın bir başlangıç ​​ve bitiş tarihine göre belirlendiği iki tarih aralığım var (tabii ki, datetime.date () örnekleri). İki aralık örtüşebilir veya örtüşmeyebilir. Çakışmanın olduğu gün sayısına ihtiyacım var. Elbette, her iki aralıktaki tüm tarihlerle iki seti önceden doldurabilirim ve belirli bir kesişim gerçekleştirebilirim, ancak bu muhtemelen verimsizdir ... tüm durumları kapsayan uzun bir if-elif bölümü kullanan başka bir çözümden daha iyi bir yol var mı?

Yanıtlar:


175
  • İki başlangıç ​​tarihinin en yenisini ve iki bitiş tarihinin en eskisini belirleyin.
  • Timedelta'yı çıkararak hesaplayın.
  • Delta pozitifse bu, çakışan günlerin sayısıdır.

İşte örnek bir hesaplama:

>>> 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 çok güzel çözüm. Yine de, bu tam olarak diğerinin kapsadığı tarihlerde işe yaramıyor. Tam sayılarda basitlik için: Aralık (1,4) ve Aralık (2,3), 5
19'da

3
@darkless Aslında doğru olan 2'yi döndürür . Bu girdileri deneyin 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Örtüşme hesaplamasını kaçırdığınızı düşünüyorum (aralık her iki uçta da kapalı olduğu için gereklidir).
Raymond Hettinger

Oh, kesinlikle haklısın, bunu kaçırmışım gibi görünüyor. Teşekkür ederim :)
darkless

1
Ya 2 tarih yerine 2 kez hesaplamak istersen? @RaymondHettinger
Eric

1
Zamanlarla datetime nesneleri kullanırsanız, .days yerine .total_seconds () yazabilirsiniz.
ErikXIII

10

İşlev çağrıları, aritmetik işlemlerden daha pahalıdır.

Bunu yapmanın en hızlı yolu 2 çıkarma ve 1 dakika () içerir:

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

1 çıkarma, 1 dakika () ve maks () gerektiren bir sonraki en iyi ile karşılaştırıldığında:

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

Elbette her iki ifadede de pozitif bir örtüşme olup olmadığını kontrol etmeniz gerekir.


1
Bu yöntem her zaman doğru yanıtı vermeyecektir. ör. 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 + 11 yazdırması gereken yerde 4 yazacak
tkyass

İlk denklemi kullanarak belirsiz bir seri hatası alıyorum. Belirli bir kitaplığa ihtiyacım var mı?
Arthur D. Howland

6

Aşağıda görebileceğiniz gibi bir TimeRange sınıfı uyguladım.

Get_overlapped_range, önce çakışmayan tüm seçenekleri basit bir koşulla reddeder ve ardından tüm olası seçenekleri göz önünde bulundurarak çakışan aralığı hesaplar.

Gün miktarını elde etmek için get_overlapped_range'den döndürülen TimeRange değerini almanız ve süreyi 60 * 60 * 24'e bölmeniz gerekir.

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 Kabul, ama bu çözüm organize ve daha işlevsellikle geliyor
Elad Sofer

1
Tamam ... bu daha fazla işlevsellik güzel, ama aslında StackOverflow'da bir cevap sadece OP'nin belirtilen ihtiyaçlarına uymalıdır. Yani ne daha fazla ne de daha azı. :)
L. Guthardt

5

Datetimerange paketini kullanabilirsiniz: 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 () içindeki "2015-01-01T00: 00: 00 + 0900", Zaman Damgası ('2017-08-30 20:36:25') gibi tarih saat biçimi de olabilir.


1
Teşekkürler, DateTimeRangePaketin belgelerine bir göz attım ve is_intersectioniki tarih aralığı arasında bir kesişim olup olmadığına bağlı olarak doğal olarak bir boole değeri (Doğru veya Yanlış) döndüren desteği destekliyorlar . Öyleyse, örneğiniz için: başka time_range1.is_intersection(time_range2)TrueFalse
Derin

3

Sözde kod:

 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

Tamam, benim çözümüm biraz riskli çünkü benim df tüm serileri kullanıyor - ancak aşağıdaki sütunlara sahip olduğunuzu varsayalım, bunlardan 2'si sabit olan "Mali Yılınız". PoP, değişken verileriniz olan "Performans Dönemi" dir:

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

Tüm verilerin tarih saat biçiminde olduğunu varsayın, yani -

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

Çakışan günlerin sayısını bulmak için aşağıdaki denklemleri deneyin:

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)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.