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

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


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

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

我会的

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

我认为最简单的方法是比较EndDate1是否在StartDate2之前,EndDate2是否在StartDate 1之前。

当然,如果您正在考虑StartDate总是在EndDate之前的时间间隔。


(起点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)


关于时间关系(或任何其他区间关系)的推理,请考虑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-------|----------------------

这里有一个可以在本地使用的通用方法。

    // Takes a list and returns all records that have overlapping time ranges.
    public static IEnumerable<T> GetOverlappedTimes<T>(IEnumerable<T> list, Func<T, bool> filter, Func<T,DateTime> start, Func<T, DateTime> end)
    {
        // Selects all records that match filter() on left side and returns all records on right side that overlap.
        var overlap = from t1 in list
                      where filter(t1)
                      from t2 in list
                      where !object.Equals(t1, t2) // Don't match the same record on right side.
                      let in1 = start(t1)
                      let out1 = end(t1)
                      let in2 = start(t2)
                      let out2 = end(t2)
                      where in1 <= out2 && out1 >= in2
                      let totover = GetMins(in1, out1, in2, out2)
                      select t2;

        return overlap;
    }

    public static void TestOverlap()
    {
        var tl1 = new TempTimeEntry() { ID = 1, Name = "Bill", In = "1/1/08 1:00pm".ToDate(), Out = "1/1/08 4:00pm".ToDate() };
        var tl2 = new TempTimeEntry() { ID = 2, Name = "John", In = "1/1/08 5:00pm".ToDate(), Out = "1/1/08 6:00pm".ToDate() };
        var tl3 = new TempTimeEntry() { ID = 3, Name = "Lisa", In = "1/1/08 7:00pm".ToDate(), Out = "1/1/08 9:00pm".ToDate() };
        var tl4 = new TempTimeEntry() { ID = 4, Name = "Joe", In = "1/1/08 3:00pm".ToDate(), Out = "1/1/08 8:00pm".ToDate() };
        var tl5 = new TempTimeEntry() { ID = 1, Name = "Bill", In = "1/1/08 8:01pm".ToDate(), Out = "1/1/08 8:00pm".ToDate() };
        var list = new List<TempTimeEntry>() { tl1, tl2, tl3, tl4, tl5 };
        var overlap = GetOverlappedTimes(list, (TempTimeEntry t1)=>t1.ID==1, (TempTimeEntry tIn) => tIn.In, (TempTimeEntry tOut) => tOut.Out);

        Console.WriteLine("\nRecords overlap:");
        foreach (var tl in overlap)
            Console.WriteLine("Name:{0} T1In:{1} T1Out:{2}", tl.Name, tl.In, tl.Out);
        Console.WriteLine("Done");

        /*  Output:
            Records overlap:
            Name:Joe T1In:1/1/2008 3:00:00 PM T1Out:1/1/2008 8:00:00 PM
            Name:Lisa T1In:1/1/2008 7:00:00 PM T1Out:1/1/2008 9:00:00 PM
            Done
         */
    }

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

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

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

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

其中每条线具有:

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

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

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


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

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


if (StartDate1 > StartDate2) swap(StartDate, EndDate);

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

如果重叠本身也需要计算,可以使用以下公式:

overlap = max(0, min(EndDate1, EndDate2) - max(StartDate1, StartDate2))
if (overlap > 0) { 
    ...
}

将问题分为几个案例,然后处理每个案例。

“两个日期范围相交”的情况有两种情况:第一个日期范围从第二个开始,或者第二个日期范围在第一个开始。


这是我使用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;

此处发布的解决方案不适用于所有重叠范围。。。

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

我的工作解决方案是:

AND (
  ('start_date' BETWEEN STARTDATE AND ENDDATE) -- caters for inner and end date outer
  OR
  ('end_date' BETWEEN STARTDATE AND ENDDATE) -- caters for inner and start date outer
  OR
  (STARTDATE BETWEEN 'start_date' AND 'end_date') -- only one needed for outer range where dates are inside.
) 

你可以试试这个:

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

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


在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

