Wskrzeszam ten post, bo dziś musiałem znaleźć sposób na wykluczenie nie tylko sobót i niedziel, ale także świąt. Mówiąc dokładniej, musiałem poradzić sobie z różnymi zestawami możliwych świąt, w tym:
- święta niezmienne dla krajów (przynajmniej dla krajów zachodnich - np. 1 stycznia).
- obliczone święta (takie jak Wielkanoc i Poniedziałek Wielkanocny).
- święta specyficzne dla danego kraju (takie jak włoski dzień wyzwolenia lub amerykański identyfikator 4).
- święta miejskie (takie jak Dzień Świętego Patrona w Rzymie).
- wszelkie inne święta na zamówienie (np. „jutro nasze biuro będzie zamknięte”).
Ostatecznie wyszedłem z następującym zestawem klas pomocniczych / rozszerzeń: chociaż nie są one rażąco eleganckie, ponieważ masowo wykorzystują niewydajne pętle, są wystarczająco przyzwoite, aby rozwiązać moje problemy na dobre. Upuszczam cały kod źródłowy tutaj w tym poście, mając nadzieję, że przyda się on również komuś innemu.
Kod źródłowy
public static class DateTimeExtensions
{
public static int Years(DateTime dt1,DateTime dt2)
{
return Months(dt1,dt2)/12;
}
public static int Years2(DateTime start, DateTime end)
{
return (end.Year - start.Year - 1) +
(((end.Month > start.Month) ||
((end.Month == start.Month) && (end.Day >= start.Day))) ? 1 : 0);
}
public static int Months(DateTime dt1,DateTime dt2)
{
if (dt2<dt1)
{
DateTime dt0=dt1;
dt1=dt2;
dt2=dt0;
}
dt2=dt2.AddDays(-(dt1.Day-1));
return (dt2.Year-dt1.Year)*12+(dt2.Month-dt1.Month);
}
public static DateTime Max(DateTime dt1,DateTime dt2)
{
return (dt2>dt1?dt2:dt1);
}
public static DateTime Min(DateTime dt1,DateTime dt2)
{
return (dt2<dt1?dt2:dt1);
}
public static DateTime AddBusinessDays(
this DateTime current,
int days,
IEnumerable<DateTime> holidays = null)
{
var sign = Math.Sign(days);
var unsignedDays = Math.Abs(days);
for (var i = 0; i < unsignedDays; i++)
{
do
{
current = current.AddDays(sign);
}
while (current.DayOfWeek == DayOfWeek.Saturday
|| current.DayOfWeek == DayOfWeek.Sunday
|| (holidays != null && holidays.Contains(current.Date))
);
}
return current;
}
public static DateTime SubtractBusinessDays(
this DateTime current,
int days,
IEnumerable<DateTime> holidays)
{
return AddBusinessDays(current, -days, holidays);
}
public static int GetBusinessDays(
this DateTime startDate,
DateTime endDate,
IEnumerable<DateTime> holidays)
{
if (startDate > endDate)
throw new NotSupportedException("ERROR: [startDate] cannot be greater than [endDate].");
int cnt = 0;
for (var current = startDate; current < endDate; current = current.AddDays(1))
{
if (current.DayOfWeek == DayOfWeek.Saturday
|| current.DayOfWeek == DayOfWeek.Sunday
|| (holidays != null && holidays.Contains(current.Date))
)
{
}
else cnt++;
}
return cnt;
}
public static DateTime GetEasterSunday(int year)
{
int day = 0;
int month = 0;
int g = year % 19;
int c = year / 100;
int h = (c - (int)(c / 4) - (int)((8 * c + 13) / 25) + 19 * g + 15) % 30;
int i = h - (int)(h / 28) * (1 - (int)(h / 28) * (int)(29 / (h + 1)) * (int)((21 - g) / 11));
day = i - ((year + (int)(year / 4) + i + 2 - c + (int)(c / 4)) % 7) + 28;
month = 3;
if (day > 31)
{
month++;
day -= 31;
}
return new DateTime(year, month, day);
}
public static IEnumerable<DateTime> GetHolidays(IEnumerable<int> years, string countryCode = null, string cityName = null)
{
var lst = new List<DateTime>();
foreach (var year in years.Distinct())
{
lst.AddRange(new[] {
new DateTime(year, 1, 1),
new DateTime(year, 1, 6),
new DateTime(year, 5, 1),
new DateTime(year, 8, 15),
new DateTime(year, 11, 1),
new DateTime(year, 12, 8),
new DateTime(year, 12, 25),
new DateTime(year, 12, 26)
});
var easterDate = GetEasterSunday(year);
lst.Add(easterDate);
lst.Add(easterDate.AddDays(1));
if (!String.IsNullOrEmpty(countryCode))
{
switch (countryCode.ToUpper())
{
case "IT":
lst.Add(new DateTime(year, 4, 25));
break;
case "US":
lst.Add(new DateTime(year, 7, 4));
break;
case default:
break;
}
}
if (!String.IsNullOrEmpty(cityName))
{
switch (cityName)
{
case "Rome":
case "Roma":
lst.Add(new DateTime(year, 6, 29));
break;
case "Milano":
case "Milan":
lst.Add(new DateTime(year, 12, 7));
break;
default:
break;
}
}
}
return lst;
}
}
Informacje o użytkowaniu
Kod jest dość oczywisty, jednak oto kilka przykładów wyjaśniających, jak możesz go używać.
Dodaj 10 dni roboczych (pomijając tylko sobotę i niedzielę dni tygodnia)
var dtResult = DateTimeUtil.AddBusinessDays(srcDate, 10);
Dodaj 10 dni roboczych (pomijając sobotę, niedzielę i wszystkie święta niezmienione w kraju w 2019 r.)
var dtResult = DateTimeUtil.AddBusinessDays(srcDate, 10, GetHolidays(2019));
Dodaj 10 dni roboczych (pomijając sobotę, niedzielę i wszystkie włoskie święta w 2019 r.)
var dtResult = DateTimeUtil.AddBusinessDays(srcDate, 10, GetHolidays(2019, "IT"));
Dodaj 10 dni roboczych (pomijając sobotę, niedzielę, wszystkie włoskie święta i święta specyficzne dla Rzymu w 2019 r.)
var dtResult = DateTimeUtil.AddBusinessDays(srcDate, 10, GetHolidays(2019, "IT", "Rome"));
Powyższe funkcje i przykłady kodu są dokładniej wyjaśnione w tym poście na moim blogu.