/* 'Magic' date parsing, by Simon Willison (6th October 2003)
   http://simon.incutio.com/archive/2003/10/06/betterDateInput
*/

/* Finds the index of the first occurence of item in the array, or -1 if not found */
Array.prototype.indexOf = function(item) {
    for (var i = 0; i < this.length; i++) {
        if (this[i] == item) {
            return i;
        }
    }
    return -1;
};
/* Returns an array of items judged 'true' by the passed in test function */
Array.prototype.filter = function(test) {
    var matches = [];
    for (var i = 0; i < this.length; i++) {
        if (test(this[i])) {
            matches[matches.length] = this[i];
        }
    }
    return matches;
};

var	monthNames = ["January","February","March","April","May","June","July","August","September","October","November","December"];
shortMonthNames = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
var weekdayNames = ['Sunday', 'Monday', 'Tuesday',	'Wednesday', 'Thursday', 'Friday', 'Saturday'];
var shortWeekdayNames = ['Sun', 'Mon', 'Tue','Wed', 'Thu', 'Fri', 'Sat'];
var	AMPM = ["AM","PM","am","pm"];

//var monthNames = "January February March April May June July August September October November December".split(" ");
//var weekdayNames = "Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" ");

/* Takes a string, returns the index of the month matching that string, throws
   an error if 0 or more than 1 matches
*/
function parseMonth(month) {
    var matches = monthNames.filter(function(item) { 
        return new RegExp("^" + month, "i").test(item);
    });
    if (matches.length == 0) {
        throw new Error("Invalid month string");
    }
    if (matches.length > 1) {
        throw new Error("Ambiguous month");
    }
    return monthNames.indexOf(matches[0]);
}
/* Same as parseMonth but for days of the week */
function parseWeekday(weekday) {
    var matches = weekdayNames.filter(function(item) {
        return new RegExp("^" + weekday, "i").test(item);
    });
    if (matches.length == 0) {
        throw new Error("Invalid day string");
    }
    if (matches.length > 1) {
        throw new Error("Ambiguous weekday");
    }
    return weekdayNames.indexOf(matches[0]);
}

/* Array of objects, each has 're', a regular expression and 'handler', a 
   function for creating a date from something that matches the regular 
   expression. Handlers may throw errors if string is unparseable. 
*/

