2

I have 2 lists that contain UTC dates. To determine if the lists contain overlapping dates I am doing the following:

list1.Where(
    x =>
    list2.Any(
    y =>
    x.StartDate < y.EndDate &&
    y.StartDate < x.EndDate));

Is there a way to actually return the overlapping periods? These lists are unique within themselves in that list1 will not contain overlapping dates, and list2 will not contain overlapping dates within iteself.

For example, if I have the 2 lists containing the following start and end date times

list 1:
   1/1 5AM - 1/1 10PM
   1/2 4AM - 1/2 8AM
list 2:
   1/1 10AM - 1/1 11AM
   1/1 4PM - 1/1 5PM
   1/2 5AM - 10PM

I would want to return:

1/1 10AM - 1/1 11AM
1/1 4PM - 1/1 5Pm
1/2 5AM - 1/2 8AM

The dates will never be NULL.

I'm thinking taking the MAX of the 2 starts and the MIN of the 2 ends would work, but not sure how that would look syntactically

Tamir Vered
  • 10,187
  • 5
  • 45
  • 57
mameesh
  • 3,651
  • 9
  • 37
  • 47

4 Answers4

3

Given the following DateRange class:

public class DateRange
{
    public DateRange(DateTime startDate, DateTime endDate)
    {
        StartDate = startDate;
        EndDate = endDate;
    }

    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
}

and the followingNaïve DateTime comparison functions:

public static DateTime MinDate(DateTime first, DateTime second)
{
    return first < second ? first : second;
}

public static DateTime MaxDate(DateTime first, DateTime second)
{
    return first > second ? first : second;
}

you can use the following Linq:

list1.SelectMany(x =>
    list2.Where(y => x.StartDate < y.EndDate && y.StartDate < x.EndDate)
         .Select(y => new { first = x, second = y })))
             // Here you will have:
             // {
             //     x = (1/1 5AM - 1/1 10PM), y = (1/1 10AM - 1/1 11AM),
             //     x = (1/1 5AM - 1/1 10PM), y = (1/1 4PM - 1/1 5PM),
             //     x = (1/2 4AM - 1/2 8AM), y = (1/2 5AM - 10PM)
             // }
    .Select(x => new DateRange(MaxDate(x.first.StartDate, x.second.StartDate), MinDate(x.first.EndDate, x.second.EndDate))
             // Here you will have:
             // {
             //     (1/1 10AM - 1/1 11AM),
             //     (1/1 4PM - 1/1 5PM),
             //     (1/2 5AM - 1/2 8AM)
             // }

Notice that this kind of Linq query will be an O(n2) when if the Lists are sorted this can be achieved in O(n) with an algorithm similar to sorted array merging.

Community
  • 1
  • 1
Tamir Vered
  • 10,187
  • 5
  • 45
  • 57
0

This gives me the right answer with your example.

    class DateSpan
    {
        public DateTime StartDate;
        public DateTime EndDate;

        public DateSpan(DateTime start, DateTime end)
        {
            StartDate = start;
            EndDate = end;
        }

        public DateSpan(DateTime start, int duration)
        {
            StartDate = start;
            EndDate = start.AddHours(duration);
        }
    }

    public void TestStuff()
    {
        var dates1 = new System.Collections.Generic.List<DateSpan>();
        dates1.Add(new DateSpan(new DateTime(2016, 1, 1, 5, 0, 0), 17));
        dates1.Add(new DateSpan(new DateTime(2016, 1, 2, 4, 0, 0), 4));

        var dates2 = new System.Collections.Generic.List<DateSpan>();
        dates2.Add(new DateSpan(new DateTime(2016, 1, 1, 10, 0, 0), 1));
        dates2.Add(new DateSpan(new DateTime(2016, 1, 1, 16, 0, 0), 1 ));
        dates2.Add(new DateSpan(new DateTime(2016, 1, 2, 5,0,0), 17 ));

        var e = dates1.SelectMany((DateSpan x) =>
        {
            var result = new List<DateSpan>();
            foreach (var o in dates2.Where(y => x.StartDate < y.StartDate && y.StartDate < x.EndDate).ToList())
            {
                result.Add(new DateSpan(new DateTime(Math.Max(x.StartDate.Ticks, o.StartDate.Ticks)), new DateTime(Math.Min(x.EndDate.Ticks, o.EndDate.Ticks))));
            }
            return result;
        });
        //var d = dates1.Where(x => dates2.Any(y => x.StartDate < y.StartDate && y.StartDate < x.EndDate)).ToList();
    }
Kolichikov
  • 2,944
  • 31
  • 46
0

Below is a code which is built based on your logic. Instead of using where and any combination, I just use cartesian product, which represents your logic lot more clearly. And just combined the Min and Max function to get you what you asked for

struct DateRange {
    public DateTime StartDate, EndDate;
    public override string ToString() {
        return $"{StartDate} - {EndDate}";
    }
};

static void Main(string[] args) {
    var list1 = new List<DateRange>() {
        new DateRange() { StartDate = DateTime.Parse("1/1/2016 5AM"), EndDate = DateTime.Parse("1/1/2016 10PM") },
        new DateRange() { StartDate = DateTime.Parse("1/2/2016 4AM"), EndDate = DateTime.Parse("1/2/2016 8AM") }
    };
    var list2 = new List<DateRange>() {
        new DateRange() { StartDate = DateTime.Parse("1/1/2016 10AM"), EndDate = DateTime.Parse("1/1/2016 11AM")},
        new DateRange() { StartDate = DateTime.Parse("1/1/2016 4PM"), EndDate = DateTime.Parse("1/1/2016 5PM")},
        new DateRange() { StartDate = DateTime.Parse("1/2/2016 5AM"), EndDate = DateTime.Parse("1/2/2016 10PM")}
    };

    var overlapList = from outer in list1
                        from inner in list2
                        where outer.StartDate < inner.EndDate && inner.StartDate < outer.EndDate
                        select new DateRange() {
                            StartDate = new DateTime(Math.Max(outer.StartDate.Ticks, inner.StartDate.Ticks)),
                            EndDate = new DateTime(Math.Min(outer.EndDate.Ticks, inner.EndDate.Ticks))
                        };


}
Vikhram
  • 4,294
  • 1
  • 20
  • 32
0

I used the let clauses in order to make the code more readable:

var q = 
    from x in list1
    from y in list2
    let start = Max(x.StartDate, y.StartDate)
    let end = Min(x.EndDate, y.EndDate)
    where start < end
    select new { start, end };
foreach (var item in q)
{
    Console.WriteLine($"{item.start}-{item.end}");
}

Max and Min methods are from this answer.

Community
  • 1
  • 1
user1414213562
  • 5,583
  • 1
  • 14
  • 10