Określ, czy dwa zakresy dat pokrywają się


1248

Biorąc pod uwagę dwa zakresy dat, jaki jest najprostszy lub najskuteczniejszy sposób ustalenia, czy oba zakresy dat się pokrywają?

Jako przykład załóżmy, że mamy zakresy oznaczone zmiennymi DateTime StartDate1do EndDate1 i StartDate2 do EndDate2.



@CharlesBretana dzięki za to, masz rację - to prawie jak dwuwymiarowa wersja mojego pytania!
Ian Nelson


2
Podziel sytuację „przecinają się dwa zakresy dat” na przypadki (są dwa), a następnie przetestuj dla każdego przypadku.
Pułkownik Panic

1
Ten kod działa dobrze. Możesz zobaczyć moją odpowiedź tutaj: stackoverflow.com/a/16961719/1534785
Jeyhun Rahimov

Odpowiedzi:


2288

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

Dowód:
Niech warunek A oznacza, że ​​zakres dat A całkowicie po zakresie dat B
_ |---- DateRange A ------| |---Date Range B -----| _
(prawda, jeśli StartA > EndB)

Niech warunek B oznacza, że ​​zakres dat A jest całkowicie przed zakresem dat B
|---- DateRange A -----| _ _ |---Date Range B ----|
(prawda, jeśli EndA < StartB)

Zatem nakładanie się istnieje, jeśli ani A, ani B nie jest prawdziwe -
(Jeśli jeden zakres nie jest całkowicie za drugim,
ani całkowicie przed drugim, wówczas muszą się nakładać).

Teraz jedno z praw De Morgana mówi:

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

Co przekłada się na: (StartA <= EndB) and (EndA >= StartB)


UWAGA: Obejmuje to warunki, w których krawędzie dokładnie się pokrywają. Jeśli chcesz, aby wykluczyć, że
zmiany >=operatorom >, a <= do<


UWAGA 2. Dzięki @Baodad, zobaczyć ten blog , rzeczywista zakładka jest najmniej:
{ endA-startA, endA - startB, endB-startA, endB - startB}

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


UWAGA 3. Dzięki @tomosius krótsza wersja brzmi:
DateRangesOverlap = max(start1, start2) < min(end1, end2)
Jest to w rzeczywistości skrót składniowy dla dłuższej implementacji, która obejmuje dodatkowe kontrole w celu sprawdzenia, czy daty rozpoczęcia są w lub przed datami końcowymi. Wyprowadzając to z góry:

Jeśli daty rozpoczęcia i zakończenia mogą być nieprawidłowe, tj. Jeśli jest to możliwe, startA > endAlub startB > endB, musisz również sprawdzić, czy są one w porządku, co oznacza, że ​​musisz dodać dwie dodatkowe reguły ważności:
(StartA <= EndB) and (StartB <= EndA) and (StartA <= EndA) and (StartB <= EndB) lub:
(StartA <= EndB) and (StartA <= EndA) and (StartB <= EndA) and (StartB <= EndB) lub
(StartA <= Min(EndA, EndB) and (StartB <= Min(EndA, EndB)) lub:
(Max(StartA, StartB) <= Min(EndA, EndB)

Ale do wdrożenia Min() i Max()musisz napisać kod (używając ternary C do zwięzłości):
(StartA > StartB? Start A: StartB) <= (EndA < EndB? EndA: EndB)


29
Jest to uproszczona logika oparta na tych dwóch założeniach: 1) StartA <EndA; 2) StartB <EndB. Wydaje się to oczywiste, ale w rzeczywistości dane mogą pochodzić z nieznanych źródeł, takich jak dane wejściowe użytkownika lub baza danych bez dezynfekcji. Pamiętaj, że będziesz musiał zweryfikować dane wejściowe, aby upewnić się, że te dwa założenia są prawdziwe, zanim będziesz mógł użyć tej uproszczonej logiki, inaczej wszystko się rozpadnie. Lekcja wyciągnięta z własnego doświadczenia;)
Devy

12
@Devy, masz rację. Tyle że zadziała również, jeśli startA = endA. Rzeczywiście, właśnie to słowa Starti Endznaczą. Jeśli masz dwie zmienne o nazwie Góra i Dół lub Wschód i Zachód lub HighValue i LoValue, można założyć lub sugerować, że coś lub ktoś, gdzieś, powinien upewnić się, że jedna z par wartości nie jest przechowywana w przeciwnych zmiennych. -Tylko jedna z dwóch par, ponieważ, no cóż, zadziała również, jeśli obie pary wartości zostaną przełączone.
Charles Bretana,

