Workflow funktioniert nun wieder. Es gab Probleme nach Aenderungen.
Build and Publish Site / docker (push) Successful in 23s

ABER: Die Applikation funktioniert nur lokal. Die deployte Version geht noch nicht.
This commit is contained in:
2026-07-03 13:24:08 +02:00
parent 97a22cf704
commit b518ae8edb
1845 changed files with 292358 additions and 57 deletions
+51
View File
@@ -0,0 +1,51 @@
/** JavaScript Versions
* 5 is minimum -> Last IE11
* 6 = 2015 -> Node >8.10, iOS12+
* 7 = 2016 -> FF78+,
* 8 = 2017 -> Node 10.9+
* 9 = 2018 -> Node 12.11+
*/
module.exports = {
root: true,
env: {
node: true,
browser: true,
//es6: true,
//es5: true,
},
parserOptions: {
//parser: "babel-eslint",
ecmaVersion: 2017,
// specifying a module sourcetype prevent eslint from marking import statements as errors
//sourceType: 'module',
},
extends: [
// use the recommended rule set for both plain javascript and vue
'eslint:recommended',
'plugin:prettier/recommended',
],
rules: {
// we should always disable console logs and debugging in production
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-consoledebugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'indent': [
'warn',
4,
{'SwitchCase': 1}
],
'linebreak-style': [
'error',
'unix'
],
'quotes': [
'warn',
'single'
],
'semi': [
'warn',
'never'
],
'comma-dangle': 'off',
'no-unused-vars': 'warn',
},
}
+45
View File
@@ -0,0 +1,45 @@
Please note that this node is unlikely to recieve further enhancements now that moment.js is built into JSONata and so is available from change, function and other nodes.
- 5.0.0 - Update dependencies, replace `os-locale` with `os-locale-s`.
os-locale library is now ESM only which won't work with Node-RED. It is also bloated. Replaced with the lighter-weight os-locale-s library
**WARNING** Neither os-locale nor os-locale-s seems to work correctly (at least on Windows 11) - it is returning `en_US` rather than `en_GB` on my UK only W11 system.
**BREAKING** Minimum node.js version is now 14.14 and the minimum Node-RED version is v3.
**FIXED** - output format `x` and `X` now accepted since the moment.js library is now updated.
- 4.0.0 - Dependency update, output settings, bug fix
- **BREAKING CHANGE** Updated dependencies mean that minimum Node.js version is now v10 (previously v8). Minimum Node-RED version is now v1.0 (prevously v0.13)
- Update dependencies
- Move change log to separate file from readme.
- Contributed changes, see [Issue #34](https://github.com/TotallyInformation/node-red-contrib-moment/issues/34):
- Explanation extended, examples added
- Testing flows moved from subdir examples\ to subdir testing\
- .html help extended
- Prep for Node-RED v1.
- Workaround for upstream parseFormat bug, see [Issue #24](https://github.com/TotallyInformation/node-red-contrib-moment/issues/24). If input is an ISO date string with seconds >3dp, the input is trimmed to 3dp seconds.
- Added `msg.settings` to output msg. Shows the settings/parameters used. Allows for further processing or validation if needed.
- Added ability to override input and output Timezone settings from an incoming msg object. The appropriate setting must be empty and the properties to use are `msg.inTz` and `msg.outTz`. Resolves [Issue #31](https://github.com/TotallyInformation/node-red-contrib-moment/issues/31).
- Bug fix, locale now correctly defaults to host locale if not provided.
- 3.0.3 - Just a version number bump to fix npm.
- 3.0.2 - Update dependencies to latest versions.
- 3.0.1 - **Bug Fix** Remove errant log statement.
- 3.0.0 - No feature changes, just upgrades of the dependent libraries. Note breaking change, minimum Node.JS version is now v6 or above.
- Documentation updated to clarify processing of different inputs.
- Fix: If input property contains `null`, output is now a warning + empty string in line with other invalid inputs. It was an incorrect timestamp.
- Examples added to Node-RED library to make testing easier.
- 2.0.7 - Fallback to new Date(inp) when the date string is non-standard, like this output from the file stat() function: `2017-12-12 16:03:51.832427000 -0400` as well as the Twitter API default time string: `Mon Jan 08 21:24:37 +0000 2018` (contributed by [Steve Rickus](https://github.com/shrickus)). Update dependencies.
- 2.0.6 - Upstream change in MomentJS introduced bug when feeding with a timestamp (number), fixed
- 2.0.5 - Autocorrect common tz errors (e.g. UTC+4 should be ETC/GMT+4) & autofill default
- 2.0.4 - Show warning for invalid timezone specifications
- 2.0.3 - Humanize helper node added, exposes moments humanize of timespan functionality (contributed by [Laro88](https://github.com/Laro88)),
Documentation updated to highlight issue with moment.js object handing (month is 0-11 not 1-12)
- 2.0.2 - Minor fixes - change version to get latest moment.js. 2017-03-19
- 2.0.1 - Fix get/set of msg properties. 2016-07-08
- 2.0.0 - Significant rewrite, updated moment.js, got rid of all eval's, added adjustment calcs, added time zone and locale awareness. 2016-06-26
- 1.0.9 - Merged in some fixes on Jacques44's contributions & acknowledged him in the package. Also fixed the npm readme. 2016-06-12
- 1.0.5 - Merged a pull request containing a Locale option for localisation. 2016-03-30
- 1.0.3 - First stable release. 2015-01-31
@@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at the GitHub Issues page for this repo. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
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.
+387
View File
@@ -0,0 +1,387 @@
# node-red-contrib-moment
**Please note that this node is unlikely to recieve further enhancements now that moment.js is built into JSONata and so is available from change, function and other nodes.**
[Node-RED](http://nodered.org) node ***moment*** produces a nicely formatted Date/Time string using the Moment.JS library. The node is fully time zone/DST/locale aware.
Node ***humanizer*** converts time durations (time spans) into textual descriptions (e.g. 2 minutes).
Both nodes are locale aware regarding the language of the output strings.
![node-appearance](images/node-appearance.png "Node appearance")
**Fig. 1:** Node appearance
[![Platform](https://img.shields.io/badge/platform-Node--RED-red)](https://nodered.org)
[![NPM Version](https://img.shields.io/npm/v/node-red-contrib-moment.svg?logo=npm)](https://www.npmjs.com/package/node-red-contrib-moment)
[![NPM Total Downloads](https://img.shields.io/npm/dt/node-red-contrib-moment.svg)](https://www.npmjs.com/package/node-red-contrib-moment)
[![NPM Downloads per month](https://img.shields.io/npm/dm/node-red-contrib-moment.svg)](https://www.npmjs.com/package/node-red-contrib-moment)
[![GitHub last commit](https://img.shields.io/github/last-commit/totallyinformation/node-red-contrib-moment.svg)](https://github.com/TotallyInformation/node-red-contrib-moment)
[![GitHub stars](https://img.shields.io/github/stars/TotallyInformation/node-red-contrib-moment.svg)](https://github.com/TotallyInformation/node-red-contrib-moment/watchers)
[![GitHub watchers](https://img.shields.io/github/watchers/TotallyInformation/node-red-contrib-moment.svg)](https://github.com/TotallyInformation/node-red-contrib-moment/stargazers)
[![GitHub license](https://img.shields.io/github/license/TotallyInformation/node-red-contrib-moment.svg)](https://github.com/TotallyInformation/node-red-contrib-moment/blob/master/LICENSE)
[![Min Node Version](https://img.shields.io/node/v/node-red-contrib-moment.svg)](https://www.npmjs.com/package/node-red-contrib-moment)
[![Package Quality](http://npm.packagequality.com/shield/node-red-contrib-moment.png)](http://packagequality.com/#?package=node-red-contrib-moment)
[![Dependencies](https://img.shields.io/david/TotallyInformation/node-red-contrib-moment.svg)](https://github.com/TotallyInformation/node-red-contrib-moment)
[![Open Issues](https://img.shields.io/github/issues-raw/TotallyInformation/node-red-contrib-moment.svg)](https://github.com/TotallyInformation/node-red-contrib-moment/issues)
[![Closed Issues](https://img.shields.io/github/issues-closed-raw/TotallyInformation/node-red-contrib-moment.svg)](https://github.com/TotallyInformation/node-red-contrib-moment/issues?q=is%3Aissue+is%3Aclosed)
Based on thoughts from a [conversation in the Node-RED Google Group](https://groups.google.com/d/msg/node-red/SXEGvfFLfQA/fhJCGBWvYEAJ). Updated with timezone/locale capabilities after Jaques44's initial work. Updated with +/- adjustments after [another conversion in the Google Group](https://groups.google.com/forum/#!topic/node-red/u3qoISFoKus).
<a name="installation"></a>
# Installation
<a name="installation_in_node-red"></a>
## In Node-RED (preferred)
* Via Manage Palette -> Search for "node-red-contrib-moment"
<a name="installation_in_a_shell"></a>
## In a shell
Basic installation:
* go to the Node-RED "userDir" folder, typically `~/.node-red`
* run `npm install node-red-contrib-moment`
To get the latest development version, install with:
* run `npm install TotallyInformation/node-red-contrib-moment`
<a name="moment_usage"></a>
# Usage of node *moment*
The easiest usage of the node is feeding it with an timestamp inject:
![moment-basic-usage](images/moment-basic-usage.png "Node moment usage")
**Fig. 2:** Basic usage node *moment*
# Updates
Please see the [CHANGELOG](CHANGELOG.md) document.
# Usage
## Moment
The node generally expects an input from the incoming msg. By default, this is msg.payload. If it is a recognisable date/time, it will apply a format and output the resulting string or object accordingly.
Input and output time zones are settable as is the output locale. All of which default to the host systems tz/locale.
This allows the node to be used to translate from one time zone to another. It also will take into account daylight savings time (DST).
You can also apply an adjustment to the date/time by adding or subtracting an amount.
<a name="moment_node_configuration"></a>
## Configuration of node *moment*
![moment-node-settings](images/moment-node-configuration.png "Node moment properties")
**Fig. 3:** Properties of node *moment*
### *Input from* and *Output to*
These two configuration properties define the `msg` properties in which the input and output data are read from resp. written to. Default is `msg.payload`.
### *Input Timezone* and *Output Timezone*
#### *Input Timezone*
This property defines the timezone of the time fed via the input `msg`. Internally the input time is converted into UTC for further processing.
The format of *Input Timezone* is in the format *region/location*, e.g. Europe/London. See also timezone lists e.g. built in to [moment-timezone](https://momentjs.com/timezone/) or given in [wikipedia](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).
**Note**: Spellings are not validated, if it doesn't seem to work, check the validity of *region/location* with these timezone lists.
The following behaviour is valid:
- If the input data contains a Node-RED timestamp this property is ommitted
- If the host system has a local timezone set (e.g. `dpkg-reconfigure tzdata` on Linux), the input timestamp is related to this local timezone.
- If the host system has no local timezone set, the input timestamp is related to UTC.
- If the input data contains an interpretable string, this property is used (to convert internally to UTC).
If left blank in settings, this field may be set from the incoming `msg.inTz` property.
#### *Output Timezone*
This property defines the timezone of the time emitted via the output `msg`.
The format of *Output Timezone* is described above (see *Input Timezone*).
The following behaviour is valid:
- If *Output Format* is left blank, the output format is in 'Zulu' format, independent of the contents of the additional properties *Output Timezone* and *Locale*.
Zulu format see: https://momentjs.com/docs/#/displaying/as-iso-string/
(Example: 2013-02-04T22:44:30.652Z)
If left blank in settings, this field may be set from the incoming `msg.outTz` property.
### *Adjustment*
Using this property, the time can be adjusted by a manually given value. Adjustments can be positive or negative and can be given in milliseconds, seconds, minutes, hours, days, weeks, months, quarters, years.
### *Output Format* and *Locale*
These two properties in combination define the output format emitted in the output `msg`.
#### *Output Format*
The *Output Format* property defines the format and is described in the [moment.js displaying format section](https://momentjs.com/docs/#/displaying/format/).
It may be any format string defined by moment.js. The formatting additions from *moment-timezone* are also allowed. In addition, further (not case sensitive, alternatives in brackets) format strings are also allowed.
Note that with the exception of ISO8601, other formats are in the specified timezone & DST. If not specified, the output timezone/DST is the same as the input.
Use an output timezone of UTC to force output to that.
##### Format string defined by moment.js
The format string defined by moment.js basically has two options:
- ***Manual given format string:*** This is a string where the time/date parts are represented by characters. Also text parts are allowed. Examples:
- "DD.MM.YYYY HH:mm" gives *20.09.2020 08:30*
- "dddd, MMMM Do YYYY, h:mm:ss a" gives *Sunday, February 14th 2010, 3:25:50 pm*
- "[Today is] dddd" gives *Today is Sunday*
- "[Date: ]YYYY-MM-DD [Time: ]HH:mm:ss" gives *Date: 2020-09-20 Time: 08:31:45*
- ["fromNow"](https://momentjs.com/docs/#/displaying/fromnow/) gives *in a month*
- ["calendar"](https://momentjs.com/docs/#/displaying/calendar-time/) gives *Last Monday*
- ***Predefined localized string:*** This is a string which defines a localized format. Examples:
- "LLL" gives *October 20, 2020 8:33 AM*
- "LTS" gives *8:30:25 PM*
- "llll" gives *Thu, Sep 4, 1986 8:30 PM*
For more options see https://momentjs.com/docs/#/displaying/format/.
##### Format string is left blank resp. is "ISO8601" or "ISO"
In this case the output is in ISO 8602 format, e.g. "2015-01-28T16:24:48.123Z".
Note that ISO8601 formatted output is ALWAYS in UTC ('Z', Zulu time) not local, no matter what output timezone you may specify.
See also [`moment().toISOString()`](https://momentjs.com/docs/#/displaying/as-iso-string/).
##### Format string is "date" resp. "jsDate"
This is a *Javascript Date object* in the form `{years:nnnn, months:n, date:n, hours:n, minutes:n, seconds:n, milliseconds:n}`.
It may be used for manual (fixed) data/time values.
WARNING: moment.js has a bizarre object format where the month is zero-based (0-11) instead of 1-based (1-12) like all the other elements are. I don't currently know why, I've raised an upstream issue but this appears to be a deliberate decision for some strange reason.
See also [`moment().toObject()`](https://momentjs.com/docs/#/displaying/as-object/).
##### Format string is "fromNow" resp. "timeAgo"
This is a human readable output, e.g. *30 minutes ago* or *in a month* (only rough time spans are given in this output format type, see also the *humanizer* example below). The time span is derived from the actual time and the time fed into the node.
See also [`moment().fromNow()`](https://momentjs.com/docs/#/displaying/fromnow/).
##### Format string is "calendar" resp. "aroundNow"
This is a human readable alternative, e.g. *Last Monday* or *Tomorrow 2:30pm*.
Note that dates beyond a week from now are output as yyyy-mm-dd.
See also [`moment().calendar()`](https://momentjs.com/docs/#/displaying/calendar-time/).
##### Format string is "date" resp. "jsDate"
This output format type is actually not working (see [issue #37](https://github.com/TotallyInformation/node-red-contrib-moment/issues/37)).
#### *Locale*
In case of a textual output string contents the *Locale* property defines the language of the textual parts (e.g. "October" vs. "Oktober" vs. "ottobre" vs. "lokakuuta").
If the output is shown in the wrong format, such as dates in US mm/dd/yy format, change the output locale. For example, using en_gb will force short dates to output in dd/mm/yy format. The default is en which moment assumes means the USA :-(
See also [Locale Helper](https://lh.2xlibre.net/locales/) (Note: Not every locale given there is supported).
### *Topic* (additional topic)
Using this property you can add an additional topic to the output `msg.topic`.
A resulting `msg` may be (value "myTopicString"):
```json
{"_msgid":"b16b00b5.bada55","payload":"2020-09-20T12:47:55.143Z","topic":"myTopicString"}
```
## Input of node *moment*
Input values in the object **Input from** can be of the following types:
- ***timestamp:*** The current date/time is used as input.
- ***msg***, ***global*** or ***flow*** and the given property is empty or does not exist: The current date/time is used as input.
- ***JSON date time object:*** This data time object may contain the following elements: *years*, *months*, *days*, *hours*, *minutes*, *seconds*, *milliseconds*.
Example: `{"years":2020,"months":1,"date":11,"hours":5,"minutes":6}`.
If elements are not given (e.g. *years* and *months* are missing in the object) the actual time values are used instead.
- ***a property containing a string that is a recognizable date/time:*** The value will be interpreted and processed.
Example: `2020-02-11T05:06`
- ***a property containing a numeric value:*** The value will be assumed to be a [UNIX time value](https://momentjs.com/docs/#/displaying/unix-timestamp-milliseconds/) (ms since 1970-01-01). Remark: This is the format which the node *Inject* emits at option **timestamp**.
- ***a property containing a string that is not a recognisable date/time (including `null`):*** Then no conversion takes place, the output will be an empty string plus a debug warning.
Note that parsing date/time strings is a hard problem. moment.parseFormat helps but it isn't magic. We assume that ambiguous input dates such as 3/5/05 are in EU/UK format dd/mm/yy unless either the input timezone is in America or the locale is set to en_US.
## Outputs of node *moment*
If the **output** property is not `msg.payload` the input `msg.payload` is retained in the output.
The date/time output is a formatted string if the configuration property ***Output Format*** is anything other than *date* resp. *jsDate* or *object* in which case the output is a Javascript date object or an object as described below respectively.
Output string formatting is controlled by the ***Locale*** and the ***Output Format*** setting. Note that the output Timezone is ignored for ISO8601 output (the default), such output is always in UTC. For other formats, the output will be in the specified timezone which defaults to your host timezone.
Specifying different input and output timezones allows you to translated between them.
The output `msg` will pass through the input `msg.topic` unless it is overridden by the ***Topic*** configuration property. If the ***Output to*** field is changed from the default `msg.payload`, the input `msg.payload` will also be passed through.
# Usage of node *humanizer*
This node converts an input time span to a humanized text string to the output `msg.payload.humanized`. The language of the output string is derived from the locale of the system, i.e. it is not changeable (like the *Locale* property of the *moment* node).
See also [moment.duration().humanize()](https://momentjs.com/docs/#/durations/humanize/).
(Contributed by [Laro88](https://github.com/Laro88))
## Configuration of node *humanizer*
![humanizer-node-settings](images/humanizer-node-configuration.png "Node humanizer properties")
**Fig. 4:** Properties of node *humanizer*
### 'Input variable'
This property defines the input `msg.payload` property which shall be used for the conversion. If left blank, `msg.payload` is used.
## Input of node *humanizer*
The input is a number which defines a time span in seconds.
## Outputs of node *humanizer*
The output is a string object in `msg.payload.humanized`.
The time spans are evaluated in intervals, see *humanizer* example for details.
# Examples
***
**Remark**: Example flows are present in the examples subdirectory. In Node-RED they can be imported via the import function and then selecting *Examples* in the vertical tab menue.
All example flows can also be found in the examples folder of this package.
***
## Usage of the *moment* node
The basic usage is shown in Fig. 2. The following examples shall give an overview how to use the rich configuration properties.
### Usage of configuration properties *Output Timezone*, *Output Format* and *Adjustment*
A sample flow is:
![Alt text](images/moment-timezone-format-adjustment.png?raw=true "Output Timezone, Output Format and Adjustment")
[**output-timezone-format-adjustment flow**](examples/timezones-outputformat-example-flow.json)
**Fig. 5:** Example flow showing the usage of *Output Timezone*, *Output Format* and *Adjustment*
### Usage of configuration property *Input Timezone*
A sample flow is:
![Alt text](images/moment-string-input.png?raw=true "Input Timezone")
[**input-timezone flow**](examples/input-timezones-example-flow.json)
**Fig. 6:** Example flow showing the usage of *Input Timezone*
### Usage of configuration properties *Output Format* and *Locale*
A sample flow is:
![Alt text](images/moment-locale-format.png?raw=true "Output Format and Locale")
[**output-format-locale flow**](examples/locale-example-flow.json)
**Fig. 7:** Example flow showing the usage of *Output Format* and *Locale*
## Usage of the *humanizer* node
A sample flow is:
![Alt text](images/humanizer-time-limits.png?raw=true "Humanizer node")
[**humanizer flow**](examples/humanize-example-flow.json)
**Fig. 8:** Example flow showing the usage of the *humanizer* node
# Depends on
- [Moment.js](http://momentjs.com/docs) - Clever date/time handler for Node.js and browsers
- [Moment-Timezone](http://momentjs.com/timezone/docs) - Adds timezone and locale awareness to Moment.js
- [Zulu](https://momentjs.com/docs/#/displaying/as-iso-string/) time format
- [Timezones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones/) (tzdata)
- [Moment-ParseFormat](https://github.com/gr2m/moment-parseformat) - Tries to interpret input strings as date/times and creates a format string that moment.js can use.
- [Locale Helper](https://lh.2xlibre.net/locales/) - lists locale options
- [Node-RED](http://nodered.org/docs/) - of course!
# To Do
Summary of things I'd like to do with the moment node (not necessarily immediately):
* [ ] Add some additional nodes for doing date/time calculations - partly complete, can do simple add/subtract from main node
* [ ] Add additional node for doing duration calculations
* [ ] Add a combo box to the Format field with common formats pre-populated
* [x] Improve the error messages when Moment.JS fails to interpret the input (say why)
* [x] Allow more input date/time formats - turns out Moment.JS doesn't really help here. At present, I see too many input failures from US/UK date formats, etc.
It would be great if I could parse "human" inputs like "tomorrow" and "2 minutes from now". We can output them now but not input them. As of v1.0.5, a localisation parameter is supported.
~~Partly complete: Added the [parseFormat plugin](https://github.com/gr2m/moment.parseFormat). That failed, see code for details.~~ Now complete.
# License
This code is Open Source under an Apache 2 License. Please see the [apache2-license.txt file](https://github.com/TotallyInformation/node-red-contrib-moment/apache2-license.txt) for details.
You may not use this code except in compliance with the License. You may obtain an original 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. Please see the
License for the specific language governing permissions and limitations under the License.
# Author
[Julian Knight](https://uk.linkedin.com/in/julianknight2/) ([Totally Information](https://www.totallyinformation.com)), https://github.com/totallyinformation
# Contributors/Credits
* [Vicary Archangel](https://github.com/vicary)
* [Steve Rickus](https://github.com/shrickus)
* [Jes Ramsing](https://github.com/Laro88)
* [Jacques W](https://github.com/Jacques44)
* [StephanStS](https://github.com/StephanStS)
Many thanks for the contributions.
# Feedback and Support
Please report any issues or suggestions via the [Github Issues list for this repository](https://github.com/TotallyInformation/node-red-contrib-moment/issues).
For more information, feedback, or community support see the Node-RED Google groups forum at https://groups.google.com/forum/#!forum/node-red
@@ -0,0 +1 @@
[{"id":"5cfa0538.e4611c","type":"moment","z":"7781c60a.76f4a","name":"","topic":"","input":"","inputType":"msg","inTz":"Europe/Berlin","adjAmount":0,"adjType":"days","adjDir":"add","format":"","locale":"C","output":"payload","outputType":"msg","outTz":"Europe/Berlin","x":410,"y":300,"wires":[["c4a52bba.80f5a"]]},{"id":"c56437dc.020958","type":"inject","z":"7781c60a.76f4a","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"","payloadType":"date","x":170,"y":300,"wires":[["5cfa0538.e4611c"]]},{"id":"c4a52bba.80f5a","type":"debug","z":"7781c60a.76f4a","name":"","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":660,"y":300,"wires":[]}]
@@ -0,0 +1 @@
[{"id":"d4f1e907.c039a","type":"inject","z":"7781c60a.76f4a","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"1","payloadType":"num","x":150,"y":2020,"wires":[["b69a68f2.86227"]]},{"id":"b69a68f2.86227","type":"humanizer","z":"7781c60a.76f4a","name":"","input":"","x":590,"y":2020,"wires":[["98a09560.d0f7c8"]]},{"id":"98a09560.d0f7c8","type":"debug","z":"7781c60a.76f4a","name":"","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload.humanized","targetType":"msg","statusVal":"payload","statusType":"auto","x":900,"y":2020,"wires":[]},{"id":"26b90e19.ec32ea","type":"inject","z":"7781c60a.76f4a","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"10","payloadType":"num","x":330,"y":2040,"wires":[["b69a68f2.86227"]]},{"id":"6969869b.9661a8","type":"inject","z":"7781c60a.76f4a","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"44","payloadType":"num","x":150,"y":2100,"wires":[["b69a68f2.86227"]]},{"id":"8f921df4.df755","type":"inject","z":"7781c60a.76f4a","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"90","payloadType":"num","x":330,"y":2160,"wires":[["b69a68f2.86227"]]},{"id":"bd2fd2a3.3d0b88","type":"inject","z":"7781c60a.76f4a","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"2669","payloadType":"num","x":150,"y":2260,"wires":[["b69a68f2.86227"]]},{"id":"4d1b6b65.285d2c","type":"inject","z":"7781c60a.76f4a","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"2670","payloadType":"num","x":330,"y":2260,"wires":[["b69a68f2.86227"]]},{"id":"6850f375.2dccdc","type":"inject","z":"7781c60a.76f4a","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"77399","payloadType":"num","x":150,"y":2320,"wires":[["b69a68f2.86227"]]},{"id":"67e9d190.0bc4f","type":"inject","z":"7781c60a.76f4a","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"77400","payloadType":"num","x":330,"y":2320,"wires":[["b69a68f2.86227"]]},{"id":"86e0a151.259b28","type":"inject","z":"7781c60a.76f4a","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"27612332","payloadType":"num","x":170,"y":2380,"wires":[["b69a68f2.86227"]]},{"id":"ad2a6077.ddcdb8","type":"inject","z":"7781c60a.76f4a","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"45","payloadType":"num","x":330,"y":2100,"wires":[["b69a68f2.86227"]]},{"id":"ffe556c3.2b9f78","type":"inject","z":"7781c60a.76f4a","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"89","payloadType":"num","x":150,"y":2160,"wires":[["b69a68f2.86227"]]},{"id":"6e52a601.24ab18","type":"comment","z":"7781c60a.76f4a","name":"seconds -> minutes","info":"","x":620,"y":2100,"wires":[]},{"id":"861de75a.77fa9","type":"comment","z":"7781c60a.76f4a","name":"1 minute -> 2 minutes","info":"","x":630,"y":2160,"wires":[]},{"id":"78e43b8.ee91844","type":"comment","z":"7781c60a.76f4a","name":"2 minutes -> 3 minutes","info":"","x":630,"y":2200,"wires":[]},{"id":"64ed3c6f.2abaac","type":"inject","z":"7781c60a.76f4a","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"150","payloadType":"num","x":330,"y":2200,"wires":[["b69a68f2.86227"]]},{"id":"392a1ef8.e68542","type":"inject","z":"7781c60a.76f4a","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"149","payloadType":"num","x":150,"y":2200,"wires":[["b69a68f2.86227"]]},{"id":"aa8e7959.37184","type":"comment","z":"7781c60a.76f4a","name":"44 minutes -> 1 hour","info":"","x":620,"y":2260,"wires":[]},{"id":"15094e69.c47492","type":"comment","z":"7781c60a.76f4a","name":"21 minutes -> 1 day","info":"","x":620,"y":2320,"wires":[]},{"id":"fe7ccdb4.4deee8","type":"inject","z":"7781c60a.76f4a","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"27612333","payloadType":"num","x":350,"y":2380,"wires":[["b69a68f2.86227"]]},{"id":"237ff9ca.f8d1ee","type":"comment","z":"7781c60a.76f4a","name":"10 months -> 1 year","info":"","x":620,"y":2380,"wires":[]},{"id":"bb9c80ff.83516","type":"inject","z":"7781c60a.76f4a","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"300000000","payloadType":"num","x":170,"y":2420,"wires":[["b69a68f2.86227"]]},{"id":"bc9cfad2.10a04","type":"comment","z":"7781c60a.76f4a","name":"several years","info":"","x":600,"y":2420,"wires":[]}]
@@ -0,0 +1 @@
[{"id":"67721678.7c81b","type":"inject","z":"7781c60a.76f4a","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":"","topic":"","payload":"","payloadType":"date","x":170,"y":1100,"wires":[["dd5ac79d.0bb1b","a0b2e140.4ef9e","2cf63a10.6dcd1e","10fbb289.d1d2cd"]]},{"id":"dd5ac79d.0bb1b","type":"debug","z":"7781c60a.76f4a","name":"","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":400,"y":1100,"wires":[]},{"id":"af01e075.0c9fa8","type":"debug","z":"7781c60a.76f4a","name":"","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":620,"y":1160,"wires":[]},{"id":"a0b2e140.4ef9e","type":"moment","z":"7781c60a.76f4a","name":"-> Berlin","topic":"","input":"","inputType":"msg","inTz":"Europe/Berlin","adjAmount":0,"adjType":"days","adjDir":"add","format":"DD.MM.YYYY HH:mm","locale":"de_DE","output":"","outputType":"msg","outTz":"Europe/Berlin","x":380,"y":1160,"wires":[["af01e075.0c9fa8"]]},{"id":"2cf63a10.6dcd1e","type":"moment","z":"7781c60a.76f4a","name":"-> New York","topic":"","input":"","inputType":"msg","inTz":"Europe/Berlin","adjAmount":0,"adjType":"days","adjDir":"add","format":"YYYY-MM-DD HH:mm","locale":"C","output":"","outputType":"msg","outTz":"America/New_York","x":400,"y":1220,"wires":[["2a25cf32.873b88","4909536f.ec7034"]]},{"id":"2a25cf32.873b88","type":"moment","z":"7781c60a.76f4a","name":"-> Cape Verde","topic":"","input":"","inputType":"msg","inTz":"America/New_York","adjAmount":0,"adjType":"days","adjDir":"add","format":"YYYY-MM-DD HH:mm","locale":"de_DE","output":"","outputType":"msg","outTz":"Atlantic/Cape_Verde","x":630,"y":1280,"wires":[["216bcf6a.3afc5","f4dad3c9.a2534"]]},{"id":"216bcf6a.3afc5","type":"moment","z":"7781c60a.76f4a","name":"-> Fiji","topic":"","input":"","inputType":"msg","inTz":"Atlantic/Cape_Verde","adjAmount":0,"adjType":"days","adjDir":"add","format":"YYYY-MM-DD HH:mm","locale":"de_DE","output":"","outputType":"msg","outTz":"Pacific/Fiji","x":830,"y":1340,"wires":[["9e860845.c3be28","75961a3d.b294fc"]]},{"id":"9e860845.c3be28","type":"moment","z":"7781c60a.76f4a","name":"-> Berlin","topic":"","input":"","inputType":"msg","inTz":"Pacific/Fiji","adjAmount":0,"adjType":"days","adjDir":"add","format":"DD.MM.YYYY HH:mm","locale":"de_DE","output":"","outputType":"msg","outTz":"Europe/Berlin","x":1000,"y":1400,"wires":[["66554f1a.2b0f28"]]},{"id":"4909536f.ec7034","type":"debug","z":"7781c60a.76f4a","name":"","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":620,"y":1220,"wires":[]},{"id":"f4dad3c9.a2534","type":"debug","z":"7781c60a.76f4a","name":"","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":860,"y":1280,"wires":[]},{"id":"75961a3d.b294fc","type":"debug","z":"7781c60a.76f4a","name":"","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":1020,"y":1340,"wires":[]},{"id":"66554f1a.2b0f28","type":"debug","z":"7781c60a.76f4a","name":"","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":1200,"y":1400,"wires":[]},{"id":"10fbb289.d1d2cd","type":"moment","z":"7781c60a.76f4a","name":"-> Cape Verde","topic":"","input":"","inputType":"msg","inTz":"America/New_York","adjAmount":0,"adjType":"days","adjDir":"add","format":"YYYY-MM-DD HH:mm","locale":"de_DE","output":"","outputType":"msg","outTz":"Atlantic/Cape_Verde","x":410,"y":1340,"wires":[["3bcfd1b5.947c1e"]]},{"id":"3bcfd1b5.947c1e","type":"debug","z":"7781c60a.76f4a","name":"","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":620,"y":1340,"wires":[]}]
@@ -0,0 +1 @@
[{"id":"190891f8.377506","type":"inject","z":"7781c60a.76f4a","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":"","topic":"","payload":"","payloadType":"date","x":170,"y":1600,"wires":[["4fea9ce2.4725cc","3c922d1a.4ec682","e9e897f3.f23db8","e3288390.06fad","cab8a0fe.d50278","a283a279.87395","82edd4e3.7517a8","856447c4.9b118"]]},{"id":"4fea9ce2.4725cc","type":"moment","z":"7781c60a.76f4a","name":"-> Berlin, DD.MM.YYYY HH:mm","topic":"","input":"","inputType":"msg","inTz":"Europe/Berlin","adjAmount":0,"adjType":"days","adjDir":"add","format":"DD.MM.YYYY HH:mm","locale":"de_DE","output":"","outputType":"msg","outTz":"Europe/Berlin","x":460,"y":1660,"wires":[["7d005619.57e83"]]},{"id":"3c922d1a.4ec682","type":"debug","z":"7781c60a.76f4a","name":"","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":400,"y":1600,"wires":[]},{"id":"7d005619.57e83","type":"debug","z":"7781c60a.76f4a","name":"","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":740,"y":1660,"wires":[]},{"id":"4eef0593.5d0e24","type":"debug","z":"7781c60a.76f4a","name":"","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":740,"y":1720,"wires":[]},{"id":"e9e897f3.f23db8","type":"moment","z":"7781c60a.76f4a","name":"-> Berlin, LLL, de_DE","topic":"","input":"","inputType":"msg","inTz":"Europe/Berlin","adjAmount":"1","adjType":"months","adjDir":"add","format":"LLL","locale":"de_DE","output":"","outputType":"msg","outTz":"Europe/Berlin","x":430,"y":1720,"wires":[["4eef0593.5d0e24"]]},{"id":"d0d9146d.59da38","type":"debug","z":"7781c60a.76f4a","name":"","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":740,"y":1780,"wires":[]},{"id":"e3288390.06fad","type":"moment","z":"7781c60a.76f4a","name":"-> Berlin, LLL, en_EN","topic":"","input":"","inputType":"msg","inTz":"Europe/Berlin","adjAmount":"1","adjType":"months","adjDir":"add","format":"LLL","locale":"en_EN","output":"","outputType":"msg","outTz":"Europe/Berlin","x":430,"y":1780,"wires":[["d0d9146d.59da38"]]},{"id":"cab8a0fe.d50278","type":"moment","z":"7781c60a.76f4a","name":"-> Berlin","topic":"","input":"","inputType":"msg","inTz":"Europe/Berlin","adjAmount":0,"adjType":"days","adjDir":"add","format":"[Date: ]YYYY-MM-DD [Time: ]HH:mm:ss","locale":"de_DE","output":"","outputType":"msg","outTz":"Europe/Berlin","x":380,"y":1840,"wires":[["757e3575.b97ea4"]]},{"id":"757e3575.b97ea4","type":"debug","z":"7781c60a.76f4a","name":"","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":740,"y":1840,"wires":[]},{"id":"e48a268c.7470a8","type":"debug","z":"7781c60a.76f4a","name":"","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":740,"y":1900,"wires":[]},{"id":"a283a279.87395","type":"moment","z":"7781c60a.76f4a","name":"-> Berlin, LLL, it_IT","topic":"","input":"","inputType":"msg","inTz":"Europe/Berlin","adjAmount":"1","adjType":"months","adjDir":"add","format":"LLL","locale":"it_IT","output":"","outputType":"msg","outTz":"Europe/Berlin","x":420,"y":1900,"wires":[["e48a268c.7470a8"]]},{"id":"e4176e86.fd4f28","type":"debug","z":"7781c60a.76f4a","name":"","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":740,"y":1960,"wires":[]},{"id":"82edd4e3.7517a8","type":"moment","z":"7781c60a.76f4a","name":"-> Berlin, calendar","topic":"","input":"","inputType":"msg","inTz":"Europe/Berlin","adjAmount":"1","adjType":"months","adjDir":"add","format":"calendar","locale":"en_EN","output":"","outputType":"msg","outTz":"Europe/Berlin","x":420,"y":1960,"wires":[["e4176e86.fd4f28"]]},{"id":"856447c4.9b118","type":"moment","z":"7781c60a.76f4a","name":"-> Berlin, fromNow","topic":"","input":"","inputType":"msg","inTz":"Europe/Berlin","adjAmount":"1","adjType":"months","adjDir":"add","format":"fromNow","locale":"en_EN","output":"","outputType":"msg","outTz":"Europe/Berlin","x":420,"y":2020,"wires":[["dbfff1bf.047e48"]]},{"id":"dbfff1bf.047e48","type":"debug","z":"7781c60a.76f4a","name":"","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":740,"y":2020,"wires":[]}]
@@ -0,0 +1 @@
[{"id":"ad0926de.26a49","type":"moment","z":"7781c60a.76f4a","name":"-> Berlin","topic":"","input":"","inputType":"msg","inTz":"Europe/Berlin","adjAmount":0,"adjType":"days","adjDir":"add","format":"YYYY-MM-DD HH:mm:ss.SSS","locale":"","output":"","outputType":"msg","outTz":"Europe/Berlin","x":380,"y":620,"wires":[["ee7a9310.023e58"]]},{"id":"c941333.51fc15","type":"inject","z":"7781c60a.76f4a","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":"","topic":"","payload":"","payloadType":"date","x":170,"y":500,"wires":[["ad0926de.26a49","f72d19a0.4615e8","e7332173.f40ac8","11105327.b13ee5","f646fc04.b042f8","bce45467.abbc2","729d46e9.f2e86","5ee3991e.5952b"]]},{"id":"ee7a9310.023e58","type":"debug","z":"7781c60a.76f4a","name":"","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":680,"y":620,"wires":[]},{"id":"f72d19a0.4615e8","type":"debug","z":"7781c60a.76f4a","name":"","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":400,"y":500,"wires":[]},{"id":"e7332173.f40ac8","type":"moment","z":"7781c60a.76f4a","name":"-> UTC (Zulu time)","topic":"","input":"","inputType":"msg","inTz":"Europe/Berlin","adjAmount":0,"adjType":"days","adjDir":"add","format":"","locale":"C","output":"","outputType":"msg","outTz":"Europe/Berlin","x":420,"y":560,"wires":[["b95b0b21.615e2"]]},{"id":"b95b0b21.615e2","type":"debug","z":"7781c60a.76f4a","name":"","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":680,"y":560,"wires":[]},{"id":"11105327.b13ee5","type":"moment","z":"7781c60a.76f4a","name":"-> New York","topic":"","input":"","inputType":"msg","inTz":"Europe/Berlin","adjAmount":0,"adjType":"days","adjDir":"add","format":"YYYY-MM-DD HH:mm","locale":"","output":"","outputType":"msg","outTz":"America/New_York","x":400,"y":800,"wires":[["fccc4c17.8cc668"]]},{"id":"fccc4c17.8cc668","type":"debug","z":"7781c60a.76f4a","name":"","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"converted","statusType":"auto","x":680,"y":800,"wires":[]},{"id":"f646fc04.b042f8","type":"moment","z":"7781c60a.76f4a","name":"-> adjustment + 1 yr","topic":"","input":"","inputType":"msg","inTz":"Europe/Berlin","adjAmount":"1","adjType":"years","adjDir":"add","format":"DD.MM.YYYY HH:mm","locale":"","output":"","outputType":"msg","outTz":"Europe/Berlin","x":430,"y":860,"wires":[["e040c9ca.d87e18"]]},{"id":"e040c9ca.d87e18","type":"debug","z":"7781c60a.76f4a","name":"","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"converted","statusType":"auto","x":680,"y":860,"wires":[]},{"id":"bce45467.abbc2","type":"moment","z":"7781c60a.76f4a","name":"-> adjustment - 1week","topic":"","input":"","inputType":"msg","inTz":"Europe/Berlin","adjAmount":"1","adjType":"weeks","adjDir":"subtract","format":"DD.MM.YYYY HH:mm","locale":"C","output":"","outputType":"msg","outTz":"Europe/Berlin","x":430,"y":920,"wires":[["b5e35173.54ef1"]]},{"id":"b5e35173.54ef1","type":"debug","z":"7781c60a.76f4a","name":"","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"converted","statusType":"auto","x":680,"y":920,"wires":[]},{"id":"5a45bddc.198614","type":"debug","z":"7781c60a.76f4a","name":"","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":680,"y":680,"wires":[]},{"id":"729d46e9.f2e86","type":"moment","z":"7781c60a.76f4a","name":"-> Berlin","topic":"","input":"","inputType":"msg","inTz":"Europe/Berlin","adjAmount":0,"adjType":"days","adjDir":"add","format":"DD.MM.YYYY HH:mm","locale":"","output":"","outputType":"msg","outTz":"Europe/Berlin","x":380,"y":680,"wires":[["5a45bddc.198614"]]},{"id":"453f43aa.10f854","type":"debug","z":"7781c60a.76f4a","name":"","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":680,"y":740,"wires":[]},{"id":"5ee3991e.5952b","type":"moment","z":"7781c60a.76f4a","name":"-> Berlin","topic":"","input":"","inputType":"msg","inTz":"Europe/Berlin","adjAmount":0,"adjType":"days","adjDir":"add","format":"LLLL","locale":"","output":"","outputType":"msg","outTz":"Europe/Berlin","x":380,"y":740,"wires":[["453f43aa.10f854"]]}]
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

@@ -0,0 +1,80 @@
<!--
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.
-->
<!-- This creates and configures the onscreen elements of the node -->
<!-- First, the content of the edit dialog is defined. -->
<script type="text/x-red" data-template-name="humanizer">
<!-- data-template-name identifies the node type this is for -->
<!-- INPUT -->
<div class="form-row">
<label for="node-input-input"><i class="fa fa-arrow-left"></i> Input variable</label>
<input type="text" id="node-input-input" style="width: 70%;" placeholder="varname in msg.payload or empty for all">
</div>
<!-- NODE NAME - Should always be the last field -->
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips" style="font-size:83%">
Specify which property on msg.payload to humanize or use msg.payload. <br />
The input must be in seconds since the event so +10 is 10 seconds ago.<br />
Only integer numbers are allowed.
</div>
</script>
<!-- Next, some simple help text is provided for the node. -->
<!-- The first <p> is used as the pop-up tool tip when hovering over pallette -->
<script type="text/x-red" data-help-name="humanizer">
<p>
Humanize timespans in seconds.
</p>
<p>
Uses the <a href="https://momentjs.com/docs/#/durations/humanize/">humanize functionality in the moment.js library</a>.
</p>
<p>
Input must be in integer seconds. Output is the humanized timespan as a localized text message (e.g. "2 seconds").
</p>
<p>
Contributed by <a href="https://github.com/Laro88">Laro88</a>
</p>
</script>
<!-- Finally, the node type is registered along with all of its properties -->
<script type="text/javascript">
RED.nodes.registerType('humanizer',{
category: 'formats', // the palette category
color: '#E6E0F8',
defaults: {
name: {value:''},
input: {value:''}
},
inputs:1, // set the number of inputs - only 0 or 1
outputs:1, // set the number of outputs - 0 to n
// set the icon (held in icons dir below where you save the node)
icon: 'timer.png', // saved in icons/myicon.png
label: function() { // sets the default label contents
return this.name||'Humanizer';
},
labelStyle: function() { // sets the class to apply to the label
return this.name?'node_label_italic':'';
}
}); // ---- end of RED.nodes.registerType ---- //
</script>
@@ -0,0 +1,75 @@
/**
* 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.
**/
// JK Note: This code contributed by (Contributed by Laro88, https://github.com/Laro88)
// Tidying done by JK. Also removed dependency on validator which is vastly OTT for just ensuring an integer
// 2017-04-13
// Node for Node-Red that humalizes a timespan msg.payload.[] //TODO whould this be extended to work on Date objects as well and if so how?
// It is helpful when working with ui things that informs users of time since last operation etc.
// require moment.js (must be installed from package.js as a dependency)
var moment = require('moment-timezone');
// Module name must match this nodes html file
var moduleName = 'humanizer';
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
// 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
//console.log(this.input);
var v = msg.payload.hasOwnProperty(this.input) ? msg.payload[this.input] : msg.payload;
// JK: Replace validator with native JS INT check, improve warning message
//v = v;
//if( !validator.isInt(v) ) {
if( ! Number.isInteger(v) ) {
return node.warn('Invalid input for humanize call, input must be an integer');
}
// JK: Pass seconds to duration - remove *1000 ms conversion
var _humanized = moment.duration(v, 'seconds').humanize();
if(typeof(msg.payload) == 'object'){
msg.payload.humanized = _humanized;
}
else{
msg.payload = {'humanized':_humanized};
}
node.send(msg);
});
} // ---- 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);
};
@@ -0,0 +1,312 @@
<!--
Copyright (c) 2020 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.
-->
<script type="text/html" data-template-name="moment">
<!-- data-template-name identifies the node type this is for -->
<!-- INPUT -->
<div class="form-row">
<label for="node-input-input"><i class="fa fa-arrow-left"></i> Input from</label>
<input type="text" id="node-input-input" style="width: 70%;" placeholder="payload">
<input type="hidden" id="node-input-inputType">
</div>
<!-- INPUT TIMEZONE -->
<div class="form-row">
<label for="node-input-inTz">Input Timezone</label>
<input type="text" id="node-input-inTz" placeholder="[determined by system]" style="width:70%;">
</div>
<!-- ADJUSTMENT -->
<div class="form-row">
<label for="node-input-adjAmount"> Adjustment</label>
<select id="node-input-adjDir" style="width:4em">
<option value="add">+</option>
<option value="subtract">-</option>
</select>
<input type="text" id="node-input-adjAmount" placeholder="0" style="width:25%;">
<select id="node-input-adjType" style="width:30%">
<option value="days">Days</option>
<option value="hours">Hours</option>
<option value="weeks">Weeks</option>
<option value="months">Months</option>
<option value="quarters">Quarters</option>
<option value="years">Years</option>
<option value="minutes">Minutes</option>
<option value="seconds">Seconds</option>
<option value="milliseconds">Milliseconds</option>
</select>
</div>
<!-- OUTPUT FORMAT -->
<br />
<div class="form-row">
<label for="node-input-format"><i class="fa fa-eye-open"></i> Output Format</label>
<input type="text" id="node-input-format" placeholder="ISO8601 (UTC)">
</div>
<!-- OUTPUT LOCALE -->
<div class="form-row">
<label for="node-input-locale"><i class="fa fa-flag"></i> Locale</label>
<input type="text" id="node-input-locale" placeholder="en">
</div>
<!-- OUTPUT TIMEZONE -->
<div class="form-row">
<label for="node-input-outTz">Output Timezone</label>
<input type="text" id="node-input-outTz" placeholder="[determined by system]" style="width:70%;">
</div>
<!-- OUTPUT OBJECT -->
<div class="form-row">
<label for="node-input-output"><i class="fa fa-arrow-right"></i> Output to</label>
<input type="text" id="node-input-output" style="width: 70%;" placeholder="payload">
<input type="hidden" id="node-input-outputType">
</div>
<br/>
<!-- TOPIC -->
<div class="form-row">
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
<input type="text" id="node-input-topic" placeholder="Topic">
</div>
<!-- NODE NAME - Should always be the last field -->
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips" style="font-size:83%">
See the info sidebar for formatting details. <br />
Use locale and format to change string output.<br />
See the info sidebar for several warnings about inputting strings.
</div>
</script>
<!-- The first <p> is used as the pop-up tool tip when hovering over palette -->
<script type="text/html" data-help-name="moment">
<p>
Convert date/time object or string to nicely formatted text or date/time object.
</p>
<ul>
<li><a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones" target="_blank">Valid Timezone reference</a></li>
<li><a href="https://momentjs.com/docs/#/displaying/format/" target="_blank">Date/Time formatting codes</a></li>
</ul>
<p>
Uses the moment.js library. Now updated to use moment-timezone.js for full timezone and DST handling.
Also uses moment.parseFormat for string inputs and os-locale for output localisation.
This version requires Node v6+.
</p>
<p>
See the <a href="https://github.com/TotallyInformation/node-red-contrib-moment">readme file</a> for a change log.
</p>
<h3>Input</h3>
<p>
If the input property does not exist or is left blank, the current date/time will be assumed.
This can be used to add a current timestamp to a flow of any kind easily.
</p>
<p>Otherwise, the input is processed as follows:</p>
<ul>
<li>If input is "timestamp" or a JS datetime object or a <a href="http://momentjs.com/docs/#/parsing/">string that Moment.js</a> can resolve
(with the help of <a href="https://github.com/gr2m/moment-parseformat">moment.parseFormat</a>). It will be processed as normal.</li>
<li>If the input is null, is a blank string or a string that cannot be converted. The output will be an empty string.</li>
</ul>
<h4>String input warnings</h4>
<p>
Note that parsing date/time strings is a hard problem. moment.parseFormat helps but it isn&apos;t magic.
We assume that ambiguous input dates such as <code>3/5/05</code> are in EU/UK format <code>dd/mm/yy</code> <i>unless</i>
either the input timezone is in <i>America</i> or the locale is set to <i>en_US</i>.
</p>
<h3>Input Timezone</h3>
<p>
Will try to use <code>msg.inTz</code>, if that is not present, the input TZ setting will be used and if that isn't present, the node will try to guess. If you have set your host system to a local timezone
(e.g. <code>dpkg-reconfigure tzdata</code> on Linux)
then by default, input timestamps from the <code>inject</code> node will be in that timezone, otherwise they will be in UTC.
You can override this by specifying the full timezone in the format <code>region/location</code>,
e.g. <code>Europe/London</code>. Note that spellings are not validated, if it doesn&apos;t seem to work, check the valid timezone
list built in to moment-timezone.
</p>
<h3>Output</h3>
<p>
Is a formatted string if the output format is anything other than <code>date</code> or <code>object</code>
in which case, the output is a Javascript date object or an object as described below respectively.
</p>
<p>
Output string formatting is controlled by the Locale setting and the output format field.
Note that the output Timezone is ignored for ISO8601 output (the default), such output is <i>always</i>
in UTC. For other formats, the output will be in the specified timezone which defaults to your host timezone.
</p>
<p>
Specifying different input and output timezones allows you to translated between them. If left blank in settings, the output timezone may be set using <code>msg.outTz</code>.
</p>
<p>
The output msg will pass through the input msg.topic unless it is overridden. If the "Output to" field is changed
from the default <code>msg.payload</code>, the input msg.payload will also be passed through.
</p>
<h3>Output String Formatting</h3>
<p>
May be any <a href="http://momentjs.com/docs/#/displaying/format/">format string
allowed by Moment.JS</a> (try <code>LLL</code> for a localised output for example).
The formatting additions from moment-timezone are also allowed.<br>
In addition, the following (not case sensitive, alternatives in brackets) format strings are also allowed:
</p>
<dl>
<dt>Blank (ISO8601, ISO)</dt>
<dd>
ISO 8602 format, e.g. "2015-01-28T16:24:48.123Z"<br>
Note that ISO8601 formatted output is ALWAYS in UTC ('Z', Zulu time) not
local, no matter what output timezone you may specify.
</dd>
<dt>date (jsDate)</dt>
<dd>
A Javascript Date object<br>
This is the default if the input is a recognised date string
</dd>
<dt>object</dt>
<dd>
A Javascript object in the form <code>{years:nnnn, months:n, date:n, hours:n, minutes:n, seconds:n, milliseconds:n}</code>
<br>
<em>WARNING</em>: moment.js has a bizarre object format where the month is zero-based (0-11) instead of 1-based (1-12) like all the other elements are.
I don't currently know why, I've raised an upstream issue but this appears to be a deliberate decision for some strange reason.
</dd>
<dt>fromNow (timeAgo)</dt>
<dd>
Human readable output<br />
e.g. 30 minutes ago
</dd>
<dt>calendar (aroundNow)</dt>
<dd>
Human readable alternative<br />
e.g. "Last Monday", "Tomorrow 2:30pm"<br />
Note that dates beyond a week from now are output as yyyy-mm-dd
</dd>
</dl>
<p>
Note that with the exception of ISO8601, other formats are in the specified
timezone & DST. If not specified, the output timezone/DST is the same as the input.<br />
Use an output timezone of <code>UTC</code> to force output to that.
</p>
<p>
If output is shown in the wrong format, such as dates in US mm/dd/yy format, change the
output locale. For example, using <code>en_gb</code> will force short dates to output in
dd/mm/yy format. The default is <code>en</code> which moment assumes means the USA :-(
</p>
</script>
<script type="text/javascript">
;(function () { // Isolate the front-end code
'use strict'
//#region --------- "global" variables for the panel --------- //
/** Module name must match this nodes html file @constant {string} moduleName */
var moduleName = 'moment'
/** Node's label @constant {string} paletteCategory */
var nodeLabel = 'Date/Time Formatter'
/** Node's palette category @constant {string} paletteCategory */
var paletteCategory = 'formats'
/** Node's background color @constant {string} paletteColor */
var paletteColor = '#E6E0F8'
//#endregion ------------------------------------------------- //
//#region --------- "global" functions for the panel --------- //
function tzValidate(val) {
// TODO: See /contribapi/momentzones in nrmoment.js
// auto-correct the entry
//$('#node-input-inTz').val( val.replace(/^GMT|UTC/i, 'ETC/GMT') )
return true
}
//#endregion ------------------------------------------------- //
RED.nodes.registerType(moduleName, {
category: paletteCategory, // the palette category
color: paletteColor,
defaults: {
name: {value:''},
topic: {value:''},
input: {value:''},
inputType: {value:'msg'},
inTz: {value:'', validate:tzValidate},
adjAmount: {value:0, validate:RED.validators.number()},
adjType: {value:'days'},
adjDir: {value:'add'},
format: {value:''},
locale: {value:''},
output: {value:''},
outputType: {value:'msg'},
outTz: {value:'', validate:tzValidate}
},
inputs:1, // set the number of inputs - only 0 or 1
outputs:1, // set the number of outputs - 0 to n
// set the icon (held in icons dir below where you save the node)
icon: 'timer.png', // saved in icons/myicon.png
paletteLabel: nodeLabel,
label: function() { // sets the default label contents
return this.name||this.topic||nodeLabel;
},
labelStyle: function() { // sets the class to apply to the label
return this.name?'node_label_italic':'';
},
oneditprepare: function() {
// Make sure we can reference parent this in async fns
var _this = this
if (!this.inputType) {
this.inputType = 'msg';
}
$('#node-input-input').typedInput({
default: 'msg',
types: ['msg','flow','global','date','str'],
typeField: $('#node-input-inputType')
});
if (!this.outputType) {
this.outputType = 'msg';
}
$('#node-input-output').typedInput({
default: 'msg',
types: ['msg','flow','global'],
typeField: $('#node-input-outputType')
});
// Get host locale and timezone
$.getJSON('contribapi/moment',function(data) {
// Save to parent object
_this.localeData = data
if ( $('#node-input-inTz').val() === '' ) $('#node-input-inTz').val(data.tz);
if ( $('#node-input-outTz').val() === '' ) $('#node-input-outTz').val(data.tz);
if ( $('#node-input-locale').val() === '' ) $('#node-input-locale').val(data.locale);
});
// Autocorrect timezone entries
$('#node-input-inTz').add('#node-input-outTz').blur( function(){
if( this.value === '' ) {
// Use saved locale data from parent object as default
this.value = _this.localeData.tz
} else {
// autocorrect common tz errors
this.value = this.value.trim().replace(/^(GMT|UTC)/i, 'ETC/$1')
}
})
}, // --- end of oneditprepare --- //
}) // ---- end of RED.nodes.registerType ---- //
})()
</script>
@@ -0,0 +1,382 @@
/**
* Copyright (c) 2020 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.
'use strict'
// require moment.js (must be installed from package.js as a dependency)
const moment = require('moment-timezone')
const parseFormat = require('moment-parseformat')
// const osLocale = require('os-locale')
const { osLocale } = require('os-locale-s')
const hostTz = moment.tz.guess()
const hostLocale = osLocale.sync()
// Module name must match this nodes html file
const moduleName = 'moment'
/** Catch & correct input strings in ISO format with seconds >3dp
* since parseFormat has an unfixed bug (Issue #24, https://github.com/gr2m/moment-parseformat/issues/96)
* @param {string} inp Input date/time string
*/
function catchDp(inp) {
// If ISO string w/ seconds >3dp, then process else exit
if ( inp.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{4,}/) ) {
inp = inp.replace(/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3})\d*(.*)/, '$1$2')
}
return inp
}
/** Export the function that defines the node */
module.exports = function (RED) {
'use strict'
// The main node definition - most things happen in here
function nodeDefinition(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 || hostLocale // valid moment.js locale string - default to host locale (v4)
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....
/** @since v3.1.0, amended ready for Node-RED v1 */
node.on('input', function (msg, send, done) {
'use strict' // We will be using eval() so lets get a bit of safety using strict
// If this is pre-1.0, 'send' will be undefined, so fallback to node.send
send = send || node.send
// If this is pre-1.0, 'done' will be undefined, so fallback to dummy function
done = done || function(){}
// 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
* @type {(string|Date|null)}
**/
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.
/** @type {(string|Object)} */
var dtHack = ''
/** @type {(string|Date)} */
var 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:
// Catch string ISO inputs with seconds >3dp which parseFormat treats incorrectly (Issue #24) - trim to 3dp only
inp = catchDp(inp)
// Try to guess locale so as to guess whether DMY or MDY
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
// Check if input TZ is provided in the msg?
if (msg.inTz) {
node.inTz = msg.inTz
} else {
// If the input TZ is not set ...
if (!node.inTz) {
// take it from the host TZ
node.inTz = hostTz
}
}
// Check if output TZ is provided in the msg?
if (msg.outTz) {
node.outTz = msg.outTz
} else {
// If the output TZ is not set ...
if (!node.outTz) {
// make it the same as the input TZ
node.outTz = node.inTz
}
}
// Validate input and output timezones - @since 2.0.4
// TODO: Needs more validation
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
// 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 is 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))
}
}
// Include settings in output
msg.settings = {
'input': inp,
'input_format': inpFmt,
'input_tz': node.inTz,
'output_format': node.format,
'output_locale': node.locale,
'output_tz': node.outTz,
}
// Send the output message
send(msg)
// Finished processing input msg (NR 1+)
done()
})
// 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, nodeDefinition)
// 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
})
})
/** Lookup API for country and zone names
* TODO: Not yet completed and no front-end settings yet
*/
RED.httpAdmin.get('/contribapi/momentzones', RED.auth.needsPermission('moment.read'), function (req, res) {
if ( req.query.country ) {
console.log('[nrmoment:get:zones] Country URL param provided', req.query.country)
}
res.json(moment.tz.names())
})
}
+83
View File
@@ -0,0 +1,83 @@
{
"name": "node-red-contrib-moment",
"version": "5.0.0",
"description": "Node-Red Node that produces formatted Date/Time output using the Moment.JS library. Timezone, dst and locale aware.",
"scripts": {
"push": "git push origin",
"pull": "git pull origin",
"presync": "npm run pull",
"sync": "npm run push",
"listbin": "ls ./node_modules/bin",
"npmtags": "npm dist-tag ls node-red-contrib-uibuilder",
"npmtagnext": "npm dist-tag add node-red-contrib-uibuilder@$npm_package_version next",
"gittags": "git tag",
"gittag": "git tag -a v%npm_package_version%",
"gitpushtags": "git push origin --tags"
},
"dependencies": {
"moment": "^2.29.0",
"moment-parseformat": "^4.0.0",
"moment-timezone": "^0.5.31",
"os-locale-s": "^1.0.8"
},
"devDependencies": {
"@types/node-red": "^1.1.0"
},
"author": {
"name": "Julian Knight",
"url": "https://github.com/totallyinformation"
},
"contributors": [
{
"name": "Jacques W (Jacques44)",
"url": "https://github.com/Jacques44"
},
{
"name": "Jes Ramsing (Laro88)",
"url": "https://github.com/Laro88"
},
{
"name": "StephanStS",
"url": "https://github.com/StephanStS"
}
],
"keywords": [
"node-red",
"moment",
"momentjs",
"time",
"date",
"timezone",
"locale",
"DST"
],
"node-red": {
"version": ">=3",
"nodes": {
"moment": "moment/nrmoment.js",
"humanizer": "moment/nrhumanizer.js"
},
"notyetnodes": {}
},
"engines": {
"node": ">=14.14"
},
"browserslist": [
"> 0.5%",
"maintained node versions",
"last 2 versions",
"not dead",
"not ie < 11"
],
"main": "moment/nrmoment.js",
"repository": {
"type": "git",
"url": "https://github.com/totallyinformation/node-red-contrib-moment"
},
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/totallyinformation/node-red-contrib-moment/issues"
},
"homepage": "https://github.com/totallyinformation/node-red-contrib-moment",
"directories": {}
}
@@ -0,0 +1,164 @@
[
{
"id": "9fe47dca.ad366",
"type": "moment",
"z": "533475c4.a28eac",
"name": "UTC-4",
"topic": "UTC-4",
"input": "payload",
"inputType": "msg",
"inTz": "ETC/GMT-4",
"adjAmount": 0,
"adjType": "days",
"adjDir": "add",
"format": "ddd Do MMM YYYY HH:mm",
"locale": "en_GB",
"output": "",
"outputType": "msg",
"outTz": "ETC/GMT-4",
"x": 450,
"y": 640,
"wires": [
[
"3b9b0dc5.bae662"
]
]
},
{
"id": "3c0267e8.e1c0b8",
"type": "inject",
"z": "533475c4.a28eac",
"name": "2017-05-28T21:00:00-04:00",
"topic": "",
"payload": "2017-05-28T21:00:00-04:00",
"payloadType": "str",
"repeat": "",
"crontab": "",
"once": false,
"x": 200,
"y": 700,
"wires": [
[
"9fe47dca.ad366",
"71ef9d10.3d4904",
"72638b85.4698d4"
]
]
},
{
"id": "3b9b0dc5.bae662",
"type": "debug",
"z": "533475c4.a28eac",
"name": "",
"active": true,
"console": "false",
"complete": "false",
"x": 890,
"y": 640,
"wires": []
},
{
"id": "71ef9d10.3d4904",
"type": "moment",
"z": "533475c4.a28eac",
"name": "UTC",
"topic": "UTC",
"input": "payload",
"inputType": "msg",
"inTz": "etc/GMT-4",
"adjAmount": 0,
"adjType": "days",
"adjDir": "add",
"format": "ddd Do MMM YYYY HH:mm",
"locale": "en_GB",
"output": "",
"outputType": "msg",
"outTz": "UTC",
"x": 450,
"y": 680,
"wires": [
[
"3b9b0dc5.bae662"
]
]
},
{
"id": "72638b85.4698d4",
"type": "moment",
"z": "533475c4.a28eac",
"name": "UTC+10",
"topic": "UTC+10",
"input": "payload",
"inputType": "msg",
"inTz": "etc/GMT-4",
"adjAmount": 0,
"adjType": "days",
"adjDir": "add",
"format": "ddd Do MMM YYYY HH:mm",
"locale": "en_GB",
"output": "",
"outputType": "msg",
"outTz": "etc/GMT+10",
"x": 460,
"y": 720,
"wires": [
[
"3b9b0dc5.bae662"
]
]
},
{
"id": "e862abcc.69b328",
"type": "inject",
"z": "533475c4.a28eac",
"name": "2017-05-28T21:00:00",
"topic": "",
"payload": "2017-05-28T21:00:00",
"payloadType": "str",
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "",
"x": 180,
"y": 760,
"wires": [
[
"72638b85.4698d4",
"71ef9d10.3d4904",
"9fe47dca.ad366"
]
]
},
{
"id": "ace5ef2a.af5c2",
"type": "inject",
"z": "533475c4.a28eac",
"name": "2017-05-28T21:00:00Z",
"topic": "",
"payload": "2017-05-28T21:00:00Z",
"payloadType": "str",
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "",
"x": 180,
"y": 640,
"wires": [
[
"9fe47dca.ad366",
"71ef9d10.3d4904",
"72638b85.4698d4"
]
]
},
{
"id": "acbc2d1d.86082",
"type": "comment",
"z": "533475c4.a28eac",
"name": "Expected Results",
"info": "| Input | Moment | Output |\n|---------------------------|--------|-------------------------|\n| 2017-05-28T21:00:00Z | UTC-4 | Mon 29th May 2017 01:00 |\n| | UTC | Sun 28th May 2017 21:00 |\n| | UTC+10 | Sun 28th May 2017 11:00 |\n| 2017-05-28T21:00:00-04:00 | UTC-4 | Mon 29th May 2017 05:00 |\n| | UTC | Mon 29th May 2017 01:00 |\n| | UTC+10 | Sun 28th May 2017 15:00 |\n| 2017-05-28T21:00:00 | UTC-4 | Sun 28th May 2017 07:00 |\n| | UTC | Sun 28th May 2017 17:00 |\n| | UTC+10 | Sun 28th May 2017 21:00 |",
"x": 700,
"y": 760,
"wires": []
}
]
@@ -0,0 +1,161 @@
[
{
"id": "320464b5.d0134c",
"type": "moment",
"z": "533475c4.a28eac",
"name": "input: msg.payload",
"topic": "",
"input": "",
"inputType": "msg",
"inTz": "America/New_York",
"adjAmount": 0,
"adjType": "days",
"adjDir": "add",
"format": "YYYY-MM-DD HH:mm",
"locale": "en_US",
"output": "now",
"outputType": "msg",
"outTz": "America/New_York",
"x": 470,
"y": 400,
"wires": [
[
"72ed58b3.7f9178"
]
]
},
{
"id": "aceef80.40a4908",
"type": "inject",
"z": "533475c4.a28eac",
"name": "",
"topic": "",
"payload": "",
"payloadType": "date",
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "",
"x": 240,
"y": 340,
"wires": [
[
"320464b5.d0134c"
]
]
},
{
"id": "72ed58b3.7f9178",
"type": "debug",
"z": "533475c4.a28eac",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"x": 650,
"y": 400,
"wires": []
},
{
"id": "bbe21120.87815",
"type": "inject",
"z": "533475c4.a28eac",
"name": "blank string",
"topic": "",
"payload": "",
"payloadType": "str",
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "",
"x": 230,
"y": 380,
"wires": [
[
"320464b5.d0134c"
]
]
},
{
"id": "e49c12a.d2215f",
"type": "inject",
"z": "533475c4.a28eac",
"name": "'test'",
"topic": "",
"payload": "test",
"payloadType": "str",
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "",
"x": 250,
"y": 420,
"wires": [
[
"320464b5.d0134c"
]
]
},
{
"id": "58881f8c.6cc45",
"type": "inject",
"z": "533475c4.a28eac",
"name": "null",
"topic": "",
"payload": "null",
"payloadType": "json",
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "",
"x": 250,
"y": 460,
"wires": [
[
"320464b5.d0134c"
]
]
},
{
"id": "43a381eb.dae01",
"type": "comment",
"z": "533475c4.a28eac",
"name": "Pass different data types to a valid input property",
"info": "",
"x": 560,
"y": 360,
"wires": []
},
{
"id": "e2fe39e8.d9aba8",
"type": "inject",
"z": "533475c4.a28eac",
"name": "Invalid Date String",
"topic": "Invalid Date String",
"payload": "36th September 3046",
"payloadType": "str",
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "",
"x": 210,
"y": 500,
"wires": [
[
"320464b5.d0134c"
]
]
},
{
"id": "4b9bf042.f40d3",
"type": "comment",
"z": "533475c4.a28eac",
"name": "Expected Results",
"info": "Requests input from `msg.payload` but puts\noutput to `msg.now`.\n\n\n## Timestamp input\n\n```\n{\n \"_msgid\":\"2792614c.a404ee\",\"topic\":\"\",\n \"payload\":1537718900126,\n \"now\":\"2018-09-23 12:08\"\n}\n```\n\n## All other inputs\n\n1. An output warning message\n `The input property was NOT a recognisable date. Output will be a blank string`\n\n2. `msg.payload` set to input msg.payload\n3. `msg.now` set to empty string.\n",
"x": 460,
"y": 460,
"wires": []
}
]
@@ -0,0 +1,141 @@
[
{
"id": "6e87ca1f.077804",
"type": "moment",
"z": "533475c4.a28eac",
"name": "input: msg.foo",
"topic": "",
"input": "foo",
"inputType": "msg",
"inTz": "America/New_York",
"adjAmount": 0,
"adjType": "days",
"adjDir": "add",
"format": "YYYY-MM-DD HH:mm",
"locale": "en_US",
"output": "now",
"outputType": "msg",
"outTz": "America/New_York",
"x": 460,
"y": 660,
"wires": [
[
"61f6f582.f8708c"
]
]
},
{
"id": "12d13caf.fd1683",
"type": "inject",
"z": "533475c4.a28eac",
"name": "",
"topic": "",
"payload": "",
"payloadType": "date",
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "",
"x": 240,
"y": 600,
"wires": [
[
"6e87ca1f.077804"
]
]
},
{
"id": "61f6f582.f8708c",
"type": "debug",
"z": "533475c4.a28eac",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"x": 650,
"y": 660,
"wires": []
},
{
"id": "63c7416c.aa3ce",
"type": "inject",
"z": "533475c4.a28eac",
"name": "blank string",
"topic": "",
"payload": "",
"payloadType": "str",
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "",
"x": 230,
"y": 640,
"wires": [
[
"6e87ca1f.077804"
]
]
},
{
"id": "c371a083.37e4f",
"type": "inject",
"z": "533475c4.a28eac",
"name": "'test'",
"topic": "",
"payload": "test",
"payloadType": "str",
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "",
"x": 250,
"y": 680,
"wires": [
[
"6e87ca1f.077804"
]
]
},
{
"id": "c0c9733a.5abb3",
"type": "inject",
"z": "533475c4.a28eac",
"name": "null",
"topic": "",
"payload": "null",
"payloadType": "json",
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "",
"x": 250,
"y": 720,
"wires": [
[
"6e87ca1f.077804"
]
]
},
{
"id": "bed64b79.64ac88",
"type": "comment",
"z": "533475c4.a28eac",
"name": "Reference a non-existent input property",
"info": "",
"x": 530,
"y": 620,
"wires": []
},
{
"id": "805b442c.c26d48",
"type": "comment",
"z": "533475c4.a28eac",
"name": "Expected Results",
"info": "As the input msg property does not exist,\n**all** outputs will be the same:\n\n```\n{\n \"_msgid\":\"fbb0e16f.af613\",\"topic\":\"\",\n \"payload\":1537719147115,\n \"now\":\"2018-09-23 12:12\"\n}\n```\n\n1. `msg.payload` set to input msg.payload\n3. `msg.now` set to current date/time string.\n",
"x": 460,
"y": 720,
"wires": []
}
]
@@ -0,0 +1,91 @@
[
{
"id": "df76bbb4.8529e8",
"type": "inject",
"z": "533475c4.a28eac",
"name": "UNIX timestamp",
"topic": "UNIX timestamp",
"payload": "1512545487",
"payloadType": "num",
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"x": 180,
"y": 380,
"wires": [
[
"c5a8c.dddf4574"
]
]
},
{
"id": "c5a8c.dddf4574",
"type": "moment",
"z": "533475c4.a28eac",
"name": "",
"topic": "",
"input": "",
"inputType": "msg",
"inTz": "",
"adjAmount": 0,
"adjType": "days",
"adjDir": "add",
"format": "",
"locale": "",
"output": "",
"outputType": "msg",
"outTz": "",
"x": 420,
"y": 380,
"wires": [
[
"e5a3e641.545528"
]
]
},
{
"id": "e5a3e641.545528",
"type": "debug",
"z": "533475c4.a28eac",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"x": 610,
"y": 380,
"wires": []
},
{
"id": "48c26998.0d90a8",
"type": "inject",
"z": "533475c4.a28eac",
"name": "JS Timestamp",
"topic": "JS Timestamp",
"payload": "1512545487000",
"payloadType": "num",
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"x": 170,
"y": 420,
"wires": [
[
"c5a8c.dddf4574"
]
]
},
{
"id": "d12e7985.07ef88",
"type": "comment",
"z": "533475c4.a28eac",
"name": "Expected Output",
"info": "UNIX Timestamps are the number of **SECONDS** \nsince 1970-01-01 00:00:00.\n\nJavaScript Timestamps are the number of **MILLIseconds**\nsince 1970-01-01 00:00:00.\n\nMoment.JS only recognises JavaScript timestamps\nso if you want to process a UNIX timestamp, \nplease multiply by 1000 as in the second\ninput.",
"x": 400,
"y": 420,
"wires": []
}
]
@@ -0,0 +1,218 @@
[
{
"id": "e75759f3.14aed8",
"type": "moment",
"z": "533475c4.a28eac",
"name": "",
"topic": "OBJECT OUTPUT",
"input": "",
"inputType": "msg",
"inTz": "Europe/London",
"adjAmount": 0,
"adjType": "days",
"adjDir": "add",
"format": "object",
"locale": "en_GB",
"output": "",
"outputType": "msg",
"outTz": "Europe/London",
"x": 470,
"y": 360,
"wires": [
[
"3514268f.2b575a"
]
]
},
{
"id": "749a67b1.8511e8",
"type": "inject",
"z": "533475c4.a28eac",
"name": "",
"topic": "",
"payload": "",
"payloadType": "date",
"repeat": "",
"crontab": "",
"once": false,
"x": 120,
"y": 360,
"wires": [
[
"e75759f3.14aed8",
"d057c5dc.43ec08",
"6a30d321.63b5ec",
"3562ef2d.e961c",
"2046d084.fb1e4"
]
]
},
{
"id": "3514268f.2b575a",
"type": "debug",
"z": "533475c4.a28eac",
"name": "",
"active": true,
"console": "false",
"complete": "payload",
"x": 870,
"y": 440,
"wires": []
},
{
"id": "d057c5dc.43ec08",
"type": "moment",
"z": "533475c4.a28eac",
"name": "",
"topic": "JSDATE (date) OUTPUT",
"input": "",
"inputType": "msg",
"inTz": "Europe/London",
"adjAmount": 0,
"adjType": "days",
"adjDir": "add",
"format": "jsdate",
"locale": "en_GB",
"output": "",
"outputType": "msg",
"outTz": "Europe/London",
"x": 490,
"y": 400,
"wires": [
[
"3514268f.2b575a"
]
]
},
{
"id": "6a30d321.63b5ec",
"type": "moment",
"z": "533475c4.a28eac",
"name": "",
"topic": "CALENDAR (AROUNDNOW) OUTPUT",
"input": "",
"inputType": "msg",
"inTz": "Europe/London",
"adjAmount": 0,
"adjType": "days",
"adjDir": "add",
"format": "calendar",
"locale": "en_GB",
"output": "",
"outputType": "msg",
"outTz": "Europe/London",
"x": 540,
"y": 440,
"wires": [
[
"3514268f.2b575a"
]
]
},
{
"id": "3562ef2d.e961c",
"type": "moment",
"z": "533475c4.a28eac",
"name": "",
"topic": "FROMNOW (TIMEAGO) OUTPUT",
"input": "",
"inputType": "msg",
"inTz": "Europe/London",
"adjAmount": 0,
"adjType": "days",
"adjDir": "add",
"format": "fromnow",
"locale": "en_GB",
"output": "",
"outputType": "msg",
"outTz": "Europe/London",
"x": 520,
"y": 480,
"wires": [
[
"3514268f.2b575a"
]
]
},
{
"id": "2046d084.fb1e4",
"type": "moment",
"z": "533475c4.a28eac",
"name": "",
"topic": "ISO8601 (ISO) OUTPUT",
"input": "",
"inputType": "msg",
"inTz": "Europe/London",
"adjAmount": 0,
"adjType": "days",
"adjDir": "add",
"format": "iso8601",
"locale": "en_GB",
"output": "",
"outputType": "msg",
"outTz": "Europe/London",
"x": 490,
"y": 520,
"wires": [
[
"3514268f.2b575a"
]
]
},
{
"id": "487012c7.d8d1bc",
"type": "inject",
"z": "533475c4.a28eac",
"name": "string yr 1st",
"topic": "",
"payload": "2017/6/14 20:10:10.276",
"payloadType": "str",
"repeat": "",
"crontab": "",
"once": false,
"x": 130,
"y": 400,
"wires": [
[
"e75759f3.14aed8",
"d057c5dc.43ec08",
"6a30d321.63b5ec",
"3562ef2d.e961c",
"2046d084.fb1e4"
]
]
},
{
"id": "366462c6.c51dbe",
"type": "inject",
"z": "533475c4.a28eac",
"name": "string complex",
"topic": "",
"payload": "Thu Jun 15 2017 18:02:13 GMT+0100 (GMT Summer Time)",
"payloadType": "str",
"repeat": "",
"crontab": "",
"once": false,
"x": 140,
"y": 440,
"wires": [
[
"e75759f3.14aed8",
"d057c5dc.43ec08",
"6a30d321.63b5ec",
"3562ef2d.e961c",
"2046d084.fb1e4"
]
]
},
{
"id": "d384ff51.8b4d",
"type": "comment",
"z": "533475c4.a28eac",
"name": "Expected Output",
"info": "| Input | Moment | Output |\n|------------------|-----------------------------|---------------------------------------------------------------------------------------------|\n| timestamp | OBJECT OUTPUT | {\"years\":2018,\"months\":8,\"date\":23,\"hours\":17,\"minutes\":0,\"seconds\":54,\"milliseconds\":298} |\n| | JSDATE (date) OUTPUT | Sun Sep 23 2018 17:00:54 GMT+0100 (GMT Summer Time) |\n| | CALENDAR (AROUNDNOW) OUTPUT | Today at 17:00 |\n| | FROMNOW (TIMEAGO) OUTPUT | a few seconds ago |\n| | ISO8601 (ISO) OUTPUT | 2018-09-23T16:00:54.298Z |\n| String Yr 1st | OBJECT OUTPUT | {\"years\":2017,\"months\":5,\"date\":14,\"hours\":20,\"minutes\":10,\"seconds\":10,\"milliseconds\":276} |\n| | JSDATE (date) OUTPUT | Wed Jun 14 2017 20:10:10 GMT+0100 (GMT Summer Time) |\n| | CALENDAR (AROUNDNOW) OUTPUT | 2017-06-14 |\n| | FROMNOW (TIMEAGO) OUTPUT | a year ago |\n| | ISO8601 (ISO) OUTPUT | 2017-06-14T19:10:10.276Z |\n| String (Complex) | OBJECT OUTPUT | {\"years\":2017,\"months\":5,\"date\":15,\"hours\":18,\"minutes\":2,\"seconds\":13,\"milliseconds\":0} |\n| | JSDATE (date) OUTPUT | Thu Jun 15 2017 18:02:13 GMT+0100 (GMT Summer Time) |\n| | CALENDAR (AROUNDNOW) OUTPUT | 2017-06-15 |\n| | FROMNOW (TIMEAGO) OUTPUT | a year ago |\n| | ISO8601 (ISO) OUTPUT | 2017-06-15T17:02:13.000Z |\n\nNeedess to say that your timestamp values will be different.",
"x": 780,
"y": 380,
"wires": []
}
]
@@ -0,0 +1,5 @@
const moment = require('moment-timezone')
const parseFormat = require('moment-parseformat')
console.log(moment().tz("America/Los_Angeles").format('d'))
console.log(moment().tz("Europe/London").format('d'))
@@ -0,0 +1,11 @@
// const locale = (process.env.LANG || process.env.LANGUAGE || process.env.LC_ALL || process.env.LC_MESSAGES).split('.')[0]
// console.log(locale, process.env.LANG, process.env.LANGUAGE, process.env.LC_ALL, process.env.LC_MESSAGES)
// const { osLocale } = require("os-locale-s");
const osLocale = require("os-locale");
// (async () => {
// console.log(await osLocale());
// //=> 'en-US'
// })();
console.log(osLocale.sync())