Calculate business hours between two dates

前端 未结 14 1119
被撕碎了的回忆
被撕碎了的回忆 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:26

    Another way of thinking, the below function work correctly if your first day of week is Monday otherwise you should change related lines including (6,7) to your local weekend's days

    create function fn_worktime(@Datetime1 DateTime,@Datetime2 DateTime)
    Returns BigInt
    as
    Begin
        Declare 
                @Date1 Date, 
                @Date2 Date,
                @DateIndex Date,
                @minutes int,
                @lastDayMinutes int,
                @StartTime int , --in minutes
                @FinishTime int ,--in minutes
                @WorkDayLong int --in minutes
    
        Set @StartTime  =8 * 60 + 30 -- 8:30
        Set @FinishTime =17* 60 + 30 -- 17:30
        Set @WorkDayLong =@FinishTime - @StartTime  
    
        Set @Date1 = Convert(Date,@DateTime1)
        Set @Date2 = Convert(Date,@DateTime2)
        Set @minutes=DateDiff(minute,@DateTime1,DateAdd(MINUTE,@FinishTime ,convert(DateTime,@Date1)))
        if @minutes<0 OR DatePart(dw,@Date1) in (6,7)  -- you can even check holdays here. '(6 Saturday,7 Sunday) according to SET DATEFIRST 1'
            Set @minutes=0
    
        Set @DateIndex=DateAdd(day,1,@Date1)
        While @DateIndex<@Date2
        Begin
            if DatePart(dw,@DateIndex) not in (6,7)  -- you can even check holdays here. '(6 Saturday,7 Sunday) according to SET DATEFIRST 1'
                set @minutes=@minutes+@WorkDayLong 
            Set @DateIndex=DateAdd(day,1,@DateIndex)
        End
        if DatePart(dw,@DateIndex) not in (6,7)  -- you can even check holdays here
        Begin
            set @lastDayMinutes=DateDiff(minute,DateAdd(MINUTE ,@StartTime ,convert(DateTime,@Date2)),@DateTime2)
            if @lastDayMinutes>@WorkDayLong 
                set @lastDayMinutes=@WorkDayLong 
            if @Date1<>@Date2   
                set @minutes=@minutes+@lastDayMinutes
            Else
                Set @minutes=@minutes+@lastDayMinutes-@WorkDayLong 
    
        End
        return @minutes
    End
    
    0 讨论(0)
  • 2020-11-27 05:29

    I have actually done this before, taking into account all the variables (weekends, holidays, etc) for business hours is very difficult, I think this task is best done outside SQL

    0 讨论(0)
  • 2020-11-27 05:30

    Baran's answer fixed and modified for SQL 2005

    SQL 2008 and above:

    -- =============================================
    -- Author:      Baran Kaynak (modified by Kodak 2012-04-18)
    -- 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:00'
    
        DECLARE @WorkFinish TIME
        SET @WorkFinish = '17:00'
    
        DECLARE @DailyWorkTime BIGINT
        SET @DailyWorkTime = DATEDIFF(MINUTE, @WorkStart, @WorkFinish)
    
        IF (@StartTime<@WorkStart)
        BEGIN
            SET @StartTime = @WorkStart
        END
        IF (@FinishTime>@WorkFinish)
        BEGIN
            SET @FinishTime=@WorkFinish
        END
        IF (@FinishTime<@WorkStart)
        BEGIN
            SET @FinishTime=@WorkStart
        END
        IF (@StartTime>@WorkFinish)
        BEGIN
            SET @StartTime = @WorkFinish
        END
    
        DECLARE @CurrentDate DATE
        SET @CurrentDate = @FirstDay
        DECLARE @LastDate DATE
        SET @LastDate = @LastDay
    
        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 + @DailyWorkTime
                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, @StartTime, @FinishTime)
                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
    

    SQL 2005 and below:

    -- =============================================
    -- Author:      Baran Kaynak (modified by Kodak 2012-04-18)
    -- 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 DATETIME
        SET @FirstDay = DATEADD(dd, 0, DATEDIFF(dd, 0, @StartDate))
    
        DECLARE @LastDay DATETIME
        SET @LastDay = DATEADD(dd, 0, DATEDIFF(dd, 0, @FinishDate))
    
        DECLARE @StartTime DATETIME
        SET @StartTime = @StartDate - DATEADD(dd, DATEDIFF(dd, 0, @StartDate), 0)
    
        DECLARE @FinishTime DATETIME
        SET @FinishTime = @FinishDate - DATEADD(dd, DATEDIFF(dd, 0, @FinishDate), 0)
    
        DECLARE @WorkStart DATETIME
        SET @WorkStart = CONVERT(DATETIME, '09:00', 8)
    
        DECLARE @WorkFinish DATETIME
        SET @WorkFinish = CONVERT(DATETIME, '17:00', 8)
    
        DECLARE @DailyWorkTime BIGINT
        SET @DailyWorkTime = DATEDIFF(MINUTE, @WorkStart, @WorkFinish)
    
        IF (@StartTime<@WorkStart)
        BEGIN
            SET @StartTime = @WorkStart
        END
        IF (@FinishTime>@WorkFinish)
        BEGIN
            SET @FinishTime=@WorkFinish
        END
        IF (@FinishTime<@WorkStart)
        BEGIN
            SET @FinishTime=@WorkStart
        END
        IF (@StartTime>@WorkFinish)
        BEGIN
            SET @StartTime = @WorkFinish
        END
    
        DECLARE @CurrentDate DATETIME
        SET @CurrentDate = @FirstDay
        DECLARE @LastDate DATETIME
        SET @LastDate = @LastDay
    
        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 + @DailyWorkTime
                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, @StartTime, @FinishTime)
                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
    
    0 讨论(0)
  • 2020-11-27 05:30

    The first step would be to calculate working days, as shown in the script below:

    DECLARE @TotalWorkDays INT, @TotalTimeDiff DECIMAL(18, 2), @DateFrom DATETIME, @DateTo DATETIME;
    SET @DateFrom = '2017-06-05 11:19:11.287';
    SET @DateTo = '2017-06-07 09:53:14.750';
    
    SET @TotalWorkDays = DATEDIFF(DAY, @DateFrom, @DateTo)
        -(DATEDIFF(WEEK, @DateFrom, @DateTo) * 2)
       -CASE
                                        WHEN DATENAME(WEEKDAY, @DateFrom) = 'Sunday'
                                        THEN 1
                                        ELSE 0
                                    END+CASE
                                            WHEN DATENAME(WEEKDAY, @DateTo) = 'Saturday'
                                            THEN 1
                                            ELSE 0
                                        END;
    

    The second step involves getting a difference in seconds between the two dates and converting that difference into hours by dividing by 3600.0 as shown in this following script:

    SET @TotalTimeDiff =
    (
        SELECT DATEDIFF(SECOND,
                       (
                           SELECT CONVERT(TIME, @DateFrom)
                       ),
                       (
                           SELECT CONVERT(TIME, @DateTo)
                       )) / 3600.0
    );
    

    The last part involves multiplying the output the first step above by 24 (total number of hours in a day) and then later adding that to the output of the second step:

    SELECT(@TotalWorkDays * 24.00) + @TotalTimeDiff;
    

    Finally, the complete script that can be used to create a user defined function for calculating working hours is shown below:

    CREATE FUNCTION [dbo].[fn_GetTotalWorkingHours]
    (
        @DateFrom Datetime,
        @DateTo Datetime
    )
    RETURNS DECIMAL(18,2)
    AS
    BEGIN
    
    DECLARE @TotalWorkDays INT, @TotalTimeDiff DECIMAL(18, 2)
    
    SET @TotalWorkDays = DATEDIFF(DAY, @DateFrom, @DateTo)
        -(DATEDIFF(WEEK, @DateFrom, @DateTo) * 2)
       -CASE
                                        WHEN DATENAME(WEEKDAY, @DateFrom) = 'Sunday'
                                        THEN 1
                                        ELSE 0
                                    END+CASE
                                            WHEN DATENAME(WEEKDAY, @DateTo) = 'Saturday'
                                            THEN 1
                                            ELSE 0
                                        END;
    SET @TotalTimeDiff =
    (
        SELECT DATEDIFF(SECOND,
                       (
                           SELECT CONVERT(TIME, @DateFrom)
                       ),
                       (
                           SELECT CONVERT(TIME, @DateTo)
                       )) / 3600.0
    );
    
    RETURN  (SELECT(@TotalWorkDays * 24.00) + @TotalTimeDiff)
    
    END
    GO
    

    The complete method is descibed in this article: https://www.sqlshack.com/how-to-calculate-work-days-and-hours-in-sql-server/     

    0 讨论(0)
  • 2020-11-27 05:31
    DECLARE @StartDate DATETIME
    DECLARE @EndDate DATETIME
    DECLARE @WORKINGHOURS INT
    DECLARE @Days INT
    SET @StartDate = '2010/01/01'
    SET @EndDate = '2010/04/01'
    
    --number of working days
    SELECT @Days = 
       (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)
    
    --8 hours a day    
    SET @WORKINGHOURS = @Days * 8 
    
    SELECT @WORKINGHOURS
    
    0 讨论(0)
  • 2020-11-27 05:33

    Here's an alternative solution, without the use of a function. Note that this relies on the existence of a numbers table, populated with at least the maximum number of days the tasks you're tracking may take.

    This doesn't take public holidays into account. If you don't work weekends, setting the opening and closing times to midnight in the @OpeningHours table variable should do the job.

    I've tested this against 8500 rows of 'real world' data and found it to be performant.

    DECLARE @OpeningHours TABLE ([DayOfWeek] INTEGER, OpeningTime TIME(0), ClosingTime TIME(0));
    
    INSERT
        @OpeningHours ([DayOfWeek], OpeningTime, ClosingTime)
    VALUES
        (1, '10:00', '16:00') -- Sun
        , (2, '06:30', '23:00') -- Mon
        , (3, '06:30', '23:00') -- Tue
        , (4, '06:30', '23:00') -- Wed
        , (5, '06:30', '23:00') -- Thu
        , (6, '06:30', '23:00') -- Fri
        , (7, '08:00', '20:00'); -- Sat
    
    DECLARE @Tasks TABLE ([Description] VARCHAR(50), CreatedDateTime DATETIME, CompletedDateTime DATETIME);
    
    INSERT
        @Tasks ([Description], CreatedDateTime, CompletedDateTime)
    VALUES
        ('Make tea', '20170404 10:00', '20170404 10:12')
        , ('Make coffee', '20170404 23:35', '20170405 06:32')
        , ('Write complex SQL query', '20170406 00:00', '20170406 23:32')
        , ('Rewrite complex SQL query', '20170406 23:50', '20170410 10:50');
    
    SELECT
        WorkingMinutesToRespond =
            SUM(CASE WHEN CAST(Tasks.CreatedDateTime AS DATE) = CAST(Tasks.CompletedDateTime AS DATE) THEN
            CASE WHEN CAST(Tasks.CreatedDateTime AS TIME) < OpeningHours.OpeningTime THEN
                -- Task created before opening time
                DATEDIFF(MINUTE, OpeningHours.OpeningTime, CAST(Tasks.CompletedDateTime AS TIME))
            ELSE
                DATEDIFF(MINUTE, Tasks.CreatedDateTime, Tasks.CompletedDateTime)
            END
        ELSE
            CASE WHEN Tasks.CoveredDate = CAST(Tasks.CreatedDateTime AS DATE) THEN 
                -- This is the day the task was created
                CASE WHEN CAST(Tasks.CreatedDateTime AS TIME(0)) > OpeningHours.ClosingTime THEN
                    0 -- after working hours
                ELSE
                    -- during or before working hours
                    CASE WHEN CAST(Tasks.CreatedDateTime AS TIME(0)) < OpeningHours.OpeningTime THEN
                        -- before opening time; take the whole day into account
                        DATEDIFF(MINUTE, OpeningHours.OpeningTime, OpeningHours.ClosingTime)
                    ELSE
                        -- during opening hours; take part of the day into account
                        DATEDIFF(MINUTE, CAST(Tasks.CreatedDateTime AS TIME), OpeningHours.ClosingTime)
                    END
                END
            ELSE
                -- This is the day the task was completed
                CASE WHEN Tasks.CoveredDate = CAST(Tasks.CompletedDateTime AS DATE) THEN 
                    CASE WHEN CAST(Tasks.CompletedDateTime AS TIME(0)) < OpeningHours.OpeningTime THEN
                        0 -- before working hours (unlikely to occur)
                    ELSE
                        -- during or after working hours
                        CASE WHEN CAST(Tasks.CompletedDateTime AS TIME(0)) > OpeningHours.ClosingTime THEN
                            -- after closing time (also unlikely); take the whole day into account
                            DATEDIFF(MINUTE, OpeningHours.OpeningTime, OpeningHours.ClosingTime)
                        ELSE
                            -- during opening hours; take part of the day into account
                            DATEDIFF(MINUTE, OpeningHours.OpeningTime, CAST(Tasks.CompletedDateTime AS TIME(0)))
                        END
                    END
            ELSE
                DATEDIFF(MINUTE, OpeningHours.OpeningTime, OpeningHours.ClosingTime)
            END 
            END
        END)
        , Tasks.Description
        , Tasks.CreatedDateTime
        , Tasks.CompletedDateTime
    FROM
        (
            SELECT
            Tasks.Description
            , Tasks.CreatedDateTime
            , Tasks.CompletedDateTime
            , CoveredDate = CAST(DATEADD(DAY, Numbers.Number, Tasks.CreatedDateTime) AS DATE)
        FROM
            @Tasks Tasks
            INNER JOIN (SELECT * FROM Numbers WHERE Number >= 0) Numbers ON DATEDIFF(DAY, Tasks.CreatedDateTime, Tasks.CompletedDateTime) >= Numbers.Number
    ) Tasks
    INNER JOIN @OpeningHours OpeningHours ON DATEPART(WEEKDAY, Tasks.CoveredDate) = OpeningHours.[DayOfWeek]
    GROUP BY
        Tasks.Description
        , Tasks.CreatedDateTime
        , Tasks.CompletedDateTime
    ORDER BY
        Tasks.CompletedDateTime;
    
    0 讨论(0)
提交回复
热议问题