日付フィールドで作業する必要があるという要件があるので、この要件は次のようなものです
フィールドを最小可能日付と呼びます。
日付に+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日を追加するこの条件でループすることができます
do {
date = date.AddDays(1);
} while(IsHolliday(date) || IsWeekEnd(date));
前のコードのIsHolliday
は、日付が休日かどうかを示す述語です。例えば、あなたのコードを恥知らずに再利用する:
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();
}
}
@fjardonからの回答に基づいて、私のプロジェクトを使用できます Nager.Date さまざまな国の週末のロジックが含まれ、90か国以上の祝日が含まれています。
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));
日付の固定リストは、休日を表現するためのやや限定的な方法です。
リストには現在の年の日付のみが含まれているため、今日が2015年12月30日の場合、次の休日は2016年1月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
ただし、これはまだ完全なソリューションではありません。多くの休日には、より複雑な計算規則があります。読者に任せる演習として、米国の感謝祭の休日の2日目のHoliday
から派生するクラスを実装してみてください。最初の日は常に11月の第4木曜日になりますが、2日目は常に「11月の第4金曜日」ではなく、常に「11月の第4木曜日の金曜日」になります(場所の例については、2019年11月を参照してください)問題)。
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)) );