// Date complementary method

// getFormatYear
// return year (in string format)
// outputFormat:
// anything shorter than or equal to 2 will return 2 digit year
// otherwise, return 4 digit year
// default is 4 digit year
Date.prototype.getFormatYear = function(outputFormat) {
	if (!outputFormat)
		outputFormat = "yyyy";

	if (String(outputFormat).length <= 2)
		return String(this.getFullYear()).substring(2);
	else
		return zeroPaddingNumber(this.getFullYear(), String(outputFormat).length);
}
// getFormatYear ends


// getFormatMonth
// return month in year (in string format)
// outputFormat:
// "MMMM" - August
// "MMM"  - Aug
// "MM"   - 08 (default)
// "M"    - 8
Date.prototype.getFormatMonth = function(outputFormat) {
	if (!outputFormat)
		outputFormat = "MM";

	var tmpMonth = Number(this.getMonth()) + 1;

	var fullname, shortname;
	switch (tmpMonth) {
	case 1:
		fullname = "January";
		shortname = "Jan";
		break;
	case 2:
		fullname = "Febrary";
		shortname = "Feb";
		break;
	case 3:
		fullname = "March";
		shortname = "Mar";
		break;
	case 4:
		fullname = "April";
		shortname = "Apr";
		break;
	case 5:
		fullname = "May";
		shortname = "May";
		break;
	case 6:
		fullname = "June";
		shortname = "Jun";
		break;
	case 7:
		fullname = "July";
		shortname = "Jul";
		break;
	case 8:
		fullname = "August";
		shortname = "Aug";
		break;
	case 9:
		fullname = "September";
		shortname = "Sep";
		break;
	case 10:
		fullname = "October";
		shortname = "Oct";
		break;
	case 11:
		fullname = "November";
		shortname = "Nov";
		break;
	case 12:
		fullname = "December";
		shortname = "Dec";
		break;
	}

	if (outputFormat == "MMMM")
		return fullname;
	else if (outputFormat == "MMM")
		return shortname;
	else if (outputFormat == "M")
		return String(tmpMonth);
	else // "MM" or anything else
		return zeroPaddingNumber(tmpMonth, 2);
}
// getFormatMonth ends


// getFormatDate
// return day in month (in string format)
// outputFormat:
// "dd" - 08 (default)
// "d"  - 8
Date.prototype.getFormatDate = function(outputFormat) {
	if (!outputFormat)
		outputFormat = "dd";

	if (outputFormat == "d")
		return String(this.getDate());
	else // "dd" or anything else
		return zeroPaddingNumber(this.getDate(), 2);
}
// getFormatDate ends


// getFormatDay
// return day in week (in string format), 1 is monday and 7 is sunday
// outputFormat:
// "EEEE" - Monday (default)
// "EEE"  - Mon
// "EE"   - 01
// "E"    - 1
Date.prototype.getFormatDay = function(outputFormat) {
	if (!outputFormat)
		outputFormat = "EEEE";

	var tmpDay = this.getDay();

	var fullname, shortname;
	switch (tmpDay) {
	case 1:
		fullname = "Monday";
		shortname = "Mon";
		break;
	case 2:
		fullname = "Tuesday";
		shortname = "Tue";
		break;
	case 3:
		fullname = "Wednesday";
		shortname = "Wed";
		break;
	case 4:
		fullname = "Thursday";
		shortname = "Thu";
		break;
	case 5:
		fullname = "Friday";
		shortname = "Fri";
		break;
	case 6:
		fullname = "Saturday";
		shortname = "Sat";
		break;
	case 0:
		fullname = "Sunday";
		shortname = "Sun";
		break;
	}

	if (outputFormat == "EEE")
		return shortname;
	else if (outputFormat == "EE")
		return zeroPaddingNumber(tmpDay, 2);
	else if (outputFormat == "E")
		return String(tmpDay);
	else // "EEEE" or anything else
		return fullname;
}
// getFormatDay ends


