Fun with Dates!

First of all, TW is not great with date calculations. The recent TIMESTAMP addition to DateFormats is very welcome, but unfortunately, there’s currently no core-only way to convert Unix epochs back to TW dates. Also useful would be the possibility to add or subtract units of time (like day, month, etc.) to / from given dates via the add and subtract filters. But I’ll shut up before Jeremy asks me to make a PR… :wink:

Anyway, Fun with Dates is mostly Fun with bending TW filter expressions.

Disclaimer: You need the TW 5.2.6 (prerelease) for this, since 5.2.5 has a filter bug. Also, some of the subfilters could be made simpler by using the days operator when all your dates are neatly stored in a date field. But I also want to work with journals, whose date is stored as “Today’s Journal (2023-02-25)” in their title and has to be extracted with something like

[trim[]split[(]last[]trim:suffix[)]split[-]join[]]

So, what I begin with is a list of TW dates, in this example manually entered.

What are we trying to do?

Given this list of dates, I want to convert them to human-friendly format (“yesterday”, “next week”, “last January”), group them by that format, and list them. The source may be journals, tasks, anything that makes sense to sort and group. The further into the past / future we get, the less fine the subdivisions need to be – the distinction between a Monday or Tuesday a month ago is not so important as it is for the current week…

The code:

\define is-past() [compare:date:lt<now YYYY0MM0DD>]
\define is-today() [compare:date:eq<now YYYY0MM0DD>]
\define is-tomorrow-or-earlier() [format:date[TIMESTAMP]compare:number:lteq<tomorrowEndEpoch>]
\define is-yesterday-or-later() [format:date[TIMESTAMP]compare:number:gteq<yesterdayStartEpoch>]
\define is-same-week() [format:date[wYYYYWW]compare:number:eq<now wYYYYWW>]
\define is-next-week-or-earlier() [format:date[TIMESTAMP]compare:number:lteq<nextWeekEndEpoch>]
\define is-previous-week-or-later() [format:date[TIMESTAMP]compare:number:gteq<lastWeekStartEpoch>]
\define is-same-month() [format:date[YYYYMM]compare:number:eq<now YYYYMM>]
\define is-same-year() [format:date[YYYY]XXX[]compare:number:eq<now YYYY>]

\define future-day() [filter<is-today>then[today]] :else[filter<is-tomorrow-or-earlier>then[tomorrow]] :else[subfilter<future-week>]
\define future-week() [filter<is-same-week>format:date[DDD]] :else[filter<is-next-week-or-earlier>then[next week]] :else[subfilter<future-month>]
\define future-month() [filter<is-same-month>format:date[MMM]] :else[subfilter<future-year>]
\define future-year() [filter<is-same-year>format:date[MMM]] :else[format:date[YYYY]]
\define past-day() [filter<is-yesterday-or-later>then[yesterday]] :else[subfilter<past-week>]
\define past-week() [filter<is-same-week>format:date[DDD]addprefix[last ]] :else[filter<is-previous-week-or-later>then[last week]] :else[subfilter<past-month>]
\define past-month() [filter<is-same-month>format:date[MMM]addprefix[earlier in ]] :else[subfilter<past-year>]
\define past-year() [filter<is-same-year>format:date[MMM]addprefix[last ]] :else[format:date[YYYY]addprefix[back in ]]

\define friendly-date() [filter<is-past>subfilter<past-day>] :else[subfilter<future-day>]

<$let tomorrowEndEpoch={{{ [<now YYYY0MM0DD>addsuffix[235959999]format:date[TIMESTAMP]add[86400000]] }}}
	  yesterdayStartEpoch={{{ [<now YYYY0MM0DD>addsuffix[000000000]format:date[TIMESTAMP]subtract[86400000]] }}}
	  nextWeekEndEpoch={{{ [<now YYYY0MM0DD>addsuffix[235959999]format:date[TIMESTAMP]] =[<now dddd>negate[]add[7]multiply[86400000]] =[[604800000]] +[sum[]] }}}
	  msFromLastWeekStart={{{ [<now dddd>subtract[1]multiply[86400000]add[604800000]] }}}
	  lastWeekStartEpoch={{{ [<now YYYY0MM0DD>addsuffix[000000000]format:date[TIMESTAMP]subtract<msFromLastWeekStart>] }}} >
	  
	<$let collectionOfDates="20221001000000000 20230102000000000 20230201000000000 20220218000000000 20230222000000000 20230224000000000 20230225000000000 20230226000000000 20230228000000000 20230301000000000 20230306000000000 20230401000000000 20240224000000000 20230415000000000 20231010000000000">

		<$list filter="[enlist<collectionOfDates>] :sort:date[<currentTiddler>] :map[subfilter<friendly-date>] +[each:value[]]" variable="friendlyDate">
			<h3><<friendlyDate>></h3>
			<ul>
			<$list filter="[enlist<collectionOfDates>] :filter[subfilter<friendly-date>match<friendlyDate>] :sort:date[<currentTiddler>]">
				<li style={{{ [<currentTiddler>filter<is-past>then[color: red;]] }}}>
					<$text text={{{ [<currentTiddler>format:date[DDth mmm YYYY]] }}} />
				</li>
			</$list>
			</ul>
		</$list>

	</$let>
</$let>

Open as a tiddler in the sharing edition of TW

That’s a lot of defines. And a bunch of lets. And this operates only down to day granularity; going to hours would add even more.

The output with this simple formatting looks like this:

If this were task due dates, the red tasks would be overdue (in the past).

In my wikis, with some more formatting (and in German), it would look like this:

image

I’ve implemented this previously in a custom filter operator (which is still used in the second screenshot), because without TIMESTAMP it wouldn’t have been possible in WikiText, but that doesn’t lend itself very easily to translation and customized “friendly” date formatting. The only drawback is that the content has to be wrapped in the $let widget with some definitions of epoch timestamps. I’ll be switching to the WikiText version shortly.

Maybe some of you can also use this for your wikis.

Have a nice day
Yaisog

4 Likes

see [IDEA] add `format:timestamp[dateformat]` filter operator · Issue #7120 · Jermolene/TiddlyWiki5 · GitHub

Thanks for sharing the usage, this is interesting.

Hello @Yaisog
Thank you for list of filter expressions.
I would like to try some, but I’m not sure how to add them to a tiddler.
Could you please (or any of the other TW users on this forum) give an example of code to achieve something similar to your Tasks example.
Thanks
Sunny