Determinar si dos intervalos de fechas se superponen


1251

Dados dos rangos de fechas, ¿cuál es la forma más simple o más eficiente de determinar si los dos rangos de fechas se superponen?

Como ejemplo, supongamos que tenemos rangos denotados por las variables DateTime StartDate1to EndDate1 y StartDate2 to EndDate2.



@CharlesBretana gracias por eso, tienes razón, ¡es casi como una versión bidimensional de mi pregunta!
Ian Nelson el


2
Divida la situación 'los dos rangos de fechas se cruzan' en casos (hay dos) y luego pruebe para cada caso.
Coronel Panic

1
Este código funciona bien. Puedes ver mi respuesta aquí: stackoverflow.com/a/16961719/1534785
Jeyhun Rahimov

Respuestas:


2290

(InicioA <= FinB) y (FinA> = InicioB)

Prueba:
Dejar que la condición A signifique que el rango de fecha A completamente después del rango de fecha B
_ |---- DateRange A ------| |---Date Range B -----| _
(verdadero si StartA > EndB)

Deje que la condición B signifique que el rango de fecha A es completamente anterior al rango de fecha B
|---- DateRange A -----| _ _ |---Date Range B ----|
(verdadero si EndA < StartB)

Entonces, la superposición existe si ni A ni B son verdaderas
(si un rango no está completamente detrás del otro,
ni completamente antes del otro, entonces deben superponerse).

Ahora una de las leyes de De Morgan dice que:

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

Lo que se traduce en: (StartA <= EndB) and (EndA >= StartB)


NOTA: Esto incluye condiciones en las que los bordes se superponen exactamente. Si desea excluir eso,
cambie los >=operadores a >, y <= a<


NOTA 2. Gracias a @Baodad, ver este blog , la superposición real es menos de:
{ endA-startA, endA - startB, endB-startA, endB - startB}

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


NOTA 3. Gracias a @tomosius, se lee una versión más corta:
DateRangesOverlap = max(start1, start2) < min(end1, end2)
este es en realidad un acceso directo sintáctico para lo que es una implementación más larga, que incluye comprobaciones adicionales para verificar que las fechas de inicio sean anteriores o posteriores a las fechas finales. Derivando esto de arriba:

Si las fechas de inicio y finalización pueden estar fuera de servicio, es decir, si es posible eso startA > endAo startB > endB, entonces también debe verificar que estén en orden, lo que significa que debe agregar dos reglas de validez adicionales:
(StartA <= EndB) and (StartB <= EndA) and (StartA <= EndA) and (StartB <= EndB) o:
(StartA <= EndB) and (StartA <= EndA) and (StartB <= EndA) and (StartB <= EndB) o,
(StartA <= Min(EndA, EndB) and (StartB <= Min(EndA, EndB)) o:
(Max(StartA, StartB) <= Min(EndA, EndB)

Pero para implementar Min()y Max(), tiene que codificar, (usando C ternary para ser conciso):
(StartA > StartB? Start A: StartB) <= (EndA < EndB? EndA: EndB)


29
Esta es una lógica simplificada basada en estos dos supuestos: 1) StartA <EndA; 2) InicioB <FinB. Parece obvio, pero en realidad los datos pueden provenir de una fuente desconocida, como la entrada del usuario o una base de datos sin desinfección. Tenga en cuenta que deberá validar los datos de entrada para asegurarse de que esos dos supuestos sean ciertos antes de poder usar esta lógica simplificada o todo se desmoronará. Lección aprendida de mi propia experiencia;)
Devy

12
@Devy, tienes razón. Excepto que también funcionará si startA = endA. De hecho, eso es exactamente lo que las palabras Starty Endmedia. Si tiene dos variables denominadas Superior e Inferior, o Este y Oeste, o HighValue y LoValue, se puede suponer o implicar que algo o alguien, en algún lugar, debe asegurarse de que uno de los pares de valores no esté almacenado en las variables opuestas. -Solo uno de los dos pares porque, bueno, también funcionará si se cambian ambos pares de valores.
Charles Bretana

15
Puede agregar fácilmente nullable starty end(con la semántica que "null start" = "Desde el principio del tiempo" y "null end" = "Hasta el final del tiempo") así:(startA === null || endB === null || startA <= endB) && (endA === null || startB === null || endA >= startB)
Kevin Robatel

99
¡La mejor respuesta en Stackexchange! ¡Se siente bien ver una explicación de por qué funciona esta fórmula inteligente!
Abeer Sul

44
Aquí está la forma más compacta que se me ocurre, que también devuelve falso en caso de entrada no válida (fecha de inicio> = fecha de finalización)DateRangesOverlap = max(start1, start2) < min(end1, end2)
tomosius

