Comparison of transcluded value not working

I’ve got one tiddler, “daysFromEndOfMonth”, which contains:

\whitespace trim
<$let
day=<<now DD>>
month=<<now MM>>
year=<<now YYYY>>
lastDay={{{ 
[<month>match[1]then[31]]
:else[<month>match[2]then[28]]
:else[<month>match[3]then[31]]
:else[<month>match[4]then[30]]
:else[<month>match[5]then[31]]
:else[<month>match[6]then[30]]
:else[<month>match[7]then[31]]
:else[<month>match[8]then[31]]
:else[<month>match[9]then[30]]
:else[<month>match[10]then[31]]
:else[<month>match[11]then[30]]
:else[<month>match[12]then[31]]
 }}}
>
<$text text={{{ [<lastDay>subtract<day>] }}} />
</$let>

And (as of the day of posting this) produces “14”.

I’ve got another tiddler “Date Test” which contains:

<$vars day={{daysFromEndOfMonth}} >
<$list filter="[<day>match[14]]">
Test
</$list>

Which I would expect would contain “Test” but is instead blank. Any ideas why this isn’t working or how to fix it?

Thanks in advance!

Hi @Devin_Baillie

Here’s how to fix your code:

<$wikify name="day" text={{daysFromEndOfMonth}} >
<$list filter="[<day>match[14]]">
Test
</$list>

I don’t really know why the <$vars> widget doesn’t work though…

Fred

1 Like

The <$vars day={{daysFromEndOfMonth}}> doesn’t automatically “wikify” (render) the transcluded content. Rather, it just assigns the content of that tiddler to the variable. It is only when you subsequently display the variable that it’s content is rendered to produce numeric output.

To obtain the rendered output of a transclusion and store it in a variable, you can use the $wikify widget, like this:

<$wikify name=day text={{daysFromEndOfMonth}}>

Also note that you can write the daysFromEndOfMonth tiddler more more compactly, like this:

{{{ [[31 28 31 30 31 30 31 31 30 31 30 31]split[ ]nth<now MM>subtract<now DD>] }}}

enjoy,
-e

3 Likes

For a simpler approach, use functions. Put

\function last.Day() [enlist:raw[31 28 31 30 31 30 31 31 30 31 30 31]]+[nth<now MM>] 
\function days.FromEndOfMonth() [<last.Day>subtract<now DD>]

In a tiddler and tag it $:/tags/Global

Then to use your test case:

<$list filter="[days.FromEndOfMonth[]match[14]]">
Test
</$list>
3 Likes

Thanks, this solves the problem!

Thanks, I like this approach - I tried to experiment with making it more general, but it doesn’t behave properly:

\function lastDay1(month) [enlist:raw[31 28 31 30 31 30 31 31 30 31 30 31]nth<month>] 
\function lastDay2() [enlist:raw[31 28 31 30 31 30 31 31 30 31 30 31]nth<now MM>]

<$text text="<<now MM>> = " /> <<now MM>>

<$text text="<<lastDay1 4>> = " /> <<lastDay1 4>>

<$text text="<<lastDay1 <now MM> >> = "/> <<lastDay1 <now MM> >>

<$text text="<<lastDay2>> = "/> <<lastDay2>>

Which gives the output:

<<now MM>> = 4

<<lastDay1 4>> = 30

<<lastDay1 <now MM> >> = 31

<<lastDay2>> = 30

Any idea why <<lastDay1 <now MM> >> is giving me 31 instead of 30?

Also, is there a difference between
\function lastDay2() [enlist:raw[31 28 31 30 31 30 31 31 30 31 30 31]]+[nth<now MM>]
as opposed to
\function lastDay2() [enlist:raw[31 28 31 30 31 30 31 31 30 31 30 31]nth<now MM>]
or are they equivalent?

I notice the length of February was hardcoded and then propagated into refactorings. So the code will turn into a pumpkin every four years?

The core does not currently provide any support for date/time operations beyond formatting. I think a strong case can be made for the universality of these operations, and so I would be happy to consider adding such primitives.

I would be inclined to focus on exposing the date/time functionality provided natively by JavaScript. There are libraries like moment.js that go a lot deeper (eg parsing dates and times in different timezones), but they tend to be quite large (moment.js is 18kb).

The area we should look at of moment.js is the chainable API. It is somewhat similar to TiddlyWiki’s filter architecture and might provide some pointers to help structure the new operators.