15
Możesz łatwo dodać zerowalne starti end(z semantycznym, że „null start” = „Od początku czasu” i „null end” = „Do końca czasu”) w ten sposób:(startA === null || endB === null || startA <= endB) && (endA === null || startB === null || endA >= startB)
Kevin Robatel

9
Najlepsza odpowiedź na Stackexchange! Dobrze jest zobaczyć wyjaśnienie, dlaczego ta inteligentna formuła działa!
Abeer Sul

4
Oto najbardziej zwarta forma, o której mogłem pomyśleć, która również zwraca false w przypadku nieprawidłowego wprowadzenia (data początkowa> = data końcowa)DateRangesOverlap = max(start1, start2) < min(end1, end2)
tomosius

406

Uważam, że wystarczy powiedzieć, że dwa zakresy pokrywają się, jeśli:

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

76
Uważam, że (StartDate1 <= EndDate2) and (EndDate1 >= StartDate2)notacja jest łatwiejsza do zrozumienia, Range1 jest zawsze po lewej stronie w testach.
AL

8
Zakłada się, że daty rozpoczęcia i zakończenia są włącznie. Zmień <=na, <czy początek jest włączający, a koniec wyłączny.
Richard Schneider,

Będzie to działać bardzo dobrze, nawet jeśli startDate2 jest wcześniejszy niż startDate1. Nie trzeba więc zakładać, że startDate1 jest wcześniejszy niż startDate2.
Shehan Simen,

3
Znalazłem (StartDate1 <= EndDate2) i (StartDate2 <= EndDate1) notację (zgodnie z odpowiedzią) łatwiejszą do zrozumienia niż w innych odpowiedziach.
apc

Jak dostosować, aby działał z danymi, które mają StartDate1 AND / OR EndDate1? Kod zakłada, że ​​StartDate1 i EndDate1 są zawsze obecne. Co jeśli podano StartDate1, ale nie podano EndDate1 LUB EndDate1, ale nie StartDate1. Jak poradzić sobie z tym dodatkowym przypadkiem?
juFo

117

W tym artykule Biblioteka okresów dla .NET opisuje relację dwóch okresów przez wyliczenie PeriodRelation :

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

wprowadź opis zdjęcia tutaj


Fajnie, zaimplementowałem również algebrę interwałową Allensa
Meno Hochschild

80

Aby uzyskać uzasadnienie dotyczące relacji czasowych (lub jakichkolwiek innych relacji przedziałowych, przejdź do tego), rozważ Algebrę przedziałową Allena . Opisuje 13 możliwych relacji, jakie mogą mieć dwa przedziały względem siebie. Możesz znaleźć inne odniesienia - „Allen Interval” wydaje się być operacyjnym terminem wyszukiwania. Informacje na temat tych operacji można także znaleźć w opracowywaniu aplikacji zorientowanych na czas w języku SQL przez Snodgrass (plik PDF dostępny online pod adresem URL) oraz w danych czasowych, danych Darwen i Lorentzos oraz modelu relacyjnym (2002) lub teorii czasu i relacji: bazy danych czasowych w Relational Model i SQL (2014; skutecznie druga edycja TD&RM).


Krótka (ish) odpowiedź brzmi: biorąc pod uwagę dwa przedziały dat Aoraz Bz komponentami .starti .endograniczeniem .start <= .end, wówczas dwa przedziały nakładają się, jeśli:

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

Możesz dostroić użycie >=vs >i <=vs, <aby spełnić wymagania dotyczące stopnia nakładania się.


Komentarze ErikE:

Możesz dostać 13, jeśli policzysz rzeczy śmieszne ... Mogę uzyskać „15 możliwych relacji, które mogą mieć dwie interwały”, kiedy oszaleję. Rozsądnie licząc, dostaję tylko sześć, a jeśli wyrzucisz opiekę nad tym, czy A lub B są pierwsze, dostaję tylko trzy (nie przecinają się, częściowo przecinają, jeden całkowicie w obrębie drugiego). 15 wygląda tak: [przed: przed, początek, w, koniec, po], [początek: początek, w, koniec, po], [w: w, koniec, po], [koniec: koniec, po], [ po: po].

