Определите, перекрываются ли два диапазона дат


1251

Учитывая два диапазона дат, какой самый простой или эффективный способ определить, перекрываются ли два диапазона дат?

В качестве примера предположим, что мы имеем диапазоны , обозначаемые DateTime переменных StartDate1в EndDate1 и StartDate2 к EndDate2.



@CharlesBretana спасибо за это, вы правы - это почти как двумерная версия моего вопроса!
Ян Нельсон


2
Разделите ситуацию «два диапазона дат пересекаются» на случаи (их два), а затем проверьте для каждого случая.
полковник Паник

1
Этот код работает нормально. Вы можете увидеть мой ответ здесь: stackoverflow.com/a/16961719/1534785
Джейхун Рагимов

Ответы:


2290

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

Доказательство:
пусть ConditionA означает, что DateRange A полностью после DateRange B
_ |---- DateRange A ------| |---Date Range B -----| _
(True, если StartA > EndB)

Пусть ConditionB означает, что DateRange A полностью перед DateRange B
|---- DateRange A -----| _ _ |---Date Range B ----|
(True, если EndA < StartB)

Тогда перекрытие существует, если ни A, ни B не верны -
(если один диапазон не полностью ни за другим,
ни полностью перед другим, тогда они должны перекрываться.)

Теперь один из законов де Моргана гласит:

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

Что переводится как: (StartA <= EndB) and (EndA >= StartB)


ПРИМЕЧАНИЕ. Это включает условия, когда края точно перекрываются. Если вы хотите исключить , что
изменения >=операторов >, и <= к<


ЗАМЕТКА 2. Благодаря @Baodad см этот блог , фактическое перекрытие меньше:
{ endA-startA, endA - startB, endB-startA, endB - startB}

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


ЗАМЕТКА 3. Благодаря @tomosius, более короткая версия гласит:
DateRangesOverlap = max(start1, start2) < min(end1, end2)
Это на самом деле синтаксический ярлык для более длинной реализации, который включает в себя дополнительные проверки, чтобы убедиться, что даты начала находятся в или до endDates. Получив это сверху:

Если даты начала и окончания могут быть не в порядке, т. Е. Если возможно, что startA > endAили startB > endB, то вы также должны проверить, что они в порядке, что означает, что вы должны добавить два дополнительных правила действия:
(StartA <= EndB) and (StartB <= EndA) and (StartA <= EndA) and (StartB <= EndB) или:
(StartA <= EndB) and (StartA <= EndA) and (StartB <= EndA) and (StartB <= EndB) или,
(StartA <= Min(EndA, EndB) and (StartB <= Min(EndA, EndB)) или:
(Max(StartA, StartB) <= Min(EndA, EndB)

Но чтобы реализовать Min()и Max(), вы должны кодировать (используя троичную C для краткости):
(StartA > StartB? Start A: StartB) <= (EndA < EndB? EndA: EndB)


29
Это упрощенная логика, основанная на следующих двух предположениях: 1) StartA <EndA; 2) StartB <EndB. Это кажется очевидным, но в действительности данные могут поступать из неизвестного источника, такого как пользовательский ввод или база данных, без очистки. Имейте в виду, что вам нужно будет проверить входные данные, чтобы убедиться, что эти два предположения верны, прежде чем вы сможете использовать эту упрощенную логику, иначе все рухнет. Урок, извлеченный из собственного опыта;)
Деви

12
@Devy, ты прав. За исключением того, что это также будет работать, если startA = endA. Ведь именно это Startи Endзначат слова . Если у вас есть две переменные с именем Top и Bottom, или East and West, или HighValue и LoValue, можно предположить или подразумевать, что что-то или кто-то где-то должен гарантировать, что одна из пар значений не будет сохранена в противоположных переменных. Только одна из двух пар, потому что, ну, это также будет работать, если обе пары значений переключаются.
Чарльз Бретана

15
Вы можете легко добавить nullable startи end(с семантикой, что «null start» = «С начала времени» и «null end» = «До конца времени»), вот так:(startA === null || endB === null || startA <= endB) && (endA === null || startB === null || endA >= startB)
Кевин Робатель

9
Лучший ответ на Stackexchange! Приятно видеть объяснение того, почему эта умная формула работает!
Abeer Sul

4
Вот самая компактная форма, которую я мог придумать, которая также возвращает false в случае неверного ввода (дата начала> = дата окончания)DateRangesOverlap = max(start1, start2) < min(end1, end2)
tomosius

406

Я считаю, что достаточно сказать, что два диапазона перекрываются, если:

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

