Calculate business hours between two dates

前端 未结 14 1121
被撕碎了的回忆
被撕碎了的回忆 2020-11-27 05:06

How can I calculate business hours between two dates? For example we have two dates; 01/01/2010 15:00 and 04/01/2010 12:00 And we have working hours 09:00 to 17:00 in weekda

相关标签:
14条回答
  • 2020-11-27 05:37

    I know this is post is very old but here is a function I wrote recently to calculate Business Hours/Minutes between any two events. It also takes into account any holidays which must be defined in a table.

    The function returns the interval in minutes - you can divide by 60 to get hours as required.

    This has been tested on SQL Server 2008. Hope it helps someone.

    Create Function GetWorkingMin(@StartDate DateTime, @EndDate DateTime, @Country Varchar(2)) Returns Int
    AS
    Begin
        Declare @WorkMin int = 0   -- Initialize counter
        Declare @Reverse bit       -- Flag to hold if direction is reverse
        Declare @StartHour int = 9   -- Start of business hours (can be supplied as an argument if needed)
        Declare @EndHour int = 17    -- End of business hours (can be supplied as an argument if needed)
        Declare @Holidays Table (HDate DateTime)   --  Table variable to hold holidayes
    
        -- If dates are in reverse order, switch them and set flag
        If @StartDate>@EndDate 
        Begin
            Declare @TempDate DateTime=@StartDate
            Set @StartDate=@EndDate
            Set @EndDate=@TempDate
            Set @Reverse=1
        End
        Else Set @Reverse = 0
    
        -- Get country holidays from table based on the country code (Feel free to remove this or modify as per your DB schema)
        Insert Into @Holidays (HDate) Select HDate from HOLIDAY Where COUNTRYCODE=@Country and HDATE>=DateAdd(dd, DateDiff(dd,0,@StartDate), 0)
    
        If DatePart(HH, @StartDate)<@StartHour Set @StartDate = DateAdd(hour, @StartHour, DateDiff(DAY, 0, @StartDate))  -- If Start time is less than start hour, set it to start hour
        If DatePart(HH, @StartDate)>=@EndHour+1 Set @StartDate = DateAdd(hour, @StartHour+24, DateDiff(DAY, 0, @StartDate)) -- If Start time is after end hour, set it to start hour of next day
        If DatePart(HH, @EndDate)>=@EndHour+1 Set @EndDate = DateAdd(hour, @EndHour, DateDiff(DAY, 0, @EndDate)) -- If End time is after end hour, set it to end hour
        If DatePart(HH, @EndDate)<@StartHour Set @EndDate = DateAdd(hour, @EndHour-24, DateDiff(DAY, 0, @EndDate)) -- If End time is before start hour, set it to end hour of previous day
    
        If @StartDate>@EndDate Return 0
    
        -- If Start and End is on same day
        If DateDiff(Day,@StartDate,@EndDate) <= 0
        Begin
            If Datepart(dw,@StartDate)>1 And DATEPART(dw,@StartDate)<7  -- If day is between sunday and saturday
                If (Select Count(*) From @Holidays Where HDATE=DateAdd(dd, DateDiff(dd,0,@StartDate), 0)) = 0  -- If day is not a holiday
                    If @EndDate<@StartDate Return 0 Else Set @WorkMin=DATEDIFF(MI, @StartDate, @EndDate) -- Calculate difference
                Else Return 0
            Else Return 0
        End
        Else Begin
            Declare @Partial int=1   -- Set partial day flag
            While DateDiff(Day,@StartDate,@EndDate) > 0   -- While start and end days are different
            Begin
                If Datepart(dw,@StartDate)>1 And DATEPART(dw,@StartDate)<7    --  If this is a weekday
                Begin
                    If (Select Count(*) From @Holidays Where HDATE=DateAdd(dd, DateDiff(dd,0,@StartDate), 0)) = 0  -- If this is not a holiday
                    Begin
                        If @Partial=1  -- If this is the first iteration, calculate partial time
                        Begin 
                            Set @WorkMin=@WorkMin + DATEDIFF(MI, @StartDate, DateAdd(hour, @EndHour, DateDiff(DAY, 0, @StartDate)))
                            Set @StartDate=DateAdd(hour, @StartHour+24, DateDiff(DAY, 0, @StartDate)) 
                            Set @Partial=0 
                        End
                        Else Begin      -- If this is a full day, add full minutes
                            Set @WorkMin=@WorkMin + (@EndHour-@StartHour)*60        
                            Set @StartDate = DATEADD(DD,1,@StartDate)
                        End
                    End
                    Else Set @StartDate = DATEADD(DD,1,@StartDate)  
                End
                Else Set @StartDate = DATEADD(DD,1,@StartDate)
            End
            If Datepart(dw,@StartDate)>1 And DATEPART(dw,@StartDate)<7  -- If last day is a weekday
                If (Select Count(*) From @Holidays Where HDATE=DateAdd(dd, DateDiff(dd,0,@StartDate), 0)) = 0   -- And it is not a holiday
                    If @Partial=0 Set @WorkMin=@WorkMin + DATEDIFF(MI, @StartDate, @EndDate) Else Set @WorkMin=@WorkMin + DATEDIFF(MI, DateAdd(hour, @StartHour, DateDiff(DAY, 0, @StartDate)), @EndDate)
        End 
        If @Reverse=1 Set @WorkMin=-@WorkMin
        Return @WorkMin
    End
    
    0 讨论(0)
  • 2020-11-27 05:37
    ALTER FUNCTION WorkTime_fn (@StartDate DATETIME, @FinishDate DATETIME)
    RETURNS VARCHAR(9)
    AS
    BEGIN
        DECLARE @Temp BIGINT
        SET @Temp=0
    
        DECLARE @FirstDay VARCHAR(9)
        SET @FirstDay = CONVERT(VARCHAR(9),@StartDate, 112)
    
        DECLARE @LastDay VARCHAR(9)
        SET @LastDay = CONVERT(VARCHAR(9),@FinishDate, 112)
    
        DECLARE @StartTime VARCHAR(9)
        SET @StartTime = CONVERT(VARCHAR(9),@StartDate, 108)
    
        DECLARE @FinishTime VARCHAR(9)
        SET @FinishTime = CONVERT(VARCHAR(9),@FinishDate, 108)
    
        DECLARE @WorkStart VARCHAR(9)
        SET @WorkStart = '09:30:00'
    
        DECLARE @WorkFinish VARCHAR(9)
        SET @WorkFinish = '17:30:00'
    
        IF (@StartTime<@WorkStart)
        BEGIN
            SET @StartTime = @WorkStart
        END
        IF (@FinishTime>@WorkFinish)
        BEGIN
            SET @FinishTime=@WorkFinish
        END
    
    DECLARE @CurrentDate VARCHAR(9)
        SET @CurrentDate = CONVERT(VARCHAR(9),@StartDate, 112)
        DECLARE @LastDate VARCHAR(9)
        SET @LastDate = CONVERT(VARCHAR(9),@FinishDate, 112)
    
    WHILE(@CurrentDate<=@LastDate)
    BEGIN       
    
            IF (DATEPART(dw, @CurrentDate)!=1 AND DATEPART(dw, @CurrentDate)!=7)
            BEGIN
                  IF (@CurrentDate!=@FirstDay) AND (@CurrentDate!=@LastDay)
                  BEGIN
                       SET @Temp = (@Temp + (8*60))
    
                  END
    
                  ELSE IF (@CurrentDate=@FirstDay) AND (@CurrentDate!=@LastDay)
                  BEGIN
                    SET @Temp = @Temp + DATEDIFF(MINUTE, @StartTime, @WorkFinish)
    
                  END
    
                  ELSE IF (@CurrentDate!=@FirstDay) AND (@CurrentDate=@LastDay)
                  BEGIN
                    SET @Temp = @Temp + DATEDIFF(MINUTE, @WorkStart, @FinishTime)
    
                  END
    
                  ELSE IF (@CurrentDate=@FirstDay) AND (@CurrentDate=@LastDay)
                  BEGIN
                    SET @Temp = DATEDIFF(MINUTE, @StartTime, @FinishTime)
    
                  END
    
                 END
    
    SET @CurrentDate = CONVERT(VARCHAR(9),DATEADD(day, 1, @CurrentDate),112)
    
    END
            Return @TEMP
    
    END
    
    0 讨论(0)
  • 2020-11-27 05:38
        -- =============================================
    -- Author:      Baran Kaynak
    -- Create date: 14.03.2011
    -- Description: 09:30 ile 17:30 arasındaki iş saatlerini hafta sonlarını almayarak toplar.
    -- =============================================
    CREATE FUNCTION [dbo].[WorkTime] 
    (
        @StartDate DATETIME,
        @FinishDate DATETIME
    )
    RETURNS BIGINT
    AS
    BEGIN
        DECLARE @Temp BIGINT
        SET @Temp=0
    
        DECLARE @FirstDay DATE
        SET @FirstDay = CONVERT(DATE, @StartDate, 112)
    
        DECLARE @LastDay DATE
        SET @LastDay = CONVERT(DATE, @FinishDate, 112)
    
        DECLARE @StartTime TIME
        SET @StartTime = CONVERT(TIME, @StartDate)
    
        DECLARE @FinishTime TIME
        SET @FinishTime = CONVERT(TIME, @FinishDate)
    
        DECLARE @WorkStart TIME
        SET @WorkStart = '09:30'
    
        DECLARE @WorkFinish TIME
        SET @WorkFinish = '17:30'
    
        IF (@StartTime<@WorkStart)
        BEGIN
            SET @StartTime = @WorkStart
        END
        IF (@FinishTime>@WorkFinish)
        BEGIN
            SET @FinishTime=@WorkFinish
        END
    
        DECLARE @CurrentDate DATE
        SET @CurrentDate = CONVERT(DATE, @StartDate, 112)
        DECLARE @LastDate DATE
        SET @LastDate = CONVERT(DATE, @FinishDate, 112)
    
        WHILE(@CurrentDate<=@LastDate)
        BEGIN       
            IF (DATEPART(dw, @CurrentDate)!=1 AND DATEPART(dw, @CurrentDate)!=7)
            BEGIN
                IF (@CurrentDate!=@FirstDay) AND (@CurrentDate!=@LastDay)
                BEGIN
                    SET @Temp = (@Temp + (9*60))
                END
                --IF it starts at startdate and it finishes not this date find diff between work finish and start as minutes
                ELSE IF (@CurrentDate=@FirstDay) AND (@CurrentDate!=@LastDay)
                BEGIN
                    SET @Temp = @Temp + DATEDIFF(MINUTE, @StartTime, @WorkFinish)
                END
    
                ELSE IF (@CurrentDate!=@FirstDay) AND (@CurrentDate=@LastDay)
                BEGIN
                    SET @Temp = @Temp + DATEDIFF(MINUTE, @WorkStart, @FinishTime)
                END
                --IF it starts and finishes in the same date
                ELSE IF (@CurrentDate=@FirstDay) AND (@CurrentDate=@LastDay)
                BEGIN
                    SET @Temp = DATEDIFF(MINUTE, @StartDate, @FinishDate)
                END
            END
            SET @CurrentDate = DATEADD(day, 1, @CurrentDate)
        END
    
        -- Return the result of the function
        IF @Temp<0
        BEGIN
            SET @Temp=0
        END
        RETURN @Temp
    
    END
    
    GO
    
    0 讨论(0)
  • 2020-11-27 05:39

    Here is an inline version Start/EndDateTime like 2015-03-16 09:52:24.000 Start/EndTime (businesshours) like 07:00:00 It is bulky but works in your select statement

    I will post it in Function version as well.

    Case when  <StartDate>= <EndDate> then 0
        When Convert(date,<StartDate>) = Convert(date,<EndDate>) Then 
             IIF( DATEPART(Dw,<StartDate>)  in(1,7)
                      or Convert(time,<StartDate>) > Convert(time,<EndTime>)
                      or Convert(time,<EndDate>) < Convert(time,<StartTime>),0, 
            DateDiff(S,IIF(Convert(time,<StartDate>) < Convert(time,<StartTime>),Convert(time,<StartTime>),Convert(time,<StartDate>))
                    ,IIF(Convert(time,<EndDate>) > Convert(time,<EndTime>), Convert(time,<EndTime>), Convert(time,<EndDate>))))
        when  Convert(date,<StartDate>) <> Convert(date,<EndDate>) then 
            IIF(DATEPART(Dw,<StartDate>) in(1,7) or Convert(time,<StartDate>) >  Convert(time,<EndTime>),0 ,DateDiff(S,IIF(Convert(time,<StartDate>) < Convert(time,<StartTime>),Convert(time,<StartTime>),Convert(time,<StartDate>)), Convert(time,<EndTime>)))
            + IIF(DATEPART(Dw,<EndDate>) in(1,7) or  Convert(time,<EndDate>) <  Convert(time,<StartTime>),0,DateDiff(S,Convert(time,<StartTime>),IIF(Convert(time,<EndDate>) > Convert(time,<EndTime>), Convert(time,<EndTime>), Convert(time,<EndDate>))))
        else -333
        end --as pday
    
    +IIF(DatePart(wEEk,<StartDate>)  = DatePart(wEEk,<EndDate>) 
    ,0, (DateDiff(wk,dateadd(d,-datepart(dw,<StartDate>),dateadd(ww,1,<StartDate>)),DATEADD(wk, DATEDIFF(wk, 6, <EndDate>), 6)-1) * 5)) * Datediff(S, Convert(time,<StartTime>),Convert(time,<EndTime>)) --Fullweek_days
    
    +Case When Convert(date,<StartDate>) = Convert(date,<EndDate>) then 0
          When DatePart(wEEk,<StartDate>)  <> DatePart(wEEk,<EndDate>) then
                            IIF( datepart(dw,<StartDate>) = 7,0,DateDIFF(DAY,<StartDate>+1,dateadd(d,-datepart(dw,<StartDate>),dateadd(ww,1,<StartDate>))))  -- beginFulldays
                            +IIF( datepart(dw,<EndDate>) = 1,0,DateDIFF(DAY,DATEADD(wk, DATEDIFF(wk, 6, <EndDate>), 6),<EndDate> -1))  --Endfulldays
          When DatePart(wEEk,<StartDate>)  = DatePart(wEEk,<EndDate>) then
                DateDiff(DAY,<StartDate>+1,<EndDate> ) 
        ELSE -333 END * Datediff(S, Convert(time,<StartTime>),Convert(time,<EndTime>))    
    

    Here is the Function Version:

    CREATE FUNCTION [dbo].[rsf_BusinessTime]
    (
    @startDateTime Datetime,
    @endDateTime Datetime ,
    @StartTime VarChar(12),
    @EndTime VarChar(12) )
    RETURNS BIGINT
    As
    BEGIN
    Declare @totalSeconds BigInt,
        @SecondsInDay int,
        @dayStart Time = Convert(time,@StartTime),
        @dayEnd Time =Convert(time,@EndTime),
        @SatAfterStart Datetime = dateadd(d,-datepart(dw,@startDateTime),dateadd(ww,1,@startDateTime)), 
        @Sunbeforend Datetime = DATEADD(wk, DATEDIFF(wk, 6, @endDateTime), 6) 
    
    -- This function calculates the seconds between the start and end dates provided for business hours. 
    -- It only returns the time between the @start and @end time (hour of day) of the work week. 
    -- Weekend days are removed.
    -- Holidays are not considered.  
    
    Set @SecondsInDay = Datediff(S, @dayStart,@dayEnd) 
    
    
    Set @totalSeconds = 
     --first/last/sameday
        Case when  @startDateTime= @endDateTime then 0
        When Convert(date,@startDateTime) = Convert(date,@endDateTime) Then 
             IIF( DATEPART(Dw,@startDateTime)  in(1,7)
                      or Convert(time,@startDateTime) > @dayEnd
                      or Convert(time,@endDateTime) < @dayStart,0, 
            DateDiff(S,IIF(Convert(time,@startDateTime) < @dayStart,@dayStart,Convert(time,@startDateTime))
                    ,IIF(Convert(time,@endDateTime) > @dayEnd, @dayEnd, Convert(time,@endDateTime))))
        when  Convert(date,@startDateTime) <> Convert(date,@endDateTime) then 
            IIF(DATEPART(Dw,@startDateTime) in(1,7) or Convert(time,@startDateTime) >  @dayEnd,0 ,DateDiff(S,IIF(Convert(time,@startDateTime) < @dayStart,@dayStart,Convert(time,@startDateTime)), @dayEnd))
            + IIF(DATEPART(Dw,@endDateTime) in(1,7) or  Convert(time,@endDateTime) <  @dayStart,0,DateDiff(S,@dayStart,IIF(Convert(time,@endDateTime) > @dayEnd, @dayEnd, Convert(time,@endDateTime))))
        else -333
        end --as pday
    
    +IIF(DatePart(wEEk,@startDateTime)  = DatePart(wEEk,@endDateTime)   
    ,0, (DateDiff(wk,@SatAfterStart,@Sunbeforend-1) * 5)) * @SecondsInDay --Fullweek_days
    
    +Case When Convert(date,@startDateTime) = Convert(date,@endDateTime) then 0
          When DatePart(wEEk,@startDateTime)  <> DatePart(wEEk,@endDateTime) then
                            IIF( datepart(dw,@startDateTime) = 7,0,DateDIFF(DAY,@startDateTime+1,@SatAfterStart))  -- beginFulldays
                            +IIF( datepart(dw,@endDateTime) = 1,0,DateDIFF(DAY,@Sunbeforend,@endDateTime -1))  --Endfulldays
          When DatePart(wEEk,@startDateTime)  = DatePart(wEEk,@endDateTime) then
                DateDiff(DAY,@startDateTime+1,@endDateTime ) 
        ELSE -333 END * @SecondsInDay
    
    
    Return @totalSeconds
    END 
    
    0 讨论(0)
  • 2020-11-27 05:40

    What do you think about this solution?

    Without using loop "While".

    create function dbo.WorkingHoursBetweenDates ( @StartDate datetime, @EndDate datetime, @StartTime time, @EndTime time )
    returns decimal ( 10, 2 )
    as
    begin
    
      return
        case
          when @EndTime < @StartTime or @EndDate < @StartDate then
            0
          else
            round
            ( ( dbo.WorkingDaysBetweenDates(@StartDate, @EndDate) -
                ( dbo.WorkingDaysBetweenDates(@StartDate, @StartDate) *
                  case
                    when cast ( @StartDate as time ) > @EndTime then
                      1
                    else
                      datediff
                      ( mi,
                        @StartTime
                        , case
                            when @StartTime > cast ( @StartDate as time ) then
                              @StartTime
                            else
                              cast ( @StartDate as time )
                          end
                      ) /
                      ( datediff ( mi, @StartTime, @EndTime ) + 0.0 )
                  end
                ) -
                ( dbo.WorkingDaysBetweenDates(@EndDate, @EndDate) *
                  case
                    when cast ( @EndDate as time ) < @StartTime then
                      1
                    else
                      datediff
                      ( mi,
                        case
                          when @EndTime < cast ( @EndDate as time ) then
                            @EndTime
                          else
                            cast ( @EndDate as time )
                        end,
                        @EndTime
                      ) /
                      ( datediff ( mi, @StartTime, @EndTime ) + 0.0 )
                  end
                )
              ) *
              ( datediff ( mi, @StartTime, @EndTime ) / 60.0 ), 2
            )
        end
    
    end
    ------
    
    create function dbo.WorkingDaysBetweenDates ( @StartDate date, @EndDate date )
    returns int
    as
    begin
    
      return 
        ( datediff(dd, @StartDate, @EndDate) + 1 ) -
        ( datediff(wk, @StartDate, @EndDate) * 2 ) -
        ( case when datename(dw, @StartDate) = 'Sunday' then 1 else 0 end ) -
        ( case when datename(dw, @EndDate) = 'Saturday' then 1 else 0 end ) -
        ( select
            count ( 1 )
          from
            dbo.Tb_Holidays
          where
            HDate between @StartDate and @EndDate
            and datename(dw, HDate) not in ( 'Sunday', 'Saturday' )
        )
    
    end
    
    0 讨论(0)
  • 2020-11-27 05:42

    The question says that public holidays should not be considered, so this answer does just that - calculates business hours taking weekends into account, but ignoring possible public holidays.

    It also assumes that the given start and end date/times are during the business hours.

    With this assumption the code doesn't care about the time when the business day starts or ends, it cares only about the total number of business hours per day. In your example, there are 8 business hours between 09:00 and 17:00. It doesn't have to be a whole number. The formula below calculates it with one minute precision, but it is trivial to make it one second or any other precision.

    If you need to take public holidays into account you'd need to have a separate table which would list dates for public holidays, which may differ from year to year and from state to state or country to country. The main formula may stay the same, but you'd need to subtract from its result hours for public holidays that fall within the given range of dates.

    The formula

    SELECT
        DATEDIFF(minute, StartDT, EndDT) / 60.0
        - DATEDIFF(day,  StartDT, EndDT) * 16
        - DATEDIFF(week, StartDT, EndDT) * 16 AS BusinessHours
    FROM T
    

    To understand how it works let's create a table with some sample data that covers various cases:

    DECLARE @T TABLE (StartDT datetime2(0), EndDT datetime2(0));
    
    INSERT INTO @T VALUES
    ('2012-03-05 09:00:00', '2012-03-05 15:00:00'), -- simple part of the same day
    ('2012-03-05 10:00:00', '2012-03-06 10:00:00'), -- full day across the midnight
    ('2012-03-05 11:00:00', '2012-03-06 10:00:00'), -- less than a day across the midnight
    ('2012-03-05 10:00:00', '2012-03-06 15:00:00'), -- more than a day across the midnight
    ('2012-03-09 16:00:00', '2012-03-12 10:00:00'), -- over the weekend, less than 7 days
    ('2012-03-06 16:00:00', '2012-03-15 10:00:00'), -- over the weekend, more than 7 days
    ('2012-03-09 16:00:00', '2012-03-19 10:00:00'); -- over two weekends
    

    The query

    SELECT
        StartDT, 
        EndDT,
        DATEDIFF(minute, StartDT, EndDT) / 60.0
        - DATEDIFF(day,  StartDT, EndDT) * 16
        - DATEDIFF(week, StartDT, EndDT) * 16 AS BusinessHours
    FROM @T;
    

    produces the following result:

    +---------------------+---------------------+---------------+
    |       StartDT       |        EndDT        | BusinessHours |
    +---------------------+---------------------+---------------+
    | 2012-03-05 09:00:00 | 2012-03-05 15:00:00 |  6.000000     |
    | 2012-03-05 10:00:00 | 2012-03-06 10:00:00 |  8.000000     |
    | 2012-03-05 11:00:00 | 2012-03-06 10:00:00 |  7.000000     |
    | 2012-03-05 10:00:00 | 2012-03-06 15:00:00 | 13.000000     |
    | 2012-03-09 16:00:00 | 2012-03-12 10:00:00 |  2.000000     |
    | 2012-03-06 16:00:00 | 2012-03-15 10:00:00 | 50.000000     |
    | 2012-03-09 16:00:00 | 2012-03-19 10:00:00 | 42.000000     |
    +---------------------+---------------------+---------------+
    

    It works, because in SQL Server DATEDIFF returns the count of the specified datepart boundaries crossed between the specified startdate and enddate.

    Each day has 8 business hours. I calculate total number of hours between two dates, then subtract the number of midnights multiplied by 16 non-business hours per day, then subtract the number of weekends multiplied by 16 (8+8 business hours for Sat+Sun).

    0 讨论(0)
提交回复
热议问题