Conducting calculations on tiddler fields then aggregating

My apologies if this has been answered elsewhere, I searched but didn’t see.

I have a set of Journal tiddlers. Each tiddler has a few fields, day.start, day.finish and day.lunch. I would like to create a table that has:

  • tiddler link,
  • day.start,
  • day.finish,
  • day.lunch,
  • time worked (day.finish-day.start-day.lunch),
  • Excess work (time worked - std hours),
  • Cumulative total of excess( sum previous excess work).

e.g. |2025-03-20_Journal | 2025-03-20 09:00:00|2025-03-20 17:00:00|00:30:00| 7:30:00 | 1:30:00| 30:30:00 | (where the std day is 6 hours)

I have been able to do all the time calculations etc and generate the table using <$list> however I am unable to work out how to maintain a running total of excess hours.
The excess hours must be calculated as it is not recorded and the std day duration may change meaning the entire table will be recalculated.

I have tried various <$list> nesting with different <$vars> <$set> <$let> without avail. Every time the <$list> loops the accumulation variable seems to reinitialize. I’ve tried other ways using filters however after an embarrassingly long period I’m no closer.

There is where I’m at:

\procedure my.proc(journals:"Default value")

<table>
<tr>
<td>''Link''</td>
<td>''Start''</td>
<td>''Finish''</td>
<td>''Lunch''</td>
<td>''Duration''</td>
<td>''Difference''</td>
<td>''Balance''</td>
</tr>

<$let TimesheetAccumulator="0">
<$list filter="[tag[Journal]sort[title]]"> 
<$let
      reg='[: -]'
      daystart = {{{ [{!!day.start}search-replace:g:regexp<reg>,[]format:date[TIMESTAMP]] }}}
      dayfinish= {{{ [{!!day.finish}search-replace:g:regexp<reg>,[]format:date[TIMESTAMP]] }}}
      lunch = {{{ [[1970-01-01]][{!!day.lunch}]+[join[ ]search-replace:g:regexp<reg>,[]format:date[TIMESTAMP]] }}}
      duration = {{{ [<dayfinish>subtract<daystart>subtract<lunch>] }}}
      difference_sec = {{{[<duration>]:then[<duration>subtract{$:/jm/MSInAWorkday}] }}}
      format = [UTC]0hh:0mm
      difference = {{{ [<difference_sec>divide[1000]trunc[]] }}}
      dur_hours={{{ [<difference>divide[3600]trunc[]] }}}
      dur_hours_sec = {{{ [<dur_hours>multiply[3600]] }}}
      dur_min={{{ [<difference>subtract<dur_hours_sec>divide[60]round[]abs[]] }}
     TimesheetAccumulator={{{=[<TimesheetAccumulator>] =[<difference>] +[sum[]] }}}
>
   <tr>
      <td><$link/></td>
      <td>{{!!day.start}}</td>
      <td>{{!!day.finish}}</td>
      <td>{{!!day.lunch}}</td>
      <td>{{{ [<duration>format:timestamp<format>] }}}</td>
      <td>{{{ [<difference_sec>compare::lt[0]then[-]]}}} {{{ [<difference_sec>abs[]format:timestamp<format>] }}}</td>
      <td><<difference>> | <<TimesheetAccumulator>></td>
   </tr>
</$let>
</$list>
</$let>
</table>

\end

<<my.proc>>

Any help from the tiddlywiki guru’s would be most appreciated.

Kind Regards, Jason

TiddlyWiki’s variables are not stateful, i.e. they do not behave like shared storage locations that can be updated with different values at different times, like in most imperative programming languages. They behave more like variables in pure functional programming languages: they might share the same names, but not the same scopes and storage locations. Every <$let>...</$let>, <$set>...</$set>, or <$vars>...</$vars> generates a new nested scope, and the variables defined by those widgets cannot be updated with different values from inside that scope.

This means that in your table, for every iteration of the list widget, new copies of your variables are being generated. It’s impossible to use these variables in a stateful manner.

To compute a total sum over a list of input numbers, you’d have to define a function that uses a recursive or an iterative approach.

For the iterative approach, there’s a reduce filter operator and a reduce filter run prefix.

You could define a function like:

\function my.total-duration(durationsInSeconds)
[enlist<durationsInSeconds>] :reduce[add<accumulator>]
\end

… plus some helper functions that convert hours, minutes, and seconds to just seconds, and a function that maps from a list of titles to a list of durations in seconds that can be passed to the my.total-duration function. If possible, the accumulating function shouldn’t be called from inside the list widget (to avoid computing things repeatedly).

HTH