/** @preserve Software from "XDate v0.8", a wrapper around JavaScript's Date object, is Copyright (c) 2010 C. F., Wong and is provided under the following license: -- 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. -- */ /** * XDate v0.8 * Docs & Licensing: http://arshaw.com/xdate/ */ /* * Internal Architecture * --------------------- * An XDate wraps a native Date. The native Date is stored in the '0' property of the object. * UTC-mode is determined by whether the internal native Date's toString method is set to * Date.prototype.toUTCString (see getUTCMode). * */ var XDate = (function(Date, Math, Array, undefined) { /** @const */ var FULLYEAR = 0; /** @const */ var MONTH = 1; /** @const */ var DATE = 2; /** @const */ var HOURS = 3; /** @const */ var MINUTES = 4; /** @const */ var SECONDS = 5; /** @const */ var MILLISECONDS = 6; /** @const */ var DAY = 7; /** @const */ var YEAR = 8; /** @const */ var WEEK = 9; /** @const */ var DAY_MS = 86400000; var ISO_FORMAT_STRING = "yyyy-MM-dd'T'HH:mm:ss(.fff)"; var ISO_FORMAT_STRING_TZ = ISO_FORMAT_STRING + "zzz"; var methodSubjects = [ 'FullYear', // 0 'Month', // 1 'Date', // 2 'Hours', // 3 'Minutes', // 4 'Seconds', // 5 'Milliseconds', // 6 'Day', // 7 'Year' // 8 ]; var subjectPlurals = [ 'Years', // 0 'Months', // 1 'Days' // 2 ]; var unitsWithin = [ 12, // months in year 31, // days in month (sort of) 24, // hours in day 60, // minutes in hour 60, // seconds in minute 1000, // milliseconds in second 1 // ]; var formatStringRE = new RegExp( "(([a-zA-Z])\\2*)|" + // 1, 2 "(\\(" + "(('.*?'|\\(.*?\\)|.)*?)" + "\\))|" + // 3, 4, 5 (allows for 1 level of inner quotes or parens) "('(.*?)')" // 6, 7 ); var UTC = Date.UTC; var toUTCString = Date.prototype.toUTCString; var proto = XDate.prototype; // This makes an XDate look pretty in Firebug and Web Inspector. // It makes an XDate seem array-like, and displays [ .toString() ] proto.length = 1; proto.splice = Array.prototype.splice; /* Constructor ---------------------------------------------------------------------------------*/ // TODO: in future, I'd change signature for the constructor regarding the `true` utc-mode param. ~ashaw // I'd move the boolean to be the *first* argument. Still optional. Seems cleaner. // I'd remove it from the `xdate`, `nativeDate`, and `milliseconds` constructors. // (because you can simply call .setUTCMode(true) after) // And I'd only leave it for the y/m/d/h/m/s/m and `dateString` constructors // (because those are the only constructors that need it for DST-gap data-loss reasons) // Should do this for 1.0 function XDate() { return init( (this instanceof XDate) ? this : new XDate(), arguments ); } function init(xdate, args) { var len = args.length; var utcMode; if (isBoolean(args[len-1])) { utcMode = args[--len]; args = slice(args, 0, len); } if (!len) { xdate[0] = new Date(); } else if (len == 1) { var arg = args[0]; if (arg instanceof Date || isNumber(arg)) { xdate[0] = new Date(+arg); } else if (arg instanceof XDate) { xdate[0] = _clone(arg); } else if (isString(arg)) { xdate[0] = new Date(0); xdate = parse(arg, utcMode || false, xdate); } } else { xdate[0] = new Date(UTC.apply(Date, args)); if (!utcMode) { xdate[0] = coerceToLocal(xdate[0]); } } if (isBoolean(utcMode)) { setUTCMode(xdate, utcMode); } return xdate; } /* UTC Mode Methods ---------------------------------------------------------------------------------*/ proto.getUTCMode = methodize(getUTCMode); function getUTCMode(xdate) { return xdate[0].toString === toUTCString; }; proto.setUTCMode = methodize(setUTCMode); function setUTCMode(xdate, utcMode, doCoercion) { if (utcMode) { if (!getUTCMode(xdate)) { if (doCoercion) { xdate[0] = coerceToUTC(xdate[0]); } xdate[0].toString = toUTCString; } }else{ if (getUTCMode(xdate)) { if (doCoercion) { xdate[0] = coerceToLocal(xdate[0]); }else{ xdate[0] = new Date(+xdate[0]); } // toString will have been cleared } } return xdate; // for chaining } proto.getTimezoneOffset = function() { if (getUTCMode(this)) { return 0; }else{ return this[0].getTimezoneOffset(); } }; /* get / set / add / diff Methods (except for week-related) ---------------------------------------------------------------------------------*/ each(methodSubjects, function(subject, fieldIndex) { proto['get' + subject] = function() { return _getField(this[0], getUTCMode(this), fieldIndex); }; if (fieldIndex != YEAR) { // because there is no getUTCYear proto['getUTC' + subject] = function() { return _getField(this[0], true, fieldIndex); }; } if (fieldIndex != DAY) { // because there is no setDay or setUTCDay // and the add* and diff* methods use DATE instead proto['set' + subject] = function(value) { _set(this, fieldIndex, value, arguments, getUTCMode(this)); return this; // for chaining }; if (fieldIndex != YEAR) { // because there is no setUTCYear // and the add* and diff* methods use FULLYEAR instead proto['setUTC' + subject] = function(value) { _set(this, fieldIndex, value, arguments, true); return this; // for chaining }; proto['add' + (subjectPlurals[fieldIndex] || subject)] = function(delta, preventOverflow) { _add(this, fieldIndex, delta, preventOverflow); return this; // for chaining }; proto['diff' + (subjectPlurals[fieldIndex] || subject)] = function(otherDate) { return _diff(this, otherDate, fieldIndex); }; } } }); function _set(xdate, fieldIndex, value, args, useUTC) { var getField = curry(_getField, xdate[0], useUTC); var setField = curry(_setField, xdate[0], useUTC); var expectedMonth; var preventOverflow = false; if (args.length == 2 && isBoolean(args[1])) { preventOverflow = args[1]; args = [ value ]; } if (fieldIndex == MONTH) { expectedMonth = (value % 12 + 12) % 12; }else{ expectedMonth = getField(MONTH); } setField(fieldIndex, args); if (preventOverflow && getField(MONTH) != expectedMonth) { setField(MONTH, [ getField(MONTH) - 1 ]); setField(DATE, [ getDaysInMonth(getField(FULLYEAR), getField(MONTH)) ]); } } function _add(xdate, fieldIndex, delta, preventOverflow) { delta = Number(delta); var intDelta = Math.floor(delta); xdate['set' + methodSubjects[fieldIndex]]( xdate['get' + methodSubjects[fieldIndex]]() + intDelta, preventOverflow || false ); if (intDelta != delta && fieldIndex < MILLISECONDS) { _add(xdate, fieldIndex+1, (delta-intDelta)*unitsWithin[fieldIndex], preventOverflow); } } function _diff(xdate1, xdate2, fieldIndex) { // fieldIndex=FULLYEAR is for years, fieldIndex=DATE is for days xdate1 = xdate1.clone().setUTCMode(true, true); xdate2 = XDate(xdate2).setUTCMode(true, true); var v = 0; if (fieldIndex == FULLYEAR || fieldIndex == MONTH) { for (var i=MILLISECONDS, methodName; i>=fieldIndex; i--) { v /= unitsWithin[i]; v += _getField(xdate2, false, i) - _getField(xdate1, false, i); } if (fieldIndex == MONTH) { v += (xdate2.getFullYear() - xdate1.getFullYear()) * 12; } } else if (fieldIndex == DATE) { var clear1 = xdate1.toDate().setUTCHours(0, 0, 0, 0); // returns an ms value var clear2 = xdate2.toDate().setUTCHours(0, 0, 0, 0); // returns an ms value v = Math.round((clear2 - clear1) / DAY_MS) + ((xdate2 - clear2) - (xdate1 - clear1)) / DAY_MS; } else { v = (xdate2 - xdate1) / [ 3600000, // milliseconds in hour 60000, // milliseconds in minute 1000, // milliseconds in second 1 // ][fieldIndex - 3]; } return v; } /* Week Methods ---------------------------------------------------------------------------------*/ proto.getWeek = function() { return _getWeek(curry(_getField, this, false)); }; proto.getUTCWeek = function() { return _getWeek(curry(_getField, this, true)); }; proto.setWeek = function(n, year) { _setWeek(this, n, year, false); return this; // for chaining }; proto.setUTCWeek = function(n, year) { _setWeek(this, n, year, true); return this; // for chaining }; proto.addWeeks = function(delta) { return this.addDays(Number(delta) * 7); }; proto.diffWeeks = function(otherDate) { return _diff(this, otherDate, DATE) / 7; }; function _getWeek(getField) { return getWeek(getField(FULLYEAR), getField(MONTH), getField(DATE)); } function getWeek(year, month, date) { var d = new Date(UTC(year, month, date)); var week1 = getWeek1( getWeekYear(year, month, date) ); return Math.floor(Math.round((d - week1) / DAY_MS) / 7) + 1; } function getWeekYear(year, month, date) { // get the year that the date's week # belongs to var d = new Date(UTC(year, month, date)); if (d < getWeek1(year)) { return year - 1; } else if (d >= getWeek1(year + 1)) { return year + 1; } return year; } function getWeek1(year) { // returns Date of first week of year, in UTC var d = new Date(UTC(year, 0, 4)); d.setUTCDate(d.getUTCDate() - (d.getUTCDay() + 6) % 7); // make it Monday of the week return d; } function _setWeek(xdate, n, year, useUTC) { var getField = curry(_getField, xdate, useUTC); var setField = curry(_setField, xdate, useUTC); if (year === undefined) { year = getWeekYear( getField(FULLYEAR), getField(MONTH), getField(DATE) ); } var week1 = getWeek1(year); if (!useUTC) { week1 = coerceToLocal(week1); } xdate.setTime(+week1); setField(DATE, [ getField(DATE) + (n-1) * 7 ]); // would have used xdate.addUTCWeeks :( // n-1 because n is 1-based } /* Parsing ---------------------------------------------------------------------------------*/ XDate.parsers = [ parseISO ]; XDate.parse = function(str) { return +XDate(''+str); }; function parse(str, utcMode, xdate) { var parsers = XDate.parsers; var i = 0; var res; for (; i=0; i--) { uniqueness.push(getField(i)); } } return getField(fieldIndex); } return _format(xdate, formatString, getFieldAndTrace, getSetting, useUTC); } function _format(xdate, formatString, getField, getSetting, useUTC) { var m; var subout; var out = ''; while (m = formatString.match(formatStringRE)) { out += formatString.substr(0, m.index); if (m[1]) { // consecutive alphabetic characters out += processTokenString(xdate, m[1], getField, getSetting, useUTC); } else if (m[3]) { // parenthesis subout = _format(xdate, m[4], getField, getSetting, useUTC); if (parseInt(subout.replace(/\D/g, ''), 10)) { // if any of the numbers are non-zero. or no numbers at all out += subout; } } else { // else if (m[6]) { // single quotes out += m[7] || "'"; // if inner is blank, meaning 2 consecutive quotes = literal single quote } formatString = formatString.substr(m.index + m[0].length); } return out + formatString; } function processTokenString(xdate, tokenString, getField, getSetting, useUTC) { var end = tokenString.length; var replacement; var out = ''; while (end > 0) { replacement = getTokenReplacement(xdate, tokenString.substr(0, end), getField, getSetting, useUTC); if (replacement !== undefined) { out += replacement; tokenString = tokenString.substr(end); end = tokenString.length; }else{ end--; } } return out + tokenString; } function getTokenReplacement(xdate, token, getField, getSetting, useUTC) { var formatter = XDate.formatters[token]; if (isString(formatter)) { return _format(xdate, formatter, getField, getSetting, useUTC); } else if (isFunction(formatter)) { return formatter(xdate, useUTC || false, getSetting); } switch (token) { case 'fff' : return zeroPad(getField(MILLISECONDS), 3); case 's' : return getField(SECONDS); case 'ss' : return zeroPad(getField(SECONDS)); case 'm' : return getField(MINUTES); case 'mm' : return zeroPad(getField(MINUTES)); case 'h' : return getField(HOURS) % 12 || 12; case 'hh' : return zeroPad(getField(HOURS) % 12 || 12); case 'H' : return getField(HOURS); case 'HH' : return zeroPad(getField(HOURS)); case 'd' : return getField(DATE); case 'dd' : return zeroPad(getField(DATE)); case 'ddd' : return getSetting('dayNamesShort')[getField(DAY)] || ''; case 'dddd' : return getSetting('dayNames')[getField(DAY)] || ''; case 'M' : return getField(MONTH) + 1; case 'MM' : return zeroPad(getField(MONTH) + 1); case 'MMM' : return getSetting('monthNamesShort')[getField(MONTH)] || ''; case 'MMMM' : return getSetting('monthNames')[getField(MONTH)] || ''; case 'yy' : return (getField(FULLYEAR)+'').substring(2); case 'yyyy' : return getField(FULLYEAR); case 't' : return _getDesignator(getField, getSetting).substr(0, 1).toLowerCase(); case 'tt' : return _getDesignator(getField, getSetting).toLowerCase(); case 'T' : return _getDesignator(getField, getSetting).substr(0, 1); case 'TT' : return _getDesignator(getField, getSetting); case 'z' : case 'zz' : case 'zzz' : return useUTC ? 'Z' : _getTZString(xdate, token); case 'w' : return _getWeek(getField); case 'ww' : return zeroPad(_getWeek(getField)); case 'S' : var d = getField(DATE); if (d > 10 && d < 20) return 'th'; return ['st', 'nd', 'rd'][d % 10 - 1] || 'th'; } } function _getTZString(xdate, token) { var tzo = xdate.getTimezoneOffset(); var sign = tzo < 0 ? '+' : '-'; var hours = Math.floor(Math.abs(tzo) / 60); var minutes = Math.abs(tzo) % 60; var out = hours; if (token == 'zz') { out = zeroPad(hours); } else if (token == 'zzz') { out = zeroPad(hours) + ':' + zeroPad(minutes); } return sign + out; } function _getDesignator(getField, getSetting) { return getField(HOURS) < 12 ? getSetting('amDesignator') : getSetting('pmDesignator'); } /* Misc Methods ---------------------------------------------------------------------------------*/ each( [ // other getters 'getTime', 'valueOf', 'toDateString', 'toTimeString', 'toLocaleString', 'toLocaleDateString', 'toLocaleTimeString', 'toJSON' ], function(methodName) { proto[methodName] = function() { return this[0][methodName](); }; } ); proto.setTime = function(t) { this[0].setTime(t); return this; // for chaining }; proto.valid = methodize(valid); function valid(xdate) { return !isNaN(+xdate[0]); } proto.clone = function() { return new XDate(this); }; proto.clearTime = function() { return this.setHours(0, 0, 0, 0); // will return an XDate for chaining }; proto.toDate = function() { return new Date(+this[0]); }; /* Misc Class Methods ---------------------------------------------------------------------------------*/ XDate.now = function() { return +new Date(); }; XDate.today = function() { return new XDate().clearTime(); }; XDate.UTC = UTC; XDate.getDaysInMonth = getDaysInMonth; /* Internal Utilities ---------------------------------------------------------------------------------*/ function _clone(xdate) { // returns the internal Date object that should be used var d = new Date(+xdate[0]); if (getUTCMode(xdate)) { d.toString = toUTCString; } return d; } function _getField(d, useUTC, fieldIndex) { return d['get' + (useUTC ? 'UTC' : '') + methodSubjects[fieldIndex]](); } function _setField(d, useUTC, fieldIndex, args) { d['set' + (useUTC ? 'UTC' : '') + methodSubjects[fieldIndex]].apply(d, args); } /* Date Math Utilities ---------------------------------------------------------------------------------*/ function coerceToUTC(date) { return new Date(UTC( date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds() )); } function coerceToLocal(date) { return new Date( date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), date.getUTCMilliseconds() ); } function getDaysInMonth(year, month) { return 32 - new Date(UTC(year, month, 32)).getUTCDate(); } /* General Utilities ---------------------------------------------------------------------------------*/ function methodize(f) { return function() { return f.apply(undefined, [this].concat(slice(arguments))); }; } function curry(f) { var firstArgs = slice(arguments, 1); return function() { return f.apply(undefined, firstArgs.concat(slice(arguments))); }; } function slice(a, start, end) { return Array.prototype.slice.call( a, start || 0, // start and end cannot be undefined for IE end===undefined ? a.length : end ); } function each(a, f) { for (var i=0; i