Click here to Skip to main content
15,666,726 members
Articles / Web Development / ASP.NET
Tip/Trick
Posted 14 May 2015

Stats

37.5K views
1.3K downloads
22 bookmarked

Calculate Business Hours Between Two Dates in C#

Rate me:
Please Sign up or sign in to vote.
4.94/5 (10 votes)
14 May 2015CPOL3 min read
C# code to find the business time between two dates and to get the next business day by adding specific duration in minutes

Introduction

The attached code contains logic to do two things with respect to calculation of business hours:

  1. How much business time elapsed (in minutes) between two dates
  2. What is the next business date and time after x minutes

Background

We had these requirements in our project so we had to start. The first check was to find if others already wrote similar logic (don't reinvent the wheel?) and got many but they were not suited well to our requirements. So this code was born and it is not only my effort but there has been a major contribution by Michael and Kathir, my teammates.

Using the Code

You can look at the complete code by downloading the source code. It has three projects:

  1. Console - client
  2. Class library - a tiny library that contains the logic
  3. UnitTest library - a testing project contains 36 test methods to make sure our code works in any given scenario. This is the place you should add if any scenario has been missed but you want to test (and fix the code if that failed)

The class which does the work is Calculation. You must pass the Holidays and open hours (office time) to use this class. While calculating the business time, the code considers only the office hours and work days. All others will not be part of the calculation.

The only constructor is as follows:

C#
public Calculation(IEnumerable<datetime> holidays, OpenHours openHours)
{
    _holidays = dateListToStringList(holidays);
    _openHours = openHours;
}

getElapsedMinutes

This method returns how much business time is left between two dates.

C#
public double getElapsedMinutes(DateTime startDate, DateTime endDate)
{
    if (_openHours.StartHour == 0 || _openHours.EndHour == 0)
        throw new InvalidOperationException
        ("Open hours cannot be started with zero hours or ended with zero hours");
        
    int hour = startDate.Hour;
    int minute = startDate.Minute;
    if (hour == 0 && minute == 0)
    {
        startDate = DateTime.Parse(string.Format("{0} {1}:{2}", 
        startDate.ToString(DateFormat), _openHours.StartHour, _openHours.StartMinute));
    }
    hour = endDate.Hour;
    minute = endDate.Minute;
    if (hour == 0 && minute == 0)
    {
        endDate = DateTime.Parse(string.Format("{0} {1}:{2}", 
        endDate.ToString(DateFormat), _openHours.EndHour, _openHours.EndMinute));
    }
    
    startDate = nextOpenDay(startDate);
    endDate = prevOpenDay(endDate);
    
    if (startDate > endDate)
        return 0;
        
    if (startDate.ToString(DateFormat).Equals(endDate.ToString(DateFormat)))
    {
        if (!isWorkingDay(startDate))
            return 0;
            
        if (startDate.DayOfWeek == DayOfWeek.Saturday || startDate.DayOfWeek == DayOfWeek.Sunday ||
            _holidays.Contains(startDate.ToString(DateFormat)))
            return 0;
            
        if (isDateBeforeOpenHours(startDate))
        {
            startDate = getStartOfDay(startDate);
        }
        if (isDateAfterOpenHours(endDate))
        {
            endDate = getEndOfDay(endDate);
        }
        var endminutes = (endDate.Hour * 60) + endDate.Minute;
        var startminutes = (startDate.Hour * 60) + startDate.Minute;
        
        return endminutes - startminutes;
    }
    
    var endOfDay = getEndOfDay(startDate);
    var startOfDay = getStartOfDay(endDate);
    var usedMinutesinEndDate = endDate.Subtract(startOfDay).TotalMinutes;
    var usedMinutesinStartDate = endOfDay.Subtract(startDate).TotalMinutes;
    var tempStartDate = startDate.AddDays(1);
    var workingHoursInMinutes = (_openHours.EndHour - _openHours.StartHour) * 60;
    var totalUsedMinutes = usedMinutesinEndDate + usedMinutesinStartDate;
    
    for (DateTime day = tempStartDate.Date; day < endDate.Date; day = day.AddDays(1.0))
    {
        if (isWorkingDay(day))
        {
            totalUsedMinutes += workingHoursInMinutes;
        }
    }
    
    return totalUsedMinutes;
}

Testing It

This is tested with 20 test methods (or 20 scenarios) to ensure the code gets the result as expected. The test methods are named as three parts as Roy Osherove suggested in his book The art of Unit Testing. That is:

[methodUnderTest]_[Scenario]_[Expected]

Example:

C#
[TestMethod]
public void getElapsedMinutes_SameDateBut2HoursDiffernt_120()
{
    var calculator = new Calculation(new List<DateTime>(), new OpenHours("08:00;16:00"));

    var startDate = DateTime.Parse("2015-04-07 08:00");
    var endDate = DateTime.Parse("2015-04-07 10:00");

    var result = calculator.getElapsedMinutes(startDate, endDate);

    Assert.AreEqual(120, result);
}

What we test above is to find how much time has passed from 8 to 10 in the same day. And the result should be 120 minutes.

The scenario:

  1. No holidays
  2. Office time is from 08:00 to 16:00
  3. Start and End date is the same
  4. Start hour is 08:00
  5. End hour is 10:00

The complete list of methods is here. You can add any missed scenario by yourself into the code and test.

Image 1

add

This method returns the next business date and time after x minutes. This is useful in scenarios like finding the deadline of a task.

C#
public DateTime add(DateTime date, int minutes)
{
    if (_openHours != null)
    {
        if (_openHours.StartHour == 0 || _openHours.EndHour == 0)
            throw new InvalidOperationException
            ("Open hours cannot be started with zero hours or ended with zero hours");
            
        date = nextOpenDay(date);
        var endOfDay = getEndOfDay(date);
        var minutesLeft = (int)endOfDay.Subtract(date).TotalMinutes;
        
        if (minutesLeft < minutes)
        {
            date = nextOpenDay(endOfDay.AddMinutes(1));
            date = nextOpenDay(date);
            minutes -= minutesLeft;
        }
        var workingHoursInMinutes = (_openHours.EndHour - _openHours.StartHour) * 60;
        while (minutes > workingHoursInMinutes)
        {
            date = getStartOfDay(date.AddDays(1));
            date = nextOpenDay(date);
            minutes -= workingHoursInMinutes;
        }
    }
    
    return date.AddMinutes(minutes);
}

Testing It

This is tested with 16 test methods (or 16 scenarios) to ensure the code gets the result as expected.

Example:

C#
[TestMethod]
public void add_StartTimeIsSaturday_addFromMonday()
{
    var calculator = getEmptyCalculator();

    var saturdayStartDate = DateTime.Parse("2013-01-05 10:00");

    var result = calculator.add(saturdayStartDate, 60);

    Assert.AreEqual(DateTime.Parse("2013-01-07 09:00"), result);//Monday
}

What we test above is to find when is the next business date and time after 60 minutes. In this case, we add 60 minutes on Saturday and the expected result is one hour after the office start hour on Monday.

The complete list of methods is here. You can add any missed scenario by yourself into the code and test.

Image 2

Points of Interest

What I think is the test methods are interesting here because we can know instantly how our code will work in real time and it is easy to fix if anything goes wrong in real time. Just add a test method with that failed scenario and fix the code.

Finally

Please download the source code and explore yourself (you need Visual Studio 12 to open it, but it does work in older versions too - but copy the files manually.)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect CGI
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionDoes not work correctly if I do not wish to convert time of 00:00 to end of day. Pin
Murray Roke2-Nov-22 14:39
Murray Roke2-Nov-22 14:39 
QuestionThe DateTime Add method only works if the StartHour is less than the EndHour Pin
robertkjr3d13-Jun-18 3:56
robertkjr3d13-Jun-18 3:56 
AnswerRe: The DateTime Add method only works if the StartHour is less than the EndHour Pin
robertkjr3d13-Jun-18 8:36
robertkjr3d13-Jun-18 8:36 
Suggestionconversions from or to string Pin
sx200819-May-15 14:23
sx200819-May-15 14:23 
GeneralRe: conversions from or to string Pin
Prabu ram20-May-15 19:28
Prabu ram20-May-15 19:28 
GeneralRe: conversions from or to string Pin
sx200821-May-15 11:53
sx200821-May-15 11:53 
To test if two DateTime values belongs to the same day:
C#
public static bool IsSameDate(DateTime a, DateTime b)
{
   return a.Date == b.Date;
}
It's all there - you just need to find out how Wink | ;)
GeneralRe: conversions from or to string Pin
Prabu ram22-May-15 22:39
Prabu ram22-May-15 22:39 
QuestionDay Light saving - British Summer Time Consideration? Pin
Ravi Lodhiya19-May-15 5:01
professionalRavi Lodhiya19-May-15 5:01 
AnswerRe: Day Light saving - British Summer Time Consideration? Pin
Prabu ram20-May-15 19:01
Prabu ram20-May-15 19:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.