// getFormatHours
// return hour (in string format)
// outputFormat:
// "HH" - 00 (default)
// "hh" - 12
// "H"  - 0
// "h"  - 12
Date.prototype.getFormatHours = function(outputFormat) {
	if (!outputFormat)
		outputFormat = "HH";

	var tmpHour = this.getHours();

	if (outputFormat == "h") {
		if (tmpHour == 0)
			return "12";
		else if (tmpHour >= 12)
			return String(tmpHour % 12);
		else
			return String(tmpHour);
	} else if (outputFormat == "hh") {
		if (tmpHour == 0)
			return "12";
		else if (tmpHour >= 12)
			return zeroPaddingNumber(tmpHour % 12, 2);
		else
			return zeroPaddingNumber(tmpHour, 2);
	} else if (outputFormat == "H") {
		return String(tmpHour);
	} else { // "HH" or anything else
		return zeroPaddingNumber(tmpHour, 2);
	}
}
// getFormatHours ends


// getFormatMinutes
// return minute (in string format)
// outputFormat:
// "mm" - 05 (default)
// "m"  - 5
Date.prototype.getFormatMinutes = function(outputFormat) {
	if (!outputFormat)
		outputFormat = "mm";

	var tmpMin = this.getMinutes();

	if (outputFormat == "m")
		return String(tmpMin);
	else // "mm" or anything else
		return zeroPaddingNumber(tmpMin, 2);
}
// getFormatMinutes ends


// getFormatSeconds
// return second (in string format)
// outputFormat:
// "ss" - 05 (default)
// "s"  - 5
Date.prototype.getFormatSeconds = function(outputFormat) {
	if (!outputFormat)
		outputFormat = "ss";

	var tmpSec = this.getSeconds();

	if (outputFormat == "s")
		return String(tmpSec);
	else // "ss" or anything else
		return zeroPaddingNumber(tmpSec, 2);
}
// getFormatSeconds ends


// getFormatAMPM
// return second (in string format)
// outputFormat:
// "A" - AM (default)
// "a" - am
Date.prototype.getFormatAMPM = function(outputFormat) {
	if (!outputFormat)
		outputFormat = "A";

	var tmpHour = this.getHours();

	if (outputFormat == "a")
		return (tmpHour < 12) ? "am" : "pm";
	else // "A" or anything else
		return (tmpHour < 12) ? "AM" : "PM";
}
// getFormatAMPM ends


// getFormatTimezone
// return second (in string format)
// outputFormat:
// "zz" - GMT+08:00
// "z"  - GMT+0800 (default)
Date.prototype.getFormatTimezone = function(outputFormat, localTimezone) {
	if (!outputFormat)
		outputFormat = "z";
	if (!localTimezone)
		localTimezone = this.getTimezoneOffset();

	var separator = (outputFormat == "zz") ? ":" : "";

	var tmpTimezoneOffset = localTimezone;
	var tmpHour = zeroPaddingNumber(Math.floor(Math.abs(tmpTimezoneOffset)/60), 2);
	var tmpMin = zeroPaddingNumber(Math.abs(tmpTimezoneOffset) % 60, 2);

	if (tmpTimezoneOffset <= 0)
		return "GMT+" + tmpHour + separator + tmpMin;
	else
		return "GMT-" + tmpHour + separator + tmpMin;
}
// getFormatTimezone ends


// subFormat
// used by toString
// return the appropriate part of the Date object in approripate format
// e.g. subFormat("h", 2) will return hour in 2 digits
Date.prototype.subFormat = function(patternChar, patternLength, localTimezone) {
	if (!patternLength)
		patternLength = 1;

	patternLength = Number(patternLength);

	if (patternLength == 0)
		return "";
	
	var outputFormat = "";
	for (var i=0; i<patternLength; i++)
		outputFormat += patternChar;

	var output = "";
	switch (patternChar) {
	case "y":
		output = this.getFormatYear(outputFormat);
		break;
	case "M":
		output = this.getFormatMonth(outputFormat);
		break;
	case "d":
		output = this.getFormatDate(outputFormat);
		break;
	case "E":
		output = this.getFormatDay(outputFormat);
		break;
	case "H":
		output = this.getFormatHours(outputFormat);
		break;
	case "h":
		output = this.getFormatHours(outputFormat);
		break;
	case "m":
		output = this.getFormatMinutes(outputFormat);
		break;
	case "s":
		output = this.getFormatSeconds(outputFormat);
		break;
	case "A":
		output = this.getFormatAMPM(outputFormat);
		break;
	case "a":
		output = this.getFormatAMPM(outputFormat);
		break;
	case "z":
		output = this.getFormatTimezone(outputFormat, localTimezone);
		break;
	default:
		output = outputFormat;
	}

	return output;
}