406

Creo que es suficiente decir que los dos rangos se superponen si:

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

76
La (StartDate1 <= EndDate2) and (EndDate1 >= StartDate2)notación me parece más fácil de entender, Range1 siempre está a la izquierda en las pruebas.
AL

8
Esto supone que las fechas de inicio y finalización son inclusivas. Cambie <=a <si el inicio es inclusivo y el final es exclusivo.
Richard Schneider

Esto funcionará muy bien incluso si startDate2 es anterior a startDate1. Por lo tanto, no es necesario suponer que startDate1 es anterior a startDate2.
Shehan Simen

3
Encontré (StartDate1 <= EndDate2) y (StartDate2 <= EndDate1) notación (según la respuesta) más fácil de entender que eso en otras respuestas.
apc

¿Cómo adaptarse para que funcione con datos que tienen StartDate1 AND / OR EndDate1? El código supone que StartDate1 y EndDate1 siempre están presentes. ¿Qué sucede si se da StartDate1 pero no EndDate1 O EndDate1 pero no StartDate1? ¿Cómo manejar este caso extra?
juFo

117

Este artículo Biblioteca de períodos de tiempo para .NET describe la relación de dos períodos de tiempo mediante la enumeración PeriodRelation :

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

ingrese la descripción de la imagen aquí


Agradable, también he implementado el álgebra de intervalo Allens en Java, consulte la API de IntervalRelation e IsoInterval
Meno Hochschild

80

Para razonar sobre las relaciones temporales (o cualquier otra relación de intervalo, veamos eso), considere el Álgebra de intervalos de Allen . Describe las 13 posibles relaciones que pueden tener dos intervalos entre sí. Puede encontrar otras referencias: "Intervalo Allen" parece ser un término de búsqueda operativa. También puede encontrar información sobre estas operaciones en Desarrollo de aplicaciones orientadas al tiempo de Snodgrass en SQL (PDF disponible en línea en URL), y en Fecha, Darwen y Lorentzos Datos temporales y el Modelo relacional (2002) o Tiempo y teoría relacional: Bases de datos temporales en el Modelo Relacional y SQL (2014; efectivamente la segunda edición de TD&RM).


La corta (más o menos) la respuesta es: dado dos intervalos de fechas Ay Bcon los componentes .starty .endy la restricción .start <= .end, a continuación, dos intervalos se solapan si:

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

Puede ajustar el uso de >=vs >y <=vs <para cumplir con sus requisitos de grado de superposición.


ErikE comenta:

Solo puedes obtener 13 si cuentas cosas divertidas ... Puedo obtener "15 posibles relaciones que pueden tener dos intervalos" cuando me vuelvo loco. Con un recuento sensato, obtengo solo seis, y si no le importa si A o B son lo primero, obtengo solo tres (sin intersección, intersección parcial, una totalmente dentro de otra). 15 dice así: [antes: antes, inicio, dentro, fin, después], [inicio: inicio, dentro, fin, después], [dentro: dentro, fin, después], [fin: fin, después], [ después: después].

Creo que no puede contar las dos entradas 'before: before' y 'after: after'. Podría ver 7 entradas si equipara algunas relaciones con sus inversas (vea el diagrama en la URL de Wikipedia referenciada; tiene 7 entradas, 6 de las cuales tienen un inverso diferente, con iguales que no tienen un inverso distinto). Y si tres es sensato depende de sus requisitos.

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

1
Solo puedes obtener 13 si cuentas cosas divertidas ... Puedo obtener "15 posibles relaciones que pueden tener dos intervalos" cuando me vuelvo loco. Con un recuento sensato, obtengo solo seis, y si no le importa si A o B son lo primero, obtengo solo tres (sin intersección, intersección parcial, una totalmente dentro de otra). 15 dice así: [antes: antes, inicio, dentro, fin, después], [inicio: inicio, dentro, fin, después], [dentro: dentro, fin, después], [fin: fin, después], [ después: después].
ErikE

@Emtucifor: Creo que no puedes contar las dos entradas 'before: before' y 'after: after'.
Jonathan Leffler

Re su actualización: B1 a A es antes: antes y B13 a A es después: después. Falta un buen diagrama de inicio: comienza entre B5 B6 y finaliza: termina entre B11 y B12. Si estar en un punto final es significativo, entonces usted tiene que contar con ella, por lo que el resultado final es de 15, no 13. Yo no creo que lo extremo no es significativa, por lo que personalmente considero que [antes: antes, en el interior, después] , [inside: inside, after], [after: after] que llega a 6. Creo que todo el punto final es solo confusión sobre si los límites son inclusivos o exclusivos. ¡La exclusividad de los puntos finales no cambia las relaciones centrales!
ErikE

