Determina se due intervalli di date si sovrappongono


1251

Dati due intervalli di date, qual è il modo più semplice o più efficiente per determinare se i due intervalli di date si sovrappongono?

Ad esempio, supponiamo di avere intervalli indicati dalle variabili DateTime StartDate1da EndDate1 e StartDate2 a EndDate2.



@CharlesBretana grazie per quello, hai ragione - è quasi come una versione bidimensionale della mia domanda!
Ian Nelson,


2
Dividi la situazione "i due intervalli di date si intersecano" in casi (ce ne sono due) quindi verifica per ogni caso.
Colonnello Panic,

1
Questo codice funziona bene. Potete vedere la mia risposta qui: stackoverflow.com/a/16961719/1534785
Jeyhun Rahimov

Risposte:


2290

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

Prova:
Lascia che Condizione A Significhi che DateRange A Completamente Dopo DateRange B
_ |---- DateRange A ------| |---Date Range B -----| _
(Vero se StartA > EndB)

Lascia che condizione B significhi che l'intervallo di date A è completamente precedente all'intervallo di date B
|---- DateRange A -----| _ _ |---Date Range B ----|
(vero se EndA < StartB)

Quindi la sovrapposizione esiste se Né A né B sono vere -
(Se un intervallo non è né completamente dopo l'altro,
né completamente prima dell'altro, devono sovrapporsi).

Ora una delle leggi di De Morgan dice che:

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

Che si traduce in: (StartA <= EndB) and (EndA >= StartB)


NOTA: ciò include le condizioni in cui i bordi si sovrappongono esattamente. Se si desidera escluderlo,
modificare gli >=operatori in >e <= in<


NOTA 2. Grazie a @Baodad, vedere questo blog , la sovrapposizione reale è almeno di:
{ endA-startA, endA - startB, endB-startA, endB - startB}

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


NOTA 3. Grazie a @tomosius, si legge una versione più breve: si
DateRangesOverlap = max(start1, start2) < min(end1, end2)
tratta in realtà di una scorciatoia sintattica per un'implementazione più lunga, che include controlli extra per verificare che le date di inizio siano entro o prima delle date di fine. Derivando questo dall'alto:

Se le date di inizio e fine possono essere fuori servizio, ovvero se è possibile che startA > endAo startB > endB, allora devi anche controllare che siano in ordine, quindi ciò significa che devi aggiungere due regole di validità aggiuntive:
(StartA <= EndB) and (StartB <= EndA) and (StartA <= EndA) and (StartB <= EndB) oppure:
(StartA <= EndB) and (StartA <= EndA) and (StartB <= EndA) and (StartB <= EndB) oppure,
(StartA <= Min(EndA, EndB) and (StartB <= Min(EndA, EndB)) oppure:
(Max(StartA, StartB) <= Min(EndA, EndB)

Ma per implementare Min()e Max(), devi codificare, (usando C ternary per terseness) ,:
(StartA > StartB? Start A: StartB) <= (EndA < EndB? EndA: EndB)


29
Questa è una logica semplificata basata su questi due presupposti: 1) StartA <EndA; 2) StartB <EndB. Sembra ovvio, ma in realtà i dati possono provenire da fonti sconosciute come l'input dell'utente o un database senza sanitizzazione. Tieni presente che dovrai convalidare i dati di input per assicurarti che quei due presupposti siano veri prima di poter utilizzare questa logica semplificata o che tutto andrà in pezzi. Lezione imparata dalla mia esperienza personale;)
Devy,

12
@Devy, hai ragione. Tranne che funzionerà anche se startA = endA. In effetti, questo è esattamente ciò che le parole Starte Endsignificano. Se hai due variabili denominate Top e Bottom, o East e West, o HighValue e LoValue, si può presumere o implicare che qualcosa o qualcuno, da qualche parte dovrebbe garantire che una delle coppie di valori non sia memorizzata nelle variabili opposte. -Solo una delle due coppie perché, beh, funzionerà anche se entrambe le coppie di valori vengono commutate.
Charles Bretana,