76
Я считаю, что (StartDate1 <= EndDate2) and (EndDate1 >= StartDate2)обозначение легче понять, Range1 всегда слева в тестах.
AL

8
Это предполагает даты начала и окончания включительно. Измените <=на, <если начало включено, а конец - эксклюзив.
Ричард Шнайдер

Это будет работать очень хорошо, даже если startDate2 до startDate1. Поэтому нет необходимости предполагать, что startDate1 раньше, чем startDate2.
Шехан Симен

3
Я обнаружил, что нотации (StartDate1 <= EndDate2) и (StartDate2 <= EndDate1) (согласно ответу) легче понять, чем в других ответах.
Апр

Как адаптировать его так, чтобы он работал с данными, имеющими StartDate1 И / ИЛИ EndDate1? Код предполагает, что StartDate1 и EndDate1 присутствуют всегда. Что делать, если задано StartDate1, но не указано EndDate1 ИЛИ EndDate1, но не StartDate1. Как справиться с этим дополнительным случаем?
19

117

В этой статье Библиотека периодов времени для .NET описывает отношение двух периодов времени перечислением PeriodRelation :

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

введите описание изображения здесь


Хорошо, я также реализовал интервальную алгебру Аллена
Meno Hochschild

80

Для рассуждений о временных отношениях (или любых других интервальных отношениях, приходите к этому), рассмотрим Интервальную Алгебру Аллена . Он описывает 13 возможных отношений, которые могут иметь два интервала по отношению друг к другу. Вы можете найти другие ссылки - «Аллен Интервал», кажется, оперативный поисковый термин. Вы также можете найти информацию об этих операциях в Сноуграссе « Разработка ориентированных на время приложений в SQL» (PDF можно найти в Интернете по адресу URL), а также в «Дата, Дарвен и Лоренцо», « Временные данные и реляционная модель» (2002) или « Время и реляционная теория: временные базы данных» в реляционная модель и SQL (2014; фактически второе издание TD & RM).


Короткий (МОГ) Ответ: даны два интервала дат , Aи Bс компонентами .startи .endи ограничения .start <= .end, а затем два интервала перекрываются , если:

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

Вы можете настроить использование >=против >и <=против, <чтобы удовлетворить ваши требования к степени перекрытия.


ErikE комментирует:

Вы можете получить только 13, если посчитаете вещи смешными ... Я могу получить «15 возможных отношений, которые могут иметь два интервала», когда я схожу с ума от этого. Путем разумного подсчета я получаю только шесть, и если вы выбрасываете заботу о том, идет ли A или B первым, я получаю только три (нет пересечения, частично пересекается, одно полностью внутри другого). 15 выглядит следующим образом: [до: до, начало, внутри, конец, после], [начало: начало, внутри, конец, после], [внутри: внутри, конец, после], [конец: конец, после], [ после того, как: после].

Я думаю, что вы не можете сосчитать две записи «до: до» и «после: после». Я мог бы увидеть 7 записей, если вы приравниваете некоторые отношения к их инверсиям (см. Диаграмму в ссылочном URL-адресе Википедии; в ней 7 записей, 6 из которых имеют разные обратные значения, а равные не имеют четких обратных). И разумно ли три, зависит от ваших требований.

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

1
Вы можете получить только 13, если посчитаете вещи смешными ... Я могу получить «15 возможных отношений, которые могут иметь два интервала», когда я схожу с ума от этого. Путем разумного подсчета я получаю только шесть, и если вы выбрасываете заботу о том, идет ли A или B первым, я получаю только три (нет пересечения, частично пересекается, одно полностью внутри другого). 15 выглядит следующим образом: [до: до, начало, внутри, конец, после], [начало: начало, внутри, конец, после], [внутри: внутри, конец, после], [конец: конец, после], [ после того, как : после].
ErikE

@Emtucifor: Я думаю, что вы не можете сосчитать две записи «до: до» и «после: после».
Джонатан Леффлер

Re ваше обновление: B1 до A до: до и B13 до A после: после. В вашей хорошей диаграмме отсутствует начало: начало между B5 и B6 и конец: конец между B11 и B12. Если нахождение в конечной точке является значительным, то вы должны посчитать его, поэтому итоговый счет равен 15, а не 13. Я не думаю, что конечная точка важна, поэтому я лично считаю это [до: до, внутри, после] , [в пределах: в пределах, после], [после: после], который приходит к 6. Я думаю, что вся вещь конечной точки - только беспорядок о том, являются ли границы включающими или исключающими. Исключительность конечных точек не меняет основных отношений!
ErikE

