Déterminer si deux plages de dates se chevauchent


1251

Étant donné deux plages de dates, quelle est la manière la plus simple ou la plus efficace de déterminer si les deux plages de dates se chevauchent?

Par exemple, supposons que nous ayons des plages désignées par des variables DateTime StartDate1vers EndDate1 et StartDate2 vers EndDate2.



@CharlesBretana merci pour cela, vous avez raison - c'est presque comme une version en deux dimensions de ma question!
Ian Nelson


2
Divisez la situation «les deux plages de dates se croisent» en cas (il y en a deux) puis testez pour chaque cas.
Colonel Panic

1
Ce code fonctionne bien. Vous pouvez voir ma réponse ici: stackoverflow.com/a/16961719/1534785
Jeyhun Rahimov

Réponses:


2290

(StartA <= EndB) et (EndA> = StartB)

Preuve:
que ConditionA signifie que DateRange A complètement après DateRange B
_ |---- DateRange A ------| |---Date Range B -----| _
(True if StartA > EndB)

Soit ConditionB signifie que DateRange A est complètement avant DateRange B
|---- DateRange A -----| _ _ |---Date Range B ----|
(Vrai si EndA < StartB)

Il y a alors chevauchement si ni A ni B n'est vrai -
(si une plage n'est ni complètement après l'autre,
ni complètement avant l'autre, alors elles doivent se chevaucher.)

Maintenant, l'une des lois de De Morgan dit que:

Not (A Or B) <=> Not A And Not B

Ce qui se traduit par: (StartA <= EndB) and (EndA >= StartB)


REMARQUE: cela inclut les conditions où les bords se chevauchent exactement. Si vous souhaitez exclure,
changer les >=opérateurs à >, et <= à<


NOTE 2. Merci à @Baodad, voir ce blog , le chevauchement réel est moins:
{ endA-startA, endA - startB, endB-startA, endB - startB}

(StartA <= EndB) and (EndA >= StartB) (StartA <= EndB) and (StartB <= EndA)


NOTE 3. Grâce à @tomosius, une version plus courte se lit comme suit:
DateRangesOverlap = max(start1, start2) < min(end1, end2)
Il s'agit en fait d'un raccourci syntaxique pour une implémentation plus longue, qui comprend des vérifications supplémentaires pour vérifier que les dates de début sont à la date de fin ou avant. Dérivant cela d'en haut:

Si les dates de début et de fin peuvent être hors service, c'est-à-dire s'il est possible que startA > endAou startB > endB, alors vous devez également vérifier qu'elles sont en ordre, ce qui signifie que vous devez ajouter deux règles de validité supplémentaires:
(StartA <= EndB) and (StartB <= EndA) and (StartA <= EndA) and (StartB <= EndB) ou:
(StartA <= EndB) and (StartA <= EndA) and (StartB <= EndA) and (StartB <= EndB) ou,
(StartA <= Min(EndA, EndB) and (StartB <= Min(EndA, EndB)) ou:
(Max(StartA, StartB) <= Min(EndA, EndB)

Mais pour implémenter Min()et Max(), vous devez coder, (en utilisant C ternaire pour la concision),:
(StartA > StartB? Start A: StartB) <= (EndA < EndB? EndA: EndB)


29
Il s'agit d'une logique simplifiée basée sur ces deux hypothèses: 1) StartA <EndA; 2) DébutB <FinB. Cela semble évident, mais en réalité, les données peuvent provenir d'une source inconnue comme les entrées des utilisateurs ou d'une base de données sans nettoyage. Gardez à l'esprit que vous devrez valider les données d'entrée pour vous assurer que ces deux hypothèses sont vraies avant de pouvoir utiliser cette logique simplifiée, sinon tout s'effondrera. Leçon tirée de ma propre expérience;)
Devy

12
@Devy, vous avez raison. Sauf que cela fonctionnera également si startA = endA. En effet, c'est exactement ce Startque Endsignifient les mots . Si vous avez deux variables nommées Top et Bottom, ou East and West, ou HighValue et LoValue, il peut être supposé ou sous-entendu que quelque chose ou quelqu'un, quelque part devrait s'assurer qu'aucune des paires de valeurs n'est stockée dans les variables opposées. -Une seule des deux paires car, eh bien, cela fonctionnera également si les deux paires de valeurs sont commutées.
Charles Bretana

15
Vous pouvez facilement ajouter nullable startet end(avec la sémantique "null start" = "depuis le début du temps" et "null end" = "jusqu'à la fin du temps") comme ça:(startA === null || endB === null || startA <= endB) && (endA === null || startB === null || endA >= startB)
Kevin Robatel

9
Meilleure réponse sur Stackexchange! Ça fait du bien de voir une explication sur pourquoi cette formule intelligente fonctionne!
Abeer Sul

4
Voici la forme la plus compacte à laquelle je pourrais penser, qui renvoie également false en cas d'entrée non valide (date de début> = date de fin)DateRangesOverlap = max(start1, start2) < min(end1, end2)
tomosius

406

Je pense qu'il suffit de dire que les deux gammes se chevauchent si:

(StartDate1 <= EndDate2) and (StartDate2 <= EndDate1)

76
Je trouve la (StartDate1 <= EndDate2) and (EndDate1 >= StartDate2)notation plus facile à comprendre, Range1 est toujours à gauche dans les tests.
AL

8
Cela suppose que les dates de début et de fin sont inclusives. Changement <=de <si le démarrage est inclusif et à la fin est exclusive.
Richard Schneider

Cela fonctionnera très bien même si startDate2 est antérieur à startDate1. Il n'est donc pas nécessaire de supposer que startDate1 est antérieure à startDate2.
Shehan Simen

3
J'ai trouvé (StartDate1 <= EndDate2) et (StartDate2 <= EndDate1) la notation (selon la réponse) plus facile à comprendre que celle des autres réponses.
apc

Comment s'adapter pour qu'il fonctionne avec des données qui ont StartDate1 ET / OR EndDate1? Le code suppose que StartDate1 et EndDate1 sont toujours présents. Que faire si StartDate1 est donné mais pas EndDate1 OU EndDate1 donné mais pas StartDate1. Comment gérer ce cas supplémentaire?
juFo

117

Cet article Time Period Library pour .NET décrit la relation de deux périodes par l'énumération PeriodRelation :

// ------------------------------------------------------------------------
public enum PeriodRelation
{
    After,
    StartTouching,
    StartInside,
    InsideStartTouching,
    EnclosingStartTouching,
    Enclosing,
    EnclosingEndTouching,
    ExactMatch,
    Inside,
    InsideEndTouching,
    EndInside,
    EndTouching,
    Before,
} // enum PeriodRelation

entrez la description de l'image ici


Bien, j'ai aussi implémenté l'algèbre d'intervalle d'Allens en Java, voir l' API d'IntervalRelation et IsoInterval
Meno Hochschild

80

Pour raisonner sur les relations temporelles (ou toute autre relation d'intervalle, venez-y), considérez l'algèbre d'intervalle d'Allen . Il décrit les 13 relations possibles que deux intervalles peuvent avoir l'un par rapport à l'autre. Vous pouvez trouver d'autres références - "Allen Interval" semble être un terme de recherche opérationnel. Vous pouvez également trouver des informations sur ces opérations dans Snodgrass's Developing Time-Oriented Applications in SQL (PDF disponible en ligne sur URL), et dans Date, Darwen et Lorentzos Temporal Data and the Relational Model (2002) ou Time and Relational Theory: Temporal Databases in le modèle relationnel et SQL (2014; en fait la deuxième édition de TD&RM).


La réponse courte (ish) est: étant donné deux intervalles de date Aet Bavec les composants .startet .endet la contrainte .start <= .end, alors deux intervalles se chevauchent si:

A.end >= B.start AND A.start <= B.end

Vous pouvez régler l'utilisation de >=vs >et <=vs <pour répondre à vos exigences de degré de chevauchement.


Commentaires d'ErikE:

Vous ne pouvez obtenir que 13 si vous comptez les choses drôles ... Je peux obtenir "15 relations possibles que deux intervalles peuvent avoir" quand je deviens fou avec ça. Par un comptage raisonnable, je n'en obtiens que six, et si vous jetez attention à savoir si A ou B vient en premier, je n'en reçois que trois (aucune intersection, partiellement intersection, une entièrement dans l'autre). 15 va comme ceci: [avant: avant, début, intérieur, fin, après], [début: début, intérieur, fin, après], [intérieur: intérieur, fin, après], [fin: fin, après], [ après: après].

Je pense que vous ne pouvez pas compter les deux entrées «avant: avant» et «après: après». Je pourrais voir 7 entrées si vous assimilez certaines relations à leurs inverses (voir le diagramme dans l'URL Wikipédia référencée; il a 7 entrées, dont 6 ont un inverse différent, avec des égaux n'ayant pas d'inverse distinct). Et si trois est raisonnable dépend de vos besoins.

----------------------|-------A-------|----------------------
    |----B1----|
           |----B2----|
               |----B3----|
               |----------B4----------|
               |----------------B5----------------|
                      |----B6----|
----------------------|-------A-------|----------------------
                      |------B7-------|
                      |----------B8-----------|
                         |----B9----|
                         |----B10-----|
                         |--------B11--------|
                                      |----B12----|
                                         |----B13----|
----------------------|-------A-------|----------------------

1
Vous ne pouvez obtenir que 13 si vous comptez les choses drôles ... Je peux obtenir "15 relations possibles que deux intervalles peuvent avoir" quand je deviens fou avec ça. Par un comptage raisonnable, je n'en obtiens que six, et si vous jetez attention à savoir si A ou B vient en premier, je n'en reçois que trois (aucune intersection, partiellement intersection, une entièrement dans l'autre). 15 va comme ceci: [avant: avant, début, intérieur, fin, après], [début: début, intérieur, fin, après], [intérieur: intérieur, fin, après], [fin: fin, après], [ après: après].
ErikE

@Emtucifor: Je pense que vous ne pouvez pas compter les deux entrées «avant: avant» et «après: après».
Jonathan Leffler

Concernant votre mise à jour: B1 à A est avant: avant et B13 à A est après: après. Votre joli diagramme manque début: début entre B5 B6, et fin: fin entre B11 et B12. Si être sur un point final est important, alors vous devez le compter, donc le décompte final est de 15, pas 13. Je ne pense pas que le point final soit significatif, donc je le compte personnellement [avant: avant, dedans, après] , [dedans: dedans, après], [après: après] ce qui revient à 6. Je pense que le point final est juste une confusion pour savoir si les limites sont inclusives ou exclusives. L'exclusivité des endpoints ne change pas les relations de base!
ErikE

Autrement dit, dans mon schéma, ceux-ci sont équivalents: (B2, B3, B4), (B6, B7, B9, B10), (B8, B11, B12). Je me rends compte que B7 implique l'information que les deux plages coïncident exactement. Mais je ne suis pas convaincu que ces informations supplémentaires devraient faire partie des relations d'intersection de base. Par exemple, lorsque deux intervalles sont exactement de la même longueur, même s'ils ne coïncident pas ou ne se chevauchent pas, cela devrait-il être considéré comme une autre "relation"? Je dis non, et vu que cet aspect supplémentaire est la seule chose qui rend B7 distinct de B6, alors je pense qu'avoir des points finaux en tant que cas séparés rend les choses incohérentes.
ErikE

@Emtucifor: OK - je vois pourquoi j'ai mal identifié «avant: avant» et «après: après» comme entrées; cependant, je ne peux pas imaginer à quoi devraient ressembler les entrées 'start: start' et 'end: end'. Comme vous ne pouvez pas modifier mon diagramme, pouvez-vous m'envoyer un e-mail (voir mon profil) avec une copie modifiée du diagramme montrant les relations «début: début» et «fin: fin»? Je n'ai aucun problème majeur avec vos groupements.
Jonathan Leffler

30

Si le chevauchement lui-même doit également être calculé, vous pouvez utiliser la formule suivante:

overlap = max(0, min(EndDate1, EndDate2) - max(StartDate1, StartDate2))
if (overlap > 0) { 
    ...
}

si le chevauchement est la quantité de temps que les deux événements partagent? Est-ce que cela fonctionne pour toutes les différentes manières dont les événements peuvent se chevaucher?
NSjonas

18

Toutes les solutions qui vérifient une multitude de conditions en fonction de la position des plages les unes par rapport aux autres peuvent être grandement simplifiées en s'assurant simplement qu'une plage spécifique démarre plus tôt! Vous vous assurez que la première plage démarre plus tôt (ou en même temps) en échangeant les plages si nécessaire à l'avance.

Ensuite, vous pouvez détecter un chevauchement si l'autre début de plage est inférieur ou égal à la première fin de plage (si les plages sont inclusives, contenant à la fois les heures de début et de fin) ou inférieur à (si les plages incluent le début et excluent la fin) .

En supposant inclusif aux deux extrémités, il n'y a que quatre possibilités dont l'une est un non-chevauchement:

|----------------------|        range 1
|--->                           range 2 overlap
 |--->                          range 2 overlap
                       |--->    range 2 overlap
                        |--->   range 2 no overlap

Le point final de la plage 2 n'y entre pas. Donc, en pseudo-code:

def doesOverlap (r1, r2):
    if r1.s > r2.s:
        swap r1, r2
    if r2.s > r1.e:
        return false
    return true

Cela pourrait être encore plus simplifié en:

def doesOverlap (r1, r2):
    if r1.s > r2.s:
        swap r1, r2
    return r2.s <= r1.e

Si les plages sont inclusives au début et exclusives à la fin, il vous suffit de remplacer >par >=dans la deuxième ifinstruction (pour le premier segment de code: dans le deuxième segment de code, vous utiliseriez <plutôt que <=):

|----------------------|        range 1
|--->                           range 2 overlap
 |--->                          range 2 overlap
                       |--->    range 2 no overlap
                        |--->   range 2 no overlap

Vous limitez considérablement le nombre de vérifications que vous devez effectuer car vous supprimez la moitié de l'espace problématique tôt en vous assurant que la plage 1 ne démarre jamais après la plage 2.


2
+1 pour avoir mentionné le problème inclusif / exclusif. J'allais préparer une réponse moi-même quand j'aurais eu le temps, mais pas besoin maintenant. Le fait est que vous ne permettez presque jamais que le début et la fin soient inclusifs simultanément. Dans mon industrie, il est courant de considérer le début comme exclusif et la fin comme inclusive, mais dans les deux cas, c'est bien tant que vous restez cohérent. C'est la première réponse complètement correcte à cette question jusqu'à présent ... OMI.
Brian Gideon

14

Voici encore une autre solution utilisant JavaScript. Spécialités de ma solution:

  • Gère les valeurs nulles à l'infini
  • Suppose que la borne inférieure est inclusive et la borne supérieure exclusive.
  • Livré avec un tas de tests

Les tests sont basés sur des nombres entiers, mais comme les objets de date en JavaScript sont comparables, vous pouvez également ajouter deux objets de date. Ou vous pouvez ajouter l'horodatage en millisecondes.

Code:

/**
 * Compares to comparable objects to find out whether they overlap.
 * It is assumed that the interval is in the format [from,to) (read: from is inclusive, to is exclusive).
 * A null value is interpreted as infinity
 */
function intervalsOverlap(from1, to1, from2, to2) {
    return (to2 === null || from1 < to2) && (to1 === null || to1 > from2);
}

Tests:

describe('', function() {
    function generateTest(firstRange, secondRange, expected) {
        it(JSON.stringify(firstRange) + ' and ' + JSON.stringify(secondRange), function() {
            expect(intervalsOverlap(firstRange[0], firstRange[1], secondRange[0], secondRange[1])).toBe(expected);
        });
    }

    describe('no overlap (touching ends)', function() {
        generateTest([10,20], [20,30], false);
        generateTest([20,30], [10,20], false);

        generateTest([10,20], [20,null], false);
        generateTest([20,null], [10,20], false);

        generateTest([null,20], [20,30], false);
        generateTest([20,30], [null,20], false);
    });

    describe('do overlap (one end overlaps)', function() {
        generateTest([10,20], [19,30], true);
        generateTest([19,30], [10,20], true);

        generateTest([10,20], [null,30], true);
        generateTest([10,20], [19,null], true);
        generateTest([null,30], [10,20], true);
        generateTest([19,null], [10,20], true);
    });

    describe('do overlap (one range included in other range)', function() {
        generateTest([10,40], [20,30], true);
        generateTest([20,30], [10,40], true);

        generateTest([10,40], [null,null], true);
        generateTest([null,null], [10,40], true);
    });

    describe('do overlap (both ranges equal)', function() {
        generateTest([10,20], [10,20], true);

        generateTest([null,20], [null,20], true);
        generateTest([10,null], [10,null], true);
        generateTest([null,null], [null,null], true);
    });
});

Résultat lors de l'exécution avec karma & jasmine & PhantomJS:

PhantomJS 1.9.8 (Linux): exécution de 20 succès sur 20 (0,003 s / 0,004 s)


9

je ferais

StartDate1.IsBetween(StartDate2, EndDate2) || EndDate1.IsBetween(StartDate2, EndDate2)

IsBetweenest quelque chose comme

    public static bool IsBetween(this DateTime value, DateTime left, DateTime right) {
        return (value > left && value < right) || (value < left && value > right);
    }

Je préférerais (gauche <valeur && valeur <droite) || (droite <valeur && valeur <gauche) pour cette méthode.
Patrick Huizinga

Merci pour cela. Rend les choses plus faciles dans ma tête.
sshow

1
Pourquoi voudriez-vous vérifier quatre conditions alors que vous n'avez qu'à en vérifier deux? Échouer.
ErikE

3
Ah, mes excuses, je vois maintenant que vous autorisez les plages à être dans l'ordre inverse (StartDateX> EndDateX). Étrange. Quoi qu'il en soit, que se passe-t-il si StartDate1 est inférieur à StartDate2 et EndDate1 est supérieur à EndDate2? Le code que vous avez donné ne détectera pas cette condition de chevauchement.
ErikE

3
Ce retour ne sera-t-il pas faux si Date1 contient toute la Date2? Puis StartDate1 est avant StartDate2 et EndDate1 est après EndDate2
user158037

9

entrez la description de l'image ici

Voici le code qui fait la magie:

 var isOverlapping =  ((A == null || D == null || A <= D) 
            && (C == null || B == null || C <= B)
            && (A == null || B == null || A <= B)
            && (C == null || D == null || C <= D));

Où..

  • A -> 1Démarrer
  • B -> 1End
  • C -> 2Démarrer
  • D -> 2End

Preuve? Découvrez cet aperçu du code de la console de test .


Cela fonctionne, mais je préférerais tester pour ne pas se chevaucher, seulement deux scénarios
John Albert

Merci d'avoir expliqué cela en utilisant des images. Votre réponse est la solution parfaite pour cette question.
Rakesh Verma

8

Voici ma solution en Java , qui fonctionne également à intervalles illimités

private Boolean overlap (Timestamp startA, Timestamp endA,
                         Timestamp startB, Timestamp endB)
{
    return (endB == null || startA == null || !startA.after(endB))
        && (endA == null || startB == null || !endA.before(startB));
}

Je pense que vous vouliez dire des fins illimitées au lieu d'intervalles ouverts.
Henrik


!startA.after(endB)signifie startA <= endB et !endA.before(startB)signifie startB <= endA. Ce sont les critères d'un intervalle fermé et non d'un intervalle ouvert.
Henrik

@Henrik true et les autres conditions telles que endB == nullet startA == nullrecherchez un intervalle ouvert.
Khaled.K

1
endB == null, startA == null, endA == nullEt startB == nullsont tous les critères pour vérifier un intervalle sans entrave et non un intervalle ouvert. Exemple pour les différences entre les intervalles non bornés et ouverts: (10, 20) et (20, null) sont deux intervalles ouverts qui ne se chevauchent pas. Le dernier a une fin illimitée. Votre fonction renverra true, mais les intervalles ne se chevauchent pas, car les intervalles n'incluent pas 20. (nombres utilisés au lieu d'horodatages pour plus de simplicité)
Henrik

7

La solution publiée ici n'a pas fonctionné pour toutes les plages qui se chevauchent ...

---------------------- | ------- A ------- | ----------- -----------
    | ---- B1 ---- |
           | ---- B2 ---- |
               | ---- B3 ---- |
               | ---------- B4 ---------- |
               | ---------------- B5 ---------------- |
                      | ---- B6 ---- |
---------------------- | ------- A ------- | ----------- -----------
                      | ------ B7 ------- |
                      | ---------- B8 ----------- |
                         | ---- B9 ---- |
                         | ---- B10 ----- |
                         | -------- B11 -------- |
                                      | ---- B12 ---- |
                                         | ---- B13 ---- |
---------------------- | ------- A ------- | ----------- -----------

ma solution de travail était:

ET (
  ('start_date' ENTRE STARTDATE ET ENDDATE) - prend en charge la date interne et la date externe de fin
  OU
  ('end_date' ENTRE STARTDATE ET ENDDATE) - prend en compte les dates internes et externes
  OU
  (STARTDATE BETWEEN 'start_date' AND 'end_date') - un seul est nécessaire pour la plage extérieure où les dates sont à l'intérieur.
) 

5

C'était ma solution javascript avec moment.js:

// Current row dates
var dateStart = moment("2014-08-01", "YYYY-MM-DD");
var dateEnd = moment("2014-08-30", "YYYY-MM-DD");

// Check with dates above
var rangeUsedStart = moment("2014-08-02", "YYYY-MM-DD");
var rangeUsedEnd = moment("2014-08-015", "YYYY-MM-DD");

// Range covers other ?
if((dateStart <= rangeUsedStart) && (rangeUsedEnd <= dateEnd)) {
    return false;
}
// Range intersects with other start ?
if((dateStart <= rangeUsedStart) && (rangeUsedStart <= dateEnd)) {
    return false;
}
// Range intersects with other end ?
if((dateStart <= rangeUsedEnd) && (rangeUsedEnd <= dateEnd)) {
    return false;
}

// All good
return true;

4

Un moyen facile de se souvenir de la solution serait
min(ends)>max(starts)


3

Dans Microsoft SQL SERVER - Fonction SQL

CREATE FUNCTION IsOverlapDates 
(
    @startDate1 as datetime,
    @endDate1 as datetime,
    @startDate2 as datetime,
    @endDate2 as datetime
)
RETURNS int
AS
BEGIN
DECLARE @Overlap as int
SET @Overlap = (SELECT CASE WHEN  (
        (@startDate1 BETWEEN @startDate2 AND @endDate2) -- caters for inner and end date outer
        OR
        (@endDate1 BETWEEN @startDate2 AND @endDate2) -- caters for inner and start date outer
        OR
        (@startDate2 BETWEEN @startDate1 AND @endDate1) -- only one needed for outer range where dates are inside.
        ) THEN 1 ELSE 0 END
    )
    RETURN @Overlap

END
GO

--Execution of the above code
DECLARE @startDate1 as datetime
DECLARE @endDate1 as datetime
DECLARE @startDate2 as datetime
DECLARE @endDate2 as datetime
DECLARE @Overlap as int
SET @startDate1 = '2014-06-01 01:00:00' 
SET @endDate1 =   '2014-06-01 02:00:00'
SET @startDate2 = '2014-06-01 01:00:00' 
SET @endDate2 =   '2014-06-01 01:30:00'

SET @Overlap = [dbo].[IsOverlapDates]  (@startDate1, @endDate1, @startDate2, @endDate2)

SELECT Overlap = @Overlap

3

le plus simple

Le moyen le plus simple consiste à utiliser une bibliothèque dédiée bien conçue pour le travail date-heure.

someInterval.overlaps( anotherInterval )

java.time & ThreeTen-Extra

Le meilleur de l'entreprise est le java.timecadre intégré à Java 8 et versions ultérieures. Ajoutez à cela le projet ThreeTen-Extra qui complète java.time avec des classes supplémentaires, en particulier la Intervalclasse dont nous avons besoin ici.

Quant à la language-agnosticbalise sur cette question, le code source pour les deux projets est disponible pour une utilisation dans d'autres langues (attention à leurs licences).

Interval

La org.threeten.extra.Intervalclasse est pratique, mais nécessite des moments date-heure ( java.time.Instantobjets) plutôt que des valeurs de date uniquement. Nous procédons donc en utilisant le premier moment de la journée en UTC pour représenter la date.

Instant start = Instant.parse( "2016-01-01T00:00:00Z" );
Instant stop = Instant.parse( "2016-02-01T00:00:00Z" );

Créez un Intervalpour représenter cette période de temps.

Interval interval_A = Interval.of( start , stop );

Nous pouvons également définir un Intervalavec un moment de départ plus un Duration.

Instant start_B = Instant.parse( "2016-01-03T00:00:00Z" );
Interval interval_B = Interval.of( start_B , Duration.of( 3 , ChronoUnit.DAYS ) );

Il est facile de comparer le test des chevauchements.

Boolean overlaps = interval_A.overlaps( interval_B );

Vous pouvez comparer un Intervalcontre un autre Intervalou Instant:

Tous ces éléments utilisent l' Half-Openapproche pour définir une période de temps où le début est inclusif et la fin est exclusive .


3

Il s'agit d'une extension de l' excellente réponse de @ charles-bretana.

Cependant, la réponse ne fait pas de distinction entre les intervalles ouverts, fermés et semi-ouverts (ou semi-fermés).

Cas 1 : A, B sont des intervalles fermés

A = [StartA, EndA]
B = [StartB, EndB]

                         [---- DateRange A ------]   (True if StartA > EndB)
[--- Date Range B -----]                           


[---- DateRange A -----]                             (True if EndA < StartB)
                         [--- Date Range B ----]

Chevauchement ssi: (StartA <= EndB) and (EndA >= StartB)

Cas 2 : A, B sont des intervalles ouverts

A = (StartA, EndA)
B = (StartB, EndB)

                         (---- DateRange A ------)   (True if StartA >= EndB)
(--- Date Range B -----)                           

(---- DateRange A -----)                             (True if EndA <= StartB)
                         (--- Date Range B ----)

Chevauchement ssi: (StartA < EndB) and (EndA > StartB)

Cas 3 : A, B ouvert à droite

A = [StartA, EndA)
B = [StartB, EndB)

                         [---- DateRange A ------)   (True if StartA >= EndB) 
[--- Date Range B -----)                           

[---- DateRange A -----)                             (True if EndA <= StartB)
                         [--- Date Range B ----)

Condition de chevauchement: (StartA < EndB) and (EndA > StartB)

Cas 4 : A, B laissé ouvert

A = (StartA, EndA]
B = (StartB, EndB]

                         (---- DateRange A ------]   (True if StartA >= EndB)
(--- Date Range B -----]                           

(---- DateRange A -----]                             (True if EndA <= StartB)
                         (--- Date Range B ----]

Condition de chevauchement: (StartA < EndB) and (EndA > StartB)

Cas 5 : A ouvert à droite, B fermé

A = [StartA, EndA)
B = [StartB, EndB]

                         [---- DateRange A ------)    (True if StartA > EndB)
[--- Date Range B -----]                           


[---- DateRange A -----)                              (True if EndA <= StartB)  
                         [--- Date Range B ----]

Condition de chevauchement: (StartA <= EndB) and (EndA > StartB)

etc...

Enfin, la condition générale de chevauchement de deux intervalles est

(StartA <🞐 EndB) et (EndA> 🞐 StartB)

où 🞐 transforme une inégalité stricte en une inégalité non stricte chaque fois que la comparaison est faite entre deux points d'extrémité inclus.


Les cas deux, trois et quatre ont la même condition de chevauchement, est-ce intentionnel?
Marie

@Marie, je viens d'énumérer quelques cas (pas tous)
user2314737

Ceci, mais aussi élaboré que la réponse de Jonathan Leffler serait ce que j'avais en tête comme réponse acceptée à la question des PO.
mbx

3

Réponse courte en utilisant momentjs :

function isOverlapping(startDate1, endDate1, startDate2, endDate2){ 
    return moment(startDate1).isSameOrBefore(endDate2) && 
    moment(startDate2).isSameOrBefore(endDate1);
}

la réponse est basée sur les réponses ci-dessus, mais elle est raccourcie.


2

Si vous utilisez une plage de dates qui n'est pas encore terminée (toujours en cours), par exemple, ne définissez pas endDate = '0000-00-00', vous ne pouvez pas utiliser BETWEEN car 0000-00-00 n'est pas une date valide!

J'ai utilisé cette solution:

(Startdate BETWEEN '".$startdate2."' AND '".$enddate2."')  //overlap: starts between start2/end2
OR (Startdate < '".$startdate2."' 
  AND (enddate = '0000-00-00' OR enddate >= '".$startdate2."')
) //overlap: starts before start2 and enddate not set 0000-00-00 (still on going) or if enddate is set but higher then startdate2

Si startdate2 est supérieur à enddate, il n'y a pas de chevauchement!


2

La réponse est trop simple pour moi, j'ai donc créé une instruction SQL dynamique plus générique qui vérifie si une personne a des dates qui se chevauchent.

SELECT DISTINCT T1.EmpID
FROM Table1 T1
INNER JOIN Table2 T2 ON T1.EmpID = T2.EmpID 
    AND T1.JobID <> T2.JobID
    AND (
        (T1.DateFrom >= T2.DateFrom AND T1.dateFrom <= T2.DateTo) 
        OR (T1.DateTo >= T2.DateFrom AND T1.DateTo <= T2.DateTo)
        OR (T1.DateFrom < T2.DateFrom AND T1.DateTo IS NULL)
    )
    AND NOT (T1.DateFrom = T2.DateFrom)

2

La solution mathématique donnée par @Bretana est bonne mais néglige deux détails spécifiques:

  1. aspect des intervalles fermés ou semi-ouverts
  2. intervalles vides

À propos de l'état fermé ou ouvert des limites d'intervalle, la solution de @Bretana valable pour les intervalles fermés

(StartA <= EndB) et (EndA> = StartB)

peut être réécrit pour des intervalles semi-ouverts pour:

(StartA <EndB) et (EndA> StartB)

Cette correction est nécessaire car une limite d'intervalle ouverte n'appartient pas à la plage de valeurs d'un intervalle par définition.


Et à propos des intervalles vides , eh bien, ici, la relation ci-dessus ne tient pas. Les intervalles vides qui ne contiennent par définition aucune valeur valide doivent être traités comme un cas spécial. Je le démontre par ma bibliothèque de temps Java Time4J via cet exemple:

MomentInterval a = MomentInterval.between(Instant.now(), Instant.now().plusSeconds(2));
MomentInterval b = a.collapse(); // make b an empty interval out of a

System.out.println(a); // [2017-04-10T05:28:11,909000000Z/2017-04-10T05:28:13,909000000Z)
System.out.println(b); // [2017-04-10T05:28:11,909000000Z/2017-04-10T05:28:11,909000000Z)

Le crochet carré de tête "[" indique un début fermé tandis que le dernier crochet ")" indique une extrémité ouverte.

System.out.println(
      "startA < endB: " + a.getStartAsInstant().isBefore(b.getEndAsInstant())); // false
System.out.println(
      "endA > startB: " + a.getEndAsInstant().isAfter(b.getStartAsInstant())); // true

System.out.println("a overlaps b: " + a.intersects(b)); // a overlaps b: false

Comme indiqué ci-dessus, les intervalles vides violent la condition de chevauchement ci-dessus (en particulier startA <endB), donc Time4J (et d'autres bibliothèques aussi) doit le traiter comme un cas de bord spécial afin de garantir que le chevauchement de tout intervalle arbitraire avec un intervalle vide n'existe pas. Bien sûr, les intervalles de date (qui sont fermés par défaut dans Time4J mais qui peuvent également être à moitié ouverts, comme les intervalles de date vides) sont traités de la même manière.


1

Voici une méthode générique qui peut être utile localement.

    // Takes a list and returns all records that have overlapping time ranges.
    public static IEnumerable<T> GetOverlappedTimes<T>(IEnumerable<T> list, Func<T, bool> filter, Func<T,DateTime> start, Func<T, DateTime> end)
    {
        // Selects all records that match filter() on left side and returns all records on right side that overlap.
        var overlap = from t1 in list
                      where filter(t1)
                      from t2 in list
                      where !object.Equals(t1, t2) // Don't match the same record on right side.
                      let in1 = start(t1)
                      let out1 = end(t1)
                      let in2 = start(t2)
                      let out2 = end(t2)
                      where in1 <= out2 && out1 >= in2
                      let totover = GetMins(in1, out1, in2, out2)
                      select t2;

        return overlap;
    }

    public static void TestOverlap()
    {
        var tl1 = new TempTimeEntry() { ID = 1, Name = "Bill", In = "1/1/08 1:00pm".ToDate(), Out = "1/1/08 4:00pm".ToDate() };
        var tl2 = new TempTimeEntry() { ID = 2, Name = "John", In = "1/1/08 5:00pm".ToDate(), Out = "1/1/08 6:00pm".ToDate() };
        var tl3 = new TempTimeEntry() { ID = 3, Name = "Lisa", In = "1/1/08 7:00pm".ToDate(), Out = "1/1/08 9:00pm".ToDate() };
        var tl4 = new TempTimeEntry() { ID = 4, Name = "Joe", In = "1/1/08 3:00pm".ToDate(), Out = "1/1/08 8:00pm".ToDate() };
        var tl5 = new TempTimeEntry() { ID = 1, Name = "Bill", In = "1/1/08 8:01pm".ToDate(), Out = "1/1/08 8:00pm".ToDate() };
        var list = new List<TempTimeEntry>() { tl1, tl2, tl3, tl4, tl5 };
        var overlap = GetOverlappedTimes(list, (TempTimeEntry t1)=>t1.ID==1, (TempTimeEntry tIn) => tIn.In, (TempTimeEntry tOut) => tOut.Out);

        Console.WriteLine("\nRecords overlap:");
        foreach (var tl in overlap)
            Console.WriteLine("Name:{0} T1In:{1} T1Out:{2}", tl.Name, tl.In, tl.Out);
        Console.WriteLine("Done");

        /*  Output:
            Records overlap:
            Name:Joe T1In:1/1/2008 3:00:00 PM T1Out:1/1/2008 8:00:00 PM
            Name:Lisa T1In:1/1/2008 7:00:00 PM T1Out:1/1/2008 9:00:00 PM
            Done
         */
    }

1
public static class NumberExtensionMethods
    {
        public static Boolean IsBetween(this Int64 value, Int64 Min, Int64 Max)
        {
            if (value >= Min && value <= Max) return true;
            else return false;
        }

        public static Boolean IsBetween(this DateTime value, DateTime Min, DateTime Max)
        {
            Int64 numricValue = value.Ticks;
            Int64 numericStartDate = Min.Ticks;
            Int64 numericEndDate = Max.Ticks;

            if (numricValue.IsBetween(numericStartDate, numericEndDate) )
            {
                return true;
            }

            return false;
        }
    }

public static Boolean IsOverlap(DateTime startDate1, DateTime endDate1, DateTime startDate2, DateTime endDate2)
        {
            Int64 numericStartDate1 = startDate1.Ticks;
            Int64 numericEndDate1 = endDate1.Ticks;
            Int64 numericStartDate2 = startDate2.Ticks;
            Int64 numericEndDate2 = endDate2.Ticks;

            if (numericStartDate2.IsBetween(numericStartDate1, numericEndDate1) ||
                numericEndDate2.IsBetween(numericStartDate1, numericEndDate1) ||
                numericStartDate1.IsBetween(numericStartDate2, numericEndDate2) ||
                numericEndDate1.IsBetween(numericStartDate2, numericEndDate2))
            {
                return true;
            }

            return false;
        } 


if (IsOverlap(startdate1, enddate1, startdate2, enddate2))
            {
                Console.WriteLine("IsOverlap");
            }

3
Voulez-vous ajouter quelques mots d'explication?
Phantômaxx

1

En utilisant Java util.Date, voici ce que j'ai fait.

    public static boolean checkTimeOverlaps(Date startDate1, Date endDate1, Date startDate2, Date endDate2)
    {
        if (startDate1 == null || endDate1 == null || startDate2 == null || endDate2 == null)
           return false;

        if ((startDate1.getTime() <= endDate2.getTime()) && (startDate2.getTime() <= endDate1.getTime()))
           return true;

        return false;
    }

1

À mon avis, la façon la plus simple de le faire serait de comparer si EndDate1 est avant StartDate2 et EndDate2 est avant StartDate1.

Cela bien sûr si vous envisagez des intervalles où StartDate est toujours avant EndDate.


1

J'ai eu une situation où nous avions des dates au lieu de datetimes, et les dates ne pouvaient se chevaucher qu'au début / fin. Exemple ci-dessous:

entrez la description de l'image ici

(Le vert est l'intervalle actuel, les blocs bleus sont des intervalles valides, les rouges sont des intervalles qui se chevauchent).

J'ai adapté la réponse d'Ian Nelson à la solution suivante:

   (startB <= startA && endB > startA)
|| (startB >= startA && startB < endA)

Cela correspond à tous les cas de chevauchement, mais ignore ceux qui se chevauchent.


0

Divisez le problème en cas, puis gérez chaque cas .

La situation «deux plages de dates se croisent» est couverte par deux cas - la première plage de dates commence dans la seconde ou la deuxième plage de dates commence dans la première.


0

Vous pouvez essayer ceci:

//custom date for example
$d1 = new DateTime("2012-07-08");
$d2 = new DateTime("2012-07-11");
$d3 = new DateTime("2012-07-08");
$d4 = new DateTime("2012-07-15");

//create a date period object
$interval = new DateInterval('P1D');
$daterange = iterator_to_array(new DatePeriod($d1, $interval, $d2));
$daterange1 = iterator_to_array(new DatePeriod($d3, $interval, $d4));
array_map(function($v) use ($daterange1) { if(in_array($v, $daterange1)) print "Bingo!";}, $daterange);

0

C'était ma solution, elle retourne vrai lorsque les valeurs ne se chevauchent pas:

X DÉBUT 1 Y FIN 1

A DÉBUT 2 B FIN 2

TEST1: (X <= A || X >= B)
        &&
TEST2: (Y >= B || Y <= A) 
        && 
TEST3: (X >= B || Y <= A)


X-------------Y
    A-----B

TEST1:  TRUE
TEST2:  TRUE
TEST3:  FALSE
RESULT: FALSE

---------------------------------------

X---Y
      A---B

TEST1:  TRUE
TEST2:  TRUE
TEST3:  TRUE
RESULT: TRUE

---------------------------------------

      X---Y
A---B

TEST1:  TRUE
TEST2:  TRUE
TEST3:  TRUE
RESULT: TRUE

---------------------------------------

     X----Y
A---------------B

TEST1:  FALSE
TEST2:  FALSE
TEST3:  FALSE
RESULT: FALSE

0

Pour rubis, j'ai également trouvé ceci:

class Interval < ActiveRecord::Base

  validates_presence_of :start_date, :end_date

  # Check if a given interval overlaps this interval    
  def overlaps?(other)
    (start_date - other.end_date) * (other.start_date - end_date) >= 0
  end

  # Return a scope for all interval overlapping the given interval, including the given interval itself
  named_scope :overlapping, lambda { |interval| {
    :conditions => ["id <> ? AND (DATEDIFF(start_date, ?) * DATEDIFF(?, end_date)) >= 0", interval.id, interval.end_date, interval.start_date]
  }}

end

Je l'ai trouvé ici avec une belle explication -> http://makandracards.com/makandra/984-test-if-two-date-ranges-overlap-in-ruby-or-rails


0

La requête ci-dessous me donne les identifiants pour lesquels la plage de dates fournie (dates de début et de fin se chevauche avec l'une des dates (dates de début et de fin) de mon nom_table)

select id from table_name where (START_DT_TM >= 'END_DATE_TIME'  OR   
(END_DT_TM BETWEEN 'START_DATE_TIME' AND 'END_DATE_TIME'))
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.