计算两次约会之间的工作日数?

在 C # 中,如何计算两个日期之间的 生意(或工作日)天数?

189429 次浏览

Define an Extension Method on DateTime like so:

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

我以前也有过这样的任务,我已经找到了解决办法。 我会避免列举两者之间可以避免的所有日子,这里就是这种情况。正如我在上面的一个答案中看到的,我甚至没有提到创建一堆 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的代码:

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

现在的结果是:

  • Friday to Friday -> 1
  • 星期六至六-> 0
  • 星期日至星期日-> 0
  • 星期五至六-> 1
  • 星期五至星期日-> 1
  • Friday to Monday -> 2
  • 星期六至星期一-> 1
  • 星期日至星期一-> 1
  • Monday to Monday -> 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/

我知道这个问题已经解决了,但我想我可以提供一个更直观的答案,可能有助于其他访问者在未来。

我是这么想的:

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

我认为以上的答案都不是正确的。它们都不能解决所有的特殊情况,比如约会在周末中间开始和结束,约会在周五开始和下周一结束等等。最重要的是,他们所有周围的计算整天,所以如果开始日期是在一个星期六的中间,例如,它将减去一整天的工作日,给出错误的结果..。

无论如何,这里是我的解决方案,这是相当有效和简单,适用于所有情况。诀窍就是找到开始和结束日期的前一个星期一,然后在开始和结束发生在周末的时候做一个小小的补偿:

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

我只是分享我的解决方案。它对我起作用了,也许我只是没有注意到/不知道有一个 bug。 我开始得到第一个不完整的一周,如果有任何。 一个完整的星期是从星期天到星期六,所以如果(int) _ now。DayOfWeek 不是0(星期日) ,第一周是不完整的。

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

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

Then finally, the number of complete weeks multiply by 5(weekdays) was added to new count.

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

这是一个通用的解决方案。

Startdayvalue 是开始日期的天数。

周末 _ 1是周末的天数。

日期: MON-1,TUE-2,... SAT-6,SUN-7。

difference is difference between two dates..

示例: 开始日期: 2013年4月4日,结束日期: 2013年4月14日

区别: 10,startdayvalue: 4,week endday _ 1:7(如果星期天是你的周末)

This will give you number of holidays.

营业日数 = (差额 + 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;
}

我使用以下代码也考虑到银行假期:

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

我在找这段代码的 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);
}
 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);
}
}

这里有一个非常简单的解决这个问题的方法。我们有开始日期、结束日期和“ 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;
}
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;
}

下面是一个快速示例代码。它是一个类方法,因此只能在类内部工作。如果您希望它是 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;
}

This method creates a loop variable d, initializes it to the start day, sd, then increments by one day each iteration (d = d.AddDays(1)).

它使用 yield返回所需的值,这将创建一个 iterator。关于迭代器的一个很酷的事情是,它们并不在内存中保存 IEnumerable的所有值,只是按顺序调用每个值。这意味着您可以从最初到现在调用这个方法,而不必担心内存不足。

下面是我们可以用来计算两个日期之间的工作日的函数。我没有使用假日列表,因为它可以因国家/地区而异。

如果我们无论如何都要使用它,我们可以把第三个参数作为假日列表,在递增 count 之前,我们应该检查列表是否包含 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;
}

这个解决方案避免了迭代,同时适用于 + ve 和-ve 工作日差异,并且包含了一个单元测试套件来针对计算工作日的较慢方法进行回归。我还包括了一个简洁的方法来添加工作日也在相同的非迭代方式工作。

Unit tests cover a few thousand date combinations in order to exhaustively test all start/end weekday combinations with both small and large date ranges.

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

注意: 这段代码需要对假期进行额外的调整,但是为了与上面的假设保持一致,这段代码必须在开始日期排除假期。

Add weekdays:

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

I found that most other solutions on stack overflow were either slow (iterative) or overly complex and many were just plain incorrect. Moral of the story is ... Don't trust it unless you've exhaustively tested it!!

基于 组合测试应该是 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);
}
}

