65.9K
CodeProject 正在变化。 阅读更多。
Home

工作日计算算法优化

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.90/5 (16投票s)

2005年6月10日

5分钟阅读

viewsIcon

190419

downloadIcon

1299

实现了工作日和年龄计算的库,并且还实现了C#中的DateDiff函数。

引言

我的队友问我,如何计算两个给定日期之间的工作日天数?我的第一反应是,从开始日期循环到结束日期。但过了一段时间,我意识到这个解决方案并不优化。所以我开始开发我自己的优化算法。

在本文中,我们将讨论该算法,并查看其在SQL Server 2000上的实现以及一些C#代码。

让我们先从算法开始。以下是计算中涉及的关键因素。

因素

  1. 每周有多少个工作日?

    这取决于公司/国家政策。在美国,通常是每周5天,而在印度和许多其他国家,许多公司每周工作6天。我们的算法需要考虑到这一点。

  2. 有多少个假期?

    第二个因素是指定期间内的假期数量。这同样取决于公司政策。我们可以将这些详细信息保存在一个表中。这也很简单。

基本算法

  1. 将时间跨度以周为单位计算。称之为W
  2. 从周数中减去第一周。W = W - 1
  3. 将周数乘以每周的工作日数。称之为D
  4. 找出指定时间段内的假期。称之为H
  5. 计算第一周的天数。称之为SD
  6. 计算最后一天的天数。称之为ED
  7. 将所有天数相加。BD = D + SD + ED – H

很简单吧…!!!

我们知道每周的工作日数,并且由于模式每周重复,所以我们首先计算完整的周数。我们应该从周数中减去一。一旦我们得到周数,我们就可以乘以工作日数,从而得到一个粗略的工作日数。

好的,我们得到了一个粗略的工作日数,例如D。我称之为粗略数字,因为它不是精确的答案,因为我们在第一步中减去了一周。

现在,我们将找出开始周有多少天。例如,如果开始日期是星期三,那么开始周的天数是3天(如果每周工作日数为5天)或4天(如果每周工作日数为6天)。

同样,找出最后一周有多少天。最后,找出在此期间有多少个假期。这将是对Holiday表的简单聚合查询。

一旦我们掌握了所有数字,根据上述表达式,我们就可以得出我们想要的答案。希望这能消除您的所有疑虑。所以让我们开始实现这个算法。

首先,我们将尝试使用T-SQL在SQL Server上实现它。

SQL Server 实现

SQL Server 有许多日期函数,其中两个是DATEDIFFDATEPART。在这次实现中它们会很有用。

您可以将此算法实现为过程或函数。首选的是函数,但在这里我将把它实现为一个过程,以便我们可以使用print命令。一切就绪后,您可以通过删除print命令并使用return将其转换为过程。

解决方案

Create procedure SpBusinessDays (@dtStartDate datetime, @dtEndDate datetime,
@indDaysInWeek int)
as
begin
    declare 
    @intWeeks int
    ,@indDays int 
    ,@intSdays int
    ,@intEdays int
  
    -- Find the number of weeks between the dates. Subtract 1 
    -- since we do not want to count the current week.
  
    select @intWeeks = datediff( week, @dtStartDate, @dtEndDate) - 1
    print 'week'
    print @intWeeks
  
    -- calculate the number of days in these compelete weeks.
    select @indDays = @intWeeks * @indDaysInWeek
    print 'Est. Days'
    print @indDays
    
    -- Get the number of days in the starting week. 
    if @indDaysInWeek = 5

        -- If Saturday, Sunday is holiday
        if datepart( dw, @dtStartDate) = 7
            select @intSdays = 7 - datepart( dw, @dtStartDate)
        else
            select @intSdays = 7 - datepart( dw, @dtStartDate) - 1
    else
        -- If Sunday is only <st1:place>Holiday</st1:place>
        select @intSdays = 7 - datepart( dw, @dtStartDate) 
    print 'Starting Days'
    print @intSdays
    -- Calculate the days in the last week. 
    if @indDaysInWeek = 5
        if datepart( dw, @dtEndDate) = 7
            select @intEdays = datepart( dw, @dtEndDate) - 2
        else
            select @intEdays = datepart( dw, @dtEndDate) - 1
    else
        select @intEdays = datepart( dw, @dtEndDate) - 1
    print 'End Days' 
    print @intEdays
    -- Sum everything together.
    select @indDays = @indDays + @intSdays + @intEdays
    print 'Ans'
    print @indDays
