views:

219

answers:

7

Hi guys, need some help. I need to count regular working days for a given date period, for example, in our country, we have 5 regular working days monday to friday, then in code i need to exclude saturdays and sundays when I use it on my computations.

I need an algorithm something like this in C#:

    int GetRegularWorkingDays(DateTime startDate, DateTime endDate)
    {

       int nonWorkingDays = ((endDate - startDate) % 7) * 2;

       return (endDate - startDate) - nonWorkingDays;
    }

I know my draft is way way off :(. Thanks in advance. =)

PS: Guys please up-vote the best/fastest/most efficient answer below. Thanks =)

+2  A: 

You could try a simple method of just counting the days. If this is usually done for periods of time like months and not years and isn't called repeatedly then this will not be a performance hit to just walk it.

int GetWorkingDays(DateTime startDate, DateTime endDate)
{
    int count = 0;
    for (DateTime currentDate = startDate; currentDate < endDate; currentDate.AddDays(1))
    {
        if (currentDate.DayOfWeek == DayOfWeek.Sunday || currentDate.DayOfWeek == DayOfWeek.Saturday)
        {
            continue;
        }
        count++;
    }
    return count;
}
Brendan Enrick
I also need to track the nonworking days, if I replace the continue with the nonworkingdays counter??
CSharpNoob
First you need to have table storing all the holidays(pre-defined) and check if current_day matches with day_holiday_table...you should not count that and rest are working days.
luvboy
i have different solution treating holidays,. and creating a database table just for this is really not my option. I just literraly want to count Mondays-Fridays.. Thanks btw =)
CSharpNoob
+2  A: 

Not very fast, but this will do the trick:

int GetRegularWorkingDays(DateTime start, DateTime end)
{
    return (
        from day in Range(start, end)
        where day.DayOfWeek != DayOfWeek.Saturday
        where day.DayOfWeek != DayOfWeek.Sunday
        select day).Count();
}

IEnumerable<DateTime> Range(DateTime start, DateTime end)
{
    while (start <= end)
    {
        yield return start;
        start = start.AddDays(1);
    }
}
Steven
Thanks sir, But I also need the most efficient because I'll be using it inside a For Loop with average of 5000 rows.. thanks..
CSharpNoob
+3  A: 

One-liner!

int workingDays = Enumerable.Range(0, Convert.ToInt32(endDate.Subtract(startDate).TotalDays)).Select(i=>new [] { DayOfWeek.Saturday, DayOfWeek.Sunday }.Contains(startDate.AddDays(i).DayOfWeek) ? 0 : 1).Sum();

Or more efficient:

DayOfWeek currDay = startDate.DayOfWeek;
int nonWorkingDays = 0;

foreach(var i in Enumerable.Range(0, Convert.ToInt32(endDate.Subtract(startDate).TotalDays)))
{
     if(currDay == DayOfWeek.Sunday || currDay == DayOfWeek.Saturday) 
          nonWorkingDays++;
     if((int)++currDay > 6) currDay = (DayOfWeek)0;
}
Jan Jongboom
I don't call that a one-liner. I call that a very long line :-)
Steven
man this awesome, but how about its efficiency? thanks, i mean performance hit
CSharpNoob
Mwah, if you don't do bank software it doesn't really care, but you are constructing an array and a datetime object. Check my edit for a supa dupa efficient way.
Jan Jongboom
is this more faster than Mr.Steven's suggestion with Linq?
CSharpNoob
Yes, you don't create a new `DateTime` object for every iteration.
Jan Jongboom
Please turn that one-liner into several lines. It's not readable as it is now.
M4N
A: 

Simple method to get work days:

int GetRegularWorkingDays(DateTime startDate, DateTime endDate)
{
    int total = 0;

    if (startDate < endDate)
    {
        var days = endDate - startDate;

        for( ; startDate < endDate; startDate = startDate.AddDays(1) )
        {
            switch(startDate.DayOfWeek)
            {
                case DayOfWeek.Saturday :
                case DayOfWeek.Sunday :                     
                    break;
                default:
                    total++;
                    break;
            }
        }
    }
    return total;
}
jlafay
+14  A: 

Check out this example on Code Project that uses a very efficient way that doesn't involve any looping ;)

It uses this alogrithm:

  1. Calculate the number of time span in terms of weeks. Call it, W.
  2. Deduct the first week from the number of weeks. W= W-1
  3. Multiply the number of weeks with the number of working days per week. Call it, D.
  4. Find out the holidays during the specified time span. Call it, H.
  5. Calculate the days in the first week. Call it, SD.
  6. Calculate the days in the last week. Call it, ED.
  7. Sum up all the days. BD = D + SD + ED � H.
