计算两个date之间的营业日数?

在C#中,如何计算两个date之间的业务 (或工作日)天数?

我以前有过这样的任务,我已经有了解决办法。 我会避免列举所有可以避免的时间,这里就是这种情况。 我甚至没有提到创build一堆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; } 

编辑Slauma,2011年8月

很好的回答! 虽然有小错误。 自从2009年回答者缺席以来,我自由地编辑了这个答案。

上面的代码假设DayOfWeek.Sunday的值是7 ,而不是这种情况。 该值实际上是0 。 如果例如firstDaylastDay都是相同的星期天,则会导致错误的计算。 在这种情况下该方法返回1 ,但应该是0

这个bug最简单的修复方法是:在firstDayOfWeeklastDayOfWeek被声明的行上面的代码replace如下:

 int firstDayOfWeek = firstDay.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)firstDay.DayOfWeek; int lastDayOfWeek = lastDay.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)lastDay.DayOfWeek; 

现在的结果是:

  • 周五至周五 – > 1
  • 周六到周六 – > 0
  • 周日至周日 – > 0
  • 周五至周六 – > 1
  • 周五至周日 – > 1
  • 星期五至星期一 – > 2
  • 周六至周一 – > 1
  • 周日至周一 – > 1
  • 周一至周一 – > 1

好。 我认为是时候发表正确的答案了:

 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解决scheme出现在上面。

我知道这个问题已经解决了,但是我想我可以提供一个更直接的答案,可以帮助其他参观者。

这是我的看法:

 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; } 

在DateTime上定义一个扩展方法,如下所示:

 public static class DateTimeExtensions { public static bool IsWorkingDay(this DateTime date) { return date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday; } } 

然后,使用Where子句来过滤更广泛的date列表:

 var allDates = GetDates(); // method which returns a list of dates // filter dates by working day's var countOfWorkDays = allDates .Where(day => day.IsWorkingDay()) .Count() ; 

我使用了下面的代码来logging银行假期:

 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; } } 

和unit testing:

 [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"); } } 

这里有一些代码为这个目的,与瑞典的假期,但你可以适应什么假期计数。 请注意,我添加了一个限制,您可能想要删除,但它是一个基于Web的系统,我不希望任何人input一些巨大的date来处理这个过程

  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; } 

那这个已经被打死了。 :)但是,我仍然会提供另一个答案,因为我需要一些不同的东西。 该解决scheme的不同之处在于它在开始和结束之间返回Business TimeSpan ,您可以设置当天的营业时间并添加节假日。 所以你可以用它来计算它是否在一天之内,几天之内,甚至是周末,甚至假期。 而且您只需要从营业时间中获取所需的信息,即可从返回的TimeSpan对象中获取所需的信息。 而且它使用date列表的方式,你可以看到添加非工作日的列表是非常容易的,如果它不是典型的星期六和星期日。 我testing了一年,看起来超级快。

我只是希望代码的粘贴是准确的。 但我知道它的作品。

 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; } } 

这里是testing代码:注意,你只需要把这个函数放在一个名为DateHelper的类中,testing代码就可以工作。

 [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); } } 

这个解决scheme避免迭代,适用于+ ve和-ve星期几之间的差异,并且包括一个unit testing套件,用于计算周一至周五的较慢方法。 我还包括一个简洁的方法来添加周日也同样的非迭代的方式工作。

unit testing涵盖了几千个date组合,以便详尽地testing所有开始/结束日间组合,包括小date和大date范围。

重要提示 :我们假设我们通过排除开始date和结束date来计算date。 在计算平日时,这一点很重要,因为您包含/排除的具体开始/结束date会影响结果。 这也确保了两个相等date之间的差异总是零,并且我们只包括完整的工作日,因为通常您希望答案在当前开始date(通常是今天)的任何时候都是正确的,并且包括完整的结束date(例如,到期日)。

注意:此代码需要对假期进行额外的调整,但与上述假设保持一致,此代码必须排除开始date的假期。

平日添加:

 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]; } 

我发现大多数堆栈溢出的其他解决scheme要么是缓慢的(迭代的),要么是过于复杂,许多只是不正确的。 道德的故事是…不要相信它,除非你已经彻底地testing了它!

基于NUnit组合testing和ShouldBe NUnit扩展的unit testing。

 [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); } } 

我认为上述答案都不是真的。 他们都没有解决所有的特殊情况,例如date在周末开始和结束的时间,date从星期五开始到下星期一结束等等。最重要的是,他们全部计算到整个所以如果开始date是在星期六的中间,那么它将从工作日中减去一整天,给出错误的结果。

