소스 검색

fix: Date and Time throws on out-of-range offsets and non-numeric amounts

offsetMinutes accepted shapes like +99:99, silently producing a
timestamp wrong by roughly a hundred hours. Now range-checked against
the real -12:00 to +14:00 bounds. The amount coercion previously used
(Number(config.amount) || 0), which turned a typo like "3d" into a
silent no-op shift instead of a failure. Also documents the diff sign
convention in the secondField description.
fszontagh 1 개월 전
부모
커밋
384029c9b0
4개의 변경된 파일과 54개의 추가작업 그리고 6개의 파일을 삭제
  1. 13 3
      nodes/core/datetime.js
  2. 17 0
      tests/nodes/datetime-errors-amount.json
  3. 17 0
      tests/nodes/datetime-errors-offset.json
  4. 7 3
      tests/nodes/datetime.json

+ 13 - 3
nodes/core/datetime.js

@@ -25,7 +25,7 @@ const configSchema = {
         secondField: {
             type: 'string',
             title: 'Second Date Field',
-            description: 'Path to the date to compare against, for the diff operation'
+            description: 'Path to the date to compare against, for the diff operation. diff returns the input date minus this one, so a later input gives a positive result'
         },
         format: {
             type: 'string',
@@ -106,7 +106,13 @@ function offsetMinutes(offset) {
     if (!match) {
         throw new Error('Date and Time: offset must look like +02:00, got "' + offset + '"');
     }
-    const minutes = parseInt(match[2], 10) * 60 + parseInt(match[3], 10);
+    const hours = parseInt(match[2], 10);
+    const mins = parseInt(match[3], 10);
+    if (hours > 14 || mins > 59) {
+        throw new Error('Date and Time: offset "' + offset +
+            '" is out of range. Real offsets run from -12:00 to +14:00');
+    }
+    const minutes = hours * 60 + mins;
     return match[1] === '-' ? -minutes : minutes;
 }
 
@@ -190,7 +196,11 @@ async function execute(config, input, context) {
         return result;
     }
 
-    const delta = (Number(config.amount) || 0) * step;
+    const amount = Number(config.amount);
+    if (isNaN(amount)) {
+        throw new Error('Date and Time: amount must be a number, got "' + config.amount + '"');
+    }
+    const delta = amount * step;
     const shifted = operation === 'subtract' ? timestamp - delta : timestamp + delta;
     result[outputField] = formatTimestamp(shifted, pattern, offset);
     result.timestamp = shifted;

+ 17 - 0
tests/nodes/datetime-errors-amount.json

@@ -0,0 +1,17 @@
+{
+  "name": "verify-datetime-errors-amount",
+  "nodes": [
+    {"id": "n1", "name": "Trigger", "type": "click-trigger", "position": {"x": 0, "y": 0}, "config": {}},
+    {"id": "n2", "name": "Fixture", "type": "code", "position": {"x": 0, "y": 100},
+     "config": {"code": "return { when: '2026-01-15T10:30:00Z' };"}},
+    {"id": "n3", "name": "DatetimeBadAmount", "type": "datetime", "position": {"x": 0, "y": 200},
+     "config": {"operation": "add", "inputField": "data.result.when", "amount": "3d", "unit": "days", "outputField": "value"}}
+  ],
+  "connections": [
+    {"sourceNodeId": "n1", "sourceOutput": "main", "targetNodeId": "n2", "targetInput": "data"},
+    {"sourceNodeId": "n2", "sourceOutput": "main", "targetNodeId": "n3", "targetInput": "data"}
+  ],
+  "expect": {
+    "n3": {"status": "failed"}
+  }
+}

+ 17 - 0
tests/nodes/datetime-errors-offset.json

@@ -0,0 +1,17 @@
+{
+  "name": "verify-datetime-errors-offset",
+  "nodes": [
+    {"id": "n1", "name": "Trigger", "type": "click-trigger", "position": {"x": 0, "y": 0}, "config": {}},
+    {"id": "n2", "name": "Fixture", "type": "code", "position": {"x": 0, "y": 100},
+     "config": {"code": "return { when: '2026-01-15T10:30:00Z' };"}},
+    {"id": "n3", "name": "DatetimeOutOfRangeOffset", "type": "datetime", "position": {"x": 0, "y": 200},
+     "config": {"operation": "format", "inputField": "data.result.when", "offset": "+99:99", "outputField": "value"}}
+  ],
+  "connections": [
+    {"sourceNodeId": "n1", "sourceOutput": "main", "targetNodeId": "n2", "targetInput": "data"},
+    {"sourceNodeId": "n2", "sourceOutput": "main", "targetNodeId": "n3", "targetInput": "data"}
+  ],
+  "expect": {
+    "n3": {"status": "failed"}
+  }
+}

+ 7 - 3
tests/nodes/datetime.json

@@ -13,7 +13,9 @@
     {"id": "n6", "name": "Diff", "type": "datetime", "position": {"x": 150, "y": 200},
      "config": {"operation": "diff", "inputField": "data.result.later", "secondField": "data.result.when", "unit": "days", "outputField": "value"}},
     {"id": "n7", "name": "Now", "type": "datetime", "position": {"x": 250, "y": 200},
-     "config": {"operation": "now"}}
+     "config": {"operation": "now"}},
+    {"id": "n8", "name": "FormatMaxOffset", "type": "datetime", "position": {"x": 350, "y": 200},
+     "config": {"operation": "format", "inputField": "data.result.when", "format": "YYYY-MM-DD HH:mm:ss", "offset": "+14:00", "outputField": "value"}}
   ],
   "connections": [
     {"sourceNodeId": "n1", "sourceOutput": "main", "targetNodeId": "n2", "targetInput": "data"},
@@ -21,13 +23,15 @@
     {"sourceNodeId": "n2", "sourceOutput": "main", "targetNodeId": "n4", "targetInput": "data"},
     {"sourceNodeId": "n2", "sourceOutput": "main", "targetNodeId": "n5", "targetInput": "data"},
     {"sourceNodeId": "n2", "sourceOutput": "main", "targetNodeId": "n6", "targetInput": "data"},
-    {"sourceNodeId": "n2", "sourceOutput": "main", "targetNodeId": "n7", "targetInput": "data"}
+    {"sourceNodeId": "n2", "sourceOutput": "main", "targetNodeId": "n7", "targetInput": "data"},
+    {"sourceNodeId": "n2", "sourceOutput": "main", "targetNodeId": "n8", "targetInput": "data"}
   ],
   "expect": {
     "n3": {"status": "completed", "output": {"value": "2026-01-15 10:30:00"}},
     "n4": {"status": "completed", "output": {"value": "2026-01-15 12:30:00"}},
     "n5": {"status": "completed", "output": {"value": "2026-01-18"}},
     "n6": {"status": "completed", "output": {"value": 5}},
-    "n7": {"status": "completed"}
+    "n7": {"status": "completed"},
+    "n8": {"status": "completed", "output": {"value": "2026-01-16 00:30:00"}}
   }
 }