6
\$\begingroup\$

I'm working on figuring out what the current 'business' day of the month it is. For instance, if it's the 20th, but there are 3 weekends, it would be the 14th business day of the month.

Is there a better way of doing this?

var first = DateTime.Today.FirstDayOfMonth();
var businessDayOfMonth = 0;
for (var i = first; i <= DateTime.Today; i = i.AddDays(1))
{
    if (i.DayOfWeek != DayOfWeek.Saturday && i.DayOfWeek != DayOfWeek.Sunday) 
        businessDayOfMonth++;
}
\$\endgroup\$
  • 1
    \$\begingroup\$ What answer is this routine supposed to return if the day you're asking about isn't a working day itself (ie. it's Saturday or Sunday)? \$\endgroup\$ – Konrad Morawski Apr 4 '16 at 19:23
  • \$\begingroup\$ @KonradMorawski Very good question. I didn't even think about that. However, for my purposes, the last business day. So if you run on Saturday, it'll return the previous Friday. \$\endgroup\$ – Blue Eyed Behemoth Apr 4 '16 at 19:29
  • \$\begingroup\$ And if it's Sunday the 1st? : ) \$\endgroup\$ – Konrad Morawski Apr 4 '16 at 19:45
  • \$\begingroup\$ @KonradMorawski Another good point lol. In that case it would have to run for Monday. Normally these wouldn't be cases because the office isn't open on weekends, but it's good to catch them. \$\endgroup\$ – Blue Eyed Behemoth Apr 4 '16 at 19:52
  • \$\begingroup\$ Another possible concern (only theoretical, if your app will not be internationalized) is that in some cultures the working week is different, eg. Saturday can be a working day. See en.wikipedia.org/wiki/Workweek_and_weekend - this being said, I actually tried to come up with an optimized algorithm for this purpose and while it's possible, it always ends up less readable, so if performance is not a problem (you're not making millions of these calculations), what you already have, while "naive", seems to be as good as it gets, save for protecting it against aforementioned edge cases \$\endgroup\$ – Konrad Morawski Apr 4 '16 at 20:16
4
\$\begingroup\$

As explained in comments (in which I pointed out a few possible edge cases), even though it's not optimized in terms of performance, this is about as clear as it gets.

A more clever algorithm - calculating the number of weekdays without iterating through all of them - is probably possible, but I bet it would be much less readable. If performance isn't a concern at this point, I'd leave it.

You could maybe replace the loop with a LINQ-based solution.

int WhichWorkingDay(DateTime day)
{
    // edge cases
    switch (day.DayOfWeek)
    {
        case DayOfWeek.Saturday:
            return day.Day > 1
                // Friday the day before
                ? WhichWorkingDay(day.AddDays(-1))
                // or next Monday if previous Friday was on last month
                : WhichWorkingDay(day.AddDays(2));
        // analogically here
        case DayOfWeek.Sunday:
            return day.Day > 2
                ? WhichWorkingDay(day.AddDays(-2))
                : WhichWorkingDay(day.AddDays(1));
    }

    // or you could inline it, obviously
    Func<DateTime, bool> isWorkingDay = d => d.DayOfWeek != DayOfWeek.Saturday 
        && d.DayOfWeek != DayOfWeek.Sunday;

    return Enumerable
        .Range(1, day.Day)
        .Select(d => new DateTime(day.Year, day.Month, d))
        .Count(isWorkingDay);
}
\$\endgroup\$
  • \$\begingroup\$ I like it, I don't know if it's more efficient, but I like the logic lol \$\endgroup\$ – Blue Eyed Behemoth Apr 5 '16 at 14:11
  • \$\begingroup\$ @BlueEyedBehemoth thanks. Yeah, it's not really more eficient (in terms of performance), pretty much the same thing, only the style is more functional than imperative. Now that's a matter of taste and habit, but personally I prefer functional versions of the same code, once you get used to it, it becomes easier to read really \$\endgroup\$ – Konrad Morawski Apr 5 '16 at 14:28
0
\$\begingroup\$

As Konrad Morawski already pointed out, working days are culture related. I've worked out a working day calculator that is extendible so every culture can be supported.

