Как получить следующий рабочий день, исключая выходные и праздничные дни

У меня есть требование, когда мне нужно работать с полем даты, поэтому требование - это что-то вроде этого

Я назову это поле как минимально возможная дата

  • Добавить +1 к дате

  • Если минимальная возможная дата выпадает на выходные (Сб или Солнце) после добавления 1 дня, отобразите следующий рабочий день в понедельник

  • Если минимальная возможная дата выпадает на Праздник, отобразите следующий рабочий день. (Праздники 1.1, 1.5, 3.10, 25.12, 26.12)

  • Если минимальная возможная дата выпадает на выходные (Сб или Солнце) после добавления 1 дня, а на следующий день после этого - выходной день, то следующий рабочий день. Например: после +1 дня, если минимальный день - суббота, нам нужно будет показать понедельник. Но если в понедельник бывает Праздник, тогда мы должны отображать вторник.

Я попытался решить проблему выше, имея несколько случаев if и else, но просто интересно, есть ли какой-либо общий и изящный способ сделать это?

Я пробовал

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
}

Ответ 1

В принципе, вы хотите получить следующий рабочий день. Таким образом, вы можете выполнить цикл в этом состоянии, добавив 1 день к текущей дате

do {
  date = date.AddDays(1);
} while(IsHolliday(date) || IsWeekEnd(date));

В предыдущем коде IsHolliday есть предикат, указывающий, что дата является holliday. Например, бесстыдно повторное использование кода:

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();
    }
}

Ответ 2

Фиксированный список дат является несколько ограниченным способом выражения праздников.

Учтите, что ваш список содержит только даты для текущего года, поэтому, если сегодня будет 30 декабря 2015 года, следующий выходной будет 1 января 2016 года, который вы не найдете в своем списке.

Также считайте, что многие праздники не падают на ту же дату каждый год. Часто они привязаны к дню недели, а иногда они определяются религиозными календарями или произвольно.

Более надежная система должна обрабатывать различные типы праздников. Вот одна из возможных реализаций:

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;
    }
}

С помощью этих определений мы можем теперь определять праздники гораздо более надежно, например:

var holidays = new List<Holiday>();

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

// President 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));

И теперь мы можем создать метод, который проверяет следующий рабочий день, как вы просили:

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;
    }
}

Этот метод может идти по абстрактному классу Holiday, или он может пойти куда угодно.

Пример использования (с вышеприведенным определением для holidays):

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

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

Это все еще не совсем полное решение. Многие праздники имеют более сложные правила расчета. Как упражнение, оставленное читателю, попробуйте реализовать класс, который происходит от Holiday на второй день в празднике Благодарения в США. Первый день всегда будет падать в четвертый четверг ноября, но второй день всегда "пятница после 4-го четверга в ноябре", а не только "четвертая пятница в ноябре" (см. В ноябре 2019 года на примере того, где это вопросы).

Ответ 3

На основе ответа от @fjardon вы можете использовать Nager.Date, он содержит логику выходных дней в разных странах и содержит праздничные дни для большего количества как 70 стран.

NuGet

PM> install-package Nager.Date

Фрагмент кода

//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));

Ответ 4

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));

var exclude = new List<DayOfWeek> {DayOfWeek.Saturday, DayOfWeek.Sunday};

var targetDate = new DateTime(2015, 12, 24);


var myDate = Enumerable.Range(1, 30)
  .Select( i => targetDate.AddDays(i) )
  .First(a =>  
  !( exclude.Contains( a.DayOfWeek ) || 
     Holidays.Contains(a)) );