106

I need to find 2 elegant complete implementations of

public static DateTime AddBusinessDays(this DateTime date, int days)
{
 // code here
}

and 

public static int GetBusinessDays(this DateTime start, DateTime end)
{
 // code here
}

O(1) preferable (no loops).

EDIT: By business days i mean working days (Monday, Tuesday, Wednesday, Thursday, Friday). No holidays, just weekends excluded.

I already have some ugly solutions that seem to work but i wonder if there are elegant ways to do this. Thanks


This is what i've written so far. It works in all cases and does negatives too. Still need a GetBusinessDays implementation

public static DateTime AddBusinessDays(this DateTime startDate,
                                         int businessDays)
{
    int direction = Math.Sign(businessDays);
    if(direction == 1)
    {
        if(startDate.DayOfWeek == DayOfWeek.Saturday)
        {
            startDate = startDate.AddDays(2);
            businessDays = businessDays - 1;
        }
        else if(startDate.DayOfWeek == DayOfWeek.Sunday)
        {
            startDate = startDate.AddDays(1);
            businessDays = businessDays - 1;
        }
    }
    else
    {
        if(startDate.DayOfWeek == DayOfWeek.Saturday)
        {
            startDate = startDate.AddDays(-1);
            businessDays = businessDays + 1;
        }
        else if(startDate.DayOfWeek == DayOfWeek.Sunday)
        {
            startDate = startDate.AddDays(-2);
            businessDays = businessDays + 1;
        }
    }

    int initialDayOfWeek = (int)startDate.DayOfWeek;

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

    if((direction == 1 && addDays + initialDayOfWeek > 5) ||
         (direction == -1 && addDays >= initialDayOfWeek))
    {
        addDays += 2;
    }

    int totalDays = (weeksBase * 7) + addDays;
    return startDate.AddDays(totalDays * direction);
}
Adrian Zanescu
  • 7,907
  • 6
  • 35
  • 53
  • 17
    Are there elegant solutions when it comes to something as illogical as dates? – Wyatt Barnett Jun 25 '09 at 15:54
  • Are you conerned with Holidays? – James Conigliaro . No – Adrian Zanescu Jun 25 '09 at 16:01
  • 9
    Voting down people who are trying to help is not a winning strategy. – Jamie Ide Jun 25 '09 at 16:24
  • Can date, start or end be weekend days? – Patrick McDonald Jun 25 '09 at 16:48
  • @Patrick I'm sure that's possible, but more importantly, what is the result of the calculation? E.g. what is Saturday + 0 days? – Adrian Godong Jun 25 '09 at 16:51
  • So does adding 1 business day to Saturday give you Monday or Tuesday? And what about 0 business days? – Patrick McDonald Jun 26 '09 at 09:04
  • Saturday + 1 should give Monday. + 0 should make no change even if Saturday – Adrian Zanescu Jun 26 '09 at 10:36
  • 1
    Brief note about the `AddBusinessDays` implementation in the question above (which was actually a deleted answer I've proposed to undelete; a mod copied that answer to the question instead): In my opinion this solution is better than all answers so far because it's the only one that deals correctly with negative values, Saturday and Sunday as source and doesn't need a Third-Party lib. (I've made a little program to test the different solutions here.) I would only add `if (businessDays == 0) return startDate;` at the beginning of the method to get the correct result for this edge case as well. – Slauma Apr 27 '14 at 21:49
  • @Slauma wow, that was 5 years ago!. time flies when you're having fun i guess. Thanks for the comment. It was me (the OP) that posted that answer and it was deleted because it was not fair at the time to answer your own question. Also i lacked a solution for the second method. The current accepted answer provided decent solutions for both (even if not perfect) so i accepted it at some point. Of coure if someone needs a copy in their own project they should test them first exhaustively. – Adrian Zanescu Apr 28 '14 at 09:20
  • actually i'm wrong. it seems someone else did the deletion and it was recent :). – Adrian Zanescu Apr 28 '14 at 09:24
  • 1
    @AZ.: The first deletion was quite old. After my request to undelete your answer a mod had undeleted the answer (for 30 seconds) to copy the content below your question and then he deleted it again. That's why your answer has this recent delete time stamp. I wrote the comment above because for my purpose your `AddBusinessDays` was the most general solution here that worked in all cases I need. I've copied it into one of my current projects (after slight modification and translation into C++), thanks for the code :) It helped a lot since it's surprisingly difficult to get all edge cases right. – Slauma Apr 28 '14 at 11:48
  • With internationalization, this is difficult. As mentioned in other threads here on SOF, holidays differ from country to country certainly and even from province to province. Most governments do not schedule out their holidays more than five years or so. – Ash Machine Jun 25 '09 at 16:04
  • I can't understand why someone downvoted Ash: it's true that it's not an answer to what was asked, but Ash IS TOTALLY RIGHT, I have the very same issue: my software has to run in Europe (Mon-Sun) AND Africa/Asia (Sat-Fri). To downvote this, one has to be an ass (the animal, just in case) who can't see beyond his nose: it looks like there are people on SO who are afraid of knowledge, just don't want to know and prefer to remain on their reassuring ignorance: but they don't do a service to AZ in first place (and to themself too, but it's their own business) – M.Turrini Jun 26 '09 at 10:20
  • i (the OP) downvoted because i thought i made it clear in the question what are the constraints and it seemed Ash dind not pay attention. I apologize if that hurt someones feeling but is just a downvote – Adrian Zanescu Jun 26 '09 at 10:59
  • @AZ : Where in the original question did you mention internationalization constraints? – Ash Machine Apr 27 '11 at 22:57
  • @AshMachine, exactly. He didn't. – jwg Mar 08 '13 at 16:26
  • Holidays differ from country to country, but this question has nothing to do with holidays. It has to do with weekends, which is clearly stated in problem description. This should have been a comment. – Victor Zakharov Mar 09 '15 at 20:35

17 Answers17

146

Latest attempt for your first function:

public static DateTime AddBusinessDays(DateTime date, int days)
{
    if (days < 0)
    {
        throw new ArgumentException("days cannot be negative", "days");
    }

    if (days == 0) return date;

    if (date.DayOfWeek == DayOfWeek.Saturday)
    {
        date = date.AddDays(2);
        days -= 1;
    }
    else if (date.DayOfWeek == DayOfWeek.Sunday)
    {
        date = date.AddDays(1);
        days -= 1;
    }

    date = date.AddDays(days / 5 * 7);
    int extraDays = days % 5;

    if ((int)date.DayOfWeek + extraDays > 5)
    {
        extraDays += 2;
    }

    return date.AddDays(extraDays);

}

The second function, GetBusinessDays, can be implemented as follows:

public static int GetBusinessDays(DateTime start, DateTime end)
{
    if (start.DayOfWeek == DayOfWeek.Saturday)
    {
        start = start.AddDays(2);
    }
    else if (start.DayOfWeek == DayOfWeek.Sunday)
    {
        start = start.AddDays(1);
    }

    if (end.DayOfWeek == DayOfWeek.Saturday)
    {
        end = end.AddDays(-1);
    }
    else if (end.DayOfWeek == DayOfWeek.Sunday)
    {
        end = end.AddDays(-2);
    }

    int diff = (int)end.Subtract(start).TotalDays;

    int result = diff / 7 * 5 + diff % 7;

    if (end.DayOfWeek < start.DayOfWeek)
    {
        return result - 2;
    }
    else{
        return result;
    }
}
Patrick McDonald
  • 64,141
  • 14
  • 108
  • 120
  • For the second one, one solution is to take the difference between date and date+days. This is nice in that it guarantees that the two functions will sync properly, and it removes redundancy. – Brian Jun 25 '09 at 16:20
  • Feed current date, run for 0 to 10 business days, it always fail on Wednesday. – Adrian Godong Jun 25 '09 at 16:35
  • 1
    Yeah, we got there in the end. (I say 'we' for my tiny contribution!) Up-voted for the effort. – Noldorin Jun 25 '09 at 16:53
  • Thanks for your input Noldorin, I can only upvote your comments unfortunately! – Patrick McDonald Jun 25 '09 at 16:57
  • Haha, no worries. With the rep I currently have, it's of little consequence. Now, it would be interesting to see a similar implementation of `GetBusinessDays` (the approach should be similar conceptually) - might have a think later. – Noldorin Jun 25 '09 at 17:24
  • This didn't work for me when I converted it to VB.Net -- not sure if it's a bug in my conversion or the main logic, but I ran it on 10/1/09, adding 3 business days and it was giving me back 10/8/09 -- it should be giving me 10/6/09. – Nicholas Head Oct 01 '09 at 18:52
  • (int)DayOfWeek.Sunday==0. So you need to define day of the week in that way: int dayOfWeek = date.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)date.DayOfWeek; – algreat May 23 '12 at 14:37
  • dont understand the if ((int)date.DayOfWeek + extraDays > 5) { extraDays += 2; } part... whats it for? – m4tt1mus Dec 19 '13 at 22:15
  • If the final day calculated is a Saturday or Sunday, it pushes the answer on to the following Monday or Tuesday respectively – Patrick McDonald Dec 19 '13 at 22:42
  • 4
    DateTime.AddDays works with negative numbers. This does not correctly follow the same pattern as using negative numbers with AddBusinessDays allows non-business days to be selected. – Ristogod May 28 '14 at 17:37
  • Thanks for the -1. The function was not designed to work with negative days. While I can see a valid reason the look for n business days in the future, I can see less reason to calculate n business days ago. – Patrick McDonald May 29 '14 at 09:16
  • If I call GetBusinessDays on a consecutive Saturday / Sunday, e.g. 18/10/2014 and 19/10/2014 I get -3. However, it does seem to work for everything else, so just forcing negative numbers to 0 seems to do the trick. – Whelkaholism Oct 07 '14 at 14:56
  • Yeah, GetBusinessDays seems to give an incorrect output when the weekend days are inside of the date range, and not the start or end date 07/12/2016 - 07/08/2016 = -4.however, it should equal -2 – Mark At Ramp51 Jul 12 '16 at 13:41
  • For the AddBusinessDays give 10 days to add from 24th Nov 18. this will return 13th Dec 18 which is incorrect. it should be 7th Dec 18. Please correct me if im wrong. – Dalton Nov 23 '18 at 16:02
  • `Console.WriteLine($"{AddBusinessDays(new DateTime(2018, 11, 24), 10)}");` gives `07/12/2018 00:00:00` on my machine – Patrick McDonald Nov 25 '18 at 22:16
  • 1
    I agree with @Ristogod. It is not the same pattern as DateTime.AddDays with negative numbers. – krypru Feb 12 '20 at 16:07