15
Puoi facilmente aggiungere nullable starte end(con la semantica che "null start" = "Dall'inizio del tempo" e "null end" = "Alla fine del tempo") in questo modo:(startA === null || endB === null || startA <= endB) && (endA === null || startB === null || endA >= startB)
Kevin Robatel,

9
La migliore risposta su Stackexchange! È bello vedere una spiegazione sul perché questa formula intelligente funzioni!
Abeer Sul

4
Ecco la forma più compatta che mi viene in mente, che restituisce anche false in caso di input non valido (data di inizio> = data di fine)DateRangesOverlap = max(start1, start2) < min(end1, end2)
tomosius

406

Credo che sia sufficiente dire che i due intervalli si sovrappongono se:

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

76
Trovo la (StartDate1 <= EndDate2) and (EndDate1 >= StartDate2)notazione più facile da capire, Range1 è sempre a sinistra nei test.
AL,

8
Ciò presuppone che le date di inizio e fine siano incluse. Passa <=a <se inizio è inclusivo e fine è esclusivo.
Richard Schneider,

Funzionerà molto bene anche se startDate2 è precedente a startDate1. Quindi non è necessario supporre che startDate1 sia precedente a startDate2.
Shehan Simen

3
Ho trovato (StartDate1 <= EndDate2) e (StartDate2 <= EndDate1) la notazione (come da risposta) più facile da capire rispetto a quella in altre risposte.
apc

Come adattarsi in modo che funzioni con i dati che hanno StartDate1 AND / OR EndDate1? Il codice presuppone che StartDate1 e EndDate1 siano sempre presenti. Cosa succede se viene fornito StartDate1 ma non viene fornito EndDate1 OR EndDate1 ma non StartDate1. Come gestire questo caso in più?
Jufo

117

Questo articolo Time Period Library per .NET descrive la relazione di due periodi di tempo dall'enumerazione PeriodRelation :

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

inserisci qui la descrizione dell'immagine


Bene, ho implementato anche l'algebra dell'intervallo Allens in Java, vedi l' API di IntervalRelation e IsoInterval
Meno Hochschild

80

