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

前端 未结 5 1019
攒了一身酷
攒了一身酷 2020-12-16 12:47

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

5条回答
  •  臣服心动
    2020-12-16 12:54

    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 _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();
    
    // 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 holidays, IList 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).

提交回复
热议问题