Skip to main content
cancel
Showing results for 
Search instead for 
Did you mean: 

Get certified in Microsoft Fabric—for free! For a limited time, get a free DP-600 exam voucher to use by the end of 2024. Register now

Reply
Heremion
Frequent Visitor

Date / Time difference excluding weekends and factoring working hours

Hello,

 

After seeing this following post and in order to not reopen it, I tried the function explained at the bottom but it returns sometimes suspicious values. 

https://community.fabric.microsoft.com/t5/Power-Query/Date-Time-difference-excluding-weekends-and-fa...

  

Remind, here's the function : 

let
    // calculates a duration between two timestamps in working hours
    fnDurationWorkingHours = (StartDateTime as datetime, EndDateTime as datetime) as duration =>
        let
            // start and end date
            StartDate = Date.From(StartDateTime),
            EndDate = Date.From(EndDateTime),

            // start and end time
            StartTime = Time.From(StartDateTime),
            EndTime = Time.From(EndDateTime),

            // end of the shift on the first day and start of the shift on the last day
            ShiftEndOnStartDate = #time(17, 0, 0),
            ShiftStartOnEndDate = #time(8, 0, 0),

            // working days are from Mon=0 to Fri=4
            fnIsWorkingDay = (dt as date) as logical => Date.DayOfWeek(dt, Day.Monday) < 5,

            // generate whole days between StartDateTime and EndDateTime
            DaysBetween = List.Generate(
                () => Date.AddDays(StartDate, 1),
                each _ < EndDate,
                each Date.AddDays(_, 1)
            ),

            // select only working days
            WorkingDaysBetween = List.Select(DaysBetween, fnIsWorkingDay),

            // get minimum of two times
            fnMin = (first as time, second as time) as time =>
                if first > second then second else first,

            // get maximum of two times
            fnMax = (first as time, second as time) as time =>
                if first > second then first else second,

            // duration on the first day
            StartDateWorkingHours = 
                if not fnIsWorkingDay(StartDate) then
                    #duration(0, 0, 0, 0)
                else if ShiftEndOnStartDate < StartTime then
                    #duration(0, 0, 0, 0)
                else
                    ShiftEndOnStartDate - fnMax(StartTime, ShiftStartOnEndDate),

            // duration on the days between
            WorkingHoursBetween = #duration(0, List.Count(WorkingDaysBetween) * 9, 0, 0),

            // duration on the last day
            EndDateWorkingHours = 
                if not fnIsWorkingDay(EndDate) then
                    #duration(0, 0, 0, 0)
                else if EndTime < ShiftStartOnEndDate then
                    #duration(0, 0, 0, 0)
                else
                    fnMin(EndTime, ShiftEndOnStartDate) - ShiftStartOnEndDate,

            // sum it up
            Result = 
                if StartDateTime > EndDateTime then 
                    #duration(0, 0, 0, 0)
                else if StartDate = EndDate and not fnIsWorkingDay(StartDate) then
                    #duration(0, 0, 0, 0)
                else if StartDate = EndDate then
                    fnMin(EndTime, ShiftEndOnStartDate) - fnMax(StartTime, ShiftStartOnEndDate)
                else 
                    StartDateWorkingHours +
                    WorkingHoursBetween +
                    EndDateWorkingHours
        in
            Result

But for example, when I have in start and end time these

Heremion_0-1726677039481.png

There's no 6 days and 20h betweend 19/09/2022 and 13/10/2022, even if we exclude weekends.

 

Some other examples :

Heremion_1-1726677146574.png

Heremion_2-1726677160854.png

 

 

Could you help me please ?

 

Thank you!

1 ACCEPTED SOLUTION
AlienSx
Super User
Super User

so complicated...

