views:

182

answers:

5

I have trouble doing this. I'm creating a method that add working days on a specific date. for example, I want to add 3 working days to sept 15, 2010 (Wednesday), the method would return sept 20 (Monday next week). it disregards saturday and sunday because its non-working day..

Something like this in C#:

DateTime AddWorkingDays(DateTime specificDate, int workingDaysToAdd)
{
   return specificDate + (workingDaysToAdd - (all saturdays and sundays))
}

I don't consider special holidays on the computations, i just literally want to add days except saturday and sundays.. Thanks in advance! =)

+7  A: 

If you don't need to consider holidays, I would suggest you do something like this:

public static DateTime AddWorkingDays(DateTime specificDate,
                                      int workingDaysToAdd)
{
    int completeWeeks = workingDaysToAdd / 5;
    DateTime date = specificDate.AddDays(completeWeeks * 7);
    workingDaysToAdd = workingDaysToAdd % 5;
    for (int i = 0; i < workingDaysToAdd; i++)
    {
        date = date.AddDays(1);
        while (!IsWeekDay(date))
        {
            date = date.AddDays(1);
        }
    }
    return date;
}

private static bool IsWeekDay(DateTime date)
{
    DayOfWeek day = date.DayOfWeek;
    return day != DayOfWeek.Saturday && day != DayOfWeek.Sunday;
}

It's inefficient, but easy to understand. For an efficient version, you'd work out the number of complete weeks to add as before, but then have a mapping from any "current day of week" and "working days left to add" to "number of actual days to add". Then you could just work out the total number of days to add, and do it in one call.

EDIT: In terms of the level of inefficiency... it's really not very bad. It'll only perform manual "is this a weekend" checks for up to 4 days, which isn't too bad. In particular, despite igor's (current at the time of posting) claims, it's rather faster than his approach, flawed benchmarks notwithstanding ;)

Note that it may not handle negative inputs yet - I haven't checked.

One of the reasons behind the approach I'm using is that it doesn't rely on either me or the code reader knowing what the values in the DayOfWeek enum are. I don't care whether it's 0-6, 1-7, Monday-Sunday, Saturday-Friday... or even if there are completely bizarre values. I only compare for equality, which makes the code more "obviously correct".

Jon Skeet
Jon, would it be more efficient to use `workingDaysToAdd = workingDaysToAdd % 5; date = date.AddDays(workingDaysToAdd); if (date.DayOfWeek == DayOfWeek.Saturday) date.AddDays(2); if (date.DayOfWeek == DayOfWeek.Sunday) date.AddDays(1);` rather than the loop construct? Just curious.
Lazarus
I tested it and it works to what I exactly need. Did you do it on the fly? in less than 5 minutes since I posted it.. man, that's quite awesome.. but I would welcome more efficient answers.. thanks btw..
CSharpNoob
I wouldn't say it's inefficient, but all things are relative. The answer accounts for weeks up front, so the most it will ever have to loop is 6 times (max 4 for workingDays % 5, max 2 for weekend days). Not a tremendous hit.
Anthony Pegram
@Lazarus: That code won't work if you try to add 3 days to Friday - it'll give Monday instead of Wednesday.
Jon Skeet
@CSharpNoob: Yes. Glad it works. I can't say I tested or even compiled it... :( @Anthony: Sure, it's likely to be *fast enough* in most cases... but there's room to improve if it ever becomes a bottleneck.
Jon Skeet
Thanks Jon. I'll convert it to extension method .. =)
CSharpNoob
after testing all the answers, this is as of now, the only one that works perfect ... thanks to all your suggestions guys.. =)
CSharpNoob
@Jon-Skeet: Of course... doh!
Lazarus
@Jon-Skeet: Cracked it in the end `if ((int)specificDate.DayOfWeek + workingdaysToAdd > 5) specificDate = specificDate.AddDays(2);` replaces the loop.
Lazarus
@Lazarus: Yup, something like that will work... but I'd find it harder to read. It's not as *obviously* right... in particular, you need to know the enum values. I don't know off the top of my head what the values for Saturday and Sunday are... which is why I've written the code to not care.
Jon Skeet
@Jon-Skeet: You've shattered my illusions... something you don't know off the top of your head! I will give you that this is framework and not C# so perhaps your status remains intact, albeit a little wobbly :) Sunday = 0, Saturday = 6, so anything bigger than 5 (Friday) means we hit a weekend and need to insert 2 days.
Lazarus
@Lazarus: There's a bigger point here though... do you want to rely on *everyone who reads the code* to know the enum values in order to understand what it's doing? That's what I'm trying to get away from.
Jon Skeet
Lazarus
@Lazarus: You're missing the fact that it's a while loop, not just a single test. Basically I add a day, and then skip over the weekend completely.
Jon Skeet
@Jon-Skeet: I claim the _n_ th amendment "Lack of Coffee" and retreat to the corner :)
Lazarus
The best answer! =) thanks mr Jon Skeets
CSharpNoob
+1  A: 

Here's what you need :

Updated :

public static DateTime AddWeekdays(DateTime start, int days)
    {
        int remainder = days % 5;
        int weekendDays = (days / 5) * 2;

        DateTime end = start.AddDays(remainder);

        if (start.DayOfWeek == DayOfWeek.Saturday && days > 0)
        {
            // fix for saturday.
            end = end.AddDays(-1);
        }

        if (end.DayOfWeek == DayOfWeek.Saturday && days > 0)
        {
            // add two days for landing on saturday
            end = end.AddDays(2);
        }
        else if (end.DayOfWeek < start.DayOfWeek)
        {
            // add two days for rounding the weekend
            end = end.AddDays(2);
        }

        // add the remaining days
        return end.AddDays(days + weekendDays - remainder);
    }
