From a0f42708d4f93ff17e76b0180d15c5dec12633b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Sat, 17 Apr 2021 10:34:48 +0200 Subject: [PATCH] :sparkles: Add calculate functionality to DateTime Node (#1589) * :zap: DateTime node improvements * :hammer: Fix parsing format in date calculation * :zap: Small improvements * :pencil2: Rename params and add tooltips * :hammer: Reorder params * :zap: Set default values * :zap: Set defaults and use same parameter name Co-authored-by: ricardo Co-authored-by: Jan Oberhauser --- packages/nodes-base/nodes/DateTime.node.ts | 239 ++++++++++++++++++++- 1 file changed, 231 insertions(+), 8 deletions(-) diff --git a/packages/nodes-base/nodes/DateTime.node.ts b/packages/nodes-base/nodes/DateTime.node.ts index a5dabb43a0..8279d654b4 100644 --- a/packages/nodes-base/nodes/DateTime.node.ts +++ b/packages/nodes-base/nodes/DateTime.node.ts @@ -1,7 +1,8 @@ -import * as moment from 'moment-timezone'; -import { set } from 'lodash'; -import { IExecuteFunctions } from 'n8n-core'; +import { + IExecuteFunctions, +} from 'n8n-core'; + import { IDataObject, ILoadOptionsFunctions, @@ -12,6 +13,11 @@ import { NodeOperationError, } from 'n8n-workflow'; +import { + set, +} from 'lodash'; + +import * as moment from 'moment-timezone'; export class DateTime implements INodeType { description: INodeTypeDescription = { @@ -21,6 +27,7 @@ export class DateTime implements INodeType { group: ['transform'], version: 1, description: 'Allows you to manipulate date and time values', + subtitle: '={{$parameter["action"]}}', defaults: { name: 'Date & Time', color: '#408000', @@ -33,6 +40,11 @@ export class DateTime implements INodeType { name: 'action', type: 'options', options: [ + { + name: 'Calculate a Date', + description: 'Add or subtract time from a date', + value: 'calculate', + }, { name: 'Format a Date', description: 'Convert a date to a different format', @@ -76,7 +88,7 @@ export class DateTime implements INodeType { name: 'custom', displayOptions: { show: { - action:[ + action: [ 'format', ], }, @@ -90,7 +102,7 @@ export class DateTime implements INodeType { name: 'toFormat', displayOptions: { show: { - action:[ + action: [ 'format', ], custom: [ @@ -109,10 +121,10 @@ export class DateTime implements INodeType { type: 'options', displayOptions: { show: { - action:[ + action: [ 'format', ], - custom:[ + custom: [ false, ], }, @@ -162,7 +174,7 @@ export class DateTime implements INodeType { name: 'options', displayOptions: { show: { - action:[ + action: [ 'format', ], }, @@ -200,6 +212,156 @@ export class DateTime implements INodeType { }, ], }, + { + displayName: 'Date Value', + name: 'value', + displayOptions: { + show: { + action: [ + 'calculate', + ], + }, + }, + type: 'string', + default: '', + description: 'The date string or timestamp from which you want to add/subtract time.', + required: true, + }, + { + displayName: 'Operation', + name: 'operation', + displayOptions: { + show: { + action: [ + 'calculate', + ], + }, + }, + type: 'options', + options: [ + { + name: 'Add', + value: 'add', + description: 'Add time to Date Value', + }, + { + name: 'Subtract', + value: 'subtract', + description: 'Subtract time from Date Value', + }, + ], + default: 'add', + required: true, + }, + { + displayName: 'Duration', + name: 'duration', + displayOptions: { + show: { + action: [ + 'calculate', + ], + }, + }, + type: 'number', + typeOptions: { + minValue: 0, + }, + default: 0, + required: true, + description: 'E.g. enter “10” then select “Days” if you want to add 10 days to Date Value.', + }, + { + displayName: 'Time Unit', + name: 'timeUnit', + description: 'Time unit for Duration parameter above.', + displayOptions: { + show: { + action: [ + 'calculate', + ], + }, + }, + type: 'options', + options: [ + { + name: 'Quarters', + value: 'quarters', + }, + { + name: 'Years', + value: 'years', + }, + { + name: 'Months', + value: 'months', + }, + { + name: 'Weeks', + value: 'weeks', + }, + { + name: 'Days', + value: 'days', + }, + { + name: 'Hours', + value: 'hours', + }, + { + name: 'Minutes', + value: 'minutes', + }, + { + name: 'Seconds', + value: 'seconds', + }, + { + name: 'Milliseconds', + value: 'milliseconds', + }, + ], + default: 'days', + required: true, + }, + { + displayName: 'Property Name', + name: 'dataPropertyName', + type: 'string', + default: 'data', + required: true, + displayOptions: { + show: { + action: [ + 'calculate', + ], + }, + }, + description: 'Name of the output property to which to write the converted date.', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + action: [ + 'calculate', + ], + }, + }, + options: [ + { + displayName: 'From Format', + name: 'fromFormat', + type: 'string', + default: '', + description: 'Format for parsing the value as a date. If unrecognized, specify the format for the value.', + }, + ], + }, ], }; @@ -297,8 +459,69 @@ export class DateTime implements INodeType { returnData.push(newItem); } + + if (action === 'calculate') { + + const dateValue = this.getNodeParameter('value', i) as string; + const operation = this.getNodeParameter('operation', i) as 'add' | 'subtract'; + const duration = this.getNodeParameter('duration', i) as number; + const timeUnit = this.getNodeParameter('timeUnit', i) as moment.DurationInputArg2; + const { fromFormat } = this.getNodeParameter('options', i) as { fromFormat?: string }; + const dataPropertyName = this.getNodeParameter('dataPropertyName', i) as string; + + const newDate = fromFormat + ? parseDateByFormat(dateValue, fromFormat) + : parseDateByDefault(dateValue); + + operation === 'add' + ? newDate.add(duration, timeUnit).utc().format() + : newDate.subtract(duration, timeUnit).utc().format(); + + let newItem: INodeExecutionData; + if (dataPropertyName.includes('.')) { + // Uses dot notation so copy all data + newItem = { + json: JSON.parse(JSON.stringify(item.json)), + }; + } else { + // Does not use dot notation so shallow copy is enough + newItem = { + json: { ...item.json }, + }; + } + + if (item.binary !== undefined) { + newItem.binary = item.binary; + } + + set(newItem, `json.${dataPropertyName}`, newDate); + + returnData.push(newItem); + } } return this.prepareOutputData(returnData); } } + +function parseDateByFormat(value: string, fromFormat: string) { + const date = moment(value, fromFormat, true); + if (moment(date).isValid()) return date; + + throw new Error('Date input cannot be parsed. Please recheck the value and the "From Format" field.'); +} + +function parseDateByDefault(value: string) { + const isoValue = getIsoValue(value); + if (moment(isoValue).isValid()) return moment(isoValue); + + throw new Error('Unrecognized date input. Please specify a format in the "From Format" field.'); +} + +function getIsoValue(value: string) { + try { + return new Date(value).toISOString(); // may throw due to unpredictable input + } catch (error) { + throw new Error('Unrecognized date input. Please specify a format in the "From Format" field.'); + } +}