无论如何,这是我的解决scheme,相当高效和简单,适用于所有情况。 诀窍是find上个星期一的开始和结束date,然后在周末开始和结束时做一个小的补偿:

 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; } 

这是一个快速示例代码。 这是一个类方法,所以只能在你的类中工作。 如果您希望它是static ,请将签名更改为private static (或public static )。

  private IEnumerable<DateTime> GetWorkingDays(DateTime sd, DateTime ed) { for (var d = sd; d <= ed; d.AddDays(1)) if (d.DayOfWeek != DayOfWeek.Saturday && d.DayOfWeek != DayOfWeek.Sunday) yield return d; } 

这个方法创build一个循环variablesd ,将其初始化为开始日( sd ,然后每次迭代增加一天( d.AddDays(1) )。

它使用yield返回所需的值,从而创build一个iterator 。 关于迭代器的一件很酷的事情是,它们并没有将IEnumerable所有值都保存在内存中,而只是依次调用每一个值。 这意味着你可以从现在开始调用这个方法,而不必担心内存不足。

我只是分享我的解决scheme。 它为我工作,也许我只是不注意/知道这是一个错误。 如果有的话,我首先得到第一个不完整的一周。 星期六星期天是整整一周,所以如果(星期一)_星期天不是星期天(星期天),第一星期是不完整的。

我只是减去1到第一周的第一个星期的星期六计数,然后加上新的计数;

然后我得到最后一个不完整的星期,然后减去1星期天,然后join新的计数。

最后,将完成周数乘以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; } 

我很难find这个代码的一个固定的TSQL版本。 下面实际上是C#代码的转换, 此处添加了应该用来预先计算假期的假期表。

 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); } 

Here is one very simple solution for this problem. We have starting date, end date and "for loop" for encreasing the day and calculating to see if it's a workday or a weekend by converting to string 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); } } 

Based on the comment marked as answer and patch recommended , as well as -> This version wants to convert the Days to Business-Hours … Considers Same day hours as well.

  /// <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; } 
 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; } } } 

I just improved @Alexander and @Slauma answer to support a business week as a parameter, for cases where saturday is a business day, or even cases where there is just a couple of days of the week that are considered business days:

 /// <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; } 

Here is the function which we can use to calculate business days between two date. I'm not using holiday list as it can vary accross country/region.

If we want to use it anyway we can take third argument as list of holiday and before incrementing count we should check that list does not contains 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; } 

I believe this could be a simpler way:

  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; } 

Here's yet another idea – this method allows to specify any working week and holidays.

The idea here is that we find the core of the date range from the first first working day of the week to the last weekend day of the week. This enables us to calculate the whole weeks easily ( without iterating over all of the dates). All we need to do then is to add the working days that fall before the start and end of this core range.

 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; } 

Since I can't comment. There is one more issue with the accepted solution where bank holidays are subtracted even when they are situated in the weekend. Seeing how other input is checked, it is only fitting that this is as well.

The foreach should therefore be:

  // 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; } 

I searched a lot for a, easy to digest, algorithm to calculate the working days between 2 dates, and also to exclude the national holidays, and finally I decide to go with this approach:

 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(); } 

Basically I wanted to go with each date and evaluate my conditions:

  1. Is not Saturday
  2. Is not Sunday
  3. Is not national holiday

but also I wanted to avoid iterating dates.

By running and measuring the time need it to evaluate 1 full year, I go the following result:

 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 } 

This is a generic solution.

startdayvalue is day number of start date.

weekendday_1 is day numner of week end.

day number – MON – 1, TUE – 2, … SAT – 6, SUN -7.

difference is difference between two dates..

Example : Start Date : 4 April, 2013, End Date : 14 April, 2013

Difference : 10, startdayvalue : 4, weekendday_1 : 7 (if SUNDAY is a weekend for you.)

This will give you number of holidays.

No of business day = (Difference + 1) – holiday1

  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; } 
  public enum NonWorkingDays { SaturdaySunday = 0, FridaySaturday = 1 }; public int getBusinessDates(DateTime dateSt, DateTime dateNd, NonWorkingDays nonWorkingDays = NonWorkingDays.SaturdaySunday) { List<DateTime> datelist = new List<DateTime>(); while (dateSt.Date < dateNd.Date) { datelist.Add((dateSt = dateSt.AddDays(1))); } if (nonWorkingDays == NonWorkingDays.SaturdaySunday) { return datelist.Count(d => d.DayOfWeek != DayOfWeek.Saturday && d.DayOfWeek != DayOfWeek.Friday); } else { return datelist.Count(d => d.DayOfWeek != DayOfWeek.Friday && d.DayOfWeek != DayOfWeek.Saturday); } }