Determinar se duas faixas de datas se sobrepõem


1250

Dado dois períodos, qual é a maneira mais simples ou mais eficiente de determinar se os dois períodos se sobrepõem?

Como exemplo, suponha que tenhamos intervalos indicados pelas variáveis ​​DateTime StartDate1para EndDate1 e StartDate2 para EndDate2.



@CharlesBretana obrigado por isso, você está certo - é quase como uma versão bidimensional da minha pergunta!
Ian Nelson


2
Divida a situação 'os dois intervalos de datas se cruzam' em casos (existem dois) e teste para cada caso.
Coronel Panic

1
Esse código funciona bem. Você pode ver a minha resposta aqui: stackoverflow.com/a/16961719/1534785
Jeyhun Rahimov

Respostas:


2290

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

Prova:
Let ConditionA significa que o DateRange A completamente após o DateRange B
_ |---- DateRange A ------| |---Date Range B -----| _
(verdadeiro se StartA > EndB)

Let ConditionB significa que o DateRange A é completamente anterior ao DateRange B
|---- DateRange A -----| _ _ |---Date Range B ----|
(True se EndA < StartB)

Então a Sobreposição existe se Nem A nem B são verdadeiros -
(Se um intervalo não é completamente após o outro,
nem completamente antes do outro, eles devem se sobrepor.)

Agora, uma das leis de De Morgan diz que:

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

Que se traduz em: (StartA <= EndB) and (EndA >= StartB)


NOTA: Isso inclui condições em que as bordas se sobrepõem exatamente. Se você deseja excluir isso,
altere os >=operadores para >e <= para<


NOTA 2. Graças à @Baodad, consulte este blogue , a sobreposição real é menos:
{ endA-startA, endA - startB, endB-startA, endB - startB}

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


NOTA 3. Graças a @tomosius, uma versão mais curta diz:
DateRangesOverlap = max(start1, start2) < min(end1, end2)
Na verdade, é um atalho sintático para uma implementação mais longa, que inclui verificações extras para verificar se as datas de início estão em ou antes das datas finais. Derivando isso de cima:

Se as datas de início e fim pode estar fora de ordem, ou seja, se é possível que startA > endAou startB > endB, então você também tem que verificar se eles estão em ordem, o que significa que você tem que adicionar duas regras de validade adicionais:
(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)

Mas para implementar Min()e Max(), você deve codificar (usando C ternary para terseness):
(StartA > StartB? Start A: StartB) <= (EndA < EndB? EndA: EndB)


29
Essa é uma lógica simplificada com base nessas duas suposições: 1) StartA <EndA; 2) StartB <EndB. Parece óbvio, mas, na realidade, os dados podem vir de fontes desconhecidas, como entrada do usuário ou banco de dados sem sanitização. Lembre-se de que você precisará validar os dados de entrada para garantir que essas duas suposições sejam verdadeiras antes de poder usar essa lógica simplificada ou tudo estará desmoronando. Lição aprendida a partir de minha própria experiência;)
Devy

12
@ Devy, você está correto. Exceto que também funcionará se startA = endA. Na verdade, é exatamente isso que as palavras Starte o Endsignificado. Se você tiver duas variáveis ​​denominadas Top e Bottom, ou East e West, ou HighValue e LoValue, pode ser assumido ou implícito que algo ou alguém, em algum lugar, deve garantir que um dos pares de valores não seja armazenado nas variáveis ​​opostas. -Só um dos dois pares porque, bem, também funcionará se os dois pares de valores forem alterados.
Charles Bretana

15
Você pode facilmente adicionar valores nulos starte end(com a semântica que "null start" = "Desde o início dos tempos" e "null end" = "Até o fim dos tempos") assim:(startA === null || endB === null || startA <= endB) && (endA === null || startB === null || endA >= startB)
Kevin Robatel

9
Melhor resposta no Stackexchange! É bom ver uma explicação sobre por que essa fórmula inteligente funciona!
Abeer Sul 12/03

4
Aqui é a forma mais compacta que eu poderia pensar, que também retorna false em caso de entrada inválida (a data de início> = data final)DateRangesOverlap = max(start1, start2) < min(end1, end2)
tomosius

406

Acredito que basta dizer que as duas faixas se sobrepõem se:

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

76
Acho a (StartDate1 <= EndDate2) and (EndDate1 >= StartDate2)notação mais fácil de entender, o Range1 está sempre à esquerda nos testes.
AL

