dockerized_openAger/nodered/rootfs/data/node_modules/node-red-contrib-moment/moment/nrmoment.js
2020-10-17 18:42:50 +02:00

312 lines
12 KiB
JavaScript

/**
* Copyright (c) 2018 Julian Knight (Totally Information)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
// Node for Node-Red that outputs a nicely formatted string from a date/time
// object or string using the moment.js library.
// require moment.js (must be installed from package.js as a dependency)
var moment = require('moment-timezone');
var parseFormat = require('moment-parseformat');
var osLocale = require('os-locale');
var hostTz = moment.tz.guess();
var hostLocale = osLocale.sync();
// Module name must match this nodes html file
var moduleName = 'moment';
module.exports = function(RED) {
'use strict';
// The main node definition - most things happen in here
function nodeGo(config) {
// Create a RED node
RED.nodes.createNode(this,config);
// Store local copies of the node configuration (as defined in the .html)
this.topic = config.topic;
this.input = config.input || 'payload'; // where to take the input from
this.inputType = config.inputType || 'msg'; // msg, flow, global, timestamp or string
this.fakeUTC = config.fakeUTC || false; // is the input UTC rather than local date/time?
this.adjAmount = config.adjAmount || 0; // number
this.adjType = config.adjType || 'days'; // days, hours, etc.
this.adjDir = config.adjDir || 'add'; // add or subtract
this.format = config.format || ''; // valid moment.js format string
this.locale = config.locale || false // valid moment.js locale string
this.output = config.output || 'payload'; // where to put the output
this.outputType = config.outputType || 'msg'; // msg, flow or global
this.inTz = config.inTz || false; // timezone, '' or zone name, e.g. Europe/London
this.outTz = config.outTz || this.inTz; // timezone, '' or zone name, e.g. Europe/London
// copy "this" object in case we need it in context of callbacks of other functions.
var node = this;
// respond to inputs....
node.on('input', function (msg) {
'use strict'; // We will be using eval() so lets get a bit of safety using strict
// If the node's topic is set, copy to output msg
if ( node.topic !== '' ) {
msg.topic = node.topic;
} // If nodes topic is blank, the input msg.topic is already there
// make sure output property is set, if not, assume msg.payload
if ( node.output === '' ) {
node.output = 'payload';
//node.warn('Output field is REQUIRED, currently blank, set to payload');
}
if ( node.outputType === '' ) {
node.outputType = 'msg';
node.warn('Output Type field is REQUIRED, currently blank, set to msg');
}
// If the input property is blank, assume NOW as the required timestamp
// or make sure that the node's input property actually exists on the input msg
var inp = '';
// If input is a blank string, use a Date object with Now DT
if ( node.input === '' ) {
inp = new Date();
} else {
// Otherwise, check which input type & get the input
try {
switch ( node.inputType ) {
case 'msg':
inp = RED.util.getMessageProperty(msg, node.input);
break;
case 'flow':
inp = node.context().flow.get(node.input);
break;
case 'global':
inp = node.context().global.get(node.input);
break;
case 'date':
inp = new Date();
break;
case 'str':
inp = node.input.trim();
break;
default:
inp = new Date();
node.warn('Unrecognised Input Type, ' + node.inputType + '. Output has been set to NOW.');
}
} catch (err) {
inp = new Date();
node.warn('Input property, ' + node.inputType + '.' + node.input + ', does NOT exist. Output has been set to NOW.');
}
}
// We are going to overwrite the output property without warning or permission!
// Final check for input being a string (which moment doesn't really want to handle)
// NB: moment.js v3 will stop accepting strings. v2.7+ throws a warning.
var dtHack = '', inpFmt = '';
// @from v3 2018-09-23: If input is `null`, change to empty string
if ( inp === null ) inp = '';
if ( (typeof inp) === 'string' ) {
inp = inp.trim();
// Some string input hacks
switch (inp.toLowerCase()) {
case 'today':
inp = new Date();
break;
case 'yesterday':
inp = new Date();
dtHack = {days:-1};
break;
case 'tomorrow':
inp = new Date();
dtHack = {days:+1};
break;
default:
var prefOrder = {preferredOrder: {'/': 'DMY', '.': 'DMY', '-': 'YMD'} };
if ( (node.locale.toLowerCase().replace('-','_') === 'en_US') || (node.inTz.split('/')[0] === 'America') ) {
prefOrder = {preferredOrder: {'/': 'MDY', '.': 'DMY', '-': 'YMD'} };
}
inpFmt = parseFormat(inp, prefOrder);
}
} else if ( (typeof inp) === 'number' ) {
// @from 2017-06-15 IF inp is a number at this point, it needs turning into a date object
inp = new Date(inp)
}
// At this point, `inp` SHOULD be a Date object and safe to pass to moment.js
if ( !node.inTz ) { node.inTz = hostTz; }
if ( !node.outTz ) { node.outTz = node.inTz; }
// Validate input and output timezones - @since 2.0.4
if ( moment.tz.zone(node.inTz) === null ) {
// tz invalid, warn and set to UTC
node.warn('Moment: Input Timezone Invalid, reset to UTC - see http://momentjs.com/timezone/docs/#/data-loading/')
node.inTz = 'UTC'
}
if ( moment.tz.zone(node.outTz) === null ) {
// tz invalid, warn and set to UTC
node.warn('Moment: Output Timezone Invalid, reset to UTC - see http://momentjs.com/timezone/docs/#/data-loading/')
node.outTz = 'UTC'
}
// Get a Moment.JS date/time - NB: the result might not be
// valid since the input might not parse as a date/time
var mDT;
if ( inpFmt !== '' ) {
mDT = moment.tz(inp, inpFmt, true, node.inTz);
} else {
// @from 2017-06-15 change to momentjs meant having to add null parameter
mDT = moment.tz(inp, null, true, node.inTz)
}
// Fallback to JS built-in Date parsing, if not recognized by moment
if (!mDT.isValid()) {
var dtm = new Date(inp);
if (dtm === 'Invalid Date') {
node.warn('Unrecognized date string format => ' + inp);
}
else {
mDT = moment(dtm);
}
}
// Adjust the date for input hacks if needed (e.g. input was "yesterday" or "tommorow")
if ( dtHack !== '' ) { mDT.add(dtHack); }
// JK: Added OS locale lookup
if ( !node.locale ) { node.locale = hostLocale; }
// JK: Add a trap to Jaques44's locale code in case the output locale string is invalid
try {
// Jacques44 - set locale for localised output formats
mDT.locale(node.locale);
} catch (err) {
node.warn('Locale string invalid - check moment.js for valid strings');
}
// Adjust the input date if required
if ( node.adjAmount !== 0 ) {
// check if measure is valid
if ( isMeasureValid(node.adjType) ) {
// NB: moments are mutable so don't need to reassign
if ( node.adjDir === 'subtract') {
mDT.subtract(node.adjAmount, node.adjType);
} else {
mDT.add(node.adjAmount, node.adjType);
}
} else {
// it isn't valid so warn and don't adjust
node.warn('Adjustment measure type not valid, no adjustment made - check moment.js docs for valid measures (days, hours, etc)');
}
}
// ==== NO MORE DATE/TIME CALCULATIONS AFTER HERE ==== //
// If required, change to the output Timezone
if ( node.outTz !== '' ) mDT.tz(node.outTz);
// Check if the input is a date?
if ( ! mDT.isValid() ) {
// THIS SHOULD NEVER BE CALLED - it left to catch the occasional error
node.warn('The input property was NOT a recognisable date. Output will be a blank string');
setOutput(msg, node.outputType, node.output, '');
} else {
// Handle different format strings. We allow any fmt str that
// Moment.JS supports but also some special formats
// If format not set, assume ISO8601 string if input is a Date otherwise assume Date
switch ( node.format.toLowerCase() ) {
case '':
case 'iso8601':
case 'iso':
// Default to ISO8601 string
setOutput(msg, node.outputType, node.output, mDT.toISOString());
break;
case 'fromnow':
case 'timeago':
// We are also going to handle time-from-now (AKA time ago) format
setOutput(msg, node.outputType, node.output, mDT.fromNow());
break;
case 'calendar':
case 'aroundnow':
// We are also going to handle calendar format (AKA around now)
// Force dates >1 week from now to be in ISO instead of US format
setOutput(msg, node.outputType, node.output, mDT.calendar(null,{sameElse:'YYYY-MM-DD'}));
break;
case 'date':
case 'jsdate':
// we also allow output as a Javascript Date object
setOutput(msg, node.outputType, node.output, mDT.toDate());
break;
case 'object':
// we also allow output as a Javascript Date object
setOutput(msg, node.outputType, node.output, mDT.toObject());
break;
default:
// or we assume it is a valid format definition ...
setOutput(msg, node.outputType, node.output, mDT.format(node.format));
}
}
// Send the output message
node.send(msg);
});
// Tidy up if we need to
//node.on('close', function() {
// Called when the node is shutdown - eg on redeploy.
// Allows ports to be closed, connections dropped etc.
// eg: node.client.disconnect();
//});
// Set the appropriate output variable
function setOutput(msg, outputType, output, value) {
try {
switch ( outputType ) {
case 'msg':
RED.util.setMessageProperty(msg, output, value);
break;
case 'flow':
node.context().flow.set(output, value);
break;
case 'global':
node.context().global.set(output, value);
break;
default:
node.warn('Unrecognised Output Type, ' + outputType + '. No output.');
}
} catch (err) {
node.warn('Output property, ' + outputType + '.' + output + ', cannot be set. No output.', err);
}
} // --- end of setOutput function --- //
// Is the date/time adjustment type (measure) valid? See moment.js docs
function isMeasureValid(adjType) {
var validTypes = ['years','y','quarters','Q','months','M','weeks','w','days','d','hours','h','minutes','m','seconds','s','milliseconds','ms'];
//return validTypes.includes(adjType);
return validTypes.indexOf(adjType) > -1;
} // --- end of isMeasureValid function --- //
} // ---- end of nodeGo function ---- //
// Register the node by name. This must be called before overriding any of the
// Node functions.
RED.nodes.registerType(moduleName,nodeGo);
// Create API listener: sends the host locale & timezone to the admin ui
// NB: uses Express middleware on the admin server
RED.httpAdmin.get('/contribapi/moment', RED.auth.needsPermission('moment.read'), function(req,res) {
res.json({
'tz': hostTz,
'locale': hostLocale
});
});
};