Учитывая два диапазона дат, каков самый простой или наиболее эффективный способ определить, перекрываются ли эти два диапазона дат?
В качестве примера предположим, что у нас есть диапазоны, обозначенные переменными DateTime от StartDate1 до EndDate1иStartDate2 до EndDate2.
@CharlesBretana, спасибо за это, вы правы - это почти как двухмерная версия моего вопроса!
очень похож на stackoverflow.com/questions/117962/…
Разделите ситуацию «два диапазона дат пересекаются» на случаи (их два), а затем проверьте каждый случай.
Если даты могут быть значениями NULL (или пустыми), когда они не установлены, существует этот вопрос, который является расширением этого.
Алгоритм Объединить перекрывающиеся интервалы может дать некоторые подсказки.
Я знаю, что это было помечено как независимый от языка, но для всех, кто реализует на Java: не изобретайте велосипед и используйте Joda Time. joda-time.sourceforge.net/api-release/org/joda/time/base/…
Привет .. A: StartDate1, B: EndDate1, C: StartDate2, D: EndDate2. если B <C или A> D, то мы предполагаем, что они не пересекаются .. Итак, мы можем легко проверить с помощью «isintersects = not (B <C или A> D)», это всегда даст нам, пересекается ли он или нет.
Еще одна интервальная утилита для .Net github.com/AlexeyBoiko/IntervalUtility (я автор)





