例如,假设我们有由DateTime变量StartDate1到EndDate1和StartDate2到EndDate2表示的范围。
(StartA <= EndB) and (EndA >= StartB)
证明:
假设条件A表示时间段A完全在时间段B之后
_ |---- DateRange A ------|
|---Date Range B -----| _
如果StartA > EndB
,则为真。
令条件B表示日期范围A完全在日期范围B之前。
|---- DateRange A -----| _
_ |---Date Range B ----|
(当且仅当 EndA < StartB
时为真)
如果既不满足 A 也不满足 B,则存在重叠 -(如果一个范围不完全在另一个范围之后,也不完全在另一个范围之前,则它们必须重叠)。
现在,德摩根定律(De Morgan's laws) 之一是:
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 三元运算符使其更简洁):
((StartA > StartB) ? StartA : StartB) <= ((EndA < EndB) ? EndA : EndB)
start
和 end
参数(其含义为“空起始”等价于“从时间开始”,而“空结束”等价于“到时间结束”),方法如下:(startA === null || endB === null || startA <= endB) && (endA === null || startB === null || endA >= startB)
。 - Kevin Robatel我相信只需要说两个范围重叠当:
(StartDate1 <= EndDate2) and (StartDate2 <= EndDate1)
(StartDate1 <= EndDate2) and (EndDate1 >= StartDate2)
这种表示法更易于理解,测试中 Range1 始终在左侧。 - A.L// ------------------------------------------------------------------------
public enum PeriodRelation
{
After,
StartTouching,
StartInside,
InsideStartTouching,
EnclosingStartTouching,
Enclosing,
EnclosingEndTouching,
ExactMatch,
Inside,
InsideEndTouching,
EndInside,
EndTouching,
Before,
} // enum PeriodRelation
为了推理时间关系(或任何其他间隔关系),可以考虑Allen's Interval Algebra。它描述了两个间隔相互之间可能存在的13种关系。你可以找到其他参考资料-“Allen Interval”似乎是一个有效的搜索词。您还可以在Snodgrass的Developing Time-Oriented Applications in SQL(可在URL上在线获取PDF)以及Date,Darwen和Lorentzos的Temporal Data and the Relational Model(2002年)或Time and Relational Theory: Temporal Databases in the Relational Model and SQL(2014年;实际上是TD&RM的第二版)中了解有关这些操作的信息。
简短的答案是:给定两个日期区间A
和B
,具有组件.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'这两项。如果您将某些关系与它们的反向关系等同起来(请参见参考Wikipedia URL中的图表),则可以看到7个条目;其中6个有不同的反向关系,而相等的没有单独的反向关系。至于三是否合理取决于您的要求。
----------------------|-------A-------|----------------------
|----B1----|
|----B2----|
|----B3----|
|----------B4----------|
|----------------B5----------------|
|----B6----|
----------------------|-------A-------|----------------------
|------B7-------|
|----------B8-----------|
|----B9----|
|----B10-----|
|--------B11--------|
|----B12----|
|----B13----|
----------------------|-------A-------|----------------------
如果需要计算重叠部分,您可以使用以下公式:
overlap = max(0, min(EndDate1, EndDate2) - max(StartDate1, StartDate2))
if (overlap > 0) {
...
}
>
表示范围结束位置不影响)。|-----| 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)
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。其他情况均为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):共执行了20项测试,全部通过,用时0.003秒/总计0.004秒。
这里是实现魔法的代码:
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));
在哪里...
证明?请查看此测试控制台代码要点。
记住解决方法的简单方式是min(ends)>max(starts)
这是我用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));
}
!startA.after(endB)
表示 startA <= endB
,而 !endA.before(startB)
表示 startB <= endA
。这些是闭区间的标准,而不是开区间。 - HenrikendB == null
和 startA == null
则是用来检查开放区间的。 - Khaled.KendB == null
, startA == null
, endA == null
和 startB == null
都是检查无界区间而不是开放区间的标准。例如,(10, 20) 和 (20, null) 是两个不重叠的开放区间。后者具有无限的结尾。您的函数将返回 true,但是这些区间不重叠,因为这些区间不包括 20。 (为简单起见使用数字而不是时间戳) - Henrik