判断两个日期范围是否重叠

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

例如,假设我们有由DateTime变量StartDate1EndDate1StartDate2EndDate2表示的范围。

584467 次浏览

我认为,只要说这两个范围重叠就足够了,如果:

(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是否在StartDate1之前。

当然,如果您正在考虑StartDate始终在EndDate之前的间隔。

(StartA<=EndB)和(EndA>=StartB)

证明:
条件A表示DateRange A完全在DateRange B之后

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

(如果StartA > EndB为真)

条件B表示DateRange A完全在DateRange B之前

|---- DateRange A -----|                        __                          |---Date Range B ----|

(如果EndA < StartB为真)

如果A和B都不为真,则存在重叠-
(如果一个范围不完全在另一个之后,
也不完全在对方之前,#36825;有重叠)

现在德摩根定律中的一个说:

Not (A Or B)<=>Not A And Not B

翻译为:(StartA <= EndB) and (EndA >= StartB)


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


注意2。感谢@Baodad,请参阅这个博客,实际重叠最少:
{endA-startAendA - startBendB-startAendB - startB}

(StartA <= EndB) and (EndA >= StartB)(StartA <= EndB) and (StartB <= EndA)


注意3。感谢@tomosius,一个较短的版本如下:
DateRangesOverlap = max(start1, start2) < min(end1, end2)
这实际上是一个较长实现的语法快捷方式,其中包括额外的检查以验证开始日期是否在endDates上或之前。从上面推导:

如果开始日期和结束日期可能是无序的,即,如果可能是startA > endAstartB > 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? Start A: StartB) <= (EndA < EndB? EndA: EndB)

要推理时间关系(或任何其他区间关系,就是这样),请考虑艾伦区间代数。它描述了两个区间相互之间可能存在的13种可能的关系。你可以找到其他参考-“艾伦区间”似乎是一个有效的搜索词。你还可以在斯诺德格拉斯的在SQL中开发面向时间的应用程序中找到关于这些操作的信息(PDF可在URL上在线获得),以及日期,Darwen和Lorentzos时间数据和关系模型(2002)或时间和关系理论:关系模型中的时态数据库和SQL(2014;实际上是TD&RM的第二版)。


简短的(ish)答案是:给定两个日期间隔AB,组件.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个有不同的逆,等于没有不同的逆)。三个是否合理取决于你的要求。

----------------------|-------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 listwhere filter(t1)from t2 in listwhere !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 >= in2let 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 PMName:Lisa T1In:1/1/2008 7:00:00 PM T1Out:1/1/2008 9:00:00 PMDone*/}

所有的解决方案,检查大量的条件的基础上,范围是相对于另一个可以大大简化简单地确保一个范围在另一个范围之前或同时开始。你可以通过交换范围,如果有必要预先做到这一点。

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

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

例如(假设两端都包含在内),范围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, r2return r2.s <= r1.e

或者,一级限制递归选项:

def overlaps(r1, r2):if r1.s <= r2.s:return r2.s <= r1.ereturn 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.endis_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): ''      ,, - Falser1=(0, 4) r2=(5, 9): ''''',,,,, - Falser1=(0, 4) r2=(4, 9): ''''|,,,,, - Truer1=(0, 7) r2=(2, 9): ''||||||,, - Truer1=(0, 4) r2=(0, 9): |||||,,,,, - Truer1=(0, 9) r2=(0, 9): |||||||||| - Truer1=(0, 9) r2=(4, 5): ''''||'''' - Truer1=(8, 9) r2=(0, 1): ,,      '' - Falser1=(5, 9) r2=(0, 4): ,,,,,''''' - Falser1=(4, 9) r2=(0, 4): ,,,,|''''' - Truer1=(2, 9) r2=(0, 7): ,,||||||'' - Truer1=(0, 9) r2=(0, 4): |||||''''' - Truer1=(0, 9) r2=(0, 9): |||||||||| - Truer1=(4, 5) r2=(0, 9): ,,,,||,,,, - True

每一行都有:

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

您可以很清楚地看到,只有在两者范围内至少有一个值(即|字符)时,您才能在重叠检查中获得true。其他所有情况都为false。

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

本文. NET的时间段库通过枚举展示周期描述了两个时间段的关系:

// ------------------------------------------------------------------------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) {...}

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

“两个日期范围相交”的情况由两种情况覆盖-第一个日期范围从第二个开始,或者第二个日期范围从第一个开始。

这是我的JavaScript解决方案与moment.js:

// Current row datesvar dateStart = moment("2014-08-01", "YYYY-MM-DD");var dateEnd = moment("2014-08-30", "YYYY-MM-DD");
// Check with dates abovevar 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 goodreturn 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 outerOR('end_date' BETWEEN STARTDATE AND ENDDATE) -- caters for inner and start date outerOR(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/end2OR (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

如果start date2高于enddate,则没有重叠!

在MicrosoftSQL服务器-SQL功能

CREATE FUNCTION IsOverlapDates(@startDate1 as datetime,@endDate1 as datetime,@startDate2 as datetime,@endDate2 as datetime)RETURNS intASBEGINDECLARE @Overlap as intSET @Overlap = (SELECT CASE WHEN  ((@startDate1 BETWEEN @startDate2 AND @endDate2) -- caters for inner and end date outerOR(@endDate1 BETWEEN @startDate2 AND @endDate2) -- caters for inner and start date outerOR(@startDate2 BETWEEN @startDate1 AND @endDate1) -- only one needed for outer range where dates are inside.) THEN 1 ELSE 0 END)RETURN @Overlap
ENDGO
--Execution of the above codeDECLARE @startDate1 as datetimeDECLARE @endDate1 as datetimeDECLARE @startDate2 as datetimeDECLARE @endDate2 as datetimeDECLARE @Overlap as intSET @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");}

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

X开始1结束1

开始2结束2

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

X-------------YA-----B
TEST1:  TRUETEST2:  TRUETEST3:  FALSERESULT: FALSE
---------------------------------------
X---YA---B
TEST1:  TRUETEST2:  TRUETEST3:  TRUERESULT: TRUE
---------------------------------------
X---YA---B
TEST1:  TRUETEST2:  TRUETEST3:  TRUERESULT: TRUE
---------------------------------------
X----YA---------------B
TEST1:  FALSETEST2:  FALSETEST3:  FALSERESULT: FALSE

使用Javautil. 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 intervaldef overlaps?(other)(start_date - other.end_date) * (other.start_date - end_date) >= 0end
# Return a scope for all interval overlapping the given interval, including the given interval itselfnamed_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&Three10-Extra

业务中最好的是Java8和更高版本中内置的#0框架。再加上额外三个项目,它用额外的类补充java.time,特别是我们这里需要的#1类。

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

Interval

#0类很方便,但需要日期时间时刻(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 );

我们还可以定义一个Interval,其中包含一个起始时刻加上一个#1

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

您可以将#0与另一个#0#2进行比较:

所有这些都使用Half-Open方法来定义一个时间跨度,其中开始是包容,结束是独家

答案对我来说太简单了,所以我创建了一个更通用的动态SQL语句,检查一个人是否有重叠的日期。

SELECT DISTINCT T1.EmpIDFROM Table1 T1INNER JOIN Table2 T2 ON T1.EmpID = T2.EmpIDAND T1.JobID <> T2.JobIDAND ((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 dateif(A.end < B.start)return falsereturn 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给出的数学解很好,但忽略了两个具体细节:

  1. 闭区间或半开区间的切面
  2. 空间隔

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

(StartA<=EndB)和(EndA>=StartB)

可以将对于半开间隔重写为:

(StartAStartB)

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


关于空间隔,好吧,这里上面显示的关系不成立。根据定义,不包含任何有效值的空间隔必须作为特殊情况处理。我通过这个例子在我的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())); // falseSystem.out.println("endA > startB: " + a.getEndAsInstant().isAfter(b.getStartAsInstant())); // true
System.out.println("a overlaps b: " + a.intersects(b)); // a overlaps b: false

如上所示,空间隔违反了上面的重叠条件(尤其是start A

这是@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 ----]

重叠iff:(StartA <= EndB) and (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 ----)

重叠iff:(StartA < EndB) and (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) and (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) and (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) and (EndA > StartB)

等…

最后,两个区间重叠的一般条件是

(StartAStartB)

其中,每当在两个包含端点之间进行比较时,将严格不等式转换为非严格不等式。

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

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

条件是:

如果@StartDateexistingStartDate之前,在existingEndDate之后,那么我们可以说@StartDate在现有日期范围的中间,因此我们可以得出结论,它将重叠

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

如果@StartDate落后于existingStartDate,但@EndDate领先于existingStartDate,我们可以说它会重叠

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

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

 (@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->1Start
  • B->1End
  • C->2Start
  • D->2End

证明?看看这个测试控制台代码要点

简短的回答,用的是瞬间

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

答案是基于上述答案,但它的缩短。

记住解决方案的一个简单方法是
min(ends)>max(starts)

一个适合我的紧凑配方

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的开始日期之前,或者daterange1的开始和结束日期在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)

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

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


在PostgreSQL中,使用日期范围也很容易测试纳入

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

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

只要有简单的解决方案,使用额外的包来做简单的事情可能效率不高。然而,如果你已经在你的项目中使用了date-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/Laravel Carbon Package中的所有重叠情况,您可以在查尔斯的回答中展开逻辑如下。

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

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

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