public static class NumberExtensionMethods
    {
        public static Boolean IsBetween(this Int64 value, Int64 Min, Int64 Max)
        {
            if (value >= Min && value <= Max) return true;
            else return false;
        }

        public static Boolean IsBetween(this DateTime value, DateTime Min, DateTime Max)
        {
            Int64 numricValue = value.Ticks;
            Int64 numericStartDate = Min.Ticks;
            Int64 numericEndDate = Max.Ticks;

            if (numricValue.IsBetween(numericStartDate, numericEndDate) )
            {
                return true;
            }

            return false;
        }
    }

public static Boolean IsOverlap(DateTime startDate1, DateTime endDate1, DateTime startDate2, DateTime endDate2)
        {
            Int64 numericStartDate1 = startDate1.Ticks;
            Int64 numericEndDate1 = endDate1.Ticks;
            Int64 numericStartDate2 = startDate2.Ticks;
            Int64 numericEndDate2 = endDate2.Ticks;

            if (numericStartDate2.IsBetween(numericStartDate1, numericEndDate1) ||
                numericEndDate2.IsBetween(numericStartDate1, numericEndDate1) ||
                numericStartDate1.IsBetween(numericStartDate2, numericEndDate2) ||
                numericEndDate1.IsBetween(numericStartDate2, numericEndDate2))
            {
                return true;
            }

            return false;
        } 


if (IsOverlap(startdate1, enddate1, startdate2, enddate2))
            {
                Console.WriteLine("IsOverlap");
            }

这是我的解决方案,当值不重叠时返回真值:

X开始1Y端1

开始2B端2

TEST1: (X <= A || X >= B)
        &&
TEST2: (Y >= B || Y <= A) 
        && 
TEST3: (X >= B || Y <= A)


X-------------Y
    A-----B

TEST1:  TRUE
TEST2:  TRUE
TEST3:  FALSE
RESULT: FALSE

---------------------------------------

X---Y
      A---B

TEST1:  TRUE
TEST2:  TRUE
TEST3:  TRUE
RESULT: TRUE

---------------------------------------

      X---Y
A---B

TEST1:  TRUE
TEST2:  TRUE
TEST3:  TRUE
RESULT: TRUE

---------------------------------------

     X----Y
A---------------B

TEST1:  FALSE
TEST2:  FALSE
TEST3:  FALSE
RESULT: FALSE

使用Java util.Date,这里是我所做的。

    public static boolean checkTimeOverlaps(Date startDate1, Date endDate1, Date startDate2, Date endDate2)
    {
        if (startDate1 == null || endDate1 == null || startDate2 == null || endDate2 == null)
           return false;

        if ((startDate1.getTime() <= endDate2.getTime()) && (startDate2.getTime() <= endDate1.getTime()))
           return true;

        return 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秒)


对于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


下面的查询给出了提供的日期范围(开始和结束日期)与table_name中的任何日期(开始和终止日期)重叠的ID

select id from table_name where (START_DT_TM >= 'END_DATE_TIME'  OR   
(END_DT_TM BETWEEN 'START_DATE_TIME' AND 'END_DATE_TIME'))

最简单的

最简单的方法是使用精心设计的专用库进行日期时间工作。

someInterval.overlaps( anotherInterval )

java.time&ThreeTen额外

业务中最好的是内置在Java8和更高版本中的java.time框架。添加ThreeTen Extra项目,该项目用额外的类来补充java.time,特别是我们这里需要的Interval类。

至于这个问题上的语言不可知标签,两个项目的源代码都可以在其他语言中使用(注意它们的许可证)。

间隔

org.threeten.extra.Interval类很方便,但需要日期时间瞬间(java.time.Instant对象),而不是仅日期值。因此,我们继续使用UTC中的一天的第一刻来表示日期。

Instant start = Instant.parse( "2016-01-01T00:00:00Z" );
Instant stop = Instant.parse( "2016-02-01T00:00:00Z" );

创建一个间隔来表示该时间跨度。

Interval interval_A = Interval.of( start , stop );