81

using Fluent DateTime:

var now = DateTime.Now;
var dateTime1 = now.AddBusinessDays(3);
var dateTime2 = now.SubtractBusinessDays(5);

internal code is as follows

    /// <summary>
    /// Adds the given number of business days to the <see cref="DateTime"/>.
    /// </summary>
    /// <param name="current">The date to be changed.</param>
    /// <param name="days">Number of business days to be added.</param>
    /// <returns>A <see cref="DateTime"/> increased by a given number of business days.</returns>
    public static DateTime AddBusinessDays(this DateTime current, int days)
    {
        var sign = Math.Sign(days);
        var unsignedDays = Math.Abs(days);
        for (var i = 0; i < unsignedDays; i++)
        {
            do
            {
                current = current.AddDays(sign);
            }
            while (current.DayOfWeek == DayOfWeek.Saturday ||
                current.DayOfWeek == DayOfWeek.Sunday);
        }
        return current;
    }

    /// <summary>
    /// Subtracts the given number of business days to the <see cref="DateTime"/>.
    /// </summary>
    /// <param name="current">The date to be changed.</param>
    /// <param name="days">Number of business days to be subtracted.</param>
    /// <returns>A <see cref="DateTime"/> increased by a given number of business days.</returns>
    public static DateTime SubtractBusinessDays(this DateTime current, int days)
    {
        return AddBusinessDays(current, -days);
    }
