Визначте, чи перекриваються два діапазони дат


1248

З огляду на два діапазони дат, який найпростіший чи найефективніший спосіб визначити, чи перекриваються два діапазони дат?

Наприклад, припустимо, що ми мали діапазони, позначені змінними DateTime, StartDate1до EndDate1 та StartDate2 до EndDate2.


3
Надзвичайно схожий на stackoverflow.com/questions/306316/…
Charles Bretana

@CharlesBretana дякую за це, ви маєте рацію - це майже як двовимірна версія мого питання!
Ян Нельсон


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

1
Цей код чудово працює. Ви можете побачити мій відповідь тут: stackoverflow.com/a/16961719/1534785
Джейхун Rahimov

Відповіді:


2288

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

Доказ:
Нехай умоваA означає, що DateRange повністю після 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)


ПРИМІТКА3. Завдяки @tomosius, у коротшій версії написано:
DateRangesOverlap = max(start1, start2) < min(end1, end2)
Це фактично синтаксичний ярлик для більш тривалої реалізації, який включає додаткові перевірки, щоб перевірити, чи дату початку є на кінцевих датах або до них. Виведення цього зверху:

Якщо дати початку і закінчення може бути в порядку, тобто, якщо це можливо, 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 ternary для терміновості):
(StartA > StartB? Start A: StartB) <= (EndA < EndB? EndA: EndB)


29
Це спрощена логіка, заснована на цих двох припущеннях: 1) StartA <EndA; 2) StartB <EndB. Це здається очевидним, але насправді дані можуть надходити з невідомого джерела, як-от введення користувача або бази даних без санітарії. Майте на увазі, що вам потрібно буде перевірити вхідні дані, щоб переконатися, що ці два припущення є істинними, перш ніж ви зможете використовувати цю спрощену логіку, інакше все розвалиться. Урок, засвоєний на власному досвіді;)
Devy

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

15
Ви можете легко додати нульовий startі end(з семантичним значенням, що "null start" = "З початку часу" та "null end" = "До кінця часу") так:(startA === null || endB === null || startA <= endB) && (endA === null || startB === null || endA >= startB)
Кевін Робатель,

9
Найкраща відповідь на Stackexchange! Добре бачити пояснення, чому ця розумна формула працює!
Abeer Sul

4
Ось найбільш компактна форма, яку я міг би придумати, яка також повертає помилку в разі недійсного введення даних (дата початку> = дата закінчення)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 AND / OR EndDate1? Код передбачає, що StartDate1 і EndDate1 завжди присутні. Що робити, якщо введено StartDate1, але не вказано EndDate1 АБО EndDate1, але не StartDate1. Як впоратися з цим додатковим випадком?
juFo

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 можливих відносин, які можуть мати два інтервали відносно один одного. Ви можете знайти інші посилання - "Аллен Інтервал", здається, є оперативним пошуковим терміном. Ви також можете знайти інформацію про ці операції у розробці програм, орієнтованих на час Snodgrass, у SQL (PDF доступний в Інтернеті за URL-адресою), а також у Date, Darwen і Lorentzos Temporal Data і реляційна модель (2002) або Time and Relational Theory: Temporal Databases in Реляційна модель та SQL (2014; фактично друга редакція TD&RM).


Короткий (МІГ) Відповідь: дано два інтервалу дат , Aі Bз компонентами .startі .endта обмеження .start <= .end, а потім два інтервали перекриваються , якщо:

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

Ви можете налаштувати використання >=vs >і <=vs, <щоб відповідати вашим вимогам щодо ступеня перекриття.


ErikE коментарі:

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

Я думаю, що ви не можете порахувати два записи "до: перед" і "після: після". Я міг би побачити 7 записів, якщо ви порівнюєте деякі відносини з їх обертами (див. Схему у посиланні на URL-адресу Вікіпедії; в ній 7 записів, 6 з яких мають іншу обернену формулу, причому рівні, які не мають чітко обернених). І чи розумне три, залежить від ваших вимог.

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

1
Ви можете отримати лише 13, якщо порахувати речі смішними ... Я можу отримати "15 можливих відносин, які можуть мати два інтервали", коли я з розуму зітнюся з цим. При розумному підрахунку я отримую лише шість, і якщо ви кидаєте турботу, чи А або В приходить першим, я отримую лише три (немає перехрестя, частково перетинаються, один повністю в межах іншого). 15 йде так: [раніше: перед, почати, всередині, закінчити, після], [почати: почати, всередині, закінчити, після], [всередині: усередині, кінець, після], [кінець: кінець, після], [ після: після].
ЕрікЕ

