2020-01-29 23:17:42 -08:00
2021-04-17 01:34:48 -07:00
import {
IExecuteFunctions ,
} from 'n8n-core' ;
2020-01-23 16:07:18 -08:00
import {
2020-01-29 23:17:42 -08:00
IDataObject ,
ILoadOptionsFunctions ,
2020-01-23 16:07:18 -08:00
INodeExecutionData ,
2020-10-01 05:01:39 -07:00
INodePropertyOptions ,
2020-01-23 16:07:18 -08:00
INodeType ,
INodeTypeDescription ,
2021-04-16 09:33:36 -07:00
NodeOperationError ,
2020-01-23 16:07:18 -08:00
} from 'n8n-workflow' ;
2021-04-17 01:34:48 -07:00
import {
set ,
} from 'lodash' ;
2022-04-08 14:32:08 -07:00
import moment from 'moment-timezone' ;
2020-01-23 16:07:18 -08:00
export class DateTime implements INodeType {
description : INodeTypeDescription = {
displayName : 'Date & Time' ,
name : 'dateTime' ,
2021-05-16 11:35:11 -07:00
icon : 'fa:clock' ,
2020-01-23 16:07:18 -08:00
group : [ 'transform' ] ,
version : 1 ,
description : 'Allows you to manipulate date and time values' ,
2021-04-17 01:34:48 -07:00
subtitle : '={{$parameter["action"]}}' ,
2020-01-23 16:07:18 -08:00
defaults : {
name : 'Date & Time' ,
color : '#408000' ,
} ,
inputs : [ 'main' ] ,
outputs : [ 'main' ] ,
properties : [
{
displayName : 'Action' ,
name : 'action' ,
type : 'options' ,
options : [
2021-04-17 01:34:48 -07:00
{
name : 'Calculate a Date' ,
description : 'Add or subtract time from a date' ,
value : 'calculate' ,
} ,
2020-01-23 16:07:18 -08:00
{
name : 'Format a Date' ,
2020-01-29 23:17:42 -08:00
description : 'Convert a date to a different format' ,
2020-10-22 06:46:03 -07:00
value : 'format' ,
2020-01-23 16:07:18 -08:00
} ,
] ,
default : 'format' ,
} ,
{
2020-02-07 23:12:15 -08:00
displayName : 'Value' ,
name : 'value' ,
2020-01-23 16:07:18 -08:00
displayOptions : {
show : {
2020-02-07 23:12:15 -08:00
action : [
2020-01-25 14:14:54 -08:00
'format' ,
2020-01-23 16:07:18 -08:00
] ,
} ,
} ,
type : 'string' ,
default : '' ,
2022-05-06 14:01:25 -07:00
description : 'The value that should be converted' ,
2020-01-23 16:07:18 -08:00
required : true ,
} ,
2020-02-07 23:12:15 -08:00
{
displayName : 'Property Name' ,
name : 'dataPropertyName' ,
type : 'string' ,
default : 'data' ,
required : true ,
displayOptions : {
show : {
action : [
'format' ,
] ,
} ,
} ,
2022-05-06 14:01:25 -07:00
description : 'Name of the property to which to write the converted date' ,
2020-02-07 23:12:15 -08:00
} ,
2020-01-25 14:14:54 -08:00
{
displayName : 'Custom Format' ,
name : 'custom' ,
displayOptions : {
show : {
2021-04-17 01:34:48 -07:00
action : [
2020-01-25 14:14:54 -08:00
'format' ,
] ,
} ,
} ,
type : 'boolean' ,
default : false ,
2022-05-06 14:01:25 -07:00
description : 'If a predefined format should be selected or custom format entered' ,
2020-01-25 14:14:54 -08:00
} ,
{
displayName : 'To Format' ,
name : 'toFormat' ,
displayOptions : {
show : {
2021-04-17 01:34:48 -07:00
action : [
2020-01-25 14:14:54 -08:00
'format' ,
] ,
custom : [
true ,
] ,
} ,
} ,
type : 'string' ,
default : '' ,
2020-01-29 23:17:42 -08:00
placeholder : 'YYYY-MM-DD' ,
2022-05-06 14:01:25 -07:00
description : 'The format to convert the date to' ,
2020-01-25 14:14:54 -08:00
} ,
2020-01-23 16:07:18 -08:00
{
displayName : 'To Format' ,
name : 'toFormat' ,
type : 'options' ,
displayOptions : {
show : {
2021-04-17 01:34:48 -07:00
action : [
2020-01-25 14:14:54 -08:00
'format' ,
] ,
2021-04-17 01:34:48 -07:00
custom : [
2020-01-25 14:14:54 -08:00
false ,
2020-01-23 16:07:18 -08:00
] ,
} ,
} ,
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' ,
2022-05-06 14:01:25 -07:00
description : 'The format to convert the date to' ,
2020-01-23 16:07:18 -08:00
} ,
{
displayName : 'Options' ,
name : 'options' ,
displayOptions : {
show : {
2021-04-17 01:34:48 -07:00
action : [
2020-10-22 06:46:03 -07:00
'format' ,
2020-01-23 16:07:18 -08:00
] ,
} ,
} ,
type : 'collection' ,
placeholder : 'Add Option' ,
default : { } ,
options : [
2020-02-07 23:12:15 -08:00
{
displayName : 'From Format' ,
name : 'fromFormat' ,
type : 'string' ,
default : '' ,
2022-05-06 14:01:25 -07:00
description : 'In case the input format is not recognized you can provide the format' ,
2020-02-07 23:12:15 -08:00
} ,
2020-01-23 16:07:18 -08:00
{
displayName : 'From Timezone' ,
name : 'fromTimezone' ,
type : 'options' ,
typeOptions : {
loadOptionsMethod : 'getTimezones' ,
} ,
default : 'UTC' ,
2022-05-06 14:01:25 -07:00
description : 'The timezone to convert from' ,
2020-01-23 16:07:18 -08:00
} ,
2020-01-29 23:17:42 -08:00
{
displayName : 'To Timezone' ,
name : 'toTimezone' ,
type : 'options' ,
typeOptions : {
loadOptionsMethod : 'getTimezones' ,
} ,
default : 'UTC' ,
2022-05-06 14:01:25 -07:00
description : 'The timezone to convert to' ,
2020-01-23 16:07:18 -08:00
} ,
] ,
} ,
2021-04-17 01:34:48 -07:00
{
displayName : 'Date Value' ,
name : 'value' ,
displayOptions : {
show : {
action : [
'calculate' ,
] ,
} ,
} ,
type : 'string' ,
default : '' ,
2022-05-06 14:01:25 -07:00
description : 'The date string or timestamp from which you want to add/subtract time' ,
2021-04-17 01:34:48 -07:00
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' ,
2022-05-06 14:01:25 -07:00
description : 'Time unit for Duration parameter above' ,
2021-04-17 01:34:48 -07:00
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' ,
] ,
} ,
} ,
2022-05-06 14:01:25 -07:00
description : 'Name of the output property to which to write the converted date' ,
2021-04-17 01:34:48 -07:00
} ,
{
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.' ,
} ,
] ,
} ,
2020-01-23 16:07:18 -08:00
] ,
} ;
methods = {
loadOptions : {
// Get all the timezones to display them to user so that he can
// select them easily
async getTimezones ( this : ILoadOptionsFunctions ) : Promise < INodePropertyOptions [ ] > {
const returnData : INodePropertyOptions [ ] = [ ] ;
for ( const timezone of moment . tz . names ( ) ) {
const timezoneName = timezone ;
const timezoneId = timezone ;
returnData . push ( {
name : timezoneName ,
value : timezoneId ,
} ) ;
}
return returnData ;
} ,
2020-10-22 06:46:03 -07:00
} ,
2020-01-23 16:07:18 -08:00
} ;
async execute ( this : IExecuteFunctions ) : Promise < INodeExecutionData [ ] [ ] > {
const items = this . getInputData ( ) ;
2022-04-22 09:29:51 -07:00
const length = items . length ;
2020-01-29 23:17:42 -08:00
const returnData : INodeExecutionData [ ] = [ ] ;
const workflowTimezone = this . getTimezone ( ) ;
let item : INodeExecutionData ;
2020-01-23 16:07:18 -08:00
for ( let i = 0 ; i < length ; i ++ ) {
2021-07-19 23:58:54 -07:00
try {
2020-01-29 23:17:42 -08:00
2021-07-19 23:58:54 -07:00
const action = this . getNodeParameter ( 'action' , 0 ) as string ;
item = items [ i ] ;
2020-01-29 23:17:42 -08:00
2021-07-19 23:58:54 -07:00
if ( action === 'format' ) {
const currentDate = this . getNodeParameter ( 'value' , i ) as string ;
const dataPropertyName = this . getNodeParameter ( 'dataPropertyName' , i ) as string ;
const toFormat = this . getNodeParameter ( 'toFormat' , i ) as string ;
const options = this . getNodeParameter ( 'options' , i ) as IDataObject ;
let newDate ;
2020-06-30 11:30:14 -07:00
2021-07-19 23:58:54 -07:00
if ( currentDate === undefined ) {
continue ;
}
if ( options . fromFormat === undefined && ! moment ( currentDate as string | number ) . isValid ( ) ) {
throw new NodeOperationError ( this . getNode ( ) , 'The date input format could not be recognized. Please set the "From Format" field' ) ;
}
if ( Number . isInteger ( currentDate as unknown as number ) ) {
newDate = moment . unix ( currentDate as unknown as number ) ;
2020-01-29 23:17:42 -08:00
} else {
2021-07-19 23:58:54 -07:00
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 as string , fromTimezone as string ) ;
}
2020-01-29 23:17:42 -08:00
} else {
2021-07-19 23:58:54 -07:00
if ( options . fromFormat ) {
newDate = moment ( currentDate as string , options . fromFormat as string ) ;
} else {
newDate = moment ( currentDate as string ) ;
}
2020-01-29 23:17:42 -08:00
}
2020-01-23 16:07:18 -08:00
}
2020-01-29 23:17:42 -08:00
2021-07-19 23:58:54 -07:00
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 ) ;
}
2020-01-29 23:17:42 -08:00
2021-07-19 23:58:54 -07:00
newDate = newDate . format ( toFormat ) ;
2020-01-29 23:17:42 -08:00
2021-07-19 23:58:54 -07:00
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 } ,
} ;
}
2020-01-29 23:17:42 -08:00
2021-07-19 23:58:54 -07:00
if ( item . binary !== undefined ) {
newItem . binary = item . binary ;
}
2020-01-29 23:17:42 -08:00
2021-07-19 23:58:54 -07:00
set ( newItem , ` json. ${ dataPropertyName } ` , newDate ) ;
2021-04-17 01:34:48 -07:00
2021-07-19 23:58:54 -07:00
returnData . push ( newItem ) ;
2021-04-17 01:34:48 -07:00
}
2021-07-19 23:58:54 -07:00
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 } ,
} ;
}
2021-04-17 01:34:48 -07:00
2021-07-19 23:58:54 -07:00
if ( item . binary !== undefined ) {
newItem . binary = item . binary ;
}
2021-10-26 09:32:33 -07:00
set ( newItem , ` json. ${ dataPropertyName } ` , newDate . toISOString ( ) ) ;
2021-07-19 23:58:54 -07:00
returnData . push ( newItem ) ;
}
2021-04-17 01:34:48 -07:00
2021-07-19 23:58:54 -07:00
} catch ( error ) {
if ( this . continueOnFail ( ) ) {
returnData . push ( { json : { error : error.message } } ) ;
continue ;
}
throw error ;
2021-04-17 01:34:48 -07:00
}
2020-01-23 16:07:18 -08:00
}
2020-01-29 23:17:42 -08:00
return this . prepareOutputData ( returnData ) ;
2020-01-23 16:07:18 -08:00
}
}
2021-04-17 01:34:48 -07:00
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.' ) ;
}
}