Simon
  • 33,714
  • 21
  • 133
  • 202
15

I created an extension that allows you to add or subtract business days. Use a negative number of businessDays to subtract. I think it's quite an elegant solution. It seems to work in all cases.

namespace Extensions.DateTime
{
    public static class BusinessDays
    {
        public static System.DateTime AddBusinessDays(this System.DateTime source, int businessDays)
        {
            var dayOfWeek = businessDays < 0
                                ? ((int)source.DayOfWeek - 12) % 7
                                : ((int)source.DayOfWeek + 6) % 7;

            switch (dayOfWeek)
            {
                case 6:
                    businessDays--;
                    break;
                case -6:
                    businessDays++;
                    break;
            }

            return source.AddDays(businessDays + ((businessDays + dayOfWeek) / 5) * 2);
        }
    }
}

Example:

using System;
using System.Windows.Forms;
using Extensions.DateTime;

namespace AddBusinessDaysTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            label1.Text = DateTime.Now.AddBusinessDays(5).ToString();
            label2.Text = DateTime.Now.AddBusinessDays(-36).ToString();
        }
    }
}
Arjen
  • 290
  • 3
  • 6
  • 2
    The result is questionable if the source date is a Saturday or Sunday. For example: Saturday + 1 business day results in Tuesday, where I'd rather expect Monday. – Slauma Apr 18 '14 at 14:00
  • 3
    @Slauma: This is how most businesses in Canada operate. +1 business day = "next business day", which in case of Saturday is Tuesday. Monday would be "same business day". – Victor Zakharov Mar 09 '15 at 20:29
  • 4
    @Slauma Program works as intended. Think about it logically. If something business related kicks off on Saturday, and you have to allow 1 business day for people to react during the span of said business day, would it make sense to tell them it must be done by Monday?! – Riegardt Steyn Feb 02 '16 at 10:04
  • 1
    I agree with @Slauma, this doesn't make sense, if I input Saturday, then for the next business day I expect it to output Monday, hence, the next closest business day. If I wanted Tuesday, then I would input 2 days. – HamsterWithPitchfork Jul 27 '21 at 12:03
  • @metabuddy I think you're both right, problem is asking for "next business day" is different than asking today + 1 business day. – PandaWood Jun 24 '22 at 04:15
9

