From 763341eacffb82116a86f3ad9a2eb40689d0cf25 Mon Sep 17 00:00:00 2001 From: flammermann Date: Tue, 2 Jan 2024 22:38:50 +0000 Subject: [PATCH] Support date properties in facet search - move parseDateField --- umap/static/umap/js/umap.controls.js | 2 +- umap/static/umap/js/umap.core.js | 60 ++++++++++++++++++++++++++++ umap/static/umap/js/umap.features.js | 12 +----- 3 files changed, 62 insertions(+), 12 deletions(-) diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js index 77e7d0f8..fc0bced2 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -695,7 +695,7 @@ const ControlsMixin = { keys.forEach((key) => { let value = feature.properties[key] if (facetKeys[key]["type"] === "date") { - value = feature.parseDateField(value) + value = L.Util.parseDateField(value) if (!!value && (!facetCriteria[key]["min"] || facetCriteria[key]["min"] > value)) { facetCriteria[key]["min"] = value } diff --git a/umap/static/umap/js/umap.core.js b/umap/static/umap/js/umap.core.js index 4981086b..47468070 100644 --- a/umap/static/umap/js/umap.core.js +++ b/umap/static/umap/js/umap.core.js @@ -67,6 +67,66 @@ L.Util.setNullableBooleanFromQueryString = function (options, name) { } } +// the Date() constructor can handle various inputs to create a date +// - value: epoch (unix timestamp) in milliseconds +// - dateString: ISO 8601 formatted (YYYY-MM-DDTHH:mm:ss.sssZ) +// - dateObject: JS date object +// - multiple parameters for different date fields (year, month, ...) +// +// a mix of those options shall be supported without the user having +// to specify the exact format, since umap is based on json the type +// of the feature property value can only be +// - string, number or boolean +// - object or array +// - null +// +// therefore, the following inputs shall be covered +// - epoch (unix timestamp) in milliseconds as number +// - epoch (unix timestamp) in seconds as number +// - epoch (unix timestamp) in milliseconds as string +// - epoch (unix timestamp) in seconds as string +// - date in ISO 8601 format as string +// +// this function tries to guess the format of the feature property value +// and adjust it a little before passing it to Date() constructor +// +L.Util.parseDateField = function (value) { + if (value != null && parseFloat(value).toString() === value.toString()) { + // if the string representation of the feature property value is + // the same with and without being parsed as a float, the value is + // a number (either of type number or string) + // + // numbers are assumed to be epochs (unix timestamps) but so far + // it is unclear whether it is in seconds, milliseconds or nanoseconds + // + // without user input it can never be determined with certainty, but + // by making some assumptions and sacrificing some small date ranges, + // it is possible to work around that + // + value = parseFloat(value); + if (Math.abs(value) < 10000000000) { + // if the absolute value of that number is smaller than 10000000000, + // it is assumed to be in seconds and must be multiplied by 1000 + // + value = value * 1000; + } else if (Math.abs(value) > 10000000000000) { + // if the absolute value of that number is bigger than 10000000000000, + // it is assumed to be in nanoseconds and must be divided by 1000 + // + value = value / 1000; + } + // in all other cases the number is assumed to be in milliseconds + } + // in all other cases the value is passed to the Date() constructor + // without modification and the constructor will try to make something out of it + // + // this can either result in something proper (e.g. string containing dateString), + // something wrong (e.g. boolean) or an invalid date (e.g. object, array, null, + // string not containing dateString) + // + return new Date(value); +} + L.DomUtil.add = (tagName, className, container, content) => { const el = L.DomUtil.create(tagName, className, container) if (content) { diff --git a/umap/static/umap/js/umap.features.js b/umap/static/umap/js/umap.features.js index 297da47b..d3c14dab 100644 --- a/umap/static/umap/js/umap.features.js +++ b/umap/static/umap/js/umap.features.js @@ -492,16 +492,6 @@ U.FeatureMixin = { return false }, - parseDateField: function (value) { - if (parseFloat(value).toString() === value.toString()) { - value = parseFloat(value); - if (value < 10000000000) { - value = value * 1000; - } - } - return new Date(value); - }, - matchFacets: function () { const facets = this.map.facets for (const [property, criteria] of Object.entries(facets)) { @@ -510,7 +500,7 @@ U.FeatureMixin = { if (type === "date") { const min = new Date(criteria["min"]) const max = new Date(criteria["max"]) - value = this.parseDateField(value) + value = L.Util.parseDateField(value) if (!!min && (!value || min > value)) return false if (!!max && (!value || max < value)) return false } else {