95 votes

Calcul d'efficacité de chevauchement de plage de dates ?

J'ai deux plages de dates où chaque plage est déterminée par une date de début et une date de fin (évidemment, des instances datetime.date). Les deux plages peuvent se chevaucher ou non. J'ai besoin du nombre de jours de chevauchement. Bien sûr, je peux pré-remplir deux ensembles avec toutes les dates comprises dans les deux plages et ensuite effectuer une intersection d'ensembles mais cela pourrait être inefficace... y a-t-il un moyen mieux mis à part une autre solution utilisant une longue section if-elif couvrant tous les cas?

0 votes

Est-ce que cette réponse répond à votre question? Déterminer si deux plages de dates se chevauchent

200voto

Raymond Hettinger Points 231
  • Déterminez la date de début la plus récente des deux et la date de fin la plus précoce des deux.
  • Calculez la différence en les soustrayant.
  • Si le delta est positif, c'est le nombre de jours de chevauchement.

Voici un exemple de calcul :

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

+1 très belle solution. Cependant, cela ne fonctionne pas tout à fait pour les dates entièrement contenues dans l'autre. Pour la simplicité en entiers: Range(1,4) et Range(2,3) renvoie 1

3 votes

@darkless En fait, cela renvoie 2 ce qui est correct. Essayez ces entrées r1 = Plage(début=datetime(2012, 1, 1), fin=datetime(2012, 1, 4)); r2 = Plage(début=datetime(2012, 1, 2), fin=datetime(2012, 1, 3)). Je pense que vous avez manqué le +1 dans le calcul du chevauchement (nécessaire car l'intervalle est fermé aux deux extrémités).

1 votes

Que se passe-t-il si vous voulez calculer 2 fois au lieu de 2 dates ? @RaymondHettinger

10voto

John Machin Points 39706

Les appels de fonction sont plus coûteux que les opérations arithmétiques.

La manière la plus rapide de procéder consiste en 2 soustractions et 1 min() :

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

comparée à la deuxième meilleure méthode qui nécessite 1 soustraction, 1 min() et un max() :

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

Évidemment, avec les deux expressions, vous devez toujours vérifier s'il y a un chevauchement positif.

1 votes

Cette méthode ne retournera pas toujours la bonne réponse. par exemple, 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 affichera 4 alors qu'elle aurait dû afficher 1

0 votes

Je reçois une erreur de série ambiguë en utilisant la première équation. Ai-je besoin d'une bibliothèque particulière ?

8voto

Elad Sofer Points 91

J'ai implémenté une classe TimeRange comme vous pouvez le voir ci-dessous.

La méthode get_overlapped_range commence par nier toutes les options qui ne se chevauchent pas par une simple condition, puis calcule la plage qui se chevauche en considérant toutes les options possibles.

Pour obtenir le nombre de jours, vous devrez prendre la valeur TimeRange retournée par get_overlapped_range et diviser la durée par 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]])

7voto

Songhua Hu Points 81

Vous pouvez utiliser le package 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':  # Pas de chevauchement
    S_Time = 0
else: # Afficher les secondes de chevauchement
    S_Time = tem3.timedelta.total_seconds()

"2015-01-01T00:00:00+0900" à l'intérieur de DateTimeRange() peut également être au format datetime, comme Timestamp('2017-08-30 20:36:25').

1 votes

Merci, Je viens de consulter la documentation pour le package DateTimeRange et il semble qu'ils prennent en charge is_intersection qui renvoie nativement une valeur booléenne (Vrai ou Faux) en fonction de s'il y a une intersection entre deux plages de dates. Donc, pour votre exemple: time_range1.is_intersection(time_range2) renverrait Vrai s'ils se croisent sinon Faux

0 votes

Intersection() retourne un objet DateTimeRange et sa propriété NOT_A_TIME_STR est toujours égale à 'NaT', donc la condition if sera toujours vraie. Une meilleure approche serait d'utiliser la méthode is_intersection qui renvoie True ou False.

3voto

ypercube Points 62714

Pseudo-code :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X