For me I had to have a solution that would skip weekends and go in either negative or positive. My criteria was if it went forward and landed on a weekend it would need to advance to Monday. If it was going back and landed on a weekend it would have to jump to Friday.

For example:

  • Wednesday - 3 business days = Last Friday
  • Wednesday + 3 business days = Monday
  • Friday - 7 business days = Last Wednesday
  • Tuesday - 5 business days = Last Tuesday

Well you get the idea ;)

I ended up writing this extension class

public static partial class MyExtensions
{
    public static DateTime AddBusinessDays(this DateTime date, int addDays)
    {
        while (addDays != 0)
        {
            date = date.AddDays(Math.Sign(addDays));
            if (MyClass.IsBusinessDay(date))
            {
                addDays = addDays - Math.Sign(addDays);
            }
        }
        return date;
    }
}

It uses this method I thought would be useful to use elsewhere...

public class MyClass
{
    public static bool IsBusinessDay(DateTime date)
    {
        switch (date.DayOfWeek)
        {
            case DayOfWeek.Monday:
            case DayOfWeek.Tuesday:
            case DayOfWeek.Wednesday:
            case DayOfWeek.Thursday:
            case DayOfWeek.Friday:
                return true;
            default:
                return false;
        }
    }
}

If you don't want to bother with that you can just replace out if (MyClass.IsBusinessDay(date)) with if if ((date.DayOfWeek != DayOfWeek.Saturday) && (date.DayOfWeek != DayOfWeek.Sunday))

So now you can do

var myDate = DateTime.Now.AddBusinessDays(-3);

or

var myDate = DateTime.Now.AddBusinessDays(5);

Here are the results from some testing:

Test                         Expected   Result
Wednesday -4 business days   Thursday   Thursday
Wednesday -3 business days   Friday     Friday
Wednesday +3 business days   Monday     Monday
Friday -7 business days      Wednesday  Wednesday
Tuesday -5 business days     Tuesday    Tuesday
Friday +1 business days      Monday     Monday
Saturday +1 business days    Monday     Monday
Sunday -1 business days      Friday     Friday
Monday -1 business days      Friday     Friday
Monday +1 business days      Tuesday    Tuesday
Monday +0 business days      Monday     Monday
Hugo Yates
  • 2,081
  • 2
  • 26
  • 24
  • I made the 2nd method an extension method as well: public static bool IsBusinessDay(this DateTime date) – Andy B Jan 24 '19 at 19:46
2
public static DateTime AddBusinessDays(this DateTime date, int days)
{
    date = date.AddDays((days / 5) * 7);

    int remainder = days % 5;

    switch (date.DayOfWeek)
    {
        case DayOfWeek.Tuesday:
            if (remainder > 3) date = date.AddDays(2);
            break;
        case DayOfWeek.Wednesday:
            if (remainder > 2) date = date.AddDays(2);
            break;
        case DayOfWeek.Thursday:
            if (remainder > 1) date = date.AddDays(2);
            break;
        case DayOfWeek.Friday:
            if (remainder > 0) date = date.AddDays(2);
            break;
        case DayOfWeek.Saturday:
            if (days > 0) date = date.AddDays((remainder == 0) ? 2 : 1);
            break;
        case DayOfWeek.Sunday:
            if (days > 0) date = date.AddDays((remainder == 0) ? 1 : 0);
            break;
        default:  // monday
            break;
    }

    return date.AddDays(remainder);
}
LukeH
  • 263,068
  • 57
  • 365
  • 409
2

I am resurrecting this post because today I had to find a way to exclude not only Saturday and Sunday weekdays but also holidays. More specifically, I needed to handle various sets of possible holidays, including:

  • country-invariant holidays (at least for the Western countries - such as January, 01).
  • calculated holidays (such as Easter and Easter monday).
  • country-specific holidays (such as the Italian liberation day or the United States ID4).
  • town-specific holidays (such as the Rome St. Patron Day).
  • any other custom-made holiday (such as "tomorrow our office wil be closed").

Eventually, I came out with the following set of helper/extensions classes: although they aren't blatantly elegant, as they do make a massive use of unefficient loops, they are decent enough to solve my issues for good. I'm dropping the whole source code here in this post, hoping it will be useful to someone else as well.

Source code

/// <summary>
/// Helper/extension class for manipulating date and time values.
/// </summary>
public static class DateTimeExtensions
{
    /// <summary>
    /// Calculates the absolute year difference between two dates.
    /// </summary>
    /// <param name="dt1"></param>
    /// <param name="dt2"></param>
    /// <returns>A whole number representing the number of full years between the specified dates.</returns>
    public static int Years(DateTime dt1,DateTime dt2)
    {
        return Months(dt1,dt2)/12;
        //if (dt2<dt1)
        //{
        //    DateTime dt0=dt1;
        //    dt1=dt2;
        //    dt2=dt0;
        //}

        //int diff=dt2.Year-dt1.Year;
        //int m1=dt1.Month;
        //int m2=dt2.Month;
        //if (m2>m1) return diff;
        //if (m2==m1 && dt2.Day>=dt1.Day) return diff;
        //return (diff-1);
    }