Es decir, en mi esquema, estos son equivalentes: (B2, B3, B4), (B6, B7, B9, B10), (B8, B11, B12). Me doy cuenta de que B7 implica la información de que los dos rangos coinciden exactamente. Pero no estoy convencido de que esta información adicional deba formar parte de las relaciones de intersección de base. Por ejemplo, cuando dos intervalos tienen exactamente la misma longitud, incluso si no coinciden o incluso se superponen, ¿debería considerarse otra "relación"? Digo que no, y dado que este aspecto adicional es lo único que distingue a B7 de B6, creo que tener puntos finales como casos separados hace que las cosas sean inconsistentes.
ErikE

@Emtucifor: OK - Veo por qué identifiqué erróneamente 'before: before' y 'after: after' como las entradas; sin embargo, no puedo imaginar cómo deberían ser las entradas 'start: start' y 'end: end'. Dado que no puede editar mi diagrama, ¿puede enviarme un correo electrónico (ver mi perfil) con una copia modificada del diagrama que muestre las relaciones 'inicio: inicio' y 'fin: fin'? No tengo problemas importantes con sus agrupaciones.
Jonathan Leffler

30

Si también se debe calcular la superposición, puede usar la siguiente fórmula:

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

Entonces, ¿se superpone la cantidad de tiempo que comparten los dos eventos? ¿Funciona para todas las diferentes formas en que los eventos pueden superponerse?
NSjonas

18

¡Todas las soluciones que comprueban una multitud de condiciones en función de dónde están los rangos en relación entre sí se pueden simplificar enormemente simplemente asegurando que un rango específico comience antes! Se asegura de que el primer rango comience antes (o al mismo tiempo) intercambiando los rangos si es necesario por adelantado.

Luego, puede detectar la superposición si el otro inicio del rango es menor o igual que el primer final del rango (si los rangos son inclusivos, que contienen las horas de inicio y finalización) o menor que (si los rangos incluyen el inicio y excluyen el final) .

Suponiendo que sea inclusivo en ambos extremos, solo hay cuatro posibilidades de las cuales una no se superpone:

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

El punto final del rango 2 no entra en él. Entonces, en pseudocódigo:

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

Esto podría simplificarse aún más en:

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

Si los rangos están incluidos en el inicio y exclusivo al final, sólo hay que sustituir >con >=en la segunda ifdeclaración (el primer segmento de código: en el segundo segmento de código, tendrá que utilizar <en lugar de <=):

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

Limita en gran medida el número de comprobaciones que tiene que hacer porque elimina la mitad del espacio del problema antes de tiempo al garantizar que el rango 1 nunca comience después del rango 2.


2
+1 por mencionar el problema inclusivo / exclusivo. Iba a preparar una respuesta cuando tuviera tiempo, pero ahora no es necesario. La cuestión es que casi nunca permites que tanto el inicio como el final sean inclusivos simultáneamente. En mi industria, es una práctica común tratar el comienzo como exclusivo y el final como inclusivo, pero de cualquier manera está bien siempre que se mantenga constante. Esta es la primera respuesta completamente correcta sobre esta pregunta hasta ahora ... OMI.
Brian Gideon el

14

Aquí hay otra solución más usando JavaScript. Especialidades de mi solución:

  • Maneja valores nulos como infinito
  • Asume que el límite inferior es inclusivo y el límite superior exclusivo.
  • Viene con un montón de pruebas

Las pruebas se basan en números enteros, pero como los objetos de fecha en JavaScript son comparables, también puede agregar dos objetos de fecha. O podría agregar la marca de tiempo de milisegundos.

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

Pruebas:

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 cuando se ejecuta con karma y jazmín y PhantomJS:

PhantomJS 1.9.8 (Linux): Ejecutado 20 de 20 ÉXITO (0.003 segundos / 0.004 segundos)


9

yo lo haría

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

Donde IsBetweenes algo como

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

Preferiría (left <value && value <right) || (derecha <valor && valor <izquierda) para este método.
Patrick Huizinga

Gracias por esto. Hace las cosas más fáciles en mi cabeza.
sshow

1
¿Por qué verificaría cuatro condiciones cuando solo tiene que verificar dos? Fallar.
ErikE

3
Ah, mis disculpas, ahora veo que está permitiendo que los rangos estén en orden inverso (StartDateX> EndDateX). Extraño. De todos modos, ¿qué pasa si StartDate1 es menor que StartDate2 y EndDate1 es mayor que EndDate2? El código que proporcionó no detectará esta condición superpuesta.
ErikE

