fix(Date & Time Node): Add fromFormat option to solve ambiguous date strings (#7675)

Github issue / Community forum post (link here to close automatically):
https://community.n8n.io/t/spreadsheet-date-issue/31551

---------

Co-authored-by: Michael Kret <michael.k@radency.com>
This commit is contained in:
Elias Meire 2023-11-10 14:30:13 +01:00 committed by GitHub
parent 3c0734bd2d
commit d2d11e0208
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 36 additions and 14 deletions

View file

@ -130,7 +130,7 @@ export class DateTimeV2 implements INodeType {
const duration = this.getNodeParameter('duration', i) as number; const duration = this.getNodeParameter('duration', i) as number;
const outputFieldName = this.getNodeParameter('outputFieldName', i) as string; const outputFieldName = this.getNodeParameter('outputFieldName', i) as string;
const dateToAdd = parseDate.call(this, addToDate, workflowTimezone); const dateToAdd = parseDate.call(this, addToDate, { timezone: workflowTimezone });
const returnedDate = dateToAdd.plus({ [timeUnit]: duration }); const returnedDate = dateToAdd.plus({ [timeUnit]: duration });
item.json[outputFieldName] = returnedDate.toString(); item.json[outputFieldName] = returnedDate.toString();
@ -141,7 +141,7 @@ export class DateTimeV2 implements INodeType {
const duration = this.getNodeParameter('duration', i) as number; const duration = this.getNodeParameter('duration', i) as number;
const outputFieldName = this.getNodeParameter('outputFieldName', i) as string; const outputFieldName = this.getNodeParameter('outputFieldName', i) as string;
const dateToAdd = parseDate.call(this, subtractFromDate, workflowTimezone); const dateToAdd = parseDate.call(this, subtractFromDate, { timezone: workflowTimezone });
const returnedDate = dateToAdd.minus({ [timeUnit]: duration }); const returnedDate = dateToAdd.minus({ [timeUnit]: duration });
item.json[outputFieldName] = returnedDate.toString(); item.json[outputFieldName] = returnedDate.toString();
@ -150,14 +150,18 @@ export class DateTimeV2 implements INodeType {
const date = this.getNodeParameter('date', i) as string; const date = this.getNodeParameter('date', i) as string;
const format = this.getNodeParameter('format', i) as string; const format = this.getNodeParameter('format', i) as string;
const outputFieldName = this.getNodeParameter('outputFieldName', i) as string; const outputFieldName = this.getNodeParameter('outputFieldName', i) as string;
const { timezone } = this.getNodeParameter('options', i) as { timezone: boolean }; const { timezone, fromFormat } = this.getNodeParameter('options', i) as {
timezone: boolean;
fromFormat: string;
};
if (date === null || date === undefined) { if (date === null || date === undefined) {
item.json[outputFieldName] = date; item.json[outputFieldName] = date;
} else { } else {
const dateLuxon = timezone const dateLuxon = parseDate.call(this, date, {
? parseDate.call(this, date, workflowTimezone) timezone: timezone ? workflowTimezone : undefined,
: parseDate.call(this, date); fromFormat,
});
if (format === 'custom') { if (format === 'custom') {
const customFormat = this.getNodeParameter('customFormat', i) as string; const customFormat = this.getNodeParameter('customFormat', i) as string;
item.json[outputFieldName] = dateLuxon.toFormat(customFormat); item.json[outputFieldName] = dateLuxon.toFormat(customFormat);
@ -171,7 +175,7 @@ export class DateTimeV2 implements INodeType {
const mode = this.getNodeParameter('mode', i) as string; const mode = this.getNodeParameter('mode', i) as string;
const outputFieldName = this.getNodeParameter('outputFieldName', i) as string; const outputFieldName = this.getNodeParameter('outputFieldName', i) as string;
const dateLuxon = parseDate.call(this, date, workflowTimezone); const dateLuxon = parseDate.call(this, date, { timezone: workflowTimezone });
if (mode === 'roundDown') { if (mode === 'roundDown') {
const toNearest = this.getNodeParameter('toNearest', i) as string; const toNearest = this.getNodeParameter('toNearest', i) as string;
@ -193,8 +197,8 @@ export class DateTimeV2 implements INodeType {
isoString: boolean; isoString: boolean;
}; };
const luxonStartDate = parseDate.call(this, startDate, workflowTimezone); const luxonStartDate = parseDate.call(this, startDate, { timezone: workflowTimezone });
const luxonEndDate = parseDate.call(this, endDate, workflowTimezone); const luxonEndDate = parseDate.call(this, endDate, { timezone: workflowTimezone });
const duration = luxonEndDate.diff(luxonStartDate, unit); const duration = luxonEndDate.diff(luxonStartDate, unit);
if (isoString) { if (isoString) {
item.json[outputFieldName] = duration.toString(); item.json[outputFieldName] = duration.toString();
@ -207,7 +211,7 @@ export class DateTimeV2 implements INodeType {
const outputFieldName = this.getNodeParameter('outputFieldName', i) as string; const outputFieldName = this.getNodeParameter('outputFieldName', i) as string;
const part = this.getNodeParameter('part', i) as keyof DateTime | 'week'; const part = this.getNodeParameter('part', i) as keyof DateTime | 'week';
const parsedDate = parseDate.call(this, date, workflowTimezone); const parsedDate = parseDate.call(this, date, { timezone: workflowTimezone });
const selectedPart = part === 'week' ? parsedDate.weekNumber : parsedDate.get(part); const selectedPart = part === 'week' ? parsedDate.weekNumber : parsedDate.get(part);
item.json[outputFieldName] = selectedPart; item.json[outputFieldName] = selectedPart;
returnData.push(item); returnData.push(item);

View file

@ -119,6 +119,16 @@ export const FormatDateDescription: INodeProperties[] = [
default: {}, default: {},
options: [ options: [
includeInputFields, includeInputFields,
{
displayName: 'From Date Format',
name: 'fromFormat',
type: 'string',
default: 'e.g yyyyMMdd',
hint: 'Tokens are case sensitive',
// eslint-disable-next-line n8n-nodes-base/node-param-description-miscased-id
description:
'Format in which the input \'Date\' is, it\'s helpful when the format is not recognized automatically. Use those <a href="https://moment.github.io/luxon/#/formatting?id=table-of-tokens&id=table-of-tokens" target="_blank">tokens</a> to define the format.',
},
{ {
displayName: 'Use Workflow Timezone', displayName: 'Use Workflow Timezone',
name: 'timezone', name: 'timezone',

View file

@ -6,15 +6,18 @@ import { NodeOperationError } from 'n8n-workflow';
export function parseDate( export function parseDate(
this: IExecuteFunctions, this: IExecuteFunctions,
date: string | number | DateTime, date: string | number | DateTime,
timezone?: string, options: Partial<{
timezone: string;
fromFormat: string;
}> = {},
) { ) {
let parsedDate; let parsedDate;
if (date instanceof DateTime) { if (date instanceof DateTime) {
parsedDate = date; parsedDate = date;
} else { } else {
// Check if the input is a number // Check if the input is a number, don't convert to number if fromFormat is set
if (!Number.isNaN(Number(date))) { if (!Number.isNaN(Number(date)) && !options.fromFormat) {
//input is a number, convert to number in case it is a string formatted number //input is a number, convert to number in case it is a string formatted number
date = Number(date); date = Number(date);
// check if the number is a timestamp in float format and convert to integer // check if the number is a timestamp in float format and convert to integer
@ -23,6 +26,7 @@ export function parseDate(
} }
} }
let timezone = options.timezone;
if (Number.isInteger(date)) { if (Number.isInteger(date)) {
const timestampLengthInMilliseconds1990 = 12; const timestampLengthInMilliseconds1990 = 12;
// check if the number is a timestamp in seconds or milliseconds and create a moment object accordingly // check if the number is a timestamp in seconds or milliseconds and create a moment object accordingly
@ -37,7 +41,11 @@ export function parseDate(
timezone = `Etc/GMT-${offset * 1}`; timezone = `Etc/GMT-${offset * 1}`;
} }
parsedDate = DateTime.fromISO(moment(date).toISOString()); if (options.fromFormat) {
parsedDate = DateTime.fromFormat(date as string, options.fromFormat);
} else {
parsedDate = DateTime.fromISO(moment(date).toISOString());
}
} }
parsedDate = parsedDate.setZone(timezone || 'Etc/UTC'); parsedDate = parsedDate.setZone(timezone || 'Etc/UTC');