2012-01-28 20 views
47

Tengo dos intervalos de fechas donde cada rango está determinado por una fecha de inicio y finalización (obviamente, instancias datetime.date()). Los dos rangos pueden superponerse o no. Necesito el número de días de la superposición. Por supuesto, puedo precompletar dos conjuntos con todas las fechas dentro de ambos rangos y realizar una intersección establecida, pero esto posiblemente sea ineficiente ... ¿hay una mejor manera aparte de otra solución que utiliza una sección larga if-elif que cubra todos los casos?¿Cálculo de superposición de rango de fechas eficiente en python?

Respuesta

108
  • Determine la última de las dos fechas de inicio y la primera de las dos fechas de finalización.
  • Calcula el timedelta restándolo.
  • Si el delta es positivo, ese es el número de días de superposición.

Aquí es un ejemplo de cálculo:

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

Excelente respuesta –

+7

+1 para crear la tupla llamada 'Rango '. :-) – GaretJax

+1

+1 para código elegante. Acabo de tomar esta rutina y la utilicé en mi aplicación PHP. – Eric

3

Pseudocódigo:

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

llamadas a funciones son más caras que las operaciones aritméticas.

La forma más rápida de hacer esto implica 2 sustracciones y 1 min():

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

en comparación con la siguiente mejor que necesita 1 resta, 1 min() y un máximo():

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

Por supuesto, con ambas expresiones, aún necesita verificar si hay una superposición positiva.

+1

Este método no devolverá la respuesta correcta siempre. p.ej.'Range = namedtuple ('Range', ['start', 'end']) r1 = Rango (start = datetime (2016, 6, 15), end = datetime (2016, 6, 15)) r2 = Rango (start = datetime (2016, 6, 11), end = datetime (2016, 6, 18)) print min (r1.end - r2.start, r2.end - r1.start) .days + 1' se imprimirá 4 donde supuso imprimir 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

Implementé una clase TimeRange como se puede ver a continuación.

El get_overlapped_range primero niega todas las opciones no superpuestas por una condición simple, y luego calcula el rango superpuesto considerando todas las opciones posibles.

para obtener la cantidad de días que tendrá que tomar el valor que fue devuelto TimeRange de get_overlapped_range y dividir la duración de 60 * 60 * 24 (:

TimeRange clase (objeto):

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

Ya existe una solución perfecta para el problema de OP. –

Cuestiones relacionadas