Well this has been beaten to death. :) However I'm still going to provide another answer because I needed something a bit different. This solution is different in that it returns a Business TimeSpan between the start and end, and you can set the business hours of the day, and add holidays. So you can use it to calculate if it happens within a day, across days, over weekends, and even holidays. And you can get just the business days or not by just getting what you need from the returned TimeSpan object. And the way it uses lists of days, you can see how very easy it would be to add the list of non-work days if it's not the typical Sat and Sun. 我测试了一年,看起来非常快。

I just hope the pasting of the code is accurate. But I know it works.

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

这里是测试代码: 注意,您只需要将这个函数放到一个名为 约会助手的类中,测试代码就可以工作了。

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

我搜索了很多,找到了一个容易消化的算法来计算两个日期之间的工作日,并且排除了国定假日,最后我决定采用这种方法:

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. 不是周六
  2. 不是星期天
  3. 不是国定假日

但我也想避免重复日期。

通过运行和测量时间需要它评估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 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;
}

因为我无可奉告。关于公认的解决方案还有一个问题,即即使在周末也要减去银行假期。看看如何检查其他输入,只有这样才合适。

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

如果您正在使用 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++;
}
}

树叶模型是这里的一个列表

可以工作,不需要循环

This method doesn't use any loops and is actually quite simple. It expands the date range to full weeks since we know that each week has 5 business days. It then uses a lookup table to find the number of business days to subtract from the start and end to get the right result. I've expanded out the calculation to help show what's going on, but the whole thing could be condensed into a single line if needed.

Anyway, this works for me and so I thought I'd post it here in case it might help others. Happy coding.

计算

  • T: 日期之间的总天数(如果最小 = 最大,则为1)
  • A + b: 需要额外的天数来将总周数增加到完整的周数
  • K: 1.4是每周的工作日数,即(t/7) * 5
  • C: 从总数中减去的工作日数
  • M: 一个查找表,用于查找一周中每一天的“ c”值

Culture

代码假定星期一至星期五工作周。对于其他区域性,如星期日至星期四,需要在计算之前抵消日期。

方法

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

下面是我为该任务编写的 helper 函数。< br/> 它还通过 out参数返回周末的计数。< br/> 如果你愿意,你可以通过 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;
}

You just have to iterate through each day in the time range and subtract a day from the counter if its a Saturday or a Sunday.

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

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 可以使这个过程更加简单,但是这种方法更加容易理解。

看看这个 1. https://github.com/yatishbalaji/moment-working-days#readme 2. https://www.npmjs.com/package/moment-working-days

它允许你计算工作日,考虑日期的顺序。您可以自定义一周的休息日,也可以声明自定义的假日日期(如: 公共假日) ,以排除它们作为工作日计算

Here's another example you can choose the weekend dates as well,

  public int GetBuisnessDays(DateTime StartDate, DateTime EndDate)
{
int counter = 0;


if (StartDate.Date == EndDate.Date)
{
if (StartDate.DayOfWeek != DayOfWeek.Saturday && StartDate.DayOfWeek != DayOfWeek.Friday)
return 1;
return 0;
}


while (StartDate <= EndDate)
{
if (StartDate.DayOfWeek != DayOfWeek.Saturday && StartDate.DayOfWeek != DayOfWeek.Friday)
++counter;
StartDate = StartDate.AddDays(1);
}


return counter;
}

所以我有一个类似的任务,除了我必须计算剩下的工作日(从日期起不应该超过到日期) ,结束日期应该跳到下一个工作日。

