How to get the next working day, excluding weekends and holidays

30,840

Solution 1

Basically you want to get the next working day. So you could loop on this condition adding 1 day to the current date

do {
  date = date.AddDays(1);
} while(IsHoliday(date) || IsWeekend(date));

In the previous code IsHoliday is a predicate telling if a date is holiday. For instance, shamelessly reusing your code:

class Program
{
    private static readonly HashSet<DateTime> Holidays = new HashSet<DateTime>();

    private static bool IsHoliday(DateTime date)
    {
        return Holidays.Contains(date);
    }

    private static bool IsWeekend(DateTime date)
    {
        return date.DayOfWeek == DayOfWeek.Saturday
            || date.DayOfWeek == DayOfWeek.Sunday;
    }


    private static DateTime GetNextWorkingDay(DateTime date)
    {
        do
        {
            date = date.AddDays(1);
        } while (IsHoliday(date) || IsWeekend(date));
        return date;
    }

    static void Main(string[] args)
    {
        Holidays.Add(new DateTime(DateTime.Now.Year, 1, 1));
        Holidays.Add(new DateTime(DateTime.Now.Year, 1, 5));
        Holidays.Add(new DateTime(DateTime.Now.Year, 3, 10));
        Holidays.Add(new DateTime(DateTime.Now.Year, 12, 25));

        var dt = GetNextWorkingDay(DateTime.Parse(@"2015-10-31"));

        Console.WriteLine(dt);

        Console.ReadKey();
    }
}

Solution 2

Based on the answer from @fjardon, you can use my project Nager.Date It contains the public holidays and weekends for more than 100 countries.

nuget

PM> install-package Nager.Date

Code snippet

//usings
using Nager.Date;
using Nager.Date.Extensions;

//logic
var date = DateTime.Today; //Set start date
var countryCode = CountryCode.US; //Set country

do
{
    date = date.AddDays(1);
} while (DateSystem.IsPublicHoliday(date, countryCode) || date.IsWeekend(countryCode));

Solution 3

A fixed list of dates is a somewhat limited way to express holidays.

Consider that your list only contains dates for the current year, so if today is December 30th 2015, then the next holiday would be Jan 1, 2016 - which you wouldn't find in your list.

Also consider that many holidays do not fall on the same date every year. Often they are tied to the day of the week, and sometimes they are determined by religious calendars, or arbitrarily.

A more robust system should handle a variety of different types of holidays. Here's one possible implementation:

public abstract class Holiday
{
    public abstract DateTime? GetDate(int year);
}

public class MonthDayBasedHoliday : Holiday
{
    private readonly int _month;
    private readonly int _day;

    public MonthDayBasedHoliday(int month, int day)
    {
        _month = month;
        _day = day;
    }

    public override DateTime? GetDate(int year)
    {
        return new DateTime(year, _month, _day);
    }
}

public class DayOfWeekBasedHoliday : Holiday
{
    private readonly int _occurrence;
    private readonly DayOfWeek _dayOfWeek;
    private readonly int _month;

    public DayOfWeekBasedHoliday(int occurrence, DayOfWeek dayOfWeek, int month)
    {
        _occurrence = occurrence;
        _dayOfWeek = dayOfWeek;
        _month = month;
    }

    public override DateTime? GetDate(int year)
    {
        if (_occurrence <= 4)
        {
            DateTime dt = new DateTime(year, _month, 1);
            int delta = (_dayOfWeek - dt.DayOfWeek + 7) % 7;
            delta += 7 * (_occurrence - 1);
            return dt.AddDays(delta);
        }
        else  // last occurrence in month
        {
            int daysInMonth = DateTime.DaysInMonth(year, _month);
            DateTime dt = new DateTime(year, _month, daysInMonth);
            int delta = (dt.DayOfWeek - _dayOfWeek + 7) % 7;
            return dt.AddDays(-delta);
        }
    }
}

public class FixedDateBasedHoliday : Holiday
{
    private readonly IDictionary<int, DateTime> _dates;

    public FixedDateBasedHoliday(params DateTime[] dates)
    {
        _dates = dates.ToDictionary(x => x.Year, x => x);
    }

    public override DateTime? GetDate(int year)
    {
        if (_dates.ContainsKey(year))
            return _dates[year];

        // fixed date not established for year
        return null;
    }
}

With these defined, we can now define holidays much more robustly, such as:

var holidays = new List<Holiday>();

// New Year's Day
holidays.Add(new MonthDayBasedHoliday(1, 1));

// President's Day (US)
holidays.Add(new DayOfWeekBasedHoliday(3, DayOfWeek.Monday, 2));

// Easter (Western Observance)
holidays.Add(new FixedDateBasedHoliday(new DateTime(2015, 4, 5), new DateTime(2016, 3, 27)));

// Memorial Day (US)
holidays.Add(new DayOfWeekBasedHoliday(5, DayOfWeek.Monday, 5));