То есть в моей схеме они эквивалентны: (B2, B3, B4), (B6, B7, B9, B10), (B8, B11, B12). Я понимаю, что B7 подразумевает информацию, что два диапазона точно совпадают. Но я не уверен, что эта дополнительная информация должна быть частью базовых отношений пересечения. Например, когда два интервала имеют одинаковую длину, даже если они не совпадают или даже перекрывают друг друга, следует ли это считать другим «отношением»? Я говорю «нет», и, поскольку этот дополнительный аспект - единственное, что отличает B7 от B6, то я думаю, что наличие конечных точек как отдельных случаев делает вещи несовместимыми.
ErikE

@Emtucifor: ОК - я понимаю, почему я ошибочно определил «до: до» и «после: после» в качестве записей; однако я не могу представить, как должны выглядеть записи «start: start» и «end: end». Поскольку вы не можете редактировать мою диаграмму, можете ли вы написать мне по электронной почте (см. Мой профиль) с измененной копией диаграммы, показывающей отношения «начало: начало» и «конец: конец»? У меня нет серьезных проблем с вашими группировками.
Джонатан Леффлер

30

Если необходимо также рассчитать само перекрытие, вы можете использовать следующую формулу:

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

так совпадает ли количество времени, которое разделяют два события? Это работает для всех различных способов, которыми события могут перекрываться?
NSjonas

18

Все решения, которые проверяют множество условий на основе расположения диапазонов по отношению друг к другу, можно значительно упростить, просто обеспечив ранний запуск определенного диапазона! Вы гарантируете, что первый диапазон начинается раньше (или в то же время), меняя диапазоны, если это необходимо, заранее.

Затем вы можете обнаружить перекрытие, если начало другого диапазона меньше или равно первому концу диапазона (если диапазоны включительно, содержат время начала и окончания) или меньше (если диапазоны включают начало и исключение конца) ,

Предполагая, что включительно с обоих концов, есть только четыре возможности, одна из которых не перекрывается:

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

Конечная точка диапазона 2 не входит в него. Итак, в псевдокоде:

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

Это может быть упрощено еще больше в:

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

Если диапазоны включают в начале , так и эксклюзивные в конце концов, вы просто должны заменить >с >=во втором ifзаявлении (для первого сегмента кода: во втором сегменте кода, вы бы использовать <вместо <=):

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

Вы значительно ограничиваете количество проверок, которые вам нужно сделать, потому что вы удаляете половину проблемного пространства раньше, гарантируя, что диапазон 1 никогда не начинается после диапазона 2.


2
+1 за упоминание включающей / исключающей проблемы. Я собирался сам придумать ответ, когда у меня было время, но сейчас не нужно. Дело в том, что вы почти никогда не допускаете, чтобы начало и конец включались одновременно. В моей отрасли обычно принято рассматривать начало как эксклюзивное, а конец - как инклюзивное, но в любом случае хорошо, если вы остаетесь последовательными. Пока это первый полностью правильный ответ на этот вопрос ... ИМО.
Брайан Гидеон

14

Вот еще одно решение с использованием JavaScript. Особенности моего решения:

  • Обрабатывает нулевые значения как бесконечность
  • Предполагается, что нижняя граница является включающей, а верхняя - исключительной.
  • Поставляется с кучей тестов

Тесты основаны на целых числах, но поскольку объекты даты в JavaScript сравнимы, вы можете просто добавить два объекта даты. Или вы можете добавить миллисекундную метку времени.

Код:

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

тесты:

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

Результат при беге с кармой, жасмином и фантомом:

PhantomJS 1.9.8 (Linux): Выполнено 20 из 20 УСПЕХ (0,003 с / 0,004 с)


9

я бы сделал

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

Где IsBetweenчто то типа

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

Я бы предпочел (слева <значение && значение <справа) || (справа <значение && значение <слева) для этого метода.
Патрик Хуизинга

Спасибо за это. Делает вещи проще в моей голове.
шоу

1
Зачем вам проверять четыре условия, когда нужно проверить только два? Потерпеть поражение.
ErikE

3
Ах, мои извинения, теперь я вижу, что вы позволяете диапазонам быть в обратном порядке (StartDateX> EndDateX). Странный. В любом случае, что если StartDate1 меньше StartDate2, а EndDate1 больше EndDate2? Код, который вы дали, не обнаружит это перекрывающееся условие.
ErikE

3
Не вернет ли это значение false, если Date1 содержит целую Date2? Тогда StartDate1 предшествует StartDate2, а EndDate1 - EndDate2
user158037

9

введите описание изображения здесь

Вот код, который делает волшебство:

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

