Forum Discussion

mrbartuss's avatar
mrbartuss
Advocate II
2 years ago
Solved

Add integer hours to date in Power Query or with DAX

My data looks like this:

NameDateTaskHours
John18/07/2024BBB3
John18/07/2024AAA5
John19/07/2024CCC8
John20/07/2024BBB3
John20/07/2024CCC3
John20/07/2024AAA2
Karin18/07/2024CCC8
Karin19/07/2024AAA4
Karin19/07/2024BBB4


I want to calculate StartDateTime and EndDateTime for each Name-Date-Task. It would be easy if there was only one row, but there can be multiple. Assumption: new task for the date always starts at 9am.

Here is what I want to achieve:

NameDateTaskStart DateTimeEnd DateTime
John18/07/2024BBB18/07/2024 9:00 AM18/07/2024 12:00 PM
John18/07/2024AAA18/07/2024 12:00 PM18/07/2024 5:00 PM
John19/07/2024CCC19/07/2024 9:00 AM19/07/2024 5:00 PM
John20/07/2024BBB20/07/2024 9:00 AM20/07/2024 12:00 PM
John20/07/2024CCC20/07/2024 12:00 PM20/07/2024 3:00 PM
John20/07/2024AAA20/07/2024 3:00 PM20/07/2024 5:00 PM
Karin18/07/2024CCC18/07/2024 9:00 AM18/07/2024 5:00 PM
Karin19/07/2024AAA19/07/2024 9:00 AM19/07/2024 1:00 PM
Karin19/07/2024BBB19/07/2024 1:00 PM19/07/2024 5:00 PM