8
Isso pressupõe que as datas de início e término sejam inclusivas. Altere <=para <se o início é inclusivo e o final é exclusivo.
Richard Schneider

Isso funcionará muito bem, mesmo que startDate2 seja anterior a startDate1. Portanto, não é necessário assumir que startDate1 é anterior a startDate2.
Shehan Simen

3
Achei (StartDate1 <= EndDate2) e (StartDate2 <= EndDate1) a notação (conforme a resposta) mais fácil de entender do que em outras respostas.
apc

Como se adaptar para que funcione com dados que possuem StartDate1 AND / OR EndDate1? O código pressupõe que StartDate1 e EndDate1 estão sempre presentes. E se StartDate1 for fornecido, mas não EndDate1 OU EndDate1 dado, mas não StartDate1. Como lidar com este caso extra?
jufo

117

Este artigo Biblioteca de Períodos de Tempo para .NET descreve a relação de dois períodos pela enumeração PeriodRelation :

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

insira a descrição da imagem aqui


Bom, eu ter implementado Allens álgebra intervalo em Java, também, ver a API de IntervalRelation e IsoInterval
Meno Hochschild

80

Para raciocinar sobre relações temporais (ou quaisquer outras relações de intervalo, chegue a isso), considere a Álgebra de Intervalo de Allen . Descreve as 13 possíveis relações que dois intervalos podem ter um em relação ao outro. Você pode encontrar outras referências - "Allen Interval" parece ser um termo de pesquisa operacional. Você também pode encontrar informações sobre essas operações em Developing Time-Oriented Applications em SQL do Snodgrass (PDF disponível on-line em URL) e em Date, Darwen e Lorentzos Temporal Data and the Relational Model (2002) ou Time and Relational Theory: Temporal Databases in o Modelo Relacional e SQL (2014; efetivamente a segunda edição do TD&RM).


A resposta curta (ish) é: dados dois intervalos de data Ae Bcom componentes .starte .ende a restrição .start <= .end, dois intervalos se sobrepõem se:

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

Você pode ajustar o uso de >=vs >e <=vs <para atender aos seus requisitos de grau de sobreposição.


ErikE comenta:

Você só pode ter 13 anos se contar coisas engraçadas ... Eu consigo "15 relações possíveis que dois intervalos podem ter" quando eu enlouquece. Pela contagem sensata, recebo apenas seis e, se você se importar em saber se A ou B vem em primeiro lugar, recebo apenas três (nenhuma interseção, parcialmente interseção, uma totalmente dentro da outra). 15 é assim: [antes: antes, começo, dentro, fim, depois], [começo: começo, dentro, fim, depois], [dentro: dentro, fim, depois], [fim: fim, depois], [ depois: depois].

Eu acho que você não pode contar as duas entradas 'antes: antes' e 'depois: depois'. Eu poderia ver 7 entradas se você equiparar algumas relações aos seus inversos (veja o diagrama no URL da Wikipedia referenciado; ele tem 7 entradas, 6 das quais têm um inverso diferente, com iguais não tendo um inverso distinto). E se três é sensato depende de suas necessidades.

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

1
Você só pode ter 13 anos se contar coisas engraçadas ... Eu consigo "15 relações possíveis que dois intervalos podem ter" quando eu enlouquece. Pela contagem sensata, recebo apenas seis e, se você se importar em saber se A ou B vem em primeiro lugar, recebo apenas três (nenhuma interseção, parcialmente interseção, uma totalmente dentro da outra). 15 é assim: [antes: antes, começo, dentro, fim, depois], [começo: começo, dentro, fim, depois], [dentro: dentro, fim, depois], [fim: fim, depois], [ depois: depois].
ErikE

@Emtucifor: Eu acho que você não pode contar as duas entradas 'antes: antes' e 'depois: depois'.
Jonathan Leffler

Re sua atualização: B1 a A é anterior: antes e B13 a A é posterior: depois. Seu diagrama está faltando: start: start entre B5 B6 e end: end entre B11 e B12. Se estar em um endpoint é significativo, você deve contar, então a contagem final é 15, não 13. Eu não acho que o endpoint seja significativo, então eu o conto pessoalmente [antes: antes, dentro, depois] , [inside: inside, after], [after: after], que chega a 6. Acho que todo o ponto final é apenas confusão sobre se os limites são inclusivos ou exclusivos. A exclusividade dos pontos de extremidade não altera as relações principais!
ErikE

