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

例如,假设我们有由DateTime变量StartDate1到EndDate1和StartDate2到EndDate2表示的范围。


当前回答

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

其他回答

这里是我的简化。假设时间间隔

       | |
     | |

不要重叠,xa<xb和ya<yb可以简化处理。上述条件可以简单检查。然后你会得到

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

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

这里还有一个使用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):执行20次成功(0.003秒/0.004秒)

关于时间关系(或任何其他区间关系)的推理,请考虑Allen的区间代数。它描述了两个区间之间可能存在的13种关系。你可以找到其他参考资料——“艾伦间隔”似乎是一个有效的搜索词。您还可以在Snodgrass的《用SQL开发面向时间的应用程序》(PDF,网址为)、《日期、达文和洛伦兹时间数据与关系模型》(2002年)或《时间与关系理论:关系模型与SQL中的时间数据库》(2014年;实际上是TD&RM的第二版)中找到有关这些操作的信息。


简短的答案是:给定两个日期间隔A和B,其中包含.start和.end以及约束.start<=.end,则两个间隔重叠,如果:

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

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


ErikE评论:

如果你数点有趣的事情,你只能得到13。。。当我疯狂地计算时,我可以得到“两个区间可以有15种可能的关系”。通过合理的计算,我只能得到6种关系,如果你不在乎A还是B先出现,我只能获得3种关系(不相交,部分相交,一种完全在另一种内)。15是这样的:[之前:之前,开始,内部,结束,之后],[开始:开始,内部、结束,之后】,[内部:内部,结束、之后],[结束:结束,之后,],[之后:之后]。

我认为你不能计算“之前:之前”和“之后:之后”这两个条目。如果你将某些关系与它们的逆关系等同起来,我可以看到7个条目(参见参考维基百科URL中的图表;它有7个条目,其中6个条目具有不同的逆关系,而equals没有不同的逆)。三个是否合理取决于你的要求。

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

我认为,在以下情况下,两个范围重叠就足够了:

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

通过简单地确保一个范围在另一个范围之前或同时开始,可以大大简化基于范围之间的关系来检查大量条件的所有解决方案。如果需要,您可以在前面交换范围。

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

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

例如(假设两端都包含),范围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

其中每条线具有:

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

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

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