The extension method WorkingDay uses a static class OffDayProvider to determine whether a given day is an day off. By default this OffDayProvider does not consider any day to be an off day. By injecting one or more IOffDayProviders you can determine what days are off days.

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main()
    {
        var startDate = new DateTime(2015,12,1);
        var date = new DateTime(2015,12,31);

        Console.WriteLine("{0} -- {1}",date.ToShortDateString(), date.WorkingDay(startDate));

        OffDayProvider.Providers.Add(new WeekendProvider());
        OffDayProvider.Providers.Add(new BankHolidayProvider());
        OffDayProvider.Providers.Add(new WinterSolsticeProvider());

        Console.WriteLine("{0} -- {1}",date.ToShortDateString(), date.WorkingDay(startDate));
    }
}

public static class DateTimeExtensions
{
    public static int WorkingDay(this DateTime date, DateTime startDate)
    {
        var result = 0;
        for(var day = startDate; day <= date; day = day.AddDays(1))
        {
            if(OffDayProvider.IsOffDay(day))
            {
                continue;
            }
            result++;
        }
        return result;
    }

    public static int WorkingDay(this DateTime date)
    {
        return date.WorkingDay(new DateTime(date.Year, 1, 1));
    }
}

public static class OffDayProvider
{
    public static readonly ICollection<IOffDayProvider> Providers = new List<IOffDayProvider>();

    public static bool IsOffDay(DateTime date)
    {
        return Providers.Any(x => x.IsOffDay(date));
    }   
}

public interface IOffDayProvider 
{
    bool IsOffDay(DateTime date);
}

public class WeekendProvider : IOffDayProvider
{
    public bool IsWeekend(DateTime date)
    {
        return date.DayOfWeek == DayOfWeek.Sunday 
            || date.DayOfWeek == DayOfWeek.Saturday;
    }

    public bool IsOffDay(DateTime date)
    {
        return IsWeekend(date);
    }
}

public class WinterSolsticeProvider : IOffDayProvider
{
    public bool IsWinterSolstice(DateTime date)
    {
        return date.Month == 12 && date.Day == 21;
    }

    public bool IsOffDay(DateTime date)
    {
        return IsWinterSolstice(date);
    }
}

public class BankHolidayProvider : IOffDayProvider
{
    public bool IsChristmasDay(DateTime date) 
    {
        return date.Month == 12 && date.Day == 25;
    }

    public bool IsBoxingDay(DateTime date) 
    {
        return date.Month == 12 && date.Day == 26;
    }

    public bool IsOffDay(DateTime date)
    {
        return IsChristmasDay(date)
            || IsBoxingDay(date);
    }
}

I did not include checks for corner cases. It's up to you to determine how to handle those.

I did not include any comments either. Well written code doesn't need comments, does it? B-)

\$\endgroup\$
0
\$\begingroup\$

From the comments it is clear that FirstDayOfMonth() is an extension method, you are using to get the first Day Of Month (I don't have that method so I'm doing the same in a different way). Anyway first will not be a good name for this variable so I would like to change it more specifically as firstDayOfMonth:

DateTime dateToday=DateTime.Today;
var firstDayOfMonth = new DateTime(dateToday.Year, dateToday.Month, 1); // will give you the First day of this month

I prefer you to use List<DayOfWeek> to specify which days are considered as week-off days (I think this may change as per the country). So that modifications will became more easier. And also it makes the conditions more simple.

You can try the following example:

List<DayOfWeek> weekOffDays = new List<DayOfWeek>() { DayOfWeek.Sunday, DayOfWeek.Saturday };
DateTime dateToday=DateTime.Today;
var firstDayOfMonth = new DateTime(dateToday.Year, dateToday.Month, 1); // will give you the First day of this month
var businessDaysOfMonth = 0; 
for (var dateObject = firstDayOfMonth; dateObject <= dateToday; dateObject = dateObject.AddDays(1))
{
    if (!weekOffDays.Contains(dateObject.DayOfWeek))
       businessDaysOfMonth++;
}
Console.WriteLine("Today is {0}th working day of this month", businessDaysOfMonth);
\$\endgroup\$
  • \$\begingroup\$ I'm pretty sure FirstDayOfMonth is a custom extension method he just forgot isn't part of the standard library \$\endgroup\$ – Konrad Morawski Apr 5 '16 at 8:42
  • \$\begingroup\$ Yeah, exactly that @KonradMorawski, I have a lot of custom DateTime extensions already. \$\endgroup\$ – Blue Eyed Behemoth Apr 5 '16 at 14:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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