确定两个日期范围是否重叠

Determine Whether Two Date Ranges Overlap

提问人:Ian Nelson 提问时间:11/28/2008 最后编辑:David FaberIan Nelson 更新时间:9/24/2023 访问量:641726

问:

给定两个日期范围,确定两个日期范围是否重叠的最简单或最有效的方法是什么?

例如,假设我们有由 DateTime 变量 to to 表示的范围。StartDate1EndDate1StartDate2EndDate2

日期时间 数学 与语言无关

评论

3赞 Charles Bretana 11/28/2008
与 stackoverflow.com/questions/306316/ 极为相似......
1赞 Ian Nelson 11/28/2008
@CharlesBretana谢谢你,你是对的——这几乎就像我问题的二维版本!
2赞 Steven A. Lowe 11/29/2008
与 stackoverflow.com/questions/117962/ 非常相似...
2赞 Colonel Panic 10/13/2012
将“两个日期范围相交”的情况划分为案例(有两个),然后针对每个案例进行测试。
1赞 Nihat Erim inceoğlu 2/11/2020
你好。。A:StartDate1,B:EndDate1,C:StartDate2,D:EndDate2。如果 B < C 或 A > D,那么我们假设它们没有相交。因此,我们可以很容易地用“isintersects = not (B < C or A > D)”来测试,这将始终告诉我们它是否相交。

答:

540赞 Ian Nelson 11/28/2008 #1

我认为,如果出现以下情况,可以说这两个范围重叠就足够了:

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

评论

110赞 A.L 12/2/2013
我发现符号更容易理解,Range1 在测试中总是在左边。(StartDate1 <= EndDate2) and (EndDate1 >= StartDate2)
18赞 Richard Schneider 4/9/2014
这假定开始日期和结束日期包括在内。更改为 if start 是 inclusive 和 end 是 exclusive。<=<
0赞 Shehan Simen 2/6/2015
即使 startDate2 在 startDate1 之前,这也将非常有效。因此,无需假设 startDate1 早于 startDate2。
3赞 apc 8/4/2016
我发现 (StartDate1 <= EndDate2) 和 (StartDate2 <= EndDate1) 表示法(根据答案)比其他答案更容易理解。
0赞 Jay Patel 1/10/2020
我怎样才能使开始排他性和结束包容性?
10赞 Bob 11/28/2008 #2

我会做的

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

评论

0赞 Patrick Huizinga 11/28/2008
我更喜欢(左<值和&值<右)||(右<值和值<左)。
0赞 sshow 11/10/2009
谢谢你。让事情在我的脑海中变得更容易。
1赞 ErikE 3/9/2010
当你只需要检查两个条件时,为什么要检查四个条件?失败。
3赞 ErikE 3/11/2010
啊,很抱歉,我现在看到您允许范围以相反的顺序(StartDateX > EndDateX)。奇怪。无论如何,如果 StartDate1 小于 StartDate2 且 EndDate1 大于 EndDate2,该怎么办?您提供的代码不会检测到此重叠情况。
3赞 user158037 8/11/2015
如果 Date1 包含整个 Date2,这不会返回 false 吗?然后 StartDate1 在 StartDate2 之前,EndDate1 在 EndDate2 之后
1赞 AlexDrenea 11/28/2008 #3

在我看来,最简单的方法是比较 EndDate1 是否在 StartDate2 之前,如果 EndDate2 在 StartDate1 之前。

当然,如果您正在考虑 StartDate 始终在 EndDate 之前的时间间隔。

2946赞 Charles Bretana 11/28/2008 #4

(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-startAendA - startBendB-startAendB - startB }

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


注3.多亏了@tomosius,一个较短的版本是这样写的:

这实际上是较长实现的语法快捷方式,其中包括额外的检查,以验证开始日期是否在 endDates 或之前。从上面推导出来:
DateRangesOverlap = max(start1, start2) < min(end1, end2)