为了使它更容易理解/可读,我按照以下步骤完成了这一步

  1. 更新为日期到下一个工作日,如果它是在周末。

  2. 找出日期之间的完整周数,并且每个完整周考虑5天的运行总数。

  3. 现在剩下的日子只与工作日和工作日不同(不会超过6天) ,所以编写一个小循环来获取它(跳过星期六和星期天)

     public static int GetBusinessDaysLeft(DateTime fromDate, DateTime toDate)
    {
    //Validate that startDate should be less than endDate
    if (fromDate >= toDate) return 0;
    
    
    //Move end date to Monday if on weekends
    if (toDate.DayOfWeek == DayOfWeek.Saturday || toDate.DayOfWeek == DayOfWeek.Sunday)
    while (toDate.DayOfWeek != DayOfWeek.Monday)
    toDate = toDate.AddDays(+1);
    
    
    //Consider 5 days per complete week in between start and end dates
    int remainder, quotient = Math.DivRem((toDate - fromDate).Days, 7, out remainder);
    var daysDiff = quotient * 5;
    
    
    var curDay = fromDate;
    while (curDay.DayOfWeek != toDate.DayOfWeek)
    {
    curDay = curDay.AddDays(1);
    if (curDay.DayOfWeek == DayOfWeek.Saturday || curDay.DayOfWeek == DayOfWeek.Sunday)
    continue;
    
    
    daysDiff += 1;
    }
    return daysDiff;
    }
    

此方法返回两个日期之间的工作日数:

在这里,我使用 DayOfWeek 枚举检查周末。

        private static int BusinessDaysLeft(DateTime first, DateTime last)
{
var count = 0;


while (first.Date != last.Date)
{
if(first.DayOfWeek != DayOfWeek.Saturday && first.DayOfWeek != DayOfWeek.Sunday)
count++;
                

first = first.AddDays(1);
}


return count;
}

