2012-11-22 246 views
272

我必须检测两个时间段是否重叠。检测重叠时间的算法

每个时期都有开始日期和结束日期。

我需要检测我的第一个时间段(A)是否与另一个(B/C)重叠。

在我的情况下,如果B的开始等于A的结束,他们没有重叠(太倒数)

我发现了以下情况:

enter image description here

所以实际上我这样做这样的:

tStartA < tStartB && tStartB < tEndA //For case 1 
OR 
tStartA < tEndB && tEndB <= tEndA //For case 2 
OR 
tStartB < tStartA && tEndB > tEndA //For case 3 

(外壳4被取入帐户或者在情况1或情况2)

作品,但它似乎不是很有效。

因此,首先是在c#中有一个现有的类,可以对此进行建模(一段时间),类似于timepsan,但具有固定的开始日期。

其次:是否已经有一个C#代码(如在Datetime类中)可以处理这个?

第三:如果不是,那么您将如何使这个比较最快?

+1

期间(C)在案例5中令我困惑。这是否代表不重叠的情况?如果是这样,你是不是分成两部分,案例5 B全部在A之前,案例6 A全部在B之前? –

+0

是它不重叠。 – J4N

+3

质量好的问题! – bob

回答

451

简单的检查,看看两个时间段重叠:

bool overlap = a.start < b.end && b.start < a.end; 

或代码:

bool overlap = tStartA < tEndB && tStartB < tEndA; 

(使用<=而不是<如果你改变主意,想要说的两个期间,只是互相重叠。)

+1

感谢您的快速响应!优秀!似乎是非常有效的:) – J4N

+3

@ J4N第一次我不得不这样做,我写了类似你的代码,直到有人指出了这一点。只是通过它:) – Rawling

+2

我不明白这是如何涵盖所有情况。 – doker

3

自定义interval-tree结构如何?您必须稍微调整一下,以定义两个区间在您的域中“重叠”的含义。

This question可能会帮助您在C#中找到现成的间隔树实现。

16

您可以创建一个可重用的震荡格局类:

public class Range<T> where T : IComparable 
{ 
    readonly T min; 
    readonly T max; 

    public Range(T min, T max) 
    { 
     this.min = min; 
     this.max = max; 
    } 

    public bool IsOverlapped(Range<T> other) 
    { 
     return Min.CompareTo(other.Max) < 0 && other.Min.CompareTo(Max) < 0; 
    } 

    public T Min { get { return min; } } 
    public T Max { get { return max; } } 
} 

您可以添加需要合并范围内的所有方法,得到交叉口等等...

+1

好的答案,但是比较应该返回Min.CompareTo(other.Max)<= 0 && other.Min.CompareTo(Max)<= 0; –

+0

'code' public bool InersectsW –

+0

如何使用IoC注入这个类,比如autofac? – Sai

2

我不相信框架本身就有这个类。也许是第三方库...

但为什么不创建一个Period值对象类来处理这种复杂性?这样您可以确保其他约束条件,如验证开始日期和结束日期时间。喜欢的东西:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 

namespace Whatever.Domain.Timing { 
    public class Period { 
     public DateTime StartDateTime {get; private set;} 
     public DateTime EndDateTime {get; private set;} 

     public Period(DateTime StartDateTime, DateTime EndDateTime) { 
      if (StartDateTime > EndDateTime) 
       throw new InvalidPeriodException("End DateTime Must Be Greater Than Start DateTime!"); 
      this.StartDateTime = StartDateTime; 
      this.EndDateTime = EndDateTime; 
     } 


     public bool Overlaps(Period anotherPeriod){ 
      return (this.StartDateTime < anotherPeriod.EndDateTime && anotherPeriod.StartDateTime < this.EndDateTime) 
     } 

     public TimeSpan GetDuration(){ 
      return EndDateTime - StartDateTime; 
     } 

    } 

    public class InvalidPeriodException : Exception { 
     public InvalidPeriodException(string Message) : base(Message) { }  
    } 
} 

这样,你就能够单独比较每个时期......

+0

试试http://www.codeproject.com/Articles/168662/Time-Period-Library-for-NET –

7

我建立一个订票系统,发现这个页面。我只对范围交叉感兴趣,所以我建立了这个结构;使用DateTime范围就足够了。

您可以检查交叉口,并检查特定日期是否在范围内,并获取 交叉口类型,最重要的是:您可以获得相交的Range。

public struct DateTimeRange 
{ 

    #region Construction 
    public DateTimeRange(DateTime start, DateTime end) { 
     if (start>end) { 
      throw new Exception("Invalid range edges."); 
     } 
     _Start = start; 
     _End = end; 
    } 
    #endregion 