// toString
// override the original toString method of Date
// it supports almost same pattern of SimpleDateFormat of Java
// and can define a group of alias format
// localTimezone is to display the date in the timezone u want
// it takes the format of the hours it leads GMT, e.g. 8 means GMT+0800 and -6 means GMT-0600
Date.prototype.toString = function(outputFormat, localTimezone) {
	if (!outputFormat) // default toString output format
		outputFormat = "EEE MMM dd HH:mm:ss z yyyy";

	// define alias format here
	else if (outputFormat.toUpperCase() == "IETF")
		outputFormat = "EEE, dd MMM yyyy HH:mm:ss z";
	else if (outputFormat.toUpperCase() == "DATE01")
		outputFormat = "EEEE, MMMM dd, yyyy";
	else if (outputFormat.toUpperCase() == "DATE02")
		outputFormat = "EEE, MMM dd, yyyy";
	else if (outputFormat.toUpperCase() == "DATE03")
		outputFormat = "MMMM dd, yyyy";
	else if (outputFormat.toUpperCase() == "DATE04")
		outputFormat = "MMM dd, yyyy";
	else if (outputFormat.toUpperCase() == "DATE05")
		outputFormat = "yyyy/MM/dd";
	else if (outputFormat.toUpperCase() == "DATE06")
		outputFormat = "dd/MM/yyyy";
	else if (outputFormat.toUpperCase() == "DATE07")
		outputFormat = "MM/dd/yyyy";

	if (!localTimezone)
		localTimezone = this.getTimezoneOffset() / -60;

	// convert the input localTimezone to the format of getTimezoneOffset
	var tmpLocalTimezone = localTimezone * -60;

	// we will use a double pass recursion algorithm to print the timezone we want
	// if no 3rd argument, means the 1st pass, check if we need to create a fake date
	// if there is 3rd argument, means the 2nd pass, just print the date with the fake timezone
	if (arguments.length < 3) {
		if (tmpLocalTimezone != this.getTimezoneOffset()) {
			var msecDifferent = (tmpLocalTimezone - this.getTimezoneOffset()) * -60000;
			var fakeDate = new Date((this.getTime() + msecDifferent));
			return fakeDate.toString(outputFormat, localTimezone, 1);
		}
	}

	outputFormat += "\0";
	var output = "";
	var quoted = false;	// boolean to check quoted text
	var patternLength = 0	// store the length of pattern
	var lastChar = "\0";
	var thisChar;

	for (var i=0; i<outputFormat.length; i++) {
		thisChar = outputFormat.charAt(i);
		if (thisChar != lastChar && patternLength > 0) {
			output += this.subFormat(lastChar, patternLength, tmpLocalTimezone);
			patternLength = 0;
		}
		if (thisChar == "'") {
			if (i+1 < outputFormat.length && outputFormat.charAt(i+1) == "'") {
				output += "'";
				i++;
			} else {
				quoted ^= true; // ^ is XOR, quoted ^= true means quoted = quoted ^ true, i.e. !quoted
						// thus, I can use: quoted = !quoted;
			}
		} else if (!quoted && (thisChar >= "a" && thisChar <= "z" || thisChar >= "A" && thisChar <= "Z")) {
			lastChar = thisChar;
			patternLength++;
		} else {
			output += thisChar;
		}
	}
	if (patternLength > 0)
		output += this.subFormat(lastChar, patternLength, tmpLocalTimezone);

	return output;
}

// covert "num" to "digit" digits
// the result will be in string format of coz!
// e.g. zeroPaddingNumber(3, 5) returns "00003"
function zeroPaddingNumber(num, digit) {
	if (String(num).length < Number(digit))
		return "0" + zeroPaddingNumber(num, Number(digit) - 1);
	else
		return String(num);
}
