Skip to content

Commit 045a3d7

Browse files
authored
JL: support epoch in date/datetime (#1171)
1 parent 8b0ecce commit 045a3d7

File tree

7 files changed

+135
-32
lines changed

7 files changed

+135
-32
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Changelog
22
- 6.6.7
33
- Fix import of ambiguous operators (like, select_any_in) (PR #1168) (issue #1159)
4+
- Allow import of epoch for date/datetime widgets from JsonLogic (PR #1171) (issue #1154)
45
- 6.6.6
56
- Fix issue with process global (PR #1166) (issue #1165)
67
- 6.6.5

packages/core/modules/config/index.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,10 +1023,20 @@ const widgets = {
10231023
jsonLogic: function (val, fieldDef, wgtDef) {
10241024
return this.utils.moment(val, wgtDef.valueFormat).toDate();
10251025
},
1026+
// Example of importing and exporting to epoch timestamp (in ms) for JsonLogic:
1027+
// jsonLogicImport: function(timestamp, wgtDef) {
1028+
// const momentVal = this.utils.moment(timestamp, "x");
1029+
// return momentVal.isValid() ? momentVal.toDate() : undefined;
1030+
// },
1031+
// jsonLogic: function (val, fieldDef, wgtDef) {
1032+
// return this.utils.moment(val, wgtDef.valueFormat).format("x");
1033+
// },
10261034
toJS: function (val, fieldSettings) {
10271035
const dateVal = this.utils.moment(val, fieldSettings.valueFormat);
10281036
return dateVal.isValid() ? dateVal.toDate() : undefined;
10291037
},
1038+
// todo: $toDate (works onliny in $expr)
1039+
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/toDate/
10301040
mongoFormatValue: function (val, fieldDef, wgtDef) {
10311041
const dateVal = this.utils.moment(val, wgtDef.valueFormat);
10321042
return dateVal.isValid() ? dateVal.toDate() : undefined;

packages/core/modules/import/jsonLogic.js

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -300,33 +300,52 @@ const convertValRhs = (val, fieldConfig, widget, config, meta) => {
300300
return undefined;
301301
}
302302

303-
// number of seconds -> time string
304-
if (fieldType === "time" && typeof val === "number") {
305-
const [h, m, s] = [Math.floor(val / 60 / 60) % 24, Math.floor(val / 60) % 60, val % 60];
306-
const valueFormat = widgetConfig.valueFormat;
307-
if (valueFormat) {
308-
const dateVal = new Date(val);
309-
dateVal.setMilliseconds(0);
310-
dateVal.setHours(h);
311-
dateVal.setMinutes(m);
312-
dateVal.setSeconds(s);
313-
val = moment(dateVal).format(valueFormat);
314-
} else {
315-
val = `${h}:${m}:${s}`;
316-
}
317-
}
318303

319-
// "2020-01-08T22:00:00.000Z" -> Date object
320-
if (["date", "datetime"].includes(fieldType) && val && !(val instanceof Date)) {
304+
if (widgetConfig?.jsonLogicImport) {
321305
try {
322-
const dateVal = new Date(val);
323-
if (dateVal instanceof Date && dateVal.toISOString() === val) {
324-
val = dateVal;
325-
}
306+
val = widgetConfig.jsonLogicImport.call(
307+
config.ctx, val,
308+
{...widgetConfig, ...(fieldConfig?.fieldSettings ?? {})}
309+
);
326310
} catch(e) {
327-
meta.errors.push(`Can't convert value ${val} as Date`);
311+
meta.errors.push(`Can't import value ${val} using import func of widget ${widget}: ${e?.message ?? e}`);
328312
val = undefined;
329313
}
314+
} else {
315+
// number of seconds -> time string
316+
if (fieldType === "time" && typeof val === "number") {
317+
const [h, m, s] = [Math.floor(val / 60 / 60) % 24, Math.floor(val / 60) % 60, val % 60];
318+
const valueFormat = widgetConfig.valueFormat;
319+
if (valueFormat) {
320+
const dateVal = new Date(val);
321+
dateVal.setMilliseconds(0);
322+
dateVal.setHours(h);
323+
dateVal.setMinutes(m);
324+
dateVal.setSeconds(s);
325+
val = moment(dateVal).format(valueFormat);
326+
} else {
327+
val = `${h}:${m}:${s}`;
328+
}
329+
}
330+
331+
// "2020-01-08T22:00:00.000Z" -> Date object
332+
if (["date", "datetime"].includes(fieldType) && val && !(val instanceof Date)) {
333+
try {
334+
const isEpoch = typeof val === "number" || typeof val === "string" && !isNaN(val);
335+
// Note: can import only from ms timestamp, not seconds timestamp
336+
const epoch = isEpoch && typeof val === "string" ? parseInt(val) : val;
337+
const dateVal = new Date(isEpoch ? epoch : val);
338+
if (dateVal instanceof Date) {
339+
val = dateVal;
340+
}
341+
if (isNaN(dateVal)) {
342+
throw new Error("Invalid date");
343+
}
344+
} catch(e) {
345+
meta.errors.push(`Can't convert value ${val} as Date`);
346+
val = undefined;
347+
}
348+
}
330349
}
331350

332351
// Date object -> formatted string
@@ -343,15 +362,6 @@ const convertValRhs = (val, fieldConfig, widget, config, meta) => {
343362
asyncListValues = vals;
344363
}
345364

346-
if (widgetConfig?.jsonLogicImport) {
347-
try {
348-
val = widgetConfig.jsonLogicImport.call(config.ctx, val);
349-
} catch(e) {
350-
meta.errors.push(`Can't import value ${val} using import func of widget ${widget}: ${e?.message ?? e}`);
351-
val = undefined;
352-
}
353-
}
354-
355365
return {
356366
valueSrc: "value",
357367
value: val,

packages/core/modules/utils/configSerialize.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ const compileMetaWidget = {
7070
mongoFormatValue: { type: "f", args: ["val", "fieldDef", "wgtDef", "op", "opDef"] },
7171
elasticSearchFormatValue: { type: "f", args: ["queryType", "val", "op", "field", "config"] },
7272
jsonLogic: { type: "f", args: ["val", "fieldDef", "wgtDef", "op", "opDef"] },
73-
jsonLogicImport: { type: "f", args: ["val"] },
73+
jsonLogicImport: { type: "f", args: ["val", "wgtDef"] },
7474
validateValue: { type: "f", args: ["val", "fieldSettings", "op", "opDef", "rightFieldDef"] }, // obsolete
7575
toJS: { type: "f", args: ["val"] },
7676
};

packages/tests/specs/Basic.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,46 @@ describe("basic query", () => {
229229
]);
230230
});
231231

232+
describe("should import @epoch timestamp ms from JL", () => {
233+
export_checks([configs.with_date_and_time], inits.with_date_epoch_ms, "JsonLogic", {
234+
logic: {
235+
"and": [
236+
{
237+
"==": [
238+
{
239+
"var": "datetime"
240+
},
241+
"2025-01-13T15:39:28.000Z"
242+
]
243+
}
244+
]
245+
}
246+
});
247+
});
248+
249+
describe("should import @epoch timestamp sec from JL if configured", () => {
250+
export_checks([configs.with_date_and_time, configs.with_datetime_import_epoch_sec_jl], inits.with_date_epoch, "JsonLogic", {
251+
logic: {
252+
"and": [
253+
{
254+
"==": [
255+
{
256+
"var": "datetime"
257+
},
258+
"2025-01-13T15:39:28.000Z"
259+
]
260+
}
261+
]
262+
}
263+
});
264+
});
265+
266+
describe("should export @epoch timestamp ms to JL if configured", () => {
267+
export_checks([configs.with_date_and_time, configs.with_datetime_export_epoch_ms_jl], inits.with_date_epoch_ms, "JsonLogic", {
268+
logic: inits.with_date_epoch_ms
269+
});
270+
});
271+
232272
});
233273

234274
describe("export", () => {

packages/tests/support/configs.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,32 @@ export const with_date_and_time = (BasicConfig) => ({
172172
},
173173
});
174174

175+
export const with_datetime_import_epoch_sec_jl = (BasicConfig) => ({
176+
...BasicConfig,
177+
widgets: {
178+
...BasicConfig.widgets,
179+
datetime: {
180+
...BasicConfig.widgets.datetime,
181+
jsonLogicImport: function(timestamp, wgtDef) {
182+
const momentVal = this.utils.moment(timestamp, "X");
183+
return momentVal.isValid() ? momentVal.toDate() : undefined;
184+
},
185+
}
186+
}
187+
});
188+
189+
export const with_datetime_export_epoch_ms_jl = (BasicConfig) => ({
190+
...BasicConfig,
191+
widgets: {
192+
...BasicConfig.widgets,
193+
datetime: {
194+
...BasicConfig.widgets.datetime,
195+
jsonLogic: function (val, fieldDef, wgtDef) {
196+
return this.utils.moment(val, wgtDef.valueFormat).format("x");
197+
},
198+
}
199+
}
200+
});
175201

176202
export const with_theme_material = (BasicConfig) => ({
177203
...BasicConfig,

packages/tests/support/inits.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,22 @@ export const with_date_and_time = {
290290
}]
291291
};
292292

293+
export const with_date_epoch = {
294+
"and": [
295+
{
296+
"==": [ { "var": "datetime" }, "1736782768" ]
297+
}
298+
]
299+
};
300+
301+
export const with_date_epoch_ms = {
302+
"and": [
303+
{
304+
"==": [ { "var": "datetime" }, "1736782768000" ]
305+
}
306+
]
307+
};
308+
293309
export const with_select_and_multiselect = {
294310
"and": [{
295311
"==": [ { "var": "color" }, "yellow" ]

0 commit comments

Comments
 (0)