    #region Properties 
    private DateTime _Start; 

    public DateTime Start { 
     get { return _Start; } 
     private set { _Start = value; } 
    } 
    private DateTime _End; 

    public DateTime End { 
     get { return _End; } 
     private set { _End = value; } 
    } 
    #endregion 

    #region Operators 
    public static bool operator ==(DateTimeRange range1, DateTimeRange range2) { 
     return range1.Equals(range2); 
    } 

    public static bool operator !=(DateTimeRange range1, DateTimeRange range2) { 
     return !(range1 == range2); 
    } 
    public override bool Equals(object obj) { 
     if (obj is DateTimeRange) { 
      var range1 = this; 
      var range2 = (DateTimeRange)obj; 
      return range1.Start == range2.Start && range1.End == range2.End; 
     } 
     return base.Equals(obj); 
    } 
    public override int GetHashCode() { 
     return base.GetHashCode(); 
    } 
    #endregion 

    #region Querying 
    public bool Intersects(DateTimeRange range) { 
     var type = GetIntersectionType(range); 
     return type != IntersectionType.None; 
    } 
    public bool IsInRange(DateTime date) { 
     return (date >= this.Start) && (date <= this.End); 
    } 
    public IntersectionType GetIntersectionType(DateTimeRange range) { 
     if (this == range) { 
      return IntersectionType.RangesEqauled; 
     } 
     else if (IsInRange(range.Start) && IsInRange(range.End)) { 
      return IntersectionType.ContainedInRange; 
     } 
     else if (IsInRange(range.Start)) { 
      return IntersectionType.StartsInRange; 
     } 
     else if (IsInRange(range.End)) { 
      return IntersectionType.EndsInRange; 
     } 
     else if (range.IsInRange(this.Start) && range.IsInRange(this.End)) { 
      return IntersectionType.ContainsRange; 
     } 
     return IntersectionType.None; 
    } 
    public DateTimeRange GetIntersection(DateTimeRange range) { 
     var type = this.GetIntersectionType(range); 
     if (type == IntersectionType.RangesEqauled || type==IntersectionType.ContainedInRange) { 
      return range; 
     } 
     else if (type == IntersectionType.StartsInRange) { 
      return new DateTimeRange(range.Start, this.End); 
     } 
     else if (type == IntersectionType.EndsInRange) { 
      return new DateTimeRange(this.Start, range.End); 
     } 
     else if (type == IntersectionType.ContainsRange) { 
      return this; 
     } 
     else { 
      return default(DateTimeRange); 
     } 
    } 
    #endregion 


    public override string ToString() { 
     return Start.ToString() + " - " + End.ToString(); 
    } 
} 
public enum IntersectionType 
{ 
    /// <summary> 
    /// No Intersection 
    /// </summary> 
    None = -1, 
    /// <summary> 
    /// Given range ends inside the range 
    /// </summary> 
    EndsInRange, 
    /// <summary> 
    /// Given range starts inside the range 
    /// </summary> 
    StartsInRange, 
    /// <summary> 
    /// Both ranges are equaled 
    /// </summary> 
    RangesEqauled, 
    /// <summary> 
    /// Given range contained in the range 
    /// </summary> 
    ContainedInRange, 
    /// <summary> 
    /// Given range contains the range 
    /// </summary> 
    ContainsRange, 
} 
0
public class ConcreteClassModel : BaseModel 
{ 
... rest of class 

public bool InersectsWith(ConcreteClassModel crm) 
    { 
     return !(this.StartDateDT > crm.EndDateDT || this.EndDateDT < crm.StartDateDT); 
    } 
} 

