Relative date between two given dates, not now - native tiddlywiki

The format operator and viewWidget have the opportunity to retrieve a relative date, however this compares a given date with now.

Is there anyway in native tiddlywiki to get a relative date between two given dates and not now?

I have the same question. I can almost see a solution if there was a date format/template for rendering a date as Unix epoch/timestamp (seconds since 1970).

Then we could do this:

<$let
	start={{{ [<currentTiddler>get[start-date]format:date[TOKEN]] }}}
	end={{{ [<currentTiddler>get[end-date]format:date[TOKEN]] }}}
	diff={{{ [<end>subtract<start>divide[86400]fixed[0]] }}}
>

  <<diff>> days between

</$let>

@jeremyruston, how about we introduce a token “X” to DateFormat that is substituted with the Unix Timestamp (in seconds). Another token “x” could be used for Unix Timestamp in milliseconds.

This mimics how Moment.js formats dates.

Some systems use “%s” for Unix timestamp (as in seconds since Jan 1, 1970, I guess), so “s” or “S” is another choice, but of course could be mistaken for the “ss” token.

The substituted value is what Date.getTime() returns (divided by 1000 for seconds).

Thanks @cdaven

That’s a great idea. Would you be able to make a PR?

Best wishes

Jeremy

I’ve already done this:

https://tiddlytools.com/timer.html#TiddlyTools%2FTime%2FConvertDate%2FFilter

Specifically, take note of the “timestamp” keyword (convertdate[timestamp]), which converts any ISO8601-compliant date text into a signed integer value representing milliseconds since the ECMAScript epoch base date of 01 January 1970 UTC (where negative values represent dates before the base date). You can also convert ISO8601-compliant date text or a signed integer “timestamp” value into another date format using convertdate[...] where the filter operand is any valid TiddlyWiki date format string.

Input values:

  • The inputs can use any datetime values that are recognized by the Javascript Date.parse() function and conforms to ISO 8601 standard date format.
  • Datetime values can also include English day number suffixes (“st”, “nd”, “rd”, and “th”), which are generally not recognized by the ISO 8601 standard but are supported by TiddlyWiki date formatting output. These suffixes are automatically removed before further processing.
  • Alternatively, the input values can be signed integers, representing the number of milliseconds since midnight 01 January, 1970 UTC (the ECMAScript epoch). Negative numbers represent datetime values before that date.

Output formats:

  • The output format uses TiddlyWiki Date Format codes and defaults to [UTC]YYYY0MM0DD0hh0mm0ss0XXX (the format used by the TWCore to set created and modified tiddler field values).
  • Alternatively, the output format can be the keyword timestamp, which converts the input values into the number of milliseconds since midnight 01 January, 1970 UTC.

Local vs. UTC time:

  • Datetime input values use the local timezone of your system. If the [UTC] prefix is included in the output format operand, your locale’s timezone offset will be applied to convert the result to UTC standard time. If you omit the [UTC] prefix from the output format operand, no conversion will occur and the result will be expressed using your local timezone, without adjusting for your timezone offset.
  • Output format operands containing [UTC] cannot be directly entered into ~TiddlyWiki filter syntax, because that syntax uses [ and ] to enclose literal text operand values. To work around this limitation you can use a reference to a variable that contain the operand value, like this:
    <$let format="[UTC]0hh:0mm:0ss">{{{ [[July 24 1962 02:00:00]convertdate<format>] }}}

enjoy,
-e

Yes, I think so. I’ve started looking at it.

Great! I think this belongs in the core, though, so I hope you’re ok with me “looking” at your code and creating a new official DateFormat token for this?

As a programmer, Unix timestamps are great for simple datetime calculations, so I think it belongs in the core of any language.

I’m sure you know this, but Unix timestamps are always “in UTC”, which is one of its advantages. It’s like a global, stricly increasing counter, and the same point in time around the world has the same timestamp value.


I see that both tokens “ss” (seconds), and “XXX” (milliseconds) are taken, and since most systems use “S” for Unix timestamps, I think we should too. There’s always a small risk of confusing different tokens, but I don’t think picking “X” instead will be any better. Any thoughts?