var dateParsePatterns = [
    // Today
    {   re: /^tod/i,
        handler: function() { 
            return new Date();
        } 
    },
    // Tomorrow
    {   re: /^tom/i,
        handler: function() {
            var d = new Date(); 
            d.setDate(d.getDate() + 1); 
            return d;
        }
    },
    // Yesterday
    {   re: /^yes/i,
        handler: function() {
            var d = new Date();
            d.setDate(d.getDate() - 1);
            return d;
        }
    },
    // 4th
    {   re: /^(\d{1,2})(st|nd|rd|th)?$/i, 
        handler: function(bits) {
            var d = new Date();
            d.setDate(parseInt(bits[1], 10));
            return d;
        }
    },
    // 4th Jan
    {   re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+)$/i, 
        handler: function(bits) {
            var d = new Date();
            d.setMonth(parseMonth(bits[2]));
            d.setDate(parseInt(bits[1], 10));
            return d;
        }
    },
    // Fri 4th Dec 2003 10:20 am
    {	re: /^(?:(\w+)?,?\x20+)?(\d{1,2})(?:st|nd|rd|th)?[^\w]+([a-zA-Z]+)[^\w]+(\d{4})([^\w]+([01]?[0-9]|2[0-3]):([0-5]\d)(:([0-5]\d))?\x20*([AaPp][Mm])?)?$/i,
		handler: function(bits){
			var year = parseInt(bits[4]);
			var month = parseMonth(bits[3]); 
			var day = parseInt(bits[2], 10);
			var h, m, s
			
            var d = new Date(year, month, day);
            //d.setYear(year);
	        //d.setMonth(month);
            //d.setDate(day);
 			if( bits[5] != '' ){	// has time
	 			h = parseInt(bits[6],10);
		        if (bits[10] == 'pm' || bits[10] == 'PM' ) 
			    { if(h<12) h += 12 }
				else if (bits[10] == 'am' || bits[10] == 'AM' ) 
				{ if (h==12) h = 0 };
				m = parseInt(bits[7],10);
				s = (bits[9]) ? parseInt(bits[9],10) : 0 ;
	            d.setHours(h);
		        d.setMinutes(m);
		        d.setSeconds(s);
	        }

            var parts = [year, month, day, h, m, s];
            d.parts = parts;
		    return d;
        }
    },
    // 4th Jan 2003
    /*{   re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+),? (\d{4})$/i,
        handler: function(bits) {
            var d = new Date();
            d.setDate(parseInt(bits[1], 10));
            d.setMonth(parseMonth(bits[2]));
            d.setYear(bits[3]);
            return d;
        }
    },*/
    // Jan 4th
    {   re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?$/i, 
        handler: function(bits) {
            var d = new Date();
            d.setMonth(parseMonth(bits[1]));
            d.setDate(parseInt(bits[2], 10));
            return d;
        }
    },
    // Fri Dec 4th, 2003 10:20 am
    {	re: /^(?:(\w+)?,?\x20+)?([a-zA-Z]+)[^\w]+(\d{1,2})(?:st|nd|rd|th)?[^\w]+(\d{4})([^\w]+([01]?[0-9]|2[0-3]):([0-5]\d)(:([0-5]\d))?\x20*([AaPp][Mm])?)?$/i,
		handler: function(bits){
			var year = parseInt(bits[4]);
			var month = parseMonth(bits[2]); 
			var day = parseInt(bits[3], 10);
			var h, m, s
			
            var d = new Date(year, month, day);
            //d.setYear(year);
            //d.setMonth(month);
            //d.setDate(day);
             
  			if( bits[5] != '' ){	// has time
	 			h = parseInt(bits[6],10);
		        if (bits[10] == 'pm' || bits[10] == 'PM' ) 
			    { if(h<12) h += 12 }
				else if (bits[10] == 'am' || bits[10] == 'AM' ) 
				{ if (h==12) h = 0 };
				m = parseInt(bits[7],10);
				s = (bits[9]) ? parseInt(bits[9],10) : 0 ;
	            d.setHours(h);
		        d.setMinutes(m);
		        d.setSeconds(s);
	        }

            var parts = [year, month, day, h, m, s];
            d.parts = parts;
		    return d;
        }
    },    
    
    // Jan 4th 2003
    /*{   re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?,? (\d{4})$/i,
        handler: function(bits) {
            var d = new Date();
            d.setDate(parseInt(bits[2], 10));
            d.setMonth(parseMonth(bits[1]));
            d.setYear(bits[3]);
            return d;
        }
    },*/
    // next Tuesday - this is suspect due to weird meaning of "next"
    {   re: /^next (\w+)$/i,
        handler: function(bits) {
            var d = new Date();
            var day = d.getDay();
            var newDay = parseWeekday(bits[1]);
            var addDays = newDay - day;
            if (newDay <= day) {
                addDays += 7;
            }
            d.setDate(d.getDate() + addDays);
            return d;
        }
    },
    // last Tuesday
    {   re: /^last (\w+)$/i,
        handler: function(bits) {
            throw new Error("Not yet implemented");
        }
    },
    /*
    // mm/dd/yyyy (American style)
    {   re: /^(0?[1-9]|1[0-2])[-\\./ ](0?[1-9]|[1-2][0-9]|3[0,1])[-\\./ ](\d{4})([^\w]+([01]?[0-9]|2[0-3]):([0-5]\d)(:([0-5]\d))?\x20*([AaPp][Mm])?)?/,
		handler: function(bits) {
			if( typeof arguments[1] != 'undefined' && arguments[1] == 1 ) 
				return null; // non-American ( dd/mm/yyyy )
				
			var year = parseInt(bits[3]);
			var month = parseInt(bits[1], 10) - 1; // Because months indexed from 0
			var day = parseInt(bits[2], 10);
			var h = m = s = 0;
			
            var d = new Date(year, month, day);
            //d.setDate(day);
            //d.setMonth(month);
            //d.setYear(year);
 			if( bits[4] != '' ){	// has time
	 			h = parseInt(bits[5],10);
		        if (bits[9] == 'pm' || bits[9] == 'PM' ) 
			    { if(h<12) h += 12 }
				else if (bits[9] == 'am' || bits[9] == 'AM' ) 
				{ if (h==12) h = 0 };
				m = parseInt(bits[6],10);
				s = (bits[8]) ? parseInt(bits[8],10) : 0;
	            d.setHours(h);
		        d.setMinutes(m);
		        d.setSeconds(s);
	        }

            var parts = [year, month, day, h, m, s];
            d.parts = parts;
            return d;
        }
    },*/
	// dd/mm/yyyy hh:nn tt
    {   re: /^(0?[1-9]|[1-2][0-9]|3[0,1])[-\\./ ](0?[1-9]|1[0-2])[-\\./ ](\d{4})([^\w]+([01]?[0-9]|2[0-3]):([0-5]\d)(:([0-5]\d))?\x20*([AaPp][Mm])?)?/,
		handler: function(bits){
 			var year = parseInt(bits[3]);
			var month = parseInt(bits[2], 10) - 1; // Because months indexed from 0
			var day = parseInt(bits[1], 10);
			var h = m = s = 0;
			
            var d = new Date(year, month, day);
            //d.setDate(day);
            //d.setMonth(month);
            //d.setYear(year);
 			if( bits[4] != '' ){	// has time
	 			h = parseInt(bits[5],10);
		        if (bits[9] == 'pm' || bits[9] == 'PM' ) 
			    { if(h<12) h += 12 }
				else if (bits[9] == 'am' || bits[9] == 'AM' ) 
				{ if (h==12) h = 0 };
				m = parseInt(bits[6],10);
				s = (bits[8]) ? parseInt(bits[8],10) : 0;
	            d.setHours(h);
		        d.setMinutes(m);
		        d.setSeconds(s);
	        }

            var parts = [year, month, day, h, m, s];
            d.parts = parts;
            return d;
        }
    },
    // yyyy-mm-dd (ISO style)
    {   re: /(\d{4})[-\\./ ](\d{1,2})[-\\./ ](\d{1,2})([^\w]+([01]?[0-9]|2[0-3]):([0-5]\d)(:([0-5]\d))?\x20*([AaPp][Mm])?)?/,
        handler: function(bits) {
 			var year = parseInt(bits[1]);
			var month = parseInt(bits[2], 10) - 1; // Because months indexed from 0
			var day = parseInt(bits[3], 10);
			var h = m = s = 0;

            var d = new Date(year, month, day);
            //d.setDate(day);
            //d.setMonth(month);
            //d.setYear(year);
 			if( bits[4] != '' ){	// has time
	 			h = parseInt(bits[5],10);
		        if (bits[9] == 'pm' || bits[9] == 'PM' ) 
			    { if(h<12) h += 12 }
				else if (bits[9] == 'am' || bits[9] == 'AM' ) 
				{ if (h==12) h = 0 };
				m = parseInt(bits[6],10);
				s = (bits[8]) ? parseInt(bits[8],10) : 0;
	            d.setHours(h);
		        d.setMinutes(m);
		        d.setSeconds(s);
	        }

            var parts = [year, month, day, h, m, s];
            d.parts = parts;
            return d;
        }
    }
];

