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

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


当前回答

本文《.NET时间段库》通过枚举PeriodRelation描述了两个时间段之间的关系:

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

其他回答

只要有简单的解决方案,仅仅使用额外的包来做简单的事情可能效率不高。但是,如果您已经在项目中使用了日期fns,那么有一个名为areIntervalsOverlapping的方法也会执行同样的操作。

语法:

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

@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中是关闭的,但也可以是半开的,就像空日期间隔一样)的处理方式类似。

我会的

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

由于对于不同的语言和环境有多种答案,这里有一个标准ANSISQL的答案。

在标准SQL中,它非常简单

(StartDate1, EndDate1) overlaps (StartDate2, EndDate2)

假设所有四列都是DATE或TIMESTAMP列。如果两个范围至少有一天相同(假设DATE值),则返回true

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


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

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

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

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

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

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

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

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