Skip to content

Commit

Permalink
more reliable date parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
toddtreece committed Aug 22, 2014
1 parent 33f82bf commit 585ee6a
Show file tree
Hide file tree
Showing 2 changed files with 3 additions and 278 deletions.
280 changes: 2 additions & 278 deletions lib/filter_stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

/**** Module dependencies ****/
var stream = require('stream'),
datejs = require('datejs'),
util = require('util');

/**** Make FilterStream a transform stream ****/
Expand Down Expand Up @@ -185,283 +186,6 @@ function toDate(d) {
return d;
}

// convert to int
d = strtotime(d);
return Date.parse(d) || false;

if(d === false) {
return false;
}

return new Date(d);

}

// strtotime from https://raw.githubusercontent.com/kvz/phpjs/master/functions/datetime/strtotime.js
function strtotime(text, now) {
// discuss at: http://phpjs.org/functions/strtotime/
// version: 1109.2016
// original by: Caio Ariede (http://caioariede.com)
// improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// improved by: Caio Ariede (http://caioariede.com)
// improved by: A. Matías Quezada (http://amatiasq.com)
// improved by: preuter
// improved by: Brett Zamir (http://brett-zamir.me)
// improved by: Mirko Faber
// input by: David
// bugfixed by: Wagner B. Soares
// bugfixed by: Artur Tchernychev
// note: Examples all have a fixed timestamp to prevent tests to fail because of variable time(zones)
// example 1: strtotime('+1 day', 1129633200);
// returns 1: 1129719600
// example 2: strtotime('+1 week 2 days 4 hours 2 seconds', 1129633200);
// returns 2: 1130425202
// example 3: strtotime('last month', 1129633200);
// returns 3: 1127041200
// example 4: strtotime('2009-05-04 08:30:00 GMT');
// returns 4: 1241425800

var parsed, match, today, year, date, days, ranges, len, times, regex, i, fail = false;

if (!text) {
return fail;
}

// Unecessary spaces
text = text.replace(/^\s+|\s+$/g, '')
.replace(/\s{2,}/g, ' ')
.replace(/[\t\r\n]/g, '')
.toLowerCase();

// in contrast to php, js Date.parse function interprets:
// dates given as yyyy-mm-dd as in timezone: UTC,
// dates with "." or "-" as MDY instead of DMY
// dates with two-digit years differently
// etc...etc...
// ...therefore we manually parse lots of common date formats
match = text.match(
/^(\d{1,4})([\-\.\/\:])(\d{1,2})([\-\.\/\:])(\d{1,4})(?:\s(\d{1,2}):(\d{2})?:?(\d{2})?)?(?:\s([A-Z]+)?)?$/);

if (match && match[2] === match[4]) {
if (match[1] > 1901) {
switch (match[2]) {
case '-':
{
// YYYY-M-D
if (match[3] > 12 || match[5] > 31) {
return fail;
}

return new Date(match[1], parseInt(match[3], 10) - 1, match[5],
match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
}
case '.':
{
// YYYY.M.D is not parsed by strtotime()
return fail;
}
case '/':
{
// YYYY/M/D
if (match[3] > 12 || match[5] > 31) {
return fail;
}

return new Date(match[1], parseInt(match[3], 10) - 1, match[5],
match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
}
}
} else if (match[5] > 1901) {
switch (match[2]) {
case '-':
{
// D-M-YYYY
if (match[3] > 12 || match[1] > 31) {
return fail;
}

return new Date(match[5], parseInt(match[3], 10) - 1, match[1],
match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
}
case '.':
{
// D.M.YYYY
if (match[3] > 12 || match[1] > 31) {
return fail;
}

return new Date(match[5], parseInt(match[3], 10) - 1, match[1],
match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
}
case '/':
{
// M/D/YYYY
if (match[1] > 12 || match[3] > 31) {
return fail;
}

return new Date(match[5], parseInt(match[1], 10) - 1, match[3],
match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
}
}
} else {
switch (match[2]) {
case '-':
{
// YY-M-D
if (match[3] > 12 || match[5] > 31 || (match[1] < 70 && match[1] > 38)) {
return fail;
}

year = match[1] >= 0 && match[1] <= 38 ? +match[1] + 2000 : match[1];
return new Date(year, parseInt(match[3], 10) - 1, match[5],
match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
}
case '.':
{
// D.M.YY or H.MM.SS
if (match[5] >= 70) {
// D.M.YY
if (match[3] > 12 || match[1] > 31) {
return fail;
}

return new Date(match[5], parseInt(match[3], 10) - 1, match[1],
match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
}
if (match[5] < 60 && !match[6]) {
// H.MM.SS
if (match[1] > 23 || match[3] > 59) {
return fail;
}

today = new Date();
return new Date(today.getFullYear(), today.getMonth(), today.getDate(),
match[1] || 0, match[3] || 0, match[5] || 0, match[9] || 0) / 1000;
}

// invalid format, cannot be parsed
return fail;
}
case '/':
{
// M/D/YY
if (match[1] > 12 || match[3] > 31 || (match[5] < 70 && match[5] > 38)) {
return fail;
}

year = match[5] >= 0 && match[5] <= 38 ? +match[5] + 2000 : match[5];
return new Date(year, parseInt(match[1], 10) - 1, match[3],
match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
}
case ':':
{
// HH:MM:SS
if (match[1] > 23 || match[3] > 59 || match[5] > 59) {
return fail;
}

today = new Date();
return new Date(today.getFullYear(), today.getMonth(), today.getDate(),
match[1] || 0, match[3] || 0, match[5] || 0) / 1000;
}
}
}
}

// other formats and "now" should be parsed by Date.parse()
if (text === 'now') {
return now === null || isNaN(now) ? new Date()
.getTime() / 1000 | 0 : now | 0;
}
if (!isNaN(parsed = Date.parse(text))) {
return parsed / 1000 | 0;
}

date = now ? new Date(now * 1000) : new Date();
days = {
'sun': 0,
'mon': 1,
'tue': 2,
'wed': 3,
'thu': 4,
'fri': 5,
'sat': 6
};
ranges = {
'yea': 'FullYear',
'mon': 'Month',
'day': 'Date',
'hou': 'Hours',
'min': 'Minutes',
'sec': 'Seconds'
};

function lastNext(type, range, modifier) {
var diff, day = days[range];

if (typeof day !== 'undefined') {
diff = day - date.getDay();

if (diff === 0) {
diff = 7 * modifier;
} else if (diff > 0 && type === 'last') {
diff -= 7;
} else if (diff < 0 && type === 'next') {
diff += 7;
}

date.setDate(date.getDate() + diff);
}
}

function process(val) {
var splt = val.split(' '), // Todo: Reconcile this with regex using \s, taking into account browser issues with split and regexes
type = splt[0],
range = splt[1].substring(0, 3),
typeIsNumber = /\d+/.test(type),
ago = splt[2] === 'ago',
num = (type === 'last' ? -1 : 1) * (ago ? -1 : 1);

if (typeIsNumber) {
num *= parseInt(type, 10);
}

if (ranges.hasOwnProperty(range) && !splt[1].match(/^mon(day|\.)?$/i)) {
return date['set' + ranges[range]](date['get' + ranges[range]]() + num);
}

if (range === 'wee') {
return date.setDate(date.getDate() + (num * 7));
}

if (type === 'next' || type === 'last') {
lastNext(type, range, num);
} else if (!typeIsNumber) {
return false;
}

return true;
}

times = '(years?|months?|weeks?|days?|hours?|minutes?|min|seconds?|sec' +
'|sunday|sun\\.?|monday|mon\\.?|tuesday|tue\\.?|wednesday|wed\\.?' +
'|thursday|thu\\.?|friday|fri\\.?|saturday|sat\\.?)';
regex = '([+-]?\\d+\\s' + times + '|' + '(last|next)\\s' + times + ')(\\sago)?';

match = text.match(new RegExp(regex, 'gi'));
if (!match) {
return fail;
}

for (i = 0, len = match.length; i < len; i++) {
if (!process(match[i])) {
return fail;
}
}

if (!match.every(process)) {
return false;
}

return (date.getTime() / 1000);
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"JSONStream": "^0.8.0",
"async": "~0.9.0",
"csv-write-stream": "^0.1.1",
"datejs": "^1.0.0-rc1",
"formidable": "~1.0.14",
"moment-timezone": "^0.2.1",
"qs": "^1.2.2",
Expand Down

0 comments on commit 585ee6a

Please sign in to comment.