2012-01-28 13 views
47

Ho due intervalli di date in cui ogni intervallo è determinato da una data di inizio e di fine (ovviamente, istanze datetime.date()). Le due gamme possono sovrapporsi o meno. Ho bisogno del numero di giorni della sovrapposizione. Naturalmente posso precompilare due serie con tutte le date all'interno di entrambi gli intervalli e eseguire un'intersezione impostata, ma questo è probabilmente inefficiente ... c'è un modo migliore a parte un'altra soluzione che utilizza una lunga sezione if-elif che copre tutti i casi?Calcolo di sovrapposizione intervallo di date efficiente in python?

risposta

108
  • Determinare l'ultima delle due date di inizio e la prima delle due date di fine.
  • Calcolare il timedelta sottraendolo.
  • Se il delta è positivo, è il numero di giorni di sovrapposizione.

Ecco un esempio di calcolo:

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

Risposta eccellente –

+7

+1 per creare la tupla denominata 'Range'. :-) – GaretJax

+1

+1 per codice elegante. Ho appena preso questa routine e l'ho usata nella mia app PHP. – Eric

3

Pseudocodice:

1 + max(-1, min(a.dateEnd, b.dateEnd) - max(a.dateStart, b.dateStart)) 
6

chiamate di funzione sono più costosi di operazioni aritmetiche.

Il modo più veloce per farlo comporta 2 sottrazioni e 1 min():

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

rispetto alla migliore seguente che ha bisogno di 1 sottrazione, 1 min() e un massimo():

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

Naturalmente con entrambe le espressioni è ancora necessario verificare una sovrapposizione positiva.

+1

Questo metodo non restituirà sempre la risposta corretta. per esempio.'Range = namedtuple ('Range', ['start', 'end']) r1 = Intervallo (start = datetime (2016, 6, 15), end = datetime (2016, 6, 15)) r2 = Intervallo (start = datetime (2016, 6, 11), end = datetime (2016, 6, 18)) stampa min (r1.end - r2.start, r2.end - r1.start) .days + 1' stampa 4 dove suppose di stampare 1 – tkyass

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

Ho implementato una classe TimeRange come potete vedere di seguito.

L'oggetto get_overlapped_range prima nega tutte le opzioni non sovrapposte a una condizione semplice, quindi calcola l'intervallo sovrapposto considerando tutte le opzioni possibili.

per ottenere la quantità di giorni avrete bisogno di prendere il valore TimeRange che è stato restituito da get_overlapped_range e dividere la durata del 60 * 60 * 24 (:

classe TimeRange (oggetto):

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]]) 
+0

Esiste già una soluzione perfetta per il problema dell'OP. –

Problemi correlati