Per ragionare sulle relazioni temporali (o su qualsiasi altra relazione di intervallo, vieni a quello), considera l'algebra a intervalli di Allen . Descrive le 13 possibili relazioni che possono avere due intervalli l'uno rispetto all'altro. Puoi trovare altri riferimenti - "Allen Interval" sembra essere un termine di ricerca operativa. È inoltre possibile trovare informazioni su queste operazioni in Snodgrass's Sviluppo di applicazioni orientate al tempo di in SQL (PDF disponibile online all'URL) e in Data, Darwen e Lorentzos Temporal Data and the Relational Model (2002) o Time and Relational Theory: Database temporali in the Relational Model and SQL (2014; in effetti la seconda edizione di TD&RM).


La risposta breve (ish) è: dati due intervalli di date AeB con i componenti .starte .ende il vincolo .start <= .end, quindi due intervalli si sovrappongono se:

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

È possibile ottimizzare l'uso di >= vs >e <=vs <per soddisfare i requisiti per il grado di sovrapposizione.


Commenti di ErikE:

Puoi ottenere 13 solo se conti cose divertenti ... Posso ottenere "15 possibili relazioni che possono avere due intervalli" quando impazzisco. Conteggio ragionevole, ne ottengo solo sei, e se ti butti preoccupato se A o B arrivano per primi, ne ottengo solo tre (nessuna intersezione, parzialmente si intersecano, una completamente all'interno dell'altra). 15 va così: [prima: prima, inizio, entro, fine, dopo], [inizio: inizio, entro, fine, dopo], [entro: entro, fine, dopo], [fine: fine, dopo], [ dopo: dopo].

Penso che non si possano contare le due voci "prima: prima" e "dopo: dopo". Potrei vedere 7 voci se si equivalgono alcune relazioni con i loro inversi (vedere il diagramma nell'URL di Wikipedia di riferimento; ha 7 voci, 6 delle quali hanno un inverso diverso, con uguali che non hanno un inverso distinto). E se tre è ragionevole dipende dalle tue esigenze.

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

1
Puoi ottenere 13 solo se conti cose divertenti ... Posso ottenere "15 possibili relazioni che possono avere due intervalli" quando impazzisco. Conteggio ragionevole, ne ottengo solo sei, e se ti butti preoccupato se A o B arrivano per primi, ne ottengo solo tre (nessuna intersezione, parzialmente si intersecano, una completamente all'interno dell'altra). 15 va così: [prima: prima, inizio, entro, fine, dopo], [inizio: inizio, entro, fine, dopo], [entro: entro, fine, dopo], [fine: fine, dopo], [ dopo: dopo].
ErikE

@Emtucifor: penso che non puoi contare le due voci 'prima: prima' e 'dopo: dopo'.
Jonathan Leffler

Per quanto riguarda l'aggiornamento: da B1 a A è precedente: prima e da B13 a A dopo: dopo. Nel tuo bel diagramma manca l'inizio: inizia tra B5 B6 e fine: termina tra B11 e B12. Se essere su un endpoint è significativo, allora devi contarlo, quindi il conteggio finale è 15, non 13. Non penso che la cosa dell'endpoint sia significativa, quindi la conto personalmente [prima: prima, entro, dopo] , [entro: entro, dopo], [dopo: dopo] che arriva a 6. Penso che l'intera cosa dell'endpoint sia solo confusione sul fatto che i limiti siano inclusivi o esclusivi. L'esclusività degli endpoint non cambia le relazioni fondamentali!
ErikE

Cioè, nel mio schema questi sono equivalenti: (B2, B3, B4), (B6, B7, B9, B10), (B8, B11, B12). Mi rendo conto che B7 implica l'informazione che i due intervalli coincidono esattamente. Ma non sono convinto che queste informazioni aggiuntive dovrebbero far parte delle relazioni di intersezione di base. Ad esempio, quando due intervalli hanno la stessa identica lunghezza anche se non coincidenti o addirittura sovrapposti, dovrebbe essere considerata un'altra "relazione"? Dico di no, e visto che questo ulteriore aspetto è l'unica cosa che distingue B7 da B6, quindi penso che avere endpoint come casi separati renda le cose incoerenti.
ErikE

@Emtucifor: OK - Vedo perché ho identificato erroneamente "before: before" e "after: after" come voci; tuttavia, non riesco a immaginare come dovrebbero apparire le voci 'start: start' e 'end: end'. Dato che non puoi modificare il mio diagramma, puoi inviarmi un'e-mail (vedi il mio profilo) con una copia modificata del diagramma che mostra le relazioni 'inizio: inizio' e 'fine: fine'? Non ho grossi problemi con i tuoi raggruppamenti.
Jonathan Leffler

30

Se anche la sovrapposizione stessa deve essere calcolata, puoi utilizzare la seguente formula:

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

così si sovrappongono i tempi che condividono i due eventi? Funziona in tutti i modi diversi in cui gli eventi possono sovrapporsi?
NSjonas,

18

Tutte le soluzioni che controllano una moltitudine di condizioni in base a dove gli intervalli sono in relazione tra loro possono essere notevolmente semplificate semplicemente assicurando che un intervallo specifico inizi prima! Assicurati che il primo intervallo inizi prima (o allo stesso tempo) scambiando gli intervalli se necessario in anticipo.

Quindi, è possibile rilevare la sovrapposizione se l'inizio di un altro intervallo è inferiore o uguale alla fine del primo intervallo (se gli intervalli sono inclusivi, che contengono sia l'ora di inizio che di fine) o minore di (se gli intervalli sono comprensivi dell'inizio ed esclusione della fine) .

Supponendo inclusivo ad entrambe le estremità, ci sono solo quattro possibilità di cui una non si sovrappone:

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

L'endpoint dell'intervallo 2 non entra in esso. Quindi, in pseudo-codice:

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

Ciò potrebbe essere ulteriormente semplificato in:

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

Se gli intervalli sono inclusivi all'inizio ed esclusivi alla fine, devi solo sostituirli >con >=nella seconda ifistruzione (per il primo segmento di codice: nel secondo segmento di codice, dovresti utilizzare <anziché <=):

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

Limiti notevolmente il numero di controlli che devi effettuare perché rimuovi la metà dello spazio problematico in anticipo assicurando che l'intervallo 1 non inizi mai dopo l'intervallo 2.


2
+1 per menzionare il problema inclusivo / esclusivo. Stavo per trovare una risposta quando avevo tempo, ma non ce n'è bisogno ora. Il fatto è che non permetti quasi mai sia all'inizio che alla fine di essere inclusive contemporaneamente. Nel mio settore è prassi comune considerare l'inizio come esclusivo e la fine come inclusiva, ma in entrambi i casi va bene finché si rimane coerenti. Questa è la prima risposta completamente corretta su questa domanda finora ... IMO.
Brian Gideon,

14

Ecco un'altra soluzione che utilizza JavaScript. Specialità della mia soluzione:

  • Gestisce i valori null come infinito
  • Presuppone che il limite inferiore sia inclusivo e il limite superiore esclusivo.
  • Viene fornito con un sacco di test

I test si basano su numeri interi ma poiché gli oggetti data in JavaScript sono comparabili, puoi anche inserire due oggetti data. Oppure potresti lanciare il millisecondo di data e ora.

Codice:

/**
 * 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);
}

test:

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);
    });
});

Risultato se eseguito con karma e gelsomino e PhantomJS:

PhantomJS 1.9.8 (Linux): eseguito 20 di 20 SUCCESS (0,003 secondi / 0,004 secondi)


9

farei

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

Dov'è IsBetweenqualcosa del genere

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

Preferirei (sinistra <valore && valore <destra) || (destra <valore && valore <sinistra) per questo metodo.
Patrick Huizinga,

Grazie per questo. Mi semplifica le cose.
mostra il

1
Perché dovresti controllare quattro condizioni quando devi controllarne solo due? Fallire.
ErikE

3
Ah, mi scuso, vedo ora che stai permettendo che gli intervalli siano in ordine inverso (StartDateX> EndDateX). Strano. In ogni caso, cosa succede se StartDate1 è inferiore a StartDate2 e EndDate1 è maggiore di EndDate2? Il codice che hai fornito non rileverà questa condizione di sovrapposizione.
ErikE

3
Questo ritorno non sarà falso se Date1 contiene Date2 intero? Quindi StartDate1 è prima di StartDate2 e EndDate1 è dopo EndDate2
user158037

9

inserisci qui la descrizione dell'immagine

Ecco il codice che fa la magia:

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

Dove..

  • A -> 1Avvia
  • B -> 1End
  • C -> 2Avvia
  • D -> 2End

Prova? Dai un'occhiata a questo codice della console di test .


Funziona, ma preferirei provare per non sovrapporre, solo due scenari
John Albert,

Grazie per aver spiegato questo usando le immagini. La tua risposta è la soluzione perfetta per questa domanda.
Rakesh Verma,

8

Ecco la mia soluzione in Java , che funziona anche a intervalli illimitati

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));
}

Penso che volevi dire limiti illimitati anziché intervalli aperti.
Henrik,


!startA.after(endB)significa startA <= endB e !endA.before(startB)significa startB <= endA. Questi sono i criteri per un intervallo chiuso e non per un intervallo aperto.
Henrik

@Henrik true e le altre condizioni come endB == nulle startA == nullverificare la presenza di un intervallo aperto.
Khaled.K,

1
endB == null, startA == null, endA == nullE startB == nullsono tutti i criteri per controllare un intervallo illimitata e non un intervallo aperto. Esempio per le differenze tra intervalli illimitati e aperti: (10, 20) e (20, null) sono due intervalli aperti che non si sovrappongono. L'ultimo ha una fine illimitata. La tua funzione tornerà vera, ma gli intervalli non si sovrappongono, perché gli intervalli non includono 20. (per semplicità si utilizzano i numeri anziché i timestamp)
Henrik

7

La soluzione pubblicata qui non ha funzionato per tutti gli intervalli sovrapposti ...

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

la mia soluzione di lavoro era:

E (
  ('start_date' TRA STARTDATE E ENDDATE) - si rivolge alla data di scadenza interna ed esterna
  O
  ('end_date' TRA STARTDATE E ENDDATE) - si rivolge a interno e data di inizio esterno
  O
  (STARTDATE TRA 'start_date' AND 'end_date') - solo uno necessario per l'intervallo esterno in cui sono presenti le date.
) 

5

Questa era la mia soluzione javascript con 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 modo semplice per ricordare la soluzione sarebbe
min(ends)>max(starts)


3

In Microsoft SQL SERVER - Funzione 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

il più semplice

Il modo più semplice è utilizzare una libreria dedicata ben progettata per il lavoro con data e ora.

someInterval.overlaps( anotherInterval )

java.time e ThreeTen-Extra

Il migliore del settore è il java.timeframework integrato in Java 8 e versioni successive. Aggiungete a ciò il progetto ThreeTen-Extra che integra java.time con classi aggiuntive, in particolare la Intervalclasse di cui abbiamo bisogno qui.

Per quanto riguarda il language-agnostictag su questa domanda, il codice sorgente per entrambi i progetti è disponibile per l'uso in altre lingue (attenzione alle loro licenze).

Interval

La org.threeten.extra.Intervalclasse è utile, ma richiede momenti ( java.time.Instantoggetti) di data e ora anziché valori di sola data. Quindi procediamo utilizzando il primo momento della giornata in UTC per rappresentare la data.

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

Crea un Intervalper rappresentare quel lasso di tempo.

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

Possiamo anche definire un Intervalcon un momento iniziale più a Duration.

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

Il confronto con il test per le sovrapposizioni è facile.

Boolean overlaps = interval_A.overlaps( interval_B );

Puoi confrontare un con un Intervalaltro Intervalo Instant:

Tutti questi usano l' Half-Openapproccio per definire un arco di tempo in cui l'inizio è inclusivo e la fine è esclusiva .


3

Questa è un'estensione dell'ottima risposta di @ charles-bretana.

La risposta tuttavia non fa una distinzione tra intervalli aperti, chiusi e semiaperti (o semichiusi).

Caso 1 : A, B sono intervalli chiusi

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

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


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

Sovrapponi iff: (StartA <= EndB) and (EndA >= StartB)

Caso 2 : A, B sono intervalli aperti

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

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

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

Sovrapponi iff: (StartA < EndB) and (EndA > StartB)

Caso 3 : A, B aperto a destra

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

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

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

Condizioni di sovrapposizione: (StartA < EndB) and (EndA > StartB)

Caso 4 : A, B lasciato aperto

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

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

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

Condizioni di sovrapposizione: (StartA < EndB) and (EndA > StartB)

Caso 5 : A destra aperta, B chiusa

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

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


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

Condizioni di sovrapposizione: (StartA <= EndB) and (EndA > StartB)

eccetera...

Infine, la condizione generale per la sovrapposizione di due intervalli è

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

dove 🞐 trasforma una disparità rigorosa in non diseguale ogni volta che viene effettuato il confronto tra due endpoint inclusi.


I casi due, tre e quattro hanno la stessa condizione di sovrapposizione, è intenzionale?
Marie,

@Marie, ho appena elencato alcuni casi (non tutti)
user2314737

Questo, ma elaborato come la risposta di Jonathan Leffler sarebbe quello che avevo in mente come risposta accettata alla domanda dei PO.
mbx,

3

Risposta breve usando momentjs :

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

la risposta si basa sulle risposte sopra, ma è abbreviata.


2

Nel caso in cui si stia utilizzando un intervallo di date che non è ancora terminato (ancora in corso), ad es. Non impostare endDate = '0000-00-00' non è possibile utilizzare TRA perché la registrazione non è una data valida!

Ho usato questa soluzione:

(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

Se startdate2 è più alto di enddate non c'è sovrapposizione!


2

La risposta è troppo semplice per me, quindi ho creato un'istruzione SQL dinamica più generica che verifica se una persona ha date sovrapposte.

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 soluzione matematica fornita da @Bretana è buona ma trascura due dettagli specifici:

  1. aspetto di intervalli chiusi o semiaperti
  2. intervalli vuoti

Informazioni sullo stato chiuso o aperto dei limiti dell'intervallo, la soluzione di @Bretana è valida per gli intervalli chiusi

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

può essere riscritto per intervalli semiaperti in:

(StartA <EndB) e (EndA> StartB)

Questa correzione è necessaria perché un limite di intervallo aperto non appartiene all'intervallo di valori di un intervallo per definizione.


E a intervalli vuoti , beh, qui la relazione mostrata sopra NON regge. Gli intervalli vuoti che non contengono alcun valore valido per definizione devono essere gestiti come casi speciali. Lo dimostra con la mia libreria temporale Java Time4J tramite questo esempio:

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)

La parentesi quadra iniziale "[" indica un inizio chiuso mentre l'ultima parentesi ")" indica un'estremità aperta.

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

Come mostrato sopra, gli intervalli vuoti violano la condizione di sovrapposizione sopra (in particolare startA <endB), quindi Time4J (e anche altre librerie) devono gestirlo come un caso limite speciale per garantire che la sovrapposizione di qualsiasi intervallo arbitrario con un intervallo vuoto non esiste. Naturalmente, gli intervalli di date (che sono chiusi di default in Time4J ma possono anche essere semi-aperti, come gli intervalli di date vuoti) sono gestiti in modo simile.


1

Ecco un metodo generico che può essere utile localmente.

    // 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
Ti dispiace aggiungere alcune parole di spiegazione?
Phantômaxx,

1

Usando Java util.Date, ecco cosa ho fatto.

    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

A mio avviso, il modo più semplice per farlo sarebbe confrontare se EndDate1 è prima di StartDate2 e EndDate2 è prima di StartDate1.

Questo ovviamente se stai considerando gli intervalli in cui StartDate è sempre prima di EndDate.


1

Ho avuto una situazione in cui c'erano date invece di orari, e le date potevano sovrapporsi solo all'inizio / alla fine. Esempio sotto:

inserisci qui la descrizione dell'immagine

(Il verde è l'intervallo corrente, i blocchi blu sono intervalli validi, quelli rossi sono intervalli sovrapposti).

Ho adattato la risposta di Ian Nelson alla seguente soluzione:

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

Questo corrisponde a tutti i casi di sovrapposizione ma ignora quelli sovrapposti consentiti.


0

Dividi il problema in casi, quindi gestisci ciascun caso .

La situazione "due intervalli di date si intersecano" è coperta da due casi: il primo intervallo di date inizia nel secondo o il secondo intervallo di date inizia nel primo.


0

Puoi provare questo:

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

Questa era la mia soluzione, restituisce vero quando i valori non si sovrappongono:

X INIZIO 1 Y FINE 1

A AVVIO 2 B FINE 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

Per ruby ​​ho anche trovato questo:

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

L'ho trovato qui con una bella spiegazione -> http://makandracards.com/makandra/984-test-if-two-date-ranges-overlap-in-ruby-or-rails


0

La query seguente mi fornisce gli ID per i quali l'intervallo di date fornito (date di inizio e fine si sovrappone a una qualsiasi delle date (date di inizio e fine) nel mio nome_tabella

select id from table_name where (START_DT_TM >= 'END_DATE_TIME'  OR   
(END_DT_TM BETWEEN 'START_DATE_TIME' AND 'END_DATE_TIME'))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.