32

I have four DateTime objects. A1, A2 and B1, B2.

I need to know that the period A1-A2 doesn't intersect with period B1-B2. But I don`t want to write dirty code, like many if blocks.

if (A1 < B1 && A2 > B1)
{
    return false;
}

.... etc.

EDITED

I tried to use this one: Comparing ranges

DateTime A1 = DateTime.MinValue.AddMinutes(61);
DateTime A2 = DateTime.MinValue.AddHours(1.2);
DateTime B1 = DateTime.MinValue.AddMinutes(5);
DateTime B2 = DateTime.MinValue.AddHours(1);

Console.WriteLine(Range.Overlap(
    new Range<DateTime>(A1, A2),
    new Range<DateTime>(B1, B2)
));

It returned true but I expected false. Because this code always returns true

 if (left.Start.CompareTo(left.Start) == 0)
 {
     return true;
 }
Marius Stănescu
  • 3,603
  • 2
  • 35
  • 49
Ivan Korytin
  • 1,832
  • 1
  • 27
  • 41

11 Answers11

50

If in your program the ranges A1-A2 and B1-B2 are "proper" in the sense that it is known that A1<=A2 and B1<=B2

then your non-intersection test is simply

if(A1>B2 || B1>A2)

Note I have glossed over whether this is > or >=. The proper choice of operator depends on how you have defined your ranges to include or exclude their endpoints; i.e. whether they represent closed, open, or half-open intervals.

Corey Kosak
  • 2,615
  • 17
  • 13
41

I dont believe there is going to be any manner of 'easy' code to write; you have to account for 4 distinct use cases. If you need to do this kind of check a lot, I'd write an extension method. Otherwise, you just need to check these conditions:

 |--- Date 1 ---|
      | --- Date 2 --- |


      | --- Date 1 --- |
 | --- Date 2 ---- |


 | -------- Date 1 -------- |
      | --- Date 2 --- |

      | --- Date 1 --- |
 | -------- Date 2 -------- |

EDIT: To provide actual code:

public class DateTimeRange
{
     public DateTime Start { get; set; }
     public DateTime End { get; set; }

     public bool Intersects(DateTimeRange test)
     {
         if(this.Start > this.End || test.Start > test.End)
            throw new InvalidDateRangeException();

         if(this.Start == this.End || test.Start == test.End)
              return false; // No actual date range

         if(this.Start == test.Start || this.End == test.End)
              return true; // If any set is the same time, then by default there must be some overlap. 

         if(this.Start < test.Start)
         {
              if(this.End > test.Start && this.End < test.End)
                  return true; // Condition 1

              if(this.End > test.End)
                  return true; // Condition 3
         }
         else
         {
              if(test.End > this.Start && test.End < this.End)
                  return true; // Condition 2

              if(test.End > this.End)
                  return true; // Condition 4
         }

         return false;
    }
}

That should cover the use cases.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
Tejs
  • 40,736
  • 10
  • 68
  • 86
  • 9
    +1 for near-ASCII-art, oh and the answer of course – Andras Zoltan Sep 06 '11 at 19:38
  • Your second test seems suspicious. Is it really the case that two identical zero-width ranges are not overlapping? I would say that they overlap at a point. – Eric Lippert Sep 06 '11 at 21:06
  • 4
    Also, your code seems excessively complicated because you have created four cases out of what could be only two cases. Your cases two and three are the same case, namely the case "is the end of Date2 inside Date1"? And your cases one and four are the same case, namely, "is the end of Date1 inside Date2?" – Eric Lippert Sep 06 '11 at 21:10
  • 46
    Alternatively, you can show that your four cases are reduced to two cases by considering the *opposite*. Turn the question from "how many ways are there to overlap?" to "how many ways are there to NOT overlap?" There are only two. Either the second date range starts *after* the first one ends, or the first date range starts *after* the second one ends. If neither of those two cases are true, then they overlap. You don't need to consider the four ways that they overlap if you just consider the two ways that they do not. – Eric Lippert Sep 06 '11 at 21:15
  • 2
    That is the beauty of Corey Kosak's solution below. Much more simple than this. There are other answers that express the same simplicity (mine included), but I believe Corey was first to post it. – Jonathan M Sep 07 '11 at 13:27
  • 2
    @Tejs: I downvoted this because, while it was selected by the OP, it doesn't meet the key requirement he posted: "I don`t want write dirty code, like many if blocks." There are cleaner ways to do this. – Jonathan M Sep 07 '11 at 13:42
  • This is excellent. I created a suite of unit tests. They show you are indeed correct. I'll post the test class below. – Michael Kennedy Feb 19 '14 at 19:40
  • In think there is better options in the other answers, one liners with that logic: – Anderson Danilo Apr 29 '21 at 15:15
  • return range2[1] >= range1[0] and range2[0] <= range1[1] – Anderson Danilo Apr 29 '21 at 15:15
27

Time Period Library for .NET looks interesting.

Methods like IsSamePeriod, HasInside, OverlapsWith, or IntersectsWith are available for convenience to query for special, often used variants of such period relations.

Ralph Willgoss
  • 11,750
  • 4
  • 64
  • 67
Dave Ziegler
  • 1,737
  • 3
  • 19
  • 36
19

My approach is to create a class called Period which contains Start and End properties (DateTime). This class can have methods or extension methods to calculate things like intersections. Let's say you have a method like this in your Period class:

public bool IntersectsWith(Period otherPeriod)
{
    return !(this.Start > otherPeriod.End || this.End < otherPeriod.Start);
}

Then you can write code like this:

if (!periodA.IntersectsWith(periodB))
{
    return false;
}
Meta-Knight
  • 17,626
  • 1
  • 48
  • 58
  • 1
    Way more simpler and efficient than the accepted answer. Only two tests, not a bunch of if/then, best solution here. – ken2k Nov 02 '12 at 13:57
5

The code which you tried had bug, I have fixed it:

Try this:

class Range<T> where T : IComparable<T>
{
    public T Start { get; private set;}
    public T End { get; private set;}

    public Range(T start, T end)
    {
        //Always ensure that Start < End
        if(start.CompareTo(end) >= 0)
        {
            var temp = end;
            end = start;
            start = temp;
        }

        Start = start;
        End = end;
    }
}

static class Range
{
    //Based on Eric's idea of doing negative check to figure out
    //how many ways there are for ranges to NOT overlap.
    public static bool EricOverlap<T>(Range<T> left, Range<T> right)
        where T : IComparable<T>
    {
        if (right.Start.CompareTo(left.End) > 0)
            return false;

        if (left.Start.CompareTo(right.End) > 0)
            return false;

        return true;
    }
    public static bool Overlap<T>(Range<T> left, Range<T> right)
        where T : IComparable<T>
    {
        if (left.Start.CompareTo(right.Start) == 0)
        {
            return true;
        }

        else if (left.Start.CompareTo(right.Start) > 0)
        {
            return left.Start.CompareTo(right.End) <= 0;
        }
        else
        {
            return right.Start.CompareTo(left.End) <= 0;
        }
    }
}
SolutionYogi
  • 31,807
  • 12
  • 70
  • 78
  • Simply returning true or false means you can just return the condition itself: return !((right.Start.CompareTo(left.End) > 0) && (left.Start.CompareTo(right.End) > 0)). They become oneliners. +1 nonetheless. – Louis Kottmann Jul 19 '12 at 08:39
3

No way around it:

*Edited for simplification:

Assuming B2>B1 and A2>A1:

if (A2 >= B1 && A1 <= B2) {
    // some part of a1-a2 is in b1-b2
}

This will detect if any part of A1-A2 is in B1-B2.

If you need to detect if A1-A2 is completely in B1-B2:

if (B1 <= A1 && B2 >= A2) {
    // all of a1-a2 is in b1-b2
}
Jonathan M
  • 17,145
  • 9
  • 58
  • 91
2

This unit test class accompanies the solution above by Tejs using the DateTimeRange class (modified constructor). His solution is correct and these tests prove it (in case you want to copy into production. :) )

[TestClass]
public class DateTimeRangeTests
{
    [TestMethod]
    public void overlap_dates_is_interscected_second_newer_test()
    {
        //|--- Date 1 ---|
        //    | --- Date 2 --- |
        DateTime baseTime = DateTime.Now;
        var r1 = new DateTimeRange(baseTime.AddDays(-4), baseTime.AddDays(-2));
        var r2 = new DateTimeRange(baseTime.AddDays(-3), baseTime.AddDays(-1));

        Assert.IsTrue(r1.Intersects(r2));
    }

    [TestMethod]
    public void overlap_dates_is_interscected_second_older_test()
    {
        //        |--- Date 1 ---|
        //    | --- Date 2 --- |
        DateTime baseTime = DateTime.Now;
        var r1 = new DateTimeRange(baseTime.AddDays(-3), baseTime.AddDays(-1));
        var r2 = new DateTimeRange(baseTime.AddDays(-4), baseTime.AddDays(-2));

        Assert.IsTrue(r1.Intersects(r2));
    }

    [TestMethod]
    public void overlap_dates_is_interscected_second_subset_of_first_test()
    {
        //| -------- Date 1 -------- |
        //   | --- Date 2 --- |
        DateTime baseTime = DateTime.Now;
        var r1 = new DateTimeRange(baseTime.AddDays(-4), baseTime.AddDays(-1));
        var r2 = new DateTimeRange(baseTime.AddDays(-3), baseTime.AddDays(-2));

        Assert.IsTrue(r1.Intersects(r2));
    }

    [TestMethod]
    public void overlap_dates_is_interscected_second_superset_of_first_test()
    {
        //| -------- Date 1 -------- |
        //   | --- Date 2 --- |
        DateTime baseTime = DateTime.Now;
        var r1 = new DateTimeRange(baseTime.AddDays(-3), baseTime.AddDays(-2));
        var r2 = new DateTimeRange(baseTime.AddDays(-4), baseTime.AddDays(-1));

        Assert.IsTrue(r1.Intersects(r2));
    }

    [TestMethod]
    public void non_intersects_dates_when_second_before_first_test()
    {
        //                        | --- Date 1 -------- |
        //   | --- Date 2 --- |
        DateTime baseTime = DateTime.Now;
        var r1 = new DateTimeRange(baseTime.AddDays(-1), baseTime.AddDays(0));
        var r2 = new DateTimeRange(baseTime.AddDays(-4), baseTime.AddDays(-2));

        Assert.IsFalse(r1.Intersects(r2));
    }

    [TestMethod]
    public void non_intersects_dates_when_second_after_first_test()
    {
        //   | --- Date 1 ------ |
        //                          | --- Date 2 --- |
        DateTime baseTime = DateTime.Now;
        var r1 = new DateTimeRange(baseTime.AddDays(-4), baseTime.AddDays(-2));
        var r2 = new DateTimeRange(baseTime.AddDays(-1), baseTime.AddDays(-0));

        Assert.IsFalse(r1.Intersects(r2));
    }
}
Michael Kennedy
  • 3,202
  • 2
  • 25
  • 34
1
public bool Overlap(DateTime startDate1, DateTime endDate1, DateTime startDate2, DateTime endDate2)
        {
            if (endDate1 >= startDate2 && endDate2 >= startDate1)
            {
                return true;
            }

            if (startDate1 <= endDate2 && startDate2 <= startDate1)
            {
                return true;
            }

            return false;
        }
Acaz Souza
  • 8,311
  • 11
  • 54
  • 97
1

I think you can do it like so !((end2 < start1) || (start2 > end1)):

DateTime start1 = new DateTime(1);
DateTime end1 = new DateTime(2);

DateTime start2 = new DateTime(1);
DateTime end2 = new DateTime(2);

Console.WriteLine(!( (end2 < start1) || (start2 > end1) )); //returns true

[OR]

DateTime start1 = new DateTime(1);
DateTime end1 = new DateTime(2);

DateTime start2 = new DateTime(3);
DateTime end2 = new DateTime(4);


Console.WriteLine(!( (end2 < start1) || (start2 > end1) )); // returns false
Cubicle.Jockey
  • 3,288
  • 1
  • 19
  • 31
0
DateTime[] start = new DateTime[] { new DateTime(2000, 1, 1), new DateTime(2004, 1, 1),   
                                  new DateTime(2004, 1, 1), new DateTime(2008, 1, 1) };  
                 /*date that start from*/

DateTime[] end   = new DateTime[] { new DateTime(2002, 1, 1), new DateTime(2006, 1, 1),  
new DateTime(2006, 1, 1), new DateTime(2010, 1, 1) }; /*date that end */

int timeDifference ; 

TimeSpan timespan;

 /*buttonclick  */
    {     /*find total days which note overlap*/
    for (int i=0; i<end.Length-2; i++)
    {        
        if (end[i] < end[i + 1] && start[i] < start[i + 1] && start[i + 1] >= end[i])
            {
                timespan = (end[i] - start[i]) + (end[i + 1] - end[i]);        
    }

        if (end[i] >= end[i + 1] && start[i] <= start[i + 1])          
            {
                timespan = (end[i] - start[i]);            
        }
        if (end[i] > end[i + 1] && start[i] > start[i + 1] && start[i] <= end[i + 1])         
            {
                timespan = (end[i] - start[i]) + (end[i + 1] - end[i]);
            }
        if  (end[i] <= end[i + 1] && start[i] >= start[i + 1])    
            {        
                    timespan = (end[i + 1] - start[i + 1]);
            }

            timeDifference = timespan.Days + timeDifference;                          

        } 
              MessageBox.Show(timeDifference.ToString()); 
  }
        }}
  • my question likes yours but here in the result i should got 2192 bu program shoes 2192+ 731 because it doesnot cut oerlap year when start[i] =start[i+1] and end[i]=end[i+1] – user2951991 Nov 05 '13 at 10:27
0

What about checking if your time periods do NOT overlap? Then if the not-overlapping condition is false, that means they DO overlap:

    bool NotOverlapping = (start1 < start2 && end1 < start2) || (start1 > end2 && end1 > end2); 
    return !NotOverlapping // !NotOverlapping == Overlapping
Muryan
  • 51
  • 6