给定两个日期范围,确定这两个日期范围是否重叠的最简单或最有效的方法是什么?
举个例子,假设我们有由DateTime变量StartDate1
到EndDate1
表示的范围,和StartDate2
到EndDate2
。
(StartA <= EndB)和(EndA >= StartB)。
证明:
让ConditionA意味着DateRange A完全在DateRange B之后
` |---- DateRange A ------|
|---日期范围B -----| ``。
(如果 "StartA > EndB "为真)
让ConditionB表示DateRange A完全在DateRange B之前
|---- DateRange A -----| _ |---日期范围B ----|
(如果EndA < StartB
为真)
那么如果A和B都不是真的,就存在重叠 -
(如果一个范围既不完全在另一个范围之后。
也不完全在另一个之前。
那么它们一定是重叠的)。)
现在,德-摩根定律中的一条说:。
非(A或B)<=>"非A和非B
这可以翻译为 (StartA <= EndB)和(EndA >= StartB)
。
注意:这包括边缘完全重叠的条件。 如果你希望排除这种情况。
将>=
运算符改为>
,<=
改为<
。
注意2. 感谢@Baodad,见这个博客,实际的重叠是最小的。
{ endA-startA
, endA - startB
, endB-startA
, endB - startB
}
(StartA <= EndB)和(EndA >= StartB)
。
(StartA <= EndB)和(StartB <= EndA)
。
注3. 感谢@tomosius,一个更简短的版本是::
DateRangesOverlap = max(start1, start2) < min(end1, end2)
。
这实际上是一个较长实现的语法快捷方式,其中包括额外的检查,以验证开始日期是否在endDates上或之前。 从上面推导出这一点。
如果开始和结束日期可以不按顺序,也就是说,如果有可能startA > endA
或者startB > endB
,那么你还必须检查它们是否按顺序,所以这意味着你必须增加两条额外的有效性规则。
(StartA <= EndB)和(StartB <= EndA)以及(StartA <= EndA)和(StartB <= EndB)'。 或。
(StartA <= EndB)和(StartA <= EndA)和(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)
。
本文[Time Period Library for .NET][1]通过枚举PeriodRelation来描述两个时间段的关系。
// ------------------------------------------------------------------------
public enum PeriodRelation
{
After,
StartTouching,
StartInside,
InsideStartTouching,
EnclosingStartTouching,
Enclosing,
EnclosingEndTouching,
ExactMatch,
Inside,
InsideEndTouching,
EndInside,
EndTouching,
Before,
} // enum PeriodRelation
![在此输入图片描述][2]
[1]: http://www.codeproject.com/KB/datetime/TimePeriod.aspx [2]: http://i.stack.imgur.com/0c6q0.png
对于时间关系的推理(或任何其他区间关系,来),可以考虑Allen's Interval Algebra。 它描述了两个区间相互之间可能存在的13种关系。 你可以找到其他参考资料--"Allen Interval" 似乎是一个有效的搜索术语。 你也可以在Snodgrass'的Developing Time-Oriented Applications in SQL中找到关于这些操作的信息(PDF可在URL中在线获取),以及在Date、Darwen和LorentzosTemporal Data and the Relational Model(2002)或Time and Relational Theory: 关系模型和SQL中的时态数据库(2014年)。 实际上是TD&RM的第二版)。)
<hr>。
简短(就是)的答案是:
给定两个日期区间A
和B
,成分为.start
和.end
,约束为.start <=.end
,那么两个区间重叠的情况是:{{5430068}}。
A.end >= B.start AND A.start <= B.end
你可以调整">="与"> "以及"<="与"< "的使用,以满足你对重叠程度的要求。
ErikE评论。
>.ErikE评论说:
我认为不能把'before:before' 和'after:after'。 如果你把一些关系和它们的反义等同起来,我可以看到7个条目(见参考的维基百科网址中的图。 它有 7 个条目,其中 6 个有不同的逆,等价没有明显的逆)。) 而三个是否合理,取决于你的要求。
----------------------|-------A-------|----------------------
|----B1----|
|----B2----|
|----B3----|
|----------B4----------|
|----------------B5----------------|
|----B6----|
----------------------|-------A-------|----------------------
|------B7-------|
|----------B8-----------|
|----B9----|
|----B10-----|
|--------B11--------|
|----B12----|
|----B13----|
----------------------|-------A-------|----------------------
[1]: http://en.wikipedia.org/wiki/Allen%27s_Interval_Algebra [2]: http://www.cs.arizona.edu/~rts/publications.html
所有根据范围相对于彼此的位置来检查众多条件的解决方案,都可以通过_只需确保一个特定的范围较早开始而大大简化!_你确保第一个范围较早开始(或在同一时间),如果必要的话,你可以在前面交换范围。
然后,如果另一个范围的起始时间小于或等于第一个范围的结束时间(如果范围是包含的,包含起始时间和结束时间)或小于(如果范围是包含起始时间和不包含结束时间),你可以检测到重叠。
假设两端都包含,那么只有四种可能,其中一种是非重叠的。
|----------------------| 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之后开始,提前消除了一半的问题空间。
这里又是一个使用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个SUCCESS中的20个(0.003秒/0.004秒)
我想做的是
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);
}
这就是施展魔法的代码。
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));
哪儿。
证明? 看看这个测试[控制台代码要点][2]。
1:
[2]: https://gist.github.com/sandeeptalabathula/3b469065251eb1992dd92b168fea0b61
这是我在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));
}
这里发的解决方法并不是所有的重叠范围都能用...
----------------------|-------A-------|---------------------- |----B1----| |----B2----| |----B3----| |----------B4----------| |----------------B5----------------| |----B6----| ----------------------|-------A-------|---------------------- |------B7-------| |----------B8-----------| |----B9----| |----B10-----| |--------B11--------| |----B12----| |----B13----| ----------------------|-------A-------|----------------------
我的工作方案是。
AND ( ('start_date' BETWEEN STARTDATE AND ENDDATE)--满足内部日期和外部结束日期的要求。 或 ('end_date'。 (between STARTDATE and ENDDATE) --满足内部日期和外部开始日期的要求。 或 (STARTDATE BETWEEN 'start_date'。 )
这是我用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;
最简单的
最简单的方法是使用一个设计良好的专用库来进行日期时间工作。
someInterval.overlaps( anotherInterval )
ThreeTen-Extra
业内最好的是Java 8及以后版本中内置的java.time
框架。
再加上[ThreeTen-Extra][2]项目,它用额外的类补充了java.time,特别是我们这里需要的[Interval
][3]类。
至于这个问题上的 "语言无关 "标签,这两个项目的源代码都可以在其他语言中使用(注意它们的许可证)。
Interval
[org.threeten.extra.Interval
][3]类很方便,但是需要日期-时间时刻(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 );
我们也可以定义一个 "时间间隔",其起始时刻加上一个["时间"][4]。
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
][3] 与另一个 [Interval
][3] 或 [Instant
][5] 进行比较。
encloses
][8]equals
][9]isBefore
所有这些都使用 "半开 "方法来定义一个时间跨度,在这个时间跨度内,开始是包容的,结束是排他的。
1:
[2]: http://www.threeten.org/threeten-extra/ [3]: http://www.threeten.org/threeten-extra/apidocs/org/threeten/extra/Interval.html [4]: http://docs.oracle.com/javase/8/docs/api/java/time/Duration.html [5]: http://docs.oracle.com/javase/8/docs/api/java/time/Instant.html?is-external=true [6]: http://www.threeten.org/threeten-extra/apidocs/org/threeten/extra/Interval.html#abuts-org.threeten.extra.Interval- [7]: http://www.threeten.org/threeten-extra/apidocs/org/threeten/extra/Interval.html#contains-java.time.Instant- [8]: http://www.threeten.org/threeten-extra/apidocs/org/threeten/extra/Interval.html#encloses-org.threeten.extra.Interval- [9]: http://www.threeten.org/threeten-extra/apidocs/org/threeten/extra/Interval.html#equals-java.lang.Object- [10]: http://www.threeten.org/threeten-extra/apidocs/org/threeten/extra/Interval.html#isAfter-org.threeten.extra.Interval- 11:
在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
这是对@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 ----]
重叠,如果:
(StartA <= EndB)和(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 ----)
Overlap iff:
(StartA < EndB)和(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)和(EndA > EndB)和(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)和(EndA > EndB)和(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)和(EndA >. StartB)
(StartA <= EndB)。
StartB)`
等......。
最后,两个区间重叠的一般条件是
(StartA <🞐 EndB)和(EndA >🞐 StartB)(StartA <🞐 EndB)
其中🞐。 将严格的不等式变成了非严格的不等式,只要在两个包含的端点之间进行比较。
如果你使用的日期范围还没有结束(仍在进行中),如 未设置 endDate = '0000-00-00'。 你不能使用BETWEEN,因为0000-00-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,则不存在重叠。
这个答案对我来说太简单了,所以我创建了一个更通用的动态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)