Hadn't even considered DST, since in reality I'm only dealing with whole dates (the time portions were just thrown in as an afterthought).
Biggest hassle I have is the differing lengths of months and leap years. I went ahead an kludged together a solution that works, but I'd druther it be someone else's problem. Haven't really thoroughly debugged it but here's what I came up with on short notice. (note: I know there's an issue with the mm and qq increments when they are more than 1 year in change - probably need a div function in there somewhere).
function isLeapYear(year) {\n if (year % 4 == 0) {\n if (year % 100 == 0) {\n return (year % 400 == 0);\n } else {\n return (true);\n }\n }\n return (false);\n}\n\nfunction daysInMonth(month, year) {\n switch (month) {\n case 0: return 31; // january\n case 1: // february\n if (isLeapYear(year)) {\n return 29;\n } else {\n return 28;\n }\n case 2: return 31; // march\n case 3: return 30; // april\n case 4: return 31; // may\n case 5: return 30; // june\n case 6: return 31; // july\n case 7: return 31; // august\n case 8: return 30; // september\n case 9: return 31; // october\n case 10: return 30; // november\n case 11: return 31; // december\n }\n}\n\nfunction DateAdd(datepart, number, date) {\n var d = new Date(date);\n switch (datepart) {\n // millisecond\n case "ms":\n return new Date(Date.parse(d) + (number));\n\n // second\n case "s":\n case "ss":\n return new Date(Date.parse(d) + (number*1000));\n\n // minute\n case "n":\n case "mi":\n return new Date(Date.parse(d) + (number*1000*60));\n\n // hour\n case "hh":\n return new Date(Date.parse(d) + (number*1000*60*60));\n\n // day\n case "d":\n case "dd":\n return new Date(Date.parse(d) + (number*1000*60*60*24));\n\n // week\n case "wk":\n case "ww":\n return new Date(Date.parse(d) + (number*1000*60*60*24*7));\n\n // month\n case "m":\n case "mm":\n var i = 0;\n var maxcurr = daysInMonth(d.getMonth(), d.getFullYear());\n var mm = (d.getMonth() + number) % 12;\n if (mm < 0) mm += 12;\n var yy = d.getFullYear() + Math.floor((number + d.getMonth()) / 12);\n var maxnext = daysInMonth(mm, yy);\n if (maxnext < d.getDate()) {\n i = (maxnext - d.getDate());\n }\n if (d.getDate() == maxcurr) {\n i = (maxnext - maxcurr);\n }\n return new Date(\n d.getFullYear(),\n d.getMonth()+number,\n d.getDate()+i,\n d.getHours(),\n d.getMinutes(),\n d.getSeconds());\n\n // quarter\n case "q":\n case "qq":\n var i = 0;\n var maxcurr = daysInMonth(d.getMonth(), d.getFullYear());\n var mm = (d.getMonth() + number*3) % 12;\n if (mm < 0) mm += 12;\n var yy = d.getFullYear() + Math.floor((number*3 + d.getMonth()) / 12);\n var maxnext = daysInMonth(mm, yy);\n if (maxnext < d.getDate()) {\n i = (maxnext - d.getDate());\n }\n if (d.getDate() == maxcurr) {\n i = (maxnext - maxcurr);\n }\n return new Date(\n d.getFullYear(),\n d.getMonth()+number*3,\n d.getDate()+i,\n d.getHours(),\n d.getMinutes(),\n d.getSeconds());\n\n // year\n case "yy":\n case "yyyy":\n var i = 0;\n if (d.getMonth() == 1) {\n if (d.getDate() == 29) {\n if (!isLeapYear(d.getFullYear() + number)) {\n i = -1;\n }\n }\n if (d.getDate() == 28) {\n if (!isLeapYear(d.getFullYear())) {\n if (isLeapYear(d.getFullYear() + number)) {\n i = 1;\n }\n }\n }\n }\n return new Date(\n d.getFullYear()+number,\n d.getMonth(),\n d.getDate()+i,\n d.getHours(),\n d.getMinutes(),\n d.getSeconds());\n }\n}