Search code examples
c#linqdate-range

Checking date range against set of date ranges in a data table


I need to check a user-entered start and end date against a set of start and end dates in a data table to make sure there are no overlaps.

A user requests time off using a start and end date combination. I want to make sure this start and end date is not contained in a set of dates I have read from DB into a data table.

I have used the following but not sure if this is correct. Here "table" contains user's existing time off requests taken from DB, startDate and endDate is what s/he is requesting. Data table has "StartDate" and "EndDate" columns.

private DataTable FilterTable(DataTable table, DateTime startDate, DateTime endDate)
{
    var filteredRows =
        from row in table.Rows.OfType<DataRow>()
        where (DateTime)row["StartDate"] >= startDate
        where (DateTime)row["StartDate"] <= endDate
        select row;

    var filteredTable = table.Clone();
    filteredRows.ToList().ForEach(r => filteredTable.ImportRow(r));
    return filteredTable;
}

If the returned data table has no rows, it is OK otherwise there is overlap.


Solution

  • Using an extension method to check if a Date is between two others,

    public static class DateTimeExt {
        public static bool Between(this DateTime aDate, DateTime start, DateTime end) => start <= aDate && aDate <= end;
    }
    

    You can write an Overlaps method that determines if two ranges overlap:

    public static bool Overlaps(DateTime aPeriodStart, DateTime aPeriodEnd, DateTime bPeriodStart, DateTime bPeriodEnd)
        => aPeriodStart.Between(bPeriodStart, bPeriodEnd) ||
           aPeriodEnd.Between(bPeriodStart, bPeriodEnd) ||
           bPeriodStart.Between(aPeriodStart, aPeriodEnd);
    

    Now with another extension method that converts an IEnumerable<DataRow> to a DataTable containing the rows:

    public static class IEnumerableExt {
        public static DataTable ToDataTable(this IEnumerable<DataRow> src) {
            var ans = src.First().Table.Clone();
            foreach (var r in src)
                ans.ImportRow(r);
            return ans;
        }    
    }
    

    Your final method is straight forward:

    DataTable FilterTable(DataTable timeTable, DateTime startDate, DateTime endDate) =>
        timeTable.AsEnumerable().Where(period => Overlaps(period.Field<DateTime>("StartDate"), period.Field<DateTime>("EndDate"), startDate, endDate)).ToDataTable();
    

    NOTE: If you don't need the answering DataTable for anything, it would be more efficient to replace .ToDataTable() with .Any() and just have the method return a bool indicating if any overlaps exist.