Ou seja, no meu esquema, estes são equivalentes: (B2, B3, B4), (B6, B7, B9, B10), (B8, B11, B12). Percebo que B7 implica a informação de que os dois intervalos coincidem exatamente. Mas não estou convencido de que essas informações adicionais devam fazer parte das relações básicas de interseção. Por exemplo, quando dois intervalos têm exatamente o mesmo comprimento, mesmo que não sejam coincidentes ou mesmo sobrepostos, isso deve ser considerado outra "relação"? Eu digo que não, e como esse aspecto adicional é a única coisa que diferencia B7 de B6, acho que ter pontos finais como casos separados torna as coisas inconsistentes.
ErikE

@ Emtififor: OK - vejo por que identifiquei erroneamente 'antes: antes' e 'depois: depois' como as entradas; no entanto, não consigo imaginar como devem ser as entradas 'start: start' e 'end: end'. Como você não pode editar meu diagrama, pode me enviar um email (consulte meu perfil) com uma cópia modificada do diagrama mostrando os relacionamentos 'start: start' e 'end: end'? Não tenho grandes problemas com seus agrupamentos.
Jonathan Leffler

30

Se a sobreposição em si também deve ser calculada, você pode usar a seguinte fórmula:

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

sobreposição é a quantidade de tempo que os dois eventos compartilham? Isso funciona para todas as diferentes maneiras pelas quais os eventos podem se sobrepor?
NSjonas 9/09/16

18

Todas as soluções que verificam uma infinidade de condições com base em onde os intervalos estão em relação um ao outro podem ser bastante simplificadas, garantindo apenas que um intervalo específico comece mais cedo! Você garante que o primeiro intervalo inicie mais cedo (ou ao mesmo tempo) trocando os intervalos, se necessário, antecipadamente.

Em seguida, é possível detectar sobreposição se o outro início do intervalo for menor ou igual ao final do primeiro intervalo (se os intervalos forem inclusivos, contendo os horários de início e de término) ou menor que (se os intervalos incluirem o início e o exclusivo) .

Supondo que seja inclusivo nas duas extremidades, existem apenas quatro possibilidades, das quais uma não é sobreposta:

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

O ponto final do intervalo 2 não entra nele. Então, no pseudo-código:

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

Isso pode ser simplificado ainda mais em:

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

Se os intervalos forem inclusivos no início e exclusivos no final, basta substituir >por >=na segunda ifinstrução (para o primeiro segmento de código: no segundo segmento de código, você usaria em <vez de <=):

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

Você limita bastante o número de verificações que precisa fazer porque remove metade do espaço do problema mais cedo, garantindo que o intervalo 1 nunca seja iniciado após o intervalo 2.


2
+1 por mencionar o problema inclusivo / exclusivo. Eu ia me responder quando tivesse tempo, mas não precisava agora. O problema é que você quase nunca permite que o início e o fim sejam inclusivos simultaneamente. Na minha indústria, é prática comum tratar o começo como exclusivo e o final como inclusivo, mas de qualquer maneira é bom, desde que você permaneça consistente. Esta é a primeira resposta completamente correta sobre esta questão até agora ... IMO.
Brian Gideon

14

Aqui está mais uma solução usando JavaScript. Especialidades da minha solução:

  • Manipula valores nulos como infinito
  • Assume que o limite inferior é inclusivo e o limite superior é exclusivo.
  • Vem com vários testes

Os testes são baseados em números inteiros, mas como os objetos de data no JavaScript são comparáveis, você também pode inserir dois objetos de data. Ou você pode inserir o registro de data e hora em milissegundos.

Código:

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

Testes:

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

Resultado quando executado com karma & jasmine & PhantomJS:

PhantomJS 1.9.8 (Linux): Executado 20 de 20 SUCESSO (0,003 segundos / 0,004 segundos)


9

eu faria

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

Onde IsBetweené algo como

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

Eu preferiria (esquerda <valor && valor <direita) || (direita <valor && valor <esquerda) para este método.
Patrick Huizinga

Obrigado por isso. Torna as coisas mais fáceis na minha cabeça.
Sshow

1
Por que você verificaria quatro condições quando precisa apenas verificar duas? Falhou.
ErikE

3
Ah, desculpe-me, agora vejo que você está permitindo que os intervalos estejam na ordem inversa (StartDateX> EndDateX). Estranho. Enfim, e se StartDate1 for menor que StartDate2 e EndDate1 for maior que EndDate2? O código que você forneceu não detectará essa condição sobreposta.
ErikE 11/03/10