(from, to, optional st, optional et) => 
    [s_time = st ?? #time(8, 0, 0), 
    e_time = et ?? #time(17, 0, 0),
    start = List.Min({from, to}),
    end = List.Max({from, to}),
    start_date = Date.From(start),
    end_date = Date.From(end),
    gen = List.Generate(
        () => [c = start_date, sod = List.Max({start, start_date & s_time}), eod = List.Min({end, c & e_time})], 
        (x) => x[c] <= end_date, 
        (x) => [c = Date.AddDays(x[c], 1), sod = c & s_time, eod = List.Min({end, c & e_time})],
        (x) => if Date.DayOfWeek(x[c], Day.Monday) > 4 then null else x[eod] - x[sod]
    ), 
    result = if List.NonNullCount({from, to}) < 2 then null else List.Sum(gen)][result]

View solution in original post

22 REPLIES 22
Heremion
Frequent Visitor

Thanks to both of you helping me to resolve my problems!!! 🙂

AlienSx
Super User
Super User

so complicated...

(from, to, optional st, optional et) => 
    [s_time = st ?? #time(8, 0, 0), 
    e_time = et ?? #time(17, 0, 0),
    start = List.Min({from, to}),
    end = List.Max({from, to}),
    start_date = Date.From(start),
    end_date = Date.From(end),
    gen = List.Generate(
        () => [c = start_date, sod = List.Max({start, start_date & s_time}), eod = List.Min({end, c & e_time})], 
        (x) => x[c] <= end_date, 
        (x) => [c = Date.AddDays(x[c], 1), sod = c & s_time, eod = List.Min({end, c & e_time})],
        (x) => if Date.DayOfWeek(x[c], Day.Monday) > 4 then null else x[eod] - x[sod]
    ), 
    result = if List.NonNullCount({from, to}) < 2 then null else List.Sum(gen)][result]

Hello,

 

I tried your solution but it seems to have problems in it :

 

Heremion_0-1726919744588.png

 

