【问题标题】:Calculate the number of weekdays between two dates in C#在 C# 中计算两个日期之间的工作日数
【发布时间】:2020-06-06 00:51:38
【问题描述】:

我怎样才能得到两个给定日期之间的工作日数,而不只是遍历并计算工作日之间的日期?

似乎相当简单,但我似乎无法找到符合以下规定的结论性正确答案:

  1. 总数应包含在内,因此 GetNumberOfWeekdays(new DateTime(2009,11,30), new DateTime(2009,12,4)) 应等于 5,即周一至周五。
  2. 应该允许闰日
  3. 在计算工作日时不只是遍历所有日期。

我发现similar questionanswer 接近但不正确

【问题讨论】:

  • 您想仅排除周六和周日,还是应该考虑公共假期?
  • 只是周六和周日。我将创建一个单独的方法,GetNumberOfBusinessDays(DateTime from, DateTime to, IEnumerable),它排除了一个排除列表来处理排除公共假期。

标签: c#


【解决方案1】:

O(1) 解:

// Count days from d0 to d1 inclusive, excluding weekends
public static int countWeekDays(DateTime d0, DateTime d1)
{
    int ndays = 1 + Convert.ToInt32((d1 - d0).TotalDays);
    int nsaturdays = (ndays + Convert.ToInt32(d0.DayOfWeek)) / 7;
    return ndays - 2 * nsaturdays
           - (d0.DayOfWeek == DayOfWeek.Sunday ? 1 : 0)
           + (d1.DayOfWeek == DayOfWeek.Saturday ? 1 : 0);
}

2014 年 1 月的示例:

    January 2014
Su Mo Tu We Th Fr Sa
          1  2  3  4
 5  6  7  8  9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

countWeekDays(new DateTime(2014, 1, 1), new DateTime(2014, 1, 1)); // 1
countWeekDays(new DateTime(2014, 1, 1), new DateTime(2014, 1, 2)); // 2
countWeekDays(new DateTime(2014, 1, 1), new DateTime(2014, 1, 3)); // 3
countWeekDays(new DateTime(2014, 1, 1), new DateTime(2014, 1, 4)); // 3
countWeekDays(new DateTime(2014, 1, 1), new DateTime(2014, 1, 5)); // 3
countWeekDays(new DateTime(2014, 1, 1), new DateTime(2014, 1, 6)); // 4

注意DateTime 输入应该在一天中的同一时间。如果您在上面的示例中仅基于年、月和日创建 DateTime 对象,那么您应该没问题。举个反例,1 月 1 日凌晨 12:01 到 1 月 2 日晚上 11:59 仅持续 2 天,但如果您使用这些时间,上述函数将计为 3。

【讨论】:

  • 迄今为止最简单的解决方案。
  • 很好的解决方案 - 用一周中任何一天的测试用例作为开始日和结束日期从同一天到 3 周后确认,并且每次都能完美运行
  • 它不起作用。试试这个:countWeekDays(new DateTime(2016, 10, 1), new DateTime(2016, 10, 31)); // 22. 应该是 21
【解决方案2】:

来自link

    public static int Weekdays(DateTime dtmStart, DateTime dtmEnd)
    {
        // This function includes the start and end date in the count if they fall on a weekday
        int dowStart = ((int)dtmStart.DayOfWeek == 0 ? 7 : (int)dtmStart.DayOfWeek);
        int dowEnd = ((int)dtmEnd.DayOfWeek == 0 ? 7 : (int)dtmEnd.DayOfWeek);
        TimeSpan tSpan = dtmEnd - dtmStart;
        if (dowStart <= dowEnd)
        {
            return (((tSpan.Days / 7) * 5) + Math.Max((Math.Min((dowEnd + 1), 6) - dowStart), 0));
        }
        return (((tSpan.Days / 7) * 5) + Math.Min((dowEnd + 6) - Math.Min(dowStart, 6), 5));
    }


  [1]: http://www.eggheadcafe.com/community/aspnet/2/44982/how-to-calculate-num-of-w.aspx

测试(每个测试返回 5):

    int ndays = Weekdays(new DateTime(2009, 11, 30), new DateTime(2009, 12, 4));
    System.Console.WriteLine(ndays);

    // leap year test
    ndays = Weekdays(new DateTime(2000, 2,27), new DateTime(2000, 3, 5));
    System.Console.WriteLine(ndays);

    // non leap year test
    ndays = Weekdays(new DateTime(2007, 2, 25), new DateTime(2007, 3, 4));
    System.Console.WriteLine(ndays);