3
Isso não retornará falso se Date1 contiver Date2 inteiro? Então StartDate1 é antes StartDate2 e EndDate1 é depois EndDate2
user158037

9

insira a descrição da imagem aqui

Aqui está o código que faz a mágica:

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

Onde..

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

Prova? Confira esta essência do código do console de teste .


Isso funciona, mas eu preferiria teste para a sua sobreposição, apenas dois cenários
John Albert

Obrigado por explicar isso usando imagens. Sua resposta é a solução perfeita para esta pergunta.
Rakesh Verma

8

Aqui está minha solução em Java , que também funciona em intervalos ilimitados

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

Eu acho que você quis dizer fins ilimitados em vez de intervalos abertos.
Henrik


!startA.after(endB)significa startA <= endB e !endA.before(startB)significa startB <= endA. Estes são os critérios para um intervalo fechado e não para um intervalo aberto.
Henrik

@Henrik true e outras condições como endB == nulle startA == nullverifique se há um intervalo aberto.
Khaled.K

1
endB == null, startA == null, endA == nullE startB == nullsão todos os critérios para verificar se há um intervalo ilimitado e não um intervalo aberto. Exemplo para as diferenças entre os intervalos ilimitado e aberto: (10, 20) e (20, nulo) são dois intervalos abertos que não se sobrepõem. O último tem um fim ilimitado. Sua função retornará verdadeiro, mas os intervalos não se sobrepõem, porque os intervalos não incluem 20. (números usados em vez de marcas de tempo para simplificar)
Henrik

7

A solução publicada aqui não funcionou para todos os intervalos sobrepostos ...

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

minha solução de trabalho foi:

AND (
  ('start_date' entre STARTDATE E ENDDATE) - atende às datas interna e externa
  OU
  ('end_date' ENTRE STARTDATE AND ENDDATE) - atende às datas interna e externa de início
  OU
  (STARTDATE ENTRE 'start_date' AND 'end_date') - apenas um necessário para o intervalo externo em que as datas estão dentro.
) 

5

Esta foi a minha solução javascript com 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

Uma maneira fácil de lembrar a solução seria
min(ends)>max(starts)


3

No Microsoft SQL SERVER - Função 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

o mais simples

A maneira mais simples é usar uma biblioteca dedicada bem projetada para o trabalho de data e hora.

someInterval.overlaps( anotherInterval )

java.time e ThreeTen-Extra

O melhor do negócio é a java.timeestrutura incorporada no Java 8 e posterior. Acrescente a isso o projeto ThreeTen-Extra que complementa java.time com classes adicionais, especificamente a Intervalclasse que precisamos aqui.

Quanto à language-agnostictag nesta questão, o código fonte dos dois projetos está disponível para uso em outros idiomas (lembre-se de suas licenças).

Interval

A org.threeten.extra.Intervalclasse é útil, mas requer momentos ( java.time.Instantobjetos) de data e hora em vez de valores somente de data. Portanto, prosseguimos usando o primeiro momento do dia no UTC para representar a data.

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

Crie um Intervalpara representar esse período de tempo.

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

Também podemos definir um Intervalcom um momento inicial mais um Duration.

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

Comparando para testar sobreposições é fácil.

Boolean overlaps = interval_A.overlaps( interval_B );

Você pode comparar um Intervalcontra outro Intervalou Instant:

Todos eles usam a Half-Openabordagem para definir um período de tempo em que o começo é inclusivo e o final é exclusivo .


3

Esta é uma extensão da excelente resposta de @ charles-bretana.

A resposta, no entanto, não faz distinção entre intervalos abertos, fechados e semi-abertos (ou semi-fechados).

Caso 1 : A, B são intervalos fechados

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

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


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

Sobreposição se: (StartA <= EndB) and (EndA >= StartB)

Caso 2 : A, B são intervalos abertos

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

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

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

Sobreposição se: (StartA < EndB) and (EndA > StartB)

Caso 3 : A, B aberto à direita

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

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

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

Condição de sobreposição: (StartA < EndB) and (EndA > StartB)

Caso 4 : A, B deixado em aberto

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

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

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

Condição de sobreposição: (StartA < EndB) and (EndA > StartB)

Caso 5 : Um direito aberto, B fechado

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

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


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

Condição de sobreposição: (StartA <= EndB) and (EndA > StartB)