Some results are wrong (between 3 and 17, there's not only 3 days 😞 )

that is DURATION where 1 day = 24 hours. If you want solution in (working) hours then apply Duration.TotalHours. 

Great solution. Thanks for providing.


Note: Check this link to learn how to use my query.
Check this link if you don't know how to provide sample data.

dufoq3
Super User
Super User

Hi @Heremion, I also replied to that post. Have you tried my soution? If you want help provide sample data in usable format (read note below my post if you don't know how) and expected resuld based on sample data please.


Note: Check this link to learn how to use my query.
Check this link if you don't know how to provide sample data.

Hi,

 

I tried it but it seems I got an issue with start and end because you use #time... instead I could reuse existing field with datetime datatype.

I tried to replace them with #"début" and #"fin" or [début] and [fin] but I got an error with "field not recognized"

Provide sample data as I mentioned above please.


Note: Check this link to learn how to use my query.
Check this link if you don't know how to provide sample data.

You'll find a sample below

 

débutdateECDiffECdateTdateF
19/09/2022 08:00:0019/09/2022 14:09:120.06:09:1220/01/2023 17:01:5719/09/2022 14:09:57
03/09/2024 08:00:0017/09/2024 08:09:373.18:09:37null17/09/2024 08:09:09
12/09/2024 08:00:0012/09/2024 14:09:350.06:09:35null12/09/2024 16:09:05
06/09/2024 08:00:0006/09/2024 11:09:530.03:09:5306/09/2024 14:09:4506/09/2024 14:09:29
19/09/2022 08:00:0013/10/2022 10:10:096.20:10:0903/08/2023 15:08:1203/08/2023 15:08:12
06/09/2024 08:00:0006/09/2024 10:09:450.02:09:4510/09/2024 11:09:3110/09/2024 11:09:59
19/09/2022 08:00:00nullnull20/09/2022 09:09:1820/09/2022 09:09:18
19/09/2022 08:00:00nullnullnull23/09/2022 09:09:17
09/09/2024 08:00:0009/09/2024 08:09:090.00:09:0909/09/2024 15:09:3709/09/2024 15:09:16
19/09/2022 08:00:0019/09/2022 15:09:190.07:09:1919/09/2022 15:09:1119/09/2022 15:09:01
09/09/2024 08:00:0009/09/2024 09:09:370.01:09:3709/09/2024 15:09:1409/09/2024 15:09:55
nullnullnull02/09/2024 20:09:5402/09/2024 20:09:54
19/09/2022 08:00:0019/09/2022 14:09:440.06:09:4416/02/2023 15:02:0420/09/2022 10:09:00
09/09/2024 08:00:0009/09/2024 09:09:420.01:09:4209/09/2024 15:09:4409/09/2024 15:09:31
09/09/2024 08:00:0009/09/2024 09:09:050.01:09:0509/09/2024 15:09:5609/09/2024 15:09:42
20/09/2022 08:00:0020/09/2022 15:09:300.07:09:3020/09/2022 17:09:3420/09/2022 17:09:52
21/09/2022 08:00:00nullnull29/11/2022 09:11:0329/11/2022 09:11:55
21/09/2022 08:00:0027/09/2022 15:09:541.19:09:5428/09/2022 10:09:01null
11/09/2024 08:00:0011/09/2024 16:09:480.08:09:4811/09/2024 17:09:0811/09/2024 17:09:53
03/09/2024 08:00:00nullnullnull03/09/2024 09:09:22
11/09/2024 08:00:0011/09/2024 16:09:510.08:09:5111/09/2024 17:09:3911/09/2024 17:09:31
21/09/2022 08:00:0021/09/2022 15:09:200.07:09:2022/11/2022 16:11:5529/09/2022 18:09:00
03/09/2024 08:00:00nullnullnull03/09/2024 10:09:22
11/09/2024 08:00:0011/09/2024 17:09:430.09:00:0012/09/2024 10:09:1212/09/2024 10:09:56
11/09/2024 08:00:0011/09/2024 17:09:470.09:00:0012/09/2024 10:09:0412/09/2024 10:09:51
21/09/2022 08:00:0021/09/2022 17:09:430.09:00:0021/09/2022 17:09:3721/09/2022 17:09:30
11/09/2024 08:00:0011/09/2024 17:09:210.09:00:0012/09/2024 11:09:3012/09/2024 11:09:22
11/09/2024 08:00:0011/09/2024 17:09:290.09:00:0012/09/2024 11:09:0112/09/2024 11:09:53
21/09/2022 08:00:00nullnull21/09/2022 18:09:5321/09/2022 18:09:43

 

I try to get three fieds :

* diff between début and dateEC

* diff between début and dateT

* diff between début and dateF

excluding weekends

 

Thx for your help

 

Difference in days?


Note: Check this link to learn how to use my query.
Check this link if you don't know how to provide sample data.

Difference in time. After I can convert in days if I have to.

 

Examples :

18/09/2024 08:00:00 -> 18/09/2024 10:00:00 = 02:00:00

18/09/2024 08:00:00 -> 19/09/2024 10:00:00 = 11:00:00 according working between 08:00:00 and 17:00:00 each day (so 08:00:00 -> 17:00:00 + 08:00:00 -> 10:00:00)

 

Check this. Ignore first 4 rows (I've added them just for test purpose).

You can edit shiftStart and shiftEnd in Helper step.

EDIT: I've added option where you can decide whether you want to include or exclude weekends:

dufoq3_1-1726741956920.png

 

Output

dufoq3_0-1726741112400.png

 

let
    Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("nVZRbuwwCLxKtd+rLGA7iX2Vam9Q9e/d/zng2NQmaVJppRUTDMMATj4/H7i+IL4IiD5gTgESwOP5IKgorskXtPzez3wsmsfcjWPxT8cQkisOGqWKHmRb89PxmE+ZBlJGYcp17MZWPm4u7gOXBJjCYh3M6JYGdvr+R5rlBxqT22K4CZvx/e/ry/KEKPTJjNtQYeGCos/GHld58kMIwne24ioUkatzEtc1Y+5S+2ChFM/Udy+E2sr8y8U+H/NEzdj0XIv6IeWz0iADvVYNNKoTUDUQuoIdWmg4q6YoXf7U1nAXE64H6NWIe2DXhyiTF83a4zhPXDs0o7lsYpZ5HFCcL+6ReJc0SzVGF7RQwIvVxEZ1Ajzh7S00yPibCkPbFWKZgj9Ab1ws3qvNZAPzWFIb4jyMvrttpUdwRxBPShAx+tK9KYi7pTsElUaMQeHZTC6LqvegpdG1CylQU+R6F0F70RgNJQ1eWNT4Qqz7tO25s9AyMHZEWjriPDE4YawGrX1fcefAU4SW7gqVW9uvIshaDe3CpYOJ5jv7+PVkb4Hrek50h2lAxZSNgZOLJopnQmMnNOkJYYOodi4zkc5xP9VnhVqrPwkiHbwsCJPz5RUazbc31O+NAQ3zrTTLr2n4nhnTXNX9oJrBha/jEYU71RCeVYP1WhjQe72h+GsaXtcBLWt16Z7BbgL5M2pAs7Dv938=", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [début = _t, dateEC = _t, DiffEC = _t, dateT = _t, dateF = _t]),
    ChangedType = Table.TransformColumnTypes(Source,{{"dateF", type datetime}, {"dateT", type datetime}, {"DiffEC", type duration}, {"dateEC", type datetime}, {"début", type datetime}}),
    Helper = [ shiftStart = #time(8,0,0),
    shiftEnd = #time(17,0,0),
    includeWeekends = "no", //type "yes" if you want to include weekends. Type "no" if you want to exclude weekends
    workingHours = shiftEnd - shiftStart ],
    fn_WorkingDuration = 
        (previousStep as table, start as text, end as text, optional colName as text)=>
        let
            // Ad_StartColumn = Table.DuplicateColumn(ChangedType, "début", "S"),
            // Ad_EndColumn = Table.DuplicateColumn(Ad_StartColumn, "dateEC", "E"),
            Ad_StartColumn = Table.DuplicateColumn(previousStep, start, "S"),
            Ad_EndColumn = Table.DuplicateColumn(Ad_StartColumn, end, "E"),

            Ad_SDate = Table.AddColumn(Ad_EndColumn, "S Date", each DateTime.Date([S]), type date),
            Ad_STime = Table.AddColumn(Ad_SDate, "S Time", each DateTime.Time([S]), type time),
            Ad_EDate = Table.AddColumn(Ad_STime, "E Date", each DateTime.Date([E]), type date),
            Ad_ETime = Table.AddColumn(Ad_EDate, "E Time", each DateTime.Time([E]), type time),
            // Weekends excluded
             Ad_WorkingDays = Table.AddColumn(Ad_ETime, "Working Days", each try 
                [ a = List.Dates([S Date], Duration.TotalDays([E Date] - [S Date]) +1, #duration(1,0,0,0)), //weekends included
                  b = List.Select(a, (x)=> Date.DayOfWeek(x, Day.Monday) < 5), //weekends excluded
                  c = if Text.Trim(Text.Lower(Helper[includeWeekends])) = "yes" then a else b
                ][c] otherwise null, type list),
            // [S Date] and [E Date] excluded
            Ad_FullDaysHours = Table.AddColumn(Ad_WorkingDays, "Full Days Hours", each try
                [ a = List.RemoveItems([Working Days], { [S Date], [E Date]}),
                b = Helper[workingHours] * List.Count(a)
                ][b] otherwise null, type duration),
            Ad_FirstDayHours = Table.AddColumn(Ad_FullDaysHours, "Fist Day Hours", each try if [S Date] = List.First([Working Days])
                then (if [S Date] = [E Date] then List.Min({ [E Time], Helper[shiftEnd] }) else Helper[shiftEnd]) - List.Max({ [S Time], Helper[shiftStart] })        
                else #duration(0,0,0,0) otherwise null, type duration),
            Ad_LastDayHours = Table.AddColumn(Ad_FirstDayHours, "Last Day Hours", each try if [S Date] = [E Date] then #duration(0,0,0,0) else if [E Date] = List.Last([Working Days])
                then Helper[workingHours]        
                else #duration(0,0,0,0) otherwise null, type duration),

            // Ad_TotalWorkingDuration = Table.AddColumn(Ad_LastDayHours, "Total Working Duration", each [Full Days Hours] + [Fist Day Hours] + [Last Day Hours], type duration),
            Ad_TotalWorkingDuration = Table.AddColumn(Ad_LastDayHours, if colName <> null then colName else "Total Working Duration" , each [Full Days Hours] + [Fist Day Hours] + [Last Day Hours], type duration),

            _RemovedColumns = Table.RemoveColumns(Ad_TotalWorkingDuration,{"S", "E", "S Date", "S Time", "E Date", "E Time", "Working Days", "Full Days Hours", "Fist Day Hours", "Last Day Hours"})
        in
            _RemovedColumns,
    StepBack = ChangedType,
    Ad_dateECDuration = fn_WorkingDuration(StepBack, "début", "dateEC", "dateEC duration"),
    Ad_dateTDuration = fn_WorkingDuration(Ad_dateECDuration, "début", "dateT", "dateT duration"),
    Ad_dateFDuration = fn_WorkingDuration(Ad_dateTDuration, "début", "dateF", "dateF duration")
in
    Ad_dateFDuration

 


Note: Check this link to learn how to use my query.
Check this link if you don't know how to provide sample data.

Hello, Thanks for your help and time.

 

If I want to make this diff between two of my datetime field, where do I have to put them because I see start and end but they're text field, not datetime field.

 

Thanks

Hi, I'm not sure if I understand your question. Could you be more specific please? Add some screenshots with detailed explenation please.


Note: Check this link to learn how to use my query.
Check this link if you don't know how to provide sample data.

I declare this function and tried to use it as following :

 

#"Step5" = Table.AddColumn(#"Step4", "delay", each fn_WorkingDuration(#"Step4",[début],[dateEC], "dateEC duration"))

 

but I got this issue :

 

Expression.Error : Sorry... We could not convert the value #datetime(2022, 9, 19, 8, 0, 0) in Text type
Détails :
Value=19/09/2022 08:00:00
Type=[Type]

If you want to use it as a separate function check queries in attached file.

 

dufoq3_0-1726837617247.png

 


Note: Check this link to learn how to use my query.
Check this link if you don't know how to provide sample data.

I removed ChangedType because in function, it allows only 3 or 4 parameter. I let Helper but I got this now :

 

Heremion_0-1726839231300.png

 

I've edited my Function - there are different parameters. Check my previous post again, download attached .xlsx file and use that function.

 

In that function you have to declare parameters:

1.) tbl (usualy previous step - or step before Helper step) this must be a table

2.) h (Helper step) - this must be a record

3.) start dateTime (column name with start dateTime) as text
4.) end dateTime (column name with start dateTime) as text
5.) optional - new column name


Note: Check this link to learn how to use my query.
Check this link if you don't know how to provide sample data.

I refreshed the post, but in your function, I still see only 4 parameters :

Heremion_0-1726840460811.png

 

Have you downloaded attached file as I mentioned?

 

dufoq3_0-1726841335572.png

 


Note: Check this link to learn how to use my query.
Check this link if you don't know how to provide sample data.

Helpful resources

Announcements
November Carousel

Fabric Community Update - November 2024

Find out what's new and trending in the Fabric Community.

Live Sessions with Fabric DB

Be one of the first to start using Fabric Databases

Starting December 3, join live sessions with database experts and the Fabric product team to learn just how easy it is to get started.

Las Vegas 2025

Join us at the Microsoft Fabric Community Conference

March 31 - April 2, 2025, in Las Vegas, Nevada. Use code MSCUST for a $150 discount! Early Bird pricing ends December 9th.

Nov PBI Update Carousel

Power BI Monthly Update - November 2024

Check out the November 2024 Power BI update to learn about new features.