固定的日期列表是表达假期的一种有限的方式。
考虑到您的列表仅包含当年的日期,因此如果今天是 2015 年 12 月 30 日,那么下一个假期将是 2016 年 1 月 1 日 - 您不会在列表中找到它。
还要考虑到很多假期不会在每年的同一天。它们通常与一周中的某一天相关联,有时它们由宗教日历或任意确定。
一个更强大的系统应该能够处理各种不同类型的假期。这是一种可能的实现方式:
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'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));
现在,我们可以根据您的要求创建一个检查下一个工作日的方法:
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 的类。第一天总是在 11 月的第 4 个星期四,但第二天总是“11 月的第 4 个星期四之后的星期五”,而不仅仅是“11 月的第 4 个星期五”(参见 2019 年 11 月的示例)很重要)。