From 065342b825944f3cbd4c1db5c7bf8077b5fc133c Mon Sep 17 00:00:00 2001 From: Mottie Date: Tue, 30 Sep 2014 16:20:04 -0500 Subject: [PATCH] Parsers: Update all date parsers to ensure valid dates --- js/jquery.tablesorter.js | 24 +++++++---- js/parsers/parser-date-extract.js | 55 +++++++++++++++++------- js/parsers/parser-date-iso8601.js | 7 +-- js/parsers/parser-date-month.js | 24 +++++++---- js/parsers/parser-date-two-digit-year.js | 43 ++++++++++-------- js/parsers/parser-date-weekday.js | 24 +++++++---- js/parsers/parser-date.js | 10 +++-- test.html | 2 +- 8 files changed, 120 insertions(+), 69 deletions(-) diff --git a/js/jquery.tablesorter.js b/js/jquery.tablesorter.js index 0d2d46e8..6a58e666 100644 --- a/js/jquery.tablesorter.js +++ b/js/jquery.tablesorter.js @@ -1762,7 +1762,8 @@ return (/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/).test(s); }, format: function(s, table) { - return s ? ts.formatFloat((s !== "") ? (new Date(s.replace(/-/g, "/")).getTime() || s) : "", table) : s; + var date = s ? new Date( s.replace(/-/g, "/") ) : s; + return date instanceof Date && isFinite(date) ? date.getTime() : s; }, type: "numeric" }); @@ -1799,7 +1800,8 @@ return (/^[A-Z]{3,10}\.?\s+\d{1,2},?\s+(\d{4})(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?$/i).test(s) || (/^\d{1,2}\s+[A-Z]{3,10}\s+\d{4}/i).test(s); }, format: function(s, table) { - return s ? ts.formatFloat( (new Date(s.replace(/(\S)([AP]M)$/i, "$1 $2")).getTime() || s), table) : s; + var date = s ? new Date( s.replace(/(\S)([AP]M)$/i, "$1 $2") ) : s; + return date instanceof Date && isFinite(date) ? date.getTime() : s; }, type: "numeric" }); @@ -1812,19 +1814,22 @@ }, format: function(s, table, cell, cellIndex) { if (s) { - var c = table.config, + var date, d, + c = table.config, ci = c.$headers.filter('[data-column=' + cellIndex + ']:last'), format = ci.length && ci[0].dateFormat || ts.getData( ci, ts.getColumnData( table, c.headers, cellIndex ), 'dateFormat') || c.dateFormat; - s = s.replace(/\s+/g," ").replace(/[\-.,]/g, "/"); // escaped - because JSHint in Firefox was showing it as an error + d = s.replace(/\s+/g," ").replace(/[\-.,]/g, "/"); // escaped - because JSHint in Firefox was showing it as an error if (format === "mmddyyyy") { - s = s.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$1/$2"); + d = d.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$1/$2"); } else if (format === "ddmmyyyy") { - s = s.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$2/$1"); + d = d.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$2/$1"); } else if (format === "yyyymmdd") { - s = s.replace(/(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/, "$1/$2/$3"); + d = d.replace(/(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/, "$1/$2/$3"); } + date = new Date(d); + return date instanceof Date && isFinite(date) ? date.getTime() : s; } - return s ? ts.formatFloat( (new Date(s).getTime() || s), table) : s; + return s; }, type: "numeric" }); @@ -1835,7 +1840,8 @@ return (/^(([0-2]?\d:[0-5]\d)|([0-1]?\d:[0-5]\d\s?([AP]M)))$/i).test(s); }, format: function(s, table) { - return s ? ts.formatFloat( (new Date("2000/01/01 " + s.replace(/(\S)([AP]M)$/i, "$1 $2")).getTime() || s), table) : s; + var date = s ? new Date( "2000/01/01 " + s.replace(/(\S)([AP]M)$/i, "$1 $2") ) : s; + return date instanceof Date && isFinite(date) ? date.getTime() : s; }, type: "numeric" }); diff --git a/js/parsers/parser-date-extract.js b/js/parsers/parser-date-extract.js index 3a461104..10b0651e 100644 --- a/js/parsers/parser-date-extract.js +++ b/js/parsers/parser-date-extract.js @@ -5,9 +5,20 @@ ;(function($){ "use strict"; + var regex = { + usLong : /[A-Z]{3,10}\.?\s+\d{1,2},?\s+(?:\d{4})(?:\s+\d{1,2}:\d{2}(?::\d{2})?(?:\s+[AP]M)?)?/i, + mdy : /(\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4}(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?)/i, + + dmy : /(\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4}(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?)/i, + dmyreplace : /(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, + + ymd : /(\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2}(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?)/i, + ymdreplace : /(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/ + }; + /*! extract US Long Date (ignore any other text) * e.g. "Sue's Birthday! Jun 26, 2004 7:22 AM (8# 2oz)" - * demo: http://jsfiddle.net/abkNM/2293/ + * demo: http://jsfiddle.net/Mottie/abkNM/4165/ */ $.tablesorter.addParser({ id: "extractUSLongDate", @@ -16,14 +27,19 @@ return false; }, format: function (s, table) { - var date = s.match(/[A-Z]{3,10}\.?\s+\d{1,2},?\s+(?:\d{4})(?:\s+\d{1,2}:\d{2}(?::\d{2})?(?:\s+[AP]M)?)?/i); - return date ? $.tablesorter.formatFloat((new Date(date[0]).getTime() || ''), table) || s : s; + var date, + str = s ? s.match(regex.usLong) : s; + if (str) { + date = new Date( str[0] ); + return date instanceof Date && isFinite(date) ? date.getTime() : s; + } + return s; }, type: "numeric" }); /*! extract MMDDYYYY (ignore any other text) - * demo: http://jsfiddle.net/Mottie/abkNM/2418/ + * demo: http://jsfiddle.net/Mottie/abkNM/4166/ */ $.tablesorter.addParser({ id: "extractMMDDYYYY", @@ -32,14 +48,19 @@ return false; }, format: function (s, table) { - var date = s.replace(/\s+/g," ").replace(/[\-.,]/g, "/").match(/(\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4}(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?)/i); - return date ? $.tablesorter.formatFloat((new Date(date[0]).getTime() || ''), table) || s : s; + var date, + str = s ? s.replace(/\s+/g," ").replace(/[\-.,]/g, "/").match(regex.mdy) : s; + if (str) { + date = new Date( str[0] ); + return date instanceof Date && isFinite(date) ? date.getTime() : s; + } + return s; }, type: "numeric" }); /*! extract DDMMYYYY (ignore any other text) - * demo: http://jsfiddle.net/Mottie/abkNM/2419/ + * demo: http://jsfiddle.net/Mottie/abkNM/4167/ */ $.tablesorter.addParser({ id: "extractDDMMYYYY", @@ -48,10 +69,11 @@ return false; }, format: function (s, table) { - var date = s.replace(/\s+/g," ").replace(/[\-.,]/g, "/").match(/(\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4}(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?)/i); - if (date) { - date = date[0].replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$2/$1/$3"); - return $.tablesorter.formatFloat((new Date(date).getTime() || ''), table) || s; + var date, + str = s ? s.replace(/\s+/g," ").replace(/[\-.,]/g, "/").match(regex.dmy) : s; + if (str) { + date = new Date( str[0].replace(regex.dmyreplace, "$2/$1/$3") ); + return date instanceof Date && isFinite(date) ? date.getTime() : s; } return s; }, @@ -59,7 +81,7 @@ }); /*! extract YYYYMMDD (ignore any other text) - * demo: http://jsfiddle.net/Mottie/abkNM/2420/ + * demo: http://jsfiddle.net/Mottie/abkNM/4168/ */ $.tablesorter.addParser({ id: "extractYYYYMMDD", @@ -68,10 +90,11 @@ return false; }, format: function (s, table) { - var date = s.replace(/\s+/g," ").replace(/[\-.,]/g, "/").match(/(\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2}(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?)/i); - if (date) { - date = date[0].replace(/(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/, "$2/$3/$1"); - return $.tablesorter.formatFloat((new Date(date).getTime() || ''), table) || s; + var date, + str = s ? s.replace(/\s+/g," ").replace(/[\-.,]/g, "/").match(regex.ymd) : s; + if (str) { + date = new Date( str[0].replace(regex.ymdreplace, "$2/$3/$1") ); + return date instanceof Date && isFinite(date) ? date.getTime() : s; } return s; }, diff --git a/js/parsers/parser-date-iso8601.js b/js/parsers/parser-date-iso8601.js index 0a133c32..dd747e18 100644 --- a/js/parsers/parser-date-iso8601.js +++ b/js/parsers/parser-date-iso8601.js @@ -9,13 +9,14 @@ "use strict"; var iso8601date = /^([0-9]{4})(-([0-9]{2})(-([0-9]{2})(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?$/; + $.tablesorter.addParser({ id : 'iso8601date', is : function(s) { - return s.match(iso8601date); + return s ? s.match(iso8601date) : false; }, format : function(s) { - var result = s.match(iso8601date); + var result = s ? s.match(iso8601date) : s; if (result) { var date = new Date(result[1], 0, 1); if (result[3]) { date.setMonth(result[3] - 1); } @@ -24,7 +25,7 @@ if (result[8]) { date.setMinutes(result[8]); } if (result[10]) { date.setSeconds(result[10]); } if (result[12]) { date.setMilliseconds(Number('0.' + result[12]) * 1000); } - return date; + return date.getTime(); } return s; }, diff --git a/js/parsers/parser-date-month.js b/js/parsers/parser-date-month.js index 4c725d66..4a1e158d 100644 --- a/js/parsers/parser-date-month.js +++ b/js/parsers/parser-date-month.js @@ -1,5 +1,5 @@ /*! Month parser - * Demo: http://jsfiddle.net/Mottie/abkNM/477/ + * Demo: http://jsfiddle.net/Mottie/abkNM/4169/ */ /*jshint jquery:true */ ;(function($){ @@ -18,14 +18,20 @@ return false; }, format: function(s, table) { - var j = -1, c = table.config, - n = c.ignoreCase ? s.toLocaleLowerCase() : s; - $.each(ts.dates[ 'month' + (c.ignoreCase ? 'Lower' : 'Cased') ], function(i,v){ - if (j < 0 && n.match(v)) { j = i; } - }); - // return s (original string) if there isn't a match - // (non-weekdays will sort separately and empty cells will sort as expected) - return j < 0 ? s : j; + if (s) { + var j = -1, c = table.config, + n = c.ignoreCase ? s.toLocaleLowerCase() : s; + $.each(ts.dates[ 'month' + (c.ignoreCase ? 'Lower' : 'Cased') ], function(i,v){ + if (j < 0 && n.match(v)) { + j = i; + return false; + } + }); + // return s (original string) if there isn't a match + // (non-weekdays will sort separately and empty cells will sort as expected) + return j < 0 ? s : j; + } + return s; }, type: "numeric" }); diff --git a/js/parsers/parser-date-two-digit-year.js b/js/parsers/parser-date-two-digit-year.js index 8d322bc8..d993bff8 100644 --- a/js/parsers/parser-date-two-digit-year.js +++ b/js/parsers/parser-date-two-digit-year.js @@ -1,16 +1,18 @@ /*! Two digit year parser - * Demo: http://jsfiddle.net/Mottie/abkNM/427/ + * Demo: http://mottie.github.io/tablesorter/docs/example-parsers-dates.html */ /*jshint jquery:true */ ;(function($){ "use strict"; - var ts = $.tablesorter, - // Make the date be within +/- range of the 2 digit year // so if the current year is 2020, and the 2 digit year is 80 (2080 - 2020 > 50), it becomes 1980 // if the 2 digit year is 50 (2050 - 2020 < 50), then it becomes 2050. - range = 50; + var range = 50, + + // no need to change any of the code below + ts = $.tablesorter, + now = new Date().getFullYear(); ts.dates = $.extend({}, ts.dates, { regxxxxyy: /(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{2})/, @@ -18,21 +20,26 @@ }); ts.formatDate = function(s, regex, format, table){ - var n = s - // replace separators - .replace(/\s+/g," ").replace(/[-.,]/g, "/") - // reformat xx/xx/xx to mm/dd/19yy; - .replace(regex, format), - d = new Date(n), - y = d.getFullYear(), - rng = table && table.config.dateRange || range, - now = new Date().getFullYear(); - // if date > 50 years old (set range), add 100 years - // this will work when people start using "50" and mean "2050" - while (now - y > rng) { - y += 100; + if (s) { + var y, rng, + n = s + // replace separators + .replace(/\s+/g," ").replace(/[-.,]/g, "/") + // reformat xx/xx/xx to mm/dd/19yy; + .replace(regex, format), + d = new Date(n); + if ( d instanceof Date && isFinite(d) ) { + y = d.getFullYear(); + rng = table && table.config.dateRange || range; + // if date > 50 years old (set range), add 100 years + // this will work when people start using "50" and mean "2050" + while (now - y > rng) { + y += 100; + } + return d.setFullYear(y); + } } - return d.setFullYear(y) || s; + return s; }; $.tablesorter.addParser({ diff --git a/js/parsers/parser-date-weekday.js b/js/parsers/parser-date-weekday.js index 62f4504e..3a7ce92f 100644 --- a/js/parsers/parser-date-weekday.js +++ b/js/parsers/parser-date-weekday.js @@ -1,5 +1,5 @@ /*! Weekday parser - * Demo: http://jsfiddle.net/Mottie/abkNM/477/ + * Demo: http://jsfiddle.net/Mottie/abkNM/4169/ */ /*jshint jquery:true */ ;(function($){ @@ -18,14 +18,20 @@ return false; }, format: function(s, table) { - var j = -1, c = table.config; - s = c.ignoreCase ? s.toLocaleLowerCase() : s; - $.each(ts.dates[ 'weekday' + (c.ignoreCase ? 'Lower' : 'Cased') ], function(i,v){ - if (j < 0 && s.match(v)) { j = i; } - }); - // return s (original string) if there isn't a match - // (non-weekdays will sort separately and empty cells will sort as expected) - return j < 0 ? s : j; + if (s) { + var j = -1, c = table.config; + s = c.ignoreCase ? s.toLocaleLowerCase() : s; + $.each(ts.dates[ 'weekday' + (c.ignoreCase ? 'Lower' : 'Cased') ], function(i,v){ + if (j < 0 && s.match(v)) { + j = i; + return false; + } + }); + // return s (original string) if there isn't a match + // (non-weekdays will sort separately and empty cells will sort as expected) + return j < 0 ? s : j; + } + return s; }, type: "numeric" }); diff --git a/js/parsers/parser-date.js b/js/parsers/parser-date.js index 52ece6c9..267d5598 100644 --- a/js/parsers/parser-date.js +++ b/js/parsers/parser-date.js @@ -6,7 +6,7 @@ "use strict"; /*! Sugar (http://sugarjs.com/dates#comparing_dates) - * demo: http://jsfiddle.net/Mottie/abkNM/551/ + * demo: http://jsfiddle.net/Mottie/abkNM/4163/ */ $.tablesorter.addParser({ id: "sugar", @@ -14,13 +14,14 @@ return false; }, format: function(s) { - return Date.create ? Date.create(s).getTime() || s : new Date(s).getTime() || s; + var date = Date.create ? Date.create(s) : s ? new Date(s) : s; + return date instanceof Date && isFinite(date) ? date.getTime() : s; }, type: "numeric" }); /*! Datejs (http://www.datejs.com/) - * demo: http://jsfiddle.net/Mottie/abkNM/550/ + * demo: http://jsfiddle.net/Mottie/abkNM/4164/ */ $.tablesorter.addParser({ id: "datejs", @@ -28,7 +29,8 @@ return false; }, format: function(s) { - return Date.parse && Date.parse(s) || s; + var date = Date.parse ? Date.parse(s) : s ? new Date(s) : s; + return date instanceof Date && isFinite(date) ? date.getTime() : s; }, type: "numeric" }); diff --git a/test.html b/test.html index 4417c4c9..c5d1ca9c 100644 --- a/test.html +++ b/test.html @@ -11,7 +11,7 @@ - +