【讨论】:

  • 警告!当两个日期都在同一周时,此算法似乎不起作用。请参阅我的答案,以获得稍微不那么聪明但在我看来可以工作的代码。
  • 这将返回 3 表示工作日(新日期时间(2012,3,1),新日期时间(2012,3,2))。答案应该是 2。
【解决方案3】:

如果最后一天是周六或周日,eFloh 的答案会多出一天。这将解决它。

     public static int Weekdays(DateTime dtmStart, DateTime dtmEnd)
    {
        if (dtmStart > dtmEnd)
        {
            DateTime temp = dtmStart;
            dtmStart = dtmEnd;
            dtmEnd = temp;
        }

        /* Move border dates to the monday of the first full week and sunday of the last week */
        DateTime startMonday = dtmStart;
        int startDays = 1;
        while (startMonday.DayOfWeek != DayOfWeek.Monday)
        {
            if (startMonday.DayOfWeek != DayOfWeek.Saturday && startMonday.DayOfWeek != DayOfWeek.Sunday)
            {
                startDays++;
            }
            startMonday = startMonday.AddDays(1);
        }

        DateTime endSunday = dtmEnd;
        int endDays = 0;
        while (endSunday.DayOfWeek != DayOfWeek.Sunday)
        {
            if (endSunday.DayOfWeek != DayOfWeek.Saturday && endSunday.DayOfWeek != DayOfWeek.Sunday)
            {
                endDays++;
            }
            endSunday = endSunday.AddDays(1);
        }

        int weekDays;

        /* calculate weeks between full week border dates and fix the offset created by moving the border dates */
        weekDays = (Math.Max(0, (int)Math.Ceiling((endSunday - startMonday).TotalDays + 1)) / 7 * 5) + startDays - endDays;

        if (dtmEnd.DayOfWeek == DayOfWeek.Saturday || dtmEnd.DayOfWeek == DayOfWeek.Sunday)
        {
            weekDays -= 1;
        }

        return weekDays; 

    }

【讨论】:

  • 这是正确的,但比简单的迭代解决方案要慢。
【解决方案4】:

这应该比 dcp 的解决方案做得更好:

    /// <summary>
    /// Count Weekdays between two dates
    /// </summary>
    /// <param name="dtmStart">first date</param>
    /// <param name="dtmEnd">second date</param>
    /// <returns>weekdays between the two dates, including the start and end day</returns>
    internal static int getWeekdaysBetween(DateTime dtmStart, DateTime dtmEnd)
    {
        if (dtmStart > dtmEnd)
        {
            DateTime temp = dtmStart;
            dtmStart = dtmEnd;
            dtmEnd = temp;
        }

        /* Move border dates to the monday of the first full week and sunday of the last week */
        DateTime startMonday = dtmStart;
        int startDays = 1;
        while (startMonday.DayOfWeek != DayOfWeek.Monday)
        {
            if (startMonday.DayOfWeek != DayOfWeek.Saturday && startMonday.DayOfWeek != DayOfWeek.Sunday)
            {
                startDays++;
            }
            startMonday = startMonday.AddDays(1);
        }

        DateTime endSunday = dtmEnd;
        int endDays = 0;
        while (endSunday.DayOfWeek != DayOfWeek.Sunday)
        {
            if (endSunday.DayOfWeek != DayOfWeek.Saturday && endSunday.DayOfWeek != DayOfWeek.Sunday)
            {
                endDays++;
            }
            endSunday = endSunday.AddDays(1);
        }

        int weekDays;

        /* calculate weeks between full week border dates and fix the offset created by moving the border dates */
        weekDays = (Math.Max(0, (int)Math.Ceiling((endSunday - startMonday).TotalDays + 1)) / 7 * 5) + startDays - endDays;

        return weekDays;
    }