etc ...

Finalmente, a condição geral para dois intervalos se sobreporem é

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

onde 🞐 transforma uma desigualdade estrita em não-estrita sempre que a comparação é feita entre dois pontos de extremidade incluídos.


Os casos dois, três e quatro têm a mesma condição de sobreposição, isso é intencional?
22717 Marie

@Marie, eu apenas listados alguns casos (não todos)
user2314737

Isso, mas tão elaborado quanto a resposta de Jonathan Leffler, seria o que eu tinha em mente como resposta aceita para a pergunta dos OP.
mbx 20/04

3

Resposta curta usando momentjs :

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

a resposta é baseada nas respostas acima, mas é encurtada.


2

Caso você esteja usando um período que ainda não terminou (ainda em andamento), por exemplo, não defina endDate = '0000-00-00', não é possível usar ENTRE, porque 0000-00-00 não é uma data válida!

Eu usei esta solução:

(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 for maior, então enddate não haverá sobreposição!


2

A resposta é muito simples para mim, por isso criei uma instrução SQL dinâmica mais genérica que verifica se uma pessoa tem datas sobrepostas.

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

A solução matemática dada pelo @Bretana é boa, mas negligencia dois detalhes específicos:

  1. aspecto de intervalos fechados ou semi-abertos
  2. intervalos vazios

Sobre o estado fechado ou aberto dos limites de intervalo, a solução do @Bretana é válida para intervalos fechados

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

pode ser reescrito por intervalos semi-abertos para:

(StartA <EndB) e (EndA> StartB)

Essa correção é necessária porque um limite de intervalo aberto não pertence ao intervalo de valores de um intervalo por definição.


E sobre intervalos vazios , bem, aqui a relação mostrada acima NÃO se aplica. Intervalos vazios que não contêm nenhum valor válido por definição devem ser tratados como caso especial. Eu demonstro isso na minha biblioteca de tempo Java Time4J através deste exemplo:

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)

O colchete à esquerda "[" indica um início fechado, enquanto o último colchete ")" indica um fim em aberto.

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

Como mostrado acima, os intervalos vazios violam a condição de sobreposição acima (especialmente startA <endB), portanto, o Time4J (e outras bibliotecas também) deve lidar com isso como um caso especial de borda para garantir que a sobreposição de qualquer intervalo arbitrário com um intervalo vazio não existe. Obviamente, os intervalos de datas (que são fechados por padrão no Time4J, mas também podem ser semi-abertos, como intervalos de datas vazios) são tratados da mesma maneira.


1

Aqui está um método genérico que pode ser útil 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
Importa-se de adicionar algumas palavras de explicação?
Phantômaxx 18/07/2014

1

Usando Java util.Date, aqui o que eu fiz.

    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 maneira mais fácil de fazer isso, na minha opinião, seria comparar se EndDate1 é anterior a StartDate2 e EndDate2 é anterior a StartDate1.

Isso, é claro, se você está considerando intervalos em que StartDate está sempre antes de EndDate.


1

Eu tive uma situação em que tínhamos datas em vez de datas e as datas podiam se sobrepor apenas no início / fim. Exemplo abaixo:

insira a descrição da imagem aqui

(Verde é o intervalo atual, blocos azuis são intervalos válidos, vermelhos são intervalos sobrepostos).

Adaptei a resposta de Ian Nelson à seguinte solução:

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

Isso corresponde a todos os casos de sobreposição, mas ignora os casos de sobreposição permitidos.


0

Divida o problema em casos e lide com cada caso .

A situação 'dois intervalos de datas se cruzam' é coberta por dois casos - o primeiro período começa no segundo ou o segundo período começa no primeiro.


0

Você pode tentar isso:

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

Esta foi a minha solução, retorna true quando os valores não se sobrepõem:

X INÍCIO 1 E FIM 1

A INÍCIO 2 B FIM 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

Para ruby, também encontrei o seguinte:

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

Encontrei aqui com uma boa explicação -> http://makandracards.com/makandra/984-test-if-two-date-ranges-overlap-in-ruby-or-rails


0

A consulta abaixo fornece os IDs para os quais o período fornecido (datas de início e término se sobrepõe a qualquer uma das datas (datas de início e término) em meu nome_tabela

select id from table_name where (START_DT_TM >= 'END_DATE_TIME'  OR   
(END_DT_TM BETWEEN 'START_DATE_TIME' AND 'END_DATE_TIME'))
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.