PageSourceSearch

https://kultur-leipzigerraum.de/module/jscript/lib/jquery_plugins/datepick/jquery.datepick.js

js kultur-leipzigerraum.de collected 2026-10-04 20:46:24 UTC 84,826 bytes, 2,075 lines download raw bytes

1/* http://keith-wood.name/datepick.html
2   Date picker for jQuery v4.0.4.
3   Written by Keith Wood (kbwood{at}iinet.com.au) February 2010.
4   Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and 
5   MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses. 
6   Please attribute the author if you use it. */
7
8(function($) { // Hide scope, no $ conflict
9
10/* Datepicker manager. */
11function Datepicker() {
12	this._defaults = {
13		pickerClass: '', // CSS class to add to this instance of the datepicker
14		showOnFocus: true, // True for popup on focus, false for not
15		showTrigger: null, // Element to be cloned for a trigger, null for none
16		showAnim: 'show', // Name of jQuery animation for popup, '' for no animation
17		showOptions: {}, // Options for enhanced animations
18		showSpeed: 'normal', // Duration of display/closure
19		popupContainer: null, // The element to which a popup calendar is added, null for body
20		alignment: 'bottom', // Alignment of popup - with nominated corner of input:
21			// 'top' or 'bottom' aligns depending on language direction,
22			// 'topLeft', 'topRight', 'bottomLeft', 'bottomRight'
23		fixedWeeks: false, // True to always show 6 weeks, false to only show as many as are needed
24		firstDay: 0, // First day of the week, 0 = Sunday, 1 = Monday, ...
25		calculateWeek: this.iso8601Week, // Calculate week of the year from a date, null for ISO8601
26		monthsToShow: 1, // How many months to show, cols or [rows, cols]
27		monthsOffset: 0, // How many months to offset the primary month by
28		monthsToStep: 1, // How many months to move when prev/next clicked
29		monthsToJump: 12, // How many months to move when large prev/next clicked
30		useMouseWheel: true, // True to use mousewheel if available, false to never use it
31		changeMonth: true, // True to change month/year via drop-down, false for navigation only
32		yearRange: 'c-10:c+10', // Range of years to show in drop-down: 'any' for direct text entry
33			// or 'start:end', where start/end are '+-nn' for relative to today
34			// or 'c+-nn' for relative to the currently selected date
35			// or 'nnnn' for an absolute year
36		shortYearCutoff: '+10', // Cutoff for two-digit year in the current century
37		showOtherMonths: false, // True to show dates from other months, false to not show them
38		selectOtherMonths: false, // True to allow selection of dates from other months too
39		defaultDate: null, // Date to show if no other selected
40		selectDefaultDate: false, // True to pre-select the default date if no other is chosen
41		minDate: null, // The minimum selectable date
42		maxDate: null, // The maximum selectable date
43		dateFormat: 'mm/dd/yyyy', // Format for dates
44		autoSize: false, // True to size the input field according to the date format
45		rangeSelect: false, // Allows for selecting a date range on one date picker
46		rangeSeparator: ' - ', // Text between two dates in a range
47		multiSelect: 0, // Maximum number of selectable dates, zero for single select
48		multiSeparator: ',', // Text between multiple dates
49		onDate: null, // Callback as a date is added to the datepicker
50		onShow: null, // Callback just before a datepicker is shown
51		onChangeMonthYear: null, // Callback when a new month/year is selected
52		onSelect: null, // Callback when a date is selected
53		onClose: null, // Callback when a datepicker is closed
54		altField: null, // Alternate field to update in synch with the datepicker
55		altFormat: null, // Date format for alternate field, defaults to dateFormat
56		constrainInput: true, // True to constrain typed input to dateFormat allowed characters
57		commandsAsDateFormat: false, // True to apply formatDate to the command texts
58		commands: this.commands // Command actions that may be added to a layout by name
59	};
60	this.regional = {
61		'': { // US/English
62			monthNames: ['January', 'February', 'March', 'April', 'May', 'June',
63			'July', 'August', 'September', 'October', 'November', 'December'],
64			monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
65			dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
66			dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
67			dayNamesMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
68			dateFormat: 'mm/dd/yyyy', // See options on formatDate
69			firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ...
70			renderer: this.defaultRenderer, // The rendering templates
71			prevText: '< Prev', // Text for the previous month command
72			prevStatus: 'Show the previous month', // Status text for the previous month command
73			prevJumpText: '<<', // Text for the previous year command
74			prevJumpStatus: 'Show the previous year', // Status text for the previous year command
75			nextText: 'Next >', // Text for the next month command
76			nextStatus: 'Show the next month', // Status text for the next month command
77			nextJumpText: '>>', // Text for the next year command
78			nextJumpStatus: 'Show the next year', // Status text for the next year command
79			currentText: 'Current', // Text for the current month command
80			currentStatus: 'Show the current month', // Status text for the current month command
81			todayText: 'Today', // Text for the today's month command
82			todayStatus: 'Show today\'s month', // Status text for the today's month command
83			clearText: 'Clear', // Text for the clear command
84			clearStatus: 'Clear all the dates', // Status text for the clear command
85			closeText: 'Close', // Text for the close command
86			closeStatus: 'Close the datepicker', // Status text for the close command
87			yearStatus: 'Change the year', // Status text for year selection
88			monthStatus: 'Change the month', // Status text for month selection
89			weekText: 'Wk', // Text for week of the year column header
90			weekStatus: 'Week of the year', // Status text for week of the year column header
91			dayStatus: 'Select DD, M d, yyyy', // Status text for selectable days
92			defaultStatus: 'Select a date', // Status text shown by default
93			isRTL: false // True if language is right-to-left
94		}};
95	$.extend(this._defaults, this.regional['']);
96	this._disabled = [];
97}
98
99$.extend(Datepicker.prototype, {
100	dataName: 'datepick',
101	
102	/* Class name added to elements to indicate already configured with datepicker. */
103	markerClass: 'hasDatepick',
104
105	_popupClass: 'datepick-popup', // Marker for popup division
106	_triggerClass: 'datepick-trigger', // Marker for trigger element
107	_disableClass: 'datepick-disable', // Marker for disabled element
108	_coverClass: 'datepick-cover', // Marker for iframe backing element
109	_monthYearClass: 'datepick-month-year', // Marker for month/year inputs
110	_curMonthClass: 'datepick-month-', // Marker for current month/year
111	_anyYearClass: 'datepick-any-year', // Marker for year direct input
112	_curDoWClass: 'datepick-dow-', // Marker for day of week
113	
114	commands: { // Command actions that may be added to a layout by name
115		// name: { // The command name, use '{button:name}' or '{link:name}' in layouts
116		//		text: '', // The field in the regional settings for the displayed text
117		//		status: '', // The field in the regional settings for the status text
118		//      // The keystroke to trigger the action
119		//		keystroke: {keyCode: nn, ctrlKey: boolean, altKey: boolean, shiftKey: boolean},
120		//		enabled: fn, // The function that indicates the command is enabled
121		//		date: fn, // The function to get the date associated with this action
122		//		action: fn} // The function that implements the action
123		prev: {text: 'prevText', status: 'prevStatus', // Previous month
124			keystroke: {keyCode: 33}, // Page up
125			enabled: function(inst) {
126				var minDate = inst.curMinDate();
127				return (!minDate || $.datepick.add($.datepick.day(
128					$.datepick.add($.datepick.newDate(inst.drawDate),
129					1 - inst.get('monthsToStep') - inst.get('monthsOffset'), 'm'), 1), -1, 'd').
130					getTime() >= minDate.getTime()); },
131			date: function(inst) {
132				return $.datepick.day($.datepick.add($.datepick.newDate(inst.drawDate),
133					-inst.get('monthsToStep') - inst.get('monthsOffset'), 'm'), 1); },
134			action: function(inst) {
135				$.datepick.changeMonth(this, -inst.get('monthsToStep')); }
136		},
137		prevJump: {text: 'prevJumpText', status: 'prevJumpStatus', // Previous year
138			keystroke: {keyCode: 33, ctrlKey: true}, // Ctrl + Page up
139			enabled: function(inst) {
140				var minDate = inst.curMinDate();
141				return (!minDate || $.datepick.add($.datepick.day(
142					$.datepick.add($.datepick.newDate(inst.drawDate),
143					1 - inst.get('monthsToJump') - inst.get('monthsOffset'), 'm'), 1), -1, 'd').
144					getTime() >= minDate.getTime()); },
145			date: function(inst) {
146				return $.datepick.day($.datepick.add($.datepick.newDate(inst.drawDate),
147					-inst.get('monthsToJump') - inst.get('monthsOffset'), 'm'), 1); },
148			action: function(inst) {
149				$.datepick.changeMonth(this, -inst.get('monthsToJump')); }
150		},
151		next: {text: 'nextText', status: 'nextStatus', // Next month
152			keystroke: {keyCode: 34}, // Page down
153			enabled: function(inst) {
154				var maxDate = inst.get('maxDate');
155				return (!maxDate || $.datepick.day($.datepick.add($.datepick.newDate(inst.drawDate),
156					inst.get('monthsToStep') - inst.get('monthsOffset'), 'm'), 1).
157					getTime() <= maxDate.getTime()); },
158			date: function(inst) {
159				return $.datepick.day($.datepick.add($.datepick.newDate(inst.drawDate),
160					inst.get('monthsToStep') - inst.get('monthsOffset'), 'm'), 1); },
161			action: function(inst) {
162				$.datepick.changeMonth(this, inst.get('monthsToStep')); }
163		},
164		nextJump: {text: 'nextJumpText', status: 'nextJumpStatus', // Next year
165			keystroke: {keyCode: 34, ctrlKey: true}, // Ctrl + Page down
166			enabled: function(inst) {
167				var maxDate = inst.get('maxDate');
168				return (!maxDate || $.datepick.day($.datepick.add($.datepick.newDate(inst.drawDate),
169					inst.get('monthsToJump') - inst.get('monthsOffset'), 'm'), 1).
170					getTime() <= maxDate.getTime()); },
171			date: function(inst) {
172				return $.datepick.day($.datepick.add($.datepick.newDate(inst.drawDate),
173					inst.get('monthsToJump') - inst.get('monthsOffset'), 'm'), 1); },
174			action: function(inst) {
175				$.datepick.changeMonth(this, inst.get('monthsToJump')); }
176		},
177		current: {text: 'currentText', status: 'currentStatus', // Current month
178			keystroke: {keyCode: 36, ctrlKey: true}, // Ctrl + Home
179			enabled: function(inst) {
180				var minDate = inst.curMinDate();
181				var maxDate = inst.get('maxDate');
182				var curDate = inst.selectedDates[0] || $.datepick.today();
183				return (!minDate || curDate.getTime() >= minDate.getTime()) &&
184					(!maxDate || curDate.getTime() <= maxDate.getTime()); },
185			date: function(inst) {
186				return inst.selectedDates[0] || $.datepick.today(); },
187			action: function(inst) {
188				var curDate = inst.selectedDates[0] || $.datepick.today();
189				$.datepick.showMonth(this, curDate.getFullYear(), curDate.getMonth() + 1); }
190		},
191		today: {text: 'todayText', status: 'todayStatus', // Today's month
192			keystroke: {keyCode: 36, ctrlKey: true}, // Ctrl + Home
193			enabled: function(inst) {
194				var minDate = inst.curMinDate();
195				var maxDate = inst.get('maxDate');
196				return (!minDate || $.datepick.today().getTime() >= minDate.getTime()) &&
197					(!maxDate || $.datepick.today().getTime() <= maxDate.getTime()); },
198			date: function(inst) { return $.datepick.today(); },
199			action: function(inst) { $.datepick.showMonth(this); }
200		},
201		clear: {text: 'clearText', status: 'clearStatus', // Clear the datepicker
202			keystroke: {keyCode: 35, ctrlKey: true}, // Ctrl + End
203			enabled: function(inst) { return true; },
204			date: function(inst) { return null; },
205			action: function(inst) { $.datepick.clear(this); }
206		},
207		close: {text: 'closeText', status: 'closeStatus', // Close the datepicker
208			keystroke: {keyCode: 27}, // Escape
209			enabled: function(inst) { return true; },
210			date: function(inst) { return null; },
211			action: function(inst) { $.datepick.hide(this); }
212		},
213		prevWeek: {text: 'prevWeekText', status: 'prevWeekStatus', // Previous week
214			keystroke: {keyCode: 38, ctrlKey: true}, // Ctrl + Up
215			enabled: function(inst) {
216				var minDate = inst.curMinDate();
217				return (!minDate || $.datepick.add($.datepick.newDate(inst.drawDate), -7, 'd').
218					getTime() >= minDate.getTime()); },
219			date: function(inst) { return $.datepick.add($.datepick.newDate(inst.drawDate), -7, 'd'); },
220			action: function(inst) { $.datepick.changeDay(this, -7); }
221		},
222		prevDay: {text: 'prevDayText', status: 'prevDayStatus', // Previous day
223			keystroke: {keyCode: 37, ctrlKey: true}, // Ctrl + Left
224			enabled: function(inst) {
225				var minDate = inst.curMinDate();
226				return (!minDate || $.datepick.add($.datepick.newDate(inst.drawDate), -1, 'd').
227					getTime() >= minDate.getTime()); },
228			date: function(inst) { return $.datepick.add($.datepick.newDate(inst.drawDate), -1, 'd'); },
229			action: function(inst) { $.datepick.changeDay(this, -1); }
230		},
231		nextDay: {text: 'nextDayText', status: 'nextDayStatus', // Next day
232			keystroke: {keyCode: 39, ctrlKey: true}, // Ctrl + Right
233			enabled: function(inst) {
234				var maxDate = inst.get('maxDate');
235				return (!maxDate || $.datepick.add($.datepick.newDate(inst.drawDate), 1, 'd').
236					getTime() <= maxDate.getTime()); },
237			date: function(inst) { return $.datepick.add($.datepick.newDate(inst.drawDate), 1, 'd'); },
238			action: function(inst) { $.datepick.changeDay(this, 1); }
239		},
240		nextWeek: {text: 'nextWeekText', status: 'nextWeekStatus', // Next week
241			keystroke: {keyCode: 40, ctrlKey: true}, // Ctrl + Down
242			enabled: function(inst) {
243				var maxDate = inst.get('maxDate');
244				return (!maxDate || $.datepick.add($.datepick.newDate(inst.drawDate), 7, 'd').
245					getTime() <= maxDate.getTime()); },
246			date: function(inst) { return $.datepick.add($.datepick.newDate(inst.drawDate), 7, 'd'); },
247			action: function(inst) { $.datepick.changeDay(this, 7); }
248		}
249	},
250
251	/* Default template for generating a datepicker. */
252	defaultRenderer: {
253		// Anywhere: '{l10n:name}' to insert localised value for name,
254		// '{link:name}' to insert a link trigger for command name,
255		// '{button:name}' to insert a button trigger for command name,
256		// '{popup:start}...{popup:end}' to mark a section for inclusion in a popup datepicker only,
257		// '{inline:start}...{inline:end}' to mark a section for inclusion in an inline datepicker only
258		// Overall structure: '{months}' to insert calendar months
259		picker: '<div class="datepick">' +
260		'<div class="datepick-nav">{link:prev}{link:today}{link:next}</div>{months}' +
261		'{popup:start}<div class="datepick-ctrl">{link:clear}{link:close}</div>{popup:end}' +
262		'<div class="datepick-clear-fix"></div></div>',
263		// One row of months: '{months}' to insert calendar months
264		monthRow: '<div class="datepick-month-row">{months}</div>',
265		// A single month: '{monthHeader:dateFormat}' to insert the month header -
266		// dateFormat is optional and defaults to 'MM yyyy',
267		// '{weekHeader}' to insert a week header, '{weeks}' to insert the month's weeks
268		month: '<div class="datepick-month"><div class="datepick-month-header">{monthHeader}</div>' +
269		'<table><thead>{weekHeader}</thead><tbody>{weeks}</tbody></table></div>',
270		// A week header: '{days}' to insert individual day names
271		weekHeader: '<tr>{days}</tr>',
272		// Individual day header: '{day}' to insert day name
273		dayHeader: '<th>{day}</th>',
274		// One week of the month: '{days}' to insert the week's days, '{weekOfYear}' to insert week of year
275		week: '<tr>{days}</tr>',
276		// An individual day: '{day}' to insert day value
277		day: '<td>{day}</td>',
278		// jQuery selector, relative to picker, for a single month
279		monthSelector: '.datepick-month',
280		// jQuery selector, relative to picker, for individual days
281		daySelector: 'td',
282		// Class for right-to-left (RTL) languages
283		rtlClass: 'datepick-rtl',
284		// Class for multi-month datepickers
285		multiClass: 'datepick-multi',
286		// Class for selectable dates
287		defaultClass: '',
288		// Class for currently selected dates
289		selectedClass: 'datepick-selected',
290		// Class for highlighted dates
291		highlightedClass: 'datepick-highlight',
292		// Class for today
293		todayClass: 'datepick-today',
294		// Class for days from other months
295		otherMonthClass: 'datepick-other-month',
296		// Class for days on weekends
297		weekendClass: 'datepick-weekend',
298		// Class prefix for commands
299		commandClass: 'datepick-cmd',
300		// Extra class(es) for commands that are buttons
301		commandButtonClass: '',
302		// Extra class(es) for commands that are links
303		commandLinkClass: '',
304		// Class for disabled commands
305		disabledClass: 'datepick-disabled'
306	},
307
308	/* Override the default settings for all datepicker instances.
309	   @param  settings  (object) the new settings to use as defaults
310	   @return  (Datepicker) this object */
311	setDefaults: function(settings) {
312		$.extend(this._defaults, settings || {});
313		return this;
314	},
315
316	_ticksTo1970: (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) +
317		Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000),
318	_msPerDay: 24 * 60 * 60 * 1000,
319
320	ATOM: 'yyyy-mm-dd', // RFC 3339/ISO 8601
321	COOKIE: 'D, dd M yyyy',
322	FULL: 'DD, MM d, yyyy',
323	ISO_8601: 'yyyy-mm-dd',
324	JULIAN: 'J',
325	RFC_822: 'D, d M yy',
326	RFC_850: 'DD, dd-M-yy',
327	RFC_1036: 'D, d M yy',
328	RFC_1123: 'D, d M yyyy',
329	RFC_2822: 'D, d M yyyy',
330	RSS: 'D, d M yy', // RFC 822
331	TICKS: '!',
332	TIMESTAMP: '@',
333	W3C: 'yyyy-mm-dd', // ISO 8601
334
335	/* Format a date object into a string value.
336	   The format can be combinations of the following:
337	   d  - day of month (no leading zero)
338	   dd - day of month (two digit)
339	   o  - day of year (no leading zeros)
340	   oo - day of year (three digit)
341	   D  - day name short
342	   DD - day name long
343	   w  - week of year (no leading zero)
344	   ww - week of year (two digit)
345	   m  - month of year (no leading zero)
346	   mm - month of year (two digit)
347	   M  - month name short
348	   MM - month name long
349	   yy - year (two digit)
350	   yyyy - year (four digit)
351	   @  - Unix timestamp (s since 01/01/1970)
352	   !  - Windows ticks (100ns since 01/01/0001)
353	   '...' - literal text
354	   '' - single quote
355	   @param  format    (string) the desired format of the date (optional, default datepicker format)
356	   @param  date      (Date) the date value to format
357	   @param  settings  (object) attributes include:
358	                     dayNamesShort    (string[]) abbreviated names of the days from Sunday (optional)
359	                     dayNames         (string[]) names of the days from Sunday (optional)
360	                     monthNamesShort  (string[]) abbreviated names of the months (optional)
361	                     monthNames       (string[]) names of the months (optional)
362						 calculateWeek    (function) function that determines week of the year (optional)
363	   @return  (string) the date in the above format */
364	formatDate: function(format, date, settings) {
365		if (typeof format != 'string') {
366			settings = date;
367			date = format;
368			format = '';
369		}
370		if (!date) {
371			return '';
372		}
373		format = format || this._defaults.dateFormat;
374		settings = settings || {};
375		var dayNamesShort = settings.dayNamesShort || this._defaults.dayNamesShort;
376		var dayNames = settings.dayNames || this._defaults.dayNames;
377		var monthNamesShort = settings.monthNamesShort || this._defaults.monthNamesShort;
378		var monthNames = settings.monthNames || this._defaults.monthNames;
379		var calculateWeek = settings.calculateWeek || this._defaults.calculateWeek;
380		// Check whether a format character is doubled
381		var doubled = function(match, step) {
382			var matches = 1;
383			while (iFormat + matches < format.length && format.charAt(iFormat + matches) == match) {
384				matches++;
385			}
386			iFormat += matches - 1;
387			return Math.floor(matches / (step || 1)) > 1;
388		};
389		// Format a number, with leading zeroes if necessary
390		var formatNumber = function(match, value, len, step) {
391			var num = '' + value;
392			if (doubled(match, step)) {
393				while (num.length < len) {
394					num = '0' + num;
395				}
396			}
397			return num;
398		};
399		// Format a name, short or long as requested
400		var formatName = function(match, value, shortNames, longNames) {
401			return (doubled(match) ? longNames[value] : shortNames[value]);
402		};
403		var output = '';
404		var literal = false;
405		for (var iFormat = 0; iFormat < format.length; iFormat++) {
406			if (literal) {
407				if (format.charAt(iFormat) == "'" && !doubled("'")) {
408					literal = false;
409				}
410				else {
411					output += format.charAt(iFormat);
412				}
413			}
414			else {
415				switch (format.charAt(iFormat)) {
416					case 'd': output += formatNumber('d', date.getDate(), 2); break;
417					case 'D': output += formatName('D', date.getDay(),
418						dayNamesShort, dayNames); break;
419					case 'o': output += formatNumber('o', this.dayOfYear(date), 3); break;
420					case 'w': output += formatNumber('w', calculateWeek(date), 2); break;
421					case 'm': output += formatNumber('m', date.getMonth() + 1, 2); break;
422					case 'M': output += formatName('M', date.getMonth(),
423						monthNamesShort, monthNames); break;
424					case 'y':
425						output += (doubled('y', 2) ? date.getFullYear() :
426							(date.getFullYear() % 100 < 10 ? '0' : '') + date.getFullYear() % 100);
427						break;
428					case '@': output += Math.floor(date.getTime() / 1000); break;
429					case '!': output += date.getTime() * 10000 + this._ticksTo1970; break;
430					case "'":
431						if (doubled("'")) {
432							output += "'";
433						}
434						else {
435							literal = true;
436						}
437						break;
438					default:
439						output += format.charAt(iFormat);
440				}
441			}
442		}
443		return output;
444	},
445
446	/* Parse a string value into a date object.
447	   See formatDate for the possible formats, plus:
448	   * - ignore rest of string
449	   @param  format    (string) the expected format of the date ('' for default datepicker format)
450	   @param  value     (string) the date in the above format
451	   @param  settings  (object) attributes include:
452	                     shortYearCutoff  (number) the cutoff year for determining the century (optional)
453	                     dayNamesShort    (string[]) abbreviated names of the days from Sunday (optional)
454	                     dayNames         (string[]) names of the days from Sunday (optional)
455	                     monthNamesShort  (string[]) abbreviated names of the months (optional)
456	                     monthNames       (string[]) names of the months (optional)
457	   @return  (Date) the extracted date value or null if value is blank
458	   @throws  errors if the format and/or value are missing,
459	            if the value doesn't match the format,
460	            or if the date is invalid */
461	parseDate: function(format, value, settings) {
462		if (value == null) {
463			throw 'Invalid arguments';
464		}
465		value = (typeof value == 'object' ? value.toString() : value + '');
466		if (value == '') {
467			return null;
468		}
469		format = format || this._defaults.dateFormat;
470		settings = settings || {};
471		var shortYearCutoff = settings.shortYearCutoff || this._defaults.shortYearCutoff;
472		shortYearCutoff = (typeof shortYearCutoff != 'string' ? shortYearCutoff :
473			this.today().getFullYear() % 100 + parseInt(shortYearCutoff, 10));
474		var dayNamesShort = settings.dayNamesShort || this._defaults.dayNamesShort;
475		var dayNames = settings.dayNames || this._defaults.dayNames;
476		var monthNamesShort = settings.monthNamesShort || this._defaults.monthNamesShort;
477		var monthNames = settings.monthNames || this._defaults.monthNames;
478		var year = -1;
479		var month = -1;
480		var day = -1;
481		var doy = -1;
482		var shortYear = false;
483		var literal = false;
484		// Check whether a format character is doubled
485		var doubled = function(match, step) {
486			var matches = 1;
487			while (iFormat + matches < format.length && format.charAt(iFormat + matches) == match) {
488				matches++;
489			}
490			iFormat += matches - 1;
491			return Math.floor(matches / (step || 1)) > 1;
492		};
493		// Extract a number from the string value
494		var getNumber = function(match, step) {
495			doubled(match, step);
496			var size = [2, 3, 4, 11, 20]['oy@!'.indexOf(match) + 1];
497			var digits = new RegExp('^-?\\d{1,' + size + '}');
498			var num = value.substring(iValue).match(digits);
499			if (!num) {
500				throw 'Missing number at position {0}'.replace(/\{0\}/, iValue);
501			}
502			iValue += num[0].length;
503			return parseInt(num[0], 10);
504		};
505		// Extract a name from the string value and convert to an index
506		var getName = function(match, shortNames, longNames, step) {
507			var names = (doubled(match, step) ? longNames : shortNames);
508			for (var i = 0; i < names.length; i++) {
509				if (value.substr(iValue, names[i].length) == names[i]) {
510					iValue += names[i].length;
511					return i + 1;
512				}
513			}
514			throw 'Unknown name at position {0}'.replace(/\{0\}/, iValue);
515		};
516		// Confirm that a literal character matches the string value
517		var checkLiteral = function() {
518			if (value.charAt(iValue) != format.charAt(iFormat)) {
519				throw 'Unexpected literal at position {0}'.replace(/\{0\}/, iValue);
520			}
521			iValue++;
522		};
523		var iValue = 0;
524		for (var iFormat = 0; iFormat < format.length; iFormat++) {
525			if (literal) {
526				if (format.charAt(iFormat) == "'" && !doubled("'")) {
527					literal = false;
528				}
529				else {
530					checkLiteral();
531				}
532			}
533			else {
534				switch (format.charAt(iFormat)) {
535					case 'd': day = getNumber('d'); break;
536					case 'D': getName('D', dayNamesShort, dayNames); break;
537					case 'o': doy = getNumber('o'); break;
538					case 'w': getNumber('w'); break;
539					case 'm': month = getNumber('m'); break;
540					case 'M': month = getName('M', monthNamesShort, monthNames); break;
541					case 'y':
542						var iSave = iFormat;
543						shortYear = !doubled('y', 2);
544						iFormat = iSave;
545						year = getNumber('y', 2);
546						break;
547					case '@':
548						var date = this._normaliseDate(new Date(getNumber('@') * 1000));
549						year = date.getFullYear();
550						month = date.getMonth() + 1;
551						day = date.getDate();
552						break;
553					case '!':
554						var date = this._normaliseDate(
555							new Date((getNumber('!') - this._ticksTo1970) / 10000));
556						year = date.getFullYear();
557						month = date.getMonth() + 1;
558						day = date.getDate();
559						break;
560					case '*': iValue = value.length; break;
561					case "'":
562						if (doubled("'")) {
563							checkLiteral();
564						}
565						else {
566							literal = true;
567						}
568						break;
569					default: checkLiteral();
570				}
571			}
572		}
573		if (iValue < value.length) {
574			throw 'Additional text found at end';
575		}
576		if (year == -1) {
577			year = this.today().getFullYear();
578		}
579		else if (year < 100 && shortYear) {
580			year += (shortYearCutoff == -1 ? 1900 : this.today().getFullYear() -
581				this.today().getFullYear() % 100 - (year <= shortYearCutoff ? 0 : 100));
582		}
583		if (doy > -1) {
584			month = 1;
585			day = doy;
586			for (var dim = this.daysInMonth(year, month); day > dim;
587					dim = this.daysInMonth(year, month)) {
588				month++;
589				day -= dim;
590			}
591		}
592		var date = this.newDate(year, month, day);
593		if (date.getFullYear() != year || date.getMonth() + 1 != month || date.getDate() != day) {
594			throw 'Invalid date';
595		}
596		return date;
597	},
598
599	/* A date may be specified as an exact value or a relative one.
600	   @param  dateSpec     (Date or number or string) the date as an object or string
601	                        in the given format or an offset - numeric days from today,
602	                        or string amounts and periods, e.g. '+1m +2w'
603	   @param  defaultDate  (Date) the date to use if no other supplied, may be null
604	   @param  currentDate  (Date) the current date as a possible basis for relative dates,
605	                        if null today is used (optional)
606	   @param  dateFormat   (string) the expected date format - see formatDate above (optional)
607	   @param  settings     (object) attributes include:
608	                        shortYearCutoff  (number) the cutoff year for determining the century (optional)
609	                        dayNamesShort    (string[7]) abbreviated names of the days from Sunday (optional)
610	                        dayNames         (string[7]) names of the days from Sunday (optional)
611	                        monthNamesShort  (string[12]) abbreviated names of the months (optional)
612	                        monthNames       (string[12]) names of the months (optional)
613	   @return  (Date) the decoded date */
614	determineDate: function(dateSpec, defaultDate, currentDate, dateFormat, settings) {
615		if (currentDate && typeof currentDate != 'object') {
616			settings = dateFormat;
617			dateFormat = currentDate;
618			currentDate = null;
619		}
620		if (typeof dateFormat != 'string') {
621			settings = dateFormat;
622			dateFormat = '';
623		}
624		var offsetString = function(offset) {
625			try {
626				return $.datepick.parseDate(dateFormat, offset, settings);
627			}
628			catch (e) {
629				// Ignore
630			}
631			offset = offset.toLowerCase();
632			var date = (offset.match(/^c/) && currentDate ? $.datepick.newDate(currentDate) : null) ||
633				$.datepick.today();
634			var pattern = /([+-]?[0-9]+)\s*(d|w|m|y)?/g;
635			var matches = pattern.exec(offset);
636			while (matches) {
637				date = $.datepick.add(date, parseInt(matches[1], 10), matches[2] || 'd');
638				matches = pattern.exec(offset);
639			}
640			return date;
641		};
642		defaultDate = (defaultDate ? $.datepick.newDate(defaultDate) : null);
643		dateSpec = (dateSpec == null ? defaultDate :
644			(typeof dateSpec == 'string' ? offsetString(dateSpec) : (typeof dateSpec == 'number' ?
645			(isNaN(dateSpec) || dateSpec == Infinity || dateSpec == -Infinity ? defaultDate :
646			$.datepick.add($.datepick.today(), dateSpec, 'd')) : $.datepick._normaliseDate(dateSpec))));
647		return dateSpec;
648	},
649
650	/* Find the number of days in a given month.
651	   @param  year   (Date) the date to get days for or
652	                  (number) the full year
653	   @param  month  (number) the month (1 to 12)
654	   @return  (number) the number of days in this month */
655	daysInMonth: function(year, month) {
656		month = (year.getFullYear ? year.getMonth() + 1 : month);
657		year = (year.getFullYear ? year.getFullYear() : year);
658		return this.newDate(year, month + 1, 0).getDate();
659	},
660
661	/* Calculate the day of the year for a date.
662	   @param  year   (Date) the date to get the day-of-year for or
663	                  (number) the full year
664	   @param  month  (number) the month (1-12)
665	   @param  day    (number) the day
666	   @return  (number) the day of the year */
667	dayOfYear: function(year, month, day) {
668		var date = (year.getFullYear ? year : this.newDate(year, month, day));
669		var newYear = this.newDate(date.getFullYear(), 1, 1);
670		return Math.floor((date.getTime() - newYear.getTime()) / this._msPerDay) + 1;
671	},
672
673	/* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition.
674	   @param  year   (Date) the date to get the week for or
675	                  (number) the full year
676	   @param  month  (number) the month (1-12)
677	   @param  day    (number) the day
678	   @return  (number) the number of the week within the year that contains this date */
679	iso8601Week: function(year, month, day) {
680		var checkDate = (year.getFullYear ?
681			new Date(year.getTime()) : this.newDate(year, month, day));
682		// Find Thursday of this week starting on Monday
683		checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7));
684		var time = checkDate.getTime();
685		checkDate.setMonth(0, 1); // Compare with Jan 1
686		return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
687	},
688
689	/* Return today's date.
690	   @return  (Date) today */
691	today: function() {
692		return this._normaliseDate(new Date());
693	},
694
695	/* Return a new date.
696	   @param  year   (Date) the date to clone or
697					  (number) the year
698	   @param  month  (number) the month (1-12)
699	   @param  day    (number) the day
700	   @return  (Date) the date */
701	newDate: function(year, month, day) {
702		return (!year ? null : (year.getFullYear ? this._normaliseDate(new Date(year.getTime())) :
703			new Date(year, month - 1, day, 12)));
704	},
705
706	/* Standardise a date into a common format - time portion is 12 noon.
707	   @param  date  (Date) the date to standardise
708	   @return  (Date) the normalised date */
709	_normaliseDate: function(date) {
710		if (date) {
711			date.setHours(12, 0, 0, 0);
712		}
713		return date;
714	},
715
716	/* Set the year for a date.
717	   @param  date  (Date) the original date
718	   @param  year  (number) the new year
719	   @return  the updated date */
720	year: function(date, year) {
721		date.setFullYear(year);
722		return this._normaliseDate(date);
723	},
724
725	/* Set the month for a date.
726	   @param  date   (Date) the original date
727	   @param  month  (number) the new month (1-12)
728	   @return  the updated date */
729	month: function(date, month) {
730		date.setMonth(month - 1);
731		return this._normaliseDate(date);
732	},
733
734	/* Set the day for a date.
735	   @param  date  (Date) the original date
736	   @param  day   (number) the new day of the month
737	   @return  the updated date */
738	day: function(date, day) {
739		date.setDate(day);
740		return this._normaliseDate(date);
741	},
742
743	/* Add a number of periods to a date.
744	   @param  date    (Date) the original date
745	   @param  amount  (number) the number of periods
746	   @param  period  (string) the type of period d/w/m/y
747	   @return  the updated date */
748	add: function(date, amount, period) {
749		if (period == 'd' || period == 'w') {
750			this._normaliseDate(date);
751			date.setDate(date.getDate() + amount * (period == 'w' ? 7 : 1));
752		}
753		else {
754			var year = date.getFullYear() + (period == 'y' ? amount : 0);
755			var month = date.getMonth() + (period == 'm' ? amount : 0);
756			date.setTime($.datepick.newDate(year, month + 1,
757				Math.min(date.getDate(), this.daysInMonth(year, month + 1))).getTime());
758		}
759		return date;
760	},
761
762	/* Attach the datepicker functionality to an input field.
763	   @param  target    (element) the control to affect
764	   @param  settings  (object) the custom options for this instance */
765	_attachPicker: function(target, settings) {
766		target = $(target);
767		if (target.hasClass(this.markerClass)) {
768			return;
769		}
770		target.addClass(this.markerClass);
771		var inst = {target: target, selectedDates: [], drawDate: null, pickingRange: false,
772			inline: ($.inArray(target[0].nodeName.toLowerCase(), ['div', 'span']) > -1),
773			get: function(name) { // Get a setting value, defaulting if necessary
774				var value = this.settings[name] !== undefined ?
775					this.settings[name] : $.datepick._defaults[name];
776				if ($.inArray(name, ['defaultDate', 'minDate', 'maxDate']) > -1) { // Decode date settings
777					value = $.datepick.determineDate(
778						value, null, this.selectedDates[0], this.get('dateFormat'), inst.getConfig());
779				}
780				return value;
781			},
782			curMinDate: function() {
783				return (this.pickingRange ? this.selectedDates[0] : this.get('minDate'));
784			},
785			getConfig: function() {
786				return {dayNamesShort: this.get('dayNamesShort'), dayNames: this.get('dayNames'),
787					monthNamesShort: this.get('monthNamesShort'), monthNames: this.get('monthNames'),
788					calculateWeek: this.get('calculateWeek'),
789					shortYearCutoff: this.get('shortYearCutoff')};
790			}
791		};
792		$.data(target[0], this.dataName, inst);
793		var inlineSettings = ($.fn.metadata ? target.metadata() : {});
794		inst.settings = $.extend({}, settings || {}, inlineSettings || {});
795		if (inst.inline) {
796			this._update(target[0]);
797			if ($.fn.mousewheel) {
798				target.mousewheel(this._doMouseWheel);
799			}
800		}
801		else {
802			this._attachments(target, inst);
803			target.bind('keydown.' + this.dataName, this._keyDown).
804				bind('keypress.' + this.dataName, this._keyPress).
805				bind('keyup.' + this.dataName, this._keyUp);
806			if (target.attr('disabled')) {
807				this.disable(target[0]);
808			}
809		}
810	},
811
812	/* Retrieve the settings for a datepicker control.
813	   @param  target  (element) the control to affect
814	   @param  name    (string) the name of the setting (optional)
815	   @return  (object) the current instance settings (name == 'all') or
816	            (object) the default settings (no name) or
817	            (any) the setting value (name supplied) */
818	options: function(target, name) {
819		var inst = $.data(target, this.dataName);
820		return (inst ? (name ? (name == 'all' ?
821			inst.settings : inst.settings[name]) : $.datepick._defaults) : {});
822	},
823
824	/* Reconfigure the settings for a datepicker control.
825	   @param  target    (element) the control to affect
826	   @param  settings  (object) the new options for this instance or
827	                     (string) an individual property name
828	   @param  value     (any) the individual property value (omit if settings is an object) */
829	option: function(target, settings, value) {
830		target = $(target);
831		if (!target.hasClass(this.markerClass)) {
832			return;
833		}
834		settings = settings || {};
835		if (typeof settings == 'string') {
836			var name = settings;
837			settings = {};
838			settings[name] = value;
839		}
840		var inst = $.data(target[0], this.dataName);
841		var dates = inst.selectedDates;
842		extendRemove(inst.settings, settings);
843		this.setDate(target[0], dates, null, false, true);
844		inst.pickingRange = false;
845		inst.drawDate = $.datepick.newDate(this._checkMinMax(
846			(settings.defaultDate ? inst.get('defaultDate') : inst.drawDate) ||
847			inst.get('defaultDate') || $.datepick.today(), inst));
848		if (!inst.inline) {
849			this._attachments(target, inst);
850		}
851		if (inst.inline || inst.div) {
852			this._update(target[0]);
853		}
854	},
855
856	/* Attach events and trigger, if necessary.
857	   @param  target  (jQuery) the control to affect
858	   @param  inst    (object) the current instance settings */
859	_attachments: function(target, inst) {
860		target.unbind('focus.' + this.dataName);
861		if (inst.get('showOnFocus')) {
862			target.bind('focus.' + this.dataName, this.show);
863		}
864		if (inst.trigger) {
865			inst.trigger.remove();
866		}
867		var trigger = inst.get('showTrigger');
868		inst.trigger = (!trigger ? $([]) :
869			$(trigger).clone().removeAttr('id').addClass(this._triggerClass)
870				[inst.get('isRTL') ? 'insertBefore' : 'insertAfter'](target).
871				click(function() {
872					if (!$.datepick.isDisabled(target[0])) {
873						$.datepick[$.datepick.curInst == inst ?
874							'hide' : 'show'](target[0]);
875					}
876				}));
877		this._autoSize(target, inst);
878		var dates = this._extractDates(inst, target.val());
879		if (dates) {
880			this.setDate(target[0], dates, null, true);
881		}
882		if (inst.get('selectDefaultDate') && inst.get('defaultDate') &&
883				inst.selectedDates.length == 0) {
884			this.setDate(target[0], $.datepick.newDate(inst.get('defaultDate') || $.datepick.today()));
885		}
886	},
887
888	/* Apply the maximum length for the date format.
889	   @param  inst  (object) the current instance settings */
890	_autoSize: function(target, inst) {
891		if (inst.get('autoSize') && !inst.inline) {
892			var date = $.datepick.newDate(2009, 10, 20); // Ensure double digits
893			var dateFormat = inst.get('dateFormat');
894			if (dateFormat.match(/[DM]/)) {
895				var findMax = function(names) {
896					var max = 0;
897					var maxI = 0;
898					for (var i = 0; i < names.length; i++) {
899						if (names[i].length > max) {
900							max = names[i].length;
901							maxI = i;
902						}
903					}
904					return maxI;
905				};
906				date.setMonth(findMax(inst.get(dateFormat.match(/MM/) ? // Longest month
907					'monthNames' : 'monthNamesShort')));
908				date.setDate(findMax(inst.get(dateFormat.match(/DD/) ? // Longest day
909					'dayNames' : 'dayNamesShort')) + 20 - date.getDay());
910			}
911			inst.target.attr('size', $.datepick.formatDate(dateFormat, date, inst.getConfig()).length);
912		}
913	},
914
915	/* Remove the datepicker functionality from a control.
916	   @param  target  (element) the control to affect */
917	destroy: function(target) {
918		target = $(target);
919		if (!target.hasClass(this.markerClass)) {
920			return;
921		}
922		var inst = $.data(target[0], this.dataName);
923		if (inst.trigger) {
924			inst.trigger.remove();
925		}
926		target.removeClass(this.markerClass).empty().unbind('.' + this.dataName);
927		if (inst.inline && $.fn.mousewheel) {
928			target.unmousewheel();
929		}
930		if (!inst.inline && inst.get('autoSize')) {
931			target.removeAttr('size');
932		}
933		$.removeData(target[0], this.dataName);
934	},
935
936	/* Apply multiple event functions.
937	   Usage, for example: onShow: multipleEvents(fn1, fn2, ...)
938	   @param  fns  (function...) the functions to apply */
939	multipleEvents: function(fns) {
940		var funcs = arguments;
941		return function(args) {
942			for (var i = 0; i < funcs.length; i++) {
943				funcs[i].apply(this, arguments);
944			}
945		};
946	},
947
948	/* Enable the datepicker and any associated trigger.
949	   @param  target  (element) the control to use */
950	enable: function(target) {
951		var $target = $(target);
952		if (!$target.hasClass(this.markerClass)) {
953			return;
954		}
955		var inst = $.data(target, this.dataName);
956		if (inst.inline)
957			$target.children('.' + this._disableClass).remove().end().
958				find('button,select').attr('disabled', '').end().
959				find('a').attr('href', 'javascript:void(0)');
960		else {
961			target.disabled = false;
962			inst.trigger.filter('button.' + this._triggerClass).
963				attr('disabled', '').end().
964				filter('img.' + this._triggerClass).
965				css({opacity: '1.0', cursor: ''});
966		}
967		this._disabled = $.map(this._disabled,
968			function(value) { return (value == target ? null : value); }); // Delete entry
969	},
970
971	/* Disable the datepicker and any associated trigger.
972	   @param  target  (element) the control to use */
973	disable: function(target) {
974		var $target = $(target);
975		if (!$target.hasClass(this.markerClass))
976			return;
977		var inst = $.data(target, this.dataName);
978		if (inst.inline) {
979			var inline = $target.children(':last');
980			var offset = inline.offset();
981			var relOffset = {left: 0, top: 0};
982			inline.parents().each(function() {
983				if ($(this).css('position') == 'relative') {
984					relOffset = $(this).offset();
985					return false;
986				}
987			});
988			var zIndex = $target.css('zIndex');
989			zIndex = (zIndex == 'auto' ? 0 : parseInt(zIndex, 10)) + 1;
990			$target.prepend('<div class="' + this._disableClass + '" style="' +
991				'width: ' + inline.outerWidth() + 'px; height: ' + inline.outerHeight() +
992				'px; left: ' + (offset.left - relOffset.left) + 'px; top: ' +
993				(offset.top - relOffset.top) + 'px; z-index: ' + zIndex + '"></div>').
994				find('button,select').attr('disabled', 'disabled').end().
995				find('a').removeAttr('href');
996		}
997		else {
998			target.disabled = true;
999			inst.trigger.filter('button.' + this._triggerClass).
1000				attr('disabled', 'disabled').end().
1001				filter('img.' + this._triggerClass).
1002				css({opacity: '0.5', cursor: 'default'});
1003		}
1004		this._disabled = $.map(this._disabled,
1005			function(value) { return (value == target ? null : value); }); // Delete entry
1006		this._disabled.push(target);
1007	},
1008
1009	/* Is the first field in a jQuery collection disabled as a datepicker?
1010	   @param  target  (element) the control to examine
1011	   @return  (boolean) true if disabled, false if enabled */
1012	isDisabled: function(target) {
1013		return (target && $.inArray(target, this._disabled) > -1);
1014	},
1015
1016	/* Show a popup datepicker.
1017	   @param  target  (event) a focus event or
1018	                   (element) the control to use */
1019	show: function(target) {
1020		target = target.target || target;
1021		var inst = $.data(target, $.datepick.dataName);
1022		if ($.datepick.curInst == inst) {
1023			return;
1024		}
1025		if ($.datepick.curInst) {
1026			$.datepick.hide($.datepick.curInst, true);
1027		}
1028		if (inst) {
1029			// Retrieve existing date(s)
1030			inst.lastVal = null;
1031			inst.selectedDates = $.datepick._extractDates(inst, $(target).val());
1032			inst.pickingRange = false;
1033			inst.drawDate = $.datepick._checkMinMax($.datepick.newDate(inst.selectedDates[0] ||
1034				inst.get('defaultDate') || $.datepick.today()), inst);
1035			inst.prevDate = $.datepick.newDate(inst.drawDate);
1036			$.datepick.curInst = inst;
1037			// Generate content
1038			$.datepick._update(target, true);
1039			// Adjust position before showing
1040			var offset = $.datepick._checkOffset(inst);
1041			inst.div.css({left: offset.left, top: offset.top});
1042			// And display
1043			var showAnim = inst.get('showAnim');
1044			var showSpeed = inst.get('showSpeed');
1045			showSpeed = (showSpeed == 'normal' && $.ui && $.ui.version >= '1.8' ?
1046				'_default' : showSpeed);
1047			var postProcess = function() {
1048				var borders = $.datepick._getBorders(inst.div);
1049				inst.div.find('.' + $.datepick._coverClass). // IE6- only
1050					css({left: -borders[0], top: -borders[1],
1051						width: inst.div.outerWidth() + borders[0],
1052						height: inst.div.outerHeight() + borders[1]});
1053			};
1054			if ($.effects && $.effects[showAnim]) {
1055				var data = inst.div.data(); // Update old effects data
1056				for (var key in data) {
1057					if (key.match(/^ec\.storage\./)) {
1058						data[key] = inst._mainDiv.css(key.replace(/ec\.storage\./, ''));
1059					}
1060				}
1061				inst.div.data(data).show(showAnim, inst.get('showOptions'), showSpeed, postProcess);
1062			}
1063			else {
1064				inst.div[showAnim || 'show']((showAnim ? showSpeed : ''), postProcess);
1065			}
1066			if (!showAnim) {
1067				postProcess();
1068			}
1069		}
1070	},
1071
1072	/* Extract possible dates from a string.
1073	   @param  inst  (object) the current instance settings
1074	   @param  text  (string) the text to extract from
1075	   @return  (CDate[]) the extracted dates */
1076	_extractDates: function(inst, datesText) {
1077		if (datesText == inst.lastVal) {
1078			return;
1079		}
1080		inst.lastVal = datesText;
1081		var dateFormat = inst.get('dateFormat');
1082		var multiSelect = inst.get('multiSelect');
1083		var rangeSelect = inst.get('rangeSelect');
1084		datesText = datesText.split(multiSelect ? inst.get('multiSeparator') :
1085			(rangeSelect ? inst.get('rangeSeparator') : '\x00'));
1086		var dates = [];
1087		for (var i = 0; i < datesText.length; i++) {
1088			try {
1089				var date = $.datepick.parseDate(dateFormat, datesText[i], inst.getConfig());
1090				if (date) {
1091					var found = false;
1092					for (var j = 0; j < dates.length; j++) {
1093						if (dates[j].getTime() == date.getTime()) {
1094							found = true;
1095							break;
1096						}
1097					}
1098					if (!found) {
1099						dates.push(date);
1100					}
1101				}
1102			}
1103			catch (e) {
1104				// Ignore
1105			}
1106		}
1107		dates.splice(multiSelect || (rangeSelect ? 2 : 1), dates.length);
1108		if (rangeSelect && dates.length == 1) {
1109			dates[1] = dates[0];
1110		}
1111		return dates;
1112	},
1113
1114	/* Update the datepicker display.
1115	   @param  target  (event) a focus event or
1116	                   (element) the control to use
1117	   @param  hidden  (boolean) true to initially hide the datepicker */
1118	_update: function(target, hidden) {
1119		target = $(target.target || target);
1120		var inst = $.data(target[0], $.datepick.dataName);
1121		if (inst) {
1122			if (inst.inline) {
1123				target.html(this._generateContent(target[0], inst));
1124			}
1125			else if ($.datepick.curInst == inst) {
1126				if (!inst.div) {
1127					inst.div = $('<div></div>').addClass(this._popupClass).
1128						css({display: (hidden ? 'none' : 'static'), position: 'absolute',
1129							left: target.offset().left,
1130							top: target.offset().top + target.outerHeight()}).
1131						appendTo($(inst.get('popupContainer') || 'body'));
1132					if ($.fn.mousewheel) {
1133						inst.div.mousewheel(this._doMouseWheel);
1134					}
1135				}
1136				inst.div.html(this._generateContent(target[0], inst));
1137				target.focus();
1138			}
1139			if (inst.inline || $.datepick.curInst == inst) {
1140				var onChange = inst.get('onChangeMonthYear');
1141				if (onChange && (!inst.prevDate ||
1142						inst.prevDate.getFullYear() != inst.drawDate.getFullYear() ||
1143						inst.prevDate.getMonth() != inst.drawDate.getMonth())) {
1144					onChange.apply(target[0], [inst.drawDate.getFullYear(), inst.drawDate.getMonth() + 1]);
1145				}
1146			}
1147		}
1148	},
1149
1150	/* Update the input field and any alternate field with the current dates.
1151	   @param  target  (element) the control to use
1152	   @param  keyUp   (boolean, internal) true if coming from keyUp processing */
1153	_updateInput: function(target, keyUp) {
1154		var inst = $.data(target, this.dataName);
1155		if (inst) {
1156			var value = '';
1157			var altValue = '';
1158			var sep = (inst.get('multiSelect') ? inst.get('multiSeparator') :
1159				inst.get('rangeSeparator'));
1160			var dateFormat = inst.get('dateFormat');
1161			var altFormat = inst.get('altFormat') || dateFormat;
1162			for (var i = 0; i < inst.selectedDates.length; i++) {
1163				value += (keyUp ? '' : (i > 0 ? sep : '') + $.datepick.formatDate(
1164					dateFormat, inst.selectedDates[i], inst.getConfig()));
1165				altValue += (i > 0 ? sep : '') + $.datepick.formatDate(
1166					altFormat, inst.selectedDates[i], inst.getConfig());
1167			}
1168			if (!inst.inline && !keyUp) {
1169				$(target).val(value);
1170			}
1171			$(inst.get('altField')).val(altValue);
1172			var onSelect = inst.get('onSelect');
1173			if (onSelect && !keyUp && !inst.inSelect) {
1174				inst.inSelect = true; // Prevent endless loops
1175				onSelect.apply(target, [inst.selectedDates]);
1176				inst.inSelect = false;
1177			}
1178		}
1179	},
1180
1181	/* Retrieve the size of left and top borders for an element.
1182	   @param  elem  (jQuery) the element of interest
1183	   @return  (number[2]) the left and top borders */
1184	_getBorders: function(elem) {
1185		var convert = function(value) {
1186			var extra = ($.browser.msie ? 1 : 0);
1187			return {thin: 1 + extra, medium: 3 + extra, thick: 5 + extra}[value] || value;
1188		};
1189		return [parseFloat(convert(elem.css('border-left-width'))),
1190			parseFloat(convert(elem.css('border-top-width')))];
1191	},
1192
1193	/* Check positioning to remain on the screen.
1194	   @param  inst  (object) the current instance settings
1195	   @return  (object) the updated offset for the datepicker */
1196	_checkOffset: function(inst) {
1197		var base = (inst.target.is(':hidden') && inst.trigger ? inst.trigger : inst.target);
1198		var offset = base.offset();
1199		var isFixed = false;
1200		$(inst.target).parents().each(function() {
1201			isFixed |= $(this).css('position') == 'fixed';
1202			return !isFixed;
1203		});
1204		if (isFixed && $.browser.opera) { // Correction for Opera when fixed and scrolled
1205			offset.left -= document.documentElement.scrollLeft;
1206			offset.top -= document.documentElement.scrollTop;
1207		}
1208		var browserWidth = (!$.browser.mozilla || document.doctype ?
1209			document.documentElement.clientWidth : 0) || document.body.clientWidth;
1210		var browserHeight = (!$.browser.mozilla || document.doctype ?
1211			document.documentElement.clientHeight : 0) || document.body.clientHeight;
1212		if (browserWidth == 0) {
1213			return offset;
1214		}
1215		var alignment = inst.get('alignment');
1216		var isRTL = inst.get('isRTL');
1217		var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
1218		var scrollY = document.documentElement.scrollTop || document.body.scrollTop;
1219		var above = offset.top - inst.div.outerHeight() -
1220			(isFixed && $.browser.opera ? document.documentElement.scrollTop : 0);
1221		var below = offset.top + base.outerHeight();
1222		var alignL = offset.left;
1223		var alignR = offset.left + base.outerWidth() - inst.div.outerWidth() -
1224			(isFixed && $.browser.opera ? document.documentElement.scrollLeft : 0);
1225		var tooWide = (offset.left + inst.div.outerWidth() - scrollX) > browserWidth;
1226		var tooHigh = (offset.top + inst.target.outerHeight() + inst.div.outerHeight() -
1227			scrollY) > browserHeight;
1228		if (alignment == 'topLeft') {
1229			offset = {left: alignL, top: above};
1230		}
1231		else if (alignment == 'topRight') {
1232			offset = {left: alignR, top: above};
1233		}
1234		else if (alignment == 'bottomLeft') {
1235			offset = {left: alignL, top: below};
1236		}
1237		else if (alignment == 'bottomRight') {
1238			offset = {left: alignR, top: below};
1239		}
1240		else if (alignment == 'top') {
1241			offset = {left: (isRTL || tooWide ? alignR : alignL), top: above};
1242		}
1243		else { // bottom
1244			offset = {left: (isRTL || tooWide ? alignR : alignL),
1245				top: (tooHigh ? above : below)};
1246		}
1247		offset.left = Math.max((isFixed ? 0 : scrollX), offset.left - (isFixed ? scrollX : 0));
1248		offset.top = Math.max((isFixed ? 0 : scrollY), offset.top - (isFixed ? scrollY : 0));
1249		return offset;
1250	},
1251
1252	/* Close date picker if clicked elsewhere.
1253	   @param  event  (MouseEvent) the mouse click to check */
1254	_checkExternalClick: function(event) {
1255		if (!$.datepick.curInst) {
1256			return;
1257		}
1258		var target = $(event.target);
1259		if (!target.parents().andSelf().hasClass($.datepick._popupClass) &&
1260				!target.hasClass($.datepick.markerClass) &&
1261				!target.parents().andSelf().hasClass($.datepick._triggerClass)) {
1262			$.datepick.hide($.datepick.curInst);
1263		}
1264	},
1265
1266	/* Hide a popup datepicker.
1267	   @param  target     (element) the control to use or
1268	                      (object) the current instance settings
1269	   @param  immediate  (boolean) true to close immediately without animation */
1270	hide: function(target, immediate) {
1271		var inst = $.data(target, this.dataName) || target;
1272		if (inst && inst == $.datepick.curInst) {
1273			var showAnim = (immediate ? '' : inst.get('showAnim'));
1274			var showSpeed = inst.get('showSpeed');
1275			showSpeed = (showSpeed == 'normal' && $.ui && $.ui.version >= '1.8' ?
1276				'_default' : showSpeed);
1277			var postProcess = function() {
1278				inst.div.remove();
1279				inst.div = null;
1280				$.datepick.curInst = null;
1281				var onClose = inst.get('onClose');
1282				if (onClose) {
1283					onClose.apply(target, [inst.selectedDates]);
1284				}
1285			};
1286			inst.div.stop();
1287			if ($.effects && $.effects[showAnim]) {
1288				inst.div.hide(showAnim, inst.get('showOptions'), showSpeed, postProcess);
1289			}
1290			else {
1291				var hideAnim = (showAnim == 'slideDown' ? 'slideUp' :
1292					(showAnim == 'fadeIn' ? 'fadeOut' : 'hide'));
1293				inst.div[hideAnim]((showAnim ? showSpeed : ''), postProcess);
1294			}
1295			if (!showAnim) {
1296				postProcess();
1297			}
1298		}
1299	},
1300
1301	/* Handle keystrokes in the datepicker.
1302	   @param  event  (KeyEvent) the keystroke
1303	   @return  (boolean) true if not handled, false if handled */
1304	_keyDown: function(event) {
1305		var target = event.target;
1306		var inst = $.data(target, $.datepick.dataName);
1307		var handled = false;
1308		if (inst.div) {
1309			if (event.keyCode == 9) { // Tab - close
1310				$.datepick.hide(target);
1311			}
1312			else if (event.keyCode == 13) { // Enter - select
1313				$.datepick.selectDate(target,
1314					$('a.' + inst.get('renderer').highlightedClass, inst.div)[0]);
1315				handled = true;
1316			}
1317			else { // Command keystrokes
1318				var commands = inst.get('commands');
1319				for (var name in commands) {
1320					var command = commands[name];
1321					if (command.keystroke.keyCode == event.keyCode &&
1322							!!command.keystroke.ctrlKey == !!(event.ctrlKey || event.metaKey) &&
1323							!!command.keystroke.altKey == event.altKey &&
1324							!!command.keystroke.shiftKey == event.shiftKey) {
1325						$.datepick.performAction(target, name);
1326						handled = true;
1327						break;
1328					}
1329				}
1330			}
1331		}
1332		else { // Show on 'current' keystroke
1333			var command = inst.get('commands').current;
1334			if (command.keystroke.keyCode == event.keyCode &&
1335					!!command.keystroke.ctrlKey == !!(event.ctrlKey || event.metaKey) &&
1336					!!command.keystroke.altKey == event.altKey &&
1337					!!command.keystroke.shiftKey == event.shiftKey) {
1338				$.datepick.show(target);
1339				handled = true;
1340			}
1341		}
1342		inst.ctrlKey = ((event.keyCode < 48 && event.keyCode != 32) ||
1343			event.ctrlKey || event.metaKey);
1344		if (handled) {
1345			event.preventDefault();
1346			event.stopPropagation();
1347		}
1348		return !handled;
1349	},
1350
1351	/* Filter keystrokes in the datepicker.
1352	   @param  event  (KeyEvent) the keystroke
1353	   @return  (boolean) true if allowed, false if not allowed */
1354	_keyPress: function(event) {
1355		var target = event.target;
1356		var inst = $.data(target, $.datepick.dataName);
1357		if (inst && inst.get('constrainInput')) {
1358			var ch = String.fromCharCode(event.keyCode || event.charCode);
1359			var allowedChars = $.datepick._allowedChars(inst);
1360			return (event.metaKey || inst.ctrlKey || ch < ' ' ||
1361				!allowedChars || allowedChars.indexOf(ch) > -1);
1362		}
1363		return true;
1364	},
1365
1366	/* Determine the set of characters allowed by the date format.
1367	   @param  inst  (object) the current instance settings
1368	   @return  (string) the set of allowed characters, or null if anything allowed */
1369	_allowedChars: function(inst) {
1370		var dateFormat = inst.get('dateFormat');
1371		var allowedChars = (inst.get('multiSelect') ? inst.get('multiSeparator') :
1372			(inst.get('rangeSelect') ? inst.get('rangeSeparator') : ''));
1373		var literal = false;
1374		var hasNum = false;
1375		for (var i = 0; i < dateFormat.length; i++) {
1376			var ch = dateFormat.charAt(i);
1377			if (literal) {
1378				if (ch == "'" && dateFormat.charAt(i + 1) != "'") {
1379					literal = false;
1380				}
1381				else {
1382					allowedChars += ch;
1383				}
1384			}
1385			else {
1386				switch (ch) {
1387					case 'd': case 'm': case 'o': case 'w':
1388						allowedChars += (hasNum ? '' : '0123456789'); hasNum = true; break;
1389					case 'y': case '@': case '!':
1390						allowedChars += (hasNum ? '' : '0123456789') + '-'; hasNum = true; break;
1391					case 'J':
1392						allowedChars += (hasNum ? '' : '0123456789') + '-.'; hasNum = true; break;
1393					case 'D': case 'M': case 'Y':
1394						return null; // Accept anything
1395					case "'":
1396						if (dateFormat.charAt(i + 1) == "'") {
1397							allowedChars += "'";
1398						}
1399						else {
1400							literal = true;
1401						}
1402						break;
1403					default:
1404						allowedChars += ch;
1405				}
1406			}
1407		}
1408		return allowedChars;
1409	},
1410
1411	/* Synchronise datepicker with the field.
1412	   @param  event  (KeyEvent) the keystroke
1413	   @return  (boolean) true if allowed, false if not allowed */
1414	_keyUp: function(event) {
1415		var target = event.target;
1416		var inst = $.data(target, $.datepick.dataName);
1417		if (inst && !inst.ctrlKey && inst.lastVal != inst.target.val()) {
1418			try {
1419				var dates = $.datepick._extractDates(inst, inst.target.val());
1420				if (dates.length > 0) {
1421					$.datepick.setDate(target, dates, null, true);
1422				}
1423			}
1424			catch (event) {
1425				// Ignore
1426			}
1427		}
1428		return true;
1429	},
1430
1431	/* Increment/decrement month/year on mouse wheel activity.
1432	   @param  event  (event) the mouse wheel event
1433	   @param  delta  (number) the amount of change */
1434	_doMouseWheel: function(event, delta) {
1435		var target = ($.datepick.curInst && $.datepick.curInst.target[0]) ||
1436			$(event.target).closest('.' + $.datepick.markerClass)[0];
1437		if ($.datepick.isDisabled(target)) {
1438			return;
1439		}
1440		var inst = $.data(target, $.datepick.dataName);
1441		if (inst.get('useMouseWheel')) {
1442			delta = ($.browser.opera ? -delta : delta);
1443			delta = (delta < 0 ? -1 : +1);
1444			$.datepick.changeMonth(target,
1445				-inst.get(event.ctrlKey ? 'monthsToJump' : 'monthsToStep') * delta);
1446		}
1447		event.preventDefault();
1448	},
1449
1450	/* Clear an input and close a popup datepicker.
1451	   @param  target  (element) the control to use */
1452	clear: function(target) {
1453		var inst = $.data(target, this.dataName);
1454		if (inst) {
1455			inst.selectedDates = [];
1456			this.hide(target);
1457			if (inst.get('selectDefaultDate') && inst.get('defaultDate')) {
1458				this.setDate(target,
1459					$.datepick.newDate(inst.get('defaultDate') ||$.datepick.today()));
1460			}
1461			else {
1462				this._updateInput(target);
1463			}
1464		}
1465	},
1466
1467	/* Retrieve the selected date(s) for a datepicker.
1468	   @param  target  (element) the control to examine
1469	   @return  (CDate[]) the selected date(s) */
1470	getDate: function(target) {
1471		var inst = $.data(target, this.dataName);
1472		return (inst ? inst.selectedDates : []);
1473	},
1474
1475	/* Set the selected date(s) for a datepicker.
1476	   @param  target   (element) the control to examine
1477	   @param  dates    (CDate or number or string or [] of these) the selected date(s)
1478	   @param  endDate  (CDate or number or string) the ending date for a range (optional)
1479	   @param  keyUp    (boolean, internal) true if coming from keyUp processing
1480	   @param  setOpt   (boolean, internal) true if coming from option processing */
1481	setDate: function(target, dates, endDate, keyUp, setOpt) {
1482		var inst = $.data(target, this.dataName);
1483		if (inst) {
1484			if (!$.isArray(dates)) {
1485				dates = [dates];
1486				if (endDate) {
1487					dates.push(endDate);
1488				}
1489			}
1490			var dateFormat = inst.get('dateFormat');
1491			var minDate = inst.get('minDate');
1492			var maxDate = inst.get('maxDate');
1493			var curDate = inst.selectedDates[0];
1494			inst.selectedDates = [];
1495			for (var i = 0; i < dates.length; i++) {
1496				var date = $.datepick.determineDate(
1497					dates[i], null, curDate, dateFormat, inst.getConfig());
1498				if (date) {
1499					if ((!minDate || date.getTime() >= minDate.getTime()) &&
1500							(!maxDate || date.getTime() <= maxDate.getTime())) {
1501						var found = false;
1502						for (var j = 0; j < inst.selectedDates.length; j++) {
1503							if (inst.selectedDates[j].getTime() == date.getTime()) {
1504								found = true;
1505								break;
1506							}
1507						}
1508						if (!found) {
1509							inst.selectedDates.push(date);
1510						}
1511					}
1512				}
1513			}
1514			var rangeSelect = inst.get('rangeSelect');
1515			inst.selectedDates.splice(inst.get('multiSelect') ||
1516				(rangeSelect ? 2 : 1), inst.selectedDates.length);
1517			if (rangeSelect) {
1518				switch (inst.selectedDates.length) {
1519					case 1: inst.selectedDates[1] = inst.selectedDates[0]; break;
1520					case 2: inst.selectedDates[1] =
1521						(inst.selectedDates[0].getTime() > inst.selectedDates[1].getTime() ?
1522						inst.selectedDates[0] : inst.selectedDates[1]); break;
1523				}
1524				inst.pickingRange = false;
1525			}
1526			inst.prevDate = (inst.drawDate ? $.datepick.newDate(inst.drawDate) : null);
1527			inst.drawDate = this._checkMinMax($.datepick.newDate(inst.selectedDates[0] ||
1528				inst.get('defaultDate') || $.datepick.today()), inst);
1529			if (!setOpt) {
1530				this._update(target);
1531				this._updateInput(target, keyUp);
1532			}
1533		}
1534	},
1535
1536	/* Determine whether a date is selectable for this datepicker.
1537	   @param  target  (element) the control to check
1538	   @param  date    (Date or string or number) the date to check
1539	   @return  (boolean) true if selectable, false if not */
1540	isSelectable: function(target, date) {
1541		var inst = $.data(target, this.dataName);
1542		if (!inst) {
1543			return false;
1544		}
1545		date = $.datepick.determineDate(date, inst.selectedDates[0] || this.today(), null,
1546			inst.get('dateFormat'), inst.getConfig());
1547		return this._isSelectable(target, date, inst.get('onDate'),
1548			inst.get('minDate'), inst.get('maxDate'));
1549	},
1550
1551	/* Internally determine whether a date is selectable for this datepicker.
1552	   @param  target   (element) the control to check
1553	   @param  date     (Date) the date to check
1554	   @param  onDate   (function or boolean) any onDate callback or callback.selectable
1555	   @param  mindate  (Date) the minimum allowed date
1556	   @param  maxdate  (Date) the maximum allowed date
1557	   @return  (boolean) true if selectable, false if not */
1558	_isSelectable: function(target, date, onDate, minDate, maxDate) {
1559		var dateInfo = (typeof onDate == 'boolean' ? {selectable: onDate} :
1560			(!onDate ? {}
1560 : onDate.apply(target, [date, true])));
1561		return (dateInfo.selectable != false) &&
1562			(!minDate || date.getTime() >= minDate.getTime()) &&
1563			(!maxDate || date.getTime() <= maxDate.getTime());
1564	},
1565
1566	/* Perform a named action for a datepicker.
1567	   @param  target  (element) the control to affect
1568	   @param  action  (string) the name of the action */
1569	performAction: function(target, action) {
1570		var inst = $.data(target, this.dataName);
1571		if (inst && !this.isDisabled(target)) {
1572			var commands = inst.get('commands');
1573			if (commands[action] && commands[action].enabled.apply(target, [inst])) {
1574				commands[action].action.apply(target, [inst]);
1575			}
1576		}
1577	},
1578
1579	/* Set the currently shown month, defaulting to today's.
1580	   @param  target  (element) the control to affect
1581	   @param  year    (number) the year to show (optional)
1582	   @param  month   (number) the month to show (1-12) (optional)
1583	   @param  day     (number) the day to show (optional) */
1584	showMonth: function(target, year, month, day) {
1585		var inst = $.data(target, this.dataName);
1586		if (inst && (day != null ||
1587				(inst.drawDate.getFullYear() != year || inst.drawDate.getMonth() + 1 != month))) {
1588			inst.prevDate = $.datepick.newDate(inst.drawDate);
1589			var show = this._checkMinMax((year != null ?
1590				$.datepick.newDate(year, month, 1) : $.datepick.today()), inst);
1591			inst.drawDate = $.datepick.newDate(show.getFullYear(), show.getMonth() + 1, 
1592				(day != null ? day : Math.min(inst.drawDate.getDate(),
1593				$.datepick.daysInMonth(show.getFullYear(), show.getMonth() + 1))));
1594			this._update(target);
1595		}
1596	},
1597
1598	/* Adjust the currently shown month.
1599	   @param  target  (element) the control to affect
1600	   @param  offset  (number) the number of months to change by */
1601	changeMonth: function(target, offset) {
1602		var inst = $.data(target, this.dataName);
1603		if (inst) {
1604			var date = $.datepick.add($.datepick.newDate(inst.drawDate), offset, 'm');
1605			this.showMonth(target, date.getFullYear(), date.getMonth() + 1);
1606		}
1607	},
1608
1609	/* Adjust the currently shown day.
1610	   @param  target  (element) the control to affect
1611	   @param  offset  (number) the number of days to change by */
1612	changeDay: function(target, offset) {
1613		var inst = $.data(target, this.dataName);
1614		if (inst) {
1615			var date = $.datepick.add($.datepick.newDate(inst.drawDate), offset, 'd');
1616			this.showMonth(target, date.getFullYear(), date.getMonth() + 1, date.getDate());
1617		}
1618	},
1619
1620	/* Restrict a date to the minimum/maximum specified.
1621	   @param  date  (CDate) the date to check
1622	   @param  inst  (object) the current instance settings */
1623	_checkMinMax: function(date, inst) {
1624		var minDate = inst.get('minDate');
1625		var maxDate = inst.get('maxDate');
1626		date = (minDate && date.getTime() < minDate.getTime() ? $.datepick.newDate(minDate) : date);
1627		date = (maxDate && date.getTime() > maxDate.getTime() ? $.datepick.newDate(maxDate) : date);
1628		return date;
1629	},
1630
1631	/* Retrieve the date associated with an entry in the datepicker.
1632	   @param  target  (element) the control to examine
1633	   @param  elem    (element) the selected datepicker element
1634	   @return  (CDate) the corresponding date, or null */
1635	retrieveDate: function(target, elem) {
1636		var inst = $.data(target, this.dataName);
1637		return (!inst ? null : this._normaliseDate(
1638			new Date(parseInt(elem.className.replace(/^.*dp(-?\d+).*$/, '$1'), 10))));
1639	},
1640
1641	/* Select a date for this datepicker.
1642	   @param  target  (element) the control to examine
1643	   @param  elem    (element) the selected datepicker element */
1644	selectDate: function(target, elem) {
1645		var inst = $.data(target, this.dataName);
1646		if (inst && !this.isDisabled(target)) {
1647			var date = this.retrieveDate(target, elem);
1648			var multiSelect = inst.get('multiSelect');
1649			var rangeSelect = inst.get('rangeSelect');
1650			if (multiSelect) {
1651				var found = false;
1652				for (var i = 0; i < inst.selectedDates.length; i++) {
1653					if (date.getTime() == inst.selectedDates[i].getTime()) {
1654						inst.selectedDates.splice(i, 1);
1655						found = true;
1656						break;
1657					}
1658				}
1659				if (!found && inst.selectedDates.length < multiSelect) {
1660					inst.selectedDates.push(date);
1661				}
1662			}
1663			else if (rangeSelect) {
1664				if (inst.pickingRange) {
1665					inst.selectedDates[1] = date;
1666				}
1667				else {
1668					inst.selectedDates = [date, date];
1669				}
1670				inst.pickingRange = !inst.pickingRange;
1671			}
1672			else {
1673				inst.selectedDates = [date];
1674			}
1675			inst.prevDate = $.datepick.newDate(date);
1676			this._updateInput(target);
1677			if (inst.inline || inst.pickingRange || inst.selectedDates.length <
1678					(multiSelect || (rangeSelect ? 2 : 1))) {
1679				this._update(target);
1680			}
1681			else {
1682				this.hide(target);
1683			}
1684		}
1685	},
1686
1687	/* Generate the datepicker content for this control.
1688	   @param  target  (element) the control to affect
1689	   @param  inst    (object) the current instance settings
1690	   @return  (jQuery) the datepicker content */
1691	_generateContent: function(target, inst) {
1692		var renderer = inst.get('renderer');
1693		var monthsToShow = inst.get('monthsToShow');
1694		monthsToShow = ($.isArray(monthsToShow) ? monthsToShow : [1, monthsToShow]);
1695		inst.drawDate = this._checkMinMax(
1696			inst.drawDate || inst.get('defaultDate') || $.datepick.today(), inst);
1697		var drawDate = $.datepick.add(
1698			$.datepick.newDate(inst.drawDate), -inst.get('monthsOffset'), 'm');
1699		// Generate months
1700		var monthRows = '';
1701		for (var row = 0; row < monthsToShow[0]; row++) {
1702			var months = '';
1703			for (var col = 0; col < monthsToShow[1]; col++) {
1704				months += this._generateMonth(target, inst, drawDate.getFullYear(),
1705					drawDate.getMonth() + 1, renderer, (row == 0 && col == 0));
1706				$.datepick.add(drawDate, 1, 'm');
1707			}
1708			monthRows += this._prepare(renderer.monthRow, inst).replace(/\{months\}/, months);
1709		}
1710		var picker = this._prepare(renderer.picker, inst).replace(/\{months\}/, monthRows).
1711			replace(/\{weekHeader\}/g, this._generateDayHeaders(inst, renderer)) +
1712			($.browser.msie && parseInt($.browser.version, 10) < 7 && !inst.inline ?
1713			'<iframe src="javascript:void(0);" class="' + this._coverClass + '"></iframe>' : '');
1714		// Add commands
1715		var commands = inst.get('commands');
1716		var asDateFormat = inst.get('commandsAsDateFormat');
1717		var addCommand = function(type, open, close, name, classes) {
1718			if (picker.indexOf('{' + type + ':' + name + '}') == -1) {
1719				return;
1720			}
1721			var command = commands[name];
1722			var date = (asDateFormat ? command.date.apply(target, [inst]) : null);
1723			picker = picker.replace(new RegExp('\\{' + type + ':' + name + '\\}', 'g'),
1724				'<' + open +
1725				(command.status ? ' title="' + inst.get(command.status) + '"' : '') +
1726				' class="' + renderer.commandClass + ' ' +
1727				renderer.commandClass + '-' + name + ' ' + classes +
1728				(command.enabled(inst) ? '' : ' ' + renderer.disabledClass) + '">' +
1729				(date ? $.datepick.formatDate(inst.get(command.text), date, inst.getConfig()) :
1730				inst.get(command.text)) + '</' + close + '>');
1731		};
1732		for (var name in commands) {
1733			addCommand('button', 'button type="button"', 'button', name,
1734				renderer.commandButtonClass);
1735			addCommand('link', 'a href="javascript:void(0)"', 'a', name,
1736				renderer.commandLinkClass);
1737		}
1738		picker = $(picker);
1739		if (monthsToShow[1] > 1) {
1740			var count = 0;
1741			$(renderer.monthSelector, picker).each(function() {
1742				var nth = ++count % monthsToShow[1];
1743				$(this).addClass(nth == 1 ? 'first' : (nth == 0 ? 'last' : ''));
1744			});
1745		}
1746		// Add datepicker behaviour
1747		var self = this;
1748		picker.find(renderer.daySelector + ' a').hover(
1749				function() { $(this).addClass(renderer.highlightedClass); },
1750				function() {
1751					(inst.inline ? $(this).parents('.' + self.markerClass) : inst.div).
1752						find(renderer.daySelector + ' a').
1753						removeClass(renderer.highlightedClass);
1754				}).
1755			click(function() {
1756				self.selectDate(target, this);
1757			}).end().
1758			find('select.' + this._monthYearClass + ':not(.' + this._anyYearClass + ')').
1759			change(function() {
1760				var monthYear = $(this).val().split('/');
1761				self.showMonth(target, parseInt(monthYear[1], 10), parseInt(monthYear[0], 10));
1762			}).end().
1763			find('select.' + this._anyYearClass).
1764			click(function() {
1765				$(this).css('visibility', 'hidden').
1766					next('input').css({left: this.offsetLeft, top: this.offsetTop,
1767					width: this.offsetWidth, height: this.offsetHeight}).show().focus();
1768			}).end().
1769			find('input.' + self._monthYearClass).
1770			change(function() {
1771				try {
1772					var year = parseInt($(this).val(), 10);
1773					year = (isNaN(year) ? inst.drawDate.getFullYear() : year);
1774					self.showMonth(target, year, inst.drawDate.getMonth() + 1, inst.drawDate.getDate());
1775				}
1776				catch (e) {
1777					alert(e);
1778				}
1779			}).keydown(function(event) {
1780				if (event.keyCode == 13) { // Enter
1781					$(event.target).change();
1782				}
1783				else if (event.keyCode == 27) { // Escape
1784					$(event.target).hide().prev('select').css('visibility', 'visible');
1785					inst.target.focus();
1786				}
1787			});
1788		// Add command behaviour
1789		picker.find('.' + renderer.commandClass).click(function() {
1790				if (!$(this).hasClass(renderer.disabledClass)) {
1791					var action = this.className.replace(
1792						new RegExp('^.*' + renderer.commandClass + '-([^ ]+).*$'), '$1');
1793					$.datepick.performAction(target, action);
1794				}
1795			});
1796		// Add classes
1797		if (inst.get('isRTL')) {
1798			picker.addClass(renderer.rtlClass);
1799		}
1800		if (monthsToShow[0] * monthsToShow[1] > 1) {
1801			picker.addClass(renderer.multiClass);
1802		}
1803		var pickerClass = inst.get('pickerClass');
1804		if (pickerClass) {
1805			picker.addClass(pickerClass);
1806		}
1807		// Resize
1808		$('body').append(picker);
1809		var width = 0;
1810		picker.find(renderer.monthSelector).each(function() {
1811			width += $(this).outerWidth();
1812		});
1813		picker.width(width / monthsToShow[0]);
1814		// Pre-show customisation
1815		var onShow = inst.get('onShow');
1816		if (onShow) {
1817			onShow.apply(target, [picker, inst]);
1818		}
1819		return picker;
1820	},
1821
1822	/* Generate the content for a single month.
1823	   @param  target    (element) the control to affect
1824	   @param  inst      (object) the current instance settings
1825	   @param  year      (number) the year to generate
1826	   @param  month     (number) the month to generate
1827	   @param  renderer  (object) the rendering templates
1828	   @param  first     (boolean) true if first of multiple months
1829	   @return  (string) the month content */
1830	_generateMonth: function(target, inst, year, month, renderer, first) {
1831		var daysInMonth = $.datepick.daysInMonth(year, month);
1832		var monthsToShow = inst.get('monthsToShow');
1833		monthsToShow = ($.isArray(monthsToShow) ? monthsToShow : [1, monthsToShow]);
1834		var fixedWeeks = inst.get('fixedWeeks') || (monthsToShow[0] * monthsToShow[1] > 1);
1835		var firstDay = inst.get('firstDay');
1836		var leadDays = ($.datepick.newDate(year, month, 1).getDay() - firstDay + 7) % 7;
1837		var numWeeks = (fixedWeeks ? 6 : Math.ceil((leadDays + daysInMonth) / 7));
1838		var showOtherMonths = inst.get('showOtherMonths');
1839		var selectOtherMonths = inst.get('selectOtherMonths') && showOtherMonths;
1840		var dayStatus = inst.get('dayStatus');
1841		var minDate = (inst.pickingRange ? inst.selectedDates[0] : inst.get('minDate'));
1842		var maxDate = inst.get('maxDate');
1843		var rangeSelect = inst.get('rangeSelect');
1844		var onDate = inst.get('onDate');
1845		var showWeeks = renderer.week.indexOf('{weekOfYear}') > -1;
1846		var calculateWeek = inst.get('calculateWeek');
1847		var today = $.datepick.today();
1848		var drawDate = $.datepick.newDate(year, month, 1);
1849		$.datepick.add(drawDate, -leadDays - (fixedWeeks && (drawDate.getDay() == firstDay) ? 7 : 0), 'd');
1850		var ts = drawDate.getTime();
1851		// Generate weeks
1852		var weeks = '';
1853		for (var week = 0; week < numWeeks; week++) {
1854			var weekOfYear = (!showWeeks ? '' : '<span class="dp' + ts + '">' +
1855				(calculateWeek ? calculateWeek(drawDate) : 0) + '</span>');
1856			var days = '';
1857			for (var day = 0; day < 7; day++) {
1858				var selected = false;
1859				if (rangeSelect && inst.selectedDates.length > 0) {
1860					selected = (drawDate.getTime() >= inst.selectedDates[0] &&
1861						drawDate.getTime() <= inst.selectedDates[1]);
1862				}
1863				else {
1864					for (var i = 0; i < inst.selectedDates.length; i++) {
1865						if (inst.selectedDates[i].getTime() == drawDate.getTime()) {
1866							selected = true;
1867							break;
1868						}
1869					}
1870				}
1871				var dateInfo = (!onDate ? {} :
1872					onDate.apply(target, [drawDate, drawDate.getMonth() + 1 == month]));
1873				var selectable = (selectOtherMonths || drawDate.getMonth() + 1 == month) &&
1874					this._isSelectable(target, drawDate, dateInfo.selectable, minDate, maxDate);
1875				days += this._prepare(renderer.day, inst).replace(/\{day\}/g,
1876					(selectable ? '<a href="javascript:void(0)"' : '<span') +
1877					' class="dp' + ts + ' ' + (dateInfo.dateClass || '') +
1878					(selected && (selectOtherMonths || drawDate.getMonth() + 1 == month) ?
1879					' ' + renderer.selectedClass : '') +
1880					(selectable ? ' ' + renderer.defaultClass : '') +
1881					((drawDate.getDay() || 7) < 6 ? '' : ' ' + renderer.weekendClass) +
1882					(drawDate.getMonth() + 1 == month ? '' : ' ' + renderer.otherMonthClass) +
1883					(drawDate.getTime() == today.getTime() && (drawDate.getMonth() + 1) == month ?
1884					' ' + renderer.todayClass : '') +
1885					(drawDate.getTime() == inst.drawDate.getTime() && (drawDate.getMonth() + 1) == month ?
1886					' ' + renderer.highlightedClass : '') + '"' +
1887					(dateInfo.title || (dayStatus && selectable) ? ' title="' +
1888					(dateInfo.title || $.datepick.formatDate(
1889					dayStatus, drawDate, inst.getConfig())) + '"' : '') + '>' +
1890					(showOtherMonths || (drawDate.getMonth() + 1) == month ?
1891					dateInfo.content || drawDate.getDate() : '&nbsp;') +
1892					(selectable ? '</a>' : '</span>'));
1893				$.datepick.add(drawDate, 1, 'd');
1894				ts = drawDate.getTime();
1895			}
1896			weeks += this._prepare(renderer.week, inst).replace(/\{days\}/g, days).
1897				replace(/\{weekOfYear\}/g, weekOfYear);
1898		}
1899		var monthHeader = this._prepare(renderer.month, inst).match(/\{monthHeader(:[^\}]+)?\}/);
1900		monthHeader = (monthHeader[0].length <= 13 ? 'MM yyyy' :
1901			monthHeader[0].substring(13, monthHeader[0].length - 1));
1902		monthHeader = (first ? this._generateMonthSelection(
1903			inst, year, month, minDate, maxDate, monthHeader, renderer) :
1904			$.datepick.formatDate(monthHeader, $.datepick.newDate(year, month, 1), inst.getConfig()));
1905		var weekHeader = this._prepare(renderer.weekHeader, inst).
1906			replace(/\{days\}/g, this._generateDayHeaders(inst, renderer));
1907		return this._prepare(renderer.month, inst).replace(/\{monthHeader(:[^\}]+)?\}/g, monthHeader).
1908			replace(/\{weekHeader\}/g, weekHeader).replace(/\{weeks\}/g, weeks);
1909	},
1910
1911	/* Generate the HTML for the day headers.
1912	   @param  inst      (object) the current instance settings
1913	   @param  renderer  (object) the rendering templates
1914	   @return  (string) a week's worth of day headers */
1915	_generateDayHeaders: function(inst, renderer) {
1916		var firstDay = inst.get('firstDay');
1917		var dayNames = inst.get('dayNames');
1918		var dayNamesMin = inst.get('dayNamesMin');
1919		var header = '';
1920		for (var day = 0; day < 7; day++) {
1921			var dow = (day + firstDay) % 7;
1922			header += this._prepare(renderer.dayHeader, inst).replace(/\{day\}/g,
1923				'<span class="' + this._curDoWClass + dow + '" title="' +
1924				dayNames[dow] + '">' + dayNamesMin[dow] + '</span>');
1925		}
1926		return header;
1927	},
1928
1929	/* Generate selection controls for month.
1930	   @param  inst         (object) the current instance settings
1931	   @param  year         (number) the year to generate
1932	   @param  month        (number) the month to generate
1933	   @param  minDate      (CDate) the minimum date allowed
1934	   @param  maxDate      (CDate) the maximum date allowed
1935	   @param  monthHeader  (string) the month/year format
1936	   @return  (string) the month selection content */
1937	_generateMonthSelection: function(inst, year, month, minDate, maxDate, monthHeader) {
1938		if (!inst.get('changeMonth')) {
1939			return $.datepick.formatDate(
1940				monthHeader, $.datepick.newDate(year, month, 1), inst.getConfig());
1941		}
1942		// Months
1943		var monthNames = inst.get('monthNames' + (monthHeader.match(/mm/i) ? '' : 'Short'));
1944		var html = monthHeader.replace(/m+/i, '\\x2E').replace(/y+/i, '\\x2F');
1945		var selector = '<select class="' + this._monthYearClass +
1946			'" title="' + inst.get('monthStatus') + '">';
1947		for (var m = 1; m <= 12; m++) {
1948			if ((!minDate || $.datepick.newDate(year, m, $.datepick.daysInMonth(year, m)).
1949					getTime() >= minDate.getTime()) &&
1950					(!maxDate || $.datepick.newDate(year, m, 1).getTime() <= maxDate.getTime())) {
1951				selector += '<option value="' + m + '/' + year + '"' +
1952					(month == m ? ' selected="selected"' : '') + '>
1952' +
1953					monthNames[m - 1] + '</option>';
1954			}
1955		}
1956		selector += '</select>';
1957		html = html.replace(/\\x2E/, selector);
1958		// Years
1959		var yearRange = inst.get('yearRange');
1960		if (yearRange == 'any') {
1961			selector = '<select class="' + this._monthYearClass + ' ' + this._anyYearClass +
1962				'" title="' + inst.get('yearStatus') + '">' +
1963				'<option>' + year + '</option></select>' +
1964				'<input class="' + this._monthYearClass + ' ' + this._curMonthClass +
1965				month + '" value="' + year + '">';
1966		}
1967		else {
1968			yearRange = yearRange.split(':');
1969			var todayYear = $.datepick.today().getFullYear();
1970			var start = (yearRange[0].match('c[+-].*') ? year + parseInt(yearRange[0].substring(1), 10) :
1971				((yearRange[0].match('[+-].*') ? todayYear : 0) + parseInt(yearRange[0], 10)));
1972			var end = (yearRange[1].match('c[+-].*') ? year + parseInt(yearRange[1].substring(1), 10) :
1973				((yearRange[1].match('[+-].*') ? todayYear : 0) + parseInt(yearRange[1], 10)));
1974			selector = '<select class="' + this._monthYearClass +
1975				'" title="' + inst.get('yearStatus') + '">';
1976			start = $.datepick.add($.datepick.newDate(start + 1, 1, 1), -1, 'd');
1977			end = $.datepick.newDate(end, 1, 1);
1978			var addYear = function(y) {
1979				if (y != 0) {
1980					selector += '<option value="' + month + '/' + y + '"' +
1981						(year == y ? ' selected="selected"' : '') + '>' + y + '</option>';
1982				}
1983			};
1984			if (start.getTime() < end.getTime()) {
1985				start = (minDate && minDate.getTime() > start.getTime() ? minDate : start).getFullYear();
1986				end = (maxDate && maxDate.getTime() < end.getTime() ? maxDate : end).getFullYear();
1987				for (var y = start; y <= end; y++) {
1988					addYear(y);
1989				}
1990			}
1991			else {
1992				start = (maxDate && maxDate.getTime() < start.getTime() ? maxDate : start).getFullYear();
1993				end = (minDate && minDate.getTime() > end.getTime() ? minDate : end).getFullYear();
1994				for (var y = start; y >= end; y--) {
1995					addYear(y);
1996				}
1997			}
1998			selector += '</select>';
1999		}
2000		html = html.replace(/\\x2F/, selector);
2001		return html;
2002	},
2003
2004	/* Prepare a render template for use.
2005	   Exclude popup/inline sections that are not applicable.
2006	   Localise text of the form: {l10n:name}.
2007	   @param  text  (string) the text to localise
2008	   @param  inst  (object) the current instance settings
2009	   @return  (string) the localised text */
2010	_prepare: function(text, inst) {
2011		var replaceSection = function(type, retain) {
2012			while (true) {
2013				var start = text.indexOf('{' + type + ':start}');
2014				if (start == -1) {
2015					return;
2016				}
2017				var end = text.substring(start).indexOf('{' + type + ':end}');
2018				if (end > -1) {
2019					text = text.substring(0, start) +
2020						(retain ? text.substr(start + type.length + 8, end - type.length - 8) : '') +
2021						text.substring(start + end + type.length + 6);
2022				}
2023			}
2024		};
2025		replaceSection('inline', inst.inline);
2026		replaceSection('popup', !inst.inline);
2027		var pattern = /\{l10n:([^\}]+)\}/;
2028		var matches = null;
2029		while (matches = pattern.exec(text)) {
2030			text = text.replace(matches[0], inst.get(matches[1]));
2031		}
2032		return text;
2033	}
2034});
2035
2036/* jQuery extend now ignores nulls!
2037   @param  target  (object) the object to extend
2038   @param  props   (object) the new settings
2039   @return  (object) the updated object */
2040function extendRemove(target, props) {
2041	$.extend(target, props);
2042	for (var name in props)
2043		if (props[name] == null || props[name] == undefined)
2044			target[name] = props[name];
2045	return target;
2046};
2047
2048/* Attach the datepicker functionality to a jQuery selection.
2049   @param  command  (string) the command to run (optional, default 'attach')
2050   @param  options  (object) the new settings to use for these instances (optional)
2051   @return  (jQuery) for chaining further calls */
2052$.fn.datepick = function(options) {
2053	var otherArgs = Array.prototype.slice.call(arguments, 1);
2054	if ($.inArray(options, ['getDate', 'isDisabled', 'isSelectable', 'options', 'retrieveDate']) > -1) {
2055		return $.datepick[options].apply($.datepick, [this[0]].concat(otherArgs));
2056	}
2057	return this.each(function() {
2058		if (typeof options == 'string') {
2059			$.datepick[options].apply($.datepick, [this].concat(otherArgs));
2060		}
2061		else {
2062			$.datepick._attachPicker(this, options || {});
2063		}
2064	});
2065};
2066
2067/* Initialise the datepicker functionality. */
2068$.datepick = new Datepicker(); // singleton instance
2069
2070$(function() {
2071	$(document).mousedown($.datepick._checkExternalClick).
2072		resize(function() { $.datepick.hide($.datepick.curInst); });
2073});
2074
2075})(typeof JQUERY_CULTUREBASE !== 'undefined' ? JQUERY_CULTUREBASE : jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.