To subtract dates then sum them. How to set execution priority (like brackets in JS) in filter expression?

I want to count the work time in a day:

[all[]days:startDate[0]field:calendarEntry[yes]tag[Work]] 
:map[{!!endDate}format:date[TIMESTAMP]subtract{!!startDate}format:date[TIMESTAMP]]

But this will us ms generated by {!!endDate}format:date[TIMESTAMP] to subtract {!!startDate}, so the answer is wrong…

Is there any way to do something like [{!!endDate}format:date[TIMESTAMP]subtract ( {!!startDate}format:date[TIMESTAMP] ) ] ?

Or is there a convenient way to subtract date?

Relative date between two given dates, not now - native tiddlywiki - #2 by cdaven gives an example that subtract 2 date, but not able to count multiple result.

One workaround that not works is reverse the subtract, and move it into a subfilter,

but <currentTiddler> here is not getting the input of filter, instead, it is getting '2023-05-23T12:30:00+08:00' ( know this by \define endDateSubFilter() [<currentTiddler>debug-log[]] ), I don’t understand this.

\define endDateSubFilter() [{!!endDate}format:date[TIMESTAMP]subtract<currentTiddler>debug-log[]]

{{{ [all[]days:startDate[0]field:calendarEntry[yes]tag[Work]] 
:map[{!!startDate}format:date[TIMESTAMP]subfilter<endDateSubFilter>] }}}

This will not work too

{{{ [all[]days:startDate[0]field:calendarEntry[yes]tag[Work]]
 :map[{!!startDate}format:date[TIMESTAMP]]
 :reduce[{!!endDate}format:date[TIMESTAMP]subtract<accumulator>] }}}

Because {!!endDate} can’t get anything already.

@linonetwo I will be doing something similar myself soon to sum/count the work time in a day, with a day planer.

set execution priority (like brackets in JS)

This is not only JS it is all of mathematics and no filters do not do this. It is to do with the nature of filters, they are almost by definition defined as a series of input/outputs with the filter determining what makes it through.

  • Have a look at other command line filters to see what I mean, or even in synthesizer’s.

In a way the mathematics operators have being “shoe horned” into filters, a good thing to because we need them,

  • however as a result we end up with maths we can’t use traditional orders of precedence with.

If you look for Evans formulae plugin, the calc plugin and other mathematics plugins there are solutions that allow that kind of calculation.

However if you don’t need to do a lot of maths and want to achieve something it is a simple matter of thinking outside a single filter. I try and point this out quite often, people want to overload the filter, when all they need is a $let widget or nested $list widgets.

  • This will make the calculations look more like we see in traditional programing languages and places the logic in clear and readable steps.

I will come back and illustrate this if requested, but for now I will leave it to you (the reader) to think through the consequences of my above assertions.

Two notes

  • The let widget allows one value to be set to be referenced later in the same let statement (thus is an order of precedent model)
  • Nested list widgets allow every nested layer to use its own variable name, count items with a counter variable, and provide access to the variables from every nested list.
  • TiddlyWiki 5.3.0 and its functions will allow custom filters to be used more practically than with the subfilter operator and allow custom operators. Arguably we can then “shoe horn” more maths into the one filter, but do we have to?
1 Like

@linonetwo here is a quick attempt (untested). The ability to define custom operators in TW v5.3.0 will make this a lot more expressive.

<!-- get the end and start dates for an entry -->
\define getdates()  [{!!endDate}format:date[TIMESTAMP]] [{!!startDate}format:date[TIMESTAMP]]

<!-- get the time in milliseconds for an entry -->
\define getTimeForEntry() [all[]] :map:flat[subfilter<getdates>] :reduce[<index>compare:number:gt[0]then<accumulator>subtract<currentTiddler>else<currentTiddler>]

{{{ 
[all[]days:startDate[0]field:calendarEntry[yes]tag[Work]]
  :map[subfilter<getTimeForEntry>]
  :and[sum[]]
}}}

Edit: here is a simplified version that should work (also untested):


\define getTimeForEntry() [{!!startDate}format:date[TIMESTAMP]] :map[<..currentTiddler>get[endDate]format:date[TIMESTAMP]subtract<currentTiddler>]

{{{ 
[all[]days:startDate[0]field:calendarEntry[yes]tag[Work]]
 :map[subfilter<getTimeForEntry>]
 :and[sum[]]
}}}

Or:


\define getDatesForEntry() [{!!startDate}format:date[TIMESTAMP]negate[]] [{!!endDate}format:date[TIMESTAMP]]

{{{ 
[all[]days:startDate[1]field:calendarEntry[yes]tag[Work]]
 :map:flat[subfilter<getDatesForEntry>]
 :and[sum[]]
}}}
2 Likes

I use them when I could, for example when constructing echarts config

(use “set” here, because “let” widget don’t have “emptyValue”)

This will “output” something, but I don’t think it can “count” something, so in this thread I was confused.

I have a doubt: you have to give format before the operation. If the date is saved as a number, the following code would work for you:

:map[{!!endDate}subtract{!!startDate}format:date[TIMESTAMP]]

Thanks, this is interesting, using :map:flat to get tuple (xxx, yyy), then use <index>compare:number:gt[0] to process element 0 or element 1.

But I don’t understand then<accumulator>subtract<currentTiddler>else<currentTiddler>, this means

if (index > 0) {
 <accumulator>subtract<currentTiddler>
} else {
 <currentTiddler>
}

?

No, my Calendar and Agenda plugin outputs tw standard date string. To better work with other plugins (potentially)

But now it doesn’t work with filter expression easily…

Thanks @saqimtiaz , this works properly and clear!

And after sum[] I just add divide[1000]divide[3600] then it will output hour count.

This is a roundabout way of subtracting element 0 from element 1.
For element 0, we set accumulator equal to currentTiddler.
For element 1, we subtract currentTiddler from accumulator.

In pseudo JS code:

.reduce((accumulator,currentTiddler,index) =>
if (index > 0) {
 <accumlator> = <accumulator>subtract<currentTiddler>
} else {
 <accumulator> = <currentTiddler> //set initial value of accumulator
});

I use set for emptyValue as well but if you use a filtered transclusion you can use the else operator or else filter run to set the default value. No need for emptyValue.