9

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! =)

CSharpNoob
  • 1,271
  • 7
  • 18
  • 27

6 Answers6

19

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
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 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 Sep 15 '10 at 13:11
  • 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 Sep 15 '10 at 13:15
  • 1
    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 Sep 15 '10 at 13:17
  • 1
    @Lazarus: That code won't work if you try to add 3 days to Friday - it'll give Monday instead of Wednesday. – Jon Skeet Sep 15 '10 at 13:35
  • @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 Sep 15 '10 at 13:36
  • Thanks Jon. I'll convert it to extension method .. =) – CSharpNoob Sep 15 '10 at 13:52
  • @Jon-Skeet: Cracked it in the end `if ((int)specificDate.DayOfWeek + workingdaysToAdd > 5) specificDate = specificDate.AddDays(2);` replaces the loop. – Lazarus Sep 15 '10 at 15:00
  • @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 Sep 15 '10 at 15:10
  • @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 Sep 15 '10 at 15:22
  • @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 Sep 15 '10 at 15:25
  • @Jon-Skeet: I see what you are saying & I do agree that you'd have to resort to documentation to be certain of what I was doing where you could sit down with pen and paper to walk through your loop to understand it although on paper for Friday + 3 Working days I get Tuesday as the result. `@i==0 (date = Friday, date.AddDays(1) = Saturday, !IsWeekday, date.AddDays(1) = Sunday), @i==1 (date = Sunday, date.AddDays(1) = Monday, IsWeekday), @i==2 (date = Monday, date.AddDays(1) = Tuesday), return date = Tuesday` What am I missing? – Lazarus Sep 15 '10 at 15:54
  • @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 Sep 15 '10 at 16:08
  • @Jon-Skeet: I claim the _n_ th amendment "Lack of Coffee" and retreat to the corner :) – Lazarus Sep 16 '10 at 08:46
  • @JonSkeet Here is an tested algorithm to do it without looping over: http://stackoverflow.com/a/33943576/4389984 (second solution) Apart from having knowledge of day of the week mapping (1-7 ) and readability, do you see any flaw in the logic? Thanks! – Anmol Gupta Nov 27 '15 at 07:51
  • 3
    @Anmol: The lack of readability makes it much harder to search for flaws, to be honest. That's why I like the looping option - it's dead easy to understand, and unless you know you *are* adding a lot of work days (not the case in this question) I'd favour readability over efficiency. Additionally, it's *really* easy to go from a naive implementation which ignores public holidays to a naive implementation which includes public holidays, given a list of public holidays - it's *much* harder to do the same with a calculating version. – Jon Skeet Nov 27 '15 at 07:56
  • I get it. Especially considering public holidays is really getting complex in calculating version. Thanks for the insight! – Anmol Gupta Nov 27 '15 at 08:16
2

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
  • 476
  • 3
  • 11
  • 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 Sep 15 '10 at 13:40
  • yes, its wrong, I didn`t test well before post. sorry guys, my fault. – Leo Sep 15 '10 at 15:53
0

Is an old post but somebody could be interested in an extension that handles also negative days. (I've reworked @Jon answer)

    public static DateTime AddWeekDays(this DateTime start, int days)
    {
        int direction = Math.Sign(days);

        int completeWeeks = days / 5;
        int remaining = days % 5;

        DateTime end = start.AddDays(completeWeeks * 7);

        for (int i = 0; i < remaining * direction; i++)
        {
            end = end.AddDays(direction * 1);
            while (!IsWeekDay(end))
            {
                end = end.AddDays(direction * 1);
            }
        }
        return end;
    }

    private static bool IsWeekDay(DateTime date)
    {
        DayOfWeek day = date.DayOfWeek;
        return day != DayOfWeek.Saturday && day != DayOfWeek.Sunday;
    }
Maurizio Pozzobon
  • 3,044
  • 7
  • 34
  • 44
0

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
  • 2,397
  • 21
  • 24
0
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();
        }
    }
garik
  • 5,669
  • 5
  • 30
  • 42
  • Is it more efficient than mr. Jon Skeet's suggestion? – CSharpNoob Sep 15 '10 at 13:22
  • @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 Sep 15 '10 at 13:31
  • whats the difference between MyAddWorkingDays and AddWorkingDays in your code? – CSharpNoob Sep 15 '10 at 13:41
  • @CSharpNoob MyAddWorkingDays is main, AddWorkingDays is mr. Jon Skeet's one. I have not tested other ones. Try them all if you need – garik Sep 15 '10 at 13:44
  • tried it but it only adds 1 working day if the specified day is Mondays-Fri, and it adds 2 if sat/sun.. =) – CSharpNoob Sep 15 '10 at 13:50
  • @CSharpNoob the first code has typo "currentDate" (I have already fixed it). Try the second one! :) – garik Sep 15 '10 at 13:56
  • 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 Sep 15 '10 at 14:07
  • @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 Sep 15 '10 at 15:29
  • @Jon Skeet oh, thanks! yes of course, you are right. it was expected result. your one is faster, sorry. – garik Sep 15 '10 at 16:24
0

This seems to me the cleanest way:

public static DateTime AddWorkingDays(DateTime date, int daysToAdd)
{
    while (daysToAdd > 0)
    {
        date = date.AddDays(1);

        if (date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday) daysToAdd -= 1;
    }

    return date;
}
tocqueville
  • 5,270
  • 2
  • 40
  • 54