Я считаю, что достаточно сказать, что эти два диапазона перекрываются, если:
(StartDate1 <= EndDate2) and (StartDate2 <= EndDate1)
Я считаю, что нотация (StartDate1 <= EndDate2) and (EndDate1 >= StartDate2) легче для понимания, Range1 в тестах всегда слева.
Предполагается, что даты начала и окончания указаны включительно. Измените <= на <, если начало включающее, а конец исключающее.
Это будет работать очень хорошо, даже если startDate2 предшествует startDate1. Поэтому не нужно предполагать, что startDate1 раньше startDate2.
Я обнаружил, что обозначения (StartDate1 <= EndDate2) и (StartDate2 <= EndDate1) (согласно ответу) легче понять, чем в других ответах.
Как адаптироваться, чтобы он работал с данными, имеющими StartDate1 И / ИЛИ EndDate1? Код предполагает, что StartDate1 и EndDate1 присутствуют всегда. Что, если указана StartDate1, но не указана EndDate1 ИЛИ EndDate1, но не указана StartDate1. Как справиться с этим лишним делом?
Как я могу сделать начало эксклюзивным, а конец включенным?
я бы сделал
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);
}
Я бы предпочел (left <value && value <right) || (right <value && value <left) для этого метода.
Спасибо за это. Облегчает жизнь в моей голове.
Зачем проверять четыре условия, если нужно проверить только два? Провал.
Ах, мои извинения, теперь я вижу, что вы разрешаете диапазонам быть в обратном порядке (StartDateX> EndDateX). Странный. В любом случае, что, если StartDate1 меньше StartDate2, а EndDate1 больше EndDate2? Код, который вы указали, не обнаружит это перекрывающееся условие.
Разве это не вернет false, если Date1 содержит весь Date2? Тогда StartDate1 находится перед StartDate2, а EndDate1 - после EndDate2.
На мой взгляд, самый простой способ сделать это - сравнить, находится ли EndDate1 перед StartDate2, а EndDate2 перед StartDate1.
Это, конечно, если вы рассматриваете интервалы, в которых StartDate всегда предшествует EndDate.
(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)
ЗАМЕТКА 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)
в любом случае мы можем получить фактическое количество секунд перекрытия
Да, но я не думаю, что есть простой способ ... Без написания реального SQL можно было бы использовать один алгоритм: использование точек A0, A1, B0 и B1 в качестве точек: если нет перекрытия, то 0; Когда Bo и B1 находятся между A0 и A1, тогда это B1-B0; Когда только B0 находится между A0 и A1, тогда это A1-B0; когда только B1 находится между A0 и A1, тогда это B1-A0; иначе это A1-A0
Это упрощенная логика, основанная на этих двух предположениях: 1) StartA <EndA; 2) StartB <EndB. Это кажется очевидным, но на самом деле данные могут поступать из неизвестного источника, такого как ввод пользователя или база данных, без очистки. Имейте в виду, что вам нужно будет проверить входные данные, чтобы убедиться, что эти два предположения верны, прежде чем вы сможете использовать эту упрощенную логику, иначе все развалится. Урок извлечен из собственного опыта;)
@Devy, ты прав. За исключением того, что он также будет работать, если startA = endA. Действительно, это именно то, что означают слова Start и End. Если у вас есть две переменные с именами Top и Bottom, или East and West, или HighValue и LoValue, можно предположить или подразумевать, что что-то или кто-то где-то должен гарантировать, что одна из пар значений не хранится в противоположных переменных. -Только одна из двух пар, потому что она также будет работать, если поменять обе пары значений.
@Devy Это похоже на несвязанную проблему. Нарисуйте линию, чтобы показать 5-9 августа в календаре, затем проведите линию, чтобы показать 7-3 августа, и скажите мне, пересекаются ли они. Вы не можете, потому что вы не можете провести линию, начиная с 7 августа и заканчивая 3 августа (только наоборот).
@rashid, вот пост может дать вам несколько подсказок о том, как получить фактическую величину перекрытия.
Вы можете легко добавить обнуляемые start и end (с семантикой "нулевое начало" = "с начала времени" и "нулевой конец" = "до конца времени") вот так: (startA === null || endB === null || startA <= endB) && (endA === null || startB === null || endA >= startB)
Лучший ответ на Stackexchange! Приятно видеть объяснение того, почему эта умная формула работает!
Я был ошеломлен, когда посмотрел на него в первый раз, я сказал себе: этого не может быть. Но после тестирования всех возможных случаев у меня получился только тот случай, когда один из bools имеет значение true: (true, false), когда A полностью перед B, и (false, true), когда A полностью после B ... Это это один из самых интересных способов изучить логическую математику! Начиная со всех возможных случаев и заканчивая этим, довольно интересно
Вот самая компактная форма, которую я мог придумать, которая также возвращает false в случае недопустимого ввода (дата начала> = дата окончания) DateRangesOverlap = max(start1, start2) < min(end1, end2)
@tomosius, я добавил примечание к своему ответу, чтобы объяснить ваш комментарий
Это лучший ответ, который я когда-либо видел в StackOverflow.
Это красота!
@KevinRobatel, это работает для всех сценариев, но когда оба endA и endB равны нулю, это сценарий, в котором независимо от того, какие даты установлены, они будут конфликтовать, но в вашем сценарии они пройдут.
Прошли годы, но я все еще возвращаюсь к этому ответу, потому что это лучшее объяснение.
Чтобы рассуждать о временных отношениях (или любых других интервальных отношениях, если уж на то пошло), рассмотрите Алгебра интервалов Аллена. Он описывает 13 возможных отношений, которые могут иметь два интервала по отношению друг к другу. Вы можете найти и другие ссылки - "Интервал Аллена" кажется оперативным поисковым запросом. Вы также можете найти информацию об этих операциях в Разработка приложений, ориентированных на время, на SQL Snodgrass (PDF-файл доступен онлайн по адресу URL), а также в Date, Darwen and Lorentzos Временные данные и реляционная модель (2002) или Время и реляционная теория: временные базы данных в реляционной модели и SQL (2014; фактически второе издание TD&RM).
Краткий ответ: если даны два интервала дат A и B с компонентами .start и .end и ограничением .start <= .end, то два интервала перекрываются, если:
A.end >= B.start AND A.start <= B.end
Вы можете настроить использование >= против > и <= против < в соответствии с вашими требованиями к степени перекрытия.
ErikE комментирует:
You can only get 13 if you count things funny... I can get "15 possible relations that two intervals can have" when I go crazy with it. By sensible counting, I get only six, and if you throw out caring whether A or B comes first, I get only three (no intersect, partially intersect, one wholly within other). 15 goes like this: [before:before, start, within, end, after], [start:start, within, end, after], [within:within, end, after], [end:end, after], [after:after].
Я думаю, что вы не можете сосчитать две записи «до: до» и «после: после». Я мог бы увидеть 7 записей, если вы приравняете некоторые отношения к их обратным (см. Диаграмму в указанном URL-адресе Википедии; он имеет 7 записей, 6 из которых имеют другую обратную сторону, при этом равные не имеют отдельного обратного). А разумность трех зависит от ваших требований.
----------------------|-------A-------|----------------------
|----B1----|
|----B2----|
|----B3----|
|----------B4----------|
|----------------B5----------------|
|----B6----|
----------------------|-------A-------|----------------------
|------B7-------|
|----------B8-----------|
|----B9----|
|----B10-----|
|--------B11--------|
|----B12----|
|----B13----|
----------------------|-------A-------|----------------------
Вы можете получить только 13, если посчитаете забавные вещи ... Я могу получить «15 возможных отношений, которые могут иметь два интервала», когда я схожу с ума от этого. Путем разумного подсчета я получаю только шесть, и если вы не заботитесь о том, идет ли сначала A или B, я получаю только три (без пересечения, частичное пересечение, одно полностью внутри другого). 15 выглядит так: [до: до, начало, внутри, конец, после], [начало: начало, внутри, конец, после], [внутри: внутри, конец, после], [конец: конец, после], [ после: после].
@Emtucifor: Я думаю, вы не можете сосчитать две записи «до: до» и «после: после».
Повторите ваше обновление: от B1 до A - до: до, а от B13 до A - после: после. На вашей красивой диаграмме отсутствует начало: начало между B5 B6 и конец: конец между B11 и B12. Если нахождение на конечной точке имеет значение, вы должны его подсчитать, поэтому окончательный результат будет 15, а не 13. Я не считаю, что конечная точка имеет значение, поэтому я лично считаю ее [до: до, внутри, после], [ inside: inside, after], [after: after], что доходит до 6. Я думаю, что вся проблема конечных точек - это просто заблуждение относительно того, являются ли границы включающими или исключающими. Исключительность конечных точек не меняет основных отношений!
То есть в моей схеме они эквивалентны: (B2, B3, B4), (B6, B7, B9, B10), (B8, B11, B12). Я понимаю, что B7 подразумевает информацию о том, что два диапазона в точности совпадают. Но я не уверен, что эта информация дополнительный должна быть частью базовых отношений пересечения. Например, если два интервала имеют одинаковую длину, даже если они не совпадают или даже не перекрываются, следует ли это рассматривать как еще одно «отношение»? Я говорю «нет», и, поскольку этот дополнительный аспект - единственное, что отличает B7 от B6, я думаю, что наличие оконечных точек как отдельных случаев делает вещи непоследовательными.
@Emtucifor: Хорошо, я понимаю, почему я неправильно определил «до: до» и «после: после» в качестве записей; однако я не могу представить, как должны выглядеть записи start: start и end: end. Поскольку вы не можете редактировать мою диаграмму, можете ли вы отправить мне электронное письмо (см. Мой профиль) с измененной копией диаграммы, показывающей отношения «начало: начало» и «конец: конец»? У меня нет серьезных проблем с вашими группировками.
start: start будет началом и концом одновременно, началом A. Поскольку время в компьютерах всегда имеет степень детализации или разрешение, это по-прежнему обозначает (возможно, очень короткий) временной диапазон. end: end будет начинаться и заканчиваться в конце A. Я напишу электронное письмо для полноты.
После обмена электронной почтой с Джонатаном я понял, что в моей модели, где время начала включено, а время окончания является исключительным, диапазон, начинающийся и заканчивающийся одновременно, имеет нулевую длину (потому что он заканчивается раньше, чем начинается). Так что [start: start] и [end: end] не имеют смысла в моей схеме. Однако предложенная Джонатаном схема требует, чтобы конечные точки были инклюзивными, иначе диапазоны B2 и A (например) не пересекались бы. Для меня это проблематично, потому что время в компьютере не отображается с бесконечной точностью. См. Следующий комментарий ->
Если время окончания включено, то для обозначения неперекрывающихся, но смежных диапазонов в компьютере необходимо вычесть наименьшее допустимое приращение времени из времени окончания более раннего сегмента. Например, в SQL Server с типом данных datetime диапазоны A: '20100101 09:00' - '20100101 10:00' и B: '20100101 10:00' - '20100101 11:00' будут перекрываться на 1 / 300-я миллисекунда. Чтобы сделать их смежными и неперекрывающимися, первый диапазон необходимо изменить на A: «20100101 09:00» - «20100101 09: 59: 59.997». Но это совершенно неприемлемо по нескольким причинам (Далее ->)
Первая и менее важная причина в том, что люди не думают о времени таким образом. Они хотят сказать, что диапазон начинается с 9 и заканчивается на 10. Если вы собираете данные от пользователя, вам нужно либо представить мусор 09: 59: 59.997 (это никогда не сработает), либо все время корректировать, когда вы хранить их (не намного лучше). Вторая и более важная причина заключается в том, что базовый тип данных не должен изменять бизнес-значение данных. Что, если столбец datetime был преобразован в datetime2 (technet.microsoft.com/en-us/library/bb677335.aspx), который имеет значения с 00:00:00 до 23:59: 59.9999999?
Теперь есть разрыв между концом A и началом B. Единственное разумное решение всего этого безумия - это просто рассматривать конечные времена как исключительные. Как только это будет сделано, 13 диапазонов, упомянутых Джонатаном, полностью разрушатся. Нет особой разницы между двумя соседними или разделенными неперекрывающимися диапазонами. После удаления свернутых или эквивалентных случаев у нас остается только 6 основных соотношений (3, если A и B считаются взаимозаменяемыми).
Плюс разрешения интервалов нулевой длины состоит в том, что это тривиально обобщается на проверки «включенности» временных точек.
Вот общий метод, который может быть полезен локально.
// 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
*/
}
Все решения, которые проверяют множество условий, основанных на том, где диапазоны находятся по отношению друг к другу, могут быть значительно упрощены с помощью просто убедитесь, что определенный диапазон начинается раньше!. Вы гарантируете, что первый диапазон начинается раньше (или в то же время), меняя диапазоны, если необходимо, заранее.
Затем вы можете обнаружить перекрытие, если начало другого диапазона меньше или равно первому концу диапазона (если диапазоны включены, содержат время начала и окончания) или меньше (если диапазоны включают начало и не включают конец) .
Предполагая инклюзивность на обоих концах, есть только четыре возможности, одна из которых не перекрывается:
|----------------------| 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.
+1 за упоминание инклюзивной / исключительной проблемы. Я собирался сам придумать ответ, когда у меня будет время, но сейчас в этом нет необходимости. Дело в том, что вы почти никогда не позволяете одновременно включать начало и конец. В моей отрасли принято рассматривать начало как исключительное, а конец как инклюзивное, но в любом случае все в порядке, если вы остаетесь последовательными. На данный момент это первый полностью правильный ответ на этот вопрос ... ИМО.
В этой статье Библиотека периодов времени для .NET описывается отношение двух периодов времени с помощью перечисления PeriodRelation:
// ------------------------------------------------------------------------
public enum PeriodRelation
{
After,
StartTouching,
StartInside,
InsideStartTouching,
EnclosingStartTouching,
Enclosing,
EnclosingEndTouching,
ExactMatch,
Inside,
InsideEndTouching,
EndInside,
EndTouching,
Before,
} // enum PeriodRelation