end

注意:开始日期是排他的。

如果注意到这里,如果工作日数为6,我们无需担心任何事情,只需计算天数即可。如果工作日数为5,那么我们必须处理星期六。

就是这样。很简单。

C# 实现

好的,但是,如果您在表示层上进行计算,存储过程或“函数”将无法工作。因此,现在我们将在C#中实现相同的算法。

这是C#中的实现

/// <summary>
/// Calulates Business Days within the given range of days.
/// Start date and End date inclusive.
/// </summary>
/// <param name="startDate">Datetime object 
/// containing Starting Date</param>
/// <param name="EndDate">Datetime object containing 
/// End Date</param>
/// <param name="NoOfDayWeek">integer denoting No of Business 
/// Day in a week</param>
/// <param name="DayType"> DayType=0 for Business Day and 
/// DayType=1 for WeekEnds </param>
/// <returns></returns>

public static double CalculateBDay(
    DateTime startDate, 
    DateTime EndDate, 
    int NoOfDayWeek, /* No of Working Day per week*/
    int DayType 
)
{
    double iWeek, iDays, isDays, ieDays;
    //* Find the number of weeks between the dates. Subtract 1 */
    // since we do not want to count the current week. * /
    iWeek =DateDiff("ww",startDate,EndDate)-1 ;
    iDays = iWeek * NoOfDayWeek;
    //
    if( NoOfDayWeek == 5)
    {
        //-- If Saturday, Sunday is holiday
        if ( startDate.DayOfWeek == DayOfWeek.Saturday ) 
            isDays = 7 -(int) startDate.DayOfWeek;
        else
            isDays = 7 - (int)startDate.DayOfWeek - 1;
    } 
    else
    {
        //-- If Sunday is only <st1:place>Holiday</st1:place>
        isDays = 7 - (int)startDate.DayOfWeek;
    }
    //-- Calculate the days in the last week. These are not included in the
    //-- week calculation. Since we are starting with the end date, we only
    //-- remove the Sunday (datepart=1) from the number of days. If the end
    //-- date is Saturday, correct for this.
    if( NoOfDayWeek == 5)
    {
        if( EndDate.DayOfWeek == DayOfWeek.Saturday ) 
            ieDays = (int)EndDate.DayOfWeek - 2;
        else
            ieDays = (int)EndDate.DayOfWeek - 1;
    }
    else
    {
        ieDays = (int)EndDate.DayOfWeek - 1 ;
    }
    //-- Sum everything together.
    iDays = iDays + isDays + ieDays;
    if(DayType ==0) 
        return iDays;
    else
        return T.Days - iDays; 
}

