11
\$\begingroup\$

The objective is to find the number of working days (days spanning from Monday to Friday) between two given dates inclusively.

using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 13; i <= 31; i++)
            {
                DateTime start = new DateTime(2014, 10, i);
                DateTime stop = new DateTime(2014, 10, 31);

                int totalWorkingDays = GetNumberOfWorkingDays(start, stop);

                Console.WriteLine("There are {1} working days from Oct {0}, 2014 to Oct 31, 2014.", i, totalWorkingDays);
            }
        }

        private static int GetNumberOfWorkingDays(DateTime start, DateTime stop)
        {
            TimeSpan interval = stop - start;

            int totalWeek = interval.Days / 7;
            int totalWorkingDays = 5 * totalWeek;

            int remainingDays = interval.Days % 7;


            for (int i = 0; i <= remainingDays; i++)
            {
                DayOfWeek test = (DayOfWeek)(((int)start.DayOfWeek + i) % 7);
                if (test >= DayOfWeek.Monday && test <= DayOfWeek.Friday)
                    totalWorkingDays++;
            }

            return totalWorkingDays;
        }
    }
}

Is there the simpler or better way to do so? Any comment and suggestion is welcome.

\$\endgroup\$
  • \$\begingroup\$ You should prefer unit tests over writing to the console. \$\endgroup\$ – RubberDuck Oct 12 '14 at 22:44
  • \$\begingroup\$ @RubberDuck: I am a newbie who don't know what unit test is. ;-) \$\endgroup\$ – kiss my armpit Oct 12 '14 at 23:35
  • 1
    \$\begingroup\$ Then it's a perfect time to learn. =) \$\endgroup\$ – RubberDuck Oct 12 '14 at 23:39
  • \$\begingroup\$ @RubberDuck: OK. Thank you very much for the useful link. :-) \$\endgroup\$ – kiss my armpit Oct 12 '14 at 23:50
  • 1
    \$\begingroup\$ Your country doesn't have holidays? \$\endgroup\$ – svick Oct 13 '14 at 9:02
16
\$\begingroup\$

Personally, I think your approach is very complicated for what it should be trying to do: loop over all days between the start and the end date and count how many of those are not in the weekend.

Translating that sentence to code, gives me this:

private static int GetNumberOfWorkingDaysJeroen(DateTime start, DateTime stop)
{
    int days = 0;
    while(start <= stop)
    {
        if(start.DayOfWeek != DayOfWeek.Saturday && start.DayOfWeek != DayOfWeek.Sunday)
        {
            ++days;
        }
        start = start.AddDays(1);
    }
    return days;
}

I find this much easier to interpret than a bunch of modulos, casts and divisions.

\$\endgroup\$
  • 4
    \$\begingroup\$ I think your approach is neat and I like it. The only objection is that its complexity is linear with respect the number of days in the interval when doing the maths (which I don't think it makes the code too ugly) you can get a constant complexity solution. It is also true that for the vast majority of the use cases the difference in the execution time is probably going to be hardly noticeable so one should choose the most clear approach and avoid premature optimisation. \$\endgroup\$ – mariosangiorgio Oct 12 '14 at 22:32
  • 2
    \$\begingroup\$ Evaluating from Jan 1,0 to Dec 31, 9999 will consume much time I think. \$\endgroup\$ – kiss my armpit Oct 12 '14 at 23:33
  • 1
    \$\begingroup\$ Definitely, but when it comes to working days I doubt you'll very often exceed the lifetime of a single person. It's like @mariosangiorgio says: avoid premature optimisation. Unless you know the difference in execution time will be too big, I would save myself developer-interpretation-time. But only you can answer this, ofcourse. \$\endgroup\$ – Jeroen Vannevel Oct 13 '14 at 5:38
8
\$\begingroup\$

First I feel I should warn you that computation about time are hard, damn hard. You probably want to use a library specifically designed to address it like Noda Time. For instance, your code takes into account weekends assuming a Monday to Friday work week (it is not the case everywhere in the world) and does not take into account bank holidays, which are not working days.

Said that, I think that your code is ok but I would rewrite it in this way to make it clearer. I'd like to split the code that computes the working days in full weeks and the code that computes the day in partial weeks. I also don't like the casting you do too much in the for loop. You can avoid the for loop altogether if you think a bit about how you should handle remaining days and do the maths. If the logic I'm following is not clear from the code leave a comment and I'll explain it a bit further.

    private static int GetNumberOfWorkingDays(DateTime start, DateTime stop)
    {
        var days = (stop - start).Days + 1;
        return workDaysInFullWeeks(days) + workDaysInPartialWeek(start.DayOfWeek, days);
    }

    private static int workDaysInFullWeeks(int totalDays)
    {
        return (totalDays / 7) * 5;
    }

    private static int workDaysInPartialWeek(DayOfWeek firstDay, int totalDays)
    {
        var remainingDays = totalDays % 7;
        var daysToSaturday = (int) DayOfWeek.Saturday - (int) firstDay;
        if(remainingDays <= daysToSaturday)
            return remainingDays;
        /* daysToSaturday are the days before the weekend,
         * the rest of the expression computes the days remaining after we
         * ignore Saturday and Sunday
         */
        // Range ends in a Saturday or in a Sunday
        if (remainingDays <= daysToSaturday + 2)
            return daysToSaturday;
        // Range ends after a Sunday
        else
            return remainingDays - 2;
    }

Note that you need to include both the start and stop dates but TimeSpan interval = stop - start; returns the difference between these two dates causing an off by one error (Credits to mjolka for noticing it). For instance new DateTime(2014, 10, 13) - new DateTime(2014, 10, 12) is one day but you want to include both these dates so we should add one to the result.

\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.