Date Formatting in JavaScript (and OptoScript)

There are many options for date formatting in JavaScript, among which are
toString() which results in “Tue Jul 18 2017 13:28:48 GMT-0700 (PDT)”,
toUTCString() that gives “Tue, 18 Jul 2017 20:28:48 GMT”,
and finally toISOString() for “2017-07-18T20:28:48.297Z”, as well as methods that return each piece with getFullYear()_, getHours(), getMilliseconds(), etc. (see full list).

None of these, however, provide 12 hour time or AM / PM status, which is usually easier to actually read. In order to get very specifically formatted date information I use a function node in Node-RED to work date strings with JavaScript before displaying them in groov.
Here is one example to get the date format “01:28PM Tue 17-07-18

var d = new Date();
var tstring = ('0'+((d.getHours()+11)%12 + 1)).slice(-2)+':';
tstring += ('0'+d.getMinutes()).slice(-2) + ((d.getHours() >= 12)? 'PM ':'AM ');
// tstring = 01:28PM
tstring += d.toString().substring(0,4) + d.toISOString().substring(2,10);
// tstring = 01:28PM Tue 17-07-18

Appending the character ‘0’ to the front and using slice(-2) ensures that the number has a leading zero but at most two digits, since getHours(), getMinutes() and other such methods return exact integers, thus when called at 4:01 in the morning they return 4 and 1 respectively but that is processed to output "04:01AM ".
If you wish to put a leading zero on many fields it may clean up your code to define a simple function:

function twoDig(val) {return (('0'+val).slice(-2));}

So to get the time formatted as “4:01AM”, with a leading zero on minutes but not hours, simply use:

((d.getHours()+11)%12 + 1)+':'+twoDig(d.getMinutes()) + ((d.getHours() >= 12)? 'PM ':'AM ');

The key line to get from 24-hour time to 12-hour time is (d.getHours()+11)%12 + 1, and ((d.getHours() >= 12)? 'PM ':'AM ') appends 'PM ’ if the hour is 12 - 23 and 'AM ’ if the hour is 0 (midnight) - 11.
Note that 'PM ’ is not fixed, and simply 'p ’ for “01:28p Tue 17-07-18” works great here.

The final part, appending the day and date, just grabs the first four characters from the toString() formatting, which contains only the three character day and a space, and the last eight characters of the first ten from toISOString() (does not include “20” for “2017”). To get the full “2017-07-18” date simply change the function to d.toISOString().substring(0,10);

It is also possible to build a custom day of the week and date without the to-string functions at all, just using getFullYear() getMonth(), getDate() and getDay():

var weekday = ["Sunday ","Monday ","Tuesday ","Wednesday ","Thursday ","Friday ","Saturday "];
var dstring = weekday[d.getDay()] + twoDig(d.getMonth())+'/'+twoDig(d.getDate())+'/'+d.getFullYear();
// dstring = Tuesday 06/18/2017

Simply build an array of days of the week, with free choice of spelling, capitalization, and language, then use getDay() which returns a number 0-6 to reference it. This would also work to get the text for which month it is from an array of 12 string.
Numerical month, date, and year after that are trivial, and day/month may benifit from leading zeros to keep string length consistent.

And there it is! Go ahead and arrange these basic methods and tricks to get all kinds of interesting, unique date formats from Node-RED.

Here’s one way to create a similar string "07/18/2017 Tue 1:28PM" with OptoScript:

GetSubString("SunMonTueWedThuFriSat",GetDayOfWeek()*3, 3, curr_day);
DateToStringMMDDYYYY(formatted_date);
NumberToString((GetHours()+11)%12 + 1, curr_12hour);
NumberToString(GetMinutes(), curr_minutes);
if(GetMinutes() < 10) then
  curr_minutes = "0" + curr_minutes;
endif
formatted_date = formatted_date+" "+curr_day+" "+curr_12hour+":"+curr_minutes;
if(GetHours() >= 12) then
  formatted_date += "PM";
else
  formatted_date += "AM";
endif

You could also make and reference a string table for day of the week (or even month) like the JavaScript example, but the three letter abbreviation from substring makes things neater and saves on a bit of storage.

Just like JavaScript this is very flexible and ultimately comes down to string manipulation, so to get "2017-7-18 Tue 1:28p":

GetSubString("SunMonTueWedThuFriSat",GetDayOfWeek()*3, 3, curr_day);
NumberToString(GetYear(), curr_year);
NumberToString(GetMonth(), curr_month);
NumberToString(GetDay(), curr_date);
NumberToString((GetHours()+11)%12 + 1, curr_12hour);
NumberToString(GetMinutes(), curr_minutes);
if(GetMinutes() < 10) then
  curr_minutes = "0" + curr_minutes;
endif
formatted_date = curr_year+"-"+curr_month+"-"+curr_date+" "+curr_day+" "+curr_12hour+":"+curr_minutes;
if(GetHours() >= 12) then
  formatted_date += Chr(112);
else
  formatted_date += Chr(97);
endif

You are free to put day, date, and time in any order you like with any characters separating them - just make sure formatted_date is long enough to hold whatever you’re building.
Note that for example curr_12hour could be one character for 1-9 or two characters 10-12, unless you append a "0" like I did with minutes. GetDay() and GetMonth() will return single digits as well, but for minutes it is necessary to add a zero when less than ten otherwise 12:04 appears as 12:4.

