У меня раньше была такая задача, и у меня есть решение. Я бы не стал перечислять все промежуточные дни, когда этого можно избежать, как здесь. Я даже не говорю о создании кучи экземпляров DateTime, как я видел в одном из ответов выше. Это действительно пустая трата вычислительной мощности. Особенно в реальной ситуации, когда вам нужно изучить временные интервалы в несколько месяцев. См. Мой код с комментариями ниже.
///<summary>/// Calculates number of business days, taking into account:/// - weekends (Saturdays and Sundays)/// - bank holidays in the middle of the week///</summary>///<param name="firstDay">First day in the time interval</param>///<param name="lastDay">Last day in the time interval</param>///<param name="bankHolidays">List of bank holidays excluding weekends</param>///<returns>Number of business days during the 'span'</returns>publicstaticintBusinessDaysUntil(this DateTime firstDay, DateTime lastDay, params DateTime[] bankHolidays)
{
firstDay = firstDay.Date;
lastDay = lastDay.Date;
if (firstDay > lastDay)
thrownew ArgumentException("Incorrect last day " + lastDay);
TimeSpan span = lastDay - firstDay;
int businessDays = span.Days + 1;
int fullWeekCount = businessDays / 7;
// find out if there are weekends during the time exceedng the full weeksif (businessDays > fullWeekCount*7)
{
// we are here to find out if there is a 1-day or 2-days weekend// in the time interval remaining after subtracting the complete weeksint firstDayOfWeek = (int) firstDay.DayOfWeek;
int lastDayOfWeek = (int) lastDay.DayOfWeek;
if (lastDayOfWeek < firstDayOfWeek)
lastDayOfWeek += 7;
if (firstDayOfWeek <= 6)
{
if (lastDayOfWeek >= 7)// Both Saturday and Sunday are in the remaining time interval
businessDays -= 2;
elseif (lastDayOfWeek >= 6)// Only Saturday is in the remaining time interval
businessDays -= 1;
}
elseif (firstDayOfWeek <= 7 && lastDayOfWeek >= 7)// Only Sunday is in the remaining time interval
businessDays -= 1;
}
// subtract the weekends during the full weeks in the interval
businessDays -= fullWeekCount + fullWeekCount;
// subtract the number of bank holidays during the time intervalforeach (DateTime bankHoliday in bankHolidays)
{
DateTime bh = bankHoliday.Date;
if (firstDay <= bh && bh <= lastDay)
--businessDays;
}
return businessDays;
}
Редактировать Слаума, август 2011 г.
Отличный ответ! Однако есть небольшая ошибка. Я оставляю за собой право редактировать этот ответ, поскольку отвечающий отсутствует с 2009 года.
В приведенном выше коде предполагается, что он DayOfWeek.Sundayимеет значение, 7которое не соответствует действительности. Ценность есть на самом деле 0. Это приводит к неправильному расчету, если, например, firstDayи lastDayодно и то же воскресенье. В 1этом случае метод возвращается, но это должно быть 0.
Самое простое исправление этой ошибки: заменить в коде над строками, где firstDayOfWeekи lastDayOfWeekобъявлены следующим образом:
int firstDayOfWeek = firstDay.DayOfWeek == DayOfWeek.Sunday
? 7 : (int)firstDay.DayOfWeek;
int lastDayOfWeek = lastDay.DayOfWeek == DayOfWeek.Sunday
? 7 : (int)lastDay.DayOfWeek;
+1 Вероятно, это самый простой и эффективный способ сделать это (мое решение на C ++ не использует поддержку TimeSpan, C # значительно упрощает некоторые задачи). BankHolidays - тоже приятный штрих!
RedGlyph
2
Также убедитесь, что в государственные праздники следующие: if (firstDay <= bh && bh <= lastDay && bh.IsWorkingDay ())
Tawani
5
Спасибо за метод. Тем не менее, мне пришлось добавить в оператор if-оператора вычитания / итерации банковских праздников следующее:, && !(bh.DayOfWeek == DayOfWeek.Sunday || bh.DayOfWeek == DayOfWeek.Saturday)иначе он вычтет один и тот же день дважды, если выходной приходится на выходные.
KristianB
Я изменил последний цикл для оператора Linq: businessDays - = bankHolidays.Select (bankHoliday => bankHoliday.Date) .Count (bh => firstDay <= bh && bh <= lastDay);
Хорошая работа, но, возможно, использовать сами перечисления DayOfWeek, а не преобразовывать их в целые числа?
Neo
3
Серьезно, лучшее решение. Cheers Alec
Mizmor
6
Обратите внимание, что даже если эта функция возвращает значение типа double, ей следует доверять только целые рабочие дни. Он не возвращает правильный ответ для дробных дней, когда задействовано время.
Пакман
4
Отметим, что с «1+» предполагается, что с начала первого дня до конца последнего дня, без «1+» - с конца первого дня до конца последнего дня. Мне потребовалось время, чтобы понять это, так как я предполагал, что начало первого дня - начало последнего дня, что имело для меня больше смысла.
Джеффри ван де Вюрст,
11
Это НЕ правильный ответ. Дней может быть до 4. Почти верно, не учитывает, когда начало и конец дня переходят в выходные, что является самым сложным. Начало - конец также не должно быть внутри круглых скобок. Это не имеет отношения к проблеме. В 60% случаев это решение НЕПРАВИЛЬНО .
Owl
47
Я знаю, что этот вопрос уже решен, но я подумал, что могу дать более простой ответ, который может помочь другим посетителям в будущем.
Гораздо понятнее, и перечисленные решения позволяют исключить праздничные дни. Однако они намного медленнее в большом количестве; В LINQPad расчет рабочих дней для 90-дневных перерывов в 1 миллионном цикле итераций занимает 10 секунд с использованием этого решения и всего около 0,2 с с использованием принятого ответа или гораздо более хорошего ответа Алека Пожидаева.
Велоголизм
Чтобы быть инклюзивным, код должен быть таким: return Enumerable .Range (0, dayDifference + 1) ...
Эдза,
не возвращает дни в прошлом. Вроде -18 рабочих дней.
iwtu 01
@iwtu Это предполагает это to > from. Может в этом проблема?
Alpha
22
Определите метод расширения для DateTime следующим образом:
Затем используйте предложение Where для фильтрации более широкого списка дат:
var allDates = GetDates(); // method which returns a list of dates// filter dates by working day's var countOfWorkDays = allDates
.Where(day => day.IsWorkingDay())
.Count() ;
Не могли бы вы просто пойти дальше и увеличить временной интервал, чтобы вы могли его использовать - раз он сказал, что хочет использовать расстояние между двумя датами, а не список дат?
Уэсли Джонсон,
Расстояние между двумя датами - это количество дней между ними, поэтому Count () достаточно.
Carles Company
3
Я не уверен, почему это подходящий ответ ... у него нет списка отдельных дней, у него есть две даты, и он хочет найти количество рабочих дней между ними. Чтобы использовать это решение, вам нужно будет предоставить другую функцию, которая будет создавать список всех дат между твипом.
Адам Робинсон,
1
Адам, это простой пример с минимальным количеством кода, необходимым для демонстрации концепции. В свой первоначальный ответ я также включил цикл, который заполнял список allDates, который я с тех пор отвел в функцию «GetDates». Тест IsWorkingDay можно легко переместить из оператора LINQ в этот цикл. Мне лично нравится, как обстоят дела сейчас, потому что это очень легко читается людьми относительно того, что происходит.
Qwerty,
10
Можно сократить, изменив пункт «Где считать» и исключив «счетчик»
рекурсивно,
12
Я использовал следующий код, чтобы также учитывать праздничные дни:
publicclassWorkingDays
{
public List<DateTime> GetHolidays()
{
var client = new WebClient();
var json = client.DownloadString("https://www.gov.uk/bank-holidays.json");
var js = new JavaScriptSerializer();
var holidays = js.Deserialize <Dictionary<string, Holidays>>(json);
return holidays["england-and-wales"].events.Select(d => d.date).ToList();
}
publicintGetWorkingDays(DateTime from, DateTime to)
{
var totalDays = 0;
var holidays = GetHolidays();
for (var date = from.AddDays(1); date <= to; date = date.AddDays(1))
{
if (date.DayOfWeek != DayOfWeek.Saturday
&& date.DayOfWeek != DayOfWeek.Sunday
&& !holidays.Contains(date))
totalDays++;
}
return totalDays;
}
}
publicclassHolidays
{
publicstring division { get; set; }
public List<Event> events { get; set; }
}
publicclassEvent
{
public DateTime date { get; set; }
publicstring notes { get; set; }
publicstring title { get; set; }
}
И модульные тесты:
[TestClass]
publicclassWorkingDays
{
[TestMethod]
publicvoidSameDayIsZero()
{
var service = new WorkingDays();
varfrom = new DateTime(2013, 8, 12);
Assert.AreEqual(0, service.GetWorkingDays(from, from));
}
[TestMethod]
publicvoidCalculateDaysInWorkingWeek()
{
var service = new WorkingDays();
varfrom = new DateTime(2013, 8, 12);
var to = new DateTime(2013, 8, 16);
Assert.AreEqual(4, service.GetWorkingDays(from, to), "Mon - Fri = 4");
Assert.AreEqual(1, service.GetWorkingDays(from, new DateTime(2013, 8, 13)), "Mon - Tues = 1");
}
[TestMethod]
publicvoidNotIncludeWeekends()
{
var service = new WorkingDays();
varfrom = new DateTime(2013, 8, 9);
var to = new DateTime(2013, 8, 16);
Assert.AreEqual(5, service.GetWorkingDays(from, to), "Fri - Fri = 5");
Assert.AreEqual(2, service.GetWorkingDays(from, new DateTime(2013, 8, 13)), "Fri - Tues = 2");
Assert.AreEqual(1, service.GetWorkingDays(from, new DateTime(2013, 8, 12)), "Fri - Mon = 1");
}
[TestMethod]
publicvoidAccountForHolidays()
{
var service = new WorkingDays();
varfrom = new DateTime(2013, 8, 23);
Assert.AreEqual(0, service.GetWorkingDays(from, new DateTime(2013, 8, 26)), "Fri - Mon = 0");
Assert.AreEqual(1, service.GetWorkingDays(from, new DateTime(2013, 8, 27)), "Fri - Tues = 1");
}
}
почему вы начинаете отсчет с добавления 1 дня к "from" @ for (var date = from.AddDays (1); date <= to; date = date.AddDays (1))?
Oncel Umut TURER
6
Ну это забито до смерти. :) Однако я все же дам другой ответ, потому что мне нужно было что-то немного другое. Это решение отличается тем, что возвращает Business TimeSpan между началом и концом, и вы можете установить рабочие часы дня и добавить праздники. Таким образом, вы можете использовать его, чтобы рассчитать, произойдет ли это в течение дня, через несколько дней, в выходные и даже праздничные дни. И вы можете получить только рабочие дни или нет, просто получив то, что вам нужно, из возвращенного объекта TimeSpan. И по тому, как он использует списки дней, вы можете видеть, насколько легко было бы добавить список нерабочих дней, если это не типичные суббота и воскресенье. И я тестировал год, и он кажется супер быстрым.
Я просто надеюсь, что вставка кода точная. Но я знаю, что это работает.
publicstatic TimeSpan GetBusinessTimespanBetween(
DateTime start, DateTime end,
TimeSpan workdayStartTime, TimeSpan workdayEndTime,
List<DateTime> holidays = null)
{
if (end < start)
thrownew ArgumentException("start datetime must be before end datetime.");
// Just create an empty list for easier coding.if (holidays == null) holidays = new List<DateTime>();
if (holidays.Where(x => x.TimeOfDay.Ticks > 0).Any())
thrownew ArgumentException("holidays can not have a TimeOfDay, only the Date.");
var nonWorkDays = new List<DayOfWeek>() { DayOfWeek.Saturday, DayOfWeek.Sunday };
var startTime = start.TimeOfDay;
// If the start time is before the starting hours, set it to the starting hour.if (startTime < workdayStartTime) startTime = workdayStartTime;
var timeBeforeEndOfWorkDay = workdayEndTime - startTime;
// If it's after the end of the day, then this time lapse doesn't count.if (timeBeforeEndOfWorkDay.TotalSeconds < 0) timeBeforeEndOfWorkDay = new TimeSpan();
// If start is during a non work day, it doesn't count.if (nonWorkDays.Contains(start.DayOfWeek)) timeBeforeEndOfWorkDay = new TimeSpan();
elseif (holidays.Contains(start.Date)) timeBeforeEndOfWorkDay = new TimeSpan();
var endTime = end.TimeOfDay;
// If the end time is after the ending hours, set it to the ending hour.if (endTime > workdayEndTime) endTime = workdayEndTime;
var timeAfterStartOfWorkDay = endTime - workdayStartTime;
// If it's before the start of the day, then this time lapse doesn't count.if (timeAfterStartOfWorkDay.TotalSeconds < 0) timeAfterStartOfWorkDay = new TimeSpan();
// If end is during a non work day, it doesn't count.if (nonWorkDays.Contains(end.DayOfWeek)) timeAfterStartOfWorkDay = new TimeSpan();
elseif (holidays.Contains(end.Date)) timeAfterStartOfWorkDay = new TimeSpan();
// Easy scenario if the times are during the day day.if (start.Date.CompareTo(end.Date) == 0)
{
if (nonWorkDays.Contains(start.DayOfWeek)) returnnew TimeSpan();
elseif (holidays.Contains(start.Date)) returnnew TimeSpan();
return endTime - startTime;
}
else
{
var timeBetween = end - start;
var daysBetween = (int)Math.Floor(timeBetween.TotalDays);
var dailyWorkSeconds = (int)Math.Floor((workdayEndTime - workdayStartTime).TotalSeconds);
var businessDaysBetween = 0;
// Now the fun begins with calculating the actual Business days.if (daysBetween > 0)
{
var nextStartDay = start.AddDays(1).Date;
var dayBeforeEnd = end.AddDays(-1).Date;
for (DateTime d = nextStartDay; d <= dayBeforeEnd; d = d.AddDays(1))
{
if (nonWorkDays.Contains(d.DayOfWeek)) continue;
elseif (holidays.Contains(d.Date)) continue;
businessDaysBetween++;
}
}
var dailyWorkSecondsToAdd = dailyWorkSeconds * businessDaysBetween;
var output = timeBeforeEndOfWorkDay + timeAfterStartOfWorkDay;
output = output + new TimeSpan(0, 0, dailyWorkSecondsToAdd);
return output;
}
}
И вот тестовый код: обратите внимание, что вам просто нужно поместить эту функцию в класс с именем DateHelper, чтобы тестовый код работал.
[TestMethod]
publicvoidTestGetBusinessTimespanBetween()
{
var workdayStart = new TimeSpan(8, 0, 0);
var workdayEnd = new TimeSpan(17, 0, 0);
var holidays = new List<DateTime>()
{
new DateTime(2018, 1, 15), // a Mondaynew DateTime(2018, 2, 15) // a Thursday
};
var testdata = new[]
{
new
{
expectedMinutes = 0,
start = new DateTime(2016, 10, 19, 9, 50, 0),
end = new DateTime(2016, 10, 19, 9, 50, 0)
},
new
{
expectedMinutes = 10,
start = new DateTime(2016, 10, 19, 9, 50, 0),
end = new DateTime(2016, 10, 19, 10, 0, 0)
},
new
{
expectedMinutes = 5,
start = new DateTime(2016, 10, 19, 7, 50, 0),
end = new DateTime(2016, 10, 19, 8, 5, 0)
},
new
{
expectedMinutes = 5,
start = new DateTime(2016, 10, 19, 16, 55, 0),
end = new DateTime(2016, 10, 19, 17, 5, 0)
},
new
{
expectedMinutes = 15,
start = new DateTime(2016, 10, 19, 16, 50, 0),
end = new DateTime(2016, 10, 20, 8, 5, 0)
},
new
{
expectedMinutes = 10,
start = new DateTime(2016, 10, 19, 16, 50, 0),
end = new DateTime(2016, 10, 20, 7, 55, 0)
},
new
{
expectedMinutes = 5,
start = new DateTime(2016, 10, 19, 17, 10, 0),
end = new DateTime(2016, 10, 20, 8, 5, 0)
},
new
{
expectedMinutes = 0,
start = new DateTime(2016, 10, 19, 17, 10, 0),
end = new DateTime(2016, 10, 20, 7, 5, 0)
},
new
{
expectedMinutes = 545,
start = new DateTime(2016, 10, 19, 12, 10, 0),
end = new DateTime(2016, 10, 20, 12, 15, 0)
},
// Spanning multiple weekdaysnew
{
expectedMinutes = 835,
start = new DateTime(2016, 10, 19, 12, 10, 0),
end = new DateTime(2016, 10, 21, 8, 5, 0)
},
// Spanning multiple weekdaysnew
{
expectedMinutes = 1375,
start = new DateTime(2016, 10, 18, 12, 10, 0),
end = new DateTime(2016, 10, 21, 8, 5, 0)
},
// Spanning from a Thursday to a Tuesday, 5 mins short of complete day.new
{
expectedMinutes = 1615,
start = new DateTime(2016, 10, 20, 12, 10, 0),
end = new DateTime(2016, 10, 25, 12, 5, 0)
},
// Spanning from a Thursday to a Tuesday, 5 mins beyond complete day.new
{
expectedMinutes = 1625,
start = new DateTime(2016, 10, 20, 12, 10, 0),
end = new DateTime(2016, 10, 25, 12, 15, 0)
},
// Spanning from a Friday to a Monday, 5 mins beyond complete day.new
{
expectedMinutes = 545,
start = new DateTime(2016, 10, 21, 12, 10, 0),
end = new DateTime(2016, 10, 24, 12, 15, 0)
},
// Spanning from a Friday to a Monday, 5 mins short complete day.new
{
expectedMinutes = 535,
start = new DateTime(2016, 10, 21, 12, 10, 0),
end = new DateTime(2016, 10, 24, 12, 5, 0)
},
// Spanning from a Saturday to a Monday, 5 mins short complete day.new
{
expectedMinutes = 245,
start = new DateTime(2016, 10, 22, 12, 10, 0),
end = new DateTime(2016, 10, 24, 12, 5, 0)
},
// Spanning from a Saturday to a Sunday, 5 mins beyond complete day.new
{
expectedMinutes = 0,
start = new DateTime(2016, 10, 22, 12, 10, 0),
end = new DateTime(2016, 10, 23, 12, 15, 0)
},
// Times within the same Saturday.new
{
expectedMinutes = 0,
start = new DateTime(2016, 10, 22, 12, 10, 0),
end = new DateTime(2016, 10, 23, 12, 15, 0)
},
// Spanning from a Saturday to the Sunday next week.new
{
expectedMinutes = 2700,
start = new DateTime(2016, 10, 22, 12, 10, 0),
end = new DateTime(2016, 10, 30, 12, 15, 0)
},
// Spanning a year.new
{
expectedMinutes = 143355,
start = new DateTime(2016, 10, 22, 12, 10, 0),
end = new DateTime(2017, 10, 30, 12, 15, 0)
},
// Spanning a year with 2 holidays.new
{
expectedMinutes = 142815,
start = new DateTime(2017, 10, 22, 12, 10, 0),
end = new DateTime(2018, 10, 30, 12, 15, 0)
},
};
foreach (var item in testdata)
{
Assert.AreEqual(item.expectedMinutes,
DateHelper.GetBusinessTimespanBetween(
item.start, item.end,
workdayStart, workdayEnd,
holidays)
.TotalMinutes);
}
}
Это решение избегает итераций, работает как для + ve, так и для -ve разницы дней недели и включает набор модульных тестов для регрессии против более медленного метода подсчета дней недели. Я также включил краткий метод добавления будних дней, который также работает таким же неитеративным способом.
Модульные тесты охватывают несколько тысяч комбинаций дат, чтобы исчерпывающе протестировать все комбинации дней начала и конца дня недели как для малых, так и для больших диапазонов дат.
Важный : мы предполагаем, что считаем дни, исключая дату начала и включая дату окончания. Это важно при подсчете дней недели, поскольку определенные дни начала / окончания, которые вы включаете / исключаете, влияют на результат. Это также гарантирует, что разница между двумя равными днями всегда равна нулю и что мы включаем только полные рабочие дни, поскольку обычно вы хотите, чтобы ответ был правильным для любого времени в текущую дату начала (часто сегодня) и включал полную дату окончания (например, срок).
ПРИМЕЧАНИЕ. Этот код требует дополнительной настройки для праздников, но в соответствии с приведенным выше предположением, этот код должен исключать праздники в дату начала.
staticreadonlyint[,] _diffOffset =
{
// Su M Tu W Th F Sa
{0, 1, 2, 3, 4, 5, 5}, // Su
{4, 0, 1, 2, 3, 4, 4}, // M
{3, 4, 0, 1, 2, 3, 3}, // Tu
{2, 3, 4, 0, 1, 2, 2}, // W
{1, 2, 3, 4, 0, 1, 1}, // Th
{0, 1, 2, 3, 4, 0, 0}, // F
{0, 1, 2, 3, 4, 5, 0}, // Sa
};
publicstaticintGetWeekdaysDiff(this DateTime dtStart, DateTime dtEnd)
{
int daysDiff = (int)(dtEnd - dtStart).TotalDays;
return daysDiff >= 0
? 5 * (daysDiff / 7) + _diffOffset[(int) dtStart.DayOfWeek, (int) dtEnd.DayOfWeek]
: 5 * (daysDiff / 7) - _diffOffset[6 - (int) dtStart.DayOfWeek, 6 - (int) dtEnd.DayOfWeek];
}
Я обнаружил, что большинство других решений по переполнению стека были либо медленными (итеративными), либо чрезмерно сложными, а многие были просто неверными. Мораль этой истории ... Не верь ей, если вы ее тщательно не проверили !!
Идея для этого пришла из решения SQL, которое я нашел при переполнении стека. Их идея была надежной, но, к сожалению, в ней тоже была ошибка. Он работал для значений + ve, но их сопоставление таблицы поиска было неправильным для значений -ve.
Тони О'Хаган
4
Вот код для этой цели, со шведскими праздниками, но вы можете настроить, какие праздники считать. Обратите внимание, что я добавил ограничение, которое вы, возможно, захотите удалить, но это было для веб-системы, и я не хотел, чтобы кто-то вводил какую-то огромную дату, чтобы затормозить процесс.
publicstaticintGetWorkdays(DateTime from ,DateTime to)
{
int limit = 9999;
int counter = 0;
DateTime current = from;
int result = 0;
if (from > to)
{
DateTime temp = from;
from = to;
to = temp;
}
if (from >= to)
{
return0;
}
while (current <= to && counter < limit)
{
if (IsSwedishWorkday(current))
{
result++;
}
current = current.AddDays(1);
counter++;
}
return result;
}
publicstaticboolIsSwedishWorkday(DateTime date)
{
return (!IsSwedishHoliday(date) && date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday);
}
publicstaticboolIsSwedishHoliday(DateTime date)
{
return (
IsSameDay(GetEpiphanyDay(date.Year), date) ||
IsSameDay(GetMayDay(date.Year), date) ||
IsSameDay(GetSwedishNationalDay(date.Year), date) ||
IsSameDay(GetChristmasDay(date.Year), date) ||
IsSameDay(GetBoxingDay(date.Year), date) ||
IsSameDay(GetGoodFriday(date.Year), date) ||
IsSameDay(GetAscensionDay(date.Year), date) ||
IsSameDay(GetAllSaintsDay(date.Year), date) ||
IsSameDay(GetMidsummersDay(date.Year), date) ||
IsSameDay(GetPentecostDay(date.Year), date) ||
IsSameDay(GetEasterMonday(date.Year), date) ||
IsSameDay(GetNewYearsDay(date.Year), date) ||
IsSameDay(GetEasterDay(date.Year), date)
);
}
// Trettondagenpublicstatic DateTime GetEpiphanyDay(int year)
{
returnnew DateTime(year, 1, 6);
}
// Första majpublicstatic DateTime GetMayDay(int year)
{
returnnew DateTime(year,5,1);
}
// Juldagenpublicstatic DateTime GetSwedishNationalDay(int year)
{
returnnew DateTime(year, 6, 6);
}
// Juldagenpublicstatic DateTime GetNewYearsDay(int year)
{
returnnew DateTime(year,1,1);
}
// Juldagenpublicstatic DateTime GetChristmasDay(int year)
{
returnnew DateTime(year,12,25);
}
// Annandag julpublicstatic DateTime GetBoxingDay(int year)
{
returnnew DateTime(year, 12, 26);
}
// Långfredagenpublicstatic DateTime GetGoodFriday(int year)
{
return GetEasterDay(year).AddDays(-3);
}
// Kristi himmelsfärdsdagpublicstatic DateTime GetAscensionDay(int year)
{
return GetEasterDay(year).AddDays(5*7+4);
}
// Midsommarpublicstatic DateTime GetAllSaintsDay(int year)
{
DateTime result = new DateTime(year,10,31);
while (result.DayOfWeek != DayOfWeek.Saturday)
{
result = result.AddDays(1);
}
return result;
}
// Midsommarpublicstatic DateTime GetMidsummersDay(int year)
{
DateTime result = new DateTime(year, 6, 20);
while (result.DayOfWeek != DayOfWeek.Saturday)
{
result = result.AddDays(1);
}
return result;
}
// Pingstdagenpublicstatic DateTime GetPentecostDay(int year)
{
return GetEasterDay(year).AddDays(7 * 7);
}
// Annandag påskpublicstatic DateTime GetEasterMonday(int year)
{
return GetEasterDay(year).AddDays(1);
}
publicstatic DateTime GetEasterDay(int y)
{
double c;
double n;
double k;
double i;
double j;
double l;
double m;
double d;
c = System.Math.Floor(y / 100.0);
n = y - 19 * System.Math.Floor(y / 19.0);
k = System.Math.Floor((c - 17) / 25.0);
i = c - System.Math.Floor(c / 4) - System.Math.Floor((c - k) / 3) + 19 * n + 15;
i = i - 30 * System.Math.Floor(i / 30);
i = i - System.Math.Floor(i / 28) * (1 - System.Math.Floor(i / 28) * System.Math.Floor(29 / (i + 1)) * System.Math.Floor((21 - n) / 11));
j = y + System.Math.Floor(y / 4.0) + i + 2 - c + System.Math.Floor(c / 4);
j = j - 7 * System.Math.Floor(j / 7);
l = i - j;
m = 3 + System.Math.Floor((l + 40) / 44);// month
d = l + 28 - 31 * System.Math.Floor(m / 4);// daydouble days = ((m == 3) ? d : d + 31);
DateTime result = new DateTime(y, 3, 1).AddDays(days-1);
return result;
}
функция issamedate отсутствует, но является просто публичным статическим логическим значением IsSameDay (DateTime date1, DateTime date2) {return date1.Date == date2.Date; }
Choco Smith
Вы можете использовать таблицу поиска массива int вместо создания экземпляров новых объектов Date.
TheRealChx101,
3
Вот небольшой пример кода. Это метод класса, поэтому он будет работать только внутри вашего класса. Если хотите static, поменяйте подпись на private static(или public static).
private IEnumerable<DateTime> GetWorkingDays(DateTime sd, DateTime ed)
{
for (var d = sd; d <= ed; d = d.AddDays(1))
if (d.DayOfWeek != DayOfWeek.Saturday && d.DayOfWeek != DayOfWeek.Sunday)
yieldreturn d;
}
Этот метод создает переменную цикла d, инициализирует ее начальным днем sd, а затем увеличивает на один день каждую итерацию ( d = d.AddDays(1)).
Он возвращает желаемые значения с помощью yield, который создает файл iterator. Самое замечательное в итераторах заключается в том, что они не хранят все значения IEnumerableв памяти, а только вызывают каждое из них последовательно. Это означает, что вы можете вызывать этот метод с незапамятных времен, не беспокоясь о нехватке памяти.
Этот метод не возвращает количество рабочих дней между двумя датами, он возвращает рабочие даты между двумя датами. Код, который вы предлагаете, очень чистый, и мне нравится использование yield, но он не отвечает на вопрос.
Мартин
3
Я много искал легко усваиваемый алгоритм для расчета рабочих дней между двумя датами, а также для исключения национальных праздников, и, наконец, я решил пойти с этим подходом:
publicstaticintNumberOfWorkingDaysBetween2Dates(DateTime start,DateTime due,IEnumerable<DateTime> holidays)
{
var dic = new Dictionary<DateTime, DayOfWeek>();
var totalDays = (due - start).Days;
for (int i = 0; i < totalDays + 1; i++)
{
if (!holidays.Any(x => x == start.AddDays(i)))
dic.Add(start.AddDays(i), start.AddDays(i).DayOfWeek);
}
return dic.Where(x => x.Value != DayOfWeek.Saturday && x.Value != DayOfWeek.Sunday).Count();
}
По сути, я хотел идти на каждое свидание и оценивать свои условия:
Это не суббота
Это не воскресенье
Это не национальный праздник
но также я хотел избежать повторения дат.
Запустив и измерив время, необходимое для оценки 1 полного года, я пришел к следующему результату:
staticvoidMain(string[] args)
{
var start = new DateTime(2017, 1, 1);
var due = new DateTime(2017, 12, 31);
var sw = Stopwatch.StartNew();
var days = NumberOfWorkingDaysBetween2Dates(start, due,NationalHolidays());
sw.Stop();
Console.WriteLine($"Total working days = {days} --- time: {sw.Elapsed}");
Console.ReadLine();
// result is:// Total working days = 249-- - time: 00:00:00.0269087
}
Я думаю, что ни один из приведенных выше ответов не является правильным. Ни один из них не решает все особые случаи, например, когда даты начинаются и заканчиваются в середине выходных, когда дата начинается в пятницу и заканчивается в следующий понедельник и т. Д. Кроме того, все они округляют вычисления до целого. дней, поэтому, если дата начала находится в середине субботы, например, из рабочих дней будет вычтена целый день, что приведет к неверным результатам ...
В любом случае, вот мое решение, которое довольно эффективно и просто и работает во всех случаях. Хитрость заключается в том, чтобы просто найти предыдущий понедельник для дат начала и окончания, а затем сделать небольшую компенсацию, когда начало и конец происходят в выходные:
Этот метод не использует никаких циклов и на самом деле довольно прост. Он расширяет диапазон дат до полных недель, поскольку мы знаем, что в каждой неделе 5 рабочих дней. Затем он использует таблицу поиска, чтобы найти количество рабочих дней, которое нужно вычесть из начала и конца, чтобы получить правильный результат. Я расширил расчет, чтобы показать, что происходит, но при необходимости все это можно свести в одну строку.
В любом случае, это работает для меня, и поэтому я решил опубликовать его здесь, если это может помочь другим. Удачного кодирования.
Расчет
t: общее количество дней между датами (1, если мин. = макс.)
a + b: дополнительные дни, необходимые для увеличения общей суммы до полных недель
k: 1.4 - количество рабочих дней в неделю, т.е. (t / 7) * 5
c: количество дней недели, которое нужно вычесть из общей суммы
m: таблица поиска, используемая для поиска значения "c" для каждого дня недели.
Культура
Код предполагает рабочую неделю с понедельника по пятницу. Для других культур, например с воскресенья на четверг, вам нужно будет сместить даты перед вычислением.
Метод
publicintWeekdays(DateTime min, DateTime max)
{
if (min.Date > max.Date) thrownew Exception("Invalid date span");
var t = (max.AddDays(1).Date - min.Date).TotalDays;
var a = (int) min.DayOfWeek;
var b = 6 - (int) max.DayOfWeek;
var k = 1.4;
var m = newint[]{0, 0, 1, 2, 3, 4, 5};
var c = m[a] + m[b];
return (int)((t + a + b) / k) - c;
}
Я просто поделюсь своим решением. У меня это сработало, может я просто не замечаю / не знаю, что это ошибка. Я начал с получения первой неполной недели, если она есть. полная неделя начиналась с воскресенья по субботу, поэтому, если (int) _now.DayOfWeek не было 0 (воскресенье), первая неделя была неполной.
Я просто вычитаю 1 из подсчета первых недель субботы первой недели, а затем добавляю его к новому счету;
Затем я получаю данные о последней неполной неделе, затем вычитаю 1 для воскресенья и добавляю к новому счету.
Затем, наконец, к новому счету было добавлено количество полных недель, умноженное на 5 (будние дни).
У меня возникли проблемы с поиском надежной версии этого кода TSQL. Ниже приведено преобразование кода C # с добавлением таблицы Holiday, которая должна использоваться для предварительного расчета праздников.
CREATE TABLE dbo.Holiday
(
HolidayDt DATE NOT NULL,
Name NVARCHAR(50) NOT NULL,
IsWeekday BIT NOT NULL,
CONSTRAINT PK_Holiday PRIMARY KEY (HolidayDt)
)
GO
CREATE INDEX IDX_Holiday ON Holiday (HolidayDt, IsWeekday)
GO
CREATE function dbo.GetBusinessDays
(
@FirstDay datetime,
@LastDay datetime
)
RETURNS INT
AS
BEGIN
DECLARE @BusinessDays INT, @FullWeekCount INT
SELECT @FirstDay = CONVERT(DATETIME,CONVERT(DATE,@FirstDay))
, @LastDay = CONVERT(DATETIME,CONVERT(DATE,@LastDay))
IF @FirstDay > @LastDay
RETURN NULL;
SELECT @BusinessDays = DATEDIFF(DAY, @FirstDay, @LastDay) + 1
SELECT @FullWeekCount = @BusinessDays / 7;
-- find outif there are weekends during the time exceedng the full weeks
IF @BusinessDays > (@FullWeekCount * 7)
BEGIN
-- we are here to find outif there is a 1-day or 2-days weekend
-- in the time interval remaining after subtracting the complete weeks
DECLARE @firstDayOfWeek INT, @lastDayOfWeek INT;
SELECT @firstDayOfWeek = DATEPART(DW, @FirstDay), @lastDayOfWeek = DATEPART(DW, @LastDay);
IF @lastDayOfWeek < @firstDayOfWeek
SELECT @lastDayOfWeek = @lastDayOfWeek + 7;
IF @firstDayOfWeek <= 6BEGIN
IF (@lastDayOfWeek >= 7) --Both Saturday and Sunday are in the remaining time interval
BEGIN
SELECT @BusinessDays = @BusinessDays - 2
END
ELSE IF @lastDayOfWeek>=6 --Only Saturday isin the remaining time interval
BEGIN
SELECT @BusinessDays = @BusinessDays - 1
END
END
ELSE IF @firstDayOfWeek <= 7 AND @lastDayOfWeek >=7 -- Only Sunday isin the remaining time interval
BEGIN
SELECT @BusinessDays = @BusinessDays - 1
END
END
-- subtract the weekends during the full weeks in the interval
DECLARE @Holidays INT;
SELECT @Holidays = COUNT(*)
FROM Holiday
WHERE HolidayDt BETWEEN @FirstDay AND @LastDay
AND IsWeekday = CAST(1 AS BIT)
SELECT @BusinessDays = @BusinessDays - (@FullWeekCount + @FullWeekCount) -- - @Holidays
RETURN @BusinessDays
END
Вот одно очень простое решение этой проблемы. У нас есть начальная дата, конечная дата и «цикл for» для увеличения дня и вычислений, чтобы увидеть, рабочий это или выходной, преобразовав в строку DayOfWeek.
classProgram
{
staticvoidMain(string[] args)
{
DateTime day = new DateTime();
Console.Write("Inser your end date (example: 01/30/2015): ");
DateTime endDate = DateTime.Parse(Console.ReadLine());
int numberOfDays = 0;
for (day = DateTime.Now.Date; day.Date < endDate.Date; day = day.Date.AddDays(1))
{
string dayToString = Convert.ToString(day.DayOfWeek);
if (dayToString != "Saturday" && dayToString != "Sunday") numberOfDays++;
}
Console.WriteLine("Number of working days (not including local holidays) between two dates is "+numberOfDays);
}
}
На основе комментария, помеченного как ответ и рекомендованного исправления, а также -> Эта версия хочет преобразовать дни в рабочие часы ... Считает также часы одного дня.
///<summary>/// Calculates number of business days, taking into account:/// - weekends (Saturdays and Sundays)/// - bank holidays in the middle of the week///</summary>///<param name="firstDay">First day in the time interval</param>///<param name="lastDay">Last day in the time interval</param>///<param name="bankHolidays">List of bank holidays excluding weekends</param>///<returns>Number of business hours during the 'span'</returns>publicstaticintBusinessHoursUntil(DateTime firstDay, DateTime lastDay, params DateTime[] bankHolidays)
{
var original_firstDay = firstDay;
var original_lastDay = lastDay;
firstDay = firstDay.Date;
lastDay = lastDay.Date;
if (firstDay > lastDay)
return-1; //// throw new ArgumentException("Incorrect last day " + lastDay);
TimeSpan span = lastDay - firstDay;
int businessDays = span.Days + 1;
int fullWeekCount = businessDays / 7;
// find out if there are weekends during the time exceedng the full weeksif (businessDays > fullWeekCount * 7)
{
// we are here to find out if there is a 1-day or 2-days weekend// in the time interval remaining after subtracting the complete weeksint firstDayOfWeek = firstDay.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)firstDay.DayOfWeek;
int lastDayOfWeek = lastDay.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)lastDay.DayOfWeek;
if (lastDayOfWeek < firstDayOfWeek)
lastDayOfWeek += 7;
if (firstDayOfWeek <= 6)
{
if (lastDayOfWeek >= 7)// Both Saturday and Sunday are in the remaining time interval
businessDays -= 2;
elseif (lastDayOfWeek >= 6)// Only Saturday is in the remaining time interval
businessDays -= 1;
}
elseif (firstDayOfWeek <= 7 && lastDayOfWeek >= 7)// Only Sunday is in the remaining time interval
businessDays -= 1;
}
// subtract the weekends during the full weeks in the interval
businessDays -= fullWeekCount + fullWeekCount;
if (bankHolidays != null && bankHolidays.Any())
{
// subtract the number of bank holidays during the time intervalforeach (DateTime bankHoliday in bankHolidays)
{
DateTime bh = bankHoliday.Date;
if (firstDay <= bh && bh <= lastDay)
--businessDays;
}
}
int total_business_hours = 0;
if (firstDay.Date == lastDay.Date)
{//If on the same day, go granular with Hours from the Orginial_*Day values
total_business_hours = (int)(original_lastDay - original_firstDay).TotalHours;
}
else
{//Convert Business-Days to TotalHours
total_business_hours = (int)(firstDay.AddDays(businessDays).AddHours(firstDay.Hour) - firstDay).TotalHours;
}
return total_business_hours;
}
Я только что улучшил ответ @Alexander и @Slauma, чтобы поддержать рабочую неделю в качестве параметра, для случаев, когда суббота является рабочим днем, или даже для случаев, когда есть только пара дней недели, которые считаются рабочими днями:
///<summary>/// Calculate the number of business days between two dates, considering:/// - Days of the week that are not considered business days./// - Holidays between these two dates.///</summary>///<param name="fDay">First day of the desired 'span'.</param>///<param name="lDay">Last day of the desired 'span'.</param>///<param name="BusinessDaysOfWeek">Days of the week that are considered to be business days, if NULL considers monday, tuesday, wednesday, thursday and friday as business days of the week.</param>///<param name="Holidays">Holidays, if NULL, considers no holiday.</param>///<returns>Number of business days during the 'span'</returns>publicstaticintBusinessDaysUntil(this DateTime fDay, DateTime lDay, DayOfWeek[] BusinessDaysOfWeek = null, DateTime[] Holidays = null)
{
if (BusinessDaysOfWeek == null)
BusinessDaysOfWeek = new DayOfWeek[] { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday };
if (Holidays == null)
Holidays = new DateTime[] { };
fDay = fDay.Date;
lDay = lDay.Date;
if (fDay > lDay)
thrownew ArgumentException("Incorrect last day " + lDay);
int bDays = (lDay - fDay).Days + 1;
int fullWeekCount = bDays / 7;
int fullWeekCountMult = 7 - WeekDays.Length;
// Find out if there are weekends during the time exceedng the full weeksif (bDays > (fullWeekCount * 7))
{
int fDayOfWeek = (int)fDay.DayOfWeek;
int lDayOfWeek = (int)lDay.DayOfWeek;
if (fDayOfWeek > lDayOfWeek)
lDayOfWeek += 7;
// If they are the same, we already covered it right before the Holiday subtractionif (lDayOfWeek != fDayOfWeek)
{
// Here we need to see if any of the days between are considered business daysfor (int i = fDayOfWeek; i <= lDayOfWeek; i++)
if (!WeekDays.Contains((DayOfWeek)(i > 6 ? i - 7 : i)))
bDays -= 1;
}
}
// Subtract the days that are not in WeekDays[] during the full weeks in the interval
bDays -= (fullWeekCount * fullWeekCountMult);
// Subtract the number of bank holidays during the time interval
bDays = bDays - Holidays.Select(x => x.Date).Count(x => fDay <= x && x <= lDay);
return bDays;
}
Вот функция, которую мы можем использовать для расчета рабочих дней между двумя датами. Я не использую праздничный список, так как он может варьироваться в зависимости от страны / региона.
Если мы все равно хотим использовать его, мы можем взять третий аргумент как список праздников, и перед увеличением счетчика мы должны проверить, что список не содержит d
publicstaticintGetBussinessDaysBetweenTwoDates(DateTime StartDate, DateTime EndDate)
{
if (StartDate > EndDate)
return-1;
int bd = 0;
for (DateTime d = StartDate; d < EndDate; d = d.AddDays(1))
{
if (d.DayOfWeek != DayOfWeek.Saturday && d.DayOfWeek != DayOfWeek.Sunday)
bd++;
}
return bd;
}
Еще одна идея - этот метод позволяет указать любую рабочую неделю и праздники.
Идея здесь в том, что мы находим ядро диапазона дат от первого первого рабочего дня недели до последнего выходного дня недели. Это позволяет нам легко рассчитывать целые недели ( без перебора всех дат). Все, что нам нужно сделать, это добавить рабочие дни, приходящиеся на начало и конец этого основного диапазона.
publicstaticintCalculateWorkingDays(
DateTime startDate,
DateTime endDate,
IList<DateTime> holidays,
DayOfWeek firstDayOfWeek,
DayOfWeek lastDayOfWeek)
{
// Make sure the defined working days run contiguouslyif (lastDayOfWeek < firstDayOfWeek)
{
thrownew Exception("Last day of week cannot fall before first day of week!");
}
// Create a list of the days of the week that make-up the weekend by working back// from the firstDayOfWeek and forward from lastDayOfWeek to get the start and end// the weekendvar weekendStart = lastDayOfWeek == DayOfWeek.Saturday ? DayOfWeek.Sunday : lastDayOfWeek + 1;
var weekendEnd = firstDayOfWeek == DayOfWeek.Sunday ? DayOfWeek.Saturday : firstDayOfWeek - 1;
var weekendDays = new List<DayOfWeek>();
var w = weekendStart;
do {
weekendDays.Add(w);
if (w == weekendEnd) break;
w = (w == DayOfWeek.Saturday) ? DayOfWeek.Sunday : w + 1;
} while (true);
// Force simple dates - no time
startDate = startDate.Date;
endDate = endDate.Date;
// Ensure a progessive date rangeif (endDate < startDate)
{
var t = startDate;
startDate = endDate;
endDate = t;
}
// setup some working variables and constantsconstint daysInWeek = 7; // yeah - really!var actualStartDate = startDate; // this will end up on startOfWeek boundaryvar actualEndDate = endDate; // this will end up on weekendEnd boundaryint workingDaysInWeek = daysInWeek - weekendDays.Count;
int workingDays = 0; // the result we are trying to findint leadingDays = 0; // the number of working days leading up to the firstDayOfWeek boundaryint trailingDays = 0; // the number of working days counting back to the weekendEnd boundary// Calculate leading working days// if we aren't on the firstDayOfWeek we need to step forward to the nearestif (startDate.DayOfWeek != firstDayOfWeek)
{
var d = startDate;
do {
if (d.DayOfWeek == firstDayOfWeek || d >= endDate)
{
actualStartDate = d;
break;
}
if (!weekendDays.Contains(d.DayOfWeek))
{
leadingDays++;
}
d = d.AddDays(1);
} while(true);
}
// Calculate trailing working days// if we aren't on the weekendEnd we step back to the nearestif (endDate >= actualStartDate && endDate.DayOfWeek != weekendEnd)
{
var d = endDate;
do {
if (d.DayOfWeek == weekendEnd || d < actualStartDate)
{
actualEndDate = d;
break;
}
if (!weekendDays.Contains(d.DayOfWeek))
{
trailingDays++;
}
d = d.AddDays(-1);
} while(true);
}
// Calculate the inclusive number of days between the actualStartDate and the actualEndDatevar coreDays = (actualEndDate - actualStartDate).Days + 1;
var noWeeks = coreDays / daysInWeek;
// add together leading, core and trailing days
workingDays += noWeeks * workingDaysInWeek;
workingDays += leadingDays;
workingDays += trailingDays;
// Finally remove any holidays that fall within the range.if (holidays != null)
{
workingDays -= holidays.Count(h => h >= startDate && (h <= endDate));
}
return workingDays;
}
Поскольку я не могу комментировать. Есть еще одна проблема с принятым решением, когда государственные праздники вычитаются, даже если они приходятся на выходные. Видя, как проверяется другой ввод, вполне логично, что и это тоже.
Следовательно, foreach должен быть:
// subtract the number of bank holidays during the time intervalforeach (DateTime bankHoliday in bankHolidays)
{
DateTime bh = bankHoliday.Date;
// Do not subtract bank holidays when they fall in the weekend to avoid double subtractionif (bh.DayOfWeek == DayOfWeek.Saturday || bh.DayOfWeek == DayOfWeek.Sunday)
continue;
if (firstDay <= bh && bh <= lastDay)
--businessDays;
}
Вот подход, если вы используете MVC. Я также рассчитал национальные праздники или любые праздничные дни, которые нужно исключить, взяв их из календаря праздников, который вам нужно будет сделать.
Вот вспомогательная функция, которую я написал для этой задачи. он также возвращает количество выходных через outпараметр. при желании вы можете настроить "выходные" дни во время выполнения для стран, в которых используются другие выходные дни, или включить праздничные дни с помощью weekendDays[]необязательного параметра:
publicstaticintGetNetworkDays(DateTime startDate, DateTime endDate,outint totalWeekenDays, DayOfWeek[] weekendDays = null)
{
if (startDate >= endDate)
{
thrownew Exception("start date can not be greater then or equel to end date");
}
DayOfWeek[] weekends = new DayOfWeek[] { DayOfWeek.Sunday, DayOfWeek.Saturday };
if (weekendDays != null)
{
weekends = weekendDays;
}
var totaldays = (endDate - startDate).TotalDays + 1; // add one to include the first day tovar counter = 0;
var workdaysCounter = 0;
var weekendsCounter = 0;
for (int i = 0; i < totaldays; i++)
{
if (weekends.Contains(startDate.AddDays(counter).DayOfWeek))
{
weekendsCounter++;
}
else
{
workdaysCounter++;
}
counter++;
}
totalWeekenDays = weekendsCounter;
return workdaysCounter;
}
publicstaticintCalculateBusinessDaysInRange(this DateTime startDate, DateTime endDate, params DateTime[] holidayDates)
{
endDate = endDate.Date;
if(startDate > endDate)
thrownew ArgumentException("The end date can not be before the start date!", nameof(endDate));
int accumulator = 0;
DateTime itterator = startDate.Date;
do
{
if(itterator.DayOfWeek != DayOfWeek.Saturday && itterator.DayOfWeek != DayOfWeek.Sunday && !holidayDates.Any(hol => hol.Date == itterator))
{ accumulator++; }
}
while((itterator = itterator.AddDays(1)).Date <= endDate);
return accumulator
}
Я публикую это только потому, что, несмотря на все полученные отличные ответы, ни одна математика не имела для меня смысла. Это определенно метод KISS, который должен работать и быть достаточно поддерживаемым. Конечно, если вы рассчитываете диапазоны, превышающие 2–3 месяца, это не будет самым эффективным способом. Мы просто определяем, суббота это или воскресенье, или это дата праздника. Если нет, добавляем рабочий день. Если да, то все в порядке.
Я уверен, что это можно было бы еще больше упростить с помощью LINQ, но этот способ намного проще для понимания.
Ответы:
У меня раньше была такая задача, и у меня есть решение. Я бы не стал перечислять все промежуточные дни, когда этого можно избежать, как здесь. Я даже не говорю о создании кучи экземпляров DateTime, как я видел в одном из ответов выше. Это действительно пустая трата вычислительной мощности. Особенно в реальной ситуации, когда вам нужно изучить временные интервалы в несколько месяцев. См. Мой код с комментариями ниже.
/// <summary> /// Calculates number of business days, taking into account: /// - weekends (Saturdays and Sundays) /// - bank holidays in the middle of the week /// </summary> /// <param name="firstDay">First day in the time interval</param> /// <param name="lastDay">Last day in the time interval</param> /// <param name="bankHolidays">List of bank holidays excluding weekends</param> /// <returns>Number of business days during the 'span'</returns> public static int BusinessDaysUntil(this DateTime firstDay, DateTime lastDay, params DateTime[] bankHolidays) { firstDay = firstDay.Date; lastDay = lastDay.Date; if (firstDay > lastDay) throw new ArgumentException("Incorrect last day " + lastDay); TimeSpan span = lastDay - firstDay; int businessDays = span.Days + 1; int fullWeekCount = businessDays / 7; // find out if there are weekends during the time exceedng the full weeks if (businessDays > fullWeekCount*7) { // we are here to find out if there is a 1-day or 2-days weekend // in the time interval remaining after subtracting the complete weeks int firstDayOfWeek = (int) firstDay.DayOfWeek; int lastDayOfWeek = (int) lastDay.DayOfWeek; if (lastDayOfWeek < firstDayOfWeek) lastDayOfWeek += 7; if (firstDayOfWeek <= 6) { if (lastDayOfWeek >= 7)// Both Saturday and Sunday are in the remaining time interval businessDays -= 2; else if (lastDayOfWeek >= 6)// Only Saturday is in the remaining time interval businessDays -= 1; } else if (firstDayOfWeek <= 7 && lastDayOfWeek >= 7)// Only Sunday is in the remaining time interval businessDays -= 1; } // subtract the weekends during the full weeks in the interval businessDays -= fullWeekCount + fullWeekCount; // subtract the number of bank holidays during the time interval foreach (DateTime bankHoliday in bankHolidays) { DateTime bh = bankHoliday.Date; if (firstDay <= bh && bh <= lastDay) --businessDays; } return businessDays; }
Редактировать Слаума, август 2011 г.
Отличный ответ! Однако есть небольшая ошибка. Я оставляю за собой право редактировать этот ответ, поскольку отвечающий отсутствует с 2009 года.
В приведенном выше коде предполагается, что он
DayOfWeek.Sunday
имеет значение,7
которое не соответствует действительности. Ценность есть на самом деле0
. Это приводит к неправильному расчету, если, например,firstDay
иlastDay
одно и то же воскресенье. В1
этом случае метод возвращается, но это должно быть0
.Самое простое исправление этой ошибки: заменить в коде над строками, где
firstDayOfWeek
иlastDayOfWeek
объявлены следующим образом:int firstDayOfWeek = firstDay.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)firstDay.DayOfWeek; int lastDayOfWeek = lastDay.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)lastDay.DayOfWeek;
Теперь результат:
источник
&& !(bh.DayOfWeek == DayOfWeek.Sunday || bh.DayOfWeek == DayOfWeek.Saturday)
иначе он вычтет один и тот же день дважды, если выходной приходится на выходные.ОК. Думаю, пора опубликовать правильный ответ:
public static double GetBusinessDays(DateTime startD, DateTime endD) { double calcBusinessDays = 1 + ((endD - startD).TotalDays * 5 - (startD.DayOfWeek - endD.DayOfWeek) * 2) / 7; if (endD.DayOfWeek == DayOfWeek.Saturday) calcBusinessDays--; if (startD.DayOfWeek == DayOfWeek.Sunday) calcBusinessDays--; return calcBusinessDays; }
Первоисточник:
http://alecpojidaev.wordpress.com/2009/10/29/work-days-calculation-with-c/
PS Решения, опубликованные выше, почему-то заставили меня задуматься.
источник
Я знаю, что этот вопрос уже решен, но я подумал, что могу дать более простой ответ, который может помочь другим посетителям в будущем.
Вот мой взгляд на это:
public int GetWorkingDays(DateTime from, DateTime to) { var dayDifference = (int)to.Subtract(from).TotalDays; return Enumerable .Range(1, dayDifference) .Select(x => from.AddDays(x)) .Count(x => x.DayOfWeek != DayOfWeek.Saturday && x.DayOfWeek != DayOfWeek.Sunday); }
Это было мое первоначальное сообщение:
public int GetWorkingDays(DateTime from, DateTime to) { var totalDays = 0; for (var date = from; date < to; date = date.AddDays(1)) { if (date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday) totalDays++; } return totalDays; }
источник
to > from
. Может в этом проблема?Определите метод расширения для DateTime следующим образом:
public static class DateTimeExtensions { public static bool IsWorkingDay(this DateTime date) { return date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday; } }
Затем используйте предложение Where для фильтрации более широкого списка дат:
var allDates = GetDates(); // method which returns a list of dates // filter dates by working day's var countOfWorkDays = allDates .Where(day => day.IsWorkingDay()) .Count() ;
источник
Я использовал следующий код, чтобы также учитывать праздничные дни:
public class WorkingDays { public List<DateTime> GetHolidays() { var client = new WebClient(); var json = client.DownloadString("https://www.gov.uk/bank-holidays.json"); var js = new JavaScriptSerializer(); var holidays = js.Deserialize <Dictionary<string, Holidays>>(json); return holidays["england-and-wales"].events.Select(d => d.date).ToList(); } public int GetWorkingDays(DateTime from, DateTime to) { var totalDays = 0; var holidays = GetHolidays(); for (var date = from.AddDays(1); date <= to; date = date.AddDays(1)) { if (date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday && !holidays.Contains(date)) totalDays++; } return totalDays; } } public class Holidays { public string division { get; set; } public List<Event> events { get; set; } } public class Event { public DateTime date { get; set; } public string notes { get; set; } public string title { get; set; } }
И модульные тесты:
[TestClass] public class WorkingDays { [TestMethod] public void SameDayIsZero() { var service = new WorkingDays(); var from = new DateTime(2013, 8, 12); Assert.AreEqual(0, service.GetWorkingDays(from, from)); } [TestMethod] public void CalculateDaysInWorkingWeek() { var service = new WorkingDays(); var from = new DateTime(2013, 8, 12); var to = new DateTime(2013, 8, 16); Assert.AreEqual(4, service.GetWorkingDays(from, to), "Mon - Fri = 4"); Assert.AreEqual(1, service.GetWorkingDays(from, new DateTime(2013, 8, 13)), "Mon - Tues = 1"); } [TestMethod] public void NotIncludeWeekends() { var service = new WorkingDays(); var from = new DateTime(2013, 8, 9); var to = new DateTime(2013, 8, 16); Assert.AreEqual(5, service.GetWorkingDays(from, to), "Fri - Fri = 5"); Assert.AreEqual(2, service.GetWorkingDays(from, new DateTime(2013, 8, 13)), "Fri - Tues = 2"); Assert.AreEqual(1, service.GetWorkingDays(from, new DateTime(2013, 8, 12)), "Fri - Mon = 1"); } [TestMethod] public void AccountForHolidays() { var service = new WorkingDays(); var from = new DateTime(2013, 8, 23); Assert.AreEqual(0, service.GetWorkingDays(from, new DateTime(2013, 8, 26)), "Fri - Mon = 0"); Assert.AreEqual(1, service.GetWorkingDays(from, new DateTime(2013, 8, 27)), "Fri - Tues = 1"); } }
источник
Ну это забито до смерти. :) Однако я все же дам другой ответ, потому что мне нужно было что-то немного другое. Это решение отличается тем, что возвращает Business TimeSpan между началом и концом, и вы можете установить рабочие часы дня и добавить праздники. Таким образом, вы можете использовать его, чтобы рассчитать, произойдет ли это в течение дня, через несколько дней, в выходные и даже праздничные дни. И вы можете получить только рабочие дни или нет, просто получив то, что вам нужно, из возвращенного объекта TimeSpan. И по тому, как он использует списки дней, вы можете видеть, насколько легко было бы добавить список нерабочих дней, если это не типичные суббота и воскресенье. И я тестировал год, и он кажется супер быстрым.
Я просто надеюсь, что вставка кода точная. Но я знаю, что это работает.
public static TimeSpan GetBusinessTimespanBetween( DateTime start, DateTime end, TimeSpan workdayStartTime, TimeSpan workdayEndTime, List<DateTime> holidays = null) { if (end < start) throw new ArgumentException("start datetime must be before end datetime."); // Just create an empty list for easier coding. if (holidays == null) holidays = new List<DateTime>(); if (holidays.Where(x => x.TimeOfDay.Ticks > 0).Any()) throw new ArgumentException("holidays can not have a TimeOfDay, only the Date."); var nonWorkDays = new List<DayOfWeek>() { DayOfWeek.Saturday, DayOfWeek.Sunday }; var startTime = start.TimeOfDay; // If the start time is before the starting hours, set it to the starting hour. if (startTime < workdayStartTime) startTime = workdayStartTime; var timeBeforeEndOfWorkDay = workdayEndTime - startTime; // If it's after the end of the day, then this time lapse doesn't count. if (timeBeforeEndOfWorkDay.TotalSeconds < 0) timeBeforeEndOfWorkDay = new TimeSpan(); // If start is during a non work day, it doesn't count. if (nonWorkDays.Contains(start.DayOfWeek)) timeBeforeEndOfWorkDay = new TimeSpan(); else if (holidays.Contains(start.Date)) timeBeforeEndOfWorkDay = new TimeSpan(); var endTime = end.TimeOfDay; // If the end time is after the ending hours, set it to the ending hour. if (endTime > workdayEndTime) endTime = workdayEndTime; var timeAfterStartOfWorkDay = endTime - workdayStartTime; // If it's before the start of the day, then this time lapse doesn't count. if (timeAfterStartOfWorkDay.TotalSeconds < 0) timeAfterStartOfWorkDay = new TimeSpan(); // If end is during a non work day, it doesn't count. if (nonWorkDays.Contains(end.DayOfWeek)) timeAfterStartOfWorkDay = new TimeSpan(); else if (holidays.Contains(end.Date)) timeAfterStartOfWorkDay = new TimeSpan(); // Easy scenario if the times are during the day day. if (start.Date.CompareTo(end.Date) == 0) { if (nonWorkDays.Contains(start.DayOfWeek)) return new TimeSpan(); else if (holidays.Contains(start.Date)) return new TimeSpan(); return endTime - startTime; } else { var timeBetween = end - start; var daysBetween = (int)Math.Floor(timeBetween.TotalDays); var dailyWorkSeconds = (int)Math.Floor((workdayEndTime - workdayStartTime).TotalSeconds); var businessDaysBetween = 0; // Now the fun begins with calculating the actual Business days. if (daysBetween > 0) { var nextStartDay = start.AddDays(1).Date; var dayBeforeEnd = end.AddDays(-1).Date; for (DateTime d = nextStartDay; d <= dayBeforeEnd; d = d.AddDays(1)) { if (nonWorkDays.Contains(d.DayOfWeek)) continue; else if (holidays.Contains(d.Date)) continue; businessDaysBetween++; } } var dailyWorkSecondsToAdd = dailyWorkSeconds * businessDaysBetween; var output = timeBeforeEndOfWorkDay + timeAfterStartOfWorkDay; output = output + new TimeSpan(0, 0, dailyWorkSecondsToAdd); return output; } }
И вот тестовый код: обратите внимание, что вам просто нужно поместить эту функцию в класс с именем DateHelper, чтобы тестовый код работал.
[TestMethod] public void TestGetBusinessTimespanBetween() { var workdayStart = new TimeSpan(8, 0, 0); var workdayEnd = new TimeSpan(17, 0, 0); var holidays = new List<DateTime>() { new DateTime(2018, 1, 15), // a Monday new DateTime(2018, 2, 15) // a Thursday }; var testdata = new[] { new { expectedMinutes = 0, start = new DateTime(2016, 10, 19, 9, 50, 0), end = new DateTime(2016, 10, 19, 9, 50, 0) }, new { expectedMinutes = 10, start = new DateTime(2016, 10, 19, 9, 50, 0), end = new DateTime(2016, 10, 19, 10, 0, 0) }, new { expectedMinutes = 5, start = new DateTime(2016, 10, 19, 7, 50, 0), end = new DateTime(2016, 10, 19, 8, 5, 0) }, new { expectedMinutes = 5, start = new DateTime(2016, 10, 19, 16, 55, 0), end = new DateTime(2016, 10, 19, 17, 5, 0) }, new { expectedMinutes = 15, start = new DateTime(2016, 10, 19, 16, 50, 0), end = new DateTime(2016, 10, 20, 8, 5, 0) }, new { expectedMinutes = 10, start = new DateTime(2016, 10, 19, 16, 50, 0), end = new DateTime(2016, 10, 20, 7, 55, 0) }, new { expectedMinutes = 5, start = new DateTime(2016, 10, 19, 17, 10, 0), end = new DateTime(2016, 10, 20, 8, 5, 0) }, new { expectedMinutes = 0, start = new DateTime(2016, 10, 19, 17, 10, 0), end = new DateTime(2016, 10, 20, 7, 5, 0) }, new { expectedMinutes = 545, start = new DateTime(2016, 10, 19, 12, 10, 0), end = new DateTime(2016, 10, 20, 12, 15, 0) }, // Spanning multiple weekdays new { expectedMinutes = 835, start = new DateTime(2016, 10, 19, 12, 10, 0), end = new DateTime(2016, 10, 21, 8, 5, 0) }, // Spanning multiple weekdays new { expectedMinutes = 1375, start = new DateTime(2016, 10, 18, 12, 10, 0), end = new DateTime(2016, 10, 21, 8, 5, 0) }, // Spanning from a Thursday to a Tuesday, 5 mins short of complete day. new { expectedMinutes = 1615, start = new DateTime(2016, 10, 20, 12, 10, 0), end = new DateTime(2016, 10, 25, 12, 5, 0) }, // Spanning from a Thursday to a Tuesday, 5 mins beyond complete day. new { expectedMinutes = 1625, start = new DateTime(2016, 10, 20, 12, 10, 0), end = new DateTime(2016, 10, 25, 12, 15, 0) }, // Spanning from a Friday to a Monday, 5 mins beyond complete day. new { expectedMinutes = 545, start = new DateTime(2016, 10, 21, 12, 10, 0), end = new DateTime(2016, 10, 24, 12, 15, 0) }, // Spanning from a Friday to a Monday, 5 mins short complete day. new { expectedMinutes = 535, start = new DateTime(2016, 10, 21, 12, 10, 0), end = new DateTime(2016, 10, 24, 12, 5, 0) }, // Spanning from a Saturday to a Monday, 5 mins short complete day. new { expectedMinutes = 245, start = new DateTime(2016, 10, 22, 12, 10, 0), end = new DateTime(2016, 10, 24, 12, 5, 0) }, // Spanning from a Saturday to a Sunday, 5 mins beyond complete day. new { expectedMinutes = 0, start = new DateTime(2016, 10, 22, 12, 10, 0), end = new DateTime(2016, 10, 23, 12, 15, 0) }, // Times within the same Saturday. new { expectedMinutes = 0, start = new DateTime(2016, 10, 22, 12, 10, 0), end = new DateTime(2016, 10, 23, 12, 15, 0) }, // Spanning from a Saturday to the Sunday next week. new { expectedMinutes = 2700, start = new DateTime(2016, 10, 22, 12, 10, 0), end = new DateTime(2016, 10, 30, 12, 15, 0) }, // Spanning a year. new { expectedMinutes = 143355, start = new DateTime(2016, 10, 22, 12, 10, 0), end = new DateTime(2017, 10, 30, 12, 15, 0) }, // Spanning a year with 2 holidays. new { expectedMinutes = 142815, start = new DateTime(2017, 10, 22, 12, 10, 0), end = new DateTime(2018, 10, 30, 12, 15, 0) }, }; foreach (var item in testdata) { Assert.AreEqual(item.expectedMinutes, DateHelper.GetBusinessTimespanBetween( item.start, item.end, workdayStart, workdayEnd, holidays) .TotalMinutes); } }
источник
Это решение избегает итераций, работает как для + ve, так и для -ve разницы дней недели и включает набор модульных тестов для регрессии против более медленного метода подсчета дней недели. Я также включил краткий метод добавления будних дней, который также работает таким же неитеративным способом.
Модульные тесты охватывают несколько тысяч комбинаций дат, чтобы исчерпывающе протестировать все комбинации дней начала и конца дня недели как для малых, так и для больших диапазонов дат.
Важный : мы предполагаем, что считаем дни, исключая дату начала и включая дату окончания. Это важно при подсчете дней недели, поскольку определенные дни начала / окончания, которые вы включаете / исключаете, влияют на результат. Это также гарантирует, что разница между двумя равными днями всегда равна нулю и что мы включаем только полные рабочие дни, поскольку обычно вы хотите, чтобы ответ был правильным для любого времени в текущую дату начала (часто сегодня) и включал полную дату окончания (например, срок).
ПРИМЕЧАНИЕ. Этот код требует дополнительной настройки для праздников, но в соответствии с приведенным выше предположением, этот код должен исключать праздники в дату начала.
Добавить дни недели:
private static readonly int[,] _addOffset = { // 0 1 2 3 4 {0, 1, 2, 3, 4}, // Su 0 {0, 1, 2, 3, 4}, // M 1 {0, 1, 2, 3, 6}, // Tu 2 {0, 1, 4, 5, 6}, // W 3 {0, 1, 4, 5, 6}, // Th 4 {0, 3, 4, 5, 6}, // F 5 {0, 2, 3, 4, 5}, // Sa 6 }; public static DateTime AddWeekdays(this DateTime date, int weekdays) { int extraDays = weekdays % 5; int addDays = weekdays >= 0 ? (weekdays / 5) * 7 + _addOffset[(int)date.DayOfWeek, extraDays] : (weekdays / 5) * 7 - _addOffset[6 - (int)date.DayOfWeek, -extraDays]; return date.AddDays(addDays); }
Вычислить разницу в будние дни:
static readonly int[,] _diffOffset = { // Su M Tu W Th F Sa {0, 1, 2, 3, 4, 5, 5}, // Su {4, 0, 1, 2, 3, 4, 4}, // M {3, 4, 0, 1, 2, 3, 3}, // Tu {2, 3, 4, 0, 1, 2, 2}, // W {1, 2, 3, 4, 0, 1, 1}, // Th {0, 1, 2, 3, 4, 0, 0}, // F {0, 1, 2, 3, 4, 5, 0}, // Sa }; public static int GetWeekdaysDiff(this DateTime dtStart, DateTime dtEnd) { int daysDiff = (int)(dtEnd - dtStart).TotalDays; return daysDiff >= 0 ? 5 * (daysDiff / 7) + _diffOffset[(int) dtStart.DayOfWeek, (int) dtEnd.DayOfWeek] : 5 * (daysDiff / 7) - _diffOffset[6 - (int) dtStart.DayOfWeek, 6 - (int) dtEnd.DayOfWeek]; }
Я обнаружил, что большинство других решений по переполнению стека были либо медленными (итеративными), либо чрезмерно сложными, а многие были просто неверными. Мораль этой истории ... Не верь ей, если вы ее тщательно не проверили !!
Юнит - тесты , основанные на NUnit тестирования комбинаторной и ShouldBe расширения NUnit.
[TestFixture] public class DateTimeExtensionsTests { /// <summary> /// Exclude start date, Include end date /// </summary> /// <param name="dtStart"></param> /// <param name="dtEnd"></param> /// <returns></returns> private IEnumerable<DateTime> GetDateRange(DateTime dtStart, DateTime dtEnd) { Console.WriteLine(@"dtStart={0:yy-MMM-dd ddd}, dtEnd={1:yy-MMM-dd ddd}", dtStart, dtEnd); TimeSpan diff = dtEnd - dtStart; Console.WriteLine(diff); if (dtStart <= dtEnd) { for (DateTime dt = dtStart.AddDays(1); dt <= dtEnd; dt = dt.AddDays(1)) { Console.WriteLine(@"dt={0:yy-MMM-dd ddd}", dt); yield return dt; } } else { for (DateTime dt = dtStart.AddDays(-1); dt >= dtEnd; dt = dt.AddDays(-1)) { Console.WriteLine(@"dt={0:yy-MMM-dd ddd}", dt); yield return dt; } } } [Test, Combinatorial] public void TestGetWeekdaysDiff( [Values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 20, 30)] int startDay, [Values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 20, 30)] int endDay, [Values(7)] int startMonth, [Values(7)] int endMonth) { // Arrange DateTime dtStart = new DateTime(2016, startMonth, startDay); DateTime dtEnd = new DateTime(2016, endMonth, endDay); int nDays = GetDateRange(dtStart, dtEnd) .Count(dt => dt.DayOfWeek != DayOfWeek.Saturday && dt.DayOfWeek != DayOfWeek.Sunday); if (dtEnd < dtStart) nDays = -nDays; Console.WriteLine(@"countBusDays={0}", nDays); // Act / Assert dtStart.GetWeekdaysDiff(dtEnd).ShouldBe(nDays); } [Test, Combinatorial] public void TestAddWeekdays( [Values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 20, 30)] int startDay, [Values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 20, 30)] int weekdays) { DateTime dtStart = new DateTime(2016, 7, startDay); DateTime dtEnd1 = dtStart.AddWeekdays(weekdays); // ADD dtStart.GetWeekdaysDiff(dtEnd1).ShouldBe(weekdays); DateTime dtEnd2 = dtStart.AddWeekdays(-weekdays); // SUBTRACT dtStart.GetWeekdaysDiff(dtEnd2).ShouldBe(-weekdays); } }
источник
Вот код для этой цели, со шведскими праздниками, но вы можете настроить, какие праздники считать. Обратите внимание, что я добавил ограничение, которое вы, возможно, захотите удалить, но это было для веб-системы, и я не хотел, чтобы кто-то вводил какую-то огромную дату, чтобы затормозить процесс.
public static int GetWorkdays(DateTime from ,DateTime to) { int limit = 9999; int counter = 0; DateTime current = from; int result = 0; if (from > to) { DateTime temp = from; from = to; to = temp; } if (from >= to) { return 0; } while (current <= to && counter < limit) { if (IsSwedishWorkday(current)) { result++; } current = current.AddDays(1); counter++; } return result; } public static bool IsSwedishWorkday(DateTime date) { return (!IsSwedishHoliday(date) && date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday); } public static bool IsSwedishHoliday(DateTime date) { return ( IsSameDay(GetEpiphanyDay(date.Year), date) || IsSameDay(GetMayDay(date.Year), date) || IsSameDay(GetSwedishNationalDay(date.Year), date) || IsSameDay(GetChristmasDay(date.Year), date) || IsSameDay(GetBoxingDay(date.Year), date) || IsSameDay(GetGoodFriday(date.Year), date) || IsSameDay(GetAscensionDay(date.Year), date) || IsSameDay(GetAllSaintsDay(date.Year), date) || IsSameDay(GetMidsummersDay(date.Year), date) || IsSameDay(GetPentecostDay(date.Year), date) || IsSameDay(GetEasterMonday(date.Year), date) || IsSameDay(GetNewYearsDay(date.Year), date) || IsSameDay(GetEasterDay(date.Year), date) ); } // Trettondagen public static DateTime GetEpiphanyDay(int year) { return new DateTime(year, 1, 6); } // Första maj public static DateTime GetMayDay(int year) { return new DateTime(year,5,1); } // Juldagen public static DateTime GetSwedishNationalDay(int year) { return new DateTime(year, 6, 6); } // Juldagen public static DateTime GetNewYearsDay(int year) { return new DateTime(year,1,1); } // Juldagen public static DateTime GetChristmasDay(int year) { return new DateTime(year,12,25); } // Annandag jul public static DateTime GetBoxingDay(int year) { return new DateTime(year, 12, 26); } // Långfredagen public static DateTime GetGoodFriday(int year) { return GetEasterDay(year).AddDays(-3); } // Kristi himmelsfärdsdag public static DateTime GetAscensionDay(int year) { return GetEasterDay(year).AddDays(5*7+4); } // Midsommar public static DateTime GetAllSaintsDay(int year) { DateTime result = new DateTime(year,10,31); while (result.DayOfWeek != DayOfWeek.Saturday) { result = result.AddDays(1); } return result; } // Midsommar public static DateTime GetMidsummersDay(int year) { DateTime result = new DateTime(year, 6, 20); while (result.DayOfWeek != DayOfWeek.Saturday) { result = result.AddDays(1); } return result; } // Pingstdagen public static DateTime GetPentecostDay(int year) { return GetEasterDay(year).AddDays(7 * 7); } // Annandag påsk public static DateTime GetEasterMonday(int year) { return GetEasterDay(year).AddDays(1); } public static DateTime GetEasterDay(int y) { double c; double n; double k; double i; double j; double l; double m; double d; c = System.Math.Floor(y / 100.0); n = y - 19 * System.Math.Floor(y / 19.0); k = System.Math.Floor((c - 17) / 25.0); i = c - System.Math.Floor(c / 4) - System.Math.Floor((c - k) / 3) + 19 * n + 15; i = i - 30 * System.Math.Floor(i / 30); i = i - System.Math.Floor(i / 28) * (1 - System.Math.Floor(i / 28) * System.Math.Floor(29 / (i + 1)) * System.Math.Floor((21 - n) / 11)); j = y + System.Math.Floor(y / 4.0) + i + 2 - c + System.Math.Floor(c / 4); j = j - 7 * System.Math.Floor(j / 7); l = i - j; m = 3 + System.Math.Floor((l + 40) / 44);// month d = l + 28 - 31 * System.Math.Floor(m / 4);// day double days = ((m == 3) ? d : d + 31); DateTime result = new DateTime(y, 3, 1).AddDays(days-1); return result; }
источник
Вот небольшой пример кода. Это метод класса, поэтому он будет работать только внутри вашего класса. Если хотите
static
, поменяйте подпись наprivate static
(илиpublic static
).private IEnumerable<DateTime> GetWorkingDays(DateTime sd, DateTime ed) { for (var d = sd; d <= ed; d = d.AddDays(1)) if (d.DayOfWeek != DayOfWeek.Saturday && d.DayOfWeek != DayOfWeek.Sunday) yield return d; }
Этот метод создает переменную цикла
d
, инициализирует ее начальным днемsd
, а затем увеличивает на один день каждую итерацию (d = d.AddDays(1)
).Он возвращает желаемые значения с помощью
yield
, который создает файлiterator
. Самое замечательное в итераторах заключается в том, что они не хранят все значенияIEnumerable
в памяти, а только вызывают каждое из них последовательно. Это означает, что вы можете вызывать этот метод с незапамятных времен, не беспокоясь о нехватке памяти.источник
Я много искал легко усваиваемый алгоритм для расчета рабочих дней между двумя датами, а также для исключения национальных праздников, и, наконец, я решил пойти с этим подходом:
public static int NumberOfWorkingDaysBetween2Dates(DateTime start,DateTime due,IEnumerable<DateTime> holidays) { var dic = new Dictionary<DateTime, DayOfWeek>(); var totalDays = (due - start).Days; for (int i = 0; i < totalDays + 1; i++) { if (!holidays.Any(x => x == start.AddDays(i))) dic.Add(start.AddDays(i), start.AddDays(i).DayOfWeek); } return dic.Where(x => x.Value != DayOfWeek.Saturday && x.Value != DayOfWeek.Sunday).Count(); }
По сути, я хотел идти на каждое свидание и оценивать свои условия:
но также я хотел избежать повторения дат.
Запустив и измерив время, необходимое для оценки 1 полного года, я пришел к следующему результату:
static void Main(string[] args) { var start = new DateTime(2017, 1, 1); var due = new DateTime(2017, 12, 31); var sw = Stopwatch.StartNew(); var days = NumberOfWorkingDaysBetween2Dates(start, due,NationalHolidays()); sw.Stop(); Console.WriteLine($"Total working days = {days} --- time: {sw.Elapsed}"); Console.ReadLine(); // result is: // Total working days = 249-- - time: 00:00:00.0269087 }
Изменить: новый метод более простой:
public static int ToBusinessWorkingDays(this DateTime start, DateTime due, DateTime[] holidays) { return Enumerable.Range(0, (due - start).Days) .Select(a => start.AddDays(a)) .Where(a => a.DayOfWeek != DayOfWeek.Sunday) .Where(a => a.DayOfWeek != DayOfWeek.Saturday) .Count(a => !holidays.Any(x => x == a)); }
источник
Я думаю, что ни один из приведенных выше ответов не является правильным. Ни один из них не решает все особые случаи, например, когда даты начинаются и заканчиваются в середине выходных, когда дата начинается в пятницу и заканчивается в следующий понедельник и т. Д. Кроме того, все они округляют вычисления до целого. дней, поэтому, если дата начала находится в середине субботы, например, из рабочих дней будет вычтена целый день, что приведет к неверным результатам ...
В любом случае, вот мое решение, которое довольно эффективно и просто и работает во всех случаях. Хитрость заключается в том, чтобы просто найти предыдущий понедельник для дат начала и окончания, а затем сделать небольшую компенсацию, когда начало и конец происходят в выходные:
public double WorkDays(DateTime startDate, DateTime endDate){ double weekendDays; double days = endDate.Subtract(startDate).TotalDays; if(days<0) return 0; DateTime startMonday = startDate.AddDays(DayOfWeek.Monday - startDate.DayOfWeek).Date; DateTime endMonday = endDate.AddDays(DayOfWeek.Monday - endDate.DayOfWeek).Date; weekendDays = ((endMonday.Subtract(startMonday).TotalDays) / 7) * 2; // compute fractionary part of weekend days double diffStart = startDate.Subtract(startMonday).TotalDays - 5; double diffEnd = endDate.Subtract(endMonday).TotalDays - 5; // compensate weekenddays if(diffStart>0) weekendDays -= diffStart; if(diffEnd>0) weekendDays += diffEnd; return days - weekendDays; }
источник
using System; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { DateTime start = new DateTime(2014, 1, 1); DateTime stop = new DateTime(2014, 12, 31); int totalWorkingDays = GetNumberOfWorkingDays(start, stop); Console.WriteLine("There are {0} working days.", totalWorkingDays); } private static int GetNumberOfWorkingDays(DateTime start, DateTime stop) { TimeSpan interval = stop - start; int totalWeek = interval.Days / 7; int totalWorkingDays = 5 * totalWeek; int remainingDays = interval.Days % 7; for (int i = 0; i <= remainingDays; i++) { DayOfWeek test = (DayOfWeek)(((int)start.DayOfWeek + i) % 7); if (test >= DayOfWeek.Monday && test <= DayOfWeek.Friday) totalWorkingDays++; } return totalWorkingDays; } } }
источник
Работает и без шлейфов
Этот метод не использует никаких циклов и на самом деле довольно прост. Он расширяет диапазон дат до полных недель, поскольку мы знаем, что в каждой неделе 5 рабочих дней. Затем он использует таблицу поиска, чтобы найти количество рабочих дней, которое нужно вычесть из начала и конца, чтобы получить правильный результат. Я расширил расчет, чтобы показать, что происходит, но при необходимости все это можно свести в одну строку.
В любом случае, это работает для меня, и поэтому я решил опубликовать его здесь, если это может помочь другим. Удачного кодирования.
Расчет
Культура
Код предполагает рабочую неделю с понедельника по пятницу. Для других культур, например с воскресенья на четверг, вам нужно будет сместить даты перед вычислением.
Метод
public int Weekdays(DateTime min, DateTime max) { if (min.Date > max.Date) throw new Exception("Invalid date span"); var t = (max.AddDays(1).Date - min.Date).TotalDays; var a = (int) min.DayOfWeek; var b = 6 - (int) max.DayOfWeek; var k = 1.4; var m = new int[]{0, 0, 1, 2, 3, 4, 5}; var c = m[a] + m[b]; return (int)((t + a + b) / k) - c; }
источник
Я просто поделюсь своим решением. У меня это сработало, может я просто не замечаю / не знаю, что это ошибка. Я начал с получения первой неполной недели, если она есть. полная неделя начиналась с воскресенья по субботу, поэтому, если (int) _now.DayOfWeek не было 0 (воскресенье), первая неделя была неполной.
Я просто вычитаю 1 из подсчета первых недель субботы первой недели, а затем добавляю его к новому счету;
Затем я получаю данные о последней неполной неделе, затем вычитаю 1 для воскресенья и добавляю к новому счету.
Затем, наконец, к новому счету было добавлено количество полных недель, умноженное на 5 (будние дни).
public int RemoveNonWorkingDays(int numberOfDays){ int workingDays = 0; int firstWeek = 7 - (int)_now.DayOfWeek; if(firstWeek < 7){ if(firstWeek > numberOfDays) return numberOfDays; workingDays += firstWeek-1; numberOfDays -= firstWeek; } int lastWeek = numberOfDays % 7; if(lastWeek > 0){ numberOfDays -= lastWeek; workingDays += lastWeek - 1; } workingDays += (numberOfDays/7)*5; return workingDays; }
источник
У меня возникли проблемы с поиском надежной версии этого кода TSQL. Ниже приведено преобразование кода C # с добавлением таблицы Holiday, которая должна использоваться для предварительного расчета праздников.
CREATE TABLE dbo.Holiday ( HolidayDt DATE NOT NULL, Name NVARCHAR(50) NOT NULL, IsWeekday BIT NOT NULL, CONSTRAINT PK_Holiday PRIMARY KEY (HolidayDt) ) GO CREATE INDEX IDX_Holiday ON Holiday (HolidayDt, IsWeekday) GO CREATE function dbo.GetBusinessDays ( @FirstDay datetime, @LastDay datetime ) RETURNS INT AS BEGIN DECLARE @BusinessDays INT, @FullWeekCount INT SELECT @FirstDay = CONVERT(DATETIME,CONVERT(DATE,@FirstDay)) , @LastDay = CONVERT(DATETIME,CONVERT(DATE,@LastDay)) IF @FirstDay > @LastDay RETURN NULL; SELECT @BusinessDays = DATEDIFF(DAY, @FirstDay, @LastDay) + 1 SELECT @FullWeekCount = @BusinessDays / 7; -- find out if there are weekends during the time exceedng the full weeks IF @BusinessDays > (@FullWeekCount * 7) BEGIN -- we are here to find out if there is a 1-day or 2-days weekend -- in the time interval remaining after subtracting the complete weeks DECLARE @firstDayOfWeek INT, @lastDayOfWeek INT; SELECT @firstDayOfWeek = DATEPART(DW, @FirstDay), @lastDayOfWeek = DATEPART(DW, @LastDay); IF @lastDayOfWeek < @firstDayOfWeek SELECT @lastDayOfWeek = @lastDayOfWeek + 7; IF @firstDayOfWeek <= 6 BEGIN IF (@lastDayOfWeek >= 7) --Both Saturday and Sunday are in the remaining time interval BEGIN SELECT @BusinessDays = @BusinessDays - 2 END ELSE IF @lastDayOfWeek>=6 --Only Saturday is in the remaining time interval BEGIN SELECT @BusinessDays = @BusinessDays - 1 END END ELSE IF @firstDayOfWeek <= 7 AND @lastDayOfWeek >=7 -- Only Sunday is in the remaining time interval BEGIN SELECT @BusinessDays = @BusinessDays - 1 END END -- subtract the weekends during the full weeks in the interval DECLARE @Holidays INT; SELECT @Holidays = COUNT(*) FROM Holiday WHERE HolidayDt BETWEEN @FirstDay AND @LastDay AND IsWeekday = CAST(1 AS BIT) SELECT @BusinessDays = @BusinessDays - (@FullWeekCount + @FullWeekCount) -- - @Holidays RETURN @BusinessDays END
источник
int BusinessDayDifference(DateTime Date1, DateTime Date2) { int Sign = 1; if (Date2 > Date1) { Sign = -1; DateTime TempDate = Date1; Date1 = Date2; Date2 = TempDate; } int BusDayDiff = (int)(Date1.Date - Date2.Date).TotalDays; if (Date1.DayOfWeek == DayOfWeek.Saturday) BusDayDiff -= 1; if (Date2.DayOfWeek == DayOfWeek.Sunday) BusDayDiff -= 1; int Week1 = GetWeekNum(Date1); int Week2 = GetWeekNum(Date2); int WeekDiff = Week1 - Week2; BusDayDiff -= WeekDiff * 2; foreach (DateTime Holiday in Holidays) if (Date1 >= Holiday && Date2 <= Holiday) BusDayDiff--; BusDayDiff *= Sign; return BusDayDiff; } private int GetWeekNum(DateTime Date) { return (int)(Date.AddDays(-(int)Date.DayOfWeek).Ticks / TimeSpan.TicksPerDay / 7); }
источник
Вот одно очень простое решение этой проблемы. У нас есть начальная дата, конечная дата и «цикл for» для увеличения дня и вычислений, чтобы увидеть, рабочий это или выходной, преобразовав в строку DayOfWeek.
class Program { static void Main(string[] args) { DateTime day = new DateTime(); Console.Write("Inser your end date (example: 01/30/2015): "); DateTime endDate = DateTime.Parse(Console.ReadLine()); int numberOfDays = 0; for (day = DateTime.Now.Date; day.Date < endDate.Date; day = day.Date.AddDays(1)) { string dayToString = Convert.ToString(day.DayOfWeek); if (dayToString != "Saturday" && dayToString != "Sunday") numberOfDays++; } Console.WriteLine("Number of working days (not including local holidays) between two dates is "+numberOfDays); } }
источник
На основе комментария, помеченного как ответ и рекомендованного исправления, а также -> Эта версия хочет преобразовать дни в рабочие часы ... Считает также часы одного дня.
/// <summary> /// Calculates number of business days, taking into account: /// - weekends (Saturdays and Sundays) /// - bank holidays in the middle of the week /// </summary> /// <param name="firstDay">First day in the time interval</param> /// <param name="lastDay">Last day in the time interval</param> /// <param name="bankHolidays">List of bank holidays excluding weekends</param> /// <returns>Number of business hours during the 'span'</returns> public static int BusinessHoursUntil(DateTime firstDay, DateTime lastDay, params DateTime[] bankHolidays) { var original_firstDay = firstDay; var original_lastDay = lastDay; firstDay = firstDay.Date; lastDay = lastDay.Date; if (firstDay > lastDay) return -1; //// throw new ArgumentException("Incorrect last day " + lastDay); TimeSpan span = lastDay - firstDay; int businessDays = span.Days + 1; int fullWeekCount = businessDays / 7; // find out if there are weekends during the time exceedng the full weeks if (businessDays > fullWeekCount * 7) { // we are here to find out if there is a 1-day or 2-days weekend // in the time interval remaining after subtracting the complete weeks int firstDayOfWeek = firstDay.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)firstDay.DayOfWeek; int lastDayOfWeek = lastDay.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)lastDay.DayOfWeek; if (lastDayOfWeek < firstDayOfWeek) lastDayOfWeek += 7; if (firstDayOfWeek <= 6) { if (lastDayOfWeek >= 7)// Both Saturday and Sunday are in the remaining time interval businessDays -= 2; else if (lastDayOfWeek >= 6)// Only Saturday is in the remaining time interval businessDays -= 1; } else if (firstDayOfWeek <= 7 && lastDayOfWeek >= 7)// Only Sunday is in the remaining time interval businessDays -= 1; } // subtract the weekends during the full weeks in the interval businessDays -= fullWeekCount + fullWeekCount; if (bankHolidays != null && bankHolidays.Any()) { // subtract the number of bank holidays during the time interval foreach (DateTime bankHoliday in bankHolidays) { DateTime bh = bankHoliday.Date; if (firstDay <= bh && bh <= lastDay) --businessDays; } } int total_business_hours = 0; if (firstDay.Date == lastDay.Date) {//If on the same day, go granular with Hours from the Orginial_*Day values total_business_hours = (int)(original_lastDay - original_firstDay).TotalHours; } else {//Convert Business-Days to TotalHours total_business_hours = (int)(firstDay.AddDays(businessDays).AddHours(firstDay.Hour) - firstDay).TotalHours; } return total_business_hours; }
источник
Я только что улучшил ответ @Alexander и @Slauma, чтобы поддержать рабочую неделю в качестве параметра, для случаев, когда суббота является рабочим днем, или даже для случаев, когда есть только пара дней недели, которые считаются рабочими днями:
/// <summary> /// Calculate the number of business days between two dates, considering: /// - Days of the week that are not considered business days. /// - Holidays between these two dates. /// </summary> /// <param name="fDay">First day of the desired 'span'.</param> /// <param name="lDay">Last day of the desired 'span'.</param> /// <param name="BusinessDaysOfWeek">Days of the week that are considered to be business days, if NULL considers monday, tuesday, wednesday, thursday and friday as business days of the week.</param> /// <param name="Holidays">Holidays, if NULL, considers no holiday.</param> /// <returns>Number of business days during the 'span'</returns> public static int BusinessDaysUntil(this DateTime fDay, DateTime lDay, DayOfWeek[] BusinessDaysOfWeek = null, DateTime[] Holidays = null) { if (BusinessDaysOfWeek == null) BusinessDaysOfWeek = new DayOfWeek[] { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday }; if (Holidays == null) Holidays = new DateTime[] { }; fDay = fDay.Date; lDay = lDay.Date; if (fDay > lDay) throw new ArgumentException("Incorrect last day " + lDay); int bDays = (lDay - fDay).Days + 1; int fullWeekCount = bDays / 7; int fullWeekCountMult = 7 - WeekDays.Length; // Find out if there are weekends during the time exceedng the full weeks if (bDays > (fullWeekCount * 7)) { int fDayOfWeek = (int)fDay.DayOfWeek; int lDayOfWeek = (int)lDay.DayOfWeek; if (fDayOfWeek > lDayOfWeek) lDayOfWeek += 7; // If they are the same, we already covered it right before the Holiday subtraction if (lDayOfWeek != fDayOfWeek) { // Here we need to see if any of the days between are considered business days for (int i = fDayOfWeek; i <= lDayOfWeek; i++) if (!WeekDays.Contains((DayOfWeek)(i > 6 ? i - 7 : i))) bDays -= 1; } } // Subtract the days that are not in WeekDays[] during the full weeks in the interval bDays -= (fullWeekCount * fullWeekCountMult); // Subtract the number of bank holidays during the time interval bDays = bDays - Holidays.Select(x => x.Date).Count(x => fDay <= x && x <= lDay); return bDays; }
источник
Вот функция, которую мы можем использовать для расчета рабочих дней между двумя датами. Я не использую праздничный список, так как он может варьироваться в зависимости от страны / региона.
Если мы все равно хотим использовать его, мы можем взять третий аргумент как список праздников, и перед увеличением счетчика мы должны проверить, что список не содержит d
public static int GetBussinessDaysBetweenTwoDates(DateTime StartDate, DateTime EndDate) { if (StartDate > EndDate) return -1; int bd = 0; for (DateTime d = StartDate; d < EndDate; d = d.AddDays(1)) { if (d.DayOfWeek != DayOfWeek.Saturday && d.DayOfWeek != DayOfWeek.Sunday) bd++; } return bd; }
источник
Я считаю, что это может быть более простой способ:
public int BusinessDaysUntil(DateTime start, DateTime end, params DateTime[] bankHolidays) { int tld = (int)((end - start).TotalDays) + 1; //including end day int not_buss_day = 2 * (tld / 7); //Saturday and Sunday int rest = tld % 7; //rest. if (rest > 0) { int tmp = (int)start.DayOfWeek - 1 + rest; if (tmp == 6 || start.DayOfWeek == DayOfWeek.Sunday) not_buss_day++; else if (tmp > 6) not_buss_day += 2; } foreach (DateTime bankHoliday in bankHolidays) { DateTime bh = bankHoliday.Date; if (!(bh.DayOfWeek == DayOfWeek.Saturday || bh.DayOfWeek == DayOfWeek.Sunday) && (start <= bh && bh <= end)) { not_buss_day++; } } return tld - not_buss_day; }
источник
Еще одна идея - этот метод позволяет указать любую рабочую неделю и праздники.
Идея здесь в том, что мы находим ядро диапазона дат от первого первого рабочего дня недели до последнего выходного дня недели. Это позволяет нам легко рассчитывать целые недели ( без перебора всех дат). Все, что нам нужно сделать, это добавить рабочие дни, приходящиеся на начало и конец этого основного диапазона.
public static int CalculateWorkingDays( DateTime startDate, DateTime endDate, IList<DateTime> holidays, DayOfWeek firstDayOfWeek, DayOfWeek lastDayOfWeek) { // Make sure the defined working days run contiguously if (lastDayOfWeek < firstDayOfWeek) { throw new Exception("Last day of week cannot fall before first day of week!"); } // Create a list of the days of the week that make-up the weekend by working back // from the firstDayOfWeek and forward from lastDayOfWeek to get the start and end // the weekend var weekendStart = lastDayOfWeek == DayOfWeek.Saturday ? DayOfWeek.Sunday : lastDayOfWeek + 1; var weekendEnd = firstDayOfWeek == DayOfWeek.Sunday ? DayOfWeek.Saturday : firstDayOfWeek - 1; var weekendDays = new List<DayOfWeek>(); var w = weekendStart; do { weekendDays.Add(w); if (w == weekendEnd) break; w = (w == DayOfWeek.Saturday) ? DayOfWeek.Sunday : w + 1; } while (true); // Force simple dates - no time startDate = startDate.Date; endDate = endDate.Date; // Ensure a progessive date range if (endDate < startDate) { var t = startDate; startDate = endDate; endDate = t; } // setup some working variables and constants const int daysInWeek = 7; // yeah - really! var actualStartDate = startDate; // this will end up on startOfWeek boundary var actualEndDate = endDate; // this will end up on weekendEnd boundary int workingDaysInWeek = daysInWeek - weekendDays.Count; int workingDays = 0; // the result we are trying to find int leadingDays = 0; // the number of working days leading up to the firstDayOfWeek boundary int trailingDays = 0; // the number of working days counting back to the weekendEnd boundary // Calculate leading working days // if we aren't on the firstDayOfWeek we need to step forward to the nearest if (startDate.DayOfWeek != firstDayOfWeek) { var d = startDate; do { if (d.DayOfWeek == firstDayOfWeek || d >= endDate) { actualStartDate = d; break; } if (!weekendDays.Contains(d.DayOfWeek)) { leadingDays++; } d = d.AddDays(1); } while(true); } // Calculate trailing working days // if we aren't on the weekendEnd we step back to the nearest if (endDate >= actualStartDate && endDate.DayOfWeek != weekendEnd) { var d = endDate; do { if (d.DayOfWeek == weekendEnd || d < actualStartDate) { actualEndDate = d; break; } if (!weekendDays.Contains(d.DayOfWeek)) { trailingDays++; } d = d.AddDays(-1); } while(true); } // Calculate the inclusive number of days between the actualStartDate and the actualEndDate var coreDays = (actualEndDate - actualStartDate).Days + 1; var noWeeks = coreDays / daysInWeek; // add together leading, core and trailing days workingDays += noWeeks * workingDaysInWeek; workingDays += leadingDays; workingDays += trailingDays; // Finally remove any holidays that fall within the range. if (holidays != null) { workingDays -= holidays.Count(h => h >= startDate && (h <= endDate)); } return workingDays; }
источник
Поскольку я не могу комментировать. Есть еще одна проблема с принятым решением, когда государственные праздники вычитаются, даже если они приходятся на выходные. Видя, как проверяется другой ввод, вполне логично, что и это тоже.
Следовательно, foreach должен быть:
// subtract the number of bank holidays during the time interval foreach (DateTime bankHoliday in bankHolidays) { DateTime bh = bankHoliday.Date; // Do not subtract bank holidays when they fall in the weekend to avoid double subtraction if (bh.DayOfWeek == DayOfWeek.Saturday || bh.DayOfWeek == DayOfWeek.Sunday) continue; if (firstDay <= bh && bh <= lastDay) --businessDays; }
источник
Вот подход, если вы используете MVC. Я также рассчитал национальные праздники или любые праздничные дни, которые нужно исключить, взяв их из календаря праздников, который вам нужно будет сделать.
foreach (DateTime day in EachDay(model)) { bool key = false; foreach (LeaveModel ln in holidaycalendar) { if (day.Date == ln.Date && day.DayOfWeek != DayOfWeek.Saturday && day.DayOfWeek != DayOfWeek.Sunday) { key = true; break; } } if (day.DayOfWeek == DayOfWeek.Saturday || day.DayOfWeek == DayOfWeek.Sunday) { key = true; } if (key != true) { leavecount++; } }
Leavemodel - это список здесь
источник
Вот вспомогательная функция, которую я написал для этой задачи.
он также возвращает количество выходных через
out
параметр.при желании вы можете настроить "выходные" дни во время выполнения для стран, в которых используются другие выходные дни, или включить праздничные дни с помощью
weekendDays[]
необязательного параметра:public static int GetNetworkDays(DateTime startDate, DateTime endDate,out int totalWeekenDays, DayOfWeek[] weekendDays = null) { if (startDate >= endDate) { throw new Exception("start date can not be greater then or equel to end date"); } DayOfWeek[] weekends = new DayOfWeek[] { DayOfWeek.Sunday, DayOfWeek.Saturday }; if (weekendDays != null) { weekends = weekendDays; } var totaldays = (endDate - startDate).TotalDays + 1; // add one to include the first day to var counter = 0; var workdaysCounter = 0; var weekendsCounter = 0; for (int i = 0; i < totaldays; i++) { if (weekends.Contains(startDate.AddDays(counter).DayOfWeek)) { weekendsCounter++; } else { workdaysCounter++; } counter++; } totalWeekenDays = weekendsCounter; return workdaysCounter; }
источник
Я придумал следующее решение
var dateStart = new DateTime(2019,01,10); var dateEnd = new DateTime(2019,01,31); var timeBetween = (dateEnd - dateStart).TotalDays + 1; int numberOf7DayWeeks = (int)(timeBetween / 7); int numberOfWeekendDays = numberOf7DayWeeks * 2; int workingDays =(int)( timeBetween - numberOfWeekendDays); if(dateStart.DayOfWeek == DayOfWeek.Saturday || dateEnd.DayOfWeek == DayOfWeek.Sunday){ workingDays -=2; } if(dateStart.DayOfWeek == DayOfWeek.Sunday || dateEnd.DayOfWeek == DayOfWeek.Saturday){ workingDays -=1; }
источник
Вам просто нужно перебирать каждый день во временном диапазоне и вычитать день из счетчика, если это суббота или воскресенье.
private float SubtractWeekend(DateTime start, DateTime end) { float totaldays = (end.Date - start.Date).Days; var iterationVal = totalDays; for (int i = 0; i <= iterationVal; i++) { int dayVal = (int)start.Date.AddDays(i).DayOfWeek; if(dayVal == 6 || dayVal == 0) { // saturday or sunday totalDays--; } } return totalDays; }
источник
public static int CalculateBusinessDaysInRange(this DateTime startDate, DateTime endDate, params DateTime[] holidayDates) { endDate = endDate.Date; if(startDate > endDate) throw new ArgumentException("The end date can not be before the start date!", nameof(endDate)); int accumulator = 0; DateTime itterator = startDate.Date; do { if(itterator.DayOfWeek != DayOfWeek.Saturday && itterator.DayOfWeek != DayOfWeek.Sunday && !holidayDates.Any(hol => hol.Date == itterator)) { accumulator++; } } while((itterator = itterator.AddDays(1)).Date <= endDate); return accumulator }
Я публикую это только потому, что, несмотря на все полученные отличные ответы, ни одна математика не имела для меня смысла. Это определенно метод KISS, который должен работать и быть достаточно поддерживаемым. Конечно, если вы рассчитываете диапазоны, превышающие 2–3 месяца, это не будет самым эффективным способом. Мы просто определяем, суббота это или воскресенье, или это дата праздника. Если нет, добавляем рабочий день. Если да, то все в порядке.
Я уверен, что это можно было бы еще больше упростить с помощью LINQ, но этот способ намного проще для понимания.
источник
Еще один подход для расчета рабочих дней, не считая праздников, но с учетом времени суток, возвращающего дробное количество дней:
public static double GetBusinessDays(DateTime startD, DateTime endD) { while (IsWeekend(startD)) startD = startD.Date.AddDays(1); while (IsWeekend(endD)) endD = endD.Date.AddDays(-1); var bussDays = (endD - startD).TotalDays - (2 * ((int)(endD - startD).TotalDays / 7)) - (startD.DayOfWeek > endD.DayOfWeek ? 2 : 0); return bussDays; } public static bool IsWeekend(DateTime d) { return d.DayOfWeek == DayOfWeek.Saturday || d.DayOfWeek == DayOfWeek.Sunday; }
Вы можете поиграть с этим здесь: https://rextester.com/ASHRS53997
источник
Это универсальное решение.
startdayvalue - это номер дня начальной даты.
Weekendday_1 - номер выходного дня.
номер дня - ПН - 1, ВТ - 2, ... СБ - 6, ВС -7.
разница разница между двумя датами ..
Пример: Дата начала: 4 апреля 2013 г., Дата окончания: 14 апреля 2013 г.
Разница: 10, начальное значение: 4, выходной день_1: 7 (если ВОСКРЕСЕНЬЕ - выходной для вас.)
Это даст вам количество выходных.
Количество рабочих дней = (Разница + 1) - праздник1
if (startdayvalue > weekendday_1) { if (difference > ((7 - startdayvalue) + weekendday_1)) { holiday1 = (difference - ((7 - startdayvalue) + weekendday_1)) / 7; holiday1 = holiday1 + 1; } else { holiday1 = 0; } } else if (startdayvalue < weekendday_1) { if (difference > (weekendday_1 - startdayvalue)) { holiday1 = (difference - (weekendday_1 - startdayvalue)) / 7; holiday1 = holiday1 + 1; } else if (difference == (weekendday_1 - startdayvalue)) { holiday1 = 1; } else { holiday1 = 0; } } else { holiday1 = difference / 7; holiday1 = holiday1 + 1; }
источник