BlackWasp
Algorithms
.NET 1.1+

Calculate the Number of Business Days in a Range

A common task is to calculate the number of business days that exist within a date range. In most western cultures, these are the days from Monday to Friday and excluding Saturdays and Sundays. The resultant number should also exclude national holidays.

Business Days

Software that records or calculates durations often needs to determine the number of days within a date range. This is a simple task when using the DateTime structure. However, when the software is designed for use by businesses, it is sometimes necessary to perform such a calculation using business days. For most western cultures, these are the days between Monday and Friday only. Saturdays and Sundays are weekends and not counted as business days. Furthermore, some dates are national holidays that are also not business days.

In this article we will create a class containing a single public method that calculates the number of business days within a range of dates. The algorithm will first determine the number of days in the range and then subtract the number of weekend days and holidays. It will assume a standard working week starts on Monday and ends on Friday, though it would be an easy task to modify the calculation for other cultures. This version of the algorithm will be inclusive, counting the start and end date. The range of dates from Monday to Friday, assuming no holidays, should return a result of five days.

Calculating the Number of Business Days Between Two Dates

To begin, create new a new console application project. Add a new class to the project named, "BusinessDaysCalculator". This class will contain the algorithm's code.

Creating the Calculate Method

The only public method in the BusinessDaysCalculator class is the one that performs the calculation. This method requires three input parameters. The first two parameters accept the start and end dates for the range. The third parameter is used to provide an array of dates that are holidays. The array can include dates that are not within the main date range. However, these will not affect the final result. The calculation will always return an integer value containing the number of business days within the given range.

To create the signature for the new method, add the following declaration to the BusinessDaysCalculator class:

public int Calculate(DateTime startDate, DateTime endDate, DateTime[] holidays)
{
}

Preparing the Date Range

When working with the date range, we are only interested in the date portion of the start, end and holiday dates. The time part of the DateTimes should be set to midnight. For the start and end date, we should also be able to assume that each date falls on a weekday. Instead of assuming that the values provided to the method meet these criteria, we will create private methods that enforce them, modifying the date range as necessary.

The first of these methods will fix any problems with the start date. First the day of the week will be checked. If the start date falls within a weekend, it will be advanced to the next Monday. Secondly, the time portion of the DateTime will be removed. To create this method, add the following to the class:

private DateTime FixStartDate(DateTime date)
{
    if (date.DayOfWeek == DayOfWeek.Sunday)
        date = date.AddDays(1);
    else if (date.DayOfWeek == DayOfWeek.Saturday)
        date = date.AddDays(2);
    return date.Date;
}

A similar method will fix problems with the end date. The difference here is that if the date is a Saturday or Sunday, it will be changed to the preceding Friday.

private DateTime FixEndDate(DateTime date)
{
    if (date.DayOfWeek == DayOfWeek.Sunday)
        date = date.AddDays(-2);
    else if (date.DayOfWeek == DayOfWeek.Saturday)
        date = date.AddDays(-1);
    return date.Date;
}

To ensure that any problems with the input dates are rectified before the calculation occurs, add calls to the two new methods by adding the following code to the Calculate method:

startDate = FixStartDate(startDate);
endDate = FixEndDate(endDate.Date);

Checking for Invalid Date Ranges

The above code ensures that both the start date and end date are valid for use in the algorithm. However, it does not guarantee that the range itself is valid. It is possible that the start date occurs after the end date, or that the original dates provided to the method both fell within the same weekend. In either case, the range should be considered invalid.

If the range is invalid, we will simply return zero. To include this final validation check, add the following code to the end of the Calculate method. The code checks the date range and returns zero if it is invalid. If it is valid, another private method, which calculates the number of business days in the range, is called.

if (startDate > endDate)
    return 0;
else
    return CalculateDifference(startDate, endDate, holidays);

Checking if a Holiday is Within the Date Range