You’re not wrong, but I’ve got almost 3 years to add in support for leap-years, by which time I may or may not still be using it (or maybe by then we’ll have native support for date operations). If I’m depending on it for anything that needs to be exact, I’ll put a reminder in my calendar for January 2028, and that still leaves me a month to fix it.

Here’s a <<last.Day>> function that includes correct handling for leap years through the year 2099:

\function last.Day()
[<now YYYY>remainder[4]match[0]then<now MM>match[2]then[29]]
~[[31 28 31 30 31 30 31 31 30 31 30 31]split[ ]nth<now MM>]
\end

Notes:

  • The first filter run says “if the current year is divisible by 4 and the current month is February, then the last day is 29”
  • ELSE, the second run is the same as before
  • The above calculation is inaccurate for most century years, because the actual rule for leap years is: if the year is divisible by 4 or 400, but NOT divisible by 100 (thus, 2000 is a leap year; 2100, 2200, and 2300 are not leap years; 2400 is a leap year; 2500, 2600, and 2700 are not leap years; 2800 is a leap year, etc.).

enjoy,
-e

2 Likes

If this is legal syntax, it’s news to me. I don’t always keep up, so it might be. But I think you need a transclusion to pass the result of <<now MM>>. So your line would look like:

<$text text="""<$transclude $variable="lastDay1" month=<<now MM>> = """/> <$transclude $variable="lastDay1" month=<<now MM>> />

with the result:

<$transclude $variable="lastDay1" month=<<now MM>> = 30

I don’t know whether it’s legal, but I guess not?

I figured if I could do <<lastDay1 4>> it would make sense to be able to do <<lastDay1 <now MM> >>.
It produced an output of 31, so I figured there was probably some off-by-one error buried in there for some reason (returning either the 3rd or 5th element, instead of the 4th).

After a bit more reading, it looks like if I rename my function to .lastDay1, I can do {{{ [.lastDay1<now MM>] }}}, which is a bit more readable to me. Incorporating everything, I get:

\function .lastDay(year, month)
[<year>remainder[4]match[0]then<month>match[2]then[29]]
~[[31 28 31 30 31 30 31 31 30 31 30 31]split[ ]nth<month>]
\end
\function .daysLeft(year, month, day) [.lastDay<year>,<month>subtract<day>]

{{{ [.lastDay<now YYYY>,<now MM>] }}}

{{{ [.daysLeft<now YYYY>,<now MM>,<now DD>] }}}

<$let day={{{ [.lastDay<now YYYY>,<now MM>] }}} >
<$list filter="[<day>match[30]]">
TEST .lastDay
</$list>
</$let>

<$let days={{{ [.daysLeft<now YYYY>,<now MM>,<now DD>] }}} >
<$list filter="[<days>match[13]]">
TEST .daysLeft
</$list>
</$let>

Which works as expected.

Thanks everyone for the help!!

But then I went and tried to improve it to make life a little easier and broke it again:

\function .lastDay2(date)
<$let
year=[<date>format:date[YYYY]]
month=[<date>format:date[MM]] >
[<year>remainder[4]match[0]then<month>match[2]then[29]]
~[[31 28 31 30 31 30 31 31 30 31 30 31]split[ ]nth<month>]
</$let>
\end

<$let date=<<now YYYY-0MM-0DD>> >
{{{ [.lastDay2<date>] }}}
</$let>

Produces:

<$letyear=2025month=12></$let>

Any thougths?

Hi @Devin_Baillie

Function definitions must only be composed of filter runs, you can’t use anything else, like a <$let> widget.
Also, format:date operator input dates should be formatted like TiddlyWiki dates: YYYY0MM0DD0hh0mm0ssXXX. Thus your date variable definition using YYYY-0MM-0DD format won’t work.

Here is a working example:

\function .getYear(date) [<date>format:date[YYYY]]
\function .getMonth(date) [<date>format:date[MM]]

\function .lastDay3(date)
   [.getMonth<date>!match[2]]
   :map[[31 28 31 30 31 30 31 31 30 31 30 31]split[ ]nth<currentTiddler>]
   :else[.getYear<date>remainder[4]match[0]then[29]else[28]]
\end

<$let date=<<now YYYY0MM0DD>> >

<$text text=<<date>>/>

{{{ [.getYear<date>] }}}

{{{ [.getMonth<date>] }}}

{{{ [.lastDay3<date>] }}}

</$let>

Fred