附属产品

  • DateDiff函数

    在我处理这个问题时,我还遇到了C#中DateTime函数的另一个问题。我了解到C#没有VB.NET中存在的DateDiff这样的重要函数。所以我把它包含在同一个库中。正如Tim McCurdy所说,我们可以将Microsoft.VisualBasic.dll添加到我们的项目中,并使用VB团队实现的DateDiff函数,但我注意到许多人不喜欢将C#代码与VB.NET代码混合的想法,尽管在技术上这是完全可以的。计算周、月或年的第二个问题是,它们并不简单。您不能通过TimeSpan.Totaldays / 7 来获得周数。规则规定,周数等于给定期间内您跨越周边界的次数。为了解决这个问题,我添加了一个名为GetWeeks的新函数。

    /// <summary>
    /// Calculate weeks between starting date and ending date
    /// </summary>
    /// <param name="stdate"></param>
    /// <param name="eddate"></param>
    /// <returns></returns>
    
    public static int GetWeeks(DateTime stdate, DateTime eddate )
    {
    
      TimeSpan t= eddate - stdate;
      int iDays;
    
      if( t.Days < 7)
      {
        if(stdate.DayOfWeek > eddate.DayOfWeek)
          return 1; //It is accross the week 
    
        else
          return 0; // same week
      }
      else
      {
        iDays = t.Days -7 +(int) stdate.DayOfWeek ;
        int i=0;
        int k=0;
    
        for(i=1;k<iDays ;i++)
        {
          k+=7;
        }
    
        if(i>1 && eddate.DayOfWeek != DayOfWeek.Sunday ) i-=1; 
          return i;
      } 
    }
    
    
    /// <summary>
    /// Mimic the Implementation of DateDiff function of VB.Net.
    /// Note : Number of Year/Month is calculated
    ///        as how many times you have crossed the boundry.
    /// e.g. if you say starting date is 29/01/2005
    ///        and 01/02/2005 the year will be 0,month will be 1.
    /// 
    
    /// </summary>
    /// <param name="datePart">specifies on which part 
    ///   of the date to calculate the difference </param>
    /// <param name="startDate">Datetime object containing
    ///   the beginning date for the calculation</param>
    /// <param name="endDate">Datetime object containing
    ///   the ending date for the calculation</param>
    /// <returns></returns>
    
    public static double DateDiff(string datePart, 
                  DateTime startDate, DateTime endDate)
    {
    
      //Get the difference in terms of TimeSpan
      TimeSpan T;
    
      T = endDate - startDate;
    
      //Get the difference in terms of Month and Year.
      int sMonth, eMonth, sYear, eYear;
      sMonth = startDate.Month;
      eMonth = endDate.Month;
      sYear = startDate.Year;
      eYear = endDate.Year; 
      double Months,Years=0;
      Months = eMonth - sMonth;
      Years = eYear - sYear;
      Months = Months + ( Years*12);
    
      switch(datePart.ToUpper())
      {
        case "WW":
        case "DW":
             return (double)GetWeeks(startDate,endDate);
        case "MM":
             return Months;
        case "YY":
        case "YYYY":
             return Years;
        case "QQ":
        case "QQQQ":
             //Difference in Terms of Quater
             return Math.Ceiling((double)T.Days/90.0);
        case "MI":
        case "N":
             return T.TotalMinutes ;
        case "HH":
             return T.TotalHours ;
        case "SS":
             return T.TotalSeconds;
        case "MS":
             return T.TotalMilliseconds;
        case "DD":
        default:
             return T.Days; 
      }
    }
  • 年龄计算

    与工作日相比,这是一个简单的计算。我添加了一个函数,可以计算指定日期的年龄,以年、月和日为单位。

    /// <summary>
    /// Calculate Age on given date.
    /// Calculates as Years, Months and Days.
    /// </summary>
    /// <param name="DOB">Datetime object 
    /// containing DOB value</param>
    /// <param name="OnDate">Datetime object containing given 
    /// date, for which we need to calculate the age</param>
    /// <returns></returns>
    
    public static string Age(DateTime DOB, DateTime OnDate)
    {
        //Get the difference in terms of Month and Year.
        int sMonth, eMonth, sYear, eYear;
        double Months, Years;
        
        sMonth = DOB.Month;
        eMonth = OnDate.Month;
        sYear = DOB.Year;
        eYear = OnDate.Year; 
        
        // calculate Year
        if( eMonth >= sMonth) 
            Years = eYear - sYear;
        else
            Years = eYear - sYear -1;
            
        //calculate Months
        if( eMonth >= sMonth) 
            Months = eMonth - sMonth;
        else
            if ( OnDate.Day > DOB.Day)
                Months = (12-sMonth)+eMonth-1;
            else
                Months = (12-sMonth)+eMonth-2;
        
        double tDays=0;
        
        //calculate Days
        if( eMonth != sMonth && OnDate.Day != DOB.Day ) 
        {
            if(OnDate.Day > DOB.Day) 
            tDays = DateTime.DaysInMonth(OnDate.Year, 
                                       OnDate.Month) - DOB.Day; 
            else
            tDays = DateTime.DaysInMonth(OnDate.Year, 
                        OnDate.Month-1) - DOB.Day + OnDate.Day ; 
        }
        string strAge = Years+"/"+Months+"/"+tDays; 
        return strAge;
    }

摘要

如果您注意到算法,我也提到了假期,但我没有在上述任何代码中实现它,但可以通过一个简单的查询来处理。所以我把它留给您。如果您喜欢这段代码,或者它在您的项目中很有用,请告诉我。您可以通过Gaurang.Desai@gmail.com给我写信。

历史

  • 修正了C#代码中给定期间的周数计算的bug。2005年8月17日。
  • 2005年6月10日首次发布。
© . All rights reserved.