The next private method that we will add to the class is used to determine whether a single holiday affects the calculation. This method returns a Boolean result, with true indicating that the holiday is within the date range and does not fall on a weekend.

Add the following method to the BusinessDaysCalculator class:

private bool HolidayInRange(DateTime startDate, DateTime endDate, DateTime holiday)
{
    return (holiday.Date >= startDate
        && holiday <= endDate
        && holiday.DayOfWeek != DayOfWeek.Saturday
        && holiday.DayOfWeek != DayOfWeek.Sunday);
}

Counting the Number of Holidays Within the Date Range

The HolidayInRange method is used repeatedly by another private method. The HolidayCount method determines the total number of holidays that affect the calculation. To do so, the method loops through all of the holidays in the array and adds one to a counter each time that the call to HolidayInRange returns true. There is a limitation to this method. If the same date appears twice or more in the holidays array, it will be counted more than once. This can be corrected by ensuring that the array only ever contains unique values.

private int HolidayCount(DateTime startDate, DateTime endDate, DateTime[] holidays)
{
    int holidayCount = 0;
    foreach (DateTime holiday in holidays)
    {
        if (HolidayInRange(startDate, endDate, holiday)) holidayCount++;
    }
    return holidayCount;
}

If you are using a version of the .NET framework that supports Language-Integrated Query (LINQ), you can replace the code in the HolidayCount method with a LINQ expression. The expression below counts the number of holidays that affect the overall calculation. The use of Distinct also ensures that any duplicated holidays are only counted once.

return (from h in holidays
        where HolidayInRange(startDate, endDate, h)
        select h).Distinct().Count();

The Final Calculation

The final task to complete the algorithm is the addition of the CalculateDifference method. This method performs several steps to determine the number of business days in the provided date range. The steps are:

  1. Calculate the difference in days between the two dates. This is achieved by subtracting the start date from the end date. The result of the subtraction is a TimeSpan value containing the number of days in the TotalDays property. The value will include the start but not the end of the range. This will be corrected later to give an inclusive result.
  2. Determine the number of weekends that are in the range by dividing the number of days by seven, discarding any fraction and retaining the integer only. If the day of the week of the end date is before the day of the week of the start date, the weeks value is incremented as an extra weekend exists.
  3. Reduce the difference value to remove the weekends. The difference is lowered by the number of weeks multiplied by two.
  4. Reduce the difference value by the number of holidays in the range.
  5. Add one to the result before returning it to make the value inclusive.
private int CalculateDifference(
    DateTime startDate, DateTime endDate, DateTime[] holidays)
{
    int difference = (int)(endDate - startDate).TotalDays;
    int weeks = difference / 7;
    if (endDate.DayOfWeek < startDate.DayOfWeek) weeks++;
    difference -= weeks * 2;
    difference -= HolidayCount(startDate, endDate, holidays);
    return difference + 1;
}

Using the Calculator

To test the algorithm we can use the Main method of the program. Add the code below to calculate the number of business days in January 2010, assuming no holidays,

DateTime start = new DateTime(2010,1,1);
DateTime end = new DateTime(2010, 1, 31);
BusinessDaysCalculator calc = new BusinessDaysCalculator();
int days = calc.Calculate(start, end, new DateTime[0]);

Console.WriteLine(days);    // Outputs 21

To use the algorithm and include holidays in the calculation, try the following:

DateTime[] holidays = new DateTime[3];
holidays[0] = new DateTime(2010, 1, 11);
holidays[1] = new DateTime(2010, 1, 12);
holidays[2] = new DateTime(2010, 1, 13);

DateTime start = new DateTime(2010, 1, 1);
DateTime end = new DateTime(2010, 1, 31);
BusinessDaysCalculator calc = new BusinessDaysCalculator();
int days = calc.Calculate(start, end, holidays);

Console.WriteLine(days);    // Outputs 18
Link to this Page10 January 2010
TwitterTwitter RSS Feed RSS