Myślę, że nie można policzyć dwóch wpisów „przed: przed” i „po: po”. Mogę zobaczyć 7 pozycji, jeśli zrównujesz niektóre relacje z ich odwrotnościami (patrz diagram w odnośniku do adresu URL Wikipedii; ma 7 pozycji, z których 6 ma inną odwrotność, a równa się nie ma wyraźnej odwrotności). A czy trzy są rozsądne, zależy od twoich wymagań.

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

1
Możesz dostać 13, jeśli policzysz rzeczy śmieszne ... Mogę uzyskać „15 możliwych relacji, które mogą mieć dwie interwały”, kiedy oszaleję. Rozsądnie licząc, dostaję tylko sześć, a jeśli wyrzucisz opiekę nad tym, czy A lub B są pierwsze, dostaję tylko trzy (nie przecinają się, częściowo przecinają, jeden całkowicie w obrębie drugiego). 15 wygląda tak: [przed: przed, początek, w, koniec, po], [początek: początek, w, koniec, po], [w: w, koniec, po], [koniec: koniec, po], [ po: po].
ErikE

@Emtucifor: Myślę, że nie można policzyć dwóch wpisów „przed: przed” i „po: po”.
Jonathan Leffler,

Re aktualizacja: B1 do A jest przed: przed, a B13 do A po: po. W twoim ładnym schemacie brakuje początku: zacznij między B5 B6, a koniec: zakończ między B11 i B12. Jeśli będąc na końcowym jest znacząca, to trzeba go liczyć, więc ostateczna tally jest 15, a nie 13. I nie myślę rzeczą końcowy jest znaczna, więc ja osobiście policzyć to [wcześniej: przed, wewnątrz, po] , [w: w ciągu, po], [po: po], który dochodzi do 6. Myślę, że cały punkt końcowy to tylko zamieszanie co do tego, czy granice są włączające, czy wyłączne. Ekskluzywność punktów końcowych nie zmienia podstawowych relacji!
ErikE

Oznacza to, że w moim schemacie są one równoważne: (B2, B3, B4), (B6, B7, B9, B10), (B8, B11, B12). Zdaję sobie sprawę, że B7 oznacza informację, że dwa zakresy dokładnie się pokrywają. Ale nie jestem przekonany, że te dodatkowe informacje powinny być częścią podstawowych relacji przecięcia. Na przykład, jeśli dwa przedziały mają dokładnie taką samą długość, nawet jeśli nie są zbieżne ani nawet pokrywające się, czy należy to uznać za kolejną „relację”? Mówię „nie”, a ponieważ ten dodatkowy aspekt jest jedyną rzeczą odróżniającą B7 od B6, to myślę, że posiadanie punktów końcowych jako oddzielnych przypadków powoduje, że wszystko jest niespójne.
ErikE

@Emtucifor: OK - Rozumiem, dlaczego błędnie zidentyfikowałem „przed: przed” i „po: po” jako wpisy; nie mogę jednak wyobrazić sobie, jak powinny wyglądać wpisy „początek: początek” i „koniec: koniec”. Ponieważ nie możesz edytować mojego diagramu, czy możesz wysłać mi wiadomość e-mail (zobacz mój profil) ze zmodyfikowaną kopią diagramu pokazującą relacje „początek: początek” i „koniec: koniec”? Nie mam większych problemów z twoimi grupami.
Jonathan Leffler,

30

Jeśli samo nakładanie również powinno zostać obliczone, możesz użyć następującej formuły:

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

więc nakładają się na siebie czasy, które dzielą te dwa wydarzenia? Czy działa to na wszystkie sposoby, w jakie zdarzenia mogą się nakładać?
NSjonas

18

Wszystkie rozwiązania, które sprawdzają wiele warunków w zależności od tego, gdzie zakresy są względem siebie, można znacznie uprościć, upewniając się, że określony zakres zaczyna się wcześniej! Zapewniasz, że pierwszy zakres zaczyna się wcześniej (lub w tym samym czasie), zamieniając zakresy, jeśli to konieczne z góry.

Następnie można wykryć nakładanie się, jeśli początek drugiego zakresu jest mniejszy lub równy pierwszemu końcowi zakresu (jeśli zakresy obejmują zarówno czas rozpoczęcia, jak i koniec) lub mniej niż (jeśli zakresy obejmują początek i koniec) .

Zakładając włącznie na obu końcach, istnieją tylko cztery możliwości, z których jedna nie nakłada się:

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