Хорошо, я также реализовал алгебру интервалов Алленса на Java, см. API IntervalRelation и IsoInterval
Отличное резюме для написания спецификаций для перекрывающихся дат
if (StartDate1 > StartDate2) swap(StartDate, EndDate);
(StartDate1 <= EndDate2) and (StartDate2 <= EndDate1);
Достаточно второй строки. Какова цель первой строки? Что такое StartDate и EndDate?
Если необходимо рассчитать и само перекрытие, вы можете использовать следующую формулу:
overlap = max(0, min(EndDate1, EndDate2) - max(StartDate1, StartDate2))
if (overlap > 0) {
...
}
Итак, перекрытие - это количество времени, которое разделяют два события? Работает ли это для всех возможных способов перекрытия событий?
Разделите проблему на случаи, затем обработайте каждый случай.
Ситуация «пересечение двух диапазонов дат» покрывается двумя случаями: первый диапазон дат начинается в пределах второго или второй диапазон дат начинается в пределах первого.
Это было мое решение для 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;
Представленное здесь решение не работало для всех перекрывающихся диапазонов ...
----------------------|-------A-------|----------------------
|----B1----|
|----B2----|
|----B3----|
|----------B4----------|
|----------------B5----------------|
|----B6----|
----------------------|-------A-------|----------------------
|------B7-------|
|----------B8-----------|
|----B9----|
|----B10-----|
|--------B11--------|
|----B12----|
|----B13----|
----------------------|-------A-------|----------------------мое рабочее решение было:
AND (
('start_date' BETWEEN STARTDATE AND ENDDATE) -- caters for inner and end date outer
OR
('end_date' BETWEEN STARTDATE AND ENDDATE) -- caters for inner and start date outer
OR
(STARTDATE BETWEEN 'start_date' AND 'end_date') -- only one needed for outer range where dates are inside.
)
Вы можете попробовать это:
//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);
Если вы используете диапазон дат, который еще не закончился (все еще продолжается), например не задано 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, перекрытия нет!
В 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
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");
}
Не могли бы добавить несколько слов пояснения?
Это было мое решение, оно возвращает истину, когда значения не перекрываются:
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
Вот что я сделал с помощью 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;
}
Вот еще одно решение с использованием 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);
});
});
Результат при запуске с karma & jasmine и PhantomJS:
PhantomJS 1.9.8 (Linux): Executed 20 of 20 SUCCESS (0.003 secs / 0.004 secs)
Для рубина я также нашел это:
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
Ниже запрос дает мне идентификаторы, для которых предоставленный диапазон дат (даты начала и окончания перекрываются с любой из дат (даты начала и окончания) в моем table_name
select id from table_name where (START_DT_TM >= 'END_DATE_TIME' OR
(END_DT_TM BETWEEN 'START_DATE_TIME' AND 'END_DATE_TIME'))
the simplest
Самый простой способ - использовать хорошо спроектированную специальную библиотеку для работы с датой и временем.
someInterval.overlaps( anotherInterval )
Лучшее в бизнесе - это фреймворк 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 для определения промежутка времени, где начало - инклюзивный, а окончание - эксклюзивный.
Ответ для меня слишком прост, поэтому я создал более общий динамический оператор 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)
У меня была ситуация, когда у нас были даты вместо времени, и даты могли перекрываться только в начале / конце. Пример ниже:
(Зеленый - текущий интервал, синие блоки - допустимые интервалы, красные - перекрывающиеся интервалы).
Я адаптировал ответ Яна Нельсона к следующему решению:
(startB <= startA && endB > startA)
|| (startB >= startA && startB < endA)
Это соответствует всем случаям перекрытия, но игнорирует разрешенные случаи перекрытия.
Простое решение:
compare the two dates:
A = the one with smaller start date, B = the one with bigger start date
if (A.end < B.start)
return false
return true
Вот мое решение в Ява, которое тоже работает с неограниченными интервалами
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_(mat Mathematics)#Terminology
!startA.after(endB) означает startA <= endB, а !endA.before(startB) означает startB <= endA. Это критерии закрытого интервала, а не открытого интервала.
@Henrik true, а другие условия, такие как endB == null и startA == null, проверяют наличие открытого интервала.
endB == null, startA == null, endA == null и startB == null - все критерии для проверки неограниченного интервала, а не открытого интервала. Пример различий между неограниченными и открытыми интервалами: (10, 20) и (20, null) - это два открытых интервала, которые не перекрываются. У последнего есть неограниченный конец. Ваша функция вернет истину, но интервалы не перекрываются, потому что интервалы не включают 20. (для простоты используются числа вместо временных меток)
@Henrik Я понял, я исправил это сейчас.
Математическое решение, данное @Bretana, хорошее, но не учитывает две конкретные детали:
О закрытом или открытом состоянии границ интервала, решение @Bretana действительно для закрытых интервалов
(StartA <= EndB) and (EndA >= StartB)
можно переписать для полуоткрытых интервалов в:
(StartA < EndB) and (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, но могут быть и полуоткрытыми, как и пустые интервалы дат) обрабатываются аналогичным образом.
Это расширение отличный ответ, созданное @ 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 ----]
Перекрытие iff: (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 ----)
Перекрытие 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 правое открытое, 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)
так далее...
Наконец, общее условие перекрытия двух интервалов:
(StartA <? EndB) и (EndA> ? StartB)
где ? превращает строгое неравенство в нестрогое всякий раз, когда выполняется сравнение между двумя включенными конечными точками.
Случаи два, три и четыре имеют одинаковое условие перекрытия, это намеренно?
@ Мари, я только что перечислил несколько случаев (не все)
Это, но так подробно, как Ответ Джонатана Леффлера, было бы тем, что я имел в виду как принятый ответ на вопрос OP.
Если вы предоставляете диапазон дат в качестве входных данных и хотите узнать, перекрывается ли он с существующим диапазоном дат в базе данных, следующие условия могут успешно удовлетворить ваши требования.
Assume you provide a
@StartDateand@EndDatefrom your form input.
условия:
Если @StartDate опережает existingStartDate и отстает от existingEndDate, то мы можем сказать, что @StartDate находится в середине существующего диапазона дат, поэтому мы можем сделать вывод, что он будет перекрываться.
@StartDate >=existing.StartDate And @StartDate <= existing.EndDate)
Если @StartDate отстает от existingStartDate, но @EndDate опережает existingStartDate, мы можем сказать, что он будет перекрываться
(@StartDate <= existing.StartDate And @EndDate >= existing.StartDate)
Если @StartDate отстает от existingStartDate, а @EndDate опережает existingEndDate, мы можем сделать вывод, что указанный диапазон дат поглощает существующий диапазон дат, таким образом перекрываясь
(@StartDate <= existing.StartDate And @EndDate >= existing.EndDate))
Если какое-либо из условий выполняется, указанный вами диапазон дат перекрывается с существующими в базе данных.
Вот код, который творит чудеса:
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));
Где..
Доказательство? Посмотрите этот тест суть кода консоли.
Это работает, но я бы предпочел проверить отсутствие перекрытия, только два сценария
Спасибо, что объяснили это с помощью изображений. Ваш ответ - идеальное решение для этого вопроса.
Краткий ответ с использованием momentjs:
function isOverlapping(startDate1, endDate1, startDate2, endDate2){
return moment(startDate1).isSameOrBefore(endDate2) &&
moment(startDate2).isSameOrBefore(endDate1);
}
ответ основан на приведенных выше ответах, но в сокращенном виде.
Самый простой способ запомнить решение -
.
min(ends)>max(starts)
компактная формула, которая работает для меня
class ValidityRuleRange {
private final Date from;
private final Date to;
...
private boolean isOverlap(ValidityRuleRange vrr) {
int c1 = from.compareTo(vrr.getTo());
int c2 = to.compareTo(vrr.getFrom());
return c1 == 0 || c2 == 0 || c1 + c2 == 0;
}
Я нашел еще один довольно простой подход. Если начальная и конечная дата daterange1 приходится на дату начала daterange2 или начальная и конечная дата daterange1 приходится на конечную дату daterange2, это означает, что они не пересекаются друг с другом.
public boolean doesIntersect(DateRangeModel daterange1, DateRangeModel daterange2) {
return !(
(daterange1.getStartDate().isBefore(daterange2.getStartDate())
&& daterange1.getEndDate().isBefore(daterange2.getStartDate())) ||
(daterange1.getStartDate().isAfter(daterange2.getStartDate())
&& daterange1.getEndDate().isAfter(daterange2.getEndDate())));
}
def if_lives_overlap(s1, s2):
"""
https://stackoverflow.com/a/325964/827391
5 10 15 20
----------------
s1: -----
s2: -----
>>> if_lives_overlap(s1 = {'start': datetime(2020, 1, 5),
... 'end': datetime(2020, 1, 10)},
... s2 = {'start': datetime(2020, 1, 15),
... 'end': datetime(2020, 1, 20)})
False
s1: -----
s2: -----
>>> if_lives_overlap(s1 = {'start': datetime(2020, 1, 15),
... 'end': datetime(2020, 1, 20)},
... s2 = {'start': datetime(2020, 1, 5),
... 'end': datetime(2020, 1, 10)})
False
s1: ----------
s2: ----------
>>> if_lives_overlap(s1 = {'start': datetime(2020, 1, 5),
... 'end': datetime(2020, 1, 15)},
... s2 = {'start': datetime(2020, 1, 10),
... 'end': datetime(2020, 1, 20)})
True
s1: ----------
s2: ----------
>>> if_lives_overlap(s1 = {'start': datetime(2020, 1, 10),
... 'end': datetime(2020, 1, 20)},
... s2 = {'start': datetime(2020, 1, 5),
... 'end': datetime(2020, 1, 15)})
True
s1: ---------------
s2: -----
>>> if_lives_overlap(s1 = {'start': datetime(2020, 1, 5),
... 'end': datetime(2020, 1, 20)},
... s2 = {'start': datetime(2020, 1, 10),
... 'end': datetime(2020, 1, 15)})
True
s1: -----
s2: ---------------
>>> if_lives_overlap(s1 = {'start': datetime(2020, 1, 10),
... 'end': datetime(2020, 1, 15)},
... s2 = {'start': datetime(2020, 1, 5),
... 'end': datetime(2020, 1, 20)})
True
"""
return (s1['start'] <= s2['end']) and (s1['end'] >= s2['start'])
Поскольку было несколько ответов для разных языков и сред, вот один для стандартного ANSI SQL.
В стандартном SQL это так же просто, как
(StartDate1, EndDate1) overlaps (StartDate2, EndDate2)
предполагая, что все четыре столбца являются столбцами DATE или TIMESTAMP. Он возвращает истину, если оба диапазона имеют хотя бы один общий день (при значениях DATE).
(Однако не все СУБД поддерживают это)
В PostgreSQL также легко проверить включение с помощью диапазоны дат
daterange(StartDate1, EndDate1) @> daterange(StartDate2, EndDate2)
приведенное выше возвращает истину, если второй диапазон полностью включен в первый (что отличается от «перекрытий»).
Чрезвычайно похож на stackoverflow.com/questions/306316/…