Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix parsing format in date calculation #1589

Merged
merged 11 commits into from
Apr 17, 2021
239 changes: 231 additions & 8 deletions packages/nodes-base/nodes/DateTime.node.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 = {
Expand All @@ -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',
Expand All @@ -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',
Expand Down Expand Up @@ -76,7 +88,7 @@ export class DateTime implements INodeType {
name: 'custom',
displayOptions: {
show: {
action:[
action: [
'format',
],
},
Expand All @@ -90,7 +102,7 @@ export class DateTime implements INodeType {
name: 'toFormat',
displayOptions: {
show: {
action:[
action: [
'format',
],
custom: [
Expand All @@ -109,10 +121,10 @@ export class DateTime implements INodeType {
type: 'options',
displayOptions: {
show: {
action:[
action: [
'format',
],
custom:[
custom: [
false,
],
},
Expand Down Expand Up @@ -162,7 +174,7 @@ export class DateTime implements INodeType {
name: 'options',
displayOptions: {
show: {
action:[
action: [
'format',
],
},
Expand Down Expand Up @@ -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 <a href="https://docs.n8n.io/nodes/n8n-nodes-base.dateTime/#faqs">format</a> for the value.',
},
],
},
],
};

Expand Down Expand Up @@ -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.');
}
}