1 
  2 /**
  3  * @fileOverview Date parsing and formatting operations without extending the Date built-in object.
  4  * @author Chris Leonello
  5  * @version #VERSION#
  6  */
  7  
  8 (function() {
  9 
 10 	/** 
 11 	 * @description
 12 	 * <p>Object with extended date parsing and formatting capabilities.
 13 	 * This library borrows many concepts and ideas from the Date Instance 
 14 	 * Methods by Ken Snyder along with some parts of Ken's actual code.</p>
 15 	 *
 16 	 * <p>jsDate is fundamentally a new implementation, however.  I takes a
 17 	 * different approach by not extending the built-in Date Object, handling
 18 	 * date parsing in a more robust manner, allowing for multiple formatting
 19 	 * syntaxes and multiple, expandable localization.<p>
 20 	 * 
 21 	 * <p>Ken's origianl Date Instance Methods can be found at:
 22 	 * 
 23 	 * http://kendsnyder.com/sandbox/date/
 24 	 * 
 25 	 * @class
 26 	 * @name jsDate
 27 	 * @param  {String | Number | Array | Date Object | Options Object} arguments Optional arguments, either a parsable date/time string,
 28 	 * a JavaScript timestamp, an array of numbers of form [year, month, day, hours, minutes, seconds, milliseconds],
 29 	 * a Date object, or an options object of form {syntax: "perl", date:some Date} where all options are optional.
 30 	 */
 31 	 
 32     var jsDate = function () {
 33 	
 34     	this.syntax = jsDate.config.syntax;
 35 		this._type = "jsDate";
 36         this.utcOffset = new Date().getTimezoneOffset * 60000;
 37         this.proxy = new Date();
 38 		this.options = {};
 39 		this.locale = jsDate.regional.getLocale();
 40 		this.formatString = '';
 41 
 42         switch ( arguments.length ) {
 43             case 0:
 44                 break;
 45             case 1:
 46 				// other objects either won't have a _type property or,
 47 				// if they do, it shouldn't be set to "jsDate", so
 48 				// assume it is an options argument.
 49 				if (get_type(arguments[0]) == "[object Object]" && arguments[0]._type != "jsDate") {
 50 					var opts = this.options = arguments[0];
 51 					this.syntax= opts.syntax;
 52 					this.proxy = jsDate.createDate(opts.date);
 53 				}
 54 				else {
 55 					this.proxy = jsDate.createDate(arguments[0]);
 56 				}
 57                 break;
 58             default:
 59                 var a = [];
 60                 for ( var i=0; i<arguments.length; i++ ) {
 61                     a.push(arguments[i]);
 62                 }
 63                 this.proxy = new Date( this.utcOffset );
 64                 this.proxy.setFullYear.apply( this.proxy, a.slice(0,3) );
 65                 if ( a.slice(3).length ) {
 66                     this.proxy.setHours.apply( this.proxy, a.slice(3) );
 67                 }
 68                 break;
 69         }
 70     };
 71 	
 72 	/**
 73 	 * @namespace Configuration options that will be used as defaults for all instances on the page.
 74 	 * @property {String} defaultLocale The default locale to use [en].
 75 	 * @property {String} syntax The default syntax to use [perl].
 76 	 */
 77 	jsDate.config = {
 78 		defaultLocale: 'en',
 79 		syntax: 'perl'
 80 	};
 81 		
 82 	/**
 83 	 * Add an arbitrary amount to the currently stored date
 84 	 * 
 85 	 * @param {Number} number      
 86 	 * @param {String} unit
 87 	 * @returns {jsDate}       
 88 	 */
 89 	 
 90 	jsDate.prototype.add = function(number, unit) {
 91 		var factor = multipliers[unit] || multipliers.day;
 92 		if (typeof factor == 'number') {
 93 			this.proxy.setTime(this.proxy.getTime() + (factor * number));
 94 		} else {
 95 			factor.add(this, number);
 96 		}
 97 		return this;
 98 	};
 99 		
100 	/**
101 	 * Create a new jqplot.date object with the same date
102 	 * 
103 	 * @returns {jsDate}
104 	 */  
105 	 
106 	jsDate.prototype.clone = function() {
107 			return new jsDate(this.proxy.getTime());
108 	};
109 
110 	/**
111 	 * Find the difference between this jsDate and another date.
112 	 * 
113 	 * @param {String| Number| Array| jsDate Object| Date Object} dateObj
114 	 * @param {String} unit
115 	 * @param {Boolean} allowDecimal
116 	 * @returns {Number} Number of units difference between dates.
117 	 */
118 	 
119 	jsDate.prototype.diff = function(dateObj, unit, allowDecimal) {
120 		// ensure we have a Date object
121 		dateObj = new jsDate(dateObj);
122 		if (dateObj === null) {
123 			return null;
124 		}
125 		// get the multiplying factor integer or factor function
126 		var factor = multipliers[unit] || multipliers.day;
127 		if (typeof factor == 'number') {
128 			// multiply
129 			var unitDiff = (this.proxy.getTime() - dateObj.proxy.getTime()) / factor;
130 		} else {
131 			// run function
132 			var unitDiff = factor.diff(this.proxy, dateObj.proxy);
133 		}
134 		// if decimals are not allowed, round toward zero
135 		return (allowDecimal ? unitDiff : Math[unitDiff > 0 ? 'floor' : 'ceil'](unitDiff));          
136 	};
137 	
138 	/**
139 	 * Get the abbreviated name of the current week day
140 	 * 
141 	 * @returns {String}
142 	 */   
143 	 
144 	jsDate.prototype.getAbbrDayName = function() {
145 		return jsDate.regional[this.locale]["dayNamesShort"][this.proxy.getDay()];
146 	};
147 	
148 	/**
149 	 * Get the abbreviated name of the current month
150 	 * 
151 	 * @returns {String}
152 	 */
153 	 
154 	jsDate.prototype.getAbbrMonthName = function() {
155 		return jsDate.regional[this.locale]["monthNamesShort"][this.proxy.getMonth()];
156 	};
157 	
158 	/**
159 	 * Get UPPER CASE AM or PM for the current time
160 	 * 
161 	 * @returns {String}
162 	 */
163 	 
164 	jsDate.prototype.getAMPM = function() {
165 		return this.proxy.getHours() >= 12 ? 'PM' : 'AM';
166 	};
167 	
168 	/**
169 	 * Get lower case am or pm for the current time
170 	 * 
171 	 * @returns {String}
172 	 */
173 	 
174 	jsDate.prototype.getAmPm = function() {
175 		return this.proxy.getHours() >= 12 ? 'pm' : 'am';
176 	};
177 	
178 	/**
179 	 * Get the century (19 for 20th Century)
180 	 *
181 	 * @returns {Integer} Century (19 for 20th century).
182 	 */
183 	jsDate.prototype.getCentury = function() { 
184 		return parseInt(this.proxy.getFullYear()/100, 10);
185 	};
186 	
187 	/**
188 	 * Implements Date functionality
189 	 */
190 	jsDate.prototype.getDate = function() {
191 		return this.proxy.getDate();
192 	};
193 	
194 	/**
195 	 * Implements Date functionality
196 	 */
197 	jsDate.prototype.getDay = function() {
198 		return this.proxy.getDay();
199 	};
200 	
201 	/**
202 	 * Get the Day of week 1 (Monday) thru 7 (Sunday)
203 	 * 
204 	 * @returns {Integer} Day of week 1 (Monday) thru 7 (Sunday)
205 	 */
206 	jsDate.prototype.getDayOfWeek = function() { 
207 		var dow = this.proxy.getDay(); 
208 		return dow===0?7:dow; 
209 	};
210 	
211 	/**
212 	 * Get the day of the year
213 	 * 
214 	 * @returns {Integer} 1 - 366, day of the year
215 	 */
216 	jsDate.prototype.getDayOfYear = function() {
217 		var d = this.proxy;
218 		var ms = d - new Date('' + d.getFullYear() + '/1/1 GMT');
219 		ms += d.getTimezoneOffset()*60000;
220 		d = null;
221 		return parseInt(ms/60000/60/24, 10)+1;
222 	};
223 	
224 	/**
225 	 * Get the name of the current week day
226 	 * 
227 	 * @returns {String}
228 	 */  
229 	 
230 	jsDate.prototype.getDayName = function() {
231 		return jsDate.regional[this.locale]["dayNames"][this.proxy.getDay()];
232 	};
233 	
234 	/**
235 	 * Get the week number of the given year, starting with the first Sunday as the first week
236 	 * @returns {Integer} Week number (13 for the 13th full week of the year).
237 	 */
238 	jsDate.prototype.getFullWeekOfYear = function() {
239 		var d = this.proxy;
240 		var doy = this.getDayOfYear();
241 		var rdow = 6-d.getDay();
242 		var woy = parseInt((doy+rdow)/7, 10);
243 		return woy;
244 	};
245 	
246 	/**
247 	 * Implements Date functionality
248 	 */
249 	jsDate.prototype.getFullYear = function() {
250 		return this.proxy.getFullYear();
251 	};
252 	
253 	/**
254 	 * Get the GMT offset in hours and minutes (e.g. +06:30)
255 	 * 
256 	 * @returns {String}
257 	 */
258 	 
259 	jsDate.prototype.getGmtOffset = function() {
260 		// divide the minutes offset by 60
261 		var hours = this.proxy.getTimezoneOffset() / 60;
262 		// decide if we are ahead of or behind GMT
263 		var prefix = hours < 0 ? '+' : '-';
264 		// remove the negative sign if any
265 		hours = Math.abs(hours);
266 		// add the +/- to the padded number of hours to : to the padded minutes
267 		return prefix + addZeros(Math.floor(hours), 2) + ':' + addZeros((hours % 1) * 60, 2);
268 	};
269 	
270 	/**
271 	 * Implements Date functionality
272 	 */
273 	jsDate.prototype.getHours = function() {
274 		return this.proxy.getHours();
275 	};
276 	
277 	/**
278 	 * Get the current hour on a 12-hour scheme
279 	 * 
280 	 * @returns {Integer}
281 	 */
282 	 
283 	jsDate.prototype.getHours12  = function() {
284 		var hours = this.proxy.getHours();
285 		return hours > 12 ? hours - 12 : (hours == 0 ? 12 : hours);
286 	};
287 	
288 	
289 	jsDate.prototype.getIsoWeek = function() {
290 		var d = this.proxy;
291 		var woy = d.getWeekOfYear();
292 		var dow1_1 = (new Date('' + d.getFullYear() + '/1/1')).getDay();
293 		// First week is 01 and not 00 as in the case of %U and %W,
294 		// so we add 1 to the final result except if day 1 of the year
295 		// is a Monday (then %W returns 01).
296 		// We also need to subtract 1 if the day 1 of the year is 
297 		// Friday-Sunday, so the resulting equation becomes:
298 		var idow = woy + (dow1_1 > 4 || dow1_1 <= 1 ? 0 : 1);
299 		if(idow == 53 && (new Date('' + d.getFullYear() + '/12/31')).getDay() < 4)
300 		{
301 			idow = 1;
302 		}
303 		else if(idow === 0)
304 		{
305 			d = new jsDate(new Date('' + (d.getFullYear()-1) + '/12/31'));
306 			idow = d.getIsoWeek();
307 		}
308 		d = null;
309 		return idow;
310 	};
311 	
312 	/**
313 	 * Implements Date functionality
314 	 */
315 	jsDate.prototype.getMilliseconds = function() {
316 		return this.proxy.getMilliseconds();
317 	};
318 	
319 	/**
320 	 * Implements Date functionality
321 	 */
322 	jsDate.prototype.getMinutes = function() {
323 		return this.proxy.getMinutes();
324 	};
325 	
326 	/**
327 	 * Implements Date functionality
328 	 */
329 	jsDate.prototype.getMonth = function() {
330 		return this.proxy.getMonth();
331 	};
332 	
333 	/**
334 	 * Get the name of the current month
335 	 * 
336 	 * @returns {String}
337 	 */
338 	 
339 	jsDate.prototype.getMonthName = function() {
340 		return jsDate.regional[this.locale]["monthNames"][this.proxy.getMonth()];
341 	};
342 	
343 	/**
344 	 * Get the number of the current month, 1-12
345 	 * 
346 	 * @returns {Integer}
347 	 */
348 	 
349 	jsDate.prototype.getMonthNumber = function() {
350 		return this.proxy.getMonth() + 1;
351 	};
352 	
353 	/**
354 	 * Implements Date functionality
355 	 */
356 	jsDate.prototype.getSeconds = function() {
357 		return this.proxy.getSeconds();
358 	};
359 	
360 	/**
361 	 * Return a proper two-digit year integer
362 	 * 
363 	 * @returns {Integer}
364 	 */
365 	 
366 	jsDate.prototype.getShortYear = function() {
367 		return this.proxy.getYear() % 100;
368 	};
369 	
370 	/**
371 	 * Implements Date functionality
372 	 */
373 	jsDate.prototype.getTime = function() {
374 		return this.proxy.getTime();
375 	};
376 	
377 	/**
378 	 * Get the timezone abbreviation
379 	 *
380 	 * @returns {String} Abbreviation for the timezone
381 	 */
382 	jsDate.prototype.getTimezoneAbbr = function() {
383 		return this.proxy.toString().replace(/^.*\(([^)]+)\)$/, '$1'); 
384 	};
385 	
386 	/**
387 	 * Get the browser-reported name for the current timezone (e.g. MDT, Mountain Daylight Time)
388 	 * 
389 	 * @returns {String}
390 	 */
391 	jsDate.prototype.getTimezoneName = function() {
392 		var match = /(?:\((.+)\)$| ([A-Z]{3}) )/.exec(this.toString());
393 		return match[1] || match[2] || 'GMT' + this.getGmtOffset();
394 	}; 
395 	
396 	/**
397 	 * Implements Date functionality
398 	 */
399 	jsDate.prototype.getTimezoneOffset = function() {
400 		return this.proxy.getTimezoneOffset();
401 	};
402 	
403 	
404 	/**
405 	 * Get the week number of the given year, starting with the first Monday as the first week
406 	 * @returns {Integer} Week number (13 for the 13th week of the year).
407 	 */
408 	jsDate.prototype.getWeekOfYear = function() {
409 		var doy = this.getDayOfYear();
410 		var rdow = 7 - this.getDayOfWeek();
411 		var woy = parseInt((doy+rdow)/7, 10);
412 		return woy;
413 	};
414 	
415 	/**
416 	 * Get the current date as a Unix timestamp
417 	 * 
418 	 * @returns {Integer}
419 	 */
420 	 
421 	jsDate.prototype.getUnix = function() {
422 		return Math.round(this.proxy.getTime() / 1000, 0);
423 	}; 
424 	
425 	/**
426 	 * Implements Date functionality
427 	 */
428 	jsDate.prototype.getYear = function() {
429 		return this.proxy.getYear();
430 	};
431 	
432 	/**
433 	 * Return a date one day ahead (or any other unit)
434 	 * 
435 	 * @param {String} unit Optional, year | month | day | week | hour | minute | second | millisecond
436 	 * @returns {jsDate}
437 	 */
438 	 
439 	jsDate.prototype.next = function(unit) {
440 		unit = unit || 'day'
441 		return this.clone().add(1, unit);
442 	};
443 	
444 	/**
445 	 * Set the jsDate instance to a new date.
446 	 *
447 	 * @param  {String | Number | Array | Date Object | jsDate Object | Options Object} arguments Optional arguments, 
448 	 * either a parsable date/time string,
449 	 * a JavaScript timestamp, an array of numbers of form [year, month, day, hours, minutes, seconds, milliseconds],
450 	 * a Date object, jsDate Object or an options object of form {syntax: "perl", date:some Date} where all options are optional.
451 	 */
452 	jsDate.prototype.set = function() {
453         switch ( arguments.length ) {
454             case 0:
455                 this.proxy = new Date();
456             case 1:
457 				// other objects either won't have a _type property or,
458 				// if they do, it shouldn't be set to "jsDate", so
459 				// assume it is an options argument.
460 				if (get_type(arguments[0]) == "[object Object]" && arguments[0]._type != "jsDate") {
461 					var opts = this.options = arguments[0];
462 					this.syntax= opts.syntax;
463 					this.proxy = jsDate.createDate(opts.date);
464 				}
465 				else {
466 					this.proxy = jsDate.createDate(arguments[0]);
467 				}
468                 break;
469             default:
470                 var a = [];
471                 for ( var i=0; i<arguments.length; i++ ) {
472                     a.push(arguments[i]);
473                 }
474                 this.proxy = new Date( this.utcOffset );
475                 this.proxy.setFullYear.apply( this.proxy, a.slice(0,3) );
476                 if ( a.slice(3).length ) {
477                     this.proxy.setHours.apply( this.proxy, a.slice(3) );
478                 }
479                 break;
480         }
481 	};
482 	
483 	/**
484 	 * Sets the day of the month for a specified date according to local time.
485 	 * @param {Integer} dayValue An integer from 1 to 31, representing the day of the month. 
486 	 */
487 	jsDate.prototype.setDate = function(n) {
488 		this.proxy.setDate(n);
489 	};
490 	
491 	/**
492 	 * Sets the full year for a specified date according to local time.
493 	 * @param {Integer} yearValue The numeric value of the year, for example, 1995.  
494 	 * @param {Integer} monthValue Optional, between 0 and 11 representing the months January through December.  
495 	 * @param {Integer} dayValue Optional, between 1 and 31 representing the day of the month. If you specify the dayValue parameter, you must also specify the monthValue. 
496 	 */
497 	jsDate.prototype.setFullYear = function() {
498 		this.proxy.setFullYear.apply(this.proxy, arguments);
499 	};
500 	
501 	/**
502 	 * Sets the hours for a specified date according to local time.
503 	 * 
504 	 * @param {Integer} hoursValue An integer between 0 and 23, representing the hour.  
505 	 * @param {Integer} minutesValue Optional, An integer between 0 and 59, representing the minutes.  
506 	 * @param {Integer} secondsValue Optional, An integer between 0 and 59, representing the seconds. 
507 	 * If you specify the secondsValue parameter, you must also specify the minutesValue.  
508 	 * @param {Integer} msValue Optional, A number between 0 and 999, representing the milliseconds. 
509 	 * If you specify the msValue parameter, you must also specify the minutesValue and secondsValue. 
510 	 */
511 	jsDate.prototype.setHours = function() {
512 		this.proxy.setHours.apply(this.proxy, arguments);
513 	};
514 	
515 	/**
516 	 * Implements Date functionality
517 	 */	
518 	jsDate.prototype.setMilliseconds = function(n) {
519 		this.proxy.setMilliseconds(n);
520 	};
521 	
522 	/**
523 	 * Implements Date functionality
524 	 */	
525 	jsDate.prototype.setMinutes = function() {
526 		this.proxy.setMinutes.apply(this.proxy, arguments);
527 	};
528 	
529 	/**
530 	 * Implements Date functionality
531 	 */	
532 	jsDate.prototype.setMonth = function() {
533 		this.proxy.setMonth.apply(this.proxy, arguments);
534 	};
535 	
536 	/**
537 	 * Implements Date functionality
538 	 */	
539 	jsDate.prototype.setSeconds = function() {
540 		this.proxy.setSeconds.apply(this.proxy, arguments);
541 	};
542 	
543 	/**
544 	 * Implements Date functionality
545 	 */	
546 	jsDate.prototype.setTime = function(n) {
547 		this.proxy.setTime(n);
548 	};
549 	
550 	/**
551 	 * Provide a formatted string representation of this date.
552 	 * 
553 	 * @param {String} formatString A format string.  
554 	 * See: {@link jsDate.formats}.
555 	 * @returns {String} Date String.
556 	 */
557 			
558 	jsDate.prototype.strftime = function(formatString) {
559 		formatString = formatString || this.formatString || jsDate.regional[this.locale]['formatString'];
560 		return jsDate.strftime(this, formatString, this.syntax);
561 	};
562         
563 	/**
564 	 * Return a String representation of this jsDate object.
565 	 * @returns {String} Date string.
566 	 */
567 	
568 	jsDate.prototype.toString = function() {
569 		return this.proxy.toString();
570 	};
571 		
572 	/**
573 	 * Convert the current date to an 8-digit integer (%Y%m%d)
574 	 * 
575 	 * @returns {Integer}
576 	 */
577 	 
578 	jsDate.prototype.toYmdInt = function() {
579 		return (this.proxy.getFullYear() * 10000) + (this.getMonthNumber() * 100) + this.proxy.getDate();
580 	};
581 	
582 	/**
583 	 * @namespace Holds localizations for month/day names.
584 	 * <p>jsDate attempts to detect locale when loaded and defaults to 'en'.
585 	 * If a localization is detected which is not available, jsDate defaults to 'en'.
586 	 * Additional localizations can be added after jsDate loads.  After adding a localization,
587 	 * call the jsDate.regional.getLocale() method.  Currently, en, fr and de are defined.</p>
588 	 * 
589 	 * <p>Localizations must be an object and have the following properties defined:  monthNames, monthNamesShort, dayNames, dayNamesShort and Localizations are added like:</p>
590 	 * <pre class="code">
591 	 * jsDate.regional['en'] = {
592 	 * monthNames      : 'January February March April May June July August September October November December'.split(' '),
593 	 * monthNamesShort : 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' '),
594 	 * dayNames        : 'Sunday Monday Tuesday Wednesday Thursday Friday Saturday'.split(' '),
595 	 * dayNamesShort   : 'Sun Mon Tue Wed Thu Fri Sat'.split(' ')
596 	 * };
597 	 * </pre>
598 	 * <p>After adding localizations, call <code>jsDate.regional.getLocale();</code> to update the locale setting with the
599 	 * new localizations.</p>
600 	 */
601 	 
602 	jsDate.regional = {
603 		'en': {
604 			monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'],
605 			monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun','Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
606 			dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
607 			dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
608 			formatString: '%Y-%m-%d %H:%M:%S'
609 		},
610 		
611 		'fr': {
612 			monthNames: ['Janvier','Février','Mars','Avril','Mai','Juin','Juillet','Août','Septembre','Octobre','Novembre','Décembre'],
613 			monthNamesShort: ['Jan','Fév','Mar','Avr','Mai','Jun','Jul','Aoû','Sep','Oct','Nov','Déc'],
614 			dayNames: ['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'],
615 			dayNamesShort: ['Dim','Lun','Mar','Mer','Jeu','Ven','Sam'],
616 			formatString: '%Y-%m-%d %H:%M:%S'
617 		},
618 		
619 		'de': {
620 			monthNames: ['Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'],
621 			monthNamesShort: ['Jan','Feb','Mär','Apr','Mai','Jun','Jul','Aug','Sep','Okt','Nov','Dez'],
622 			dayNames: ['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'],
623 			dayNamesShort: ['So','Mo','Di','Mi','Do','Fr','Sa'],
624 			formatString: '%Y-%m-%d %H:%M:%S'
625 		},
626 		
627 		'es': {
628 			monthNames: ['Enero','Febrero','Marzo','Abril','Mayo','Junio', 'Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre'],
629 			monthNamesShort: ['Ene','Feb','Mar','Abr','May','Jun', 'Jul','Ago','Sep','Oct','Nov','Dic'],
630 			dayNames: ['Domingo','Lunes','Martes','Miércoles','Jueves','Viernes','Sábado'],
631 			dayNamesShort: ['Dom','Lun','Mar','Mié','Juv','Vie','Sáb'],
632 			formatString: '%Y-%m-%d %H:%M:%S'
633 		},
634 		
635 		'ru': {
636 			monthNames: ['Январь','Февраль','Март','Апрель','Май','Июнь','Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь'],
637 			monthNamesShort: ['Янв','Фев','Мар','Апр','Май','Июн','Июл','Авг','Сен','Окт','Ноя','Дек'],
638 			dayNames: ['воскресенье','понедельник','вторник','среда','четверг','пятница','суббота'],
639 			dayNamesShort: ['вск','пнд','втр','срд','чтв','птн','сбт'],
640 			formatString: '%Y-%m-%d %H:%M:%S'
641 		},
642 		
643 		'ar': {
644 			monthNames: ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'آذار', 'حزيران','تموز', 'آب', 'أيلول',	'تشرين الأول', 'تشرين الثاني', 'كانون الأول'],
645 			monthNamesShort: ['1','2','3','4','5','6','7','8','9','10','11','12'],
646 			dayNames: ['السبت', 'الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة'],
647 			dayNamesShort: ['سبت', 'أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة'],
648 			formatString: '%Y-%m-%d %H:%M:%S'
649 		},
650 		
651 		'pt': {
652 			monthNames: ['Janeiro','Fevereiro','Março','Abril','Maio','Junho','Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'],
653 			monthNamesShort: ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'],
654 			dayNames: ['Domingo','Segunda-feira','Terça-feira','Quarta-feira','Quinta-feira','Sexta-feira','Sábado'],
655 			dayNamesShort: ['Dom','Seg','Ter','Qua','Qui','Sex','Sáb'],
656 			formatString: '%Y-%m-%d %H:%M:%S'	
657 		},
658 		
659 		'pt-BR': {
660 			monthNames: ['Janeiro','Fevereiro','Março','Abril','Maio','Junho', 'Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'],
661 			monthNamesShort: ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'],
662 			dayNames: ['Domingo','Segunda-feira','Terça-feira','Quarta-feira','Quinta-feira','Sexta-feira','Sábado'],
663 			dayNamesShort: ['Dom','Seg','Ter','Qua','Qui','Sex','Sáb'],
664 			formatString: '%Y-%m-%d %H:%M:%S'
665 		}
666 		
667 	
668 	};
669 	
670 	// Set english variants to 'en'
671 	jsDate.regional['en-US'] = jsDate.regional['en-GB'] = jsDate.regional['en'];
672 	
673 	/**
674 	 * Try to determine the users locale based on the lang attribute of the html page.  Defaults to 'en'
675 	 * if it cannot figure out a locale of if the locale does not have a localization defined.
676 	 * @returns {String} locale
677 	 */
678 	 
679 	jsDate.regional.getLocale = function () {
680 		var l = jsDate.config.defaultLocale;
681 		
682 		if ( document && document.getElementsByTagName('html') && document.getElementsByTagName('html')[0].lang ) {
683 			l = document.getElementsByTagName('html')[0].lang;
684 			if (!jsDate.regional.hasOwnProperty(l)) {
685 				l = jsDate.config.defaultLocale;
686 			}
687 		}
688 		
689 		return l;
690 	};
691 	
692 	// ms in day
693     var day = 24 * 60 * 60 * 1000;
694 	
695     // padd a number with zeros
696     var addZeros = function(num, digits) {
697         num = String(num);
698 		var i = digits - num.length;
699 		var s = String(Math.pow(10, i)).slice(1);
700 		return s.concat(num);
701     };
702 
703 	// representations used for calculating differences between dates.
704 	// This borrows heavily from Ken Snyder's work.
705     var multipliers = {
706         millisecond: 1,
707         second: 1000,
708         minute: 60 * 1000,
709         hour: 60 * 60 * 1000,
710         day: day,
711         week: 7 * day,
712         month: {
713             // add a number of months
714             add: function(d, number) {
715                 // add any years needed (increments of 12)
716                 multipliers.year.add(d, Math[number > 0 ? 'floor' : 'ceil'](number / 12));
717                 // ensure that we properly wrap betwen December and January
718                 var prevMonth = d.getMonth() + (number % 12);
719                 if (prevMonth == 12) {
720                     prevMonth = 0;
721                     d.setYear(d.getFullYear() + 1);
722                 } else if (prevMonth == -1) {
723                     prevMonth = 11;
724                     d.setYear(d.getFullYear() - 1);
725                 }
726                 d.setMonth(prevMonth);
727             },
728             // get the number of months between two Date objects (decimal to the nearest day)
729             diff: function(d1, d2) {
730                 // get the number of years
731                 var diffYears = d1.getFullYear() - d2.getFullYear();
732                 // get the number of remaining months
733                 var diffMonths = d1.getMonth() - d2.getMonth() + (diffYears * 12);
734                 // get the number of remaining days
735                 var diffDays = d1.getDate() - d2.getDate();
736                 // return the month difference with the days difference as a decimal
737                 return diffMonths + (diffDays / 30);
738             }
739         },
740         year: {
741             // add a number of years
742             add: function(d, number) {
743                 d.setYear(d.getFullYear() + Math[number > 0 ? 'floor' : 'ceil'](number));
744             },
745             // get the number of years between two Date objects (decimal to the nearest day)
746             diff: function(d1, d2) {
747                 return multipliers.month.diff(d1, d2) / 12;
748             }
749         }        
750     };
751     //
752     // Alias each multiplier with an 's' to allow 'year' and 'years' for example.
753 	// This comes from Ken Snyders work.
754     //
755     for (var unit in multipliers) {
756         if (unit.substring(unit.length - 1) != 's') { // IE will iterate newly added properties :|
757             multipliers[unit + 's'] = multipliers[unit];
758         }
759     }
760 	
761     //
762     // take a jsDate instance and a format code and return the formatted value.
763 	// This is a somewhat modified version of Ken Snyder's method.
764     //
765     var format = function(d, code, syntax) {
766 		// if shorcut codes are used, recursively expand those.
767 		if (jsDate.formats[syntax]["shortcuts"][code]) {
768 			return jsDate.strftime(d, jsDate.formats[syntax]["shortcuts"][code], syntax);
769 		} else {
770 			// get the format code function and addZeros() argument
771 			var getter = (jsDate.formats[syntax]["codes"][code] || '').split('.');
772 			var nbr = d['get' + getter[0]] ? d['get' + getter[0]]() : '';
773 			if (getter[1]) {
774 				nbr = addZeros(nbr, getter[1]);
775 			}
776 			return nbr;
777 		}       
778     };
779     
780     /**
781 	 * @static
782 	 * Static function for convert a date to a string according to a given format.  Also acts as namespace for strftime format codes.
783 	 * <p>strftime formatting can be accomplished without creating a jsDate object by calling jsDate.strftime():</p>
784 	 * <pre class="code">
785 	 * var formattedDate = jsDate.strftime('Feb 8, 2006 8:48:32', '%Y-%m-%d %H:%M:%S');
786 	 * </pre>
787 	 * @param {String | Number | Array | jsDate Object | Date Object} date A parsable date string, JavaScript time stamp, Array of form [year, month, day, hours, minutes, seconds, milliseconds], jsDate Object or Date object.
788 	 * @param {String} formatString String with embedded date formatting codes.  
789 	 * See: {@link jsDate.formats}. 
790 	 * @param {String} syntax Optional syntax to use [default perl].
791 	 * @param {String} locale Optional locale to use.
792 	 * @returns {String} Formatted representation of the date.
793     */
794     //
795 	// Logic as implemented here is very similar to Ken Snyder's Date Instance Methods.
796 	//
797     jsDate.strftime = function(d, formatString, syntax, locale) {
798 		var syn = 'perl';
799 		var loc = jsDate.regional.getLocale();
800 		
801 		// check if syntax and locale are available or reversed
802 		if (syntax && jsDate.formats.hasOwnProperty(syntax)) {
803 			syn = syntax;
804 		}
805 		else if (syntax && jsDate.regional.hasOwnProperty(syntax)) {
806 			loc = syntax;
807 		}
808 		
809 		if (locale && jsDate.formats.hasOwnProperty(locale)) {
810 			syn = locale;
811 		}
812 		else if (locale && jsDate.regional.hasOwnProperty(locale)) {
813 			loc = locale;
814 		}
815 		
816         if (get_type(d) != "[object Object]" || d._type != "jsDate") {
817             d = new jsDate(d);
818 			d.locale = loc;
819         }
820 		if (!formatString) {
821 			formatString = d.formatString || jsDate.regional[loc]['formatString'];
822 		}
823         // default the format string to year-month-day
824         var source = formatString || '%Y-%m-%d', 
825 			result = '', 
826 			match;
827         // replace each format code
828         while (source.length > 0) {
829             if (match = source.match(jsDate.formats[syn].codes.matcher)) {
830                 result += source.slice(0, match.index);
831                 result += (match[1] || '') + format(d, match[2], syn);
832                 source = source.slice(match.index + match[0].length);
833             } else {
834                 result += source;
835                 source = '';
836             }
837         }
838         return result;
839     };
840 	
841 	/**
842 	 * @namespace
843 	 * Namespace to hold format codes and format shortcuts.  "perl" and "php" format codes 
844 	 * and shortcuts are defined by default.  Additional codes and shortcuts can be
845 	 * added like:
846 	 * 
847 	 * <pre class="code">
848 	 * jsDate.formats["perl"] = {
849 	 *     "codes": {
850 	 *         matcher: /someregex/,
851 	 *         Y: "fullYear",  // name of "get" method without the "get",
852 	 *         ...,            // more codes
853 	 *     },
854 	 *     "shortcuts": {
855 	 *         F: '%Y-%m-%d',
856 	 *         ...,            // more shortcuts
857 	 *     }
858 	 * };
859 	 * </pre>
860 	 * 
861 	 * <p>Additionally, ISO and SQL shortcuts are defined and can be accesses via:
862 	 * <code>jsDate.formats.ISO</code> and <code>jsDate.formats.SQL</code>
863 	 */
864 	
865 	jsDate.formats = {
866 		ISO:'%Y-%m-%dT%H:%M:%S.%N%G',
867 		SQL:'%Y-%m-%d %H:%M:%S'
868 	};
869 	
870 	/**
871 	 * Perl format codes and shortcuts for strftime.
872 	 * 
873 	 * A hash (object) of codes where each code must be an array where the first member is 
874 	 * the name of a Date.prototype or jsDate.prototype function to call
875 	 * and optionally a second member indicating the number to pass to addZeros()
876 	 * 
877 	 * <p>The following format codes are defined:</p>
878 	 * 
879 	 * <pre class="code">
880 	 * Code    Result                    Description
881 	 * == Years ==           
882 	 * %Y      2008                      Four-digit year
883 	 * %y      08                        Two-digit year
884 	 * 
885 	 * == Months ==          
886 	 * %m      09                        Two-digit month
887 	 * %#m     9                         One or two-digit month
888 	 * %B      September                 Full month name
889 	 * %b      Sep                       Abbreviated month name
890 	 * 
891 	 * == Days ==            
892 	 * %d      05                        Two-digit day of month
893 	 * %#d     5                         One or two-digit day of month
894 	 * %e      5                         One or two-digit day of month
895 	 * %A      Sunday                    Full name of the day of the week
896 	 * %a      Sun                       Abbreviated name of the day of the week
897 	 * %w      0                         Number of the day of the week (0 = Sunday, 6 = Saturday)
898 	 * 
899 	 * == Hours ==           
900 	 * %H      23                        Hours in 24-hour format (two digits)
901 	 * %#H     3                         Hours in 24-hour integer format (one or two digits)
902 	 * %I      11                        Hours in 12-hour format (two digits)
903 	 * %#I     3                         Hours in 12-hour integer format (one or two digits)
904 	 * %p      PM                        AM or PM
905 	 * 
906 	 * == Minutes ==         
907 	 * %M      09                        Minutes (two digits)
908 	 * %#M     9                         Minutes (one or two digits)
909 	 * 
910 	 * == Seconds ==         
911 	 * %S      02                        Seconds (two digits)
912 	 * %#S     2                         Seconds (one or two digits)
913 	 * %s      1206567625723             Unix timestamp (Seconds past 1970-01-01 00:00:00)
914 	 * 
915 	 * == Milliseconds ==    
916 	 * %N      008                       Milliseconds (three digits)
917 	 * %#N     8                         Milliseconds (one to three digits)
918 	 * 
919 	 * == Timezone ==        
920 	 * %O      360                       difference in minutes between local time and GMT
921 	 * %Z      Mountain Standard Time    Name of timezone as reported by browser
922 	 * %G      06:00                     Hours and minutes between GMT
923 	 * 
924 	 * == Shortcuts ==       
925 	 * %F      2008-03-26                %Y-%m-%d
926 	 * %T      05:06:30                  %H:%M:%S
927 	 * %X      05:06:30                  %H:%M:%S
928 	 * %x      03/26/08                  %m/%d/%y
929 	 * %D      03/26/08                  %m/%d/%y
930 	 * %#c     Wed Mar 26 15:31:00 2008  %a %b %e %H:%M:%S %Y
931 	 * %v      3-Sep-2008                %e-%b-%Y
932 	 * %R      15:31                     %H:%M
933 	 * %r      03:31:00 PM               %I:%M:%S %p
934 	 * 
935 	 * == Characters ==      
936 	 * %n      \n                        Newline
937 	 * %t      \t                        Tab
938 	 * %%      %                         Percent Symbol
939 	 * </pre>
940 	 * 
941 	 * <p>Formatting shortcuts that will be translated into their longer version.
942 	 * Be sure that format shortcuts do not refer to themselves: this will cause an infinite loop.</p>
943 	 * 
944 	 * <p>Format codes and format shortcuts can be redefined after the jsDate
945 	 * module is imported.</p>
946 	 * 
947 	 * <p>Note that if you redefine the whole hash (object), you must supply a "matcher"
948 	 * regex for the parser.  The default matcher is:</p>
949 	 * 
950 	 * <code>/()%(#?(%|[a-z]))/i</code>
951 	 * 
952 	 * <p>which corresponds to the Perl syntax used by default.</p>
953 	 * 
954 	 * <p>By customizing the matcher and format codes, nearly any strftime functionality is possible.</p>
955 	 */
956 	 
957 	jsDate.formats.perl = {
958 		codes: {
959 			//
960 			// 2-part regex matcher for format codes
961 			//
962 			// first match must be the character before the code (to account for escaping)
963 			// second match must be the format code character(s)
964 			//
965 			matcher: /()%(#?(%|[a-z]))/i,
966 			// year
967 			Y: 'FullYear',
968 			y: 'ShortYear.2',
969 			// month
970 			m: 'MonthNumber.2',
971 			'#m': 'MonthNumber',
972 			B: 'MonthName',
973 			b: 'AbbrMonthName',
974 			// day
975 			d: 'Date.2',
976 			'#d': 'Date',
977 			e: 'Date',
978 			A: 'DayName',
979 			a: 'AbbrDayName',
980 			w: 'Day',
981 			// hours
982 			H: 'Hours.2',
983 			'#H': 'Hours',
984 			I: 'Hours12.2',
985 			'#I': 'Hours12',
986 			p: 'AMPM',
987 			// minutes
988 			M: 'Minutes.2',
989 			'#M': 'Minutes',
990 			// seconds
991 			S: 'Seconds.2',
992 			'#S': 'Seconds',
993 			s: 'Unix',
994 			// milliseconds
995 			N: 'Milliseconds.3',
996 			'#N': 'Milliseconds',
997 			// timezone
998 			O: 'TimezoneOffset',
999 			Z: 'TimezoneName',
1000 			G: 'GmtOffset'  
1001 		},
1002 		
1003 		shortcuts: {
1004 			// date
1005 			F: '%Y-%m-%d',
1006 			// time
1007 			T: '%H:%M:%S',
1008 			X: '%H:%M:%S',
1009 			// local format date
1010 			x: '%m/%d/%y',
1011 			D: '%m/%d/%y',
1012 			// local format extended
1013 			'#c': '%a %b %e %H:%M:%S %Y',
1014 			// local format short
1015 			v: '%e-%b-%Y',
1016 			R: '%H:%M',
1017 			r: '%I:%M:%S %p',
1018 			// tab and newline
1019 			t: '\t',
1020 			n: '\n',
1021 			'%': '%'
1022 		}
1023 	};
1024 	
1025 	/**
1026 	 * PHP format codes and shortcuts for strftime.
1027 	 * 
1028 	 * A hash (object) of codes where each code must be an array where the first member is 
1029 	 * the name of a Date.prototype or jsDate.prototype function to call
1030 	 * and optionally a second member indicating the number to pass to addZeros()
1031 	 * 
1032 	 * <p>The following format codes are defined:</p>
1033 	 * 
1034 	 * <pre class="code">
1035 	 * Code    Result                    Description
1036 	 * === Days ===        
1037 	 * %a      Sun through Sat           An abbreviated textual representation of the day
1038 	 * %A      Sunday - Saturday         A full textual representation of the day
1039 	 * %d      01 to 31                  Two-digit day of the month (with leading zeros)
1040 	 * %e      1 to 31                   Day of the month, with a space preceding single digits.
1041 	 * %j      001 to 366                Day of the year, 3 digits with leading zeros
1042 	 * %u      1 - 7 (Mon - Sun)         ISO-8601 numeric representation of the day of the week
1043 	 * %w      0 - 6 (Sun - Sat)         Numeric representation of the day of the week
1044 	 *                                  
1045 	 * === Week ===                     
1046 	 * %U      13                        Full Week number, starting with the first Sunday as the first week
1047 	 * %V      01 through 53             ISO-8601:1988 week number, starting with the first week of the year 
1048 	 *                                   with at least 4 weekdays, with Monday being the start of the week
1049 	 * %W      46                        A numeric representation of the week of the year, 
1050 	 *                                   starting with the first Monday as the first week
1051 	 * === Month ===                    
1052 	 * %b      Jan through Dec           Abbreviated month name, based on the locale
1053 	 * %B      January - December        Full month name, based on the locale
1054 	 * %h      Jan through Dec           Abbreviated month name, based on the locale (an alias of %b)
1055 	 * %m      01 - 12 (Jan - Dec)       Two digit representation of the month
1056 	 * 
1057 	 * === Year ===                     
1058 	 * %C      19                        Two digit century (year/100, truncated to an integer)
1059 	 * %y      09 for 2009               Two digit year
1060 	 * %Y      2038                      Four digit year
1061 	 * 
1062 	 * === Time ===                     
1063 	 * %H      00 through 23             Two digit representation of the hour in 24-hour format
1064 	 * %I      01 through 12             Two digit representation of the hour in 12-hour format
1065 	 * %l      1 through 12              Hour in 12-hour format, with a space preceeding single digits
1066 	 * %M      00 through 59             Two digit representation of the minute
1067 	 * %p      AM/PM                     UPPER-CASE 'AM' or 'PM' based on the given time
1068 	 * %P      am/pm                     lower-case 'am' or 'pm' based on the given time
1069 	 * %r      09:34:17 PM               Same as %I:%M:%S %p
1070 	 * %R      00:35                     Same as %H:%M
1071 	 * %S      00 through 59             Two digit representation of the second
1072 	 * %T      21:34:17                  Same as %H:%M:%S
1073 	 * %X      03:59:16                  Preferred time representation based on locale, without the date
1074 	 * %z      -0500 or EST              Either the time zone offset from UTC or the abbreviation
1075 	 * %Z      -0500 or EST              The time zone offset/abbreviation option NOT given by %z
1076 	 * 
1077 	 * === Time and Date ===            
1078 	 * %D      02/05/09                  Same as %m/%d/%y
1079 	 * %F      2009-02-05                Same as %Y-%m-%d (commonly used in database datestamps)
1080 	 * %s      305815200                 Unix Epoch Time timestamp (same as the time() function)
1081 	 * %x      02/05/09                  Preferred date representation, without the time
1082 	 * 
1083 	 * === Miscellaneous ===            
1084 	 * %n        ---                     A newline character (\n)
1085 	 * %t        ---                     A Tab character (\t)
1086 	 * %%        ---                     A literal percentage character (%)
1087 	 * </pre>
1088 	 */
1089  
1090 	jsDate.formats.php = {
1091 		codes: {
1092 			//
1093 			// 2-part regex matcher for format codes
1094 			//
1095 			// first match must be the character before the code (to account for escaping)
1096 			// second match must be the format code character(s)
1097 			//
1098 			matcher: /()%((%|[a-z]))/i,
1099 			// day
1100 			a: 'AbbrDayName',
1101 			A: 'DayName',
1102 			d: 'Date.2',
1103 			e: 'Date',
1104 			j: 'DayOfYear.3',
1105 			u: 'DayOfWeek',
1106 			w: 'Day',
1107 			// week
1108 			U: 'FullWeekOfYear.2',
1109 			V: 'IsoWeek.2',
1110 			W: 'WeekOfYear.2',
1111 			// month
1112 			b: 'AbbrMonthName',
1113 			B: 'MonthName',
1114 			m: 'MonthNumber.2',
1115 			h: 'AbbrMonthName',
1116 			// year
1117 			C: 'Century.2',
1118 			y: 'ShortYear.2',
1119 			Y: 'FullYear',
1120 			// time
1121 			H: 'Hours.2',
1122 			I: 'Hours12.2',
1123 			l: 'Hours12',
1124 			p: 'AMPM',
1125 			P: 'AmPm',
1126 			M: 'Minutes.2',
1127 			S: 'Seconds.2',
1128 			s: 'Unix',
1129 			O: 'TimezoneOffset',
1130 			z: 'GmtOffset',
1131 			Z: 'TimezoneAbbr',
1132 		},
1133 		
1134 		shortcuts: {
1135 			D: '%m/%d/%y',
1136 			F: '%Y-%m-%d',
1137 			T: '%H:%M:%S',
1138 			X: '%H:%M:%S',
1139 			x: '%m/%d/%y',
1140 			R: '%H:%M',
1141 			r: '%I:%M:%S %p',
1142 			t: '\t',
1143 			n: '\n',
1144 			'%': '%'
1145 		}
1146 	};   
1147 	//
1148 	// Conceptually, the logic implemented here is similar to Ken Snyder's Date Instance Methods.
1149 	// I use his idea of a set of parsers which can be regular expressions or functions,
1150 	// iterating through those, and then seeing if Date.parse() will create a date.
1151 	// The parser expressions and functions are a little different and some bugs have been
1152 	// worked out.  Also, a lot of "pre-parsing" is done to fix implementation
1153 	// variations of Date.parse() between browsers.
1154 	//
1155     jsDate.createDate = function(date) {
1156         // if passing in multiple arguments, try Date constructor
1157         if (date == null) {
1158             return new Date();
1159         }
1160         // If the passed value is already a date object, return it
1161         if (date instanceof Date) {
1162             return date;
1163         }
1164         // if (typeof date == 'number') return new Date(date * 1000);
1165         // If the passed value is an integer, interpret it as a javascript timestamp
1166         if (typeof date == 'number') {
1167             return new Date(date);
1168         }
1169         
1170 		// Before passing strings into Date.parse(), have to normalize them for certain conditions.
1171 		// If strings are not formatted staccording to the EcmaScript spec, results from Date parse will be implementation dependent.  
1172 		// 
1173 		// For example: 
1174 		//  * FF and Opera assume 2 digit dates are pre y2k, Chome assumes <50 is pre y2k, 50+ is 21st century.  
1175 		//  * Chrome will correctly parse '1984-1-25' into localtime, FF and Opera will not parse.
1176 		//  * Both FF, Chrome and Opera will parse '1984/1/25' into localtime.
1177         
1178         // remove leading and trailing spaces
1179         var parsable = String(date).replace(/^\s*(.+)\s*$/g, '$1');
1180         
1181         // replace dahses (-) with slashes (/) in dates like n[nnn]/n[n]/n[nnn]
1182         parsable = parsable.replace(/^([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,4})/, "$1/$2/$3");
1183         
1184         /////////
1185         // Need to check for '15-Dec-09' also.
1186         // FF will not parse, but Chrome will.
1187         // Chrome will set date to 2009 as well.
1188         /////////
1189         
1190         // first check for 'dd-mmm-yyyy' or 'dd/mmm/yyyy' like '15-Dec-2010'
1191         parsable = parsable.replace(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{4})/i, "$1 $2 $3");
1192         
1193         // Now check for 'dd-mmm-yy' or 'dd/mmm/yy' and normalize years to 1900.
1194         var match = parsable.match(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{2})\D*/i);
1195         if (match && match.length > 3) {
1196             var m3 = parseFloat(match[3]);
1197             var ny = 1900 + m3;
1198             ny = String(ny);
1199             
1200             // now replace 2 digit year with 4 digit year
1201             parsable = parsable.replace(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{2})\D*/i, match[1] +' '+ match[2] +' '+ ny);
1202             
1203         }
1204         
1205         // Check for '1/19/70 8:14PM'
1206         // where starts with mm/dd/yy or yy/mm/dd and have something after
1207         // Check if 1st postiion is greater than 12, assume it is year.
1208         // Assme all 2 digit years are 1900's.
1209         // Finally, change them into US style mm/dd/yyyy representations.
1210         match = parsable.match(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})[^0-9]/);
1211         
1212         function h1(parsable, match) {
1213             var m1 = parseFloat(match[1]);
1214             var m2 = parseFloat(match[2]);
1215             var m3 = parseFloat(match[3]);
1216             var cent = 1900;
1217             var ny, nd, nm, str;
1218             
1219             if (m1 > 31) { // first number is a year
1220                 nd = m3;
1221                 nm = m2;
1222                 ny = cent + m1;
1223             }
1224             
1225             else { // last number is the year
1226                 nd = m2;
1227                 nm = m1;
1228                 ny = cent + m3;
1229             }
1230             
1231             str = nm+'/'+nd+'/'+ny;
1232             
1233             // now replace 2 digit year with 4 digit year
1234             return  parsable.replace(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})/, str);
1235         
1236         }
1237         
1238         if (match && match.length > 3) {
1239             parsable = h1(parsable, match);
1240         }
1241         
1242         // Now check for '1/19/70' with nothing after and do as above
1243         var match = parsable.match(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})$/);
1244         
1245         if (match && match.length > 3) {
1246             parsable = h1(parsable, match);
1247         }
1248                 
1249         
1250         var i = 0;
1251         var length = jsDate.matchers.length;
1252         var pattern;
1253         var current = parsable;
1254         while (i < length) {
1255             ms = Date.parse(current);
1256             if (!isNaN(ms)) {
1257                 return new Date(ms);
1258             }
1259             pattern = jsDate.matchers[i];
1260             if (typeof pattern == 'function') {
1261                 obj = pattern.call(jsDate, current);
1262                 if (obj instanceof Date) {
1263                     return obj;
1264                 }
1265             } else {
1266                 current = parsable.replace(pattern[0], pattern[1]);
1267             }
1268             i++;
1269         }
1270         return NaN;
1271     };
1272     
1273     // sort of shortcut to create a jsDate date.
1274     jsDate.create = function() {
1275         return new jsDate(arguments);
1276     };
1277     
1278     /**
1279 	 * @static
1280 	 * Handy static utility function to return the number of days in a given month.
1281 	 * @param {Integer} year Year
1282 	 * @param {Integer} month Month (1-12)
1283 	 * @returns {Integer} Number of days in the month.
1284     */
1285 	//
1286 	// handy utility method Borrowed right from Ken Snyder's Date Instance Mehtods.
1287 	// 
1288     jsDate.daysInMonth = function(year, month) {
1289         if (month == 2) {
1290             return new Date(year, 1, 29).getDate() == 29 ? 29 : 28;
1291         }
1292         return [undefined,31,undefined,31,30,31,30,31,31,30,31,30,31][month];
1293     };
1294 
1295 
1296 	//
1297 	// An Array of regular expressions or functions that will attempt to match the date string.
1298 	// Functions are called with scope of a jsDate instance.
1299     //
1300     jsDate.matchers = [
1301 		// convert dd.mmm.yyyy to mm/dd/yyyy (world date to US date).
1302         [/(3[01]|[0-2]\d)\s*\.\s*(1[0-2]|0\d)\s*\.\s*([1-9]\d{3})/, '$2/$1/$3'],
1303 		// convert yyyy-mm-dd to mm/dd/yyyy (ISO date to US date).
1304         [/([1-9]\d{3})\s*-\s*(1[0-2]|0\d)\s*-\s*(3[01]|[0-2]\d)/, '$2/$3/$1'],
1305 		// Handle 12 hour or 24 hour time with milliseconds am/pm and optional date part.
1306         function(str) { 
1307             var match = str.match(/^(?:(.+)\s+)?([012]?\d)(?:\s*\:\s*(\d\d))?(?:\s*\:\s*(\d\d(\.\d*)?))?\s*(am|pm)?\s*$/i);
1308             //                   opt. date      hour       opt. minute     opt. second       opt. msec   opt. am or pm
1309             if (match) {
1310                 if (match[1]) {
1311                     var d = this.createDate(match[1]);
1312                     if (isNaN(d)) {
1313                         return;
1314                     }
1315                 } else {
1316                     var d = new Date();
1317                     d.setMilliseconds(0);
1318                 }
1319                 var hour = parseFloat(match[2]);
1320                 if (match[6]) {
1321                     hour = match[6].toLowerCase() == 'am' ? (hour == 12 ? 0 : hour) : (hour == 12 ? 12 : hour + 12);
1322                 }
1323                 d.setHours(hour, parseInt(match[3] || 0, 10), parseInt(match[4] || 0, 10), ((parseFloat(match[5] || 0)) || 0)*1000);
1324                 return d;
1325             }
1326             else {
1327                 return str;
1328             }
1329         },
1330 		// Handle ISO timestamp with time zone.
1331         function(str) {
1332             var match = str.match(/^(?:(.+))[T|\s+]([012]\d)(?:\:(\d\d))(?:\:(\d\d))(?:\.\d+)([\+\-]\d\d\:\d\d)$/i);
1333             if (match) {
1334                 if (match[1]) {
1335                     var d = this.createDate(match[1]);
1336                     if (isNaN(d)) {
1337                         return;
1338                     }
1339                 } else {
1340                     var d = new Date();
1341                     d.setMilliseconds(0);
1342                 }
1343                 var hour = parseFloat(match[2]);
1344                 d.setHours(hour, parseInt(match[3], 10), parseInt(match[4], 10), parseFloat(match[5])*1000);
1345                 return d;
1346             }
1347             else {
1348                     return str;
1349             }
1350         },
1351         // Try to match ambiguous strings like 12/8/22.
1352         // Use FF date assumption that 2 digit years are 20th century (i.e. 1900's).
1353 		// This may be redundant with pre processing of date already performed.
1354         function(str) {
1355             var match = str.match(/^([0-3]?\d)\s*[-\/.\s]{1}\s*([a-zA-Z]{3,9})\s*[-\/.\s]{1}\s*([0-3]?\d)$/);
1356             if (match) {
1357                 var d = new Date();
1358                 var cent = 1900;
1359                 var m1 = parseFloat(match[1]);
1360                 var m3 = parseFloat(match[3]);
1361                 var ny, nd, nm;
1362                 if (m1 > 31) { // first number is a year
1363                     nd = m3;
1364                     ny = cent + m1;
1365                 }
1366                 
1367                 else { // last number is the year
1368                     nd = m1;
1369                     ny = cent + m3;
1370                 }
1371                 
1372                 var nm = inArray(match[2], jsDate.regional[this.locale]["monthNamesShort"]);
1373                 
1374                 if (nm == -1) {
1375                     nm = inArray(match[2], jsDate.regional[this.locale]["monthNames"]);
1376                 }
1377             
1378                 d.setFullYear(ny, nm, nd);
1379                 d.setHours(0,0,0,0);
1380                 return d;
1381             }
1382             
1383             else {
1384                 return str;
1385             }
1386         }      
1387     ];
1388 
1389 	//
1390 	// I think John Reisig published this method on his blog, ejohn.
1391 	//
1392     function inArray( elem, array ) {
1393         if ( array.indexOf ) {
1394             return array.indexOf( elem );
1395         }
1396 
1397         for ( var i = 0, length = array.length; i < length; i++ ) {
1398             if ( array[ i ] === elem ) {
1399                 return i;
1400             }
1401         }
1402 
1403         return -1;
1404     }
1405 	
1406 	//
1407 	// Thanks to Kangax, Christian Sciberras and Stack Overflow for this method.
1408 	//
1409 	function get_type(thing){
1410 		if(thing===null)return "[object Null]"; // special case
1411 		return Object.prototype.toString.call(thing);
1412 	}
1413 
1414     
1415     // Make sure window is defined for headless operations like 
1416     // jsdoc-toolkit and some unit testing frameworks.
1417     window = window || {};
1418     
1419     if (window.jQuery !== undefined) {
1420     	window.jQuery.jsDate = jsDate;
1421     }
1422     
1423     else {
1424     	window.jsDate = jsDate;
1425     }
1426 
1427 })();