@Emtucifor: Я думаю, що ви не можете порахувати два записи "до: перед" і "після: після".
Джонатан Леффлер

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

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

@Emtucifor: Гаразд - я бачу, чому я неправильно ідентифікував "раніше: перед" та "після: після" як записи; однак я не можу уявити, як мають виглядати записи "початок: початок" та "кінець: кінець". Оскільки ви не можете редагувати мою діаграму, чи можете ви надіслати мені електронну пошту (див. Мій профіль) зі зміненою копією діаграми, що показує стосунки "початок: початок" та "кінець: кінець"? У мене немає великих проблем з вашими угрупованнями.
Джонатан Леффлер

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:

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

Я вважаю за краще (ліворуч <значення & & значення <право) || (право <значення & & значення <ліворуч) для цього методу.
Патрік Хуїзінга

Дякую за це Полегшує справи в моїй голові.
sshow

1
Навіщо ви перевіряли чотири умови, коли вам потрібно перевірити лише два? Збій.
ЕрікЕ

3
Ах, вибачте, зараз я бачу, що ви дозволяєте діапазонам бути в зворотному порядку (StartDateX> EndDateX). Дивно. У будь-якому випадку, що, якщо StartDate1 менше StartDate2, а EndDate1 більше EndDate2? Введений вами код не виявить цю умову, що перекривається.
ЕрікЕ

3
Чи не буде це поверненням помилковим, якщо Date1 містить цілу Date2? Тоді StartDate1 - перед StartDate2, а EndDate1 - після EndDate2
користувача158037

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 -> 1Start
  • B -> 1 Кінець
  • C -> 2Start
  • D -> 2 Кінець

Доказ? Ознайомтеся з цим кодом тестової консолі .


Це працює, але я вважаю за краще тестувати, чи не перекриваються лише два сценарії
Джон Альберт

Дякуємо, що пояснили це за допомогою зображень. Ваша відповідь - ідеальне рішення для цього питання.
Ракеш Верма

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_(mathematics)#Terminology
Khaled.K

!startA.after(endB)означає startA <= endB і !endA.before(startB)означає startB <= endA. Це критерії закритого інтервалу, а не відкритого інтервалу.
Генрік

@Henrik правда, і інші умови , такі , як endB == nullі startA == nullперевірка на відкритому інтервалі.
Халед.К

1
endB == null, startA == null, endA == nullІ startB == nullвсе критерії для перевірки на необмежений інтервал , а не відкритий інтервал. Приклад відмінностей між необмеженими та відкритими інтервалами: (10, 20) та (20, нульовий) - це два відкритих інтервали, які не перетинаються. Останній має необмежений кінець. Ваша функція повернеться правдою, але інтервали не перетинаються, оскільки інтервали не включають 20. (використовувані числа замість часових позначок для простоти)
Генрік

7

Розміщене тут рішення не працювало для всіх діапазонів, що перекриваються ...

---------------------- | ------- A ------- | ----------- -----------
    | ---- B1 ---- |
           | ---- B2 ---- |
               | ---- B3 ---- |
               | ---------- В4 ---------- |
               | ---------------- В5 ---------------- |
                      | ---- B6 ---- |
---------------------- | ------- A ------- | ----------- -----------
                      | ------ B7 ------- |
                      | ---------- В8 ----------- |
                         | ---- B9 ---- |
                         | ---- B10 ----- |
                         | -------- B11 -------- |
                                      | ---- B12 ---- |
                                         | ---- B13 ---- |
---------------------- | ------- A ------- | ----------- -----------

моє робоче рішення:

І (
  ('start_date' МЕЖ ДО СТАРТДАТА ТА КІНЦЯ) - забезпечує внутрішню та кінцеву дату зовнішньої
  АБО
  ('end_date' МЕЖ ДО СТАРТДАТА ТА КІНЦЯ) - забезпечує внутрішню та початкову дату зовнішньої
  АБО
  (STARTDATE МЕЖУ 'start_date' AND '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початковий момент плюс a 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 = [StartA, EndA]
B = [StartB, EndB]

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


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

Iff перекриття: (StartA <= EndB) and (EndA >= StartB)

Випадок 2 : А, В - це відкриті інтервали

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

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

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

Iff перекриття: (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

Коротка відповідь за допомогою momentjs :

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 СТАРТ 1 Y КІН 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.