How to convert a Postgresql timestamptz to a Javascript Date? - javascript

My database creates a bunch of timestamptz values using the now()::TEXT function. In several API calls I receive these values, but I'm having difficulty turning them into Javascript Date objects.
Here's an example of a Timestamptz string that Postgres returns: 2019-09-12 09:52:52.992823+00
The problem is that if I try to do new Date("2019-09-12 09:52:52.992823+00"), then there's finicky behavior in the browser. It looks like I have to append a "Z" first to the string to get the time correct (because of timezones?)
Also, Firefox doesn't seem to parse it at all (but Chrome does). If I remove the "+00", it seems to work.
So I came up with this "solution" which seems to work on both browsers, but I have to call it on all my dates before working with them, and it seems kinda brittle. Is this the proper way?
function fixDate(date) {
return date.replace("+00", "") + "Z";
}

The supported formats for the built–in parser are described in ECMA-262, essentially a specific subset of ISO 8601 and the format specified for Date.prototype.toString. Parsing of any other format (that is, anything that doesn't match exactly with one of the specified formats) is implementation dependent and should not be trusted.
You might consider parsing the string and constructing a new string that is compliant with ECMA-262, e.g. convert "2019-09-12 09:52:52.992823+00" to "2019-09-12T09:52:52.992+00:00".
// Convert 2019-09-12 09:52:52.992823+00 to
// 2019-09-12T09:52:52.992+00:00
// Assumes all strings are +00:00
function rectifyFormat(s) {
let b = s.split(/\D/);
return b[0] + '-' + b[1] + '-' + b[2] + 'T' +
b[3] + ':' + b[4] + ':' + b[5] + '.' +
b[6].substr(0,3) + '+00:00';
}
let s = '2019-09-12 09:52:52.992823+00';
console.log(new Date(rectifyFormat(s)))
However, also heed the general advice to avoid the built–in parser, even with supported formats. It also seems inefficient to parse a string to produce another string that is then parsed again to produce a Date. It's preferable to write your own parse function (a couple of lines of code) or use a library, e.g.
// Parse string like '2019-09-12 09:52:52.992823+00'
// to a date
// Assumes string is always +00
function myDateParse(s) {
let b = s.split(/\D/);
--b[1]; // Adjust month number
b[6] = b[6].substr(0,3); // Microseconds to milliseconds
return new Date(Date.UTC(...b));
}
let s = '2019-09-12 09:52:52.992823+00';
console.log(myDateParse(s).toISOString());

Related

How can I sort ISO times collection on various different JavaScript environments?

It works well in the X5 JSCore and node environment, but it doesn't work in the iOS WKWebView environment.Why?
Here is my code:
var test = [
{name: "aaa",createTime: "2019-10-10T11:11:16.000+0000"},
{name: "ccc",createTime: "2019-10-03T10:11:16.000+0000"},
{name: "bbb",createTime: "2019-10-07T10:11:16.000+0000"},
{name: "zzz",createTime: "2019-10-09T03:12:25.000+0000"},
]
const sortList = test.sort((a, b) => {
return new Date(b.createTime) - new Date(a.createTime)
})
console.log(sortList);
I use this article as a reference, but it doesn't work in other contexts
How to sort strings in JavaScript
If the ISO string were illegal, it would not work in any environment, but now it works in other environments.
Only iOS environment doesn't work.
why?
Can you help me?
The date time strings you have are not valid in JavaScript as they are missing the colon within the time zone expression. You should try to change that on the backend first. If you can't you have to add the missing colon yourself.
let dateString = '2019-10-10T11:11:16.000+0000';
const colonPosition = 26;
if (dateString.length === 28) {
dateString = dateString.substring(0, colonPosition) + ':' + dateString.substring(colonPosition);
}
console.log(dateString);
But note that just checking string length is a naive approach. Depending on what formats your backend returns, this could fail. Thus it would be best to fix it on the backend directly.
I think you should do subtract integers, like Unix timestamps, rather the two Date objects. It would be more bulletproof and less depending on the javascript environment it would run on.
eg:
const sortList = test.sort((a, b) => {
return (new Date(b.createTime)).getTime() - (new Date(a.createTime)).getTime()
})
Best

Getting ISO-8601 formatted date using Javascript in a Cosmos DB stored procedure

I'm writing a stored procedure for Cosmos DB. I am not very familiar with JavaScript.
I've been trying to get an ISO-8601 formatted Date, but have been unsuccessful so far. According to some docs I found, I should just be able to call toISOString on a Date. That does not work ("Object doesn't support property or method" error).
So I found this advice to define the prototype yourself:
function storedProcedure(arg) {
if (!Date.prototype.toISOString) {
// Here we rely on JSON serialization for dates because it matches
// the ISO standard. However, we check if JSON serializer is present
// on a page and define our own .toJSON method only if necessary
if (!Date.prototype.toJSON) {
var toISOString = function (date) {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
return date.getUTCFullYear() + '-' +
f(date.getUTCMonth() + 1) + '-' +
f(date.getUTCDate()) + 'T' +
f(date.getUTCHours()) + ':' +
f(date.getUTCMinutes()) + ':' +
f(date.getUTCSeconds()) + 'Z';
};
}
Date.prototype.toISOString = Date.prototype.toJSON;
}
var now = Date.now();
var iso8601 = now.toISOString();
console.log("ISO " + iso8601);
// other code
}
However, this still fails with:
Object doesn't support property or method 'toISOString'
I tried removing the prototype altogether and just using a function that takes a Date instead. However, then the same error occurred but for other members, like getUTCFullYear.
I tried also calling getSeconds and that failed for the same reason. Out of desperation, I tried listing out the object's properties using the getKeys function in this answer and it gives me an empty list.
What is going on here? How can I just get an ISO-8601 formatted Date representing the current time in UTC?
OK, it seems it was because I had used Date.now() per these docs instead of new Date() per this random google result.
I have no idea why this matters or how I could have known this (explanations welcome) but that was the underlying cause. I don't even need the prototype now.

Merging partial moments

Unlike a vanilla date, a moment does not have any required units that must be passed to its constructor. For instance, this is a perfectly valid way to instantiate a moment, with the unpassed units defaulting to current date and 00:00 time:
moment.utc('07:35', 'HH:mm').toISOString();
> "2013-10-24T07:35:00.000Z" //let's just ignore the timezones for now, ok?
So how can partial moments be merged into one? For instance, I could just .set() first moment's hours to values from the second:
var mergee = moment.utc('07:35', 'HH:mm');
moment.utc('2015-01-01').
set('hours', mergee.get('hours')).
set('minutes', mergee.get('minutes')).
toISOString();
> "2015-01-01T07:35:00.000Z" //yay!
But what if I don't know in advance the format of the "mergee", or even what units it has parsed?
While not entirely supported or recommended, something like this will work:
var m1 = moment.utc('07:35', 'HH:mm');
var m2 = moment.utc('2015-01-01');
var merged = moment.utc(m1._i + ' ' + m2._i, m1._f + ' ' + m2._f);
The space separator isn't necessarily required, but it might prevent conflicts with certain formats.
I could see that you might have troubles though if both moments contained the same elements with different data.

Dijit DateTextBox - setting the date in ISO/numeric format?

I'm using DateTextBox as one of many controls in my screen. I register them in one place and then batch set values to them in loop, calling set('value', val) on each of them. All controls behave correctly, only the DateTextBox won't accept the data from server.
Initially java's Date was serialized as long (ex. 1280959200000), but when I've changed to ISO format (ex. "2010-08-04T22:00:00.000+0000") it isn't accepted either. But both are accepted date formats for new Date() constructor.
On the output I get the date value in ISO format: "2013-08-04T22:00:00.000Z" so it should be also accepted on input.
What can I do with DateTextBox to make it accept values in all formats supported by JavaScript's Date object, or one of the formats that can be returned from my server?
I think the fundamental problem is that the Javascript built-in Date object only accepts certain formats, and Dojo is relying on that built-in-behavior. At work, we have a similar issue, where lots of legacy PHP code is accustomed to passing dates around in a Mysql-derived format (ex. YYYY-MM-DD HH:MM:SS)
Our current workaround is to subclass dijit/form/DateTextBox, which also lets us impose some UI improvements. When something tries to set a value which isn't already a Date object and looks like a MySQL datetime, this code re-forms it to match ISO-8601 and passes it on through.
Dojo 1.9 code for a custom version of DateTextBox:
define([
"dojo/_base/declare",
"dojo/_base/lang",
"dojo/_base/array",
"dijit/form/DateTextBox"
], function(declare, lang, array, DateTextBox){
var clazz = declare([DateTextBox], {
_mysqlStyleExp : /^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})$/,
postMixInProperties: function(){ // change value string to Date object
this.inherited(arguments);
this.constraints.datePattern = "yyyy-MM-dd"; // Affects display to user
},
_convertMysqlDateToIso: function(value){
value = lang.trim(value);
var matches = this._mysqlStyleExp.exec(value);
if(matches !== null){
// Put the "T" in the middle and add fractional seconds with UTC
// timezone
// If your MySQL dates are NOT in UTC, obviously this will screw things up!
return matches[1] + "T" + matches[2] + ".000Z";
}else{
return null;
}
},
_setValueAttr : function(/*Date|String*/ value, /*Boolean?*/ priorityChange, /*String?*/ formattedValue){
/*
We want to be slightly more permissive in terms of the strings that can be set in order to support
older code... But someday it'd be nice to standardize on Date.toJSON, so warn.
*/
if(typeof(value) === "string"){
var isoDate = this._convertMysqlDateToIso(value);
if(isoDate !== null){
console.warn("Converting non-ISO date of "+value);
value = isoDate;
}
}
this.inherited(arguments);
}
});
return clazz;
});
Note that this only affects data flowing into the Dojo widget.
The docs say:
The value of this widget as a JavaScript Date object, with only
year/month/day specified.
So instead of this (which I assume you're currently doing):
new dijit.form.DateTextBox({value: "2010-08-04T22:00:00.000+0000"}, domNode);
Do this:
var myDate = new Date("2010-08-04T22:00:00.000+0000");
new dijit.form.DateTextBox({value: myDate}, domNode);

why do Date.parse('2012-01-01') and Date.parse('1/1/2012') return different values?

Verified across browsers (Firefox & Chrome) and across platforms (OSX & Linux):
> Date.parse('2012-01-01')
1325376000000
> Date.parse('1/1/2012')
1325394000000
Relevant:
https://github.com/portablemind/compass_agile_enterprise/wiki/Javascript-Date.parse-bug%3F
The format 2012-01-01 is interpreted as ISO 8601 conformant, and the Z timezone (+00, Universal Time Coordinated) is implied. The format 1/1/2012, if accepted (this is implementation-dependent), is taken as local time.
To get more consistent results, use a library like Globalize.js.
If you add Z to the end, that will guarantee that you always mean UTC.
> Date.parse('2012-01-01')
1325376000000
> Date.parse('1/1/2012')
1325394000000
> Date.parse('1/1/2012 Z')
1325376000000
I have written a code as below:
var a = Date.parse('2012-01-01');
var b = Date.parse('2012-01-01');
var c = Date.parse('1/1/2012');
alert( a + ' - ' + b + ' - ' + c );
And result is,
1325376000000 - 1325376000000 - 1325376000000
The reason why I have written same code for a and b is, http://www.w3schools.com/jsref/jsref_parse.asp says, Date.parse returns a millisecond value, whether time passes between those lines.
Im using Firefox 9.0.1, and the result is correct.

Categories