diff --git a/packages/nodes-base/nodes/DateTime/DateTime.node.ts b/packages/nodes-base/nodes/DateTime/DateTime.node.ts
index 7dcbb06655758..478fac354d7d8 100644
--- a/packages/nodes-base/nodes/DateTime/DateTime.node.ts
+++ b/packages/nodes-base/nodes/DateTime/DateTime.node.ts
@@ -1,579 +1,27 @@
-import type {
- IDataObject,
- IExecuteFunctions,
- ILoadOptionsFunctions,
- INodeExecutionData,
- INodePropertyOptions,
- INodeType,
- INodeTypeDescription,
-} from 'n8n-workflow';
-
-import { deepCopy, NodeOperationError } from 'n8n-workflow';
-
-import set from 'lodash.set';
-
-import moment from 'moment-timezone';
-
-import { DateTime as LuxonDateTime } from 'luxon';
-
-function parseDateByFormat(this: IExecuteFunctions, value: string, fromFormat: string) {
- const date = moment(value, fromFormat, true);
- if (moment(date).isValid()) return date;
-
- throw new NodeOperationError(
- this.getNode(),
- 'Date input cannot be parsed. Please recheck the value and the "From Format" field.',
- );
-}
-
-function getIsoValue(this: IExecuteFunctions, value: string) {
- try {
- return new Date(value).toISOString(); // may throw due to unpredictable input
- } catch (error) {
- throw new NodeOperationError(
- this.getNode(),
- 'Unrecognized date input. Please specify a format in the "From Format" field.',
- );
- }
-}
-
-function parseDateByDefault(this: IExecuteFunctions, value: string) {
- const isoValue = getIsoValue.call(this, value);
- if (moment(isoValue).isValid()) return moment(isoValue);
-
- throw new NodeOperationError(
- this.getNode(),
- 'Unrecognized date input. Please specify a format in the "From Format" field.',
- );
-}
-
-export class DateTime implements INodeType {
- description: INodeTypeDescription = {
- displayName: 'Date & Time',
- name: 'dateTime',
- icon: 'fa:clock',
- group: ['transform'],
- version: 1,
- description: 'Allows you to manipulate date and time values',
- subtitle: '={{$parameter["action"]}}',
- defaults: {
- name: 'Date & Time',
- color: '#408000',
- },
- inputs: ['main'],
- outputs: ['main'],
- properties: [
- {
- displayName:
- "More powerful date functionality is available in expressions, e.g. {{ $now.plus(1, 'week') }}
",
- name: 'noticeDateTime',
- type: 'notice',
- default: '',
- },
- {
- displayName: 'Action',
- name: 'action',
- type: 'options',
- options: [
- {
- name: 'Calculate a Date',
- description: 'Add or subtract time from a date',
- value: 'calculate',
- action: 'Add or subtract time from a date',
- },
- {
- name: 'Format a Date',
- description: 'Convert a date to a different format',
- value: 'format',
- action: 'Convert a date to a different format',
- },
- ],
- default: 'format',
- },
- {
- displayName: 'Value',
- name: 'value',
- displayOptions: {
- show: {
- action: ['format'],
- },
- },
- type: 'string',
- default: '',
- description: 'The value that should be converted',
- required: true,
- },
- {
- displayName: 'Property Name',
- name: 'dataPropertyName',
- type: 'string',
- default: 'data',
- required: true,
- displayOptions: {
- show: {
- action: ['format'],
- },
- },
- description: 'Name of the property to which to write the converted date',
- },
- {
- displayName: 'Custom Format',
- name: 'custom',
- displayOptions: {
- show: {
- action: ['format'],
- },
- },
- type: 'boolean',
- default: false,
- description: 'Whether a predefined format should be selected or custom format entered',
- },
- {
- displayName: 'To Format',
- name: 'toFormat',
- displayOptions: {
- show: {
- action: ['format'],
- custom: [true],
- },
- },
- type: 'string',
- default: '',
- placeholder: 'YYYY-MM-DD',
- description: 'The format to convert the date to',
- },
- {
- displayName: 'To Format',
- name: 'toFormat',
- type: 'options',
- displayOptions: {
- show: {
- action: ['format'],
- custom: [false],
- },
- },
- // eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
- options: [
- {
- name: 'MM/DD/YYYY',
- value: 'MM/DD/YYYY',
- description: 'Example: 09/04/1986',
- },
- {
- name: 'YYYY/MM/DD',
- value: 'YYYY/MM/DD',
- description: 'Example: 1986/04/09',
- },
- {
- name: 'MMMM DD YYYY',
- value: 'MMMM DD YYYY',
- description: 'Example: April 09 1986',
- },
- {
- name: 'MM-DD-YYYY',
- value: 'MM-DD-YYYY',
- description: 'Example: 09-04-1986',
- },
- {
- name: 'YYYY-MM-DD',
- value: 'YYYY-MM-DD',
- description: 'Example: 1986-04-09',
- },
- {
- name: 'Unix Timestamp',
- value: 'X',
- description: 'Example: 513388800.879',
- },
- {
- name: 'Unix Ms Timestamp',
- value: 'x',
- description: 'Example: 513388800',
- },
- ],
- default: 'MM/DD/YYYY',
- description: 'The format to convert the date to',
- },
- {
- displayName: 'Options',
- name: 'options',
- displayOptions: {
- show: {
- action: ['format'],
- },
- },
- type: 'collection',
- placeholder: 'Add Option',
- default: {},
- options: [
- {
- displayName: 'From Format',
- name: 'fromFormat',
- type: 'string',
- default: '',
- description: 'In case the input format is not recognized you can provide the format',
- },
- {
- displayName: 'From Timezone Name or ID',
- name: 'fromTimezone',
- type: 'options',
- typeOptions: {
- loadOptionsMethod: 'getTimezones',
- },
- default: 'UTC',
- description:
- 'The timezone to convert from. Choose from the list, or specify an ID using an expression.',
- },
- {
- displayName: 'To Timezone Name or ID',
- name: 'toTimezone',
- type: 'options',
- typeOptions: {
- loadOptionsMethod: 'getTimezones',
- },
- default: 'UTC',
- description:
- 'The timezone to convert to. Choose from the list, or specify an ID using an expression.',
- },
- ],
- },
- {
- 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',
- noDataExpression: true,
- options: [
- {
- name: 'Add',
- value: 'add',
- description: 'Add time to Date Value',
- action: 'Add time to Date Value',
- },
- {
- name: 'Subtract',
- value: 'subtract',
- description: 'Subtract time from Date Value',
- action: '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',
- // eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
- 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.',
- },
- ],
- },
- ],
- };
-
- methods = {
- loadOptions: {
- // Get all the timezones to display them to user so that they can
- // select them easily
- async getTimezones(this: ILoadOptionsFunctions): Promise {
- const returnData: INodePropertyOptions[] = [];
- for (const timezone of moment.tz.names()) {
- const timezoneName = timezone;
- const timezoneId = timezone;
- returnData.push({
- name: timezoneName,
- value: timezoneId,
- });
- }
- return returnData;
- },
- },
- };
-
- async execute(this: IExecuteFunctions): Promise {
- const items = this.getInputData();
- const length = items.length;
- const returnData: INodeExecutionData[] = [];
-
- const workflowTimezone = this.getTimezone();
- let item: INodeExecutionData;
-
- for (let i = 0; i < length; i++) {
- try {
- const action = this.getNodeParameter('action', 0) as string;
- item = items[i];
-
- if (action === 'format') {
- let currentDate: string | number | LuxonDateTime = this.getNodeParameter(
- 'value',
- i,
- ) as string;
- const dataPropertyName = this.getNodeParameter('dataPropertyName', i);
- const toFormat = this.getNodeParameter('toFormat', i) as string;
- const options = this.getNodeParameter('options', i);
- let newDate;
-
- if ((currentDate as unknown as IDataObject) instanceof LuxonDateTime) {
- currentDate = (currentDate as unknown as LuxonDateTime).toISO();
- }
-
- // Check if the input is a number
- if (!Number.isNaN(Number(currentDate))) {
- //input is a number, convert to number in case it is a string
- currentDate = Number(currentDate);
- // check if the number is a timestamp in float format and convert to integer
- if (!Number.isInteger(currentDate)) {
- currentDate = currentDate * 1000;
- }
- }
-
- if (currentDate === undefined) {
- continue;
- }
- if (options.fromFormat === undefined && !moment(currentDate).isValid()) {
- throw new NodeOperationError(
- this.getNode(),
- 'The date input format could not be recognized. Please set the "From Format" field',
- { itemIndex: i },
- );
- }
-
- if (Number.isInteger(currentDate)) {
- const timestampLengthInMilliseconds1990 = 12;
- // check if the number is a timestamp in seconds or milliseconds and create a moment object accordingly
- if (currentDate.toString().length < timestampLengthInMilliseconds1990) {
- newDate = moment.unix(currentDate as number);
- } else {
- newDate = moment(currentDate);
- }
- } else {
- if (options.fromTimezone || options.toTimezone) {
- const fromTimezone = options.fromTimezone || workflowTimezone;
- if (options.fromFormat) {
- newDate = moment.tz(
- currentDate as string,
- options.fromFormat as string,
- fromTimezone as string,
- );
- } else {
- newDate = moment.tz(currentDate, fromTimezone as string);
- }
- } else {
- if (options.fromFormat) {
- newDate = moment(currentDate, options.fromFormat as string);
- } else {
- newDate = moment(currentDate);
- }
- }
- }
-
- if (options.toTimezone || options.fromTimezone) {
- // If either a source or a target timezone got defined the
- // timezone of the date has to be changed. If a target-timezone
- // is set use it else fall back to workflow timezone.
- newDate = newDate.tz((options.toTimezone as string) || workflowTimezone);
- }
-
- newDate = newDate.format(toFormat);
-
- let newItem: INodeExecutionData;
- if (dataPropertyName.includes('.')) {
- // Uses dot notation so copy all data
- newItem = {
- json: deepCopy(item.json),
- pairedItem: {
- item: i,
- },
- };
- } else {
- // Does not use dot notation so shallow copy is enough
- newItem = {
- json: { ...item.json },
- pairedItem: {
- item: i,
- },
- };
- }
-
- if (item.binary !== undefined) {
- newItem.binary = item.binary;
- }
-
- set(newItem, `json.${dataPropertyName}`, newDate);
-
- 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);
-
- const newDate = fromFormat
- ? parseDateByFormat.call(this, dateValue, fromFormat)
- : parseDateByDefault.call(this, 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: deepCopy(item.json),
- pairedItem: {
- item: i,
- },
- };
- } else {
- // Does not use dot notation so shallow copy is enough
- newItem = {
- json: { ...item.json },
- pairedItem: {
- item: i,
- },
- };
- }
-
- if (item.binary !== undefined) {
- newItem.binary = item.binary;
- }
-
- set(newItem, `json.${dataPropertyName}`, newDate.toISOString());
-
- returnData.push(newItem);
- }
- } catch (error) {
- if (this.continueOnFail()) {
- returnData.push({
- json: {
- error: error.message,
- },
- pairedItem: {
- item: i,
- },
- });
- continue;
- }
- throw error;
- }
- }
-
- return this.prepareOutputData(returnData);
+import type { INodeTypeBaseDescription, IVersionedNodeType } from 'n8n-workflow';
+import { VersionedNodeType } from 'n8n-workflow';
+
+import { DateTimeV1 } from './V1/DateTimeV1.node';
+
+import { DateTimeV2 } from './V2/DateTimeV2.node';
+
+export class DateTime extends VersionedNodeType {
+ constructor() {
+ const baseDescription: INodeTypeBaseDescription = {
+ displayName: 'Date & Time',
+ name: 'dateTime',
+ icon: 'fa:clock',
+ group: ['transform'],
+ defaultVersion: 2,
+ description: 'Allows you to manipulate date and time values',
+ subtitle: '={{$parameter["action"]}}',
+ };
+
+ const nodeVersions: IVersionedNodeType['nodeVersions'] = {
+ 1: new DateTimeV1(baseDescription),
+ 2: new DateTimeV2(baseDescription),
+ };
+
+ super(nodeVersions, baseDescription);
}
}
diff --git a/packages/nodes-base/nodes/DateTime/V1/DateTimeV1.node.ts b/packages/nodes-base/nodes/DateTime/V1/DateTimeV1.node.ts
new file mode 100644
index 0000000000000..6e039a6fa6382
--- /dev/null
+++ b/packages/nodes-base/nodes/DateTime/V1/DateTimeV1.node.ts
@@ -0,0 +1,590 @@
+/* eslint-disable n8n-nodes-base/node-filename-against-convention */
+import type {
+ IDataObject,
+ IExecuteFunctions,
+ ILoadOptionsFunctions,
+ INodeExecutionData,
+ INodePropertyOptions,
+ INodeType,
+ INodeTypeBaseDescription,
+ INodeTypeDescription,
+} from 'n8n-workflow';
+
+import { deepCopy, NodeOperationError } from 'n8n-workflow';
+
+import set from 'lodash.set';
+
+import moment from 'moment-timezone';
+
+import { DateTime as LuxonDateTime } from 'luxon';
+
+function parseDateByFormat(this: IExecuteFunctions, value: string, fromFormat: string) {
+ const date = moment(value, fromFormat, true);
+ if (moment(date).isValid()) return date;
+
+ throw new NodeOperationError(
+ this.getNode(),
+ 'Date input cannot be parsed. Please recheck the value and the "From Format" field.',
+ );
+}
+
+function getIsoValue(this: IExecuteFunctions, value: string) {
+ try {
+ return new Date(value).toISOString(); // may throw due to unpredictable input
+ } catch (error) {
+ throw new NodeOperationError(
+ this.getNode(),
+ 'Unrecognized date input. Please specify a format in the "From Format" field.',
+ );
+ }
+}
+
+function parseDateByDefault(this: IExecuteFunctions, value: string) {
+ const isoValue = getIsoValue.call(this, value);
+ if (moment(isoValue).isValid()) return moment(isoValue);
+
+ throw new NodeOperationError(
+ this.getNode(),
+ 'Unrecognized date input. Please specify a format in the "From Format" field.',
+ );
+}
+
+const versionDescription: INodeTypeDescription = {
+ displayName: 'Date & Time',
+ name: 'dateTime',
+ icon: 'fa:clock',
+ group: ['transform'],
+ version: 1,
+ description: 'Allows you to manipulate date and time values',
+ subtitle: '={{$parameter["action"]}}',
+ defaults: {
+ name: 'Date & Time',
+ color: '#408000',
+ },
+ inputs: ['main'],
+ outputs: ['main'],
+ properties: [
+ {
+ displayName:
+ "More powerful date functionality is available in expressions, e.g. {{ $now.plus(1, 'week') }}
",
+ name: 'noticeDateTime',
+ type: 'notice',
+ default: '',
+ },
+ {
+ displayName: 'Action',
+ name: 'action',
+ type: 'options',
+ options: [
+ {
+ name: 'Calculate a Date',
+ description: 'Add or subtract time from a date',
+ value: 'calculate',
+ action: 'Add or subtract time from a date',
+ },
+ {
+ name: 'Format a Date',
+ description: 'Convert a date to a different format',
+ value: 'format',
+ action: 'Convert a date to a different format',
+ },
+ ],
+ default: 'format',
+ },
+ {
+ displayName: 'Value',
+ name: 'value',
+ displayOptions: {
+ show: {
+ action: ['format'],
+ },
+ },
+ type: 'string',
+ default: '',
+ description: 'The value that should be converted',
+ required: true,
+ },
+ {
+ displayName: 'Property Name',
+ name: 'dataPropertyName',
+ type: 'string',
+ default: 'data',
+ required: true,
+ displayOptions: {
+ show: {
+ action: ['format'],
+ },
+ },
+ description: 'Name of the property to which to write the converted date',
+ },
+ {
+ displayName: 'Custom Format',
+ name: 'custom',
+ displayOptions: {
+ show: {
+ action: ['format'],
+ },
+ },
+ type: 'boolean',
+ default: false,
+ description: 'Whether a predefined format should be selected or custom format entered',
+ },
+ {
+ displayName: 'To Format',
+ name: 'toFormat',
+ displayOptions: {
+ show: {
+ action: ['format'],
+ custom: [true],
+ },
+ },
+ type: 'string',
+ default: '',
+ placeholder: 'YYYY-MM-DD',
+ description: 'The format to convert the date to',
+ },
+ {
+ displayName: 'To Format',
+ name: 'toFormat',
+ type: 'options',
+ displayOptions: {
+ show: {
+ action: ['format'],
+ custom: [false],
+ },
+ },
+ // eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
+ options: [
+ {
+ name: 'MM/DD/YYYY',
+ value: 'MM/DD/YYYY',
+ description: 'Example: 09/04/1986',
+ },
+ {
+ name: 'YYYY/MM/DD',
+ value: 'YYYY/MM/DD',
+ description: 'Example: 1986/04/09',
+ },
+ {
+ name: 'MMMM DD YYYY',
+ value: 'MMMM DD YYYY',
+ description: 'Example: April 09 1986',
+ },
+ {
+ name: 'MM-DD-YYYY',
+ value: 'MM-DD-YYYY',
+ description: 'Example: 09-04-1986',
+ },
+ {
+ name: 'YYYY-MM-DD',
+ value: 'YYYY-MM-DD',
+ description: 'Example: 1986-04-09',
+ },
+ {
+ name: 'Unix Timestamp',
+ value: 'X',
+ description: 'Example: 513388800.879',
+ },
+ {
+ name: 'Unix Ms Timestamp',
+ value: 'x',
+ description: 'Example: 513388800',
+ },
+ ],
+ default: 'MM/DD/YYYY',
+ description: 'The format to convert the date to',
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ displayOptions: {
+ show: {
+ action: ['format'],
+ },
+ },
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ options: [
+ {
+ displayName: 'From Format',
+ name: 'fromFormat',
+ type: 'string',
+ default: '',
+ description: 'In case the input format is not recognized you can provide the format',
+ },
+ {
+ displayName: 'From Timezone Name or ID',
+ name: 'fromTimezone',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getTimezones',
+ },
+ default: 'UTC',
+ description:
+ 'The timezone to convert from. Choose from the list, or specify an ID using an expression.',
+ },
+ {
+ displayName: 'To Timezone Name or ID',
+ name: 'toTimezone',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getTimezones',
+ },
+ default: 'UTC',
+ description:
+ 'The timezone to convert to. Choose from the list, or specify an ID using an expression.',
+ },
+ ],
+ },
+ {
+ 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',
+ noDataExpression: true,
+ options: [
+ {
+ name: 'Add',
+ value: 'add',
+ description: 'Add time to Date Value',
+ action: 'Add time to Date Value',
+ },
+ {
+ name: 'Subtract',
+ value: 'subtract',
+ description: 'Subtract time from Date Value',
+ action: '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',
+ // eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
+ 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.',
+ },
+ ],
+ },
+ ],
+};
+
+export class DateTimeV1 implements INodeType {
+ description: INodeTypeDescription;
+
+ constructor(baseDescription: INodeTypeBaseDescription) {
+ this.description = {
+ ...baseDescription,
+ ...versionDescription,
+ };
+ }
+
+ methods = {
+ loadOptions: {
+ // Get all the timezones to display them to user so that they can
+ // select them easily
+ async getTimezones(this: ILoadOptionsFunctions): Promise {
+ const returnData: INodePropertyOptions[] = [];
+ for (const timezone of moment.tz.names()) {
+ const timezoneName = timezone;
+ const timezoneId = timezone;
+ returnData.push({
+ name: timezoneName,
+ value: timezoneId,
+ });
+ }
+ return returnData;
+ },
+ },
+ };
+
+ async execute(this: IExecuteFunctions): Promise {
+ const items = this.getInputData();
+ const length = items.length;
+ const returnData: INodeExecutionData[] = [];
+
+ const workflowTimezone = this.getTimezone();
+ let item: INodeExecutionData;
+
+ for (let i = 0; i < length; i++) {
+ try {
+ const action = this.getNodeParameter('action', 0) as string;
+ item = items[i];
+
+ if (action === 'format') {
+ let currentDate: string | number | LuxonDateTime = this.getNodeParameter(
+ 'value',
+ i,
+ ) as string;
+ const dataPropertyName = this.getNodeParameter('dataPropertyName', i);
+ const toFormat = this.getNodeParameter('toFormat', i) as string;
+ const options = this.getNodeParameter('options', i);
+ let newDate;
+
+ if ((currentDate as unknown as IDataObject) instanceof LuxonDateTime) {
+ currentDate = (currentDate as unknown as LuxonDateTime).toISO();
+ }
+
+ // Check if the input is a number
+ if (!Number.isNaN(Number(currentDate))) {
+ //input is a number, convert to number in case it is a string
+ currentDate = Number(currentDate);
+ // check if the number is a timestamp in float format and convert to integer
+ if (!Number.isInteger(currentDate)) {
+ currentDate = currentDate * 1000;
+ }
+ }
+
+ if (currentDate === undefined) {
+ continue;
+ }
+ if (options.fromFormat === undefined && !moment(currentDate).isValid()) {
+ throw new NodeOperationError(
+ this.getNode(),
+ 'The date input format could not be recognized. Please set the "From Format" field',
+ { itemIndex: i },
+ );
+ }
+
+ if (Number.isInteger(currentDate)) {
+ const timestampLengthInMilliseconds1990 = 12;
+ // check if the number is a timestamp in seconds or milliseconds and create a moment object accordingly
+ if (currentDate.toString().length < timestampLengthInMilliseconds1990) {
+ newDate = moment.unix(currentDate as number);
+ } else {
+ newDate = moment(currentDate);
+ }
+ } else {
+ if (options.fromTimezone || options.toTimezone) {
+ const fromTimezone = options.fromTimezone || workflowTimezone;
+ if (options.fromFormat) {
+ newDate = moment.tz(
+ currentDate as string,
+ options.fromFormat as string,
+ fromTimezone as string,
+ );
+ } else {
+ newDate = moment.tz(currentDate, fromTimezone as string);
+ }
+ } else {
+ if (options.fromFormat) {
+ newDate = moment(currentDate, options.fromFormat as string);
+ } else {
+ newDate = moment(currentDate);
+ }
+ }
+ }
+
+ if (options.toTimezone || options.fromTimezone) {
+ // If either a source or a target timezone got defined the
+ // timezone of the date has to be changed. If a target-timezone
+ // is set use it else fall back to workflow timezone.
+ newDate = newDate.tz((options.toTimezone as string) || workflowTimezone);
+ }
+
+ newDate = newDate.format(toFormat);
+
+ let newItem: INodeExecutionData;
+ if (dataPropertyName.includes('.')) {
+ // Uses dot notation so copy all data
+ newItem = {
+ json: deepCopy(item.json),
+ pairedItem: {
+ item: i,
+ },
+ };
+ } else {
+ // Does not use dot notation so shallow copy is enough
+ newItem = {
+ json: { ...item.json },
+ pairedItem: {
+ item: i,
+ },
+ };
+ }
+
+ if (item.binary !== undefined) {
+ newItem.binary = item.binary;
+ }
+
+ set(newItem, `json.${dataPropertyName}`, newDate);
+
+ 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);
+
+ const newDate = fromFormat
+ ? parseDateByFormat.call(this, dateValue, fromFormat)
+ : parseDateByDefault.call(this, 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: deepCopy(item.json),
+ pairedItem: {
+ item: i,
+ },
+ };
+ } else {
+ // Does not use dot notation so shallow copy is enough
+ newItem = {
+ json: { ...item.json },
+ pairedItem: {
+ item: i,
+ },
+ };
+ }
+
+ if (item.binary !== undefined) {
+ newItem.binary = item.binary;
+ }
+
+ set(newItem, `json.${dataPropertyName}`, newDate.toISOString());
+
+ returnData.push(newItem);
+ }
+ } catch (error) {
+ if (this.continueOnFail()) {
+ returnData.push({
+ json: {
+ error: error.message,
+ },
+ pairedItem: {
+ item: i,
+ },
+ });
+ continue;
+ }
+ throw error;
+ }
+ }
+
+ return this.prepareOutputData(returnData);
+ }
+}
diff --git a/packages/nodes-base/nodes/DateTime/V2/AddToDateDescription.ts b/packages/nodes-base/nodes/DateTime/V2/AddToDateDescription.ts
new file mode 100644
index 0000000000000..7d96fe75ff140
--- /dev/null
+++ b/packages/nodes-base/nodes/DateTime/V2/AddToDateDescription.ts
@@ -0,0 +1,105 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const AddToDateDescription: INodeProperties[] = [
+ {
+ displayName:
+ "You can also do this using an expression, e.g. {{your_date.plus(5, 'minutes')}}
. More info",
+ name: 'notice',
+ type: 'notice',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: ['addToDate'],
+ },
+ },
+ },
+ {
+ displayName: 'Date to Add To',
+ name: 'magnitude',
+ type: 'string',
+ description: 'The date that you want to change',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: ['addToDate'],
+ },
+ },
+ required: true,
+ },
+ {
+ displayName: 'Time Unit to Add',
+ name: 'timeUnit',
+ description: 'Time unit for Duration parameter below',
+ displayOptions: {
+ show: {
+ operation: ['addToDate'],
+ },
+ },
+ type: 'options',
+ // eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
+ options: [
+ {
+ name: 'Years',
+ value: 'years',
+ },
+ {
+ name: 'Quarters',
+ value: 'quarters',
+ },
+ {
+ 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: 'Duration',
+ name: 'duration',
+ type: 'number',
+ description: 'The number of time units to add to the date',
+ default: 0,
+ displayOptions: {
+ show: {
+ operation: ['addToDate'],
+ },
+ },
+ },
+ {
+ displayName: 'Output Field Name',
+ name: 'outputFieldName',
+ type: 'string',
+ default: 'newDate',
+ description: 'Name of the field to put the output in',
+ displayOptions: {
+ show: {
+ operation: ['addToDate'],
+ },
+ },
+ },
+];
diff --git a/packages/nodes-base/nodes/DateTime/V2/CurrentDateDescription.ts b/packages/nodes-base/nodes/DateTime/V2/CurrentDateDescription.ts
new file mode 100644
index 0000000000000..051a1e83225a0
--- /dev/null
+++ b/packages/nodes-base/nodes/DateTime/V2/CurrentDateDescription.ts
@@ -0,0 +1,63 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const CurrentDateDescription: INodeProperties[] = [
+ {
+ displayName:
+ 'You can also refer to the current date in n8n expressions by using {{$now}}
or {{$today}}
. More info',
+ name: 'notice',
+ type: 'notice',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: ['getCurrentDate'],
+ },
+ },
+ },
+ {
+ displayName: 'Include Current Time',
+ name: 'includeTime',
+ type: 'boolean',
+ default: true,
+ description: 'Whether deactivated, the time will be set to midnight',
+ displayOptions: {
+ show: {
+ operation: ['getCurrentDate'],
+ },
+ },
+ },
+ {
+ displayName: 'Output Field Name',
+ name: 'outputFieldName',
+ type: 'string',
+ default: 'currentDate',
+ description: 'Name of the field to put the output in',
+ displayOptions: {
+ show: {
+ operation: ['getCurrentDate'],
+ },
+ },
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ displayOptions: {
+ show: {
+ operation: ['getCurrentDate'],
+ },
+ },
+ default: {},
+ options: [
+ {
+ displayName: 'Timezone',
+ name: 'timezone',
+ type: 'string',
+ placeholder: 'America/New_York',
+ default: '',
+ description:
+ 'The timezone to use. If not set, the timezone of the n8n instance will be used. Use ‘GMT’ for +00:00 timezone.',
+ },
+ ],
+ },
+];
diff --git a/packages/nodes-base/nodes/DateTime/V2/DateTimeV2.node.ts b/packages/nodes-base/nodes/DateTime/V2/DateTimeV2.node.ts
new file mode 100644
index 0000000000000..44a9a68976e39
--- /dev/null
+++ b/packages/nodes-base/nodes/DateTime/V2/DateTimeV2.node.ts
@@ -0,0 +1,210 @@
+import type {
+ IDataObject,
+ IExecuteFunctions,
+ INodeExecutionData,
+ INodeType,
+ INodeTypeBaseDescription,
+ INodeTypeDescription,
+} from 'n8n-workflow';
+import { NodeOperationError } from 'n8n-workflow';
+
+import { CurrentDateDescription } from './CurrentDateDescription';
+import { AddToDateDescription } from './AddToDateDescription';
+import { SubtractFromDateDescription } from './SubtractFromDateDescription';
+import { FormatDateDescription } from './FormatDateDescription';
+import { RoundDateDescription } from './RoundDateDescription';
+import { GetTimeBetweenDatesDescription } from './GetTimeBetweenDates';
+import type { DateTimeUnit, DurationUnit } from 'luxon';
+import { DateTime } from 'luxon';
+import { ExtractDateDescription } from './ExtractDateDescription';
+import { parseDate } from './GenericFunctions';
+
+export class DateTimeV2 implements INodeType {
+ description: INodeTypeDescription;
+
+ constructor(baseDescription: INodeTypeBaseDescription) {
+ this.description = {
+ ...baseDescription,
+ version: 2,
+ defaults: {
+ name: 'Date & Time',
+ color: '#408000',
+ },
+ inputs: ['main'],
+ outputs: ['main'],
+ properties: [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ options: [
+ {
+ name: 'Add to a Date',
+ value: 'addToDate',
+ },
+ {
+ name: 'Extract Part of a Date',
+ value: 'extractDate',
+ },
+ {
+ name: 'Format a Date',
+ value: 'formatDate',
+ },
+ {
+ name: 'Get Current Date',
+ value: 'getCurrentDate',
+ },
+ {
+ name: 'Get Time Between Dates',
+ value: 'getTimeBetweenDates',
+ },
+ {
+ name: 'Round a Date',
+ value: 'roundDate',
+ },
+ {
+ name: 'Subtract From a Date',
+ value: 'subtractFromDate',
+ },
+ ],
+ default: 'getCurrentDate',
+ },
+ ...CurrentDateDescription,
+ ...AddToDateDescription,
+ ...SubtractFromDateDescription,
+ ...FormatDateDescription,
+ ...RoundDateDescription,
+ ...GetTimeBetweenDatesDescription,
+ ...ExtractDateDescription,
+ ],
+ };
+ }
+
+ async execute(this: IExecuteFunctions): Promise {
+ const items = this.getInputData();
+ const returnData: INodeExecutionData[] = [];
+ const responseData = [];
+ const operation = this.getNodeParameter('operation', 0);
+ const workflowTimezone = this.getTimezone();
+
+ for (let i = 0; i < items.length; i++) {
+ if (operation === 'getCurrentDate') {
+ const includeTime = this.getNodeParameter('includeTime', i) as boolean;
+ const outputFieldName = this.getNodeParameter('outputFieldName', i) as string;
+ const { timezone } = this.getNodeParameter('options', i) as {
+ timezone: string;
+ };
+
+ const newLocal = timezone ? timezone : workflowTimezone;
+ if (DateTime.now().setZone(newLocal).invalidReason === 'unsupported zone') {
+ throw new NodeOperationError(
+ this.getNode(),
+ `The timezone ${newLocal} is not valid. Please check the timezone.`,
+ );
+ }
+ responseData.push(
+ includeTime
+ ? { [outputFieldName]: DateTime.now().setZone(newLocal).toString() }
+ : {
+ [outputFieldName]: DateTime.now().setZone(newLocal).startOf('day').toString(),
+ },
+ );
+ } else if (operation === 'addToDate') {
+ const addToDate = this.getNodeParameter('magnitude', i) as string;
+ const timeUnit = this.getNodeParameter('timeUnit', i) as string;
+ const duration = this.getNodeParameter('duration', i) as number;
+ const outputFieldName = this.getNodeParameter('outputFieldName', i) as string;
+
+ const dateToAdd = parseDate.call(this, addToDate, workflowTimezone);
+ const returnedDate = dateToAdd.plus({ [timeUnit]: duration });
+ responseData.push({ [outputFieldName]: returnedDate.toString() });
+ } else if (operation === 'subtractFromDate') {
+ const subtractFromDate = this.getNodeParameter('magnitude', i) as string;
+ const timeUnit = this.getNodeParameter('timeUnit', i) as string;
+ const duration = this.getNodeParameter('duration', i) as number;
+ const outputFieldName = this.getNodeParameter('outputFieldName', i) as string;
+
+ const dateToAdd = parseDate.call(this, subtractFromDate, workflowTimezone);
+ const returnedDate = dateToAdd.minus({ [timeUnit]: duration });
+ responseData.push({ [outputFieldName]: returnedDate.toString() });
+ } else if (operation === 'formatDate') {
+ const date = this.getNodeParameter('date', i) as string;
+ const format = this.getNodeParameter('format', i) as string;
+ const outputFieldName = this.getNodeParameter('outputFieldName', i) as string;
+ const { timezone } = this.getNodeParameter('options', i) as { timezone: boolean };
+
+ const dateLuxon = timezone
+ ? parseDate.call(this, date, workflowTimezone)
+ : parseDate.call(this, date);
+ if (format === 'custom') {
+ const customFormat = this.getNodeParameter('customFormat', i) as string;
+ responseData.push({
+ [outputFieldName]: dateLuxon.toFormat(customFormat),
+ });
+ } else {
+ responseData.push({
+ [outputFieldName]: dateLuxon.toFormat(format),
+ });
+ }
+ } else if (operation === 'roundDate') {
+ const date = this.getNodeParameter('date', i) as string;
+ const mode = this.getNodeParameter('mode', i) as string;
+ const outputFieldName = this.getNodeParameter('outputFieldName', i) as string;
+
+ const dateLuxon = parseDate.call(this, date, workflowTimezone);
+
+ if (mode === 'roundDown') {
+ const toNearest = this.getNodeParameter('toNearest', i) as string;
+ responseData.push({
+ [outputFieldName]: dateLuxon.startOf(toNearest as DateTimeUnit).toString(),
+ });
+ } else if (mode === 'roundUp') {
+ const to = this.getNodeParameter('to', i) as string;
+ responseData.push({
+ [outputFieldName]: dateLuxon
+ .plus({ [to]: 1 })
+ .startOf(to as DateTimeUnit)
+ .toString(),
+ });
+ }
+ } else if (operation === 'getTimeBetweenDates') {
+ const startDate = this.getNodeParameter('startDate', i) as string;
+ const endDate = this.getNodeParameter('endDate', i) as string;
+ const unit = this.getNodeParameter('units', i) as DurationUnit[];
+ const outputFieldName = this.getNodeParameter('outputFieldName', i) as string;
+ const { isoString } = this.getNodeParameter('options', i) as {
+ isoString: boolean;
+ };
+
+ const luxonStartDate = parseDate.call(this, startDate, workflowTimezone);
+ const luxonEndDate = parseDate.call(this, endDate, workflowTimezone);
+ const duration = luxonEndDate.diff(luxonStartDate, unit);
+ isoString
+ ? responseData.push({
+ [outputFieldName]: duration.toString(),
+ })
+ : responseData.push({
+ [outputFieldName]: duration.toObject(),
+ });
+ } else if (operation === 'extractDate') {
+ const date = this.getNodeParameter('date', i) as string | DateTime;
+ const outputFieldName = this.getNodeParameter('outputFieldName', i) as string;
+ const part = this.getNodeParameter('part', i) as keyof DateTime | 'week';
+
+ const parsedDate = parseDate.call(this, date, workflowTimezone);
+ const selectedPart = part === 'week' ? parsedDate.weekNumber : parsedDate.get(part);
+ responseData.push({ [outputFieldName]: selectedPart });
+ }
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray(responseData as IDataObject[]),
+ {
+ itemData: { item: i },
+ },
+ );
+ returnData.push(...executionData);
+ }
+ return this.prepareOutputData(returnData);
+ }
+}
diff --git a/packages/nodes-base/nodes/DateTime/V2/ExtractDateDescription.ts b/packages/nodes-base/nodes/DateTime/V2/ExtractDateDescription.ts
new file mode 100644
index 0000000000000..0fcb7bb20dc5a
--- /dev/null
+++ b/packages/nodes-base/nodes/DateTime/V2/ExtractDateDescription.ts
@@ -0,0 +1,82 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const ExtractDateDescription: INodeProperties[] = [
+ {
+ displayName:
+ 'You can also do this using an expression, e.g. {{ your_date.extract("month") }}}
. More info',
+ name: 'notice',
+ type: 'notice',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: ['extractDate'],
+ },
+ },
+ },
+ {
+ displayName: 'Date',
+ name: 'date',
+ type: 'string',
+ description: 'The date that you want to round',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: ['extractDate'],
+ },
+ },
+ },
+ {
+ displayName: 'Part',
+ name: 'part',
+ type: 'options',
+ // eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
+ options: [
+ {
+ name: 'Year',
+ value: 'year',
+ },
+ {
+ name: 'Month',
+ value: 'month',
+ },
+ {
+ name: 'Week',
+ value: 'week',
+ },
+ {
+ name: 'Day',
+ value: 'day',
+ },
+ {
+ name: 'Hour',
+ value: 'hour',
+ },
+ {
+ name: 'Minute',
+ value: 'minute',
+ },
+ {
+ name: 'Second',
+ value: 'second',
+ },
+ ],
+ default: 'month',
+ displayOptions: {
+ show: {
+ operation: ['extractDate'],
+ },
+ },
+ },
+ {
+ displayName: 'Output Field Name',
+ name: 'outputFieldName',
+ type: 'string',
+ default: 'datePart',
+ description: 'Name of the field to put the output in',
+ displayOptions: {
+ show: {
+ operation: ['extractDate'],
+ },
+ },
+ },
+];
diff --git a/packages/nodes-base/nodes/DateTime/V2/FormatDateDescription.ts b/packages/nodes-base/nodes/DateTime/V2/FormatDateDescription.ts
new file mode 100644
index 0000000000000..dad16abcec35a
--- /dev/null
+++ b/packages/nodes-base/nodes/DateTime/V2/FormatDateDescription.ts
@@ -0,0 +1,129 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const FormatDateDescription: INodeProperties[] = [
+ {
+ displayName:
+ "You can also do this using an expression, e.g. {{your_date.format('yyyy-MM-dd')}}
. More info",
+ name: 'notice',
+ type: 'notice',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: ['formatDate'],
+ },
+ },
+ },
+ {
+ displayName: 'Date',
+ name: 'date',
+ type: 'string',
+ description: 'The date that you want to format',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: ['formatDate'],
+ },
+ },
+ },
+ {
+ displayName: 'Format',
+ name: 'format',
+ type: 'options',
+ displayOptions: {
+ show: {
+ operation: ['formatDate'],
+ },
+ },
+ // eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
+ options: [
+ {
+ name: 'Custom Format',
+ value: 'custom',
+ },
+ {
+ name: 'MM/DD/YYYY',
+ value: 'MM/dd/yyyy',
+ description: 'Example: 09/04/1986',
+ },
+ {
+ name: 'YYYY/MM/DD',
+ value: 'yyyy/MM/dd',
+ description: 'Example: 1986/04/09',
+ },
+ {
+ name: 'MMMM DD YYYY',
+ value: 'MMMM dd yyyy',
+ description: 'Example: April 09 1986',
+ },
+ {
+ name: 'MM-DD-YYYY',
+ value: 'MM-dd-yyyy',
+ description: 'Example: 09-04-1986',
+ },
+ {
+ name: 'YYYY-MM-DD',
+ value: 'yyyy-MM-dd',
+ description: 'Example: 1986-04-09',
+ },
+ {
+ name: 'Unix Timestamp',
+ value: 'X',
+ description: 'Example: 1672531200',
+ },
+ {
+ name: 'Unix Ms Timestamp',
+ value: 'x',
+ description: 'Example: 1674691200000',
+ },
+ ],
+ default: 'MM/dd/yyyy',
+ description: 'The format to convert the date to',
+ },
+ {
+ displayName: 'Custom Format',
+ name: 'customFormat',
+ type: 'string',
+ displayOptions: {
+ show: {
+ format: ['custom'],
+ operation: ['formatDate'],
+ },
+ },
+ hint: 'List of special tokens More info',
+ default: '',
+ placeholder: 'yyyy-MM-dd',
+ },
+ {
+ displayName: 'Output Field Name',
+ name: 'outputFieldName',
+ type: 'string',
+ default: 'formattedDate',
+ description: 'Name of the field to put the output in',
+ displayOptions: {
+ show: {
+ operation: ['formatDate'],
+ },
+ },
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ displayOptions: {
+ show: {
+ operation: ['formatDate'],
+ },
+ },
+ default: {},
+ options: [
+ {
+ displayName: 'Use Workflow Timezone',
+ name: 'timezone',
+ type: 'boolean',
+ default: false,
+ description: "Whether to use the timezone of the input or the workflow's timezone",
+ },
+ ],
+ },
+];
diff --git a/packages/nodes-base/nodes/DateTime/V2/GenericFunctions.ts b/packages/nodes-base/nodes/DateTime/V2/GenericFunctions.ts
new file mode 100644
index 0000000000000..153d875d3be1a
--- /dev/null
+++ b/packages/nodes-base/nodes/DateTime/V2/GenericFunctions.ts
@@ -0,0 +1,50 @@
+import { DateTime } from 'luxon';
+import moment from 'moment';
+import type { IExecuteFunctions } from 'n8n-workflow';
+import { NodeOperationError } from 'n8n-workflow';
+
+export function parseDate(
+ this: IExecuteFunctions,
+ date: string | number | DateTime,
+ timezone?: string,
+) {
+ let parsedDate;
+
+ if (date instanceof DateTime) {
+ parsedDate = date;
+ } else {
+ // Check if the input is a number
+ if (!Number.isNaN(Number(date))) {
+ //input is a number, convert to number in case it is a string formatted number
+ date = Number(date);
+ // check if the number is a timestamp in float format and convert to integer
+ if (!Number.isInteger(date)) {
+ date = date * 1000;
+ }
+ }
+
+ if (Number.isInteger(date)) {
+ const timestampLengthInMilliseconds1990 = 12;
+ // check if the number is a timestamp in seconds or milliseconds and create a moment object accordingly
+ if (date.toString().length < timestampLengthInMilliseconds1990) {
+ parsedDate = DateTime.fromSeconds(date as number);
+ } else {
+ parsedDate = DateTime.fromMillis(date as number);
+ }
+ } else {
+ if (!timezone && (date as string).includes('+')) {
+ const offset = (date as string).split('+')[1].slice(0, 2) as unknown as number;
+ timezone = `Etc/GMT-${offset * 1}`;
+ }
+
+ parsedDate = DateTime.fromISO(moment(date).toISOString());
+ }
+
+ parsedDate = parsedDate.setZone(timezone || 'Etc/UTC');
+
+ if (parsedDate.invalidReason === 'unparsable') {
+ throw new NodeOperationError(this.getNode(), 'Invalid date format');
+ }
+ }
+ return parsedDate;
+}
diff --git a/packages/nodes-base/nodes/DateTime/V2/GetTimeBetweenDates.ts b/packages/nodes-base/nodes/DateTime/V2/GetTimeBetweenDates.ts
new file mode 100644
index 0000000000000..161858936c4b9
--- /dev/null
+++ b/packages/nodes-base/nodes/DateTime/V2/GetTimeBetweenDates.ts
@@ -0,0 +1,105 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const GetTimeBetweenDatesDescription: INodeProperties[] = [
+ {
+ displayName: 'Start Date',
+ name: 'startDate',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: ['getTimeBetweenDates'],
+ },
+ },
+ },
+ {
+ displayName: 'End Date',
+ name: 'endDate',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: ['getTimeBetweenDates'],
+ },
+ },
+ },
+ {
+ displayName: 'Units',
+ name: 'units',
+ type: 'multiOptions',
+ // eslint-disable-next-line n8n-nodes-base/node-param-multi-options-type-unsorted-items
+ options: [
+ {
+ name: 'Year',
+ value: 'year',
+ },
+ {
+ name: 'Month',
+ value: 'month',
+ },
+ {
+ name: 'Week',
+ value: 'week',
+ },
+ {
+ name: 'Day',
+ value: 'day',
+ },
+ {
+ name: 'Hour',
+ value: 'hour',
+ },
+ {
+ name: 'Minute',
+ value: 'minute',
+ },
+ {
+ name: 'Second',
+ value: 'second',
+ },
+ {
+ name: 'Millisecond',
+ value: 'millisecond',
+ },
+ ],
+ displayOptions: {
+ show: {
+ operation: ['getTimeBetweenDates'],
+ },
+ },
+ default: ['day'],
+ },
+ {
+ displayName: 'Output Field Name',
+ name: 'outputFieldName',
+ type: 'string',
+ default: 'timeDifference',
+ description: 'Name of the field to put the output in',
+ displayOptions: {
+ show: {
+ operation: ['getTimeBetweenDates'],
+ },
+ },
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ displayOptions: {
+ show: {
+ operation: ['getTimeBetweenDates'],
+ },
+ },
+ default: {},
+ options: [
+ {
+ displayName: 'Output as ISO String',
+ name: 'isoString',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to output the date as ISO string or not',
+ },
+ ],
+ },
+];
diff --git a/packages/nodes-base/nodes/DateTime/V2/RoundDateDescription.ts b/packages/nodes-base/nodes/DateTime/V2/RoundDateDescription.ts
new file mode 100644
index 0000000000000..5d244efae2723
--- /dev/null
+++ b/packages/nodes-base/nodes/DateTime/V2/RoundDateDescription.ts
@@ -0,0 +1,122 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const RoundDateDescription: INodeProperties[] = [
+ {
+ displayName:
+ "You can also do this using an expression, e.g. {{ your_date.beginningOf('month') }}
or {{ your_date.endOfMonth() }}
. More info",
+ name: 'notice',
+ type: 'notice',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: ['roundDate'],
+ },
+ },
+ },
+ {
+ displayName: 'Date',
+ name: 'date',
+ type: 'string',
+ description: 'The date that you want to round',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: ['roundDate'],
+ },
+ },
+ },
+ {
+ displayName: 'Mode',
+ name: 'mode',
+ type: 'options',
+ options: [
+ {
+ name: 'Round Down',
+ value: 'roundDown',
+ },
+ {
+ name: 'Round Up',
+ value: 'roundUp',
+ },
+ ],
+ default: 'roundDown',
+ displayOptions: {
+ show: {
+ operation: ['roundDate'],
+ },
+ },
+ },
+ {
+ displayName: 'To Nearest',
+ name: 'toNearest',
+ type: 'options',
+ // eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
+ options: [
+ {
+ name: 'Year',
+ value: 'year',
+ },
+ {
+ name: 'Month',
+ value: 'month',
+ },
+ {
+ name: 'Week',
+ value: 'week',
+ },
+ {
+ name: 'Day',
+ value: 'day',
+ },
+ {
+ name: 'Hour',
+ value: 'hour',
+ },
+ {
+ name: 'Minute',
+ value: 'minute',
+ },
+ {
+ name: 'Second',
+ value: 'second',
+ },
+ ],
+ default: 'month',
+ displayOptions: {
+ show: {
+ operation: ['roundDate'],
+ mode: ['roundDown'],
+ },
+ },
+ },
+ {
+ displayName: 'To',
+ name: 'to',
+ type: 'options',
+ options: [
+ {
+ name: 'End of Month',
+ value: 'month',
+ },
+ ],
+ default: 'month',
+ displayOptions: {
+ show: {
+ operation: ['roundDate'],
+ mode: ['roundUp'],
+ },
+ },
+ },
+ {
+ displayName: 'Output Field Name',
+ name: 'outputFieldName',
+ type: 'string',
+ default: 'roundedDate',
+ description: 'Name of the field to put the output in',
+ displayOptions: {
+ show: {
+ operation: ['roundDate'],
+ },
+ },
+ },
+];
diff --git a/packages/nodes-base/nodes/DateTime/V2/SubtractFromDateDescription.ts b/packages/nodes-base/nodes/DateTime/V2/SubtractFromDateDescription.ts
new file mode 100644
index 0000000000000..247d63b0b06cf
--- /dev/null
+++ b/packages/nodes-base/nodes/DateTime/V2/SubtractFromDateDescription.ts
@@ -0,0 +1,105 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const SubtractFromDateDescription: INodeProperties[] = [
+ {
+ displayName:
+ "You can also do this using an expression, e.g. {{your_date.minus(5, 'minutes')}}
. More info",
+ name: 'notice',
+ type: 'notice',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: ['subtractFromDate'],
+ },
+ },
+ },
+ {
+ displayName: 'Date to Subtract From',
+ name: 'magnitude',
+ type: 'string',
+ description: 'The date that you want to change',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: ['subtractFromDate'],
+ },
+ },
+ required: true,
+ },
+ {
+ displayName: 'Time Unit to Subtract',
+ name: 'timeUnit',
+ description: 'Time unit for Duration parameter below',
+ displayOptions: {
+ show: {
+ operation: ['subtractFromDate'],
+ },
+ },
+ type: 'options',
+ // eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
+ options: [
+ {
+ name: 'Years',
+ value: 'years',
+ },
+ {
+ name: 'Quarters',
+ value: 'quarters',
+ },
+ {
+ 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: 'Duration',
+ name: 'duration',
+ type: 'number',
+ description: 'The number of time units to subtract from the date',
+ default: 0,
+ displayOptions: {
+ show: {
+ operation: ['subtractFromDate'],
+ },
+ },
+ },
+ {
+ displayName: 'Output Field Name',
+ name: 'outputFieldName',
+ type: 'string',
+ default: 'newDate',
+ description: 'Name of the field to put the output in',
+ displayOptions: {
+ show: {
+ operation: ['subtractFromDate'],
+ },
+ },
+ },
+];
diff --git a/packages/nodes-base/nodes/DateTime/test/node/DateTimeWorkflowV2.json b/packages/nodes-base/nodes/DateTime/test/node/DateTimeWorkflowV2.json
new file mode 100644
index 0000000000000..fceebea6cc527
--- /dev/null
+++ b/packages/nodes-base/nodes/DateTime/test/node/DateTimeWorkflowV2.json
@@ -0,0 +1,260 @@
+{
+ "name": "node-360-quick-overhaul-of-date-and-time-node",
+ "nodes": [
+ {
+ "parameters": {},
+ "id": "21ff2e15-375d-4e68-b1ca-d48a110be238",
+ "name": "When clicking \"Execute Workflow\"",
+ "type": "n8n-nodes-base.manualTrigger",
+ "typeVersion": 1,
+ "position": [-420, 20]
+ },
+ {
+ "parameters": {
+ "operation": "addToDate",
+ "magnitude": "={{ $json.currentDate }}",
+ "duration": 2
+ },
+ "id": "b99986f1-edeb-434c-b7ed-9cc86eaec522",
+ "name": "Add to date",
+ "type": "n8n-nodes-base.dateTime",
+ "typeVersion": 2,
+ "position": [140, 40]
+ },
+ {
+ "parameters": {
+ "operation": "subtractFromDate",
+ "magnitude": "={{ $json.newDate }}",
+ "duration": 2
+ },
+ "id": "aa75a04b-0d42-46ff-87e7-75d4b4f6c7ea",
+ "name": "Subtract date",
+ "type": "n8n-nodes-base.dateTime",
+ "typeVersion": 2,
+ "position": [300, 200]
+ },
+ {
+ "parameters": {
+ "operation": "formatDate",
+ "date": "={{ $json.newDate }}",
+ "format": "yyyy/MM/dd"
+ },
+ "id": "52076d89-bc6d-4253-8ca4-9aad3a058d17",
+ "name": "Format Date",
+ "type": "n8n-nodes-base.dateTime",
+ "typeVersion": 2,
+ "position": [420, 40]
+ },
+ {
+ "parameters": {
+ "operation": "roundDate",
+ "date": "={{ $json.formattedDate }}",
+ "toNearest": "day"
+ },
+ "id": "10016499-c9da-4984-9a5f-2f8c8844fb63",
+ "name": "Round Date",
+ "type": "n8n-nodes-base.dateTime",
+ "typeVersion": 2,
+ "position": [560, 200]
+ },
+ {
+ "parameters": {
+ "operation": "getTimeBetweenDates",
+ "startDate": "={{ $node['Subtract date'].json.newDate }}",
+ "endDate": "={{ $node['Add to date'].json.newDate }}",
+ "units": ["day"]
+ },
+ "id": "f62b6d0b-b13a-4fcd-b4eb-3ec7ea85e80c",
+ "name": "Get between date",
+ "type": "n8n-nodes-base.dateTime",
+ "typeVersion": 2,
+ "position": [660, 40]
+ },
+ {
+ "parameters": {
+ "operation": "extractDate",
+ "date": "={{ $node.Code.json.currentDate }}",
+ "part": "hour",
+ "outputFieldName": "date"
+ },
+ "id": "764e3e08-f71b-4e42-b059-36285076fe10",
+ "name": "Extract Date",
+ "type": "n8n-nodes-base.dateTime",
+ "typeVersion": 2,
+ "position": [780, 220]
+ },
+ {
+ "parameters": {
+ "options": {
+ "fromFormat": ""
+ }
+ },
+ "id": "f0b75198-74a4-4a13-8842-340539f41d80",
+ "name": "V1",
+ "type": "n8n-nodes-base.dateTime",
+ "typeVersion": 1,
+ "position": [0, -180],
+ "disabled": true
+ },
+ {
+ "parameters": {
+ "jsCode": "return {\"currentDate\":\"2023-04-11T13:51:59.965+00:00\"}\n"
+ },
+ "id": "7ba0c2a1-a683-4975-a2ca-70904111a3fc",
+ "name": "Code",
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 1,
+ "position": [-140, 140]
+ }
+ ],
+ "pinData": {
+ "Code": [
+ {
+ "json": {
+ "currentDate": "2023-04-11T13:51:59.965+00:00"
+ }
+ }
+ ],
+ "Add to date": [
+ {
+ "json": {
+ "newDate": "2023-04-13T13:51:59.965+00:00"
+ }
+ }
+ ],
+ "Subtract date": [
+ {
+ "json": {
+ "newDate": "2023-04-11T13:51:59.965+00:00"
+ }
+ }
+ ],
+ "Format Date": [
+ {
+ "json": {
+ "formattedDate": "2023/04/11"
+ }
+ }
+ ],
+ "Round Date": [
+ {
+ "json": {
+ "roundedDate": "2023-04-11T00:00:00.000+00:00"
+ }
+ }
+ ],
+ "Get between date": [
+ {
+ "json": {
+ "timeDifference": {
+ "days": 2
+ }
+ }
+ }
+ ],
+ "Extract Date": [
+ {
+ "json": {
+ "date": 13
+ }
+ }
+ ]
+ },
+ "connections": {
+ "When clicking \"Execute Workflow\"": {
+ "main": [
+ [
+ {
+ "node": "V1",
+ "type": "main",
+ "index": 0
+ },
+ {
+ "node": "Code",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Add to date": {
+ "main": [
+ [
+ {
+ "node": "Subtract date",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Subtract date": {
+ "main": [
+ [
+ {
+ "node": "Format Date",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Format Date": {
+ "main": [
+ [
+ {
+ "node": "Round Date",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Round Date": {
+ "main": [
+ [
+ {
+ "node": "Get between date",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Get between date": {
+ "main": [
+ [
+ {
+ "node": "Extract Date",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Code": {
+ "main": [
+ [
+ {
+ "node": "Add to date",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ }
+ },
+ "active": false,
+ "settings": {
+ "saveManualExecutions": false,
+ "callerPolicy": "workflowsFromSameOwner",
+ "timezone": "Etc/GMT",
+ "executionTimeout": -1
+ },
+ "versionId": "c21daa0b-83ae-45f1-b680-d2e57423800b",
+ "id": "48",
+ "meta": {
+ "instanceId": "8e9416f42a954d0a370d988ac3c0f916f44074a6e45189164b1a8559394a7516"
+ },
+ "tags": []
+}
diff --git a/packages/nodes-base/nodes/DateTime/test/node/workflow.timestamp_v2.json b/packages/nodes-base/nodes/DateTime/test/node/workflow.timestamp_v2.json
new file mode 100644
index 0000000000000..0ca9ec5e9396c
--- /dev/null
+++ b/packages/nodes-base/nodes/DateTime/test/node/workflow.timestamp_v2.json
@@ -0,0 +1,352 @@
+{
+ "name": "dateTime overhaul",
+ "nodes": [
+ {
+ "parameters": {},
+ "id": "4ef93910-a6f8-43e2-bba7-8319ef62f9ee",
+ "name": "When clicking \"Execute Workflow\"",
+ "type": "n8n-nodes-base.manualTrigger",
+ "typeVersion": 1,
+ "position": [260, 820]
+ },
+ {
+ "parameters": {
+ "values": {
+ "number": [
+ {
+ "name": "dateMilis",
+ "value": 1682918315906
+ },
+ {
+ "name": "dateMilisFloat",
+ "value": 1682918315.906
+ },
+ {
+ "name": "dateUnix",
+ "value": 1682918315
+ }
+ ],
+ "string": [
+ {
+ "name": "dateMilisStr",
+ "value": "1682918315906"
+ },
+ {
+ "name": "dateMilisFloatStr",
+ "value": "1682918315.906"
+ },
+ {
+ "name": "dateUnixStr",
+ "value": "1682918315"
+ }
+ ]
+ },
+ "options": {}
+ },
+ "id": "1d9bc8b7-9c8d-40c8-92f2-e94ed50d0ae5",
+ "name": "Set",
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 2,
+ "position": [420, 820]
+ },
+ {
+ "parameters": {
+ "operation": "formatDate",
+ "date": "={{ $json.dateMilis }}",
+ "format": "yyyy/MM/dd",
+ "outputFieldName": "data",
+ "additionalFields": {}
+ },
+ "id": "c07b9cbd-4aeb-4267-a1d3-b45ecadbd1cb",
+ "name": "Date & Time6",
+ "type": "n8n-nodes-base.dateTime",
+ "typeVersion": 2,
+ "position": [680, 640]
+ },
+ {
+ "parameters": {
+ "operation": "formatDate",
+ "date": "={{ $json.dateMilisFloat }}",
+ "format": "yyyy/MM/dd",
+ "outputFieldName": "data",
+ "additionalFields": {}
+ },
+ "id": "a5b7bb44-63e2-4b71-ad91-55bac329e3f6",
+ "name": "Date & Time",
+ "type": "n8n-nodes-base.dateTime",
+ "typeVersion": 2,
+ "position": [680, 780]
+ },
+ {
+ "parameters": {
+ "operation": "formatDate",
+ "date": "={{ $json.dateUnix }}",
+ "format": "yyyy/MM/dd",
+ "outputFieldName": "data",
+ "additionalFields": {}
+ },
+ "id": "1306d282-b5f8-4a54-8834-6207ecff65f7",
+ "name": "Date & Time1",
+ "type": "n8n-nodes-base.dateTime",
+ "typeVersion": 2,
+ "position": [680, 940]
+ },
+ {
+ "parameters": {
+ "operation": "formatDate",
+ "date": "={{ $json.dateMilisStr }}",
+ "format": "yyyy/MM/dd",
+ "outputFieldName": "data",
+ "additionalFields": {}
+ },
+ "id": "4823c095-1921-406e-9957-a75521bca1e5",
+ "name": "Date & Time2",
+ "type": "n8n-nodes-base.dateTime",
+ "typeVersion": 2,
+ "position": [680, 1080]
+ },
+ {
+ "parameters": {
+ "operation": "formatDate",
+ "date": "={{ $json.dateMilisFloatStr }}",
+ "format": "yyyy/MM/dd",
+ "outputFieldName": "data",
+ "additionalFields": {}
+ },
+ "id": "d209ac18-9935-4452-825a-42aa90daaaa5",
+ "name": "Date & Time3",
+ "type": "n8n-nodes-base.dateTime",
+ "typeVersion": 2,
+ "position": [680, 1220]
+ },
+ {
+ "parameters": {
+ "operation": "formatDate",
+ "date": "={{ $json.dateUnixStr }}",
+ "format": "yyyy/MM/dd",
+ "outputFieldName": "data",
+ "additionalFields": {}
+ },
+ "id": "b7065dfb-ae7e-4828-a5ea-e9c313302944",
+ "name": "Date & Time4",
+ "type": "n8n-nodes-base.dateTime",
+ "typeVersion": 2,
+ "position": [680, 1380]
+ },
+ {
+ "parameters": {},
+ "id": "5ae1bb29-d19e-4e3d-af11-ccc53ee23bfb",
+ "name": "No Operation, do nothing",
+ "type": "n8n-nodes-base.noOp",
+ "typeVersion": 1,
+ "position": [900, 640]
+ },
+ {
+ "parameters": {},
+ "id": "8716cc32-d4a6-48d6-af5d-e15646006dd8",
+ "name": "No Operation, do nothing1",
+ "type": "n8n-nodes-base.noOp",
+ "typeVersion": 1,
+ "position": [900, 780]
+ },
+ {
+ "parameters": {},
+ "id": "88f0247d-ecc0-49a2-8bae-4e7b99ae8611",
+ "name": "No Operation, do nothing2",
+ "type": "n8n-nodes-base.noOp",
+ "typeVersion": 1,
+ "position": [900, 920]
+ },
+ {
+ "parameters": {},
+ "id": "99a04c1d-5426-446e-9171-2d12a5b14a13",
+ "name": "No Operation, do nothing3",
+ "type": "n8n-nodes-base.noOp",
+ "typeVersion": 1,
+ "position": [900, 1060]
+ },
+ {
+ "parameters": {},
+ "id": "923e317f-3e7b-4609-883d-c630034bd20c",
+ "name": "No Operation, do nothing4",
+ "type": "n8n-nodes-base.noOp",
+ "typeVersion": 1,
+ "position": [900, 1200]
+ },
+ {
+ "parameters": {},
+ "id": "93745a80-a2b6-414b-bcf0-938f2a2da985",
+ "name": "No Operation, do nothing5",
+ "type": "n8n-nodes-base.noOp",
+ "typeVersion": 1,
+ "position": [900, 1340]
+ }
+ ],
+ "pinData": {
+ "No Operation, do nothing5": [
+ {
+ "json": {
+ "data": "2023/05/01"
+ }
+ }
+ ],
+ "No Operation, do nothing4": [
+ {
+ "json": {
+ "data": "2023/05/01"
+ }
+ }
+ ],
+ "No Operation, do nothing3": [
+ {
+ "json": {
+ "data": "2023/05/01"
+ }
+ }
+ ],
+ "No Operation, do nothing2": [
+ {
+ "json": {
+ "data": "2023/05/01"
+ }
+ }
+ ],
+ "No Operation, do nothing1": [
+ {
+ "json": {
+ "data": "2023/05/01"
+ }
+ }
+ ],
+ "No Operation, do nothing": [
+ {
+ "json": {
+ "data": "2023/05/01"
+ }
+ }
+ ]
+ },
+ "connections": {
+ "When clicking \"Execute Workflow\"": {
+ "main": [
+ [
+ {
+ "node": "Set",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Set": {
+ "main": [
+ [
+ {
+ "node": "Date & Time6",
+ "type": "main",
+ "index": 0
+ },
+ {
+ "node": "Date & Time",
+ "type": "main",
+ "index": 0
+ },
+ {
+ "node": "Date & Time1",
+ "type": "main",
+ "index": 0
+ },
+ {
+ "node": "Date & Time2",
+ "type": "main",
+ "index": 0
+ },
+ {
+ "node": "Date & Time3",
+ "type": "main",
+ "index": 0
+ },
+ {
+ "node": "Date & Time4",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Date & Time6": {
+ "main": [
+ [
+ {
+ "node": "No Operation, do nothing",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Date & Time4": {
+ "main": [
+ [
+ {
+ "node": "No Operation, do nothing5",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Date & Time3": {
+ "main": [
+ [
+ {
+ "node": "No Operation, do nothing4",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Date & Time2": {
+ "main": [
+ [
+ {
+ "node": "No Operation, do nothing3",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Date & Time1": {
+ "main": [
+ [
+ {
+ "node": "No Operation, do nothing2",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Date & Time": {
+ "main": [
+ [
+ {
+ "node": "No Operation, do nothing1",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ }
+ },
+ "active": false,
+ "settings": {},
+ "versionId": "19282890-eff2-40ca-be11-a8fff559c964",
+ "id": "21",
+ "meta": {
+ "instanceId": "6ebec4953fe56f1c009e7c3b107578b375137523af057073c0b5da17350651bd"
+ },
+ "tags": []
+}
diff --git a/packages/nodes-base/test/nodes/ExecuteWorkflow.ts b/packages/nodes-base/test/nodes/ExecuteWorkflow.ts
index d88304a355ea6..5cbe8499b2f8e 100644
--- a/packages/nodes-base/test/nodes/ExecuteWorkflow.ts
+++ b/packages/nodes-base/test/nodes/ExecuteWorkflow.ts
@@ -13,11 +13,13 @@ export async function executeWorkflow(testData: WorkflowTestData, nodeTypes: INo
active: false,
nodeTypes,
});
-
const waitPromise = await createDeferredPromise();
const nodeExecutionOrder: string[] = [];
- const additionalData = Helpers.WorkflowExecuteAdditionalData(waitPromise, nodeExecutionOrder);
-
+ const additionalData = Helpers.WorkflowExecuteAdditionalData(
+ waitPromise,
+ nodeExecutionOrder,
+ testData,
+ );
const workflowExecute = new WorkflowExecute(additionalData, executionMode);
const executionData = await workflowExecute.run(workflowInstance);
diff --git a/packages/nodes-base/test/nodes/Helpers.ts b/packages/nodes-base/test/nodes/Helpers.ts
index 2ef784be0925a..f1ba61aa128ec 100644
--- a/packages/nodes-base/test/nodes/Helpers.ts
+++ b/packages/nodes-base/test/nodes/Helpers.ts
@@ -145,6 +145,7 @@ export class CredentialsHelper extends ICredentialsHelper {
export function WorkflowExecuteAdditionalData(
waitPromise: IDeferredPromise,
nodeExecutionOrder: string[],
+ workflowTestData?: WorkflowTestData,
): IWorkflowExecuteAdditionalData {
const hookFunctions = {
nodeExecuteAfter: [
@@ -167,7 +168,6 @@ export function WorkflowExecuteAdditionalData(
nodes: [],
connections: {},
};
-
return {
credentialsHelper: new CredentialsHelper(credentialTypes),
hooks: new WorkflowHooks(hookFunctions, 'trigger', '1', workflowData),
@@ -175,7 +175,7 @@ export function WorkflowExecuteAdditionalData(
sendMessageToUI: (message: string) => {},
restApiUrl: '',
encryptionKey: 'test',
- timezone: 'America/New_York',
+ timezone: workflowTestData?.input.workflowData.settings?.timezone || 'America/New_York',
webhookBaseUrl: 'webhook',
webhookWaitingBaseUrl: 'webhook-waiting',
webhookTestBaseUrl: 'webhook-test',
@@ -339,7 +339,6 @@ const preparePinData = (pinData: IDataObject) => {
);
return returnData;
};
-
export const workflowToTests = (workflowFiles: string[]) => {
const testCases: WorkflowTestData[] = [];
for (const filePath of workflowFiles) {
diff --git a/packages/nodes-base/test/nodes/types.ts b/packages/nodes-base/test/nodes/types.ts
index 59f069b9fc0a3..000c34c9b1176 100644
--- a/packages/nodes-base/test/nodes/types.ts
+++ b/packages/nodes-base/test/nodes/types.ts
@@ -6,6 +6,12 @@ export interface WorkflowTestData {
workflowData: {
nodes: INode[];
connections: IConnections;
+ settings?: {
+ saveManualExecutions: boolean;
+ callerPolicy: string;
+ timezone: string;
+ saveExecutionProgress: string;
+ };
};
};
output: {