Sorting dates, comparing dates etc… are fine but I believe we need to get add and and subtract date functions even if it is only days. Do any of these dates allow us to convert, add/subtract and convert back without error?

My convertdate[...] filter isn’t just for converting to/from “epoch” timestamps. It can also be used to convert an ISO8601-compliant formatted datetime input into another formatted datetime output by using TWCore standard date codes.

As such, it can accept a local datetime text value entered by a user and output another formatted datetime text value or an epoch timestamp that represents the same local datetime. It can also automatically convert a local datetime input into a UTC datetime or epoch timestamp by adding/subtracting the appropriate timezone offset. This offers significantly more utility than simply converting to/from UTC epoch timestamp values, and I’d hate to see any new extension of TWCore datetime functionality limited to just handling UTC epoch timestamps.

-e

Yes, you can take a “regular” date, convert it to Unix time, do mathematics with it, and convert back to a “regular” date. That’s the beauty of it. Javascript has this built in, and takes timezones and daylight savings into account when converting.

I’m not sure I follow. I’m talking about adding a missing token to DateFormat, to make it possible to format a datetime as Unix time.

What is it that you don’t like about that?

When you have a solution, plugin or core please provide instructions on how to do this if possible.

Or if @EricShulman timer tools can do it, what is the minimal package, I can install to get this date addition/subtraction feature, it is this we need in the core. It’s like having a car (tiddlywiki) that you can’t schedule for it next service.

  • Then ideally we get this into the core. I suspect this code is somewhere deep in the core already just not exposed.

All you need is
https://tiddlytools.com/timer.html#TiddlyTools%2FTime%2FConvertDate%2FFilter

Then you can write filter syntax like this:

<$let fmt="MMM DDth YYYY at 0hh:0mm:0ss.0XXX"
  oneyear={{{ [[365]multiply[24]multiply[60]multiply[60]multiply[1000]] }}}>
<$edit-text field="start_date"/><br>
One year later is:
{{{ [{!!start_date}convertdate[timestamp]add<oneyear>convertdate<fmt>] }}}
</$let>

Notes:

  • fmt and oneyear are provided for readability in this example
  • You can enter a date in any ISO8601-compliant format (see ISO 8601 - Wikipedia)
  • The filter converts the input to an numeric “epoch time” value using convertdate[timestamp]
  • Then it add one year (in milliseconds)
  • Then it converts to the desired output format

The first issue is that adding a new token to the DateFormat handling (see formatDateString in $:/core/modules/utils/utils.js) is problematic with regard to backward compatibility. In the current TWCore code, any unrecognized characters in the format string are “passed through unchanged”, and adding a new token could result in previously unchanged “pass through” text being processed and replaced with a formatted datetime value.

We ran into this problem in the past when we added the “day of year” format. We had initially chosen “dd” and “0dd” as the formatting codes, but this resulted in text such as “tiddler” being converted to “ti365ler”. To avoid this, we changed to using “ddddd” and “0ddddd”, reasoning that those sequences of 5 "d"s, while certainly more awkward, were unlikely to occur in normal text.

Thus, using a simple format token like “S” (or “X”) will break existing handling for passing through plain text (e.g., formatting “Something YYYY/0MM/0DD” could result in “1640995200000omething 2022/07/16”).

This is why my TiddlyTools ConvertDate filter uses timestamp as a stand-alone keyword (not to be combined with any other DateFormat tokens) and also why I opted to create a completely separate filter (convertdate[...]) rather than than attempting to add another token to the DateFormat handling.

A second issue is that the current format:date[] filter only accepts input values that use the TWCore 17-digit “system date” format of “YYYY0MM0DD0hh0mm0ss0XXX” and cannot handle any other formatted date text. In contrast, my convertdate[...] filter also handles conversion from any ISO8601-compliant formatted datetime text representation to any TWCore date-formatted text representation, enabling usage like this:

Enter your birthdate:<br>
<$edit-text field="test-date"/> {{{ [{!!test-date}convertdate[was a DDD]] }}}<br>
<$let years=60 msec={{{ [<years>multiply[365.25]multiply[24]multiply[3600000]] }}}>
You will be <<years>> years old on 
{{{ [{!!test-date}convertdate[timestamp]add<msec>convertdate[DDD, MMM DDth YYYY]] }}}
</$let>

