How to get first two consecutive datetime points which do not reside within a time period using Linq?

c#, linq

Solution

It's not very efficient nor very readable, but you can do it in single query (see efficient solution at the bottom):

var twoHours = list.Where(d => d < blackoutStartTime || blackoutEndTime < d)
                   .OrderBy(d => d) // if sequence is not ordered
                   .GroupBy(d => blackoutEndTime < d)
                   .OrderBy(g => g.Key)
                   .Select(g => g.Take(2))
                   .Where(g => g.Count() == 2)
                   .SelectMany(g => g)
                   .Take(2);

Output:

7/8/2014 04:00:00
7/8/2014 05:00:00

Explanation:

- Filter out dates which does not fall in range - we don't need them

- Group all dates in two groups - dates less than range and dates bigger than range

- Order two groups so that smaller dates group will be first

- Select only first two dates from each group

- Take those groups which have at least two dates

- Project filtered result into flat sequence of dates

- Select first two, if any

More efficient way (if sequence is sorted, otherwise you should sort it before querying) - a little improved suggestion by Jim Mischel (I would go two queries way for much better readability):

var twoHours = list.TakeWhile(d => d < blackoutStartTime).Take(2).ToList();

if (twoHours.Count < 2)
    twoHours = list.SkipWhile(d => d <= blackoutEndTime).Take(2).ToList();

What was improved - you don't need to save each query result into list. That will enumerate all items which match condition and create new list in memory. If you have many items before range, or if you have less than two items before range and many items after range - that is not what you want. So, take only first two items and save them to list. In ideal world you would enumerate only first two items an stop. If not, then you will enumerate all items till the range end + 2.

Problem

I have a list of datetime values. I am trying to get the first two consecutive datetime values which reside outside of a time range using Linq. I am not sure how to do this. Example data (can be copied into LinqPad: ``` List<DateTime> list = new List<DateTime> { DateTime.Parse("07/08/2014 01:00 AM"), DateTime.Parse("07/08/2014 02:00 AM"), DateTime.Parse("07/08/2014 03:00 AM"),DateTime.Parse("07/08/2014 04:00 AM"),DateTime.Parse("07/08/2014 05:00 AM"), }; DateTime blackoutStartTime = DateTime.Parse("07/08/2014 02:00 AM"); DateTime blackoutEndTime = DateTime.Parse("07/08/2014 03:00 AM"); ``` I tried this which is wrong: ``` var twoHours = list.Where(e => e <= blackoutStartTime || e >= blackoutEndTime) .Take(2); ``` I am expecting the result to be the last two hours, 4AM and 5AM. The two hours in any example should be either before the blackout time range (if there are at least two hours) or after the blackout time range (like in example here).

Original source