Punkt końcowy zakresu 2 nie wchodzi w to. Tak więc w pseudokodzie:

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

Można to jeszcze bardziej uprościć:

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

Jeśli zakresy są włączające na początku, a wyłączne na końcu, wystarczy zastąpić >je >=w drugiej ifinstrukcji (dla pierwszego segmentu kodu: w drugim segmencie kodu należy użyć <zamiast <=):

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

Znacznie ograniczasz liczbę kontroli, które musisz wykonać, ponieważ usuwasz połowę przestrzeni problemów, zapewniając, że zasięg 1 nigdy nie rozpocznie się po zasięgu 2.


2
+1 za wzmiankę o włączającym / wyłącznym problemie. Chciałem wymyślić odpowiedź, kiedy będę miał czas, ale teraz nie muszę. Chodzi o to, że prawie nigdy nie pozwalasz, aby zarówno początek, jak i koniec były jednocześnie. W mojej branży powszechną praktyką jest traktowanie początku jako wyjątkowego, a końca jako obejmującego, ale w obu przypadkach jest w porządku, o ile pozostajesz konsekwentny. To jak dotąd pierwsza całkowicie poprawna odpowiedź na to pytanie ... IMO.
Brian Gideon,

14

Oto kolejne rozwiązanie wykorzystujące JavaScript. Specjalizacje mojego rozwiązania:

  • Obsługuje wartości zerowe jako nieskończoność
  • Zakłada, że ​​dolna granica jest włączająca, a górna granica wyłączna.
  • Pochodzi z wieloma testami

Testy są oparte na liczbach całkowitych, ale ponieważ obiekty daty w JavaScript są porównywalne, możesz po prostu dodać także dwa obiekty daty. Lub możesz dodać milisekundowy znacznik czasu.

Kod:

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

Testy:

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

Wynik podczas działania z karmą, jaśminem i PhantomJS:

PhantomJS 1.9.8 (Linux): Wykonano 20 z 20 SUKCESÓW (0,003 s / 0,004 s)


9

chciałbym zrobić

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

Gdzie IsBetweenjest coś takiego

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

Wolałbym (lewa <wartość i & wartość <prawa) || (prawy <wartość i & wartość <lewy) dla tej metody.
Patrick Huizinga

Dzięki za to. Ułatwia mi to w głowie.
pokaż

1
Dlaczego miałbyś sprawdzać cztery warunki, kiedy musisz tylko sprawdzać dwa? Zawieść.
ErikE

3
Ach, przepraszam, widzę teraz, że pozwalasz, aby zakresy były w odwrotnej kolejności (StartDateX> EndDateX). Dziwne. W każdym razie, co jeśli StartDate1 jest krótszy niż StartDate2, a EndDate1 jest większy niż EndDate2? Podany kod nie wykryje tego nakładającego się warunku.
ErikE

3
Czy ten zwrot nie będzie fałszywy, jeśli data 1 zawiera całą datę 2? Następnie StartDate1 jest przed StartDate2, a EndDate1 jest po EndDate2
użytkownik158037

9

wprowadź opis zdjęcia tutaj

Oto kod, który robi magię:

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

Gdzie..

  • A -> 1 Start
  • B -> 1 Koniec
  • C -> 2 Rozpocznij
  • D -> 2 Koniec

Dowód? Sprawdź ten kod kodu konsoli testowej .


To działa, ale wolałbym przetestować pod kątem braku nakładania się, tylko dwa scenariusze
John Albert,

Dziękujemy za wyjaśnienie tego za pomocą obrazów. Twoja odpowiedź jest idealnym rozwiązaniem na to pytanie.
Rakesh Verma

8

Oto moje rozwiązanie w Javie , które działa również w nieograniczonych odstępach czasu

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

Myślę, że miałeś na myśli nieograniczone końce zamiast otwartych interwałów.
Henrik


!startA.after(endB)oznacza startA <= endB i !endA.before(startB)oznacza startB <= endA. Są to kryteria dla przedziału zamkniętego, a nie otwartego.
Henrik

@Henrik true i inne warunki, takie jak endB == nulli startA == nullsprawdź, czy przerwa jest otwarta.
Khaled.K

