Wednesday, July 29, 2009

Using javascript and regex to parse date time string

In the last year I've been parsing a lot of strings that represent dates into javascript Date objects. Somewhere along the way I decided to write some date parsing code using regular expressions.

Requirements:
We wanted to let the user entry of date/time data to be easy. This meant that formats like "2p" should parse as well as "1/21 3". In our actual solution there is usually a context date we use to fill in the information that the user leaves out, but the implementation below just uses the current time.

The code we used also takes culture into account (think reversed month and day), but I'll leave that for you to solve (unless I get requests and have time to work on it).

I should point out that we started off using DateJS, but we ran into some parsing errors where we were getting off by 1 hour parsed dates. So that was the end of that...

Hopefully I can add more description to this post later, but I want to publish this draft.

(function(method)
{
var rexp = new RegExp(
"^" +
"(?:" + // date
"(1[0-2]|0?[1-9])" + // Month
"[/-]" +
"(3[0,1]|[1,2][0-9]|0?[1-9])" + // Day
"(?:[/-]" +
"(19[7-9][0-9]|2[0-9]{3}|[0-9]{2})" + // Year
")?" +
"\\s?" +
")?" +
"(?:" + // Time
"(2[0-3]|1[0-9]|[1-9])" + // Hour
"(?:[:]" +
"([0-5][0-9])" + // Min
"(?:[:]" +
"([0-5][0-9])" + // Sec
")?" +
")?" +
"\\s?" +
"(?:([aApP])[mM]?)?" + // ampm
")?$"
);
var i = { Month: 1, Day: 2, Year: 3, Hour: 4, Min: 5, Sec: 6, AMPM: 7 };
var trim = function(s) { return s.replace(/^\s+|\s+$/g, ''); };
if (Date[method]) throw method + " already exists on the date object.";

Date[method] = function(strDate)//, optFormat)
{
strDate = trim(strDate);
var parsed = rexp.exec(strDate);
if (!strDate || !parsed) { return null; }

var ret = new Date(); // could also pass in a context date

var year = ret.getFullYear();
if (parsed[i.Year])
{
switch (parsed[i.Year].length)
{
case 4:
year = parseInt(parsed[i.Year], 10);
break;
case 2:
year = year - (year % 100) + parseInt(parsed[i.Year], 10);
break;
}
}

var month = (parsed[i.Month] ? parseInt(parsed[i.Month], 10) - 1 : ret.getMonth());
var day = (parsed[i.Day] ? parseInt(parsed[i.Day], 10) : ret.getDate());
var hour = (parsed[i.Hour] ? parseInt(parsed[i.Hour], 10) : 0);
var min = (parsed[i.Min] ? parseInt(parsed[i.Min], 10) : 0);
var sec = (parsed[i.Sec] ? parseInt(parsed[i.Sec], 10) : 0);
var ms = 0;

ret.setFullYear(year, month, day);
ret.setHours(hour, min, sec, ms);
if (!parsed[i.AMPM]) { return ret; }

if (hour > 12 && parsed[i.AMPM].toLowerCase() === 'p')
{
ret.setHours(ret.getHours() + 12);
}
else if (hour === 12 && parsed[i.AMPM].toLowerCase() === 'a')
{
ret.setHours(ret.getHours() - 12);
}
return ret;
};
})('parseDate');


You'll notice the usage in the test code below Date.parseDate('12/26/2005 8pm');
And yes I know it's a stretch to call these tests, but KISS. If you don't like parseDate as the fn name then change it in the last line above.

function test(str)
{
document.write("<tr><td>(" + str + ")</td><td>" + Date.parseDate(str) + "</td></tr>");
}
document.write("<table>");
test("1/13");
test("1/13/1999");
test("1/13/2001");
test("1/13/99");
test("1/13/01");
test("4/5/2008 2:00:06 pm");
test("11/5/2008 2:00 pm");
test("4/5/2008 2 pm");
test("4/5/2008");
test("4/5");
test("2:00:00");
test("4:00am");
test("4 am");
test("3p");
test("12:00");
test("22:59");
test("1:59");
test("12:45 a");
test("3");
test("19");
test("4p");
test("5 p");
test("12/26/2008");
test("1/2/08");
test("1/2");
test("1/03");
test("01/03");
test("01/13/2008");
test("1/2 2p");
test(" 2 ");
test(" 2 ");
test("01-13-2008");
test("1-2 2p");
test("FAIL CASES:");
test("");
test(" ");
test("24");
test("24 pm");
test("36:00");
test("1:60");
test("13/02");
test("2008/13/02");
document.write("</table>");