Try entering your birthdate as formatted date text (e.g., “July 24 1962”)

-e

1 Like

@EricShulman certianly deserves the title

TiddlyWiki Timelord

:nerd_face:

Great information, thanks for sharing! I understand your point, and agree with you that a new token maybe isn’t the best way to solve this problem.

Another thing that I realized is that formatting a date as Unix time is not the right way to think about it. Some languages really do format to Unix time, but not e.g. .NET, where you have other ways to convert to and from Unix time.

Why? Because C# is a typed language, and you want your Unix time as a long, not a string – and formatting always returns strings. (In TiddlyWiki, it probably wouldn’t matter, since we parse strings like ‘123’ to a number before doing maths on it.)

So, does this mean we want the convertdate filter operator in the TiddlyWiki core?

I see now that convertdate only handles ISO8601-compliant datetimes, and not TiddlyWiki’s own format, and my own shorthand formats (omitting dashes and spaces).

None of these work as I would expect them to:

*{{{ [{!!created}convertdate[timestamp]] }}}
*{{{ [{!!created}convertdate[]] }}}
*{{{ [[202207180910]convertdate[timestamp]] }}}
*{{{ [[202207180910]convertdate[]] }}}
*{{{ [[20220718]convertdate[timestamp]] }}}
*{{{ [[20220718]convertdate[]] }}}

Here’s some of the internal code for the convertdate[] filter:

if (format=="timestamp") {
   results.push(new Date(title.replace(re,"$1")).getTime().toString());
} else {
   if (title.match(/^-?\d+$/)) {
      results.push($tw.utils.formatDateString(new Date(Number(title)),format));
   } else {
      results.push($tw.utils.formatDateString(new Date(title.replace(re,"$1")),format));
   }
}

As you can see, the code uses new Date(...) to turn the input to a JS date object. This standard JS method only accepts ISO8601-compliant text or epoch date signed integers as input, and thus does not handle the TWCore’s 17-digit “system date” format (or any abbreviated forms thereof).

Also, take note that, if the input is an integer value (as detected by the regexp pattern “/^-?\d+$/”), then it is assumed to be an epoch date value and is passed directly to new Date() to turn it into a JS date object.

In order to achieve the results you want, you would first need to format the input to an ISO8601-compliant form by using the TWCore’s existing format:date[...] filter, like this:

*{{{ [{!!created}format:date[YYYY 0MM 0DD 0hh:0mm:0ss.0XXX]convertdate[timestamp]] }}}
*{{{ [{!!created}format:date[YYYY 0MM 0DD 0hh:0mm:0ss.0XXX]convertdate[]] }}}
*{{{ [[202207180910]format:date[YYYY 0MM 0DD 0hh:0mm:0ss.0XXX]convertdate[timestamp]] }}}
*{{{ [[202207180910]format:date[YYYY 0MM 0DD 0hh:0mm:0ss.0XXX]convertdate[]] }}}
*{{{ [[20220718]format:date[YYYY 0MM 0DD 0hh:0mm:0ss.0XXX]convertdate[timestamp]] }}}
*{{{ [[20220718]format:date[YYYY 0MM 0DD 0hh:0mm:0ss.0XXX]convertdate[]] }}}

Perhaps I can add a filter suffix (e.g., “convertdate:system[…]”) to indicate that the numeric input is a TWCore “system date” (or an abbreviated form of system date) rather than an epoch date signed integer value. This suffix would then automatically apply the format:date[...] handling internally, so that the filter syntax could be simplified to something like this:

*{{{ [{!!created}convertdate:system[timestamp]] }}}
*{{{ [{!!created}convertdate:system[]] }}}
*{{{ [[202207180910]convertdate:system[timestamp]] }}}
*{{{ [[202207180910]convertdate:system[]] }}}
*{{{ [[20220718]convertdate:system[timestamp]] }}}
*{{{ [[20220718]convertdate:system[]] }}}

Let me experiment a bit to see what I can come up with…

-e