// Christmas Day
holidays.Add(new MonthDayBasedHoliday(12, 25));

And now, we can create a method that checks for the next working day, as you requested:

public static DateTime GetNextNonHolidayWeekDay(DateTime date, IList<Holiday> holidays, IList<DayOfWeek> weekendDays)
{
    // always start with tomorrow, and truncate time to be safe
    date = date.Date.AddDays(1);

    // calculate holidays for both this year and next year
    var holidayDates = holidays.Select(x => x.GetDate(date.Year))
        .Union(holidays.Select(x => x.GetDate(date.Year + 1)))
        .Where(x=> x != null)
        .Select(x=> x.Value)
        .OrderBy(x => x).ToArray();

    // increment until we get a non-weekend and non-holiday date
    while (true)
    {
        if (weekendDays.Contains(date.DayOfWeek) || holidayDates.Contains(date))
            date = date.AddDays(1);
        else
            return date;
    }
}

That method could go on the abstract Holiday class, or it could go anywhere really.

Example usage (with above definition for holidays):

var weekendDays = new[] { DayOfWeek.Saturday, DayOfWeek.Sunday };

DateTime workDay = GetNextNonHolidayWeekDay(new DateTime(2015, 12, 31), holidays, weekendDays);
// returns 2016-01-04

This is still not quite a complete solution though. Many holidays have more complex calculation rules. As an exercise left to the reader, try implementing a class that derives from Holiday for the second day in the US Thanksgiving holiday. The first day will always fall on the 4th Thursday in November, but the second day is always "the Friday following the 4th Thursday in November", rather than just "the 4th Friday in November" (see November 2019 for an example of where this matters).

Share:
30,840

Related videos on Youtube

CSharped
Author by

CSharped

Updated on July 14, 2022

Comments

  • CSharped
    CSharped almost 2 years

    I have a requirement where I need to work on a date field, so the requirement is some thing like this

    I will call the field as minimum possible date

    1. Add +1 to the date

    2. If the minimum possible date happens to fall on a weekend(Sat or Sun) after adding 1 day, Display the next working day i.e Monday

    3. If the minimum possible date happens to fall on a Holiday, display the next working day. (Holidays 1.1 , 1.5 , 3.10 , 25.12 , 26.12)

    4. If the minimum possible date happens to fall on a weekend(Sat or Sun) after adding 1 day, and the day after that is a holiday then show the next working day. Eg: After +1 day if min possible day is Saturday, we will have to display Monday. But if Monday happens to be a Holiday then we have to display Tuesday.

    I have tried a solution to the above problem by having multiple if and else cases, but just wondering if there is any generic and graceful way of doing it?

    I have tried

    var holidays = new List<DateTime>();
    holidays.Add(new DateTime(DateTime.Now.Year,1,1));
    holidays.Add(new DateTime(DateTime.Now.Year,1,5));
    holidays.Add(new DateTime(DateTime.Now.Year,3,10));
    holidays.Add(new DateTime(DateTime.Now.Year,12,25));
    
    if (date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday)
    {
         //Logic to add +1 and again some logic to check for weekends and weekdays
    }
    else if (holidays.Contain(date))
    {
       //Logic to add +1 and again some logic to check for weekends and weekdays
    }
    
  • Ian Mercer
    Ian Mercer over 8 years
    +1 and by making IsHoliday a function you can avoid building a static list of holidays and instead calculate on the fly for the date/year passed in - which is important for dates like Easter that move.
  • Fabjan
    Fabjan over 8 years
    What is IsHolliday(date) ? Does this method exist ?
  • fjardon
    fjardon over 8 years
    @Fabjan You have to implement this method yourself. For instance you can insert the code the OP wrote for checking if a date is hollidays.
  • juharr
    juharr over 8 years
    You should start datesAhead from the targetDate instead of the current date. Then there's not need for the a > targetDate.
  • juharr
    juharr over 8 years
    Well since it will only iterate til it gets to a match the count passed to Enumerable.Range just need to be more than whatever the maximum number of days til the next work day could be. My guess is 5 or 6, but making it larger doesn't have a negative effect.
  • Arthur Rizzo
    Arthur Rizzo almost 7 years
    The way you modeled the holidays seems very appropriate. That's is a great answer.
  • Shiva
    Shiva over 6 years
    I like this implementation better. Simpler than the other active one.
  • Igor Mironenko
    Igor Mironenko almost 6 years
    Would be slightly nicer if GetNextWorkingDay() returned the same date passed if it was a WorkDay - ie remove the assumption that the day passed must have been already been calculated to be a WeekDay. Probably do it with a while loop without the "do"
  • LakshmiSarada
    LakshmiSarada over 5 years
    I felt this is the easiest way , and it worked for me. If someone not getting correct output then let me know, i can help
  • Ben Voigt
    Ben Voigt almost 3 years
    Completely ignored the "and holidays" part of the question.