Manaf Abu.Rous
great! no loops, I'll test it .. thanks
CSharpNoob
but the datetime parameter(start) can also be sunday and saturrday .. :(.. if saturday and sunday, it must return wednesday of the next week,
CSharpNoob
I just edited the do what u asked for.
Manaf Abu.Rous
+1 works perfect dude, and no loops! thankls a lot
CSharpNoob
+1  A: 
int foundWorkingDays = 0;
while (foundWorkingDays < workingDaysToAdd)
{ 
  specificDate= specificDate.AddDays(1); 
  if(specificDate.DayOfWeek != DayOfWeek.Sunday && specificDate.DayOfWeek != DayOfWeek.Saturday)
     foundWorkingDays++;

}
return specificDate;

ADDED:

class Program
    {

        public static DateTime AddWorkingDays(DateTime specificDate,
                                      int workingDaysToAdd)
        {
            int completeWeeks = workingDaysToAdd / 5;
            DateTime date = specificDate.AddDays(completeWeeks * 7);
            workingDaysToAdd = workingDaysToAdd % 5;
            for (int i = 0; i < workingDaysToAdd; i++)
            {
                date = date.AddDays(1);
                while (!IsWeekDay(date))
                {
                    date = date.AddDays(1);
                }
            }
            return date;
        }

        private static bool IsWeekDay(DateTime date)
        {
            DayOfWeek day = date.DayOfWeek;
            return day != DayOfWeek.Saturday && day != DayOfWeek.Sunday;
        }

        public static DateTime MyAddWorkingDays(DateTime specificDate,
                                      int workingDaysToAdd)
        {
            int foundWorkingDays = 0;
            while (foundWorkingDays < workingDaysToAdd)
            {
                specificDate = specificDate.AddDays(1);
                if (specificDate.DayOfWeek != DayOfWeek.Sunday && specificDate.DayOfWeek != DayOfWeek.Saturday)
                    foundWorkingDays++;

            }
            return specificDate;
        }


        static void Main(string[] args)
        {

            DateTime specificDate = DateTime.Now;

            Stopwatch globalTimer = Stopwatch.StartNew();
            Console.WriteLine(AddWorkingDays(specificDate, 300));  // 100000 :)
            globalTimer.Stop();
            Console.WriteLine(globalTimer.ElapsedMilliseconds);

            globalTimer = Stopwatch.StartNew();
            Console.WriteLine(MyAddWorkingDays(specificDate, 300)); // 100000 :)
            globalTimer.Stop();
            Console.WriteLine(globalTimer.ElapsedMilliseconds);



            Console.ReadLine();
        }
    }
igor
Is it more efficient than mr. Jon Skeet's suggestion?
CSharpNoob
@CSharpNoob, no. This is essentially the same logic without handling entire weeks up front. So instead of a loop that could be from 1 to 4 (plus up to two weekend days), you have a loop that will be from 1 to *n* (plus *all* weekend days)
Anthony Pegram
whats the difference between MyAddWorkingDays and AddWorkingDays in your code?
CSharpNoob
@CSharpNoob MyAddWorkingDays is main, AddWorkingDays is mr. Jon Skeet's one. I have not tested other ones. Try them all if you need
igor
+1 thanks man =)
CSharpNoob
tried it but it only adds 1 working day if the specified day is Mondays-Fri, and it adds 2 if sat/sun.. =)
CSharpNoob
@CSharpNoob the first code has typo "currentDate" (I have already fixed it). Try the second one! :)
igor
@CSharpNoob performance, performance, performance :)
igor
Thanks Igor, I have tested it again and it now works perfectly, it's shorter than Jon's and performance wise as you say, its better.. and I'll convert it to extension method.. thanks alot man..
CSharpNoob
@igor: Your benchmark is *horribly* flawed. Try swapping round which result you print first... the results switch too. You're including JIT compilation, including JIT compilation of Console.WriteLine. Try running both versions 10000 times in a loop, even for just 300 days, and you'll find my version is *much* faster than yours... and so it should be, given that you're testing whether *every single day* is on the weekend, rather than just a few of them.
Jon Skeet
@Jon Skeet oh, thanks! yes of course, you are right. it was expected result. your one is faster, sorry.
igor
+2  A: 

A cool way (i think) is put that in a extension method, like:

public static class DateTimeExtensions
{
    public static DateTime AddWorkingDays(this DateTime self, int days)
    {
        self = self.AddDays(days);
        while (self.DayOfWeek == DayOfWeek.Saturday || self.DayOfWeek == DayOfWeek.Sunday)
        {
            self = self.AddDays(1);
        }

        return self;
    }
}

so your final code will look like:

specificDate.AddWorkingDays(3);
Leo Nowaczyk
looks cool man, I'll try it. thanks
CSharpNoob
it only adds one day even if I pass 2 or more days..
CSharpNoob
As CSharpNoob pointed out, this code doesn't quite work correctly, but I really like the approach of using an extension method for this.
Patrick
yes, I like extension methods!
CSharpNoob
yes, its wrong, I didn`t test well before post. sorry guys, my fault.
Leo Nowaczyk
A: 

I have a non-looping type extender that performs weekday math and handles starting and ending on weekends.

http://stackoverflow.com/questions/3708881/counting-regular-working-days-in-a-given-period-of-time/3709215#3709215

Brad