我们也可以用开始时刻加上持续时间来定义间隔。

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

您可以将“间隔”与另一个“间隔”或“即时”进行比较:

邻接,邻接包含外壳等于在之后是之前重叠,重叠

所有这些都使用半开放的方法来定义一段时间,其中开始是包容性的,结束是排他性的。


答案对我来说太简单了,所以我创建了一个更通用的动态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)

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

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

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

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

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


简单的解决方案:

compare the two dates: 
    A = the one with smaller start date, B = the one with bigger start date
if(A.end < B.start)
    return false
return true

这是我的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));
}

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


如果您提供一个日期范围作为输入,并希望找出它是否与数据库中的现有日期范围重叠,则以下条件可以成功满足您的需求

假设您从表单输入中提供了@StartDate和@EndDate。

条件是:

如果@StartDate在existingStartDate之前,而在existing EndDate之后,那么我们可以说@StartDate's在现有日期范围的中间,因此我们可以断定它将重叠

@StartDate >=existing.StartDate And @StartDate <= existing.EndDate) 

如果@StartDate在现有StartDate之后,而@EndDate在现有的StartDate之前,我们可以说它将重叠

 (@StartDate <= existing.StartDate And @EndDate >= existing.StartDate)

如果@StartDate晚于现有的StartDate,而@EndDate先于现有的EndDate,我们可以得出结论,所提供的日期范围吞噬了现有的日期范围,因此重叠

 (@StartDate <= existing.StartDate And @EndDate >= existing.EndDate))

如果任何条件成立,则提供的日期范围与数据库中的现有日期范围重叠。


这是一段神奇的代码:

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

哪里

A->1启动B->1结束C->2启动D->2结束

证据查看此测试控制台代码要点。


使用momentjs的简短回答:

function isOverlapping(startDate1, endDate1, startDate2, endDate2){ 
    return moment(startDate1).isSameOrBefore(endDate2) && 
    moment(startDate2).isSameOrBefore(endDate1);
}

答案基于上述答案,但缩短了。


记住解决方案的一个简单方法是最小(结束)>最大(开始)


适合我的紧凑公式

class ValidityRuleRange {
        private final Date from;
        private final Date to;
    ...
    private boolean isOverlap(ValidityRuleRange vrr) {
        int c1 = from.compareTo(vrr.getTo());
        int c2 = to.compareTo(vrr.getFrom());
        return c1 == 0 || c2 == 0 || c1 + c2 == 0;
    }

我找到了另一个非常简单的方法。如果daterange1的开始和结束日期在daterange2的开始日期之前,或者daterange3的开始和终止日期在daterange2的结束日期之后,这意味着它们彼此不相交。

public boolean doesIntersect(DateRangeModel daterange1, DateRangeModel  daterange2) {
    return !(
            (daterange1.getStartDate().isBefore(daterange2.getStartDate())
                    && daterange1.getEndDate().isBefore(daterange2.getStartDate())) ||
                    (daterange1.getStartDate().isAfter(daterange2.getStartDate())
                            && daterange1.getEndDate().isAfter(daterange2.getEndDate())));
}

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

在标准SQL中,它非常简单

(StartDate1, EndDate1) overlaps (StartDate2, EndDate2)

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

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


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

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

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


只要有简单的解决方案,仅仅使用额外的包来做简单的事情可能效率不高。但是,如果您已经在项目中使用了日期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

为了涵盖PHP/LaravelCarbon包中的所有重叠情况,您可以在Charles的答案中展开逻辑,如下所示。

if ( ($startTime1->between($startTime2, $endTime2, true) || $endTime1->between($startTime2, $endTime2, true)) || (($startTime1 <= $endTime2) && ($endTime2 <= $endTime1)) ){
//Complete Overlap, Partial Left Overlap, Partial Right Overlap. 
}

这将检查StartTime1是否在(StartTime2-EndTime2)范围之间,或者EndTime1是否位于(StartTime2-EndTime2的范围之间。

其余部分完全重叠,如其他答案所述。


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

       | |
     | |

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

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

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