Я воскрешаю этот пост, потому что сегодня мне нужно было найти способ исключить не только субботу и воскресенье, но и праздничные дни. В частности, мне нужно было обработать различные наборы возможных праздников, в том числе:
- загородные праздники (по крайней мере, для западных стран - например, 01 января).
- рассчитанные праздники (например, Пасха и Пасхальный понедельник).
- национальные праздники (например, День освобождения Италии или ID4 США).
- городские праздники (например, День святого покровителя в Риме).
- любой другой праздник по индивидуальному заказу (например, «завтра наш офис будет закрыт»).
В конце концов, я выпустил следующий набор классов помощников / расширений: хотя они не выглядят явно элегантными, поскольку в них широко используются неэффективные циклы, они достаточно приличны, чтобы решить мои проблемы навсегда. Я оставляю весь исходный код здесь, в этом посте, надеясь, что он будет полезен и кому-то еще.
Исходный код
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;
}
}
Информация об использовании
Код не требует пояснений, однако вот пара примеров, объясняющих, как его можно использовать.
Добавьте 10 рабочих дней (пропуская только субботу и воскресенье будние дни)
var dtResult = DateTimeUtil.AddBusinessDays(srcDate, 10);
Добавьте 10 рабочих дней (пропуская субботу, воскресенье и все праздничные дни, не зависящие от страны в 2019 году)
var dtResult = DateTimeUtil.AddBusinessDays(srcDate, 10, GetHolidays(2019));
Добавьте 10 рабочих дней (без субботы, воскресенья и всех праздников Италии в 2019 году)
var dtResult = DateTimeUtil.AddBusinessDays(srcDate, 10, GetHolidays(2019, "IT"));
Добавьте 10 рабочих дней (пропуская субботу, воскресенье, все итальянские праздники и праздничные дни Рима на 2019 год)
var dtResult = DateTimeUtil.AddBusinessDays(srcDate, 10, GetHolidays(2019, "IT", "Rome"));
Вышеупомянутые функции и примеры кода более подробно описаны в этом посте моего блога.