Куда..

  • A -> 1старт
  • B -> 1End
  • C -> 2старт
  • D -> 2End

Доказательство? Посмотрите эту суть кода тестовой консоли .


Это работает, но я бы предпочел проверить, не перекрываются ли только два сценария
Джон Альберт

Спасибо за объяснение этого с помощью изображений. Ваш ответ является идеальным решением для этого вопроса.
Ракеш Верма

8

Вот мое решение на Java , которое работает и на неограниченных интервалах

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

Я думаю, что вы имели в виду неограниченные концы вместо открытых интервалов.
Хенрик

@Henrik оба условия работают en.wikipedia.org/wiki/Interval_(matmatics)#Terminology
Khaled.K

!startA.after(endB)означает startA <= endB и !endA.before(startB)означает startB <= endA. Это критерии для закрытого интервала, а не открытого интервала.
Хенрик

@Henrik true и другие условия, такие как endB == nullи startA == nullпроверка на открытый интервал.
Халед.К

1
endB == null, startA == null, endA == nullИ startB == nullвсе критерии для проверки на неограниченный интервал , а не открытый интервал. Пример различий между неограниченными и открытыми интервалами: (10, 20) и (20, ноль) - два открытых интервала, которые не перекрываются. Последний действительно имеет неограниченный конец. Ваша функция вернет true, но интервалы не перекрываются, потому что интервалы не включают 20. (для простоты использовались числа вместо временных меток)
Henrik

7

Размещенное здесь решение не работает для всех перекрывающихся диапазонов ...

---------------------- | ------- ------- | ----------- -----------
    | ---- B1 ---- |
           | ---- B2 ---- |
               | ---- B3 ---- |
               | ---------- ---------- B4 |
               | ---------------- B5 ---------------- |
                      | ---- В6 ---- |
---------------------- | ------- ------- | ----------- -----------
                      | ------ B7 ------- |
                      | ---------- B8 ----------- |
                         | ---- B9 ---- |
                         | ---- B10 ----- |
                         | -------- B11 -------- |
                                      | ---- B12 ---- |
                                         | ---- B13 ---- |
---------------------- | ------- ------- | ----------- -----------

мое рабочее решение было:

А ТАКЖЕ (
  ('start_date' BETWEEN STARTDATE AND ENDDATE) - обслуживает внутреннюю и внешнюю дату окончания
  ИЛИ
  ('end_date' BETWEEN STARTDATE AND ENDDATE) - обслуживает внутреннюю и внешнюю дату начала
  ИЛИ
  (STARTDATE BETWEEN 'start_date' И 'end_date') - только один необходим для внешнего диапазона, где даты находятся внутри.
) 

5

Это было мое решение javascript с помощью 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;


3

В Microsoft SQL SERVER - функция 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

простейший

Самый простой способ - использовать специально разработанную специализированную библиотеку для работы с датой и временем.

someInterval.overlaps( anotherInterval )

java.time & ThreeTen-Extra

Лучшим в бизнесе является java.timeфреймворк, встроенный в Java 8 и более поздние версии . Добавьте к этому проект ThreeTen-Extra, который дополняет java.time дополнительными классами, в частности Intervalклассом, который нам нужен здесь.

Что касается language-agnosticтега в этом Вопросе, исходный код для обоих проектов доступен для использования на других языках (обратите внимание на их лицензии).

Interval

org.threeten.extra.IntervalКласс удобен, но требует дат времени моментов ( java.time.Instantобъекты) , а не даты только значение. Итак, мы используем первый момент дня в UTC для представления даты.

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

Создайте, Intervalчтобы представить этот промежуток времени.

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

Мы также можем определить Intervalначальный момент плюс а Duration.

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

Сравнение с тестом на совпадения легко.

Boolean overlaps = interval_A.overlaps( interval_B );

Вы можете сравнить Intervalс другим Intervalили Instant:

Все они используют Half-Openподход к определению промежутка времени, когда начало включительно, а окончание - исключительно .


3

Это продолжение превосходного ответа @ charles-bretana.

Ответ, однако, не делает различий между открытым, закрытым и полуоткрытым (или полузакрытым) интервалами.

Случай 1 : A, B - закрытые интервалы

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

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


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

Перекрываются, если: (StartA <= EndB) and (EndA >= StartB)

Случай 2 : A, B - открытые интервалы

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

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

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

Перекрываются, если: (StartA < EndB) and (EndA > StartB)

Дело 3 : A, B прямо открыто

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

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

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

Условие перекрытия: (StartA < EndB) and (EndA > StartB)