function parseDateString(s) {
    for (var i = 0; i < dateParsePatterns.length; i++) {
        var re = dateParsePatterns[i].re;
        var handler = dateParsePatterns[i].handler;
        var bits = re.exec(s);
        if (bits) {
            return handler(bits);
        }
    }
    //throw new Error("Invalid date string");
}

// add format() to Date
Date.prototype.format = function(formatString) {
	var out = new String();
	var token = '';

	for (var i = 0; i < formatString.length; i++) {
		if (formatString.charAt(i) == token.charAt(0)) {
			token = token.concat(formatString.charAt(i));
			continue;
		}

		out = out.concat(this.convertToken(token));
		token = formatString.charAt(i);
	}
	return out + this.convertToken(token);
}

Date.prototype.convertToken = function (str) {
	switch(str.charAt(0)) {
	case 'y': // set year
		if (str.length > 2) 
		return this.getFullYear();
		return this.getFullYear().toString().substring(2);
	case 'd': // set date
		return zeroPad(this.getDate(),str.length);
	case 'D': // set day in year
		return this.getYearDay();
	case 't':
		if (str.length > 1) return this.getHours() > 11 ? AMPM[1] : AMPM[0];
		return this.getHours() > 11 ? AMPM[1].substring(1) : AMPM[0].substring(1);
	case 'H': // set hours
		return zeroPad(this.getHours(),str.length);
	case 'h':
		return zeroPad(this.get12Hours(),str.length);
	case 'm': // set minutes
		return zeroPad(this.getMinutes(),2);
	case 's': // set secondes
		return zeroPad(this.getSeconds(),2);
	case 'S': // set milisecondes
		return zeroPad(this.getMilliseconds(),str.length);
	case 'x': // set epoch time
		return this.getTime();
	case 'Z': // set time zone
		return (this.getTimezoneOffset() / 60) + ':' +
		zeroPad(this.getTimezoneOffset() % 60,2);
	case 'M': // set month
		if (str.length > 3) return this.getFullMonthName();
		if (str.length > 2) return this.getShortMonthName();
		return zeroPad(this.getMonth()+1,str.length);
	case 'E': // set dow
		if (str.length > 3) return this.getDOWName();
		if (str.length > 1) return this.getShortDOWName();
		return this.getDay();
	default:
		return str;
	}
}

Date.prototype.getFullMonthName = function() {
	return monthNames[this.getMonth()];
}

Date.prototype.getShortMonthName = function() {
	if(typeof shortMonthNames != 'undefined' ) return shortMonthNames[this.getMonth()];
	return this.getFullMonthName().substring(0,3);
}

Date.prototype.getDOWName = function () {
	return weekdayNames[this.getDay()];
}

Date.prototype.getShortDOWName = function () {
	if(typeof shortWeekdayNames != 'undefined' ) return shortWeekdayNames[this.getDay()];
	return this.getDOWName().substring(0,3);
}

Date.prototype.get12Hours = function () {
	return this.getHours() == 0 ? 12 :
	(this.getHours() > 12 ? this.getHours() - 12 : this.getHours());
}

function zeroPad(num) {
	//return (num	< 10)? '0' + num : num ;
	return zeroPad(num, 2);
}

function zeroPad(num, width) {
	num = num.toString();
	while (num.length < width)
		num = '0' + num;
	return num;
}