1
endB == null, startA == null, endA == nullI startB == nullsą wszystkie kryteria, aby sprawdzić nieograniczonego przedziale i nie otwarty przedział. Przykładem różnic między przedziałami nieograniczonymi i otwartymi: (10, 20) i (20, null) są dwa otwarte przedziały, które się nie pokrywają. Ten ostatni ma nieograniczony koniec. Twoja funkcja zwróci true, ale odstępy nie pokrywają się, ponieważ odstępy nie obejmują 20. (używanych numerów zamiast znaczników czasu dla uproszczenia)
Henrik

7

Opublikowane tutaj rozwiązanie nie działało dla wszystkich nakładających się zakresów ...

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

moim działającym rozwiązaniem było:

I (
  („data_początkowa” MIĘDZY STARTDATE A ENDDATE) - uwzględnia datę wewnętrzną i końcową zewnętrzną
  LUB
  („end_date” MIĘDZY STARTDATE A ENDDATE) - obsługuje datę wewnętrzną i datę zewnętrzną
  LUB
  (STARTDATE MIĘDZY „datą_początkową” ORAZ „datą_końcową”) - tylko jeden wymagany dla zewnętrznego zakresu, w którym mieszczą się daty.
) 

5

To było moje rozwiązanie javascript z 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

Łatwym sposobem na zapamiętanie rozwiązania byłoby
min(ends)>max(starts)


3

W Microsoft SQL SERVER - funkcja 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

najprostszy

Najprostszym sposobem jest użycie dobrze zaprojektowanej biblioteki dedykowanej do pracy z datą i godziną.

someInterval.overlaps( anotherInterval )

java.time i ThreeTen-Extra

Najlepsze w branży to java.timeplatforma wbudowana w Javę 8 i nowsze wersje. Dodaj do tego projekt ThreeTen-Extra , który uzupełnia java.time o dodatkowe klasy, szczególnie Intervalklasę, której potrzebujemy tutaj.

Jeśli chodzi o language-agnostictag w tym pytaniu, kod źródłowy dla obu projektów jest dostępny do użycia w innych językach (pamiętaj o ich licencjach).

Interval

org.threeten.extra.IntervalKlasa jest przydatny, ale wymaga Date-Time momentach ( java.time.Instantobiekty) zamiast wartości daty-only. Kontynuujemy, wykorzystując pierwszą chwilę dnia w UTC do przedstawienia daty.

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

Utwórz symbol Intervalreprezentujący ten przedział czasu.

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

Możemy również zdefiniować Intervalmoment początkowy plus Duration.

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

Porównywanie z testem na nakładanie się jest łatwe.

Boolean overlaps = interval_A.overlaps( interval_B );

Możesz porównać Intervalz innym Intervallub Instant:

Wszystkie wykorzystują Half-Openpodejście do definiowania przedziału czasu, w którym początek jest włączający, a zakończenie wyłączny .


3

To rozszerzenie doskonałej odpowiedzi @ charles-bretana.

Odpowiedź nie rozróżnia jednak przedziałów otwartych, zamkniętych i półotwartych (lub półotwartych).

Przypadek 1 : A, B to przedziały zamknięte

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

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


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

Pokrywaj się iff: (StartA <= EndB) and (EndA >= StartB)

Przypadek 2 : A, B to przerwy otwarte

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

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

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

Pokrywaj się iff: (StartA < EndB) and (EndA > StartB)

Przypadek 3 : A, B prawy otwarty

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

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

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

Warunek nakładania się: (StartA < EndB) and (EndA > StartB)

Przypadek 4 : A, B pozostawiono otwarte

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

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

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

Warunek nakładania się: (StartA < EndB) and (EndA > StartB)

Przypadek 5 : A otwarte, B zamknięte

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

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


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

Warunek nakładania się: (StartA <= EndB) and (EndA > StartB)

itp...

Wreszcie ogólny warunek nakładania się dwóch przedziałów to

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

gdzie 🞐 zamienia surową nierówność w niespecyficzną, ilekroć dokonuje się porównania między dwoma uwzględnionymi punktami końcowymi.


Przypadki drugi, trzeci i czwarty mają ten sam warunek nakładania się, czy jest to celowe?
Marie,

@Marie, właśnie wymieniłem kilka przypadków (nie wszystkie)
2314737

To, ale tak dopracowane, jak odpowiedź Jonathana Lefflera, byłoby tym, co miałem na myśli jako zaakceptowaną odpowiedź na pytanie PO.
mbx

3

Krótka odpowiedź z wykorzystaniem momentjs :

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

