
/**
 * DateFormatSymbol Defined Pattern Letters: (all other letters are reserved)
 * Letter	Component				Presentation		Example
 *	G		Era designator			Text				AD
 *	y		Year					Year				1996; 96
 *	M		Month in year			Month				July; Jul; 07
 *	w		Week in year			Number				27
 *	W		Week in month			Number				2
 *	D		Day in year				Number				189
 *	d		Day in month			Number				10
 *	F		Day of week in month	Number				2
 *	E		Day in week				Text				Tuesday; Tue
 *	a		Am/pm marker			Text				PM
 *	H		Hour in day (0-23)		Number				0
 *	k		Hour in day (1-24)		Number				24
 *	K		Hour in am/pm (0-11)	Number				0
 *	h		Hour in am/pm (1-12) 	Number				12
 *	m		Minute in hour			Number				30
 *	s		Second in minute		Number				55
 *	S		Millisecond				Number				978
 *	z		Time zone				General time zone	Pacific Standard Time; PST; GMT-08:00
 *	Z		Time zone				RFC 822 time zone	-0800
 */
function DateFormatSymbols () {
	this.aAmPmStrings = [ 'AM', 'PM', ];
	this.aEraStrings = [ 'BC', 'AD', ];
	this.sLocalPatternChars = 'GyMdkHmsSEDFwWahKzZ';
	this.aMonthStrings = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', ];
	this.aShortMonthStrings = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec', ];
	this.aShortWeekdayStrings = [ 'Mon', 'Tue', 'Wed', 'Thur', 'Fri', 'Sat', 'Sun', ];
	this.aWeekdayStrings = [ 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday', ];
          
	this.getAmPmStrings = function () {
		return this.aAmPmStrings;
	}
	this.getEras = function () {
		return this.aEraStrings;
	}
	this.getLocalPatternChars = function () {
		return this.sLocalPatternChars;
	}
	this.getMonths = function () {
		return this.aMonthStrings;
	}
	this.getShortMonths = function () {
		return this.aShortMonthStrings;
	}
	this.getShortWeekdays = function () {
		return this.aShortWeekdayStrings;
	}
	this.getWeekdays = function () {
		return this.aWeekdayStrings;
	}
}
      
function DateFormat (sFormat, oDate) {
	// DateFormat Constants
	this.nTagQuoteASCIIChar = 100;
	this.nTagQuoteChars = 101;
	this.nMinsPerHour = 60;
	this.sGMTPlus = 'GMT+';
	this.sGMTMinus = 'GMT-';
          
	// DateFormat Member Fields
	this.oFormatSymbols = new DateFormatSymbols ();
	this.sPattern = sFormat;
	this.aCompiledPattern = null;
          
	// DateFormat Member Methods
	this.compile = function (sPattern) {
		if (this.sPattern != sPattern)
			this.sPattern = sPattern;
		
		var nLength = this.sPattern.length;
		var bInQuote = false;
		var sCompiledBuffer = '';
		var sTempBuffer = '';
		var nCount = 0;
		var nLastTag = -1;
              
		for (var i = 0; i < nLength; ++i) {
			var c = this.sPattern.charAt (i);
			if (c == '\'') {
				if ((i + 1) < nLength) {
					c = this.sPattern.charAt (i + 1);
					if (c == '\'') {
						++i;
						if (nCount != 0) {
							sCompiledBuffer = this.encode (nLastTag, nCount, sCompiledBuffer);
							nLastTag = -1;
							nCount = 0;
						}
						if (bInQuote) {
							sTempBuffer += c;
						} else {
							sCompiledBuffer += String.fromCharCode (this.nTagQuoteASCIIChar << 8 | c.charCodeAt (0));
						}
						continue;
					}
				}
				if (!bInQuote) {
					if (nCount != 0) {
						sCompiledBuffer = this.encode (nLastTag, nCount, sCompiledBuffer);
						nLastTag = -1;
						nCount = 0;
					}
					if (sTempBuffer == null) {
						sTempBuffer = '';
					} else {
						sTempBuffer = '';
					}
					bInQuote = true;
				} else {
					var nLen = sTempBuffer.length;
					if (nLen == 1) {
						var ch = sTempBuffer.charCodeAt (0);
						if (ch < 128) {
							sCompiledBuffer += String.fromCharCode (this.nTagQuoteASCIIChar << 8 | ch);
						} else {
							sCompiledBuffer += String.fromCharCode (this.nTagQuoteChars << 8 | 1);
							sCompiledBuffer += String.fromCharCode (ch);
						}
					} else {
						sCompiledBuffer = this.encode (this.nTagQuoteChars, nLen, sCompiledBuffer);
						sCompiledBuffer += sTempBuffer;
					}
					bInQuote = false;
				}
				continue;
			}
			if (bInQuote) {
				sTempBuffer += c;
				continue;
			}
			if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) {
				if (nCount != 0) {
					sCompiledBuffer = this.encode (nLastTag, nCount, sCompiledBuffer);
					nLastTag = -1;
					nCount = 0;
				}
				if (c.charCodeAt (0) < 128) {
					sCompiledBuffer += String.fromCharCode (this.nTagQuoteASCIIChar << 8 | c.charCodeAt (0));
				} else {
					var j;
					for (j = i + 1; j < nLength; ++j) {
						var d = this.sPattern.charAt (j);
						if (d == '\'' || (d >= 'a' && d <= 'z' || d >= 'A' && d <= 'Z')) {
							break;
						}
					}
					sCompiledBuffer += String.fromCharCode (this.nTagQuoteChars << 8 | (j - i));
					for (; i < j; ++i) {
						sCompiledBuffer += this.sPattern.charAt (i);
					}
					--i;
				}
				continue;
			}
                  
			var nTag;
			if ((nTag = this.oFormatSymbols.getLocalPatternChars ().indexOf (c)) == -1) {
				throw new Error ('IllegalArgumentException: Illegal pattern character \'' + c + '\'');
			}
			if (nLastTag == -1 || nLastTag == nTag) {
				nLastTag = nTag;
				++nCount;
				continue;
			}
			sCompiledBuffer = this.encode (nLastTag, nCount, sCompiledBuffer);
			nLastTag = nTag;
			nCount = 1;
		}
              
		if (bInQuote) {
			throw new Error ('IllegalArgumentException: Unterminated Quote');
		}
		if (nCount != 0) {
			sCompiledBuffer = this.encode (nLastTag, nCount, sCompiledBuffer);
		}
              
		var nLen = sCompiledBuffer.length;
		var aBuff = new Array ();
		for (var i = 0; i < nLen; ++i) {
			var sTemp = sCompiledBuffer.charAt (i);
			aBuff.push (sTemp);
		}
		return aBuff;
	}
          
	this.encode = function (nTag, nLength, sBuffer) {
		if (nLength < 255) {
			sBuffer += String.fromCharCode (nTag << 8 | nLength);
		}
		else {
			sBuffer += String.fromCharCode ((nTag << 8) | 0xFF);
			sBuffer += String.fromCharCode (nLength >>> 16);
			sBuffer += String.fromCharCode (nLength & 0xFFFF);
		}
		return sBuffer;
	}
	
	this.format = function (oDate) {
		if (this.sPattern == null) {
			throw new Error ('InvalidOperationException: Unable to format a Date without a pattern string');
		}
		
		if (this.aCompiledPattern == null) {
			this.aCompiledPattern = this.compile (this.sPattern);
		}
		
		var sFormattedString = '';
		for (var i = 0; i < this.aCompiledPattern.length; ) {
			var nTag = this.aCompiledPattern[i].charCodeAt (0) >>> 8;
			var nCount = this.aCompiledPattern[i++].charCodeAt (0) & 0xFF;
			if (nCount == 255) {
				nCount = this.aCompiledPattern[i++].charCodeAt (0) << 16;
				nCount |= this.aCompiledPattern[i++].charCodeAt (0);
			}
               
			switch (nTag) {
			case this.nTagQuoteASCIIChar:
				sFormattedString += String.fromCharCode (nCount);
				break;
			case this.nTagQuoteChars:
				for (var j = i; j < i + nCount; ++j)
					sFormattedString += this.aCompiledPattern[j];
				i += nCount;
				break;
               
			default:
				sFormattedString = this.subFormat (oDate, nTag, nCount, sFormattedString);
				break;
			}
		}
		return sFormattedString;
	}
	
	this.subFormat = function (oDate, nPatternCharIndex, nCount, sBuffer) {
		var nMaxIntCount = Number.MAX_VALUE;
		var sCurrent = null;
		var nBeginOffset = sBuffer.length;
		var nValue = 0;
              
		switch (nPatternCharIndex) {
		case 0: // 'G' - ERA
			// Hard code this to be 'AD' sort it out later, maybe
			nValue = 1;
			sCurrent = this.oFormatSymbols.getEras ()[nValue];
			break;
		case 1: // 'y' - YEAR
			nValue = oDate.getFullYear ();
			if (nCount >= 4)
				sBuffer = this.zeroPaddingNumber (nValue, nCount, nMaxIntCount, sBuffer);
			else
				sBuffer = this.zeroPaddingNumber (nValue, 2, 2 ,sBuffer);
			break;
		case 2: // 'M' - MONTH
			nValue = oDate.getMonth ();
			if (nCount >= 4)
				sCurrent = this.oFormatSymbols.getMonths ()[nValue];
			else if (nCount == 3)
				sCurrent = this.oFormatSymbols.getShortMonths ()[nValue];
			else
				sBuffer = this.zeroPaddingNumber (nValue + 1, nCount, nMaxIntCount, sBuffer);
			break;
		case 4: // 'k' - HOUR_OF_DAY: 1-based. eg 23:59 + 1 hour =>> 24:59
			nValue = oDate.getHours ();
			if (nValue == 0) {
				// The value 24 is hard coded because there is not Calander object in Javascript
				sBuffer = this.zeroPaddingNumber (24, nCount, nMaxIntCount, sBuffer);
			}
			else
				sBuffer = this.zeroPaddingNumber (nValue, nCount, nMaxIntCount, sBuffer);
			break;
		case 9: // 'E' - DAY_OF_WEEK
			nValue = oDate.getDay ();
			if (nCount >= 4)
				sCurrent = this.oFormatSymbols.getWeekdays ()[nValue];
			else
				sCurrent = this.oFormatSymbols.getShortWeekdays ()[nValue];
			break;
		case 14: // 'a' - AM_PM
			nValue = (oDate.getHours () >= 12) ? 0 : 1;
			sCurrent = this.oFormatSymbols.getAmPmStrings ()[nValue];
			break;
		case 15: // 'h' - HOUR: 1-based. eg, 11PM + 1 hour =>> 12 AM
			nValue = (oDate.getHours () >= 12) ? (oDate.getHours () - 12) : oDate.getHours ();
			if (nValue == 0)
				sBuffer = this.zeroPaddingNumber (12, nCount, nMaxIntCount, sBuffer);
			else
				sBuffer = this.zeroPaddingNumber (nValue, nCount, nMaxIntCount, sBuffer);
			break;
		case 17: // 'z' - ZONE_OFFSET
			var nZoneIndex = -1; // this.oFormatSymbols.getZoneIndex (0);
			if (nZoneIndex == -1) {
				nValue = oDate.getTimezoneOffset ();
				if (nValue < 0) {
					sBuffer += this.sGMTPlus;
					nValue = -nValue;
				} else {
					sBuffer += this.sGMTMinus;
				}
				var nNum = nValue / this.nMinsPerHour;
				if (nNum < 10) {
					sBuffer += '0';
				}
				sBuffer += nNum;
				sBuffer += ':';
				nNum = nValue % this.nMinsPerHour;
				if (nNum < 10) {
					sBuffer += '0';
				}
				sBuffer += nNum;
				var nDaylightSavingOffset = this.getDaylightSavingOffset (oDate);
				if (nDaylightSavingOffset != 0) {
					sBuffer += ' DST';
					if (nDaylightSavingOffset < 0) {
						nDaylightSavingOffset = -nDaylightSavingOffset;
						sBuffer += '-';
					}
					else {
						sBuffer += '+';
					}
					var nNum = nDaylightSavingOffset / this.nMinsPerHour;
					sBuffer += nNum;
					sBuffer += ':';
					nNum = nDaylightSavingOffset % this.nMinsPerHour;
					if (nNum < 10) {
						sBuffer += '0';
					}
					sBuffer += nNum;
				}
			}
			break;
		case 18: // 'Z' - ZONE_OFFSET ("-/+hhmm" form)
			nValue = oDate.getTimezoneOffset ();
			if (nValue < 0) {
				sBuffer += '+';
				nValue = -nValue;
			} else {
				sBuffer += '-';
			}
			
			var nNum = nValue / this.nMinsPerHour;
			if (nNum < 10) {
				sBuffer += '0';
			}
			sBuffer += nNum;
			nNum = nValue % this.nMinsPerHour;
			if (nNum < 10) {
				sBuffer += '0';
			}
			sBuffer += nNum;
			break;
		
		case 3: // 'd' - DATE
			nValue = oDate.getDate ();
			sBuffer = this.zeroPaddingNumber (nValue, nCount, nMaxIntCount, sBuffer);
			break;
		case 5: // 'H' - HOUR_OF_DAY:0-based. eg, 23:59 + 1 hour =>> 00:59
			nValue = oDate.getHours ();
			sBuffer = this.zeroPaddingNumber (nValue, nCount, nMaxIntCount, sBuffer);
			break;
		case 6: // 'm' - MINUTE
			nValue = oDate.getMinutes ();
			sBuffer = this.zeroPaddingNumber (nValue, nCount, nMaxIntCount, sBuffer);
			break;
		case 7: // 's' - SECOND
			nValue = oDate.getSeconds ();
			sBuffer = this.zeroPaddingNumber (nValue, nCount, nMaxIntCount, sBuffer);
			break;
		case 8: // 'S' - MILLISECOND
			nValue = oDate.getMilliseconds ();
			sBuffer = this.zeroPaddingNumber (nValue, nCount, nMaxIntCount, sBuffer);
			break;
		case 10: // 'D' - DAY_OF_YEAR:1-365 or 366. eg, 189
			nValue = 0; // oDate.getDayOfYear ();
			// sBuffer = this.zeroPaddingNumber (nValue, nCount, nMaxIntCount, sBuffer);
			break;
		case 11: // 'F' - DAY_OF_WEEK_IN_MONTH: ???
			nValue = 0; // oDate.getDayOfWeekInMonth ();
			// sBuffer = this.zeroPaddingNumber (nValue, nCount, nMaxIntCount, sBuffer);
			break;
		case 12: // 'w' - WEEK_OF_YEAR: 1-42
			nValue = 0; // oDate.getWeekOfYear ();
			// sBuffer = this.zeroPaddingNumber (nValue, nCount, nMaxIntCount, sBuffer);
			break;
		case 13: // 'W' - WEEK_OF_MONTH: 1-4
			nValue = 0; // oDate.getWeekOfMonth ();
			// sBuffer = this.zeroPaddingNumber (nValue, nCount, nMaxIntCount, sBuffer);
			break;
		case 16: // 'K' - HOUR: 0-based. eg, 11PM + 1 hour =>> 0AM
			nValue = (oDate.getHours () >= 12) ? (oDate.getHours () - 12) : oDate.getHours ();
			sBuffer = this.zeroPaddingNumber (nValue, nCount, nMaxIntCount, sBuffer);
			break;
			
		default:
			break;
		}
		
		if (sCurrent != null) {
			sBuffer += sCurrent;
		}
		
		return sBuffer;
	}
	
	this.zeroPaddingNumber = function (nValue, nMinDigits, nMaxDigits, sBuffer) {
		try {
			if (nValue >= 0) {
				if (nValue < 100 && nMinDigits >= 1 &&  nMinDigits <= 2) {
					if (nValue < 10) {
						if (nMinDigits == 2) {
							sBuffer += '0';
						}
						sBuffer += nValue;
					}
					else {
						sBuffer += Math.floor (nValue / 10);
						sBuffer += Math.floor (nValue % 10);
					}
					return sBuffer;
				} else if (nValue >= 1000 && nValue < 10000) {
					if (nMinDigits == 4) {
						sBuffer += Math.floor (nValue / 1000);
						nValue %= 1000;
						sBuffer += Math.floor (nValue / 100);
						nValue %= 100;
						sBuffer += Math.floor (nValue / 10);
						sBuffer += Math.floor (nValue % 10);
						return sBuffer;
					}
					if (nMinDigits == 2 && nMaxDigits == 2) {
						sBuffer = this.zeroPaddingNumber (Math.floor (nValue % 100), 2, 2, sBuffer);
						return sBuffer;
					}
				}
			}		
		}
		catch (error) {
		}
		
		var aDigits = new Array ();
		var nPower = Math.floor (Math.log (nValue) / Math.LN10);
		for (var i = nPower; i > 0; --i) {
			aDigits.push (Math.floor (nValue / Math.pow (10, i)));
			nValue %= Math.pow (10, i);
		}
		aDigits.push (Math.floor (nValue % 10));
		if (aDigits.length < nMinDigits) {
			var nLen = aDigits.length;
			for (var i = nLen; i < nMinDigits; ++i) {
				aDigits.unshift ('0');
			}
		}
		for (var i = 0; i < aDigits.length; ++i) {
			sBuffer += aDigits[i];
		}
		
		return sBuffer;
	}
          
	this.getDaylightSavingOffset = function (oDate) {
		var oDateJan1st = new Date (oDate.getFullYear (), 0, 1);
		var oDateJul1st = new Date (oDate.getFullYear (), 6, 1);
		
		return oDateJan1st.getTimezoneOffset () - oDateJul1st.getTimezoneOffset ();
	}
	
	if (oDate != null) {
		return this.format (oDate);
	}
}
