Skip to content

Commit

Permalink
Align how strings are converted to timestamps (#1534)
Browse files Browse the repository at this point in the history
* Better date support for Safari
* Config separators for now() output
* Support timezone timestamps
  • Loading branch information
alsundukov authored and mathiasrw committed Jun 12, 2023
1 parent 4cc991d commit ca814a3
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 11 deletions.
3 changes: 3 additions & 0 deletions src/17alasql.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ alasql.options = {

/** Use valueof in orderfn */
valueof: true,
progress: false, // Callback for async queries progress
nowdateseparator: '-', // Date separator for Now() function
nowdatetimeseparator: ' ', //'T'; // date-time Separator for Now() function

/** DROP database in any case */
dropifnotexists: false,
Expand Down
72 changes: 61 additions & 11 deletions src/61date.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ stdfn.ASCII = function (a) {
return a.charCodeAt(0);
};

/**
/**
Return first non-null argument
See https://msdn.microsoft.com/en-us/library/ms190349.aspx
*/
Expand All @@ -43,20 +43,24 @@ stdfn.OBJECT_ID = function (objid) {
};

stdfn.DATE = function (d) {
if (/\d{8}/.test(d)) return new Date(+d.substr(0, 4), +d.substr(4, 2) - 1, +d.substr(6, 2));
if (/^\d{8}$/.test(d.toString()))
return new Date(+d.substr(0, 4), +d.substr(4, 2) - 1, +d.substr(6, 2));
return newDate(d);
};

stdfn.NOW = function () {
var d = new Date();
stdfn.NOW = function (param) {
var d;
if (param) d = stdfn.DATE(param);
else d = new Date();
var separator = alasql.options.nowdateseparator;
var s =
d.getFullYear() +
'-' +
separator +
('0' + (d.getMonth() + 1)).substr(-2) +
'-' +
separator +
('0' + d.getDate()).substr(-2);
s +=
' ' +
alasql.options.nowdatetimeseparator +
('0' + d.getHours()).substr(-2) +
':' +
('0' + d.getMinutes()).substr(-2) +
Expand Down Expand Up @@ -166,12 +170,58 @@ alasql.stdfn.DATE_SUB = alasql.stdfn.SUBDATE = function (d, interval) {
return new Date(nd);
};

var dateRegexp = /^\d{4}\.\d{2}\.\d{2} \d{2}:\d{2}:\d{2}/;
var dateRegexp =
// /^(?<year>\d{4})[-\.](?<month>\d{1,2})[-\.](?<day>\d{1,2})( (?<hours>\d{2}):(?<minutes>\d{2})(:(?<seconds>\d{2})(\.(?<milliseconds>)\d{3})?)?)?/;
/^\W*(?<year>\d{4})[-\/\.](?<month>[0-1]?\d)[-\/\.](?<day>[0-3]?\d)([T ](?<hour>[0-2][0-9]):(?<min>[0-5][0-9])(:(?<sec>[0-5][0-9])(\.(?<msec>\d{3}))?\d*)?(?<tz>Z|(?<tz_dir>[-+])(?<tz_HH>\d\d):(?<tz_mm>\d\d))?)?/i;
function newDate(d) {
// Read https://stackoverflow.com/questions/2587345/why-does-date-parse-give-incorrect-results/20463521#20463521 to understand
// why we need to rely on our own parsing to ensure consistency across runtimes.
// Also: YYYY-MM-DD defaults to UTC is required by ECMA-262 but YYYY-MM-DD HH:ss is not.
// AlaSQL will treat _anything_ with no timezone as local time.

var date;

if (typeof d === 'interger') {
return new Date(d);
}

if (typeof d === 'string') {
if (dateRegexp.test(d)) {
d = d.replace('.', '-').replace('.', '-');
const match = d.match(dateRegexp);
if (match) {
const {year, month, day, hour, min, sec, msec, tz, tz_dir, tz_HH, tz_mm} = match.groups;

if (tz) {
var offset_HH = 0;
var offset_mm = 0;

if ('Z' != tz) {
offset_HH = -1 * +(tz_dir + tz_HH);
offset_mm = -1 * +(tz_dir + tz_mm);
}
date = new Date(
Date.UTC(+year, +month - 1, +day, +hour + offset_HH, +min + offset_mm, +sec, +msec)
);
} else {
const dateArrguments = [year, month - 1, day];
if (hour) {
dateArrguments.push(hour, min, sec || 0, msec || 0);
}
date = new Date(...dateArrguments);
}
} else {
date = new Date(Date.parse(d));
}
}
return new Date(d);

if (isNaN(date)) {
date = new Date(d);
}

// https://github.com/AlaSQL/alasql/pull/1534#discussion_r1051623032
//if (isNaN(date)) {
// throw 'The value could not be converted to a date: ' + JSON.stringify(d);
// //console.warn(`d:${d}, date:${date}`)
//}

return date;
}
120 changes: 120 additions & 0 deletions test/test845.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,124 @@ describe('Test ' + test + ' - use NOW() function', function () {
//02/25/22
assert(/\d{2}\/\d{2}\/\d{2}/.test(res[0].conv));
});

it('3. NOW() with point', function () {
var currentSeparator = alasql.options.nowdateseparator;
alasql.options.nowdateseparator = '.';
var res = alasql('SELECT NOW() AS now');
//2022.02.25 19:21:27.839
assert(/\d{4}.\d{2}.\d{2} \d{2}:\d{2}:\d{2}.\d{3}/.test(res[0].now));
alasql.options.nowdateseparator = currentSeparator;
});

it('4. CONVERT with NOW() with point as an argument', function () {
var currentSeparator = alasql.options.nowdateseparator;
alasql.options.nowdateseparator = '.';
var res = alasql('SELECT CONVERT(STRING,NOW(),1) AS conv');
//02/25/22
assert(/\d{2}\/\d{2}\/\d{2}/.test(res[0].conv));
alasql.options.nowdateseparator = currentSeparator;
});

it('5. Reads multiple formats of date strings', function () {
assert.equal(
alasql('VALUE OF SELECT DATE("2023-06-10 16:27:36.224Z")').toISOString(),
'2023-06-10T16:27:36.224Z'
);

// Despite ECMA-262 where YYYY-MM-DD must be set in UTC and YYYY-MM-DD HH:ss must not AlaSQL will treat _anything_ with no timezone as local time.

assert.equal(
alasql('VALUE OF SELECT DATE("2022-01-10T00:00:00")').toISOString(),
alasql('VALUE OF SELECT DATE("2022-01-10")').toISOString()
);

assert.equal(
alasql('VALUE OF SELECT DATE("2022-01-20T12:01:02.123456789Z")').toISOString(),
'2022-01-20T12:01:02.123Z'
);

assert.equal(
alasql('VALUE OF SELECT DATE("2022-01.20 12:00:00.123456789-10:30")').toISOString(),
'2022-01-20T22:30:00.123Z'
);

assert.equal(
alasql('VALUE OF SELECT DATE("2022-01.20 12:00:00.123456789+10:30")').toISOString(),
'2022-01-20T01:30:00.123Z'
);

assert.equal(
alasql('VALUE OF SELECT DATE("2022-01.20 12:00+10:30")').toISOString(),
'2022-01-20T01:30:00.000Z'
);

assert.equal(
alasql('VALUE OF SELECT DATE("2022-01-20T12:00:00.123456789+03:30")').toISOString(),
'2022-01-20T08:30:00.123Z'
);

assert.equal(
alasql('VALUE OF SELECT DATE("2022-01-20T12:00:00.123456789+03:30")').toISOString(),
'2022-01-20T08:30:00.123Z'
);

/*
Hmmm. How best to test that this gets converted correclty to locala time (including DST)?
"2022.01.10 04:10",
"2022.01.10 04:10:11",
"2022.01.10 04:10:11.123",
"2022-01-10 04:10",
"2022-01-10 04:10:11",
"2022-01-10 04:10:11.123",
"2022.1.10",
"2022-1-1"
"2022/1/1"
For now: Lets make sure that it converts to a valid date.
*/

assert(!isNaN(alasql('VALUE OF SELECT DATE("2012.01.10 04:10")')));
assert(!isNaN(alasql('VALUE OF SELECT DATE("2022.01.10 04:10:11")')));
assert(!isNaN(alasql('VALUE OF SELECT DATE("2022.01.10 04:10:11.123")')));
assert(!isNaN(alasql('VALUE OF SELECT DATE("2022.01.10T04:10:11.123")')));
assert(!isNaN(alasql('VALUE OF SELECT DATE("2022-01-10 04:10")')));
assert(!isNaN(alasql('VALUE OF SELECT DATE("2022-01-10 04:10:11")')));
assert(!isNaN(alasql('VALUE OF SELECT DATE("2022-01-10 04:10:11.123")')));
assert(!isNaN(alasql('VALUE OF SELECT DATE("2022.1.10")')));
assert(!isNaN(alasql('VALUE OF SELECT DATE("2022-1-1")')));
assert(!isNaN(alasql('VALUE OF SELECT DATE("2022/1/1")')));
});

it('6. Will seek to trim value', function () {
assert.deepEqual(
alasql('VALUE OF SELECT DATE(" 2022-01-10T00:00:01")'),
alasql('VALUE OF SELECT DATE("2022-01-10T00:00:01")')
);

assert.deepEqual(
alasql('VALUE OF SELECT DATE(" 2022-01-10T00:00:02")'),
alasql('VALUE OF SELECT DATE("2022-01-10T00:00:02")')
);

assert.notDeepEqual(
alasql('VALUE OF SELECT DATE("xyz 2022-01-10T00:00:03")'),
alasql('VALUE OF SELECT DATE("2022-01-10T00:00:03")')
);

assert.deepEqual(
alasql('VALUE OF SELECT DATE("€%&/()\'2022-01-10T00:00:04")'),
alasql('VALUE OF SELECT DATE("2022-01-10T00:00:04")')
);

assert.deepEqual(
alasql('VALUE OF SELECT DATE("2022-01-10T00:00:05 XXYYYZZZ5678&/(")'),
alasql('VALUE OF SELECT DATE("2022-01-10T00:00:05")')
);

assert.deepEqual(
alasql('VALUE OF SELECT DATE("2022-01-10T00:00:06 2000-02-20T20:20:20")'),
alasql('VALUE OF SELECT DATE("2022-01-10T00:00:06")')
);
});
});

0 comments on commit ca814a3

Please sign in to comment.