odpowiedź opiera się na powyższych odpowiedziach, ale jest skrócona.


2

Jeśli używasz zakresu dat, który jeszcze się nie zakończył (wciąż trwa), np. Nie ustawiłeś endDate = „0000-00-00”, nie możesz użyć MIĘDZY, ponieważ 0000-00-00 nie jest prawidłową datą!

Użyłem tego rozwiązania:

(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

Jeśli startdate2 jest wyższy niż enddate, nie ma nakładania się!


2

Odpowiedź jest dla mnie zbyt prosta, dlatego utworzyłem bardziej ogólną dynamiczną instrukcję SQL, która sprawdza, czy dana osoba ma nakładające się daty.

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

Rozwiązanie matematyczne podane przez @Bretana jest dobre, ale pomija dwa konkretne szczegóły:

  1. aspekt zamkniętych lub półotwartych przedziałów
  2. puste interwały

O zamkniętym lub otwartym stanie granic przedziałów, rozwiązanie @Bretana obowiązuje dla zamkniętych przedziałów

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

można przepisać na półotwarte interwały aby:

(StartA <EndB) i (EndA> StartB)

Ta korekta jest konieczna, ponieważ z definicji granica przedziału otwartego nie należy do zakresu wartości przedziału.


A jeśli chodzi o puste odstępy czasu , cóż, tutaj pokazany powyżej związek NIE obowiązuje. Puste interwały, które z definicji nie zawierają żadnej prawidłowej wartości, należy traktować jako przypadek szczególny. Wykazuję to za pomocą mojej biblioteki czasu Java Time4J na tym przykładzie:

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)

Wiodący nawias kwadratowy „[” oznacza zamknięty początek, a ostatni nawias „)” oznacza otwarty koniec.

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

Jak pokazano powyżej, puste przedziały naruszają powyższe warunki nakładania się (szczególnie startA <endB), więc Time4J (i inne biblioteki również) musi traktować to jako specjalny przypadek zbocza, aby zagwarantować, że nakładanie się dowolnego dowolnego przedziału z pustym przedziałem nie istnieje. Oczywiście przedziały dat (które są domyślnie zamknięte w Time4J, ale mogą być również częściowo otwarte, jak puste przedziały dat) są obsługiwane w podobny sposób.


1

Oto ogólna metoda, która może być przydatna lokalnie.

    // 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
Chcesz dodać jakieś wyjaśnienie?
Phantômaxx

1

Za pomocą Java util.Date, oto co zrobiłem.

    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

Moim zdaniem najłatwiej to zrobić, porównując, jeśli albo EndDate1 jest przed StartDate2, a EndDate2 przed StartDate1.

To oczywiście, jeśli bierzesz pod uwagę przedziały, w których StartDate jest zawsze przed EndDate.


1

Miałem sytuację, w której mieliśmy daty zamiast dat, a daty mogły się nakładać tylko na początku / na końcu. Przykład poniżej:

wprowadź opis zdjęcia tutaj

(Zielony to aktualny interwał, niebieskie bloki to prawidłowe interwały, czerwone to nakładające się interwały).

Dostosowałem odpowiedź Iana Nelsona do następującego rozwiązania:

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

To pasuje do wszystkich przypadków nakładania się, ale ignoruje dozwolone przypadki nakładania się.


0

Podziel problem na przypadki, a następnie obsłuż każde z nich .

Sytuacja „przecinają się dwa zakresy dat” obejmuje dwa przypadki - pierwszy zakres dat rozpoczyna się w drugim lub drugi zakres dat rozpoczyna się w pierwszym.


0

Możesz spróbować:

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

To było moje rozwiązanie, zwraca wartość true, gdy wartości się nie pokrywają:

X START 1 Y KONIEC 1

A START 2 B KONIEC 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

Dla ruby ​​znalazłem też:

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

Znalazłem go tutaj z dobrym wyjaśnieniem -> http://makandracards.com/makandra/984-test-if-two-date-ranges-overlap-in-ruby-or-rails


0

Poniższe zapytanie daje mi identyfikatory, dla których podany zakres dat (daty rozpoczęcia i zakończenia pokrywają się z dowolną datą (daty rozpoczęcia i zakończenia) w mojej nazwie tabeli

select id from table_name where (START_DT_TM >= 'END_DATE_TIME'  OR   
(END_DT_TM BETWEEN 'START_DATE_TIME' AND 'END_DATE_TIME'))
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.