diff --git a/core/language/en-GB/Misc.multids b/core/language/en-GB/Misc.multids
index d8c091375..0bedbc21a 100644
--- a/core/language/en-GB/Misc.multids
+++ b/core/language/en-GB/Misc.multids
@@ -32,6 +32,7 @@ Error/FilterSyntax: Syntax error in filter expression
Error/FilterRunPrefix: Filter Error: Unknown prefix for filter run
Error/IsFilterOperator: Filter Error: Unknown parameter for the 'is' filter operator
Error/FormatFilterOperator: Filter Error: Unknown suffix for the 'format' filter operator
+Error/ParseDateFilterOperator: Filter Error: Unknown parameter for the 'parseDate' filter operator
Error/LoadingPluginLibrary: Error loading plugin library
Error/NetworkErrorAlert: `
''Network Error''
It looks like the connection to the server has been lost. This may indicate a problem with your network connection. Please attempt to restore network connectivity before continuing.
''Any unsaved changes will be automatically synchronised when connectivity is restored''.`
Error/PutEditConflict: File changed on server
diff --git a/core/modules/filters/parsedate.js b/core/modules/filters/parsedate.js
new file mode 100644
index 000000000..3e7bfc722
--- /dev/null
+++ b/core/modules/filters/parsedate.js
@@ -0,0 +1,59 @@
+/*\
+title: $:/core/modules/filters/parsedate.js
+type: application/javascript
+module-type: filteroperator
+
+Filter operator converting different date formats into TiddlyWiki's date format
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+/*
+Parser for the ECMAScript date format as specified in
+[ECMA262 Chapter 21.4.1.15](https://tc39.es/ecma262/#sec-date-time-string-format)
+*/
+function parseECMAScriptDate(input) {
+ const dateValidator = new RegExp("^(\\d{4}(-\\d{2}){0,2})?((^|T)\\d{2}:\\d{2}(:\\d{2}(\\.\\d{3})?)?(Z|([+-]\\d{2}:\\d{2}))?)?$");
+
+ if(dateValidator.test(input)) {
+ // This code makes ECMAScript 2015 (ES6) behave like ES7 when parsing
+ // a date.
+ if((input.length < 11) && (input.indexOf("T") === -1)) {
+ input += "T00:00:00Z";
+ }
+ return new Date(input);
+ } else {
+ return false;
+ }
+}
+
+/*
+Export our filter function
+*/
+exports.parsedate = function(source,operator,options) {
+ var parser = null;
+ switch (operator.operand) {
+ case "JS":
+ parser = parseECMAScriptDate;
+ break;
+ }
+ if(!(parser instanceof Function)) {
+ return [$tw.language.getString("Error/ParseDateFilterOperator")];
+ }
+
+ var results = [];
+ source(function(tiddler,title) {
+ const date = parser(title);
+ // Check that date is a Date instance _and_ that it contains a valid date
+ if((date instanceof Date) && !isNaN(date.valueOf())) {
+ results.push($tw.utils.stringifyDate(date));
+ }
+ });
+ return results;
+};
+
+})();
diff --git a/editions/test/tiddlers/tests/test-filters.js b/editions/test/tiddlers/tests/test-filters.js
index 96100e2f7..5c3d90d6c 100644
--- a/editions/test/tiddlers/tests/test-filters.js
+++ b/editions/test/tiddlers/tests/test-filters.js
@@ -1061,6 +1061,46 @@ Tests the filtering mechanism.
expect(wiki.filterTiddlers("void +[format:timestamp[]]").join(",")).toBe("");
});
+ it("should handle the parsedate operator", function() {
+ // The following test cases are based on the
+ // [ES5 date format test cases](https://github.com/tc39/test262/blob/0bccacda693ada2cd1736d35eb912b27291ac6ff/implementation-contributed/v8/mjsunit/date-parse.js#L236)
+ // of the [Test262: ECMAScript Test Suite](https://github.com/tc39/test262).
+ // They are necessary to test the validator regexp.
+ expect(wiki.filterTiddlers("[[2000-01-01T08:00:00.000Z]parsedate[JS]]").join(" ")).toBe("20000101080000000");
+ expect(wiki.filterTiddlers("[[2000-01-01T08:00:00Z]parsedate[JS]]").join(" ")).toBe("20000101080000000");
+ expect(wiki.filterTiddlers("[[2000-01-01T08:00Z]parsedate[JS]]").join(" ")).toBe("20000101080000000");
+ expect(wiki.filterTiddlers("[[2000-01T08:00:00.000Z]parsedate[JS]]").join(" ")).toBe("20000101080000000");
+ expect(wiki.filterTiddlers("[[2000T08:00:00.000Z]parsedate[JS]]").join(" ")).toBe("20000101080000000");
+ expect(wiki.filterTiddlers("[[2000T08:00Z]parsedate[JS]]").join(" ")).toBe("20000101080000000");
+ expect(wiki.filterTiddlers("[[2000-01T00:00:00.000-08:00]parsedate[JS]]").join(" ")).toBe("20000101080000000");
+ expect(wiki.filterTiddlers("[[2000-01T08:00:00.001Z]parsedate[JS]]").join(" ")).toBe("20000101080000001");
+ expect(wiki.filterTiddlers("[[2000-01T08:00:00.099Z]parsedate[JS]]").join(" ")).toBe("20000101080000099");
+ expect(wiki.filterTiddlers("[[2000-01T08:00:00.999Z]parsedate[JS]]").join(" ")).toBe("20000101080000999");
+ expect(wiki.filterTiddlers("[[2000-01T00:00:00.001-08:00]parsedate[JS]]").join(" ")).toBe("20000101080000001");
+ expect(wiki.filterTiddlers("[[2000-01-01T24:00Z]parsedate[JS]]").join(" ")).toBe("20000102000000000");
+ expect(wiki.filterTiddlers("[[2000-01-01T24:00:00Z]parsedate[JS]]").join(" ")).toBe("20000102000000000");
+ expect(wiki.filterTiddlers("[[2000-01-01T24:00:00.000Z]parsedate[JS]]").join(" ")).toBe("20000102000000000");
+ // Invalid dates for the regexp validator
+ expect(wiki.filterTiddlers("[[2000-01-01TZ]parsedate[JS]]").join(" ")).toBe("");
+ expect(wiki.filterTiddlers("[[2000-01-01T60Z]parsedate[JS]]").join(" ")).toBe("");
+ expect(wiki.filterTiddlers("[[2000-01-01T60:60Z]parsedate[JS]]").join(" ")).toBe("");
+ expect(wiki.filterTiddlers("[[2000-01-0108:00Z]parsedate[JS]]").join(" ")).toBe("");
+ expect(wiki.filterTiddlers("[[2000-01-01T08Z]parsedate[JS]]").join(" ")).toBe("");
+ expect(wiki.filterTiddlers("[[2000-01-01T24:01]parsedate[JS]]").join(" ")).toBe("");
+ expect(wiki.filterTiddlers("[[2000-01-01T24:00:01]parsedate[JS]]").join(" ")).toBe("");
+ expect(wiki.filterTiddlers("[[2000-01-01T24:00:00.001]parsedate[JS]]").join(" ")).toBe("");
+ expect(wiki.filterTiddlers("[[2000-01-01T24:00:00.999Z']parsedate[JS]]").join(" ")).toBe("");
+ // Date that pass the validation regexp, but is invalid for Date.parse.
+ // This checks the error handling after the regex validator.
+ expect(wiki.filterTiddlers("[[2000-14-01]parsedate[JS]]").join(" ")).toBe("");
+ // Invalid operator
+ expect(wiki.filterTiddlers("[[2015-03-25]parsedate[INVALID]]")).toEqual([$tw.language.getString("Error/ParseDateFilterOperator")]);
+ // Validate that a date in the local timezone is correctly parsed and represented as UTC
+ // This is tricky because we can not set the used timezone.
+ expect(wiki.filterTiddlers("[[2015-03-25T15:40]parsedate[JS]]").join(" ").substr(8, 2)).toBe((new Date("2015-03-15T15:40:32")).getUTCHours().toString());
+ expect(wiki.filterTiddlers("[[2015]] [[2020]] +[parsedate[JS]]").join(" ")).toBe("20150101000000000 20200101000000000");
+ });
+
it("should handle the deserializers operator", function() {
var expectedDeserializers = ["application/javascript","application/json","application/x-tiddler","application/x-tiddler-html-div","application/x-tiddlers","text/css","text/html","text/plain"];
if($tw.browser) {
diff --git a/editions/tw5.com/tiddlers/filters/examples/parsedate.tid b/editions/tw5.com/tiddlers/filters/examples/parsedate.tid
new file mode 100644
index 000000000..dd356a8fd
--- /dev/null
+++ b/editions/tw5.com/tiddlers/filters/examples/parsedate.tid
@@ -0,0 +1,39 @@
+created: 20220110203115000
+modified: 20220110203115000
+tags: [[Operator Examples]] [[parsedate Operator]]
+title: parsedate Operator (Examples)
+isodate: 2022-01-01T12:15:00
+type: text/vnd.tiddlywiki
+
+\define utc-datetime() [UTC]MMM the DD. YYYY 0hh:0mm UTC
+
+<<.operator-example 1 "[[2011-01-01T12:15:21]parsedate[JS]]">>
+
+This example uses the `isodate` field of this tiddler containing "<$view field="isodate"/>":
+
+<<.operator-example 2 "[get[isodate]parsedate[JS]]">>
+
+The following example uses a HTML5 date and time picker in combination with the `parsedate` operator to convert the output of the HTML5 input element into TiddlyWiki's [[DateFormat|date format]]. It uses the [[format Operator]] to output the date in human-readable form.
+
+<$macrocall $name=".example" n="3"
+eg="""<$edit-text field="isodate" type="datetime-local"/>
<$text text={{{[get[isodate]parsedate[JS]format:date[MMM the DDth YYYY at 0hh:0mm]]}}}/>"""/>
+
+! Time zone examples for format `JS`
+
+The following examples use the predefined variable `utc-datetime` that outputs the date and time in UTC for illustration.
+
+Values that contain date and time but no time zone information are interpreted within the local timezone. This date is midnight on January the 1st, 2011 in the local timezone. Observe how it is converted to UTC.
+
+<<.operator-example 4 "[[2011-01-01T00:00:00]parsedate[JS]format:date]">>
+
+Values that contain only a date are interpreted as UTC. This makes sure that the date is not changed by the conversion. If your time zone is ahead of UTC in the above example, the date will change to December the 31st, 2010. In this example, the date will be correctly represented.
+
+<<.operator-example 5 "[[2011-01-01]parsedate[JS]format:date]">>
+
+Appending a `Z` to the Date and Time information makes sure it is always interpreted as UTC. The local time zone is ignored.
+
+<<.operator-example 6 "[[2011-01-01T00:00:00Z]parsedate[JS]format:date]">>
+
+Instead of `Z` a time zone offset can be specified. This offset will be used to interprete the date and time information. The local time zone is ignored.
+
+<<.operator-example 7 "[[2011-01-01T00:00:00+05:00]parsedate[JS]format:date]">>
\ No newline at end of file
diff --git a/editions/tw5.com/tiddlers/filters/parsedate.tid b/editions/tw5.com/tiddlers/filters/parsedate.tid
new file mode 100644
index 000000000..3c68833e6
--- /dev/null
+++ b/editions/tw5.com/tiddlers/filters/parsedate.tid
@@ -0,0 +1,22 @@
+caption: parsedate
+created: 20220110203115000
+modified: 20220110203115000
+op-input: a [[selection of titles|Title Selection]]
+op-output: the date stored within the input string converted to the default [[date format|DateFormat]] <<.place B>>
+op-parameter: source format
+op-parameter-name: B
+op-purpose: parse the input string and convert it into the default [[date format|DateFormat]]
+tags: [[Filter Operators]] [[Date Operators]]
+title: parsedate Operator
+type: text/vnd.tiddlywiki
+
+<<.from-version "5.2.2">>
+
+The parameter <<.place B>> is one of the following supported input formats:
+
+|!Format |!Description |
+|^`JS` |The input string is interpreted as a date in [[ECMAScript date format|https://tc39.es/ecma262/#sec-date-time-string-format]]. This format is a subset of ISO 8601. It is used for example by the HTML5 date/time input fields. Expanded years are ''not supported''.|
+
+If the input string does not contain a valid date, the output is an empty list.
+
+<<.operator-examples "parsedate">>