Определите, перекрываются ли два диапазона дат - PullRequest
1104 голосов
/ 28 ноября 2008

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

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

Ответы [ 34 ]

2072 голосов
/ 28 ноября 2008

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

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

Пусть ConditionB означает, что DateRange A полностью перед DateRange B
|---- DateRange A -----| _ _ |---Date Range B ----|
(Истинно, если 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)


Note3. Благодаря @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)

359 голосов
/ 28 ноября 2008

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

(StartDate1 <= EndDate2) and (StartDate2 <= EndDate1)
100 голосов
/ 09 апреля 2011

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

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

enter image description here

72 голосов
/ 30 ноября 2008

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


Краткий (ish) ответ: с учетом двух интервалов дат 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-------|----------------------
26 голосов
/ 06 сентября 2012

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

overlap = max(0, min(EndDate1, EndDate2) - max(StartDate1, StartDate2))
if (overlap > 0) { 
    ...
}
16 голосов
/ 06 августа 2010

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

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

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

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

13 голосов
/ 13 марта 2015

Вот еще одно решение с использованием 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);
    });
});

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

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

8 голосов
/ 28 ноября 2008

Я бы сделал

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);
    }
7 голосов
/ 28 октября 2017

enter image description here

Вот код, который делает магию:

 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 -> 1Start
  • B -> 1End
  • C -> 2старт
  • D -> 2End

Доказательство? Проверьте этот тест консольный код gist .

7 голосов
/ 21 февраля 2017

Вот мое решение в 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));
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...