【讨论】:

    【解决方案5】:

    我需要正数/负数(不是绝对值),所以我是这样解决的:

        public static int WeekdayDifference(DateTime StartDate, DateTime EndDate)
        {
            DateTime thisDate = StartDate;
            int weekDays = 0;
            while (thisDate != EndDate)
            {
                if (thisDate.DayOfWeek != DayOfWeek.Saturday && thisDate.DayOfWeek != DayOfWeek.Sunday) { weekDays++; }
                if (EndDate > StartDate) { thisDate = thisDate.AddDays(1); } else { thisDate = thisDate.AddDays(-1); }
            }
    
            /* Determine if value is positive or negative */
            if (EndDate > StartDate) {
                return weekDays;
            }
            else
            {
                return weekDays * -1;
            }
        }
    

    【讨论】:

    • Math.Abs 获得绝对值怎么样!
    【解决方案6】:
            public static int GetWeekDays(DateTime startDay, DateTime endDate, bool countEndDate = true)
            {
                var daysBetween = (int)(endDate - startDay).TotalDays;
                daysBetween = countEndDate ? daysBetween += 1 : daysBetween;
                return Enumerable.Range(0, daysBetween).Count(d => !startDay.AddDays(d).DayOfWeek.In(DayOfWeek.Saturday, DayOfWeek.Sunday));
            }
    
            public static bool In<T>(this T source, params T[] list)
            {
                if (null == source)
                {
                    throw new ArgumentNullException("source");
                }
                return list.Contains(source);
            }
    

    【讨论】:

      【解决方案7】:
        public static List<DateTime> Weekdays(DateTime startDate, DateTime endDate)
        {
            if (startDate > endDate)
            {
                Swap(ref startDate, ref endDate);
            }
            List<DateTime> days = new List<DateTime>();
      
            var ts = endDate - startDate;
            for (int i = 0; i < ts.TotalDays; i++)
            {
                var cur = startDate.AddDays(i);
                if (cur.DayOfWeek != DayOfWeek.Saturday && cur.DayOfWeek != DayOfWeek.Sunday)
                    days.Add(cur);
                //if (startingDate.AddDays(i).DayOfWeek != DayOfWeek.Saturday || startingDate.AddDays(i).DayOfWeek != DayOfWeek.Sunday)
                //yield return startingDate.AddDays(i);
            }
            return days;
        }
      

      和交换日期

        private static void Swap(ref DateTime startDate, ref DateTime endDate)
        {
            object a = startDate;
            startDate = endDate;
            endDate = (DateTime)a;
        }
      

      【讨论】:

      • 您可以从这里获取日期以及日期计数。
      【解决方案8】:

      获取日期范围的实用函数:

      public IEnumerable<DateTime> GetDates(DateTime begin, int count)
      {
          var first = new DateTime(begin.Year, begin.Month, begin.Day);
          var maxYield = Math.Abs(count);
          for (int i = 0; i < maxYield; i++)
          {
              if(count < 0)
                  yield return first - TimeSpan.FromDays(i);
              else
                  yield return first + TimeSpan.FromDays(i);      
          }
          yield break;
      }
      
      public IEnumerable<DateTime> GetDates(DateTime begin, DateTime end)
      {
          var days = (int)Math.Ceiling((end - begin).TotalDays);
          return GetDates(begin, days);
      }
      

      LINQPad 演示代码:

      var begin = DateTime.Now;
      var end = begin + TimeSpan.FromDays(14);
      
      var dates = GetDates(begin, end);
      var weekdays = dates.Count(x => x.DayOfWeek != DayOfWeek.Saturday && x.DayOfWeek != DayOfWeek.Sunday);
      var mondays = dates.Count(x => x.DayOfWeek == DayOfWeek.Monday);
      var firstThursday = dates
          .OrderBy(d => d)
          .FirstOrDefault(d => d.DayOfWeek == DayOfWeek.Thursday);
      
      dates.Dump("Dates in Range");
      weekdays.Dump("Count of Weekdays");
      mondays.Dump("Count of Mondays");
      firstThursday.Dump("First Thursday");
      

      【讨论】:

        【解决方案9】:
        var dates = new List<DateTime>();
        
                for (var dt = YourStartDate; dt <= YourEndDate; dt = dt.AddDays(1))
                {
        
                    if (dt.DayOfWeek != DayOfWeek.Sunday && dt.DayOfWeek != DayOfWeek.Saturday)
                    { dates.Add(dt); }
        
                }
        

        在此代码中,您可以列出两个日期之间的所有营业日。

        如果你想要这些日期的计数,你可以得到dates.Count 作为一个整数。 或者如果你想得到每一天,你可以将列表加入一个字符串。

        【讨论】:

          【解决方案10】:

          这里的函数计算两个日期之间的 DayOfWeek 计数。小心它包含计算它(包括计算中的开始日期和结束日期):

          private int GetWeekdayCount(DayOfWeek dayOfWeek, DateTime begin, DateTime end)
              {
                  if (begin < end)
                  {
                      var timeSpan = end.Subtract(begin);
                      var fullDays = timeSpan.Days;
                      var count = fullDays / 7; // количество дней равно как минимум количеству полных недель попавших в диапазон
                      var remain = fullDays % 7; // остаток от деления
          
                      // и вычислим попал ли еще один день в те куски недели, что остаются от полной
                      if (remain > 0)
                      {
                          var dowBegin = (int)begin.DayOfWeek;
                          var dowEnd = (int)end.DayOfWeek;
                          var dowDay = (int)dayOfWeek;
                          if (dowBegin < dowEnd)
                          {
                              // когда день недели начала меньше дня недели конца, например:
                              //  начало       конец
                              //    \/          \/
                              //    -- -- -- -- --
                              // Вс Пн Вт Ср Чт Пт Сб
                              if (dowDay >= dowBegin && dowDay <= dowEnd)
                                  count++;
                          }
                          else
                          {
                              // когда день недели начала больше дня недели конца, например:
                              //   конец      начало
                              //    \/          \/
                              // -- --          -- --
                              // Вс Пн Вт Ср Чт Пт Сб
                              if (dowDay <= dowEnd || dowDay >= dowBegin)
                                  count++;
                          }
                      }
                      else if (begin.DayOfWeek == dayOfWeek)
                          count++;
          
                      return count;
                  }
                  return 0;
              }
          

          这里是之前函数的另一个简单模拟:

          private int GetWeekdayCountStupid(DayOfWeek dayOfWeek, DateTime begin, DateTime end)
              {
                  if (begin < end)
                  {
                      var count = 0;
                      var day = begin;
                      while (day <= end)
                      {
                          if (day.DayOfWeek == dayOfWeek)
                              count++;
                          day = day.AddDays(1);
                      }
                      return count;
                  }
                  return 0;
              }
          

          并测试这两个功能:

              [TestMethod()]
              public void TestWeekdayCount()
              {
                  var init = new DateTime(2000, 01, 01);
                  for (int day = 0; day < 7; day++)
                  {
                      var dayOfWeek = (DayOfWeek)day;
                      for (int shift = 0; shift < 8; shift++)
                      {
                          for (int i = 0; i < 365; i++)
                          {
                              var begin = init.AddDays(shift);
                              var end = init.AddDays(shift + i);
                              var count1 = GetWeekdayCount(dayOfWeek, begin, end);
                              var count2 = GetWeekdayCountStupid(dayOfWeek, begin, end);
                              Assert.AreEqual(count1, count2);
                          }
                      }
                  }
              }
          

          【讨论】:

            【解决方案11】:

            这是一个老问题,但我想我会分享一个更灵活的答案,允许删除一周中的任何一天。

            我试图通过最多循环 6 天来保持代码简洁易读,同时保持高效。

            所以对于 OP,你可以像这样使用它:

                myDate.DaysUntill(DateTime.UtcNow, new List<DayOfWeek> { DayOfWeek.Saturday, DayOfWeek.Sunday });
            
            
                /// <summary>
                /// For better accuracy make sure <paramref name="startDate"/> and <paramref name="endDate"/> have the same time zone.
                /// This is only really useful if we use <paramref name="daysOfWeekToExclude"/> - otherwise the computation is trivial.
                /// </summary>
                /// <param name="startDate"></param>
                /// <param name="endDate"></param>
                /// <param name="daysOfWeekToExclude"></param>
                /// <returns></returns>
                public static int DaysUntill(this DateTime startDate, DateTime endDate, IEnumerable<DayOfWeek> daysOfWeekToExclude = null)
                {
                    if (startDate >= endDate) return 0;
                    daysOfWeekToExclude = daysOfWeekToExclude?.Distinct() ?? new List<DayOfWeek>();
            
                    int nbOfWeeks = (endDate - startDate).Days / 7;
                    int nbOfExtraDays = (endDate - startDate).Days % 7;
            
                    int result = nbOfWeeks * (7 - daysOfWeekToExclude.Count());
            
                    for (int i = 0; i < nbOfExtraDays; i++)
                    {
                        if (!daysOfWeekToExclude.Contains(startDate.AddDays(i + 1).DayOfWeek)) result++;
                    }
                    return result;
                }
            

            【讨论】:

              猜你喜欢
              • 2018-08-03
              • 2010-09-20
              • 2014-10-20
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多