w69rdy
This is really key. If you have a 6 or 7 (or 66 or 77) week span, you don't need to iterate through week after week skipping Sat and Sun. The first week and last week are special cases, you don't know if the span includes weekdays or weekend days, but the big middle is just 5-working-days-per-week less holiday. This will save you a lot of computation.
Kate Gregory
+1: I am not sure, why the looping stuff gets all voted up, when this solution is essentially O(1)... Step 4 is not required according to the OP's question, though.
Dirk
currently reading it. this is interesting, no loops..
CSharpNoob
@Kate: Exactly, the looping becomes a waste of time as you can work out quite easily the number of workdays in the middle, its just the start and end weeks that need special attention
w69rdy
+1  A: 

You could do it with a time line helper class - this class also allows for other intervals:

public class TimeLine
{
    public static IEnumerable<DateTime> CreateTimeLine(DateTime start, TimeSpan interval) {
        return TimeLine.CreateTimeLine(start, interval, DateTime.MaxValue);
    }

    public static IEnumerable<DateTime> CreateTimeLine(DateTime start, TimeSpan interval, DateTime end) {
        var currentVal = start;
        var endVal = end.Subtract(interval);

        do {
            currentVal = currentVal.Add(interval);
            yield return currentVal;
        } while (currentVal <= endVal);
    }
}

To solve your problem you can do the following:

var workingDays = TimeLine.CreateTimeLine(DateTime.Now.Date, TimeSpan.FromDays(1), DateTime.Now.Date.AddDays(30))
                          .Where(x => x.DayOfWeek != DayOfWeek.Saturday && x.DayOfWeek != DayOfWeek.Sunday);
var noOfWorkingDays = workingDays.Count();

This time line class can be used for any continuous time line of any interval.

Obalix
+1  A: 

I wrote a type extender to allow me to add (or subtract) weekdays to a given date. Maybe this will help you. Works great, so please vote for my post if this helped you.

    /// <summary>
    /// Adds weekdays to date
    /// </summary>
    /// <param name="value">DateTime to add to</param>
    /// <param name="weekdays">Number of weekdays to add</param>
    /// <returns>DateTime</returns>
    public static DateTime AddWeekdays(this DateTime value, int weekdays)
    {
        int direction = Math.Sign(weekdays);
        int initialDayOfWeek = Convert.ToInt32(value.DayOfWeek);

        //---------------------------------------------------------------------------
        // if the day is a weekend, shift to the next weekday before calculating
        if ((value.DayOfWeek == DayOfWeek.Sunday && direction < 0)
            || (value.DayOfWeek == DayOfWeek.Saturday && direction > 0))
        {
            value = value.AddDays(direction * 2);
            weekdays += (direction * -1); // adjust days to add by one
        }
        else if ((value.DayOfWeek == DayOfWeek.Sunday && direction > 0)
            || (value.DayOfWeek == DayOfWeek.Saturday && direction < 0))
        {
            value = value.AddDays(direction);
            weekdays += (direction * -1); // adjust days to add by one
        }
        //---------------------------------------------------------------------------

        int weeksBase = Math.Abs(weekdays / 5);
        int addDays = Math.Abs(weekdays % 5);

        int totalDays = (weeksBase * 7) + addDays;
        DateTime result = value.AddDays(totalDays * direction);

        //---------------------------------------------------------------------------
        // if the result is a weekend, shift to the next weekday
        if ((result.DayOfWeek == DayOfWeek.Sunday && direction > 0)
            || (result.DayOfWeek == DayOfWeek.Saturday && direction < 0))
        {
            result = result.AddDays(direction);
        }
        else if ((result.DayOfWeek == DayOfWeek.Sunday && direction < 0)
            || (result.DayOfWeek == DayOfWeek.Saturday && direction > 0))
        {
            result = result.AddDays(direction * 2);
        }
        //---------------------------------------------------------------------------

        return result;
    }
Brad
I also need this.. thanks a lot =)
CSharpNoob
i have a lot of these type extenders that i put in the System namespace so they're always available. i'm glad this could help you -- it took a LONG time to get right! :)
Brad
this just in.. I love extension methods!
jlafay
Sir, there's a bug when I try to add 3 business days from the value .. when it is Saturday and Sunday, the answer supposed to be wednesday but it returns friday...
CSharpNoob
CSharpNoob, I posted an edit that fixes this problem.
Brad