mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 12:44:07 -08:00
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:
parent
3c0734bd2d
commit
d2d11e0208
|
@ -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);
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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');
|
||||||
|
|
Loading…
Reference in a new issue