[TestClass] 
public class ConcreteClassTest 
{ 
    [TestMethod] 
    public void TestConcreteClass_IntersectsWith() 
    { 
     var sutPeriod = new ConcreteClassModel() { StartDateDT = new DateTime(2016, 02, 01), EndDateDT = new DateTime(2016, 02, 29) }; 

     var periodBeforeSutPeriod = new ConcreteClassModel() { StartDateDT = new DateTime(2016, 01, 01), EndDateDT = new DateTime(2016, 01, 31) }; 
     var periodWithEndInsideSutPeriod = new ConcreteClassModel() { StartDateDT = new DateTime(2016, 01, 10), EndDateDT = new DateTime(2016, 02, 10) }; 
     var periodSameAsSutPeriod = new ConcreteClassModel() { StartDateDT = new DateTime(2016, 02, 01), EndDateDT = new DateTime(2016, 02, 29) }; 

     var periodWithEndDaySameAsStartDaySutPeriod = new ConcreteClassModel() { StartDateDT = new DateTime(2016, 01, 01), EndDateDT = new DateTime(2016, 02, 01) }; 
     var periodWithStartDaySameAsEndDaySutPeriod = new ConcreteClassModel() { StartDateDT = new DateTime(2016, 02, 29), EndDateDT = new DateTime(2016, 03, 31) }; 
     var periodEnclosingSutPeriod = new ConcreteClassModel() { StartDateDT = new DateTime(2016, 01, 01), EndDateDT = new DateTime(2016, 03, 31) }; 

     var periodWithinSutPeriod = new ConcreteClassModel() { StartDateDT = new DateTime(2016, 02, 010), EndDateDT = new DateTime(2016, 02, 20) }; 
     var periodWithStartInsideSutPeriod = new ConcreteClassModel() { StartDateDT = new DateTime(2016, 02, 10), EndDateDT = new DateTime(2016, 03, 10) }; 
     var periodAfterSutPeriod = new ConcreteClassModel() { StartDateDT = new DateTime(2016, 03, 01), EndDateDT = new DateTime(2016, 03, 31) }; 

     Assert.IsFalse(sutPeriod.InersectsWith(periodBeforeSutPeriod), "sutPeriod.InersectsWith(periodBeforeSutPeriod) should be false"); 
     Assert.IsTrue(sutPeriod.InersectsWith(periodWithEndInsideSutPeriod), "sutPeriod.InersectsWith(periodEndInsideSutPeriod)should be true"); 
     Assert.IsTrue(sutPeriod.InersectsWith(periodSameAsSutPeriod), "sutPeriod.InersectsWith(periodSameAsSutPeriod) should be true"); 

     Assert.IsTrue(sutPeriod.InersectsWith(periodWithEndDaySameAsStartDaySutPeriod), "sutPeriod.InersectsWith(periodWithEndDaySameAsStartDaySutPeriod) should be true"); 
     Assert.IsTrue(sutPeriod.InersectsWith(periodWithStartDaySameAsEndDaySutPeriod), "sutPeriod.InersectsWith(periodWithStartDaySameAsEndDaySutPeriod) should be true"); 
     Assert.IsTrue(sutPeriod.InersectsWith(periodEnclosingSutPeriod), "sutPeriod.InersectsWith(periodEnclosingSutPeriod) should be true"); 

     Assert.IsTrue(sutPeriod.InersectsWith(periodWithinSutPeriod), "sutPeriod.InersectsWith(periodWithinSutPeriod) should be true"); 
     Assert.IsTrue(sutPeriod.InersectsWith(periodWithStartInsideSutPeriod), "sutPeriod.InersectsWith(periodStartInsideSutPeriod) should be true"); 
     Assert.IsFalse(sutPeriod.InersectsWith(periodAfterSutPeriod), "sutPeriod.InersectsWith(periodAfterSutPeriod) should be false"); 
    } 
} 

感谢上述答案,帮助我上面代码中的一个MVC项目。

注StartDateDT和EndDateDT是日期时间类型

2

此代码检查如果两个区间重叠。

---------|---| 
---|---|    > FALSE 
xxxxxxxxxxxxxxxxxxxxxxxxx 
-------|---| 
---|---|    > FALSE 
xxxxxxxxxxxxxxxxxxxxxxxxx 
------|---| 
---|---|    > TRUE 
xxxxxxxxxxxxxxxxxxxxxxxxx 
---|---| 
---|--|     > TRUE 
xxxxxxxxxxxxxxxxxxxxxxxxx 
----|---| 
---|-----|    > TRUE 
xxxxxxxxxxxxxxxxxxxxxxxxx 
---|---| 
----|-|     > TRUE 
xxxxxxxxxxxxxxxxxxxxxxxxx 
---|---| 
----|--|    > TRUE 
xxxxxxxxxxxxxxxxxxxxxxxxx 
---|---| 
---|---|    > TRUE 
xxxxxxxxxxxxxxxxxxxxxxxxx 
---|---| 
----|---|    > TRUE 
xxxxxxxxxxxxxxxxxxxxxxxxx 
---|---| 
-------|---|   > TRUE 
xxxxxxxxxxxxxxxxxxxxxxxxx 
---|---| 
--------|---|   > TRUE 

算法:

x1 < y2 
and 
x2 > y1 

例如12:00 - 12:30未与12:30 13:00

1
--logic FOR OVERLAPPING DATES 
DECLARE @StartDate datetime --Reference start date 
DECLARE @EndDate datetime --Reference end date 
DECLARE @NewStartDate datetime --New Start date 
DECLARE @NewEndDate datetime --New End Date 