3
¿No devolverá esto falso si Date1 contiene Date2 completo? Entonces StartDate1 es anterior a StartDate2 y EndDate1 es posterior a EndDate2
user158037

9

ingrese la descripción de la imagen aquí

Aquí está el código que hace 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));

Dónde..

  • A -> 1Inicio
  • B -> 1Fin
  • C -> 2 Inicio
  • D -> 2Fin

¿Prueba? Echa un vistazo a este código de consola de prueba .


Eso funciona, pero preferiría probar que no se superpongan, solo dos escenarios
John Albert

Gracias por explicar esto usando imágenes. Su respuesta es la solución perfecta para esta pregunta.
Rakesh Verma

8

Aquí está mi solución en Java , que también funciona en 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));
}

Creo que te referías a fines ilimitados en lugar de intervalos abiertos.
Henrik


!startA.after(endB)significa startA <= endB y !endA.before(startB)significa startB <= endA. Estos son los criterios para un intervalo cerrado y no un intervalo abierto.
Henrik

@Henrik verdadero, y las otras condiciones como endB == nully startA == nullcompruebe un intervalo abierto
Khaled.K

1
endB == null, startA == null, endA == nullY startB == nullson todos los criterios para verificar si hay un intervalo acotado y no un intervalo abierto. Ejemplo de las diferencias entre los intervalos ilimitados y abiertos: (10, 20) y (20, nulo) son dos intervalos abiertos que no se superponen. El último tiene un final ilimitado. Su función devolverá verdadero, pero los intervalos no se superponen, porque los intervalos no incluyen 20. (números usados ​​en lugar de marcas de tiempo para simplificar)
Henrik

7

La solución publicada aquí no funcionó para todos los rangos superpuestos ...

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

mi solución de trabajo fue:

Y (
  ('start_date' ENTRE STARTDATE Y ENDDATE): atiende a la fecha interna y final externa
  O
  ('end_date' ENTRE STARTDATE y ENDDATE): atiende a la fecha interna y a la fecha de inicio externa
  O
  (STARTDATE BETWEEN 'start_date' AND 'end_date'): solo se necesita uno para el rango externo donde las fechas están dentro.
) 

5

Esta fue mi solución de 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

Una forma fácil de recordar la solución sería
min(ends)>max(starts)


3

En Microsoft SQL SERVER - Función 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

lo más simple

La forma más simple es usar una biblioteca dedicada bien diseñada para el trabajo de fecha y hora.

someInterval.overlaps( anotherInterval )

java.time y ThreeTen-Extra

Lo mejor en el negocio es el java.timemarco integrado en Java 8 y versiones posteriores. Agregue a eso el proyecto ThreeTen-Extra que complementa java.time con clases adicionales, específicamente la Intervalclase que necesitamos aquí.

En cuanto a la language-agnosticetiqueta de esta pregunta, el código fuente de ambos proyectos está disponible para su uso en otros idiomas (tenga en cuenta sus licencias).

Interval

La org.threeten.extra.Intervalclase es útil, pero requiere momentos de fecha y hora ( java.time.Instantobjetos) en lugar de valores de solo fecha. Entonces, procedemos utilizando el primer momento del día en UTC para representar la fecha.

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

Crea una Intervalpara representar ese lapso de tiempo.

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

También podemos definir un Intervalcon un momento inicial más un Duration.

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

Comparar las pruebas de superposiciones es fácil.

Boolean overlaps = interval_A.overlaps( interval_B );

Puede comparar una Intervalcon otra Intervalo Instant:

Todos estos utilizan el Half-Openenfoque para definir un período de tiempo donde el comienzo es inclusivo y el final es exclusivo .


3

Esta es una extensión de la excelente respuesta de @ charles-bretana.

Sin embargo, la respuesta no distingue entre intervalos abiertos, cerrados y medio abiertos (o medio cerrados).

Caso 1 : A, B son intervalos cerrados

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

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


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

Superposición iff: (StartA <= EndB) and (EndA >= StartB)

Caso 2 : A, B son intervalos abiertos

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

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

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

Superposición iff: (StartA < EndB) and (EndA > StartB)

Caso 3 : A, B derecho abierto

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

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

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

Condición de superposición: (StartA < EndB) and (EndA > StartB)

Caso 4 : A, B dejado abierto

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

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

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

Condición de superposición: (StartA < EndB) and (EndA > StartB)

Caso 5 : A derecha abierta, B cerrada

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

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


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