I think it would be best to do it in Power Query as calculated columns in DAX are not recommended.

  • Based on your description, I created data to reproduce your scenario. The pbix file is attached in the end.

    let
        Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("i45W8srPyFPSUTK00DMw1zMyMDIBcpycnICksVKsDnZ5R0dHIGmKIm+JJO/s7AwkLZDljQzwm48iD9GPWx5ivxFY3juxKBPDgcgOgCuwxDDBBLcCiBOBCmIB", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [Name = _t, Date = _t, Task = _t, Hours = _t]),
        #"Changed Type" = Table.TransformColumnTypes(Source,{{"Name", type text}, {"Date", type date}, {"Task", type text}, {"Hours", Int64.Type}}),
        #"Grouped Rows" = Table.Group(#"Changed Type", {"Name", "Date"}, {{"Count", 
        (x)=>[a =Table.ToRecords(x),
         b =List.Generate (
        () => [ i = 0, result = a{i}[Date]&#time(9+a{i}[Hours],0,0) ], 
        ( x ) => x[i] < List.Count ( a ), 
        ( x ) => [ i = x[i] + 1, result = ( x[result] +#duration(0, a{i}[Hours],0,0) )  ], 
        ( x ) => Record.AddField ( a{x[i]}, "End DateTime", x[result] )
      ),
      c =  List.Transform(b,(x)=>  Record.AddField ( x,"Start DateTime",x[End DateTime]-#duration(0,x[Hours],0,0)  )),
      d = Table.FromRecords ( c )
       
      ][d]}}),
        #"Removed Other Columns" = Table.Combine( Table.SelectColumns(#"Grouped Rows",{"Count"})[Count]),
        #"Changed Type1" = Table.TransformColumnTypes(#"Removed Other Columns",{{"End DateTime", type datetime}, {"Start DateTime", type datetime}}),
        #"Reordered Columns" = Table.ReorderColumns(#"Changed Type1",{"Name", "Date", "Task", "Hours", "Start DateTime", "End DateTime"}),
        #"Removed Columns" = Table.RemoveColumns(#"Reordered Columns",{"Date"})
    in
        #"Removed Columns"
  • Hi mrbartuss, another solution (List.Accumulate):

     

    Result:

    let
        fnDateTimes = 
            (myTable as table)=>
            [
                // _Detail = GroupedRows{2}[All],
                _Detail = myTable,
                _BufferedHours = List.Buffer(_Detail[Hours]),
                _AccumulatedDateTimes = List.Accumulate(
                    {1..List.Count(_BufferedHours)-1},
                    {{ _Detail{0}[Date] & #time(9,0,0), _Detail{0}[Date] & #time(9,0,0) + #duration(0,_BufferedHours{0},0,0) }},
                    (s,c)=> s & {{ List.Last(s){1}, List.Last(s){1} + #duration(0,_BufferedHours{c},0,0) }}
                ),
                _RemovedColumns = Table.RemoveColumns(_Detail, {"Hours"}),
                _ToTable = Table.FromColumns(Table.ToColumns(_RemovedColumns) & Table.ToColumns(Table.FromRows(_AccumulatedDateTimes)), Value.Type(_RemovedColumns & #table(type table[Start DateTime=datetime, End DateTime=datetime], {})))
            ][_ToTable],
    
        Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("i45W8srPyFPSUTK00Dcw1zcyMDIBcpycnICksVKsDnZ5R0dHIGmKIm+JJO/s7AwkLZDljQzwm48iD9GPWx5ivxFY3juxKBPDgcgOgCuwxDDBBLcCiBOBCmIB", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [Name = _t, Date = _t, Task = _t, Hours = _t]),
        ChangedType = Table.TransformColumnTypes(Source,{{"Hours", type number}, {"Date", type date}}, "sk-SK"),
        GroupedRows = Table.Group(ChangedType, {"Name", "Date"}, {{"fn", fnDateTimes, type table}}),
        Combined = Table.Combine(GroupedRows[fn])
    in
        Combined
  • Ahmedx dufoq3 Thank you both very much!
    Below is my solution. How would you rate all three solutions based on performance?

    let
        Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("i45W8srPyFPSUTK00DMw1zMyMDIBcpycnICksVKsDnZ5R0dHIGmKIm+JJO/s7AwkLZDljQzwm48iD9GPWx5ivxFY3juxKBPDgcgOgCuwxDDBBLcCiBOBCmIB", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [Name = _t, Date = _t, Task = _t, Hours = _t]),
        #"Changed Type" = Table.TransformColumnTypes(Source,{{"Name", type text}, {"Date", type datetime}, {"Task", type text}, {"Hours", Int64.Type}}),
        Group = Table.Group(
        #"Changed Type",
        {"Name", "Date"},
        {{"All", each Table.AddIndexColumn(_, "Index", 1, 1, Int64.Type), type table}}
      ),
      Result = Table.AddColumn(Group, "Result", each let
            BuffValues = List.Buffer([All][Hours]),
            RunningTotal = List.Generate(
     
              // Start value: Set RT to 1st value in BuffValues and set RowIndex to 0
              () => [RT = BuffValues{0}, RowIndex = 0],
              // Condition: Generate values as long as RowIndex is < than number of list items
              each [RowIndex] < List.Count(BuffValues),
              // Record with logic for next values:
              each [
     
                // Calculate running total by adding current RT value and the next value
                RT = List.Sum({[RT], BuffValues{[RowIndex] + 1}}),
                // Increment the RowIndex by 1 for the next iteration
                RowIndex = [RowIndex] + 1
              ],
              // Only return the running total (RT) value for each row
              each [RT]
            ),
            CombinedTable = Table.FromColumns(
              Table.ToColumns([All]) & {Value.ReplaceType(RunningTotal, type {Int64.Type})},
              Table.ColumnNames([All]) & {"Running Total"}
            ),
            StartDateStage = Table.AddColumn(
              CombinedTable,
              "Start Date Stage",
              each [Date] + #duration(0, 9, 0, 0),
              type datetime
            ),
            EndDate = Table.AddColumn(
              StartDateStage,
              "End Date",
              each [Start Date Stage] + #duration(0, [#"Running Total"], 0, 0),
              type datetime
            ),
            StartDate = Table.AddColumn(
              EndDate,
              "Start Date",
              each if [Index] = 1 then [Start Date Stage] else EndDate[End Date]{[Index] - 2},
              type datetime
            ),
            Result = Table.SelectColumns(
              StartDate,
              {
                "Name",
                "Date",
                "Hours",
                "Task",
                "Start Date",
                "End Date"
              }
            )
          in
            Result),
      FinalResult = Table.Combine(Table.SelectColumns(Result, {"Result"})[Result])
    in
        FinalResult

     

  • Ahmedx's avatar
    Ahmedx
    2 years ago

    orks a little slow

    here is the difference in time

6 Replies

  • Based on your description, I created data to reproduce your scenario. The pbix file is attached in the end.

    let
        Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("i45W8srPyFPSUTK00DMw1zMyMDIBcpycnICksVKsDnZ5R0dHIGmKIm+JJO/s7AwkLZDljQzwm48iD9GPWx5ivxFY3juxKBPDgcgOgCuwxDDBBLcCiBOBCmIB", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [Name = _t, Date = _t, Task = _t, Hours = _t]),
        #"Changed Type" = Table.TransformColumnTypes(Source,{{"Name", type text}, {"Date", type date}, {"Task", type text}, {"Hours", Int64.Type}}),
        #"Grouped Rows" = Table.Group(#"Changed Type", {"Name", "Date"}, {{"Count", 
        (x)=>[a =Table.ToRecords(x),
         b =List.Generate (
        () => [ i = 0, result = a{i}[Date]&#time(9+a{i}[Hours],0,0) ], 
        ( x ) => x[i] < List.Count ( a ), 
        ( x ) => [ i = x[i] + 1, result = ( x[result] +#duration(0, a{i}[Hours],0,0) )  ], 
        ( x ) => Record.AddField ( a{x[i]}, "End DateTime", x[result] )
      ),
      c =  List.Transform(b,(x)=>  Record.AddField ( x,"Start DateTime",x[End DateTime]-#duration(0,x[Hours],0,0)  )),
      d = Table.FromRecords ( c )
       
      ][d]}}),
        #"Removed Other Columns" = Table.Combine( Table.SelectColumns(#"Grouped Rows",{"Count"})[Count]),
        #"Changed Type1" = Table.TransformColumnTypes(#"Removed Other Columns",{{"End DateTime", type datetime}, {"Start DateTime", type datetime}}),
        #"Reordered Columns" = Table.ReorderColumns(#"Changed Type1",{"Name", "Date", "Task", "Hours", "Start DateTime", "End DateTime"}),
        #"Removed Columns" = Table.RemoveColumns(#"Reordered Columns",{"Date"})
    in
        #"Removed Columns"
  • dufoq3's avatar
    dufoq3
    Community Champion

    Hi mrbartuss, another solution (List.Accumulate):

     

    Result:

    let
        fnDateTimes = 
            (myTable as table)=>
            [
                // _Detail = GroupedRows{2}[All],
                _Detail = myTable,
                _BufferedHours = List.Buffer(_Detail[Hours]),
                _AccumulatedDateTimes = List.Accumulate(
                    {1..List.Count(_BufferedHours)-1},
                    {{ _Detail{0}[Date] & #time(9,0,0), _Detail{0}[Date] & #time(9,0,0) + #duration(0,_BufferedHours{0},0,0) }},
                    (s,c)=> s & {{ List.Last(s){1}, List.Last(s){1} + #duration(0,_BufferedHours{c},0,0) }}
                ),
                _RemovedColumns = Table.RemoveColumns(_Detail, {"Hours"}),
                _ToTable = Table.FromColumns(Table.ToColumns(_RemovedColumns) & Table.ToColumns(Table.FromRows(_AccumulatedDateTimes)), Value.Type(_RemovedColumns & #table(type table[Start DateTime=datetime, End DateTime=datetime], {})))
            ][_ToTable],
    
        Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("i45W8srPyFPSUTK00Dcw1zcyMDIBcpycnICksVKsDnZ5R0dHIGmKIm+JJO/s7AwkLZDljQzwm48iD9GPWx5ivxFY3juxKBPDgcgOgCuwxDDBBLcCiBOBCmIB", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [Name = _t, Date = _t, Task = _t, Hours = _t]),
        ChangedType = Table.TransformColumnTypes(Source,{{"Hours", type number}, {"Date", type date}}, "sk-SK"),
        GroupedRows = Table.Group(ChangedType, {"Name", "Date"}, {{"fn", fnDateTimes, type table}}),
        Combined = Table.Combine(GroupedRows[fn])
    in
        Combined
    • Ahmedx's avatar
      Ahmedx
      Super User

      great, but List.Accumulate is a bit slow

  • Ahmedx dufoq3 Thank you both very much!
    Below is my solution. How would you rate all three solutions based on performance?

    let
        Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("i45W8srPyFPSUTK00DMw1zMyMDIBcpycnICksVKsDnZ5R0dHIGmKIm+JJO/s7AwkLZDljQzwm48iD9GPWx5ivxFY3juxKBPDgcgOgCuwxDDBBLcCiBOBCmIB", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [Name = _t, Date = _t, Task = _t, Hours = _t]),
        #"Changed Type" = Table.TransformColumnTypes(Source,{{"Name", type text}, {"Date", type datetime}, {"Task", type text}, {"Hours", Int64.Type}}),
        Group = Table.Group(
        #"Changed Type",
        {"Name", "Date"},
        {{"All", each Table.AddIndexColumn(_, "Index", 1, 1, Int64.Type), type table}}
      ),
      Result = Table.AddColumn(Group, "Result", each let
            BuffValues = List.Buffer([All][Hours]),
            RunningTotal = List.Generate(
     
              // Start value: Set RT to 1st value in BuffValues and set RowIndex to 0
              () => [RT = BuffValues{0}, RowIndex = 0],
              // Condition: Generate values as long as RowIndex is < than number of list items
              each [RowIndex] < List.Count(BuffValues),
              // Record with logic for next values:
              each [
     
                // Calculate running total by adding current RT value and the next value
                RT = List.Sum({[RT], BuffValues{[RowIndex] + 1}}),
                // Increment the RowIndex by 1 for the next iteration
                RowIndex = [RowIndex] + 1
              ],
              // Only return the running total (RT) value for each row
              each [RT]
            ),
            CombinedTable = Table.FromColumns(
              Table.ToColumns([All]) & {Value.ReplaceType(RunningTotal, type {Int64.Type})},
              Table.ColumnNames([All]) & {"Running Total"}
            ),
            StartDateStage = Table.AddColumn(
              CombinedTable,
              "Start Date Stage",
              each [Date] + #duration(0, 9, 0, 0),
              type datetime
            ),
            EndDate = Table.AddColumn(
              StartDateStage,
              "End Date",
              each [Start Date Stage] + #duration(0, [#"Running Total"], 0, 0),
              type datetime
            ),
            StartDate = Table.AddColumn(
              EndDate,
              "Start Date",
              each if [Index] = 1 then [Start Date Stage] else EndDate[End Date]{[Index] - 2},
              type datetime
            ),
            Result = Table.SelectColumns(
              StartDate,
              {
                "Name",
                "Date",
                "Hours",
                "Task",
                "Start Date",
                "End Date"
              }
            )
          in
            Result),
      FinalResult = Table.Combine(Table.SelectColumns(Result, {"Result"})[Result])
    in
        FinalResult

     

    • Ahmedx's avatar
      Ahmedx
      Super User

      orks a little slow

      here is the difference in time

      • mrbartuss's avatar
        mrbartuss
        Advocate II

        Luckily I have a small dataset 😄 Thank again!