Some of the requirements around nested ranges and other corner cases are not exactly clear. But here's another algorithm that seems to do what you're after. The usual caveats of limited testing apply of course - could not work at all for corner cases I didn't test.
The algorithm relies on sorting the data first. If you can't do that then this won't work. The algorithm itself is as follows:
static IEnumerable<DateRange> ReduceToOverlapping(IEnumerable<DateRange> source)
{
if (!source.Any())
yield break;
Stack<DateRange> stack = new Stack<DateRange>();
foreach (var r in source)
{
while (stack.Count > 0 && r.Start > stack.Peek().End)
stack.Pop();
foreach (var left in stack)
{
if (left.GetOverlap(r) is DateRange overlap)
yield return overlap;
}
stack.Push(r);
}
}
The DateRange is a simple class to hold the dates you presented. It looks like this:
class DateRange
{
public DateRange(DateRange other)
{ this.Start = other.Start; this.End = other.End; }
public DateRange(DateTime start, DateTime end)
{ this.Start = start; this.End = end; }
public DateRange(string start, string end)
{
const string format = "dd/MM/yyyy";
this.Start = DateTime.ParseExact(start, format, CultureInfo.InvariantCulture);
this.End = DateTime.ParseExact(end, format, CultureInfo.InvariantCulture);
}
public DateTime Start { get; set; }
public DateTime End { get; set; }
public DateRange GetOverlap(DateRange next)
{
if (this.Start <= next.Start && this.End >= next.Start)
{
return new DateRange(next.Start, this.End < next.End ? this.End : next.End);
}
return null;
}
}
As I mentioned this is used by sorting it first. Example of sorting and calling the method on some test data is here:
static void Main(string[] _)
{
foreach (var (inputdata, expected) in TestData)
{
var sorted = inputdata.OrderBy(x => x.Start).ThenBy(x => x.End);
var reduced = ReduceToOverlapping(sorted).ToArray();
if (!Enumerable.SequenceEqual(reduced, expected, new CompareDateRange()))
throw new ArgumentException("failed to produce correct result");
}
Console.WriteLine("all results correct");
}
To Test you'll need a equality comparer and the test data which is here:
class CompareDateRange : IEqualityComparer<DateRange>
{
public bool Equals([AllowNull] DateRange x, [AllowNull] DateRange y)
{
if (null == x && null == y)
return true;
if (null == x || null == y)
return false;
return x.Start == y.Start && x.End == y.End;
}
public int GetHashCode([DisallowNull] DateRange obj)
{
return obj.Start.GetHashCode() ^ obj.End.GetHashCode();
}
}
public static (DateRange[], DateRange[])[] TestData = new (DateRange[], DateRange[])[]
{
(new DateRange[]
{
new DateRange("01/01/2020", "18/01/2020"),
new DateRange("08/01/2020", "17/01/2020"),
new DateRange("09/01/2020", "15/01/2020"),
new DateRange("14/01/2020", "20/01/2020"),
},
new DateRange[]
{
new DateRange("08/01/2020", "17/01/2020"),
new DateRange("09/01/2020", "15/01/2020"),
new DateRange("09/01/2020", "15/01/2020"),
new DateRange("14/01/2020", "15/01/2020"),
new DateRange("14/01/2020", "17/01/2020"),
new DateRange("14/01/2020", "18/01/2020"),
}),
(new DateRange[]
{
new DateRange("01/01/2020", "10/01/2020"),
new DateRange("08/01/2020", "20/01/2020"),
},
new DateRange[]
{
new DateRange("08/01/2020", "10/01/2020"),
}),
(new DateRange[]
{
new DateRange("01/01/2020", "10/01/2020"),
new DateRange("08/01/2020", "20/01/2020"),
new DateRange("18/01/2020", "22/01/2020"),
},
new DateRange[]
{
new DateRange("08/01/2020", "10/01/2020"),
new DateRange("18/01/2020", "20/01/2020"),
}),
(new DateRange[]
{
new DateRange("01/01/2020", "18/01/2020"),
new DateRange("08/01/2020", "10/01/2020"),
new DateRange("18/01/2020", "22/01/2020"),
},
new DateRange[]
{
new DateRange("08/01/2020", "10/01/2020"),
new DateRange("18/01/2020", "18/01/2020"),
}),
};