Yes, that works, but is cumbersome.

My reading journal now calculates how many days it took to read each book, and even the number of pages read per day! :nerd_face:

<$list filter="[<currentTiddler>status[complete]]">
<$list filter="[<currentTiddler>has[start-date]]">
<$list filter="[<currentTiddler>has[end-date]]">
	<$let
		start={{{ [<currentTiddler>get[start-date]format:date[YYYY-0MM-0DD 0hh:0mm:0ss.0XXX]convertdate[timestamp]] }}}
		end={{{ [<currentTiddler>get[end-date]format:date[YYYY-0MM-0DD 0hh:0mm:0ss.0XXX]convertdate[timestamp]] }}}
		days={{{ [<end>subtract<start>divide[86400000]fixed[0]] }}}
	>
		<tr>
			<th align="right">Reading time</th>
			<td><<days>> days
			<$list filter="[<currentTiddler>has[pages]]">
			<$let ppd={{{ [{!!pages}divide<days>fixed[0]] }}}>
				(<<ppd>> pages per day)
			</$let>
			</$list>
			</td>
		</tr>
	</$let>
</$list>
</$list>
</$list>

There are three types of inputs, right?

  1. Unix time (any number!) – like 1658155612
  2. “TiddlyWiki datetimes” (numbers that look like ISO datetimes) – like 20220718164703, but also 20220718
  3. Javascript-Date()'s accepted inputs – like “2022-07-18 16:47:16” or “Mon Jul 18 2022” or “12:34:56”

The first two overlap, but not the third one. A Unix time can also be a valid TiddlyWiki datetime.

My suggestion would be to consider #2 and #3 the defaults/expected inputs, and #1 the special case.

Then you would have to type convertdate:timestamp[…] when you’re coming from a Unix timestamp, which also is symmetrical with convertdate[timestamp], right?

I’ve updated https://tiddlytools.com/timer.html#TiddlyTools%2FTime%2FConvertDate%2FFilter to add the :timestamp operator suffix as we have discussed.

Syntax now supports:

  • convertdate[timestamp] - converts ISO8601 OR TWCore datetime text value to posix time epoch signed integer
  • convertdate[outputformat] - converts ISO8601 or TWCore datetime text value to TWCore Date Formatted text output
  • convertdate:timestamp[outputformat] - converts posix time epoch signed integer to TWCore Date Formatted text output

enjoy,
-e

1 Like

We have an outstanding PR for the core for a parseDate operator which has a very narrow scope. It would be helpful @EricShulman and @cdaven if you reviewed it and provided any relevant feedback: https://github.com/Jermolene/TiddlyWiki5/pull/6512

I’m not sure whether I should put my feedback here or there, but anyway:

As I understand it, the use case for parsedate is this:

  • DatetimeJS → DatetimeTW

You can convert a datetime from some formats that Javascript supports (a subset of ISO, for one) to the TWCore format.

convertdate supports this as well (the test cases from that PR give the same results). It also adds these use cases:

  • DatetimeJS → DatetimeCustom
  • DatetimeJS → DatetimeUNIX
  • DatetimeTW → DatetimeCustom
  • DatetimeTW → DatetimeUNIX
  • DatetimeUNIX → DatetimeCustom
  • DatetimeUNIX → DatetimeTW

It has three acceptable input formats: JS/ISO, TWCore, Unix time

And three output formats: TWCore, custom format/template string, Unix time

Unless I’m missing something, I would say that convertdate is a much better candidate for inclusion in TiddlyWiki core. Well done, @EricShulman!

(Note that “DatetimeTW → DatetimeCustom” is the same thing (?) as format:date, which could also be obsoleted by convertdate. Again, unless I’m missing something… Chesterton’s Fence, and all that.)

hmm… I’m thinking if my convertdate filter is to be a candidate for core inclusion perhaps I should change the filter operator name to “parsedate”, and use “number” as the keyword suffix/operand for conversion to/from “unix date”:

parsedate[...]
parsedate[number]
parsedate:number[...]

where “…” is any TW Date Format string which, if omitted defaults to “[UTC]YYYY0MM0DD0hh0mm0ss0XXX

-e