Condición de superposición: (StartA <= EndB) and (EndA > StartB)

etc ...

Finalmente, la condición general para que dos intervalos se superpongan es

(InicioA <🞐 FinB) y (FinA> 🞐 InicioB)

donde 🞐 convierte una desigualdad estricta en una no estricta siempre que se haga la comparación entre dos puntos finales incluidos.


Los casos dos, tres y cuatro tienen la misma condición de superposición, ¿es intencional?
Marie

@Marie, acabo de enumerar algunos casos (no todos)
user2314737

Esto, pero tan elaborado como la respuesta de Jonathan Leffler sería lo que tenía en mente como respuesta aceptada para la pregunta de OP.
mbx

3

Respuesta corta usando momentjs :

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

la respuesta se basa en las respuestas anteriores, pero se acorta.


2

En caso de que esté usando un intervalo de fechas que aún no ha finalizado (todavía en curso), por ejemplo, no establecido endDate = '0000-00-00', no puede usar ENTRE porque ¡0000-00-00 no es una fecha válida!

Usé esta solución:

(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 es más alto que enddate, ¡no hay superposición!


2

La respuesta es demasiado simple para mí, así que he creado una instrucción SQL dinámica más genérica que verifica si una persona tiene fechas superpuestas.

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 solución matemática dada por @Bretana es buena pero descuida dos detalles específicos:

  1. aspecto de intervalos cerrados o medio abiertos
  2. intervalos vacíos

Sobre el estado cerrado o abierto de límites de intervalo, la solución de @Bretana válida para intervalos cerrados

(InicioA <= FinB) y (FinA> = InicioB)

puede reescribirse para intervalos medio abiertos para:

(InicioA <FinB) y (FinA> InicioB)

Esta corrección es necesaria porque un límite de intervalo abierto no pertenece al rango de valores de un intervalo por definición.


Y sobre intervalos vacíos , bueno, aquí la relación que se muestra arriba NO es válida. Los intervalos vacíos que no contienen ningún valor válido por definición deben manejarse como casos especiales. Lo demuestro con mi biblioteca de tiempo Java Time4J a través de este ejemplo:

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)

El corchete inicial "[" indica un inicio cerrado mientras que el último corchete ")" indica un final abierto.

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 se muestra arriba, los intervalos vacíos violan la condición de superposición anterior (especialmente startA <endB), por lo que Time4J (y otras bibliotecas también) deben manejarlo como un caso especial para garantizar que la superposición de cualquier intervalo arbitrario con un intervalo vacío no existe. Por supuesto, los intervalos de fechas (que están cerrados por defecto en Time4J pero también pueden estar medio abiertos, como los intervalos de fechas vacías) se manejan de manera similar.


1

Aquí hay un método genérico que puede 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
¿Te importaría agregar algunas palabras de explicación?
Phantômaxx

1

Usando Java util.Date, aquí lo que hice.

    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

La forma más fácil de hacerlo en mi opinión sería comparar si EndDate1 es anterior a StartDate2 y EndDate2 es anterior a StartDate1.

Eso, por supuesto, si está considerando intervalos donde StartDate siempre es anterior a EndDate.


1

Tuve una situación en la que teníamos fechas en lugar de fechas y las fechas solo podían superponerse al inicio / final. Ejemplo a continuación:

ingrese la descripción de la imagen aquí

(El verde es el intervalo actual, los bloques azules son intervalos válidos, los rojos son intervalos superpuestos).

Adapte la respuesta de Ian Nelson a la siguiente solución:

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

Esto coincide con todos los casos de superposición, pero ignora los casos de superposición permitidos.


0

Divida el problema en casos y luego maneje cada caso .

La situación 'dos ​​intervalos de fechas se cruzan' está cubierta por dos casos: el primer intervalo de fechas comienza dentro del segundo, o el segundo intervalo de fechas comienza dentro del primero.


0

Puedes probar esto:

//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 fue mi solución, devuelve verdadero cuando los valores no se superponen:

X INICIO 1 Y FINAL 1

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

Para ruby ​​también encontré esto:

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

Lo encontré aquí con una buena explicación -> http://makandracards.com/makandra/984-test-if-two-date-ranges-overlap-in-ruby-or-rails


0

La siguiente consulta me da los identificadores para los cuales el rango de fechas suministrado (fechas de inicio y finalización se superpone con cualquiera de las fechas (fechas de inicio y finalización) en mi nombre_tabla

select id from table_name where (START_DT_TM >= 'END_DATE_TIME'  OR   
(END_DT_TM BETWEEN 'START_DATE_TIME' AND 'END_DATE_TIME'))
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.