/* http://keith-wood.name/calendars.html Calendars extras for jQuery v2.1.0. Written by Keith Wood (wood.keith{at}optusnet.com.au) August 2009. Available under the MIT (http://keith-wood.name/licence.html) license. Please attribute the author if you use it. */ (function($) { // Hide scope, no $ conflict 'use strict'; $.extend($.calendars.regionalOptions[''], { invalidArguments: 'Invalid arguments', invalidFormat: 'Cannot format a date from another calendar', missingNumberAt: 'Missing number at position {0}', unknownNameAt: 'Unknown name at position {0}', unexpectedLiteralAt: 'Unexpected literal at position {0}', unexpectedText: 'Additional text found at end' }); $.calendars.local = $.calendars.regionalOptions['']; $.extend($.calendars.cdate.prototype, { /** Format this date. Found in the jquery.calendars.plus.js module. @memberof CDate @param {string} [format] The date format to use (see {@linkcode BaseCalendar.formatDate|formatDate}). @param {object} [settings] Options for the formatDate function. @return {string} The formatted date. */ formatDate: function(format, settings) { if (typeof format !== 'string') { settings = format; format = ''; } return this._calendar.formatDate(format || '', this, settings); } }); $.extend($.calendars.baseCalendar.prototype, { UNIX_EPOCH: $.calendars.instance().newDate(1970, 1, 1).toJD(), SECS_PER_DAY: 24 * 60 * 60, TICKS_EPOCH: $.calendars.instance().jdEpoch, // 1 January 0001 CE TICKS_PER_DAY: 24 * 60 * 60 * 10000000, /** Date format for ATOM (RFC 3339/ISO 8601) - 'yyyy-mm-dd'. @memberof BaseCalendar */ ATOM: 'yyyy-mm-dd', /** Date format for cookies - 'D, dd M yyyy'. @memberof BaseCalendar */ COOKIE: 'D, dd M yyyy', /** Date format for the full date - 'DD, MM d, yyyy'. @memberof BaseCalendar */ FULL: 'DD, MM d, yyyy', /** Date format for ISO 8601 - 'yyyy-mm-dd'. @memberof BaseCalendar */ ISO_8601: 'yyyy-mm-dd', /** Date format for Julian date - days since January 1, 4713 BCE Greenwich noon. @memberof BaseCalendar */ JULIAN: 'J', /** Date format for RFC 822 - 'D, d M yy'. @memberof BaseCalendar */ RFC_822: 'D, d M yy', /** Date format for RFC 850 - 'DD, dd-M-yy'. @memberof BaseCalendar */ RFC_850: 'DD, dd-M-yy', /** Date format for RFC 1036 - 'D, d M yy'. @memberof BaseCalendar */ RFC_1036: 'D, d M yy', /** Date format for RFC 1123 - 'D, d M yyyy'. @memberof BaseCalendar */ RFC_1123: 'D, d M yyyy', /** Date format for RFC 2822 - 'D, d M yyyy'. @memberof BaseCalendar */ RFC_2822: 'D, d M yyyy', /** Date format for RSS (RFC 822) - 'D, d M yy'. @memberof BaseCalendar */ RSS: 'D, d M yy', /** Date format for Windows ticks - number of 100-nanosecond ticks since 1 January 0001 00:00:00 UTC. @memberof BaseCalendar */ TICKS: '!', /** Date format for Unix timestamp - number of seconds elapsed since the start of the Unix epoch at 1 January 1970 00:00:00 UTC. @memberof BaseCalendar */ TIMESTAMP: '@', /** Date format for W3C (ISO 8601) - 'yyyy-mm-dd'. @memberof BaseCalendar */ W3C: 'yyyy-mm-dd', /** Format a date object into a string value. The format can be combinations of the following: Found in the jquery.calendars.plus.js module. @memberof BaseCalendar @param {string} [format] The desired format of the date (defaults to calendar format). @param {CDate} date The date value to format. @param {object} [settings] Addition options, whose attributes include: @param {string[]} [settings.dayNamesShort] Abbreviated names of the days from day 0 (Sunday). @param {string[]} [settings.dayNames] Names of the days from day 0 (Sunday). @param {string[]} [settings.monthNamesShort] Abbreviated names of the months. @param {string[]} [settings.monthNames] Names of the months. @param {boolean} [settings.localNumbers=false] true to localise numbers (if available), false to use normal Arabic numerals. @return {string} The date in the above format. @throws Errors if the date is from a different calendar. */ formatDate: function(format, date, settings) { if (typeof format !== 'string') { settings = date; date = format; format = ''; } if (!date) { return ''; } if (date.calendar() !== this) { throw $.calendars.local.invalidFormat || $.calendars.regionalOptions[''].invalidFormat; } format = format || this.local.dateFormat; settings = settings || {}; var dayNamesShort = settings.dayNamesShort || this.local.dayNamesShort; var dayNames = settings.dayNames || this.local.dayNames; var monthNamesShort = settings.monthNamesShort || this.local.monthNamesShort; var monthNames = settings.monthNames || this.local.monthNames; var localNumbers = settings.localNumbers || this.local.localNumbers; // Check whether a format character is doubled var doubled = function(match, step) { var matches = 1; while (iFormat + matches < format.length && format.charAt(iFormat + matches) === match) { matches++; } iFormat += matches - 1; return Math.floor(matches / (step || 1)) > 1; }; // Format a number, with leading zeroes if necessary var formatNumber = function(match, value, len, step) { var num = '' + value; if (doubled(match, step)) { while (num.length < len) { num = '0' + num; } } return num; }; // Format a name, short or long as requested var formatName = function(match, value, shortNames, longNames) { return (doubled(match) ? longNames[value] : shortNames[value]); }; // Localise numbers if requested and available var localiseNumbers = localNumbers && this.local.digits ? this.local.digits : function(value) { return value; }; var output = ''; var literal = false; for (var iFormat = 0; iFormat < format.length; iFormat++) { if (literal) { if (format.charAt(iFormat) === '\'' && !doubled('\'')) { literal = false; } else { output += format.charAt(iFormat); } } else { switch (format.charAt(iFormat)) { case 'd': output += localiseNumbers(formatNumber('d', date.day(), 2)); break; case 'D': output += formatName('D', date.dayOfWeek(), dayNamesShort, dayNames); break; case 'o': output += formatNumber('o', date.dayOfYear(), 3); break; case 'w': output += formatNumber('w', date.weekOfYear(), 2); break; case 'm': output += localiseNumbers(formatNumber('m', date.month(), 2)); break; case 'M': output += formatName('M', date.month() - this.minMonth, monthNamesShort, monthNames); break; case 'y': output += localiseNumbers(doubled('y', 2) ? date.year() : (date.year() % 100 < 10 ? '0' : '') + date.year() % 100); break; case 'Y': doubled('Y', 2); output += date.formatYear(); break; case 'J': output += date.toJD(); break; case '@': output += (date.toJD() - this.UNIX_EPOCH) * this.SECS_PER_DAY; break; case '!': output += (date.toJD() - this.TICKS_EPOCH) * this.TICKS_PER_DAY; break; case '\'': if (doubled('\'')) { output += '\''; } else { literal = true; } break; default: output += format.charAt(iFormat); } } } return output; }, /** Parse a string value into a date object. See {@linkcode BaseCalendar.formatDate|formatDate} for the possible formats, plus: Found in the jquery.calendars.plus.js module. @memberof BaseCalendar @param {string} format The expected format of the date ('' for default calendar format). @param {string} value The date in the above format. @param {object} [settings] Additional options whose attributes include: @param {number} [settings.shortYearCutoff] The cutoff year for determining the century. @param {string[]} [settings.dayNamesShort] Abbreviated names of the days from day 0 (Sunday). @param {string[]} [settings.dayNames] Names of the days from day 0 (Sunday). @param {string[]} [settings.monthNamesShort] Abbreviated names of the months. @param {string[]} [settings.monthNames] Names of the months. @return {CDate} The extracted date value or null if value is blank. @throws Errors if the format and/or value are missing, if the value doesn't match the format, or if the date is invalid. */ parseDate: function(format, value, settings) { if (typeof value === 'undefined' || value === null) { throw $.calendars.local.invalidArguments || $.calendars.regionalOptions[''].invalidArguments; } value = (typeof value === 'object' ? value.toString() : value + ''); if (value === '') { return null; } format = format || this.local.dateFormat; settings = settings || {}; var shortYearCutoff = settings.shortYearCutoff || this.shortYearCutoff; shortYearCutoff = (typeof shortYearCutoff !== 'string' ? shortYearCutoff : this.today().year() % 100 + parseInt(shortYearCutoff, 10)); var dayNamesShort = settings.dayNamesShort || this.local.dayNamesShort; var dayNames = settings.dayNames || this.local.dayNames; var monthNamesShort = settings.monthNamesShort || this.local.monthNamesShort; var monthNames = settings.monthNames || this.local.monthNames; var jd = -1; var year = -1; var month = -1; var day = -1; var doy = -1; var shortYear = false; var literal = false; // Check whether a format character is doubled var doubled = function(match, step) { var matches = 1; while (iFormat + matches < format.length && format.charAt(iFormat + matches) === match) { matches++; } iFormat += matches - 1; return Math.floor(matches / (step || 1)) > 1; }; // Extract a number from the string value var getNumber = function(match, step) { var isDoubled = doubled(match, step); var size = [2, 3, isDoubled ? 4 : 2, isDoubled ? 4 : 2, 10, 11, 20]['oyYJ@!'.indexOf(match) + 1]; var digits = new RegExp('^-?\\d{1,' + size + '}'); var num = value.substring(iValue).match(digits); if (!num) { throw ($.calendars.local.missingNumberAt || $.calendars.regionalOptions[''].missingNumberAt). replace(/\{0\}/, iValue); } iValue += num[0].length; return parseInt(num[0], 10); }; // Extract a name from the string value and convert to an index var calendar = this; var getName = function(match, shortNames, longNames, step) { var names = (doubled(match, step) ? longNames : shortNames); for (var i = 0; i < names.length; i++) { if (value.substr(iValue, names[i].length).toLowerCase() === names[i].toLowerCase()) { iValue += names[i].length; return i + calendar.minMonth; } } throw ($.calendars.local.unknownNameAt || $.calendars.regionalOptions[''].unknownNameAt). replace(/\{0\}/, iValue); }; // Confirm that a literal character matches the string value var checkLiteral = function() { if (value.charAt(iValue) !== format.charAt(iFormat)) { throw ($.calendars.local.unexpectedLiteralAt || $.calendars.regionalOptions[''].unexpectedLiteralAt).replace(/\{0\}/, iValue); } iValue++; }; var iValue = 0; for (var iFormat = 0; iFormat < format.length; iFormat++) { if (literal) { if (format.charAt(iFormat) === '\'' && !doubled('\'')) { literal = false; } else { checkLiteral(); } } else { switch (format.charAt(iFormat)) { case 'd': day = getNumber('d'); break; case 'D': getName('D', dayNamesShort, dayNames); break; case 'o': doy = getNumber('o'); break; case 'w': getNumber('w'); break; case 'm': month = getNumber('m'); break; case 'M': month = getName('M', monthNamesShort, monthNames); break; case 'y': var iSave = iFormat; shortYear = !doubled('y', 2); iFormat = iSave; year = getNumber('y', 2); break; case 'Y': year = getNumber('Y', 2); break; case 'J': jd = getNumber('J') + 0.5; if (value.charAt(iValue) === '.') { iValue++; getNumber('J'); } break; case '@': jd = getNumber('@') / this.SECS_PER_DAY + this.UNIX_EPOCH; break; case '!': jd = getNumber('!') / this.TICKS_PER_DAY + this.TICKS_EPOCH; break; case '*': iValue = value.length; break; case '\'': if (doubled('\'')) { checkLiteral(); } else { literal = true; } break; default: checkLiteral(); } } } if (iValue < value.length) { throw $.calendars.local.unexpectedText || $.calendars.regionalOptions[''].unexpectedText; } if (year === -1) { year = this.today().year(); } else if (year < 100 && shortYear) { year += (shortYearCutoff === -1 ? 1900 : this.today().year() - this.today().year() % 100 - (year <= shortYearCutoff ? 0 : 100)); } if (doy > -1) { month = 1; day = doy; for (var dim = this.daysInMonth(year, month); day > dim; dim = this.daysInMonth(year, month)) { month++; day -= dim; } } return (jd > -1 ? this.fromJD(jd) : this.newDate(year, month, day)); }, /** A date may be specified as an exact value or a relative one. Found in the jquery.calendars.plus.js module. @memberof BaseCalendar @param {CDate|number|string} dateSpec The date as an object or string in the given format or an offset - numeric days from today, or string amounts and periods, e.g. '+1m +2w'. @param {CDate} defaultDate The date to use if no other supplied, may be null. @param {CDate} [currentDate=null] The current date as a possible basis for relative dates, if null today is used. @param {string} [dateFormat] The expected date format - see {@linkcode BaseCalendar.formatDate|formatDate}. Use '' for the calendar default format. @param {object} [settings] Additional options whose attributes include: @param {number} [settings.shortYearCutoff] The cutoff year for determining the century. @param {string[]} [settings.dayNamesShort] Abbreviated names of the days from day 0 (Sunday). @param {string[]} [settings.dayNames] Names of the days from day 0 (Sunday). @param {string[]} [settings.monthNamesShort] Abbreviated names of the months. @param {string[]} [settings.monthNames] Names of the months. @return {CDate} The decoded date. */ determineDate: function(dateSpec, defaultDate, currentDate, dateFormat, settings) { if (currentDate && typeof currentDate !== 'object') { settings = dateFormat; dateFormat = currentDate; currentDate = null; } if (typeof dateFormat !== 'string') { settings = dateFormat; dateFormat = ''; } var calendar = this; var offsetString = function(offset) { try { return calendar.parseDate(dateFormat, offset, settings); } catch (e) { // Ignore } offset = offset.toLowerCase(); var date = (offset.match(/^c/) && currentDate ? currentDate.newDate() : null) || calendar.today(); var pattern = /([+-]?[0-9]+)\s*(d|w|m|y)?/g; var matches = pattern.exec(offset); while (matches) { date.add(parseInt(matches[1], 10), matches[2] || 'd'); matches = pattern.exec(offset); } return date; }; defaultDate = (defaultDate ? defaultDate.newDate() : null); dateSpec = (typeof dateSpec === 'undefined' || dateSpec === null ? defaultDate : (typeof dateSpec === 'string' ? offsetString(dateSpec) : (typeof dateSpec === 'number' ? (isNaN(dateSpec) || dateSpec === Infinity || dateSpec === -Infinity ? defaultDate : calendar.today().add(dateSpec, 'd')) : calendar.newDate(dateSpec)))); return dateSpec; } }); })(jQuery);