Select 
(Case 
    when @StartDate is null 
     then @NewStartDate 
    when (@StartDate<@NewStartDate and @EndDate < @NewStartDate) 
     then @NewStartDate 
    when (@StartDate<@NewStartDate and @EndDate > @NewEndDate) 
     then @NewStartDate 
    when (@StartDate<@NewStartDate and @EndDate > @NewStartDate) 
     then @NewStartDate 
    when (@StartDate>@NewStartDate and @NewEndDate < @StartDate) 
     then @NewStartDate 
    else @StartDate end) as StartDate, 

(Case 
    when @EndDate is null 
     then @NewEndDate 
    when (@EndDate>@NewEndDate and @Startdate < @NewEndDate) 
     then @NewEndDate 
    when (@EndDate>@NewEndDate and @Startdate > @NewEndDate) 
     then @NewEndDate 
    when (@EndDate<@NewEndDate and @NewStartDate > @EndDate) 
     then @NewEndDate 
    else @EndDate end) as EndDate 

Distrubution logic

+1

不知道你怎么能比我接受的那个更好地考虑这个答案? – J4N

0

重叠试试这个。无论方法的输入参数的顺序如何,此方法将确定(两个)日期跨度是否重叠。这也可以有两个以上的时间跨度中,通过逐一检查每个日期跨径组合为(前3日期跨度,运行span1span2span2span3span1span3):

public static class HelperFunctions 
{ 
    public static bool AreSpansOverlapping(Tuple<DateTime,DateTime> span1, Tuple<DateTime,DateTime> span2, bool includeEndPoints) 
    { 
     if (span1 == null || span2 == null) 
     { 
      return false; 
     } 
     else if ((new DateTime[] { span1.Item1, span1.Item2, span2.Item1, span2.Item2 }).Any(v => v == DateTime.MinValue)) 
     { 
      return false; 
     } 
     else 
     { 
      if (span1.Item1 > span1.Item2) 
      { 
       span1 = new Tuple<DateTime, DateTime>(span1.Item2, span1.Item1); 
      } 
      if (span2.Item1 > span2.Item2) 
      { 
       span2 = new Tuple<DateTime, DateTime>(span2.Item2, span2.Item1); 
      } 

      if (includeEndPoints) 
      { 
       return 
       ((
        (span1.Item1 <= span2.Item1 && span1.Item2 >= span2.Item1) 
        || (span1.Item1 <= span2.Item2 && span1.Item2 >= span2.Item2) 
       ) || (
        (span2.Item1 <= span1.Item1 && span2.Item2 >= span1.Item1) 
        || (span2.Item1 <= span1.Item2 && span2.Item2 >= span1.Item2) 
       )); 
      } 
      else 
      { 
       return 
       ((
        (span1.Item1 < span2.Item1 && span1.Item2 > span2.Item1) 
        || (span1.Item1 < span2.Item2 && span1.Item2 > span2.Item2) 
       ) || (
        (span2.Item1 < span1.Item1 && span2.Item2 > span1.Item1) 
        || (span2.Item1 < span1.Item2 && span2.Item2 > span1.Item2) 
       ) || (
        span1.Item1 == span2.Item1 && span1.Item2 == span2.Item2 
       )); 
      } 
     } 
    } 
} 

测试:

static void Main(string[] args) 
{ 
    Random r = new Random(); 

    DateTime d1; 
    DateTime d2; 
    DateTime d3; 
    DateTime d4; 

    for (int i = 0; i < 100; i++) 
    { 
     d1 = new DateTime(2012,1, r.Next(1,31)); 
     d2 = new DateTime(2012,1, r.Next(1,31)); 
     d3 = new DateTime(2012,1, r.Next(1,31)); 
     d4 = new DateTime(2012,1, r.Next(1,31)); 

     Console.WriteLine("span1 = " + d1.ToShortDateString() + " to " + d2.ToShortDateString()); 
     Console.WriteLine("span2 = " + d3.ToShortDateString() + " to " + d4.ToShortDateString()); 
     Console.Write("\t"); 
     Console.WriteLine(HelperFunctions.AreSpansOverlapping(
      new Tuple<DateTime, DateTime>(d1, d2), 
      new Tuple<DateTime, DateTime>(d3, d4), 
      true //or use False, to ignore span's endpoints 
      ).ToString()); 
     Console.WriteLine(); 
    } 

    Console.WriteLine("COMPLETE"); 
    System.Console.ReadKey(); 
}