/* © 2011 by Jerry Sievert Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ (function () { // constants var monthsAbbr = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]; var monthsFull = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ]; var daysAbbr = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ]; var daysFull = [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ]; var dayNames = { 'su': 0, 'sun': 0, 'sunday': 0, 'mo': 1, 'mon': 1, 'monday': 1, 'tu': 2, 'tue': 2, 'tuesday': 2, 'we': 3, 'wed': 3, 'wednesday': 3, 'th': 4, 'thu': 4, 'thursday': 4, 'fr': 5, 'fri': 5, 'friday': 5, 'sa': 6, 'sat': 6, 'saturday': 6 }; var monthsAll = monthsFull.concat(monthsAbbr); var daysAll = [ 'su', 'sun', 'sunday', 'mo', 'mon', 'monday', 'tu', 'tue', 'tuesday', 'we', 'wed', 'wednesday', 'th', 'thu', 'thursday', 'fr', 'fri', 'friday', 'sa', 'sat', 'saturday' ]; var monthNames = { 'jan': 0, 'january': 0, 'feb': 1, 'february': 1, 'mar': 2, 'march': 2, 'apr': 3, 'april': 3, 'may': 4, 'jun': 5, 'june': 5, 'jul': 6, 'july': 6, 'aug': 7, 'august': 7, 'sep': 8, 'september': 8, 'oct': 9, 'october': 9, 'nov': 10, 'november': 10, 'dec': 11, 'december': 11 }; var daysInMonth = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]; // private helper functions /** @ignore */ function pad(str, length) { str = String(str); while (str.length < length) { str = '0' + str; } return str; } var isInteger = function (str) { if (str.match(/^(\d+)$/)) { return true; } return false; }; var getInt = function (str, i, minlength, maxlength) { for (var x = maxlength; x >= minlength; x--) { var token = str.substring(i, i + x); if (token.length < minlength) { return null; } if (isInteger(token)) { return token; } } return null; }; // static class methods var origParse = Date.parse; // ------------------------------------------------------------------ // getDateFromFormat( date_string , format_string ) // // This function takes a date string and a format string. It matches // If the date string matches the format string, it returns the // getTime() of the date. If it does not match, it returns NaN. // Original Author: Matt Kruse // WWW: http://www.mattkruse.com/ // Adapted from: http://www.mattkruse.com/javascript/date/source.html // ------------------------------------------------------------------ var getDateFromFormat = function (val, format) { val = val + ""; format = format + ""; var iVal = 0; var iFormat = 0; var c = ""; var token = ""; var token2 = ""; var x, y; var now = new Date(); var year = now.getYear(); var month = now.getMonth() + 1; var date = 1; var hh = 0; var mm = 0; var ss = 0; var ampm = ""; while (iFormat < format.length) { // Get next token from format string c = format.charAt(iFormat); token = ""; while ((format.charAt(iFormat) === c) && (iFormat < format.length)) { token += format.charAt(iFormat++); } // Extract contents of value based on format token if (token === "yyyy" || token === "yy" || token === "y") { if (token === "yyyy") { x = 4; y = 4; } if (token === "yy") { x = 2; y = 2; } if (token === "y") { x = 2; y = 4; } year = getInt(val, iVal, x, y); if (year === null) { return NaN; } iVal += year.length; if (year.length === 2) { if (year > 70) { year = 1900 + (year - 0); } else { year = 2000 + (year - 0); } } } else if (token === "MMM" || token === "NNN") { month = 0; for (var i = 0; i < monthsAll.length; i++) { var monthName = monthsAll[i]; if (val.substring(iVal, iVal + monthName.length).toLowerCase() === monthName.toLowerCase()) { if (token === "MMM" || (token === "NNN" && i > 11)) { month = i + 1; if (month > 12) { month -= 12; } iVal += monthName.length; break; } } } if ((month < 1) || (month > 12)) { return NaN; } } else if (token === "EE" || token === "E") { for (var n = 0; n < daysAll.length; n++) { var dayName = daysAll[n]; if (val.substring(iVal, iVal + dayName.length).toLowerCase() === dayName.toLowerCase()) { iVal += dayName.length; break; } } } else if (token === "MM" || token === "M") { month = getInt(val, iVal, token.length, 2); if (month === null || (month < 1) || (month > 12)) { return NaN; } iVal += month.length; } else if (token === "dd" || token === "d") { date = getInt(val, iVal, token.length, 2); if (date === null || (date < 1) || (date > 31)) { return NaN; } iVal += date.length; } else if (token === "hh" || token === "h") { hh = getInt(val, iVal, token.length, 2); if (hh === null || (hh < 1) || (hh > 12)) { return NaN; } iVal += hh.length; } else if (token === "HH" || token === "H") { hh = getInt(val, iVal, token.length, 2); if (hh === null || (hh < 0) || (hh > 23)) { return NaN; } iVal += hh.length; } else if (token === "KK" || token === "K") { hh = getInt(val, iVal, token.length, 2); if (hh === null || (hh < 0) || (hh > 11)) { return NaN; } iVal += hh.length; } else if (token === "kk" || token === "k") { hh = getInt(val, iVal, token.length, 2); if (hh === null || (hh < 1) || (hh > 24)) { return NaN; } iVal += hh.length; hh--; } else if (token === "mm" || token === "m") { mm = getInt(val, iVal, token.length, 2); if (mm === null || (mm < 0) || (mm > 59)) { return NaN; } iVal += mm.length; } else if (token === "ss" || token === "s") { ss = getInt(val, iVal, token.length, 2); if (ss === null || (ss < 0) || (ss > 59)) { return NaN; } iVal += ss.length; } else if (token === "a") { if (val.substring(iVal, iVal + 2).toLowerCase() === "am") { ampm = "AM"; } else if (val.substring(iVal, iVal + 2).toLowerCase() === "pm") { ampm = "PM"; } else { return NaN; } iVal += 2; } else { if (val.substring(iVal, iVal + token.length) !== token) { return NaN; } else { iVal += token.length; } } } // If there are any trailing characters left in the value, it doesn't match if (iVal !== val.length) { return NaN; } // Is date valid for month? if (month === 2) { // Check for leap year if (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)) { // leap year if (date > 29) { return NaN; } } else { if (date > 28) { return NaN; } } } if ((month === 4) || (month === 6) || (month === 9) || (month === 11)) { if (date > 30) { return NaN; } } // Correct hours value if (hh < 12 && ampm === "PM") { hh = hh - 0 + 12; } else if (hh > 11 && ampm === "AM") { hh -= 12; } var newdate = new Date(year, month - 1, date, hh, mm, ss); return newdate.getTime(); }; /** @ignore */ Date.parse = function (date, format) { if (format) { return getDateFromFormat(date, format); } var timestamp = origParse(date), minutesOffset = 0, match; if (isNaN(timestamp) && (match = /^(\d{4}|[+\-]\d{6})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3,}))?)?(?:(Z)|([+\-])(\d{2})(?::?(\d{2}))?))?/.exec(date))) { if (match[8] !== 'Z') { minutesOffset = +match[10] * 60 + (+match[11]); if (match[9] === '+') { minutesOffset = 0 - minutesOffset; } } match[7] = match[7] || '000'; timestamp = Date.UTC(+match[1], +match[2] - 1, +match[3], +match[4], +match[5] + minutesOffset, +match[6], +match[7].substr(0, 3)); } return timestamp; }; function polyfill(name, func) { if (Date.prototype[name] === undefined) { Date.prototype[name] = func; } } /** Returns new instance of Date object with the date set to today and the time set to midnight @returns {Date} Today's Date @function */ Date.today = function () { return new Date().clearTime(); }; /** Returns new instance of Date object with the date set to today and the time set to midnight in UTC @returns {Date} Today's Date in UTC @function */ Date.UTCtoday = function () { return new Date().clearUTCTime(); }; /** Returns new instance of Date object with the date set to tomorrow and the time set to midnight @returns {Date} Tomorrow's Date @function */ Date.tomorrow = function () { return Date.today().add({days: 1}); }; /** Returns new instance of Date object with the date set to tomorrow and the time set to midnight in UTC @returns {Date} Tomorrow's Date in UTC @function */ Date.UTCtomorrow = function () { return Date.UTCtoday().add({days: 1}); }; /** Returns new instance of Date object with the date set to yesterday and the time set to midnight @returns {Date} Yesterday's Date @function */ Date.yesterday = function () { return Date.today().add({days: -1}); }; /** Returns new instance of Date object with the date set to yesterday and the time set to midnight in UTC @returns {Date} Yesterday's Date in UTC @function */ Date.UTCyesterday = function () { return Date.UTCtoday().add({days: -1}); }; Date.validateDay = function (day, year, month) { var date = new Date(year, month, day); return (date.getFullYear() === year && date.getMonth() === month && date.getDate() === day); }; Date.validateYear = function (year) { return (year >= 0 && year <= 9999); }; Date.validateSecond = function (second) { return (second >= 0 && second < 60); }; Date.validateMonth = function (month) { return (month >= 0 && month < 12); }; Date.validateMinute = function (minute) { return (minute >= 0 && minute < 60); }; Date.validateMillisecond = function (milli) { return (milli >= 0 && milli < 1000); }; Date.validateHour = function (hour) { return (hour >= 0 && hour < 24); }; Date.compare = function (date1, date2) { if (date1.valueOf() < date2.valueOf()) { return -1; } else if (date1.valueOf() > date2.valueOf()) { return 1; } return 0; }; Date.equals = function (date1, date2) { return date1.valueOf() === date2.valueOf(); }; Date.getDayNumberFromName = function (name) { return dayNames[name.toLowerCase()]; }; Date.getMonthNumberFromName = function (name) { return monthNames[name.toLowerCase()]; }; Date.isLeapYear = function (year) { return (new Date(year, 1, 29).getDate() === 29); }; Date.getDaysInMonth = function (year, month) { if (month === 1) { return Date.isLeapYear(year) ? 29 : 28; } return daysInMonth[month]; }; polyfill('getMonthAbbr', function () { return monthsAbbr[this.getMonth()]; }); polyfill('getMonthName', function () { return monthsFull[this.getMonth()]; }); polyfill('getUTCOffset', function () { var tz = pad(Math.abs(this.getTimezoneOffset() / 0.6), 4); if (this.getTimezoneOffset() > 0) { tz = '-' + tz; } return tz; }); polyfill('toCLFString', function () { return pad(this.getDate(), 2) + '/' + this.getMonthAbbr() + '/' + this.getFullYear() + ':' + pad(this.getHours(), 2) + ':' + pad(this.getMinutes(), 2) + ':' + pad(this.getSeconds(), 2) + ' ' + this.getUTCOffset(); }); polyfill('toYMD', function (separator) { separator = typeof separator === 'undefined' ? '-' : separator; return this.getFullYear() + separator + pad(this.getMonth() + 1, 2) + separator + pad(this.getDate(), 2); }); polyfill('toDBString', function () { return this.getUTCFullYear() + '-' + pad(this.getUTCMonth() + 1, 2) + '-' + pad(this.getUTCDate(), 2) + ' ' + pad(this.getUTCHours(), 2) + ':' + pad(this.getUTCMinutes(), 2) + ':' + pad(this.getUTCSeconds(), 2); }); polyfill('clearTime', function () { this.setHours(0); this.setMinutes(0); this.setSeconds(0); this.setMilliseconds(0); return this; }); polyfill('clearUTCTime', function () { this.setUTCHours(0); this.setUTCMinutes(0); this.setUTCSeconds(0); this.setUTCMilliseconds(0); return this; }); polyfill('add', function (obj) { if (obj.milliseconds !== undefined) { this.setMilliseconds(this.getMilliseconds() + obj.milliseconds); } if (obj.seconds !== undefined) { this.setSeconds(this.getSeconds() + obj.seconds); } if (obj.minutes !== undefined) { this.setMinutes(this.getMinutes() + obj.minutes); } if (obj.hours !== undefined) { this.setHours(this.getHours() + obj.hours); } if (obj.days !== undefined) { this.setDate(this.getDate() + obj.days); } if (obj.weeks !== undefined) { this.setDate(this.getDate() + (obj.weeks * 7)); } if (obj.months !== undefined) { this.setMonth(this.getMonth() + obj.months); } if (obj.years !== undefined) { this.setFullYear(this.getFullYear() + obj.years); } return this; }); polyfill('addMilliseconds', function (milliseconds) { return this.add({ milliseconds: milliseconds }); }); polyfill('addSeconds', function (seconds) { return this.add({ seconds: seconds }); }); polyfill('addMinutes', function (minutes) { return this.add({ minutes: minutes }); }); polyfill('addHours', function (hours) { return this.add({ hours: hours }); }); polyfill('addDays', function (days) { return this.add({ days: days }); }); polyfill('addWeeks', function (weeks) { return this.add({ days: (weeks * 7) }); }); polyfill('addMonths', function (months) { return this.add({ months: months }); }); polyfill('addYears', function (years) { return this.add({ years: years }); }); polyfill('setTimeToNow', function () { var n = new Date(); this.setMilliseconds(n.getMilliseconds()); this.setSeconds(n.getSeconds()); this.setMinutes(n.getMinutes()); this.setHours(n.getHours()); }); polyfill('clone', function () { return new Date(this.valueOf()); }); polyfill('between', function (start, end) { return (this.valueOf() >= start.valueOf() && this.valueOf() <= end.valueOf()); }); polyfill('compareTo', function (date) { return Date.compare(this, date); }); polyfill('equals', function (date) { return Date.equals(this, date); }); polyfill('isAfter', function (date) { date = date ? date : new Date(); return (this.compareTo(date) > 0); }); polyfill('isBefore', function (date) { date = date ? date : new Date(); return (this.compareTo(date) < 0); }); polyfill('getDaysBetween', function (date) { return ((date.clone().valueOf() - this.valueOf()) / 86400000) | 0; }); polyfill('getHoursBetween', function (date) { return ((date.clone().valueOf() - this.valueOf()) / 3600000) | 0; }); polyfill('getMinutesBetween', function (date) { return ((date.clone().valueOf() - this.valueOf()) / 60000) | 0; }); polyfill('getSecondsBetween', function (date) { return ((date.clone().valueOf() - this.valueOf()) / 1000) | 0; }); polyfill('getMillisecondsBetween', function (date) { return ((date.clone().valueOf() - this.valueOf())) | 0; }); polyfill('getOrdinalNumber', function () { return Math.ceil((this.clone().clearTime() - new Date(this.getFullYear(), 0, 1)) / 86400000) + 1; }); polyfill('toFormat', function (format) { return toFormat(format, getReplaceMap(this)); }); polyfill('toUTCFormat', function (format) { return toFormat(format, getUTCReplaceMap(this)); }); var toFormat = function(format, replaceMap) { var f = [ format ], i, l, s; var replace = function (str, rep) { var i = 0, l = f.length, j, ll, t, n = []; for (; i < l; i++) { if (typeof f[i] == 'string') { t = f[i].split(str); for (j = 0, ll = t.length - 1; j < ll; j++) { n.push(t[j]); n.push([rep]); // replacement pushed as non-string } n.push(t[ll]); } else { // must be a replacement, don't process, just push n.push(f[i]); } } f = n; }; for (i in replaceMap) { replace(i, replaceMap[i]); } s = ''; for (i = 0, l = f.length; i < l; i++) s += typeof f[i] == 'string' ? f[i] : f[i][0]; return f.join(''); }; var getReplaceMap = function(date) { var hours = (date.getHours() % 12) ? date.getHours() % 12 : 12; return { 'YYYY': date.getFullYear(), 'YY': String(date.getFullYear()).slice(-2), 'MMMM': monthsFull[date.getMonth()], 'MMM': monthsAbbr[date.getMonth()], 'MM': pad(date.getMonth() + 1, 2), 'MI': pad(date.getMinutes(), 2), 'M': date.getMonth() + 1, 'DDDD': daysFull[date.getDay()], 'DDD': daysAbbr[date.getDay()], 'DD': pad(date.getDate(), 2), 'D': date.getDate(), 'HH24': pad(date.getHours(), 2), 'HH': pad(hours, 2), 'H': hours, 'SS': pad(date.getSeconds(), 2), 'PP': (date.getHours() >= 12) ? 'PM' : 'AM', 'P': (date.getHours() >= 12) ? 'pm' : 'am', 'LL': pad(date.getMilliseconds(), 3) }; }; var getUTCReplaceMap = function(date) { var hours = (date.getUTCHours() % 12) ? date.getUTCHours() % 12 : 12; return { 'YYYY': date.getUTCFullYear(), 'YY': String(date.getUTCFullYear()).slice(-2), 'MMMM': monthsFull[date.getUTCMonth()], 'MMM': monthsAbbr[date.getUTCMonth()], 'MM': pad(date.getUTCMonth() + 1, 2), 'MI': pad(date.getUTCMinutes(), 2), 'M': date.getUTCMonth() + 1, 'DDDD': daysFull[date.getUTCDay()], 'DDD': daysAbbr[date.getUTCDay()], 'DD': pad(date.getUTCDate(), 2), 'D': date.getUTCDate(), 'HH24': pad(date.getUTCHours(), 2), 'HH': pad(hours, 2), 'H': hours, 'SS': pad(date.getUTCSeconds(), 2), 'PP': (date.getUTCHours() >= 12) ? 'PM' : 'AM', 'P': (date.getUTCHours() >= 12) ? 'pm' : 'am', 'LL': pad(date.getUTCMilliseconds(), 3) }; }; }());