计算两个日期之间的工作日数?

Mar*_*tin 86 c# datetime

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

小智 115

我之前有过这样的任务,而且我已经得到了解决方案.我会避免在可以避免之间的所有日子进行枚举,这就是这种情况.我甚至没有提到创建一堆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;
    }
Run Code Online (Sandbox Code Playgroud)

由Slauma编辑,2011年8月

很棒的答案!虽然有一点虫子.自从2009年以来,回答者缺席,我可以自由编辑这个答案.

上面的代码假设DayOfWeek.Sunday具有7不是这种情况的值.这个值实际上是0.如果例如firstDay并且lastDay两者都是相同的星期日,则会导致错误的计算.1在这种情况下,该方法返回但它应该是0.

此错误最简单的解决:替换在该线以上的代码firstDayOfWeeklastDayOfWeek由以下声明:

int firstDayOfWeek = firstDay.DayOfWeek == DayOfWeek.Sunday 
    ? 7 : (int)firstDay.DayOfWeek;
int lastDayOfWeek = lastDay.DayOfWeek == DayOfWeek.Sunday
    ? 7 : (int)lastDay.DayOfWeek;
Run Code Online (Sandbox Code Playgroud)

结果是:

  • 周五到周五 - > 1
  • 周六至周六 - > 0
  • 周日至周日 - > 0
  • 周五到周六 - > 1
  • 周五至周日 - > 1
  • 周五到周一 - > 2
  • 周六到周一 - > 1
  • 周日至周一 - > 1
  • 周一至周一 - > 1

  • 谢谢你的方法.虽然,我不得不将以下内容添加到银行假期减法/迭代if-statement:`&&!(bh.DayOfWeek == DayOfWeek.Sunday || bh.DayOfWeek == DayOfWeek.Saturday)`,否则它会减去相同的一天两次,如果假期在一个周末. (5认同)
  • 还要确保银行假日如下:if(firstDay <= bh && bh <= lastDay && bh.IsWorkingDay()) (2认同)

小智 94

好.我认为是时候发布正确答案了:

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;
}
Run Code Online (Sandbox Code Playgroud)

原始来源:

http://alecpojidaev.wordpress.com/2009/10/29/work-days-calculation-with-c/

上面发布的PS解决方案由于某种原因让我成为了原因.

  • 好的工作,但也许使用DayOfWeek自己的枚举而不是将它们强制转换为整数? (10认同)
  • 这不是正确的答案.天可以超出4.几乎是正确的,没有考虑周末开始和结束日期,这是最棘手的一点.起点也不应该在括号内.这与问题无关.60%的时间此解决方案是*错误*. (10认同)
  • 请注意,即使此函数返回double,也应该只信任**整个**工作日.当涉及时间时,它不会返回小数天的正确答案. (6认同)
  • 只是要注意,用'1+'假定**开始**的第一天直到**结束**的最后一天,没有'1+'它假设**结束**的第一天直到**结束**最后一天.我花了一段时间来弄清楚,因为我假设**开始**的第一天直到**开始**的最后一天,这对我来说更有意义. (4认同)
  • 说真的,最好的解决方案.干杯亚力克 (3认同)

Alp*_*pha 46

我知道这个问题已经解决了,但我想我可以提供一个更直接的答案,可以帮助其他访客.

这是我的看法:

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);
}
Run Code Online (Sandbox Code Playgroud)

这是我最初提交的内容:

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;
}
Run Code Online (Sandbox Code Playgroud)


Qwe*_*rty 22

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

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

然后,使用在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() ;
Run Code Online (Sandbox Code Playgroud)

  • 可以通过更改计数位置和消除计数来缩短 (10认同)
  • 我不确定为什么这是一个合适的答案...他没有个人日的清单,他有两个约会,他想找到他们之间的工作日数.为了使用这个解决方案,您必须提供*another*函数,该函数生成twyp之间的每个日期的列表. (3认同)

pau*_*r19 12

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

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; }
}
Run Code Online (Sandbox Code Playgroud)

和单元测试:

[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");
    }
}
Run Code Online (Sandbox Code Playgroud)


Ton*_*gan 6

此解决方案避免迭代,适用于 +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);
}
Run Code Online (Sandbox Code Playgroud)

计算工作日差异:

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];
}
Run Code Online (Sandbox Code Playgroud)

我发现堆栈溢出的大多数其他解决方案要么缓慢(迭代)要么过于复杂,而且很多都是完全不正确的。 这个故事的寓意是......除非你已经彻底测试过它,否则不要相信它!!

基于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);
    }
}
Run Code Online (Sandbox Code Playgroud)


use*_*376 6

好吧,这已经被打死了。:)但是我仍然要提供另一个答案,因为我需要一些不同的东西。该解决方案的不同之处在于,它在开始和结束之间返回一个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;
    }
}
Run Code Online (Sandbox Code Playgroud)

这是测试代码:请注意,您只需要将此函数放在名为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);
    }
}
Run Code Online (Sandbox Code Playgroud)


Luc*_*umb 5

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

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();
        } 
Run Code Online (Sandbox Code Playgroud)

基本上我想和每个日期一起去评估我的条件:

  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
        }
Run Code Online (Sandbox Code Playgroud)

编辑:一种更简单的新方法:

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

        }
Run Code Online (Sandbox Code Playgroud)