许多答案都很棒,但它们没有考虑到开始/结束时间。下面是我基于 这个答案改进的解决方案(在 C # 中) ,但是考虑到了开始和结束时间。 For example:

  • 2021年2月8日(星期一)至2021年4月8日(星期三)为2天——在这种情况下,8/4/2021被排除在外,因为没有指定时间。网络自动分配到凌晨12点。

  • 2021年8月2日星期一下午12点到2021年4月8日星期三下午12点是两天。

  • 2021年6月8日星期五下午12点到2021年10月8日星期二下午6点是2.25天。

    public static double GetWorkingDays(DateTime startDate, DateTime endDate)
    {
    // Get next full day from start date and last full day of end date
    var nextFullStartDate = startDate.AddDays(1).Date;
    var lastFullEndDate = endDate.Date;
    
    
    // Calculate total days based on start and end's day of week
    var dayOfWeekFactor = (nextFullStartDate.DayOfWeek - lastFullEndDate.DayOfWeek) * 2;
    if (lastFullEndDate.DayOfWeek == DayOfWeek.Sunday)
    {
    // In .net, day of week for Sunday is 0, we need to change it to 7
    dayOfWeekFactor = ((int)nextFullStartDate.DayOfWeek - 7) * 2;
    }
    
    
    // Calculate working days (ie: Monday, Tuesday, Wednesday, Thursday and Friday)
    var workingDayCount = ((lastFullEndDate - nextFullStartDate).TotalDays * 5 - dayOfWeekFactor) / 7;
    
    
    // Subtract working days if full start date or last full end date is Sunday
    // Saturday is not included here because if startDate is Saturday the formula above would have excluded Saturday. If the endDate is Saturday, it would have been excluded too because the time would be Saturday 12:00 AM
    if (nextFullStartDate.DayOfWeek == DayOfWeek.Sunday || lastFullEndDate.DayOfWeek == DayOfWeek.Sunday)
    {
    workingDayCount--;
    }
    
    
    // Count the real start/end date
    if (startDate.DayOfWeek != DayOfWeek.Saturday &&
    startDate.DayOfWeek != DayOfWeek.Sunday)
    {
    var startDateFraction = (nextFullStartDate - startDate).TotalHours / 24;
    workingDayCount += startDateFraction;
    }
    
    
    if (endDate.DayOfWeek != DayOfWeek.Saturday &&
    endDate.DayOfWeek != DayOfWeek.Sunday)
    {
    var endDateFraction = (endDate - lastFullEndDate).TotalHours / 24;
    workingDayCount += endDateFraction;
    }
    
    
    return workingDayCount;
    }
    

通过使用 Marinjw库, 和解决方案提供的 Alec Pojidaev 我创建了以下,这将跳过公共(你需要指定国家)假期:

请注意,如果您使用实际的日期时间,并有小时存在,那么您必须添加 .Date,并最好将 FromDateToDate重新格式化为 FromDate = FromDate.DateToDate = ToDate.Date

public static int GetProcessingTime(this DateTime? FromDate, DateTime? ToDate)
{
if (FromDate == null) return 0;
if (ToDate == null) return 0;


List<DateTime> Holidays = new List<DateTime>();


int startyear = FromDate.Value.Year;
int endyear = ToDate.Value.Year;


//loop years to extract all holidays
for(int i = startyear; i <= endyear; i++)
{
IList<DateTime> Temp = new PolandPublicHoliday().PublicHolidays(i);
Holidays.AddRange(Temp.ToList());
}


//exclude holidays outside of range
Holidays = Holidays.Where(x => x >= FromDate && x <= ToDate).ToList();


//exclude holidays that are set to be on sunday/saturday
Holidays = Holidays.Where(x => x.DayOfWeek != DayOfWeek.Sunday && x.DayOfWeek != DayOfWeek.Saturday).ToList();


//calculate date difference without sundays and saturdays Value need to be added as I am using nullable DateTime
double calcBusinessDays = 1 + ((FromDate.Value - ToDate.Value).TotalDays * 5 - (FromDate.Value.DayOfWeek - ToDate.Value.DayOfWeek) * 2) / 7;


if (FromDate.Value.DayOfWeek == DayOfWeek.Saturday) calcBusinessDays--;
if (ToDate.Value.DayOfWeek == DayOfWeek.Sunday) calcBusinessDays--;


//remove left holidays
if (Holidays!=null)
calcBusinessDays -= Holidays.Count;


return (int)calcBusinessDays;
}

  <!--  **Here is the solution for getting working days except for all Sundays and 2nd-4th Saturdays**-->


public void GetWorkingDays
{
DateTime from = new DateTime(2022, 10, 03, 10, 30, 50);
DateTime to = new DateTime(2022, 11, 15, 16, 30, 50);
            

Console.WriteLine("From: "+from);
Console.WriteLine("To: "+to);
Program pgm = new Program();
List<DateTime> allDates1 = pgm.GetDatesBetween(from, to);
<!--  //here this above list will print all working days except for all Sundays and 2nd-4th Saturdays -->
}


<!--  //get List of Between dates-->
public List<DateTime> GetDatesBetween(DateTime startDate, DateTime endDate)
{
List<DateTime> allDates = new List<DateTime>();
List<DateTime> Saturday = GetSaturdayDatesBetween(startDate, endDate);
for (DateTime date = startDate; date <= endDate; date = date.AddDays(1))
if (date.DayOfWeek != DayOfWeek.Sunday && !Saturday.Contains(date.Date))
{
if (date.Date != endDate.Date)
{
allDates.Add(date);
}
else
{
allDates.Add(endDate);
}
}
return allDates;


}


<!-- //Get 2nd and 3rd Saturday -->
public List<DateTime> GetSaturdayDatesBetween(DateTime from, DateTime to)
{
List<DateTime> Saturday = new List<DateTime>();


for (DateTime date = from; date <= to; date = date.AddMonths(1))
{
var start = new DateTime(from.Year, date.Month, 1, from.Hour, from.Minute, from.Second, from.Millisecond);
var end = DateTime.DaysInMonth(from.Year, date.Month);
var endDate = new DateTime(date.Year, date.Month, end,  date.Hour, date.Minute, date.Second, date.Millisecond);
int satCount=0;
for (DateTime curDate = start; curDate <= endDate; curDate = curDate.AddDays(1))
{
if(curDate.DayOfWeek == DayOfWeek.Saturday)
{
satCount++;
if (satCount == 2 || satCount == 4)
{
Saturday.Add(curDate.Date);
}


}
}
}
return Saturday;
}