    /// <summary>
    /// Calculates the absolute year difference between two dates.
    /// Alternative, stand-alone version (without other DateTimeUtil dependency nesting required)
    /// </summary>
    /// <param name="start"></param>
    /// <param name="end"></param>
    /// <returns></returns>
    public static int Years2(DateTime start, DateTime end)
    {
        return (end.Year - start.Year - 1) +
            (((end.Month > start.Month) ||
            ((end.Month == start.Month) && (end.Day >= start.Day))) ? 1 : 0);
    }

    /// <summary>
    /// Calculates the absolute month difference between two dates.
    /// </summary>
    /// <param name="dt1"></param>
    /// <param name="dt2"></param>
    /// <returns>A whole number representing the number of full months between the specified dates.</returns>
    public static int Months(DateTime dt1,DateTime dt2)
    {
        if (dt2<dt1)
        {
            DateTime dt0=dt1;
            dt1=dt2;
            dt2=dt0;
        }

        dt2=dt2.AddDays(-(dt1.Day-1));
        return (dt2.Year-dt1.Year)*12+(dt2.Month-dt1.Month);
    }

    /// <summary>
    /// Returns the higher of the two date time values.
    /// </summary>
    /// <param name="dt1">The first of the two <c>DateTime</c> values to compare.</param>
    /// <param name="dt2">The second of the two <c>DateTime</c> values to compare.</param>
    /// <returns><c>dt1</c> or <c>dt2</c>, whichever is higher.</returns>
    public static DateTime Max(DateTime dt1,DateTime dt2)
    {
        return (dt2>dt1?dt2:dt1);
    }

    /// <summary>
    /// Returns the lower of the two date time values.
    /// </summary>
    /// <param name="dt1">The first of the two <c>DateTime</c> values to compare.</param>
    /// <param name="dt2">The second of the two <c>DateTime</c> values to compare.</param>
    /// <returns><c>dt1</c> or <c>dt2</c>, whichever is lower.</returns>
    public static DateTime Min(DateTime dt1,DateTime dt2)
    {
        return (dt2<dt1?dt2:dt1);
    }

    /// <summary>
    /// Adds the given number of business days to the <see cref="DateTime"/>.
    /// </summary>
    /// <param name="current">The date to be changed.</param>
    /// <param name="days">Number of business days to be added.</param>
    /// <param name="holidays">An optional list of holiday (non-business) days to consider.</param>
    /// <returns>A <see cref="DateTime"/> increased by a given number of business days.</returns>
    public static DateTime AddBusinessDays(
        this DateTime current, 
        int days, 
        IEnumerable<DateTime> holidays = null)
    {
        var sign = Math.Sign(days);
        var unsignedDays = Math.Abs(days);
        for (var i = 0; i < unsignedDays; i++)
        {
            do
            {
                current = current.AddDays(sign);
            }
            while (current.DayOfWeek == DayOfWeek.Saturday
                || current.DayOfWeek == DayOfWeek.Sunday
                || (holidays != null && holidays.Contains(current.Date))
                );
        }
        return current;
    }

    /// <summary>
    /// Subtracts the given number of business days to the <see cref="DateTime"/>.
    /// </summary>
    /// <param name="current">The date to be changed.</param>
    /// <param name="days">Number of business days to be subtracted.</param>
    /// <param name="holidays">An optional list of holiday (non-business) days to consider.</param>
    /// <returns>A <see cref="DateTime"/> increased by a given number of business days.</returns>
    public static DateTime SubtractBusinessDays(
        this DateTime current, 
        int days,
        IEnumerable<DateTime> holidays)
    {
        return AddBusinessDays(current, -days, holidays);
    }

    /// <summary>
    /// Retrieves the number of business days from two dates
    /// </summary>
    /// <param name="startDate">The inclusive start date</param>
    /// <param name="endDate">The inclusive end date</param>
    /// <param name="holidays">An optional list of holiday (non-business) days to consider.</param>
    /// <returns></returns>
    public static int GetBusinessDays(
        this DateTime startDate, 
        DateTime endDate,
        IEnumerable<DateTime> holidays)
    {
        if (startDate > endDate)
            throw new NotSupportedException("ERROR: [startDate] cannot be greater than [endDate].");

        int cnt = 0;
        for (var current = startDate; current < endDate; current = current.AddDays(1))
        {
            if (current.DayOfWeek == DayOfWeek.Saturday
                || current.DayOfWeek == DayOfWeek.Sunday
                || (holidays != null && holidays.Contains(current.Date))
                )
            {
                // skip holiday
            }
            else cnt++;
        }
        return cnt;
    }

