public interface ITimeRange
{
DateTime? Start { get; }
DateTime? End { get; }
}
public static class TimeRangeExtensions
{
public static bool HasOverlap(this ITimeRange timeRange, ITimeRange outer) => HasOverlapSimple(timeRange, outer);
public static TimeSpan GetOverlap(this ITimeRange timeRange, ITimeRange outer) => DateRangeOverlap(timeRange.Start, timeRange.End, outer.Start, outer.End);
private static TimeSpan DateRangeOverlap(DateTime? min1, DateTime? max1, DateTime? min2, DateTime? max2)
{
return DateRangeOverlap(
min1 ?? DateTime.MinValue,
max1 ?? DateTime.MaxValue,
min2 ?? DateTime.MinValue,
max2 ?? DateTime.MaxValue);
}
private static TimeSpan DateRangeOverlap(DateTime min1, DateTime max1, DateTime min2, DateTime max2)
{
if (min1 > max1 || min2 > max2)
return new TimeSpan(0);
if (min1 == min2 && max1 == max2)
return new TimeSpan(0);
if (max2 <= min1 || min2 >= max1)
return new TimeSpan(0);
if (min1 <= min2 && max1 >= max2)
return (max2 - min2).Duration();
if (min1 >= min2 && max1 <= max2)
return (max1 - min1).Duration();
if (min1 >= min2 && max1 >= max2 && max2 >= min1)
return (max2 - min1).Duration();
if (min1 <= min2 && max1 >= min2 && max2 >= max1)
return (max1 - min2).Duration();
return new TimeSpan(0);
}
private static bool HasOverlapSimple(this ITimeRange timeRange, ITimeRange outer) => HasOverlapSimple(timeRange.Start, timeRange.End, outer.Start, outer.End);
private static bool HasOverlapSimple(DateTime? min1, DateTime? max1, DateTime? min2, DateTime? max2)
{
return HasOverlapSimple(
min1 ?? DateTime.MinValue,
max1 ?? DateTime.MaxValue,
min2 ?? DateTime.MinValue,
max2 ?? DateTime.MaxValue);
}
private static bool HasOverlapSimple(DateTime min1, DateTime max1, DateTime min2, DateTime max2)
{
var isExact = min1 == min2 && max1 == max2;
if (isExact) return true;
// if one of the periods is a single point in time
// then match inclusive at the start or end (as overlap)
var isInclusive = min1 == max1 || min2 == max2;
if (isInclusive)
return min1 <= max2 && max1 >= min2;
return min1 < max2 && max1 > min2;
}
}
public sealed class TimeRange : ITimeRange
{
public TimeRange(DateTime? start, DateTime? end)
{
Start = start;
End = end;
}
public DateTime? Start { get; set; }
public DateTime? End { get; set; }
public static ITimeRange Today => new TimeRange(DateTime.Today, DateTime.Today.AddDays(1).AddSeconds(-1));
public static ITimeRange From(DateTime? start, DateTime? end) => new TimeRange(start, end);
public static ITimeRange GetRelativeYear(int relative = 0)
{
var month = DateTime.Now.AddYears(relative);
var start = new DateTime(month.Year, 1, 1);
var end = start.AddYears(1).AddSeconds(-1);
return new TimeRange(start, end);
}
public static ITimeRange GetRelativeMonth(int relative = 0)
{
var month = DateTime.Now.AddMonths(relative);
var start = new DateTime(month.Year, month.Month, 1);
var end = start.AddMonths(1).AddSeconds(-1);
return new TimeRange(start, end);
}
public static ITimeRange GetRelativeWeek(int relative)
{
var date = DateTime.Now;
while (date.DayOfWeek != DayOfWeek.Monday)
date = date.AddDays(-1);
date = date.AddDays(relative * 7);
return new TimeRange(date, date.AddDays(6));
}
public static ITimeRange GetRelativeDay(int relative)
{
var date = DateTime.Today;
date = date.AddDays(relative);
return new TimeRange(date, date.AddDays(1).AddSeconds(-1));
}
public static string AsString(ITimeRange period) => $"{period.Start:s}-{period.End:s}";
// [---------------] [----------] <= children
// [---------------] <= children
// ==>
// [-------------------------] <= parent
// [-----][----][---][-------]
public static IEnumerable<TimeRange> GetPeriodsFlattened(ITimeRange parent, IEnumerable<ITimeRange> children)
{
if (!children.Any())
{
yield return new TimeRange(parent.Start, parent.End);
yield break;
}
var allTimesInParent = children.SelectMany(x => LinqHelper.Select(x.Start, x.End))
.Concat(LinqHelper.Select(parent.Start, parent.End))
.Where(x => x.HasValue && x >= parent.Start && x <= parent.End)
.OrderBy(x => x).Distinct().ToList();
if (allTimesInParent.Count < 2)
{
yield return new TimeRange(parent.Start, parent.End);
yield break;
}
DateTime? prev = null;
foreach (var curr in allTimesInParent)
{
if (prev != null)
yield return new TimeRange(prev, curr);
prev = curr;
}
}
}
// With number ranges:
private static bool HasOverlapSimple(int? min1, int? max1, int? min2, int? max2)
{
return HasOverlapSimple(
min1 ?? int.MinValue,
max1 ?? int.MaxValue,
min2 ?? int.MinValue,
max2 ?? int.MaxValue);
}
private static bool HasOverlapSimple(int min1, int max1, int min2, int max2)
{
var isExact = min1 == min2 && max1 == max2;
if (isExact) return true;
// if one of the ranges is a single point
// then match inclusive at the start or end (as overlap)
var isInclusive = min1 == max1 || min2 == max2;
if (isInclusive)
return min1 <= max2 && max1 >= min2;
return min1 < max2 && max1 > min2;
}
private static bool HasOverlapSimple(double? min1, double? max1, double? min2, double? max2)
{
return HasOverlapSimple(
min1 ?? double.MinValue,
max1 ?? double.MaxValue,
min2 ?? double.MinValue,
max2 ?? double.MaxValue);
}
private static bool HasOverlapSimple(double min1, double max1, double min2, double max2)
{
var isExact = min1 == min2 && max1 == max2;
if (isExact) return true;
// if one of the ranges is a single point
// then match inclusive at the start or end (as overlap)
var isInclusive = min1 == max1 || min2 == max2;
if (isInclusive)
return min1 <= max2 && max1 >= min2;
return min1 < max2 && max1 > min2;
}using System;
using Domain.TimeRanges;
using NUnit.Framework;
namespace Domain.Tests
{
[TestFixture]
public class TimeRangeOverlapTests
{
[Test]
public void TestSomeNonOverlappingRanges()
{
var firstDate = new DateTime(2022, 6, 15);
var SecondDate = new DateTime(2022, 6, 18);
var range1 = new TimeRange(null, firstDate);
var range2 = new TimeRange(firstDate, SecondDate);
var range3 = new TimeRange(SecondDate, null);
Assert.That(!range1.HasOverlap(range2));
Assert.That(!range1.HasOverlap(range3));
Assert.That(!range2.HasOverlap(range1));
Assert.That(!range2.HasOverlap(range3));
Assert.That(!range3.HasOverlap(range1));
Assert.That(!range3.HasOverlap(range2));
}
[Test]
public void TestSomeOverlappingRanges()
{
var firstDate = new DateTime(2022, 6, 15);
var SecondDate = new DateTime(2022, 6, 18);
var range1 = new TimeRange(null, SecondDate);
var range2 = new TimeRange(firstDate, null);
var range3 = new TimeRange(firstDate, firstDate);
var range4 = new TimeRange(firstDate, firstDate);
var range5 = new TimeRange(firstDate, SecondDate);
var range6 = new TimeRange(SecondDate, SecondDate);
Assert.That(range1.HasOverlap(range2));
Assert.That(range3.HasOverlap(range4));
Assert.That(range5.HasOverlap(range6));
}
}
}Old code:
public static TimeSpan DateRangeOverlapTimeSpan(DateTime min1, DateTime max1, DateTime min2, DateTime max2)
{
if (min1 > max1 || min2 > max2)
{
throw new Exception("Invalid date range");
}
// Cases:
// [---- Date1 ----]
// [--2C--]
// [------------2D------------]
// [----2A----] [----2B----]
// [--E--] [--F--]
// 2E, 2F: skip
if (max2 <= min1 || min2 >= max1)
{
return new TimeSpan(0);
}
// 2C
if (min1 <= min2 && max1 >= max2)
{
return (max2 - min2).Duration();
}
// 2D
if (min1 >= min2 && max1 <= max2) { return (max1 - min1).Duration(); } // 2A if (min1 >= min2 && max1 >= max2 && max2 >= min1)
{
return (max2 - min1).Duration();
}
// 2B
if (min1 <= min2 && max1 >= min2 && max2 >= max1)
{
return (max1 - min2).Duration();
}
return new TimeSpan(0);
}
public static TimeSpan TimespanRangeOverlapTimeSpan(TimeSpan min1, TimeSpan max1, TimeSpan min2, TimeSpan max2)
{
if (min1 > max1 || min2 > max2)
{
throw new Exception("Invalid date range");
}
// Cases:
// [---- Date1 ----]
// [--2C--]
// [------------2D------------]
// [----2A----] [----2B----]
// [--E--] [--F--]
// 2E, 2F: skip
if (max2 <= min1 || min2 >= max1) {
return new TimeSpan(0);
}
// 2C
if (min1 <= min2 && max1 >= max2)
{
return (max2 - min2).Duration();
}
// 2D
if (min1 >= min2 && max1 <= max2) { return (max1 - min1).Duration(); } // 2A if (min1 >= min2 && max1 >= max2 && max2 >= min1)
{
return (max2 - min1).Duration();
}
// 2B
if (min1 <= min2 && max1 >= min2 && max2 >= max1)
{
return (max1 - min2).Duration();
}
return new TimeSpan(0);
}
public void TestTimespanRangeOverlapTimeSpan()
{
// Cases:
// [---- Date1 ----]
// [--2C--]
// [------------2D------------]
// [----2A----] [----2B----]
// [--E--] [--F--]
TimeSpan min1 = new TimeSpan(8, 0, 0);
TimeSpan max1 = new TimeSpan(17, 0, 0);
TimeSpan min2;
TimeSpan max2;
// Case C:
min2 = new TimeSpan(10, 0, 0);
max2 = new TimeSpan(11, 0, 0);
Assert.AreEqual(DateTimeHelper.TimespanRangeOverlapTimeSpan(min1, max1, min2, max2), TimeSpan.FromHours(1));
Assert.AreEqual(DateTimeHelper.TimespanRangeOverlapTimeSpan(min2, max2, min1, max1), TimeSpan.FromHours(1));
// Case D:
min2 = new TimeSpan(7, 0, 0);
max2 = new TimeSpan(18, 0, 0);
Assert.AreEqual(DateTimeHelper.TimespanRangeOverlapTimeSpan(min1, max1, min2, max2), TimeSpan.FromHours(9));
Assert.AreEqual(DateTimeHelper.TimespanRangeOverlapTimeSpan(min2, max2, min1, max1), TimeSpan.FromHours(9));
// Case A:
min2 = new TimeSpan(7, 0, 0);
max2 = new TimeSpan(10, 0, 0);
Assert.AreEqual(DateTimeHelper.TimespanRangeOverlapTimeSpan(min1, max1, min2, max2), TimeSpan.FromHours(2));
Assert.AreEqual(DateTimeHelper.TimespanRangeOverlapTimeSpan(min2, max2, min1, max1), TimeSpan.FromHours(2));
// Case B:
min2 = new TimeSpan(16, 0, 0);
max2 = new TimeSpan(18, 0, 0);
Assert.AreEqual(DateTimeHelper.TimespanRangeOverlapTimeSpan(min1, max1, min2, max2), TimeSpan.FromHours(1));
Assert.AreEqual(DateTimeHelper.TimespanRangeOverlapTimeSpan(min2, max2, min1, max1), TimeSpan.FromHours(1));
// Case E:
min2 = new TimeSpan(4, 0, 0);
max2 = new TimeSpan(8, 0, 0);
Assert.AreEqual(DateTimeHelper.TimespanRangeOverlapTimeSpan(min1, max1, min2, max2), TimeSpan.FromHours(0));
Assert.AreEqual(DateTimeHelper.TimespanRangeOverlapTimeSpan(min2, max2, min1, max1), TimeSpan.FromHours(0));
// Case F:
min2 = new TimeSpan(17, 0, 0);
max2 = new TimeSpan(18, 0, 0);
Assert.AreEqual(DateTimeHelper.TimespanRangeOverlapTimeSpan(min1, max1, min2, max2), TimeSpan.FromHours(0));
Assert.AreEqual(DateTimeHelper.TimespanRangeOverlapTimeSpan(min2, max2, min1, max1), TimeSpan.FromHours(0));
}
65800cookie-checkC# overlapping time ranges