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

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


当前回答

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

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

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

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

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

其他回答

(起点A<=终点B)和(终点A>=起点B)

证明:让条件A表示日期范围A完全在日期范围B之后

_                        |---- DateRange A ------|
|---Date Range B -----|                          _

(如果StartA>EndB,则为True)

让条件B表示日期范围A完全在日期范围B之前

|---- DateRange A -----|                        _ 
_                          |---Date Range B ----|

(如果EndA<StartB,则为True)

如果A和B都不为真,则存在重叠-(如果一个范围既不完全在另一个范围之后,也不完全在另一个之前,则它们必须重叠。)

现在,德摩根的一项法律规定:

不是(A或B)<=>不是A也不是B

转换为:(StartA<=EndB)和(EndA>=StartB)


注意:这包括边缘完全重叠的情况。如果你想排除这一点,将>=运算符更改为>,并将<=更改为<


注2.多亏了@宝爸,看看这个博客,实际的重叠最少:{endA startA,endA-startB,endB startA,end B-startB}

(起点A<=终点B)和(终点A>=起点B)(开始A<=结束B)和(开始B<=结束A)


注3.多亏了@tomosius,一个简短的版本写道:DateRangesOverlap=最大值(start1,start2)<最小值(end1,end2)这实际上是一个较长实现的语法快捷方式,它包括额外的检查,以验证开始日期是否在结束日期之前。从上面得出:

如果开始日期和结束日期可能是无序的,即,如果startA>endA或startB>endB是可能的,那么您还必须检查它们是否有序,这意味着您必须添加两个额外的有效性规则:(StartA<=EndB)和(StartB<=EndA)以及或:(开始A<=结束B)和(开始A<=结束A)以及(开始B<=结束A”和(开始B<=结束B”)或(开始A<=最小值(结束A,结束B)和(开始B<=最小(结束A、结束B))或:(最大值(开始A,开始B)<=最小值(结束A,结束B)

但要实现Min()和Max(),必须编写代码(使用C三进制表示简洁):((开始A>开始B)?开始A:开始B)<=((结束A<结束B)?端A:端B)

在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

对于ruby,我也发现了这一点:

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

@Bretana给出的数学解很好,但忽略了两个具体细节:

封闭或半开放间隔的方面空间隔


关于区间边界的封闭或开放状态,@Bretana的解对封闭区间有效

(起点A<=终点B)和(终点A>=起点B)

可以重写为半开间隔:

(开始A<结束B)和(结束A>开始B)

这种校正是必要的,因为根据定义,开放区间边界不属于区间的值范围。


关于空间隔,这里上面所示的关系不成立。根据定义不包含任何有效值的空间隔必须作为特殊情况处理。我通过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中是关闭的,但也可以是半开的,就像空日期间隔一样)的处理方式类似。

你可以试试这个:

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