    /// <summary>
    /// Calculate Easter Sunday for any given year.
    /// src.: https://stackoverflow.com/a/2510411/1233379
    /// </summary>
    /// <param name="year">The year to calcolate Easter against.</param>
    /// <returns>a DateTime object containing the Easter month and day for the given year</returns>
    public static DateTime GetEasterSunday(int year)
    {
        int day = 0;
        int month = 0;

        int g = year % 19;
        int c = year / 100;
        int h = (c - (int)(c / 4) - (int)((8 * c + 13) / 25) + 19 * g + 15) % 30;
        int i = h - (int)(h / 28) * (1 - (int)(h / 28) * (int)(29 / (h + 1)) * (int)((21 - g) / 11));

        day = i - ((year + (int)(year / 4) + i + 2 - c + (int)(c / 4)) % 7) + 28;
        month = 3;

        if (day > 31)
        {
            month++;
            day -= 31;
        }

        return new DateTime(year, month, day);
    }

    /// <summary>
    /// Retrieve holidays for given years
    /// </summary>
    /// <param name="years">an array of years to retrieve the holidays</param>
    /// <param name="countryCode">a country two letter ISO (ex.: "IT") to add the holidays specific for that country</param>
    /// <param name="cityName">a city name to add the holidays specific for that city</param>
    /// <returns></returns>
    public static IEnumerable<DateTime> GetHolidays(IEnumerable<int> years, string countryCode = null, string cityName = null)
    {
        var lst = new List<DateTime>();

        foreach (var year in years.Distinct())
        {
            lst.AddRange(new[] {
                new DateTime(year, 1, 1),       // 1 gennaio (capodanno)
                new DateTime(year, 1, 6),       // 6 gennaio (epifania)
                new DateTime(year, 5, 1),       // 1 maggio (lavoro)
                new DateTime(year, 8, 15),      // 15 agosto (ferragosto)
                new DateTime(year, 11, 1),      // 1 novembre (ognissanti)
                new DateTime(year, 12, 8),      // 8 dicembre (immacolata concezione)
                new DateTime(year, 12, 25),     // 25 dicembre (natale)
                new DateTime(year, 12, 26)      // 26 dicembre (s. stefano)
            });

            // add easter sunday (pasqua) and monday (pasquetta)
            var easterDate = GetEasterSunday(year);
            lst.Add(easterDate);
            lst.Add(easterDate.AddDays(1));

            // country-specific holidays
            if (!String.IsNullOrEmpty(countryCode))
            {
                switch (countryCode.ToUpper())
                {
                    case "IT":
                        lst.Add(new DateTime(year, 4, 25));     // 25 aprile (liberazione)
                        break;
                    case "US":
                        lst.Add(new DateTime(year, 7, 4));     // 4 luglio (Independence Day)
                        break;

                    // todo: add other countries

                    case default:
                        // unsupported country: do nothing
                        break;
                }
            }

            // city-specific holidays
            if (!String.IsNullOrEmpty(cityName))
            {
                switch (cityName)
                {
                    case "Rome":
                    case "Roma":
                        lst.Add(new DateTime(year, 6, 29));  // 29 giugno (s. pietro e paolo)
                        break;
                    case "Milano":
                    case "Milan":
                        lst.Add(new DateTime(year, 12, 7));  // 7 dicembre (s. ambrogio)
                        break;

                    // todo: add other cities

                    default:
                        // unsupported city: do nothing
                        break;

                }
            }
        }
        return lst;
    }
}

Usage info

The code is quite self-explanatory, however here's a couple examples to explain how you can use it.

Add 10 business days (skipping only saturday and sunday week days)

var dtResult = DateTimeUtil.AddBusinessDays(srcDate, 10);

Add 10 business days (skipping saturday, sunday and all country-invariant holidays for 2019)

var dtResult = DateTimeUtil.AddBusinessDays(srcDate, 10, GetHolidays(2019));

Add 10 business days (skipping saturday, sunday and all italian holidays for 2019)

var dtResult = DateTimeUtil.AddBusinessDays(srcDate, 10, GetHolidays(2019, "IT"));

Add 10 business days (skipping saturday, sunday, all italian holidays and the Rome-specific holidays for 2019)

var dtResult = DateTimeUtil.AddBusinessDays(srcDate, 10, GetHolidays(2019, "IT", "Rome"));

The above functions and code examples are further explained in this post of my blog.

Darkseal
  • 9,205
  • 8
  • 78
  • 111
1

The only real solution is to have those calls access a database table that defines the calendar for your business. You could code it for a Monday to Friday workweek without too much difficult but handling holidays would be a challenge.

Edited to add non-elegant and non-tested partial solution:

public static DateTime AddBusinessDays(this DateTime date, int days)
{
    for (int index = 0; index < days; index++)
    {
        switch (date.DayOfWeek)
        {
            case DayOfWeek.Friday:
                date = date.AddDays(3);
                break;
            case DayOfWeek.Saturday:
                date = date.AddDays(2);
                break;
            default:
                date = date.AddDays(1);
                break;
         }
    }
    return date;
}

Also I violated the no loops requirement.

bytecode77
  • 14,163
  • 30
  • 110
  • 141
Jamie Ide
  • 48,427
  • 16
  • 81
  • 117
1

I'm coming late for the answer, but I made a little library with all the customization needed to do simple operations on working days... I leave it here : Working Days Management

Boneless
  • 104
  • 1
  • 6
  • 2
    Unfortunately this is GNU licenced so is "legal poison" for any commercial app. Any chance you'd relax this to "MIT" or "Apache"? – Tony O'Hagan Jun 08 '16 at 08:23
  • Some static lists should probably be arrays (rather than linked lists). – Tony O'Hagan Jun 08 '16 at 08:24
  • 1
    I just changed the license to MIT (I do not want to block anything on something that simple). I will look into your other proposition. – Boneless Jul 08 '16 at 13:10
  • Nice, it would be interesting to see working day management by country as some countries may have other working days than Monday to Friday. – serializer Jul 27 '16 at 20:26
0
    public static DateTime AddBusinessDays(DateTime date, int days)
    {
        if (days == 0) return date;
        int i = 0;
        while (i < days)
        {
            if (!(date.DayOfWeek == DayOfWeek.Saturday ||  date.DayOfWeek == DayOfWeek.Sunday)) i++;  
            date = date.AddDays(1);
        }
        return date;
    }
Alex
  • 1
  • in the future add a bit more context for answer and maybe why you've put what you have :) – dax Sep 16 '13 at 19:12
0

I wanted an "AddBusinessDays" that supported negative numbers of days to add, and I ended up with this:

// 0 == Monday, 6 == Sunday
private static int epochDayToDayOfWeek0Based(long epochDay) {
    return (int)Math.floorMod(epochDay + 3, 7);
}

public static int daysBetween(long fromEpochDay, long toEpochDay) {
    // http://stackoverflow.com/questions/1617049/calculate-the-number-of-business-days-between-two-dates
    final int fromDOW = epochDayToDayOfWeek0Based(fromEpochDay);
    final int toDOW = epochDayToDayOfWeek0Based(toEpochDay);
    long calcBusinessDays = ((toEpochDay - fromEpochDay) * 5 + (toDOW - fromDOW) * 2) / 7;

    if (toDOW   == 6) calcBusinessDays -= 1;
    if (fromDOW == 6) calcBusinessDays += 1;
    return (int)calcBusinessDays;
}

public static long addDays(long epochDay, int n) {
    // https://alecpojidaev.wordpress.com/2009/10/29/work-days-calculation-with-c/
    // NB: in .NET, Sunday == 0, but in our code Monday == 0
    final int dow = (epochDayToDayOfWeek0Based(epochDay) + 1) % 7;
    final int wds = n + (dow == 0 ? 1 : dow); // Adjusted number of working days to add, given that we now start from the immediately preceding Sunday
    final int wends = n < 0 ? ((wds - 5) / 5) * 2
                            : (wds / 5) * 2 - (wds % 5 == 0 ? 2 : 0);
    return epochDay - dow + // Find the immediately preceding Sunday
           wds +            // Add computed working days
           wends;           // Add weekends that occur within each complete working week
}

No loop required, so it should be reasonably fast even for "big" additions.

It works with days expressed as a number of calendar days since the epoch, since that's exposed by the new JDK8 LocalDate class and I was working in Java. Should be simple to adapt to other settings though.

The fundamental properties are that addDays always returns a weekday, and that for all d and n, daysBetween(d, addDays(d, n)) == n

Note that theoretically speaking adding 0 days and subtracting 0 days should be different operations (if your date is a Sunday, adding 0 days should take you to Monday, and subtracting 0 days should take you to Friday). Since there's no such thing as negative 0 (outside of floating point!), I've chosen to interpret an argument n=0 as meaning add zero days.

Max Bolingbroke
  • 2,089
  • 18
  • 18
0

I believe this could be a simpler way to GetBusinessDays:

    public int GetBusinessDays(DateTime start, DateTime end, params DateTime[] bankHolidays)
    {
        int tld = (int)((end - start).TotalDays) + 1; //including end day
        int not_buss_day = 2 * (tld / 7); //Saturday and Sunday
        int rest = tld % 7; //rest.

        if (rest > 0)
        {
            int tmp = (int)start.DayOfWeek - 1 + rest;
            if (tmp == 6 || start.DayOfWeek == DayOfWeek.Sunday) not_buss_day++; else if (tmp > 6) not_buss_day += 2;
        }

        foreach (DateTime bankHoliday in bankHolidays)
        {
            DateTime bh = bankHoliday.Date;
            if (!(bh.DayOfWeek == DayOfWeek.Saturday || bh.DayOfWeek == DayOfWeek.Sunday) && (start <= bh && bh <= end))
            {
                not_buss_day++;
            }
        }
        return tld - not_buss_day;
    }