Good luck & have fun!

I just stumbled on a really simple way to convert Epoch or Unix time to a human readable time stamp using Node-RED.

Use a change node.
Configure it like so;

Your Epoch time goes in on the payload; 1600154034059
And your UTC time stamp comes out on the payload; 2020-09-15T07:13:54.512Z

1 Like

Thanks @Beno, this has come up for multiple customers of mine! Neat shortcut!

On the Optoscript example, one word of caution is to not use the GetYear, GetMonth, GetDay, etc functions together since time functions are nondeterministic. For instance, the call to GetHours() could happen at 11:59, then the call to GetMinutes() could be at 12:00. Those functions are fine if you just need the month or the year, etc, but they shouldn’t be used in combination.

Instead, use the GetDateTime method that returns all the above values into an integer table and then use the values out of the table.

See Mary’s example and comments on Convert Integer 32 to Timestamp String - #2 by mstjohn for the proper way to do this.

1 Like

Drop down a few most posts and use the code from there - that works, this does not. (Left it up as an example of what not to do).

The above methods are very compact and clean for sure, but what if you don’t need seconds to three decimal places?
What if you need a more compact human readable UTC date time stamp?

Once again, @torchard to the rescue with the following code…

Simply paste in this JavaScript (ie, Node-RED function node)…

//desired UTC format. hh:mmz dd-mm-yyyy, ex: 15:11Z 20-12-2020
// twoDig helper function to give leading zeros on single-digit values:
function twoDig(val) {return ((‘0’+val).slice(-2));}
var d = new Date();
offset = d.getTimezoneOffset()/60;
msg.utcdts = twoDig(d.getHours()+offset)+‘:’+twoDig(d.getMinutes())+‘Z ’ +
twoDig(d.getDate())+’-‘+twoDig((d.getMonth()+1))+’-'+d.getFullYear();

//Comment out the above line and remove the comment // from the below line to add seconds to the timestamp
//msg.utcdts = twoDig(d.getHours()+offset)+‘:’+twoDig(d.getMinutes())+‘:’+twoDig(d.getSeconds())+‘Z ’+
//twoDig(d.getDate())+‘-’+twoDig((d.getMonth()+1))+‘-’+d.getFullYear();

return msg;

1 Like

There is a big swath in the outback (among other places) where this code won’t work in.

True. And very remiss of me to not mention it in that post.
Timezones are hard. Spent more than a few hours on it last night… Bleh.

Agreed, date formatting / time zones / date arithmetic is all challenging.

You could use the built in getUTCHours, getUTCMinutes etc and remove the offset calculation all together though, yes?

Updated code. Use this not that. Should even work in Outback Australia (In case you are wondering about that, Broken Hill is one of the very few time zones on the planet that does a 30 minute split - a few zones even have 45 minute splits).

//desired UTC format. hh:mm:ssZ dd-mm-yyyy, ex: `15:38:44Z 20-12-2020`
// twoDig helper function to give leading zeros on single-digit values:
function twoDig(val) {return (('0'+val).slice(-2));}
var d = new Date();
msg.utcdts = twoDig(d.getUTCHours())+':'+twoDig(d.getUTCMinutes())+':'+twoDig(d.getUTCSeconds())+'Z ' +
    twoDig(d.getUTCDate())+'-'+twoDig((d.getUTCMonth()+1))+'-'+d.getUTCFullYear();
return msg;

Thanks @philip for the prod to re-visit (ie, fix) this.

2 Likes

@bensonh was making some changes to his weather API and wanted to keep as much existing code as possible, the new API returned sunrise and sunset as UTC seconds. His existing flow needed hours and minutes as integers in their own payload.
It took both of us a bit of digging to find this solution…

Here is an example msg.payload.current.sunrise: 1666375218535
Turns out, there is a Javascript library to get what you need, month, day, year, hours, minutes and seconds.
We are just going to get the hours and minutes like thus:

var rise = new Date(msg.payload.current.sunrise);
var set = new Date(msg.payload.current.sunset);

var msgSunriseH = {payload:msg.payload = rise.getHours()};
var msgSunriseM = {payload:msg.payload = rise.getMinutes()};
var msgSunsetH = {payload:msg.payload = set.getHours()};
var msgSunsetM = {payload:msg.payload = set.getMinutes()};

return [msgSunriseH, msgSunriseM, msgSunsetH, msgSunsetM];

It looks like this in the flow editor:

Small thing, but it looks like milliseconds.

1 Like

Yup. your too fast for me to edit my post!

I tested the code with the Node-RED timestamp inject then sent it to Benson and posted it here.
He came back a few minutes later and said the rise set times were wrong.
His API was seconds, but the Node-RED timestamp was milliseconds and the JavaScript function needs millisonds… so Bensons code is now thus:

var rise = new Date(msg.payload.current.sunrise * 1000);
var set = new Date(msg.payload.current.sunset * 1000);

var msgSunriseH = {payload:msg.payload = rise.getHours()};
var msgSunriseM = {payload:msg.payload = rise.getMinutes()};
var msgSunsetH = {payload:msg.payload = set.getHours()};
var msgSunsetM = {payload:msg.payload = set.getMinutes()};

return [msgSunriseH, msgSunriseM, msgSunsetH, msgSunsetM];
1 Like