Here's the output:

(1/13)Tue Jan 13 2009 00:00:00 GMT-0500 (Eastern Standard Time)
(1/13/1999)Wed Jan 13 1999 00:00:00 GMT-0500 (Eastern Standard Time)
(1/13/2001)Sat Jan 13 2001 00:00:00 GMT-0500 (Eastern Standard Time)
(1/13/99)Tue Jan 13 2099 00:00:00 GMT-0500 (Eastern Standard Time)
(1/13/01)Sat Jan 13 2001 00:00:00 GMT-0500 (Eastern Standard Time)
(4/5/2008 2:00:06 pm)Sat Apr 05 2008 14:00:06 GMT-0400 (Eastern Daylight Time)
(11/5/2008 2:00 pm)Wed Nov 05 2008 14:00:00 GMT-0500 (Eastern Standard Time)
(4/5/2008 2 pm)Sat Apr 05 2008 14:00:00 GMT-0400 (Eastern Daylight Time)
(4/5/2008)Sat Apr 05 2008 00:00:00 GMT-0400 (Eastern Daylight Time)
(4/5)Sun Apr 05 2009 00:00:00 GMT-0400 (Eastern Daylight Time)
(2:00:00)Wed Jul 29 2009 02:00:00 GMT-0400 (Eastern Daylight Time)
(4:00am)Wed Jul 29 2009 04:00:00 GMT-0400 (Eastern Daylight Time)
(4 am)Wed Jul 29 2009 04:00:00 GMT-0400 (Eastern Daylight Time)
(3p)Wed Jul 29 2009 15:00:00 GMT-0400 (Eastern Daylight Time)
(12:00)Wed Jul 29 2009 12:00:00 GMT-0400 (Eastern Daylight Time)
(22:59)Wed Jul 29 2009 22:59:00 GMT-0400 (Eastern Daylight Time)
(1:59)Wed Jul 29 2009 01:59:00 GMT-0400 (Eastern Daylight Time)
(12:45 a)Wed Jul 29 2009 00:45:00 GMT-0400 (Eastern Daylight Time)
(3)Wed Jul 29 2009 03:00:00 GMT-0400 (Eastern Daylight Time)
(19)Wed Jul 29 2009 19:00:00 GMT-0400 (Eastern Daylight Time)
(4p)Wed Jul 29 2009 16:00:00 GMT-0400 (Eastern Daylight Time)
(5 p)Wed Jul 29 2009 17:00:00 GMT-0400 (Eastern Daylight Time)
(12/26/2008)Fri Dec 26 2008 00:00:00 GMT-0500 (Eastern Standard Time)
(1/2/08)Wed Jan 02 2008 00:00:00 GMT-0500 (Eastern Standard Time)
(1/2)Fri Jan 02 2009 00:00:00 GMT-0500 (Eastern Standard Time)
(1/03)Sat Jan 03 2009 00:00:00 GMT-0500 (Eastern Standard Time)
(01/03)Sat Jan 03 2009 00:00:00 GMT-0500 (Eastern Standard Time)
(01/13/2008)Sun Jan 13 2008 00:00:00 GMT-0500 (Eastern Standard Time)
(1/2 2p)Fri Jan 02 2009 14:00:00 GMT-0500 (Eastern Standard Time)
( 2 )Wed Jul 29 2009 02:00:00 GMT-0400 (Eastern Daylight Time)
( 2 )Wed Jul 29 2009 02:00:00 GMT-0400 (Eastern Daylight Time)
(01-13-2008)Sun Jan 13 2008 00:00:00 GMT-0500 (Eastern Standard Time)
(1-2 2p)Fri Jan 02 2009 14:00:00 GMT-0500 (Eastern Standard Time)
(FAIL CASES:)null
()null
( )null
(24)null
(24 pm)null
(36:00)null
(1:60)null
(13/02)null
(2008/13/02)null

0 comments:

Post a Comment