0

Here is my code with both departure date, and delivery date at customer.

            // Calculate departure date
            TimeSpan DeliveryTime = new TimeSpan(14, 30, 0); 
            TimeSpan now = DateTime.Now.TimeOfDay;
            DateTime dt = DateTime.Now;
            if (dt.TimeOfDay > DeliveryTime) dt = dt.AddDays(1);
            if (dt.DayOfWeek == DayOfWeek.Saturday) dt = dt.AddDays(1);
            if (dt.DayOfWeek == DayOfWeek.Sunday) dt = dt.AddDays(1);
            dt = dt.Date + DeliveryTime;
            string DepartureDay = "today at "+dt.ToString("HH:mm");
            if (dt.Day!=DateTime.Now.Day)
            {
                DepartureDay = dt.ToString("dddd at HH:mm", new CultureInfo(WebContextState.CurrentUICulture));
            }
            Return DepartureDay;

            // Caclulate delivery date
            dt = dt.AddDays(1);
            if (dt.DayOfWeek == DayOfWeek.Saturday) dt = dt.AddDays(1);
            if (dt.DayOfWeek == DayOfWeek.Sunday) dt = dt.AddDays(1);
            string DeliveryDay = dt.ToString("dddd", new CultureInfo(WebContextState.CurrentUICulture));
            return DeliveryDay;
Nimantha
  • 6,405
  • 6
  • 28
  • 69
JanBorup
  • 5,337
  • 1
  • 29
  • 17
0
public static DateTime AddWorkingDays(this 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
0
public static int GetBusinessDays(this DateTime start, DateTime end)
            {
                return Enumerable.Range(0, (end- start).Days)
                                .Select(a => start.AddDays(a))
                                .Where(a => a.DayOfWeek != DayOfWeek.Sunday)
                                .Where(a => a.DayOfWeek != DayOfWeek.Saturday)
                                .Count();
    
            }
Kokul Jose
  • 1,384
  • 2
  • 14
  • 26
0

Ok so this solution is slightly different (has some good and bad points):

  1. Solves weekends depending on the country (i.e. Arab countries have Friday-Saturday weekends)
  2. Depends on external library https://www.nuget.org/packages/Nager.Date/
  3. Only deals with adding business days
  4. Skips holidays as well (this can be removed)
  5. Uses recursion
public static class DateTimeExtensions
{
    public static DateTime AddBusinessDays(this DateTime date, int days, CountryCode countryCode)
    {
        if (days < 0)
        {
            throw new ArgumentException("days cannot be negative", "days");
        }

        if (days == 0)
        {
            return date;
        }

        date = date.AddDays(1);

        if (DateSystem.IsWeekend(date, countryCode) || DateSystem.IsPublicHoliday(date, countryCode))
        {
            return date.AddBusinessDays(days, countryCode);
        }

        days -= 1;

        return date.AddBusinessDays(days, countryCode);
    }
}

Usage:

[TestFixture]
public class BusinessDaysTests
{
    [TestCase("2021-06-04", 5, "2021-06-11", Nager.Date.CountryCode.GB)]
    [TestCase("2021-06-04", 6, "2021-06-14", Nager.Date.CountryCode.GB)]
    [TestCase("2021-05-28", 6, "2021-06-08", Nager.Date.CountryCode.GB)] // UK holidays 2021-05-31
    [TestCase("2021-06-01", 3, "2021-06-06", Nager.Date.CountryCode.KW)] // Friday-Saturday weekend in Kuwait
    public void AddTests(DateTime initDate, int delayDays, DateTime expectedDate, Nager.Date.CountryCode countryCode)
    {
        var outputDate = initDate.AddBusinessDays(delayDays, countryCode);
        Assert.AreEqual(expectedDate, outputDate);
    }
}
Artur Kedzior
  • 3,994
  • 1
  • 36
  • 58
0

You can use the Fluent DateTime library easily. Please refer to this Github library repository. https://github.com/FluentDateTime/FluentDateTime

var dateTime = DateTime.Now.AddBusinessDays(5);

You can consider weekdays as BusinessDays

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
csandun
  • 374
  • 2
  • 9
-1

Hope this helps someone.

private DateTime AddWorkingDays(DateTime addToDate, int numberofDays)
    {
        addToDate= addToDate.AddDays(numberofDays);
        while (addToDate.DayOfWeek == DayOfWeek.Saturday || addToDate.DayOfWeek == DayOfWeek.Sunday)
        {
            addToDate= addToDate.AddDays(1);
        }
        return addToDate;
    }
user2686690
  • 175
  • 2
  • 3