Дело 4 : A, B оставлено открытым

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

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

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

Условие перекрытия: (StartA < EndB) and (EndA > StartB)

Случай 5 : Право открыто, Б закрыто

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

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


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

Условие перекрытия: (StartA <= EndB) and (EndA > StartB)

так далее...

Наконец, общее условие перекрытия двух интервалов

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

где 🞐 превращает строгое неравенство в нестрогое всякий раз, когда проводится сравнение между двумя включенными конечными точками.


Случаи два, три и четыре имеют одинаковое условие перекрытия, это намеренно?
Мари

@Marie, я только перечислил несколько случаев (не все)
user2314737

Это, но так же подробно, как и ответ Джонатана Леффлера, было бы тем, что я имел в виду в качестве принятого ответа на вопрос ОП.
mbx

3

Краткий ответ с использованием моментов :

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

ответ основан на ответах выше, но его сокращают.


2

Если вы используете диапазон дат, который еще не закончился (все еще продолжается), например, не установлен endDate = '0000-00-00', вы не можете использовать BETWEEN, потому что 0000-00-00 не является действительной датой!

Я использовал это решение:

(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

Если startdate2 выше, тогда enddate не перекрывается!


2

Для меня ответ слишком прост, поэтому я создал более общий динамический оператор SQL, который проверяет, есть ли у человека перекрывающиеся даты.

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

Математическое решение, данное @Bretana, хорошо, но игнорирует две конкретные детали:

  1. аспект закрытых или полуоткрытых интервалов
  2. пустые интервалы

О закрытом или открытом состоянии интервальных границ, решение @Bretana справедливо для замкнутых интервалов

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

может быть переписан для полуоткрытых интервалов, чтобы:

(StartA <EndB) и (EndA> StartB)

Это исправление необходимо, поскольку граница открытого интервала по определению не принадлежит диапазону значений интервала.


А насчет пустых интервалов , ну, здесь показанные выше отношения НЕ имеют места. Пустые интервалы, которые не содержат никаких допустимых значений по определению, должны обрабатываться как особый случай. Я демонстрирую это своей библиотекой времени Java Time4J на следующем примере:

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)

Первая квадратная скобка «[» обозначает закрытое начало, в то время как последняя квадратная скобка «)» обозначает открытый конец.

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

Как показано выше, пустые интервалы нарушают условие перекрытия выше (особенно startA <endB), поэтому Time4J (и другие библиотеки тоже) должны обрабатывать его как специальный крайний случай, чтобы гарантировать перекрытие любого произвольного интервала с пустым интервалом не существует. Конечно, интервалы дат (которые по умолчанию закрыты в Time4J, но могут быть и полуоткрытыми, как пустые интервалы дат) обрабатываются аналогичным образом.


1

Вот универсальный метод, который может быть полезным локально.

    // 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
Не могли бы вы добавить несколько слов объяснения?
Phantômaxx

1

Используя Java util.Date, вот что я сделал.

    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

По моему мнению, самый простой способ сделать это - сравнить EndDate1 с StartDate2 и EndDate2 с StartDate1.

Это, конечно, если вы рассматриваете интервалы, когда StartDate всегда предшествует EndDate.


1

У меня была ситуация, когда у нас были даты, а не даты, и даты могли перекрываться только в начале / конце. Пример ниже:

введите описание изображения здесь

(Зеленый - текущий интервал, синие блоки - допустимые интервалы, красные - перекрывающиеся).

Я адаптировал ответ Яна Нельсона к следующему решению:

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

Это соответствует всем случаям перекрытия, но игнорирует разрешенные случаи перекрытия.


0

Отделить проблему в случаях затем обрабатывать каждый случай .

Ситуация «пересечение двух диапазонов дат» охватывается двумя случаями: первый диапазон дат начинается во втором, или второй диапазон дат начинается в первом.


0

Вы можете попробовать это:

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

Это было мое решение, оно возвращает истину, когда значения не перекрываются:

X START 1 Y END 1

НАЧАЛО 2 B КОНЕЦ 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

Для рубина я также нашел это:

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

Нашел это здесь с хорошим объяснением -> http://makandracards.com/makandra/984-test-if-two-date-ranges-overlap-in-ruby-or-rails


0

Приведенный ниже запрос дает мне идентификаторы, для которых предоставленный диапазон дат (даты начала и окончания перекрывается с любой из дат (даты начала и окончания) в моем имени таблицы

select id from table_name where (START_DT_TM >= 'END_DATE_TIME'  OR   
(END_DT_TM BETWEEN 'START_DATE_TIME' AND 'END_DATE_TIME'))
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.