如果开始日期和结束日期可能不按顺序排列,即,如果可能为 或 ,那么您还必须检查它们是否按顺序排列,因此这意味着您必须添加两个额外的有效性规则: 或者: 或者,
或者:

startA > endAstartB > 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)

但是要实现 and,您必须编写代码(使用 C 三元为简洁起见):Min()Max()
((StartA > StartB) ? StartA : StartB) <= ((EndA < EndB) ? EndA : EndB)

评论

59赞 Devy 7/28/2015
这是一个基于以下两个假设的简化逻辑:1) StartA < EndA;2) 开始 B <结束 B。这似乎是显而易见的,但实际上数据可能来自未知来源,例如用户输入或未经清理的数据库。请记住,您需要验证输入数据以确保这两个假设为真,然后才能使用此简化的逻辑,否则一切都会分崩离析。从我自己的经验中吸取的教训;)
15赞 Charles Bretana 8/5/2015
@Devy,你是对的。除了如果 startA = endA,它也会起作用。事实上,这正是这些词和意思。如果有两个变量分别名为 Top 和 Bottom、East 和 West、HighValue 和 LoValue,则可以假设或暗示某物或某人在某处应该确保其中一对值不存储在相反的变量中。-只有两对中的一对,因为如果切换两对值,它也会起作用。StartEnd
3赞 Baodad 12/16/2015
@rashid,这里有一篇文章可能会给你一些关于如何获得实际重叠量的提示。
29赞 Kevin Robatel 2/26/2016
您可以轻松地添加 nullable 和 (语义为 “null start” = “From the beginning of the time” 和 “null end” = “To the end of the time”),如下所示:startend(startA === null || endB === null || startA <= endB) && (endA === null || startB === null || endA >= startB)
1赞 habib 11/11/2019
silentmatt.com/rectangle-intersection使用此工具通过拖放轻松检查不同的案例并验证您的解决方案。
104赞 Jonathan Leffler 11/30/2008 #5

对于时间关系(或任何其他区间关系,来吧)的推理,请考虑艾伦的区间代数。它描述了两个区间之间可以具有的 13 种可能关系。你可以找到其他参考资料——“Allen Interval”似乎是一个有效的搜索词。您还可以在 Snodgrass 的 Developing Time-Oriented Applications in SQL(PDF 可在 URL 上在线获得)以及 Date, Darwen and Lorentzos Temporal Data and the Relational Model (2002) 或 Time and Relational Theory: Temporal Databases in the Relational Model and SQL (2014;实际上是 TD&RM 的第二版)中找到有关这些操作的信息。


简短的答案是:给定两个日期间隔,并且有分量和约束,则两个间隔重叠,如果:AB.start.end.start <= .end

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

您可以调整 vs 和 vs 的使用,以满足您对重叠程度的要求。>=><=<


ErikE 评论:

如果你把有趣的事情算在内,你只能得到 13 分......当我疯狂使用它时,我可以得到“两个区间可以有的 15 种可能的关系”。通过合理的计算,我只得到六个,如果你不关心是 A 还是 B 先来,我只得到三个(没有相交,部分相交,一个完全在另一个内部)。15 是这样的:[before:before、start、within、end、after]、[start:start、within、end、after]、[within:within、end、after]、[end:end、after]、[after:after]。

我认为你不能计算“before:before”和“after:after”这两个条目。如果你把一些关系等同于它们的逆,我可以看到 7 个条目(参见引用的维基百科 URL 中的图表;它有 7 个条目,其中 6 个具有不同的逆函数,等号没有明显的逆函数)。而三是否合理取决于你的要求。

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

评论

1赞 ErikE 3/9/2010
如果你把有趣的事情算在内,你只能得到 13 分......当我疯狂使用它时,我可以得到“两个区间可以有的 15 种可能的关系”。通过合理的计算,我只得到六个,如果你不关心是 A 还是 B 先来,我只得到三个(没有相交,部分相交,一个完全在另一个内部)。15 是这样的:[before:before、start、within、end、after]、[start:start、within、end、after]、[within:within、end、after]、[end:end、after]、[after:after]。
0赞 Jonathan Leffler 3/9/2010
@Emtucifor:我认为你不能计算“before:before”和“after:after”这两个条目。
0赞 ErikE 3/11/2010
重新更新:B1 到 A 是 before:before,B13 到 A 是 after:after。您漂亮的图表缺少 B5 B6 之间的 start:start 和 B11 和 B12 之间的 end:end。如果位于端点上很重要,那么您必须计算它,因此最终计数是 15,而不是 13。我不认为端点的事情很重要,所以我个人数了一下 [before: before, within, after], [within: within, after], [after:after] 等于 6。我认为整个端点问题只是混淆了边界是包容性的还是排他性的。端点的排他性不会改变核心关系!
0赞 ErikE 3/11/2010
也就是说,在我的方案中,这些是等价的:(B2、B3、B4)、(B6、B7、B9、B10)、(B8、B11、B12)。我意识到 B7 暗示了两个范围完全重合的信息。但我不相信这些附加信息应该是基本交叉关系的一部分。例如,当两个间隔的长度完全相同时,即使不重合甚至重叠,这是否应该被视为另一种“关系”?我说不,并且看到这个额外的方面是使 B7 与 B6 区别的唯一因素,那么我认为将端点作为单独的情况会使事情变得不一致。
0赞 Jonathan Leffler 3/11/2010
@Emtucifor:好的 - 我明白为什么我错误地将“before:before”和“after:after”识别为条目;但是,我无法想象“start:start”和“end:end”条目应该是什么样子。由于您无法编辑我的图表,您能否通过电子邮件向我发送(请参阅我的个人资料)显示“开始:开始”和“结束:结束”关系的图表的修改副本?我对你们的分组没有重大问题。
-1赞 staceyw 4/13/2009 #6

这是一个通用方法,可以在本地使用。

    // 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
         */
    }
23赞 paxdiablo 8/6/2010 #7

通过简单地确保一个范围在另一个范围之前或同时开始,可以大大简化所有基于范围相互关联的位置检查多种条件的解决方案。如有必要,您可以通过预先交换范围来执行此操作。

然后,如果第二个范围开始为:

  • 小于或等于第一个范围结束(如果范围包括在内,则包含开始和结束时间);或
  • 小于(如果范围包括开始和不包括结束)。

例如(假设两端都包含),范围 2 只有四种可能性,其中一种是非重叠的(范围末尾的表示范围的结束位置无关紧要):>

|-----|        range 1, lines below are all range 2.
|-->  :        overlap.
 |--> :        overlap.
      |--->    overlap (no overlap in exclusive-of-end case).
       |--->   no overlap.

第二个范围的终结点完全不会影响结果。因此,在伪代码中,您可以执行类似操作(假设所有范围都保持 - 如果没有,您可能也可以进行顶部交换):s <= e

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

或者,单级限制递归选项:

def overlaps(r1, r2):
    if r1.s <= r2.s:
        return r2.s <= r1.e
    return overlaps(r2, r1)

如果范围在末尾是互斥的,则只需在返回的表达式中替换为(在两个代码片段中)。<=<

这极大地限制了您必须进行的检查次数,因为您可以通过确保第一个范围永远不会在第二个范围之后开始,提前删除一半的问题空间。


而且,由于“代码会说话”,这里有一些 Python 代码展示了这一点,并有相当多的测试用例。一、上课:InclusiveRange

class InclusiveRange:
    """InclusiveRange class to represent a lower and upper bound."""

    def __init__(self, start, end):
        """Initialisation, ensures start <= end.
        Args:
            start: The start of the range.
            end: The end of the range.
        """
        self.start = min(start, end)
        self.end = max(start, end)

    def __repr__(self):
        """Return representation for f-string."""
        return f"({self.start}, {self.end})"

    def overlaps(self, other):
        """True if range overlaps with another.
        Args:
            other: The other InclusiveRange to check against.
        """

        # Very limited recursion to ensure start of first range
        # isn't after start of second.

        if self.start > other.start:
            return other.overlaps(self)

        # Greatly simplified check for overlap.

        return other.start <= self.end

然后是一个测试用例处理程序,允许我们很好地呈现单个测试用例的结果:

def test_case(range1, range2):
    """Single test case checker."""

    # Get low and high value for "graphic" output.

    low = min(range1.start, range2.start)
    high = max(range1.end, range2.end)

    # Output ranges and graphic.

    print(f"r1={range1} r2={range2}: ", end="")
    for val in range(low, high + 1):
        is_in_first = range1.start <= val <= range1.end
        is_in_second = range2.start <= val <= range2.end

        if is_in_first and is_in_second:
            print("|", end="")
        elif is_in_first:
            print("'", end="")
        elif is_in_second:
            print(",", end="")
        else:
            print(" ", end="")

    # Finally, output result of overlap check.

    print(f" - {range1.overlaps(range2)}\n")

最后,如果需要,您可以添加自己的测试用例:

# Various test cases, add others if you doubt the correctness.

test_case(InclusiveRange(0, 1), InclusiveRange(8, 9))
test_case(InclusiveRange(0, 4), InclusiveRange(5, 9))
test_case(InclusiveRange(0, 4), InclusiveRange(4, 9))
test_case(InclusiveRange(0, 7), InclusiveRange(2, 9))
test_case(InclusiveRange(0, 4), InclusiveRange(0, 9))
test_case(InclusiveRange(0, 9), InclusiveRange(0, 9))
test_case(InclusiveRange(0, 9), InclusiveRange(4, 5))

test_case(InclusiveRange(8, 9), InclusiveRange(0, 1))
test_case(InclusiveRange(5, 9), InclusiveRange(0, 4))
test_case(InclusiveRange(4, 9), InclusiveRange(0, 4))
test_case(InclusiveRange(2, 9), InclusiveRange(0, 7))
test_case(InclusiveRange(0, 9), InclusiveRange(0, 4))
test_case(InclusiveRange(0, 9), InclusiveRange(0, 9))
test_case(InclusiveRange(4, 5), InclusiveRange(0, 9))

产生输出的运行:

r1=(0, 1) r2=(8, 9): ''      ,, - False
r1=(0, 4) r2=(5, 9): ''''',,,,, - False
r1=(0, 4) r2=(4, 9): ''''|,,,,, - True
r1=(0, 7) r2=(2, 9): ''||||||,, - True
r1=(0, 4) r2=(0, 9): |||||,,,,, - True
r1=(0, 9) r2=(0, 9): |||||||||| - True
r1=(0, 9) r2=(4, 5): ''''||'''' - True
r1=(8, 9) r2=(0, 1): ,,      '' - False
r1=(5, 9) r2=(0, 4): ,,,,,''''' - False
r1=(4, 9) r2=(0, 4): ,,,,|''''' - True
r1=(2, 9) r2=(0, 7): ,,||||||'' - True
r1=(0, 9) r2=(0, 4): |||||''''' - True
r1=(0, 9) r2=(0, 9): |||||||||| - True
r1=(4, 5) r2=(0, 9): ,,,,||,,,, - True

其中每行有:

  • 被评估的两个范围;
  • “范围空间”(从最低开始到最高结束)的图形表示,其中每个字符都是该“范围空间”中的一个值:
    • '仅表示第一个范围内的值;
    • ,仅表示第二个范围内的值;
    • |表示两个范围内的值;和
    • 表示两个范围内的值。
  • 重叠检查的结果。

您可以非常清楚地看到,只有当两个范围内至少有一个值(即字符)时,才会在重叠检查中获得 true。所有其他情况都是错误的。|

如果要添加更多测试用例,请随意使用任何其他值。

评论

2赞 Brian Gideon 8/6/2010
+1 提及包含/排除问题。当我有时间时,我打算自己想出一个答案,但现在没有必要了。问题是你几乎从不允许开始和结束同时具有包容性。在我的行业中,通常的做法是将开始视为排他性的,将结束视为包容性的,但只要您保持一致,无论哪种方式都可以。这是迄今为止关于这个问题的第一个完全正确的答案......国际 海事 组织。
169赞 user687474 4/9/2011 #8

本文 Time Period Library for .NET 通过枚举 PeriodRelation 描述了两个时间段的关系:

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

enter image description here

评论

0赞 Meno Hochschild 4/10/2017
很好,我也在 Java 中实现了 Allens 区间代数,请参阅 IntervalRelation 和 IsoInterval 的 API
-1赞 Syam 1/23/2012 #9
if (StartDate1 > StartDate2) swap(StartDate, EndDate);

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

评论

3赞 0xF 10/22/2013
第二行就足够了。第一行的目的是什么?它指的是什么 StartDate 和 EndDate?
40赞 Vitalii Fedorenko 9/6/2012 #10

如果还应计算重叠本身,则可以使用以下公式:

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

评论

0赞 NSjonas 9/10/2016
那么这两个事件共享的时间量是重叠的吗?这是否适用于事件重叠的所有不同方式?
2赞 Kakshil Shah 4/15/2021
当完全重叠时,这不起作用。例如,范围 1:1-7 范围 2:4-5
0赞 Colonel Panic 10/13/2012 #11

将问题拆分为多个案例,然后处理每个案例

“两个日期范围相交”的情况由两种情况涵盖 - 第一个日期范围从第二个日期范围开始,或者第二个日期范围从第一个日期范围开始。

5赞 Ignacio Pascual 9/13/2013 #12

这是我使用 moment.js 的 JavaScript 解决方案:

// 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;
9赞 on_ 11/5/2013 #13

此处发布的解决方案不适用于所有重叠范围......

----------------------|-------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.
) 
0赞 Ilya 3/25/2014 #14

使用原生 PHP 类:

//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);
2赞 jack 3/28/2014 #15

如果您使用的日期范围尚未结束(仍在进行中),例如未设置 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,则没有重叠!

3赞 Prasenjit Banerjee 7/5/2014 #16

在 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
0赞 mmarjeh 7/19/2014 #17
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 7/19/2014
介意补充一些解释吗?
0赞 Fez Vrasta 11/27/2014 #18

这是我的解决方案,当值不重叠时,它返回 true:

X 开始 1 Y 结束 1

A 开始 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
1赞 Shehan Simen 2/6/2015 #19

使用 Java util。日期,这是我所做的:

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;
}
20赞 yankee 3/13/2015 #20

这是另一个使用 JavaScript 的解决方案。我的解决方案的特点:

  • 将 null 值处理为无穷大
  • 假定下限是非独占的,上限是非独占的。
  • 附带一堆测试

测试基于整数,但由于 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):执行了 20 次成功中的 20 次(0.003 秒 / 0.004 秒)

1赞 mahatmanich 10/25/2015 #21

对于红宝石,我还发现了这个:

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赞 Shravan Ramamurthy 1/20/2016 #22

下面的查询为我提供了提供的日期范围(开始和结束日期与我table_name中的任何日期(开始和结束日期)重叠的 ID

select id from table_name where (START_DT_TM >= 'END_DATE_TIME'  OR   
(END_DT_TM BETWEEN 'START_DATE_TIME' AND 'END_DATE_TIME'))
3赞 Basil Bourque 11/5/2016 #23

最简单的

最简单的方法是使用精心设计的专用库进行日期时间工作。

someInterval.overlaps( anotherInterval )

java.time 和 ThreeTen-Extra

业内最好的是 Java 8 及更高版本中内置的 java.time 框架。除此之外,还有 ThreeTen-Extra 项目,它用额外的类来补充 java.time,特别是我们在这里需要的 Interval 类。

至于这个问题的标签,这两个项目的源代码都可以用其他语言使用(注意他们的许可证)。language-agnostic

Interval

org.threeten.extra.Interval 类很方便,但需要日期时间时刻(对象)而不是仅日期值。因此,我们继续使用 UTC 中一天中的第一个时刻来表示日期。java.time.Instant

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

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 与另一个 IntervalInstant 进行比较

所有这些都使用这种方法来定义一个时间跨度,其中开始是包容性的,结束是排他性的。Half-Open

2赞 Tom McDonough 12/10/2016 #24

答案对我来说太简单了,所以我创建了一个更通用的动态 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)
3赞 Gus 1/18/2017 #25

我遇到过这样一种情况,我们有日期而不是日期时间,并且日期只能在开始/结束时重叠。示例如下:

enter image description here

(绿色是当前间隔,蓝色块是有效间隔,红色块是重叠间隔)。

我将 Ian Nelson 的回答改编为以下解决方案:

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

这将匹配所有重叠情况,但忽略允许的重叠情况。

-1赞 sorry_I_wont 1/20/2017 #26

简单的解决方案:

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
10赞 Khaled.K 2/21/2017 #27

这是我用 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));
}

评论

0赞 Henrik 10/4/2017
我想你的意思是无限的末端而不是开放的间隔。
0赞 Khaled.K 10/4/2017
@Henrik这两个术语都 en.wikipedia.org/wiki/Interval_(数学)起作用 #Terminology
0赞 Henrik 10/4/2017
!startA.after(endB)表示 startA <= endB,表示 startB <= endA。这些是闭合区间而不是开放区间的标准。!endA.before(startB)
0赞 Khaled.K 10/4/2017
@Henrik true,以及其他条件,例如 和 检查打开间隔。endB == nullstartA == null
1赞 Henrik 10/4/2017
endB == null、 和 都是检查无界间隔而不是开放间隔的条件。无界间隔和开放间隔之间的差异示例:(10, 20) 和 (20, null) 是两个不重叠的开放间隔。最后一个确实有一个无限的结局。函数将返回 true,但间隔不重叠,因为间隔不包括 20。(为简单起见,使用数字而不是时间戳)startA == nullendA == nullstartB == null
3赞 Meno Hochschild 4/10/2017 #28

@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 中默认关闭,但也可以半打开,如空日期间隔)以类似的方式处理。

4赞 user2314737 6/15/2017 #29

这是对@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)

其中 🞐 每当在两个包含的端点之间进行比较时,就会将严格的不等式转换为非严格的不等式。

评论

0赞 Marie 11/22/2017
案例二、案例三、案例四具有相同的重叠条件,这是故意的吗?
0赞 user2314737 11/23/2017
@Marie,我只是列出了几个案例(不是全部)
1赞 AL-zami 10/5/2017 #30

如果您提供日期范围作为输入,并希望确定它是否与数据库中的现有日期范围重叠,则以下条件可以成功满足您的需求

假设您提供了表单输入的 and。@StartDate@EndDate

条件是:

如果在前面和后面,那么我们可以说它处于现有日期范围的中间,因此我们可以得出结论,它将重叠@StartDateexistingStartDateexistingEndDate@StartDate

@StartDate >=existing.StartDate And @StartDate <= existing.EndDate) 

如果落后但领先,我们可以说它会重叠@StartDateexistingStartDate@EndDateexistingStartDate

 (@StartDate <= existing.StartDate And @EndDate >= existing.StartDate)

如果 落后于 And 领先,我们可以得出结论,提供的日期范围吞噬了现有的日期范围,因此重叠@StartDateexistingStartDate@EndDateexistingEndDate

 (@StartDate <= existing.StartDate And @EndDate >= existing.EndDate))

如果任一条件成立,则您提供的日期范围将与数据库中的现有日期范围重叠。

17赞 sandeep talabathula 10/29/2017 #31

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 -> 1结束
  • C -> 2Start
  • D -> 2结束

证明?查看此测试控制台代码要点

评论

0赞 John Albert 10/25/2018
这可行,但我更愿意测试不重叠,只有两种情况
1赞 Manuel Romeiro 6/1/2021
因为根据定义,A 总是 <= B,C 总是 <= D,所以你可以简化为 (A <= D) && (C <= B)
4赞 Nitin Jadhav 7/19/2018 #32

使用 momentjs 的简短回答

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

答案是基于上述答案,但被缩短了。

评论

0赞 bryanjclark 9/24/2023
如果这两个日期范围相邻,则会将它们报告为重叠。(换句话说,如果 dateRange1 在 dateRange2 开始的时候结束,它们不会重叠 - 但此函数会说它们重叠。
13赞 Radacina 1/9/2019 #33

记住解决方案的一个简单方法是
min(ends)>max(starts)

-1赞 Roberto77 4/12/2019 #34

适合我的紧凑配方

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;
    }
0赞 Bilal Ahmed Yaseen 7/5/2020 #35

我找到了另一种非常简单的方法。如果 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())));
}
6赞 user330315 2/8/2021 #36

由于针对不同的语言和环境有几种答案,因此这里有一个针对标准 ANSI SQL 的答案。

在标准 SQL 中,它就像

(StartDate1, EndDate1) overlaps (StartDate2, EndDate2)

假设所有四列都是 OR 列。如果两个范围至少有一天的共同点(假设值),则返回 trueDATETIMESTAMPDATE

(但是,并非所有 DBMS 产品都支持此功能)


在 PostgreSQL 中,使用日期范围测试是否包含也很容易

daterange(StartDate1, EndDate1) @> daterange(StartDate2, EndDate2)

如果第二个范围完全包含在第一个范围中,则上述返回 true(这与“重叠”不同)

0赞 John 6/16/2022 #37

只要有简单的解决方案,仅仅使用额外的包来做简单的事情可能效率不高。但是,如果您已经在项目中使用了,则有一个调用的方法可以执行相同的操作。date-fnsareIntervalsOverlapping

语法:

areIntervalsOverlapping(intervalLeft, intervalRight, [options])

例:

// For overlapping time intervals:
areIntervalsOverlapping(
  { start: new Date(2014, 0, 10), end: new Date(2014, 0, 20) },
  { start: new Date(2014, 0, 17), end: new Date(2014, 0, 21) }
)
//=> true
0赞 Umer 9/27/2022 #38

为了涵盖 PHP/Laravel Carbon Package 中的所有重叠情况,您可以按如下方式扩展 Charles' Answer 中的逻辑。

if ( ($startTime1->between($startTime2, $endTime2, true) || $endTime1->between($startTime2, $endTime2, true)) || (($startTime1 <= $endTime2) && ($endTime2 <= $endTime1)) ){
//Complete Overlap, Partial Left Overlap, Partial Right Overlap. 
}

这将检查 StartTime1 是否在 (StartTime2-EndTime2) 的范围之间,或者 EndTime1 的范围是否在 (StartTime2-EndTime2) 的范围之间。

该部分的其余部分完全重叠,如其他答案中所述。

0赞 Jay-Pi 12/7/2022 #39

这是我的简化。 假设间隔

       | |
     | |

不要重叠,XA < XB,YA < YB 以简化处理。 上述条件可以简单检查。 然后你最终会得到

      ya       yb
      |--------|
xa |--|xb
   |----|
         |----|
ya > xa => return ya - xa < xb - xa
else    => return xa < yb

您可能注意到,第二个区间仅在 xa >= ya 的情况下才相关。

0赞 bryanjclark 9/24/2023 #40

这是一个 TypeScript 实现,我觉得它很容易阅读/推理。我已经针对它编写了一些单元测试,它的行为符合我的预期。

type DateRange = {
  start: Date;
  end: Date;
};

const doDateRangesOverlap = (a: DateRange, b: DateRange) => {
  // First, let's figure out which date range starts earlier.
  const [earlierStart, laterStart] = a.start < b.start ? [a, b] : [b, a];

  // Now we can just do a simple check - does the earlier date range
  // end after the later date range has begun?
  return earlierStart.end > laterStart.start;
};