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; }
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