/*  © Copyright 2001 - 2006 Excira Technologies, Inc. All Rights Reserved.
 * -----------------------------------------------------------------------
 *
 * JS used by both SG and sites
 *
 */

/**
 * 
 */
function isIE() {
	return (navigator.appName == "Microsoft Internet Explorer");
}

/**
 * 
 */
function clickThru(objName) {

	var obj = document.getElementById(objName);
	window.open(obj.firstChild, '', '');
}

/**
 * 
 */
 function showOffset(city) {

	document.getElementById(city + "Offset").style.display = "inline";
}

/**
 * 
 */
function hideOffset(city) {

	document.getElementById(city + "Offset").style.display = "none";
}

/**
 * 
 */
function showUnits(metric) {

	clearErrors();

	if ( metric ) {

		document.getElementById("weightUnitsMetric").style.display = "inline";
		document.getElementById("weightUnitsStandard").style.display = "none";
		document.getElementById("heightUnitsMetric").style.display = "inline";
		document.getElementById("heightUnitsStandard").style.display = "none";
	}
	else {

		document.getElementById("weightUnitsMetric").style.display = "none";
		document.getElementById("weightUnitsStandard").style.display = "inline";
		document.getElementById("heightUnitsMetric").style.display = "none";
		document.getElementById("heightUnitsStandard").style.display = "inline";
	}
}

/**
 * 
 */
 function submitBMI(formName) {

	if ( !isBMIValid() )
		return;

	// Update the target of the BMI form
	var form = document.getElementById(formName);
	if ( !form )
		return;

	form.target = "formHandler";
	form.submit();
 }

/**
 * 
 */
function isBMIValid() {

	clearErrors();

	var valid = true;

	// Check metric values
	if ( getCheckedValue(document.forms[0].metric) == 'true' ) {

		// Check height values have been entered correctly
		var height = document.getElementsByAttribute("name", "height", false);

		if ( height.value == '' ) {

			valid = showError(height, document.getElementById("heightErrors"), 'Please enter your height');
		}
		else if ( !height.value.match(/^\d+(\.\d+)?$/) ) {

			valid = showError(height, document.getElementById("heightErrors"), 'Please enter a valid height');
		}

		// Check weight values have been entered correctly
		var weight =  document.getElementsByAttribute("name", "weightMetric", false);
		if ( weight.value == '' ) {

			valid = showError(weight, document.getElementById('weightErrors'), 'Please enter your weight');
		}
		else if ( !weight.value.match(/^\d+(\.\d+)?$/) ) {

			valid = showError(weight, document.getElementById('weightErrors'), 'Please enter a valid weight');
		}
	}
	// Check standard values
	else {

		// Check height values have been entered correctly
		var feet = document.getElementsByAttribute("name", "feet", false); 
		var inches = document.getElementsByAttribute("name", "inches", false);

		// Check a value has been entered
		if ( feet.value == "" && inches.value == "" ) {

			showError(inches, document.getElementById("heightErrors"), 'Please enter your height');
			valid = showError(feet, document.getElementById("heightErrors"), 'Please enter your height');
		}
		else if ( !feet.value.match(/^\d+(\.\d+)?$/)
				|| !inches.value.match(/^\d+(\.\d+)?$/) ) {

			showError(feet, document.getElementById("heightErrors"), 'Please enter a valid height');
			valid = showError(inches, document.getElementById("heightErrors"), 'Please enter a valid height');
		}

		// Check weight values have been entered correctly
		var weight = document.getElementsByAttribute("name", "weightStandard", false);
		if ( weight.value == '' ) {

			valid = showError(weight, document.getElementById('weightErrors'), 'Please enter your weight');
		}
		else if ( !weight.value.match(/^\d+(\.\d+)?$/) ) {

			valid = showError(weight, document.getElementById('weightErrors'), 'Please enter a valid weight');
		}
	}
	return valid;
}

/**
 * 
 */
function showError(object, errorOjbect, message) {

	if ( errorOjbect ) {

		errorOjbect.innerHTML = message;
	}

	// Update style of invalid field
//	object.focus();
	object.style.borderColor = "red";
	return false;
}

/**
 * 
 */
 function clearErrors() {
	document.getElementsByAttribute("id", "weightErrors", false).innerHTML = "";
	document.getElementsByAttribute("id", "heightErrors", false).innerHTML = "";
	document.getElementsByAttribute("name", "height", false).style.borderColor = "";
	document.getElementsByAttribute("name", "feet", false).style.borderColor = "";
	document.getElementsByAttribute("name", "inches", false).style.borderColor ="";
	document.getElementsByAttribute("name", "weightMetric", false).style.borderColor ="";
	document.getElementsByAttribute("name", "weightStandard", false).style.borderColor ="";
 }

/**
 * 
 */
function getCheckedValue(radioObj) {

	if( !radioObj ) {

		return "";
	}

	var radioLength = radioObj.length;

	if( radioLength == undefined ) {

		if(radioObj.checked) {
			return radioObj.value;
		}
		else {
			return "";
		}
	}
	for(var i = 0; i < radioLength; i++) {

		if(radioObj[i].checked) {

			return radioObj[i].value;
		}
	}
	return "";
}

/**
 * 
 */
function getXMLHTTP(){

	var xmlhttp = false;
	/*@cc_on @*/
	/*@if (@_jscript_version >= 5)
	// JScript gives us Conditional compilation, we can cope with old IE versions.
	// and security blocked creation of the objects.
	 try {
	  xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
	 } catch (e) {
	  try {
	   xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
	  } catch (E) {
	   xmlhttp = false;
	  }
	 }
	@end @*/
	if ( !xmlhttp && typeof XMLHttpRequest != 'undefined' ) {
		xmlhttp = new XMLHttpRequest();
	}
	return xmlhttp;
}

/**
 * 
 */
function afterFormSubmit(sourceName, targetName) {

	// Get the returned HTML from the iframe....
	// check if the target exists in this page
	var targetObj = document.getElementById(targetName);
	if ( !targetObj )
		return;

	// check if the iframe exists in this page
	var iframe = document.getElementById("formHandler");
	if ( !iframe )
		return;

	// check if the iframe has anything in it
	var iframeObj = (iframe.contentDocument || iframe.contentWindow.document).getElementById(sourceName);
	if ( !iframeObj )
		return;

	// get the content, remove any body tag and set it on the target.
	var content = (iframe.contentDocument || iframe.contentWindow.document).documentElement.innerHTML;
	targetObj.innerHTML = stripForBody(content);
	
	// Now clear the content of the iframe
	iframe.src = "";

	window.status = "Done";
}

/**
 * 
 */
function afterFormSubmitPopup(siteName, formName) {

	// Get the returned content from the iframe
	var iframe = document.getElementById("formHandler");
	if ( !iframe )
		return;

	var text = (iframe.contentDocument || iframe.contentWindow.document).getElementById("formStatusText").innerHTML;

	createPopupWithText(siteName, text);

		window.status = "Done";

	// Reset form
	if ( !formName )
		return;

	var form = document.getElementById(formName);
	if ( !form )
		return;

	form.reset();
}


/**
 * 
 */
function createPopupWithText(siteName, text) {

	var id = "thankyou";

	// Do not proceed if a popup already exists
	// with this id
	if ( popup && popup.id == id )
		return;

	// If a popup exists, but with a different id,
	// close it!
	if ( popup )
		popup.close();

	// Create new popup
	var titleText = siteName + " : Thank You";
	popup = new Popup(id, "490px", "popup", titleText, text);

	// Create privacy container
	var privacyDiv = popup.dom.appendDiv(popup, "", "", "popupPrivacy");

	// Add "show" link to privacy div
	var privacyShowLink = popup.dom.appendA(privacyDiv, "", "", "", "Privacy Policy", "#");

	// Create hidden privacy text
	var privacyP = popup.dom.appendP(privacyDiv, "", "", "popupPrivacyText");
	privacyP.innerHTML = "We hate spam too!<br/><br/>Your privacy is very important to us." +
		" The names and email addresses collected on this form are not saved and not shared. " +
		siteName + " will not send you unsolicited emails.";
	privacyP.style.display = "none";
	
	// Add onclick event to show link
	privacyShowLink.onclick = function() {
		popup.toggleChildDisplay(privacyP);
	}
}


/**
 * 
 */
function stripForBody(html) {

	// Convert all tags to lower case
	//html = html.replace(new RegExp("<(.|\n)+?>", "gi"), function($1) {return $1.toLowerCase()});

	var headFinish = html.indexOf("</head>");
	html = html.substring((headFinish + 7), html.length);
	html = html.replace("</head>", "");
	html = html.replace("<body>", "");
	html = html.replace("</body>", "");
	return html;
}

/*  © Copyright 2001 - 2006 Excira Technologies, Inc. All Rights Reserved.
 * -----------------------------------------------------------------------
 *
 * @author: Mim Hastie
 *
 */

 var DATE_ERROR_TEXT = "* Invalid date format (yyyy-mm-dd)";
 var EMAIL_ERROR_TEXT = "* Invalid email";
 var PASSWORD_MATCH_ERROR_TEXT = "* Passwords do not match";
 var PASSWORD_LENGTH_ERROR_TEXT = "* Password must be at least 4 characters";
 var REQUIRED_ERROR_TEXT = "* Required";
 var UNIQUE_ERROR_TEXT = "* Value already exists"
 var INVALID_TEXT = "* Invalid value";
 var TERMS_ERROR_TEXT = "* Read Terms & Privacy Policy";
 var TIME_ERROR_TEXT = "* Invalid time";
 var DURATION_ERROR_TEXT = "* Invalid duration"; 
 var PACE_ERROR_TEXT = "* Invalid pace"; 
 var NULL_RESOURCE_REF = "http://www.excira.com/stargazer/1.0/00000000000000";

/**
 * 
 */
function isValid(form, registeringClick, invalidValues) {

	if ( !invalidValues )
		invalidValues = new Array();

	var valid = true;

	// Iterate over elements on form and validate
	for ( var i = 0 ; i < form.elements.length ; i++ ) {

		var object = form.elements[i];

		if ( isConfirmObject(object) )
			continue;

		var objectValid = true;

		// Validate required field
		if ( !validateRequired(form, object) ) {

			// Highlight display field if this is a hidden object
			if ( isHiddenObject(object) ) {

				var displayObj = form["Display" + object.name];
				if ( displayObj ) 
					highlightErrors(form, displayObj, REQUIRED_ERROR_TEXT);
			}
			// Otherwise just highlight object
			else 
				highlightErrors(form, object, REQUIRED_ERROR_TEXT);

			valid = false;
			objectValid = false;
		}

		// Validate date
		if ( !validateDate(object) ) {
			highlightErrors(form, object, DATE_ERROR_TEXT);
			valid = false;
			objectValid = false;
		}

		// Validate email
		if ( !validateEmail(object) ) {
			highlightErrors(form, object, EMAIL_ERROR_TEXT);
			valid = false;
			objectValid = false;
		}
		
		// Validate password length
		if ( !validatePasswordLength(form, object) ) {
			highlightErrors(form, object, PASSWORD_LENGTH_ERROR_TEXT);

			// Get password confirm object
			var confirmObj = form["Confirm" + object.name];
			if ( confirmObj )
				highlightErrors(form, confirmObj, PASSWORD_LENGTH_ERROR_TEXT);

			valid = false;
			objectValid = false;
		}		

		// Validate password match
		if ( !validatePasswordMatch(form, object) ) {
			highlightErrors(form, object, PASSWORD_MATCH_ERROR_TEXT);

			// Get password confirm object
			var confirmObj = form["Confirm" + object.name];
			if ( confirmObj )
				highlightErrors(form, confirmObj, PASSWORD_MATCH_ERROR_TEXT);

			valid = false;
			objectValid = false;
		}

		// Validate paths
		// No white space, no starting
		if ( !validatePath(object) ) {
			highlightErrors(form, object, INVALID_TEXT);
			valid = false;
			objectValid = false;
		}

		// Check file upload confirmation has been
		// selected		
		if ( !validateFileUploadConfirmation() ) {
			valid = false;
			objectValid = false;
		}

		// Validate rules
		if ( !validateValue(object, invalidValues) ) {
			highlightErrors(form, object, INVALID_TEXT);
			valid = false;
			objectValid = false;
		}

		// Validate unique
		var validate = object.getAttribute("unique");
		if ( validate != null && validate != "false" ) {

			var propURI = object.name;
			var value = object.value;

			// Don't validate null or empty values
			if ( value != null && value != "" ) {

				// Get the subject URI to check if any existing
				// values are for this subject
				var subjectURI = "";
				if ( form.subjectURI ) 
					subjectURI = form.subjectURI.value;

				// Get the type URI
				var subjectTypeURI = "";
				if ( form.subjectTypeURI )
					subjectTypeURI = form.subjectTypeURI.value;

				// Synchronous call to server to check if unique
				var xmlhttp = getXMLHTTP();
				xmlhttp.open('GET', 
					encodeURI("/handleValidateUnique.ajax?propURI=" + propURI + "&value=" + value 
					+ "&subjectURI=" + subjectURI + "&subjectTypeURI=" + subjectTypeURI), false);
				xmlhttp.send(null);

				// Get unique value from response
				var valUniqueTag = xmlhttp.responseXML.getElementsByTagName("validateUnique");
				var unique = getElementData(valUniqueTag[0], "unique");

				// If value is not unique, show errors etc
				if ( unique == "false" ) {
					highlightErrors(form, object, UNIQUE_ERROR_TEXT);
					valid = false;
					objectValid = false;
				}
			}
		}

		// Validate terms
		if ( !validateTerms(object) ) {
			highlightErrors(form, object, TERMS_ERROR_TEXT);
			valid = false;
			objectValid = false;
		}
		
		// Validate duration or pace
		if ( !validateDuration(form, object) ) {
			if ( isDuration(object) )
				highlightErrors(form, object, DURATION_ERROR_TEXT);
			else
				highlightErrors(form, object, PACE_ERROR_TEXT);
			valid = false;
			objectValid = false;			
		}		
		
		// Validate time
		if ( !validateTime(form, object) ) {
			highlightErrors(form, object, TIME_ERROR_TEXT);
			valid = false;
			objectValid = false;			
		}	
		
		// Validate seconds
		if ( !validateSeconds(form, object) ) {
			highlightErrors(form, object, TIME_ERROR_TEXT);
			valid = false;
			objectValid = false;				
		}
		
		// Validate ints
		if ( !validateInt(form, object) ) {
			highlightErrors(form, object, INVALID_TEXT);
			valid = false;
			objectValid = false;			
		}	
		
		// Validate floats
		if ( !validateFloat(form, object) ) {
			highlightErrors(form, object, INVALID_TEXT);
			valid = false;
			objectValid = false;			
		}
		
		// Validate floats to one decimal
		if ( !validateFloatOne(form, object) ) {
			highlightErrors(form, object, INVALID_TEXT);
			valid = false;
			objectValid = false;			
		}		

		// Clear any previous errors if valid
		if ( objectValid ) {			
				highlightErrors(form, object, "");

			// Clear any confirm objects
			var confirmObj = form["Confirm" + object.name];
			if ( confirmObj )
				highlightErrors(form, confirmObj, "");
		}

		// Update durations
		updateDuration(form, object);
		
		// Update times
		updateTime(form, object);		
	}

	// Enable form clicks if invalid
	if ( !valid && registeringClick ) {
		linking = false;
		registerOnClick(null);
	}

	return valid;
}

/**
 * 
 */
function isConfirmObject(object) {

	// Clear all non-confirm fields
	var confirm = object.getAttribute("confirm");
	return !( confirm == null || confirm == "false" ) ;
}

/**
 * 
 */
function isHiddenObject(object) {

	return (object.type == "hidden");
}

/**
 * 
 */
function validateRequired(form, object) {

	var required = object.getAttribute("required");

	if ( required == null || required == "false" ) 
		return true;

	//
	// Input - Excluding Hidden
	//
	if ( object.type == "text" || object.type == "textarea" || object.type == "password" ) {
		return (object.value != "");
	}

	//
	// Input - Hidden
	//
	if ( isHiddenObject )
		return (object.value != "") && (object.value != NULL_RESOURCE_REF);

	//
	// Radio
	//
	if (object.type == "radio" || object.type == "checkbox") {

		// Get the set of radio buttons
		var radios = form[object.name];

		// Check that a button has been checked
		for ( var i = 0 ; i < radios.length ; i++ ) {
			if ( radios[i].checked )
				return true;
		}

		// Otherwise return false
		return false;
	}

	//
	// Drop down
	//
	if (object.tagName == "SELECT") {
		return (object[object.selectedIndex].value != ""
					&& object[object.selectedIndex].value != NULL_RESOURCE_REF);
	}

	return true;
}

/**
 * 
 */
function validateDate(dateElement) {

	// Ignore non-date fields
	if ( dateElement.name.indexOf("Date") == -1 )
		return true;

	return validateDateString(dateElement.value);
}

/**
 * 
 */
function validateDateString(tmpDate) {
	
	var expression = /\D/;
	if ( tmpDate != "" ) {

		// Return false if length of 
		// date is not 10 char
		if ( tmpDate.length != 10 )
			return false;

		// Return false if month is greater than 12
		// or if month is NaN
		tmpMonth = tmpDate.substring(5,7);
		if ( expression.test(tmpMonth) )
			return false;
		if ( parseFloat(tmpMonth) > 12 )
			return false;

		// Return false if day is greater than 31
		// or if month is NaN
		tmpDay = tmpDate.substring(8,10);
		if ( expression.test(tmpDay) )
			return false;
		if ( parseFloat(tmpDay) > 31 )
			return false;

		// Return false if year NaN	
		tmpYear = tmpDate.substring(0,4);
		if ( expression.test(tmpYear) )
			return false;
	
		var isValid = new Date( tmpYear, (tmpMonth - 1), tmpDay, 12, 0, 0, 0 );
		var dateExist = isValid.getDate();
		if ( dateExist != tmpDay ) {
			if ( dateExist.length == 1 ) dateExist = "0" + dateExist;
			var newMonth = isValid.getMonth() + 1;
			if ( newMonth.length == 1 ) newMonth = "0" + newMonth;
			var newDate = dateExist + "/" + newMonth + "/" + isValid.getYear();

			dateElement.value = newDate;
		}
	}
	return true;
}

/**
 * 
 */
function createDate(dateElement) {

	date = parseInt(parseFloat(dateElement.value.substring(8, 10)));
	month = parseInt(parseFloat(dateElement.value.substring(5, 7)));
	year = parseInt(dateElement.value.substring(0, 4));

	var finalDate = new Date(year, (month - 1), date, 12, 0, 0, 0 );
	
	return finalDate;

}

/**
 * 
 */
function validateEmail(object) {

	// Check if object requires email validation
	var email = object.getAttribute("email");
	if ( email == null || email == "false" ) 
		return true;

	// Return true if no value is set
	if ( !object.value )
		return true;

	// Otherwise test value to ensure valid
	// email address has been set
	var emailRegEx = 
		/^([\w\d\-\.]+)@{1}(([\w\d\-]{1,67})|([\w\d\-]+\.[\w\d\-]{1,67}))\.(([a-zA-Z]{2,4})(\.[a-zA-Z]{2})?)$/i;
	
	return emailRegEx.test(object.value);
}

/**
 * 
 */
function validatePasswordLength(form, object) {

	// Check if object requires password validation
	var password = object.getAttribute("password");
	if ( password == null || password == "false" ) 
		return true;

	// Get the value of the password field
	var password = object.value;
	
	// Return false if password is short than 4 char
	if ( password.length < 4 )
		return false;

	return true;
}

/**
 * 
 */
function validatePasswordMatch(form, object) {

	// Check if object requires password validation
	var password = object.getAttribute("password");
	if ( password == null || password == "false" ) 
		return true;

	// Get the value of the password field
	var password = object.value;
	
	// Get the value of the password confirm field
	var confirmObj = form["Confirm" + object.name];
	var passwordConfirm = confirmObj.value;

	return password == passwordConfirm;
}

/**
 * 
 */
function validatePath(object) {

	// First check if object requires path validation
	var path = object.getAttribute("path");
	if ( path == null || path == "false" )
		return true;

	// Check for white spaces
	var value = object.value;
	if ( value.indexOf(" ") != -1 )
		return false;

	// Now check for leading forward slash
	if ( value.indexOf("/") == 0 )
		return false;

	return true;
}

/**
 * 
 */
function validateFileUploadConfirmation() {

	var valid = true;

	// Check if there is an overwrite confirmation box
	var overwriteContainer = document.getElementById("overwriteContainer");
	if ( overwriteContainer ) {

		// Confirmation box exists - confirm it has been checked
		var overwrite = overwriteContainer.lastChild;
		if ( overwrite ) {

			// Confirm overwrite has been selected
			if ( !overwrite.checked ) {

				document.getElementById("overwriteContainer").className = "errors";
				valid = false;
			}
		}
	}
	return valid;
}

/**
 * 
 */
function validateValue(object, invalidValues) {

	// Only proceed if object is text box, text area or password
	if ( object.type != "text" && object.type != "textarea" ) 
		return true;

	// Iterate over invalid values, return false if object value
	// contains an invalid value
	var value = object.value;
	for ( var i = 0 ; i < invalidValues.length ; i++ ) {

		if ( value.indexOf(invalidValues[i]) != -1 )
			return false;
	}

	// Value is valid, return true!
	return true;
}

/**
 * 
 */
function validateTerms(object) {

	// Check if object requires terms validation
	var terms = object.getAttribute("terms");
	if ( terms == null || terms == "false" ) 
		return true;

	// Return true if no value is set
	if ( !object.value )
		return true;

	// Otherwise confirm that terms checkbox has been checked
	return object.checked;
}

/**
 * 
 */
function validateDistance(obj) {
	
	// Check distance is in format xxx.xx
	var pattern = /^\d{0,3}(\.\d{1,2})?$/;
	return pattern.test(obj.value);
}

/**
 * 
 */
function validateDuration(form, object) {

	// First check if object is duration or pace
	if ( !isDurationOrPace(object) )
		return true;
		
	// First get the actual duration object
	var objName = object.name;
	
	// Get hours and minutes and seconds
	var hours = form[objName + "Hours"];
	var minutes = form[objName + "Minutes"];
	var seconds = form[objName + "Seconds"];
	
	// Check that if one is specified, so are the others
	
	// Do not proceed if all fields are empty
	if ( isDurationEmpty(hours, minutes, seconds) )
		return true;
			
	// Check only integers have been specified
	var numericPattern = /^\d+$/;
	if ( hours && hours.value != "" && !numericPattern.test(hours.value) ) 
		return false;
	if ( minutes && minutes.value != "" && !numericPattern.test(minutes.value) ) 
		return false;
	if ( seconds && seconds.value != "" && !numericPattern.test(seconds.value) ) 
		return false;	
		
	// Check minutes is valid for durations
	// Pace can have a minute value > 59, duration can not
	if ( isDuration(object) && !isValidMinuteOrSecond(minutes) )		
		return false;
		
	// Check seconds is valid
	if ( !isValidMinuteOrSecond(seconds) )		
		return false;		
		
	return true;
}


/**
 *
 */
function updateDuration(form, object) {

	// First check if object is duration or pace
	if ( !isDurationOrPace(object) )
		return true;
		
	// Get hour, minute and second objects
	var objName = object.name;

	var hours = form[objName + "Hours"];
	var minutes = form[objName + "Minutes"];
	var seconds = form[objName + "Seconds"];
		
	// Finally update the value of the object with the duration string
	object.value = generateDurationString(hours, minutes, seconds);	
}

/**
 * 
 */
function isDurationOrPace(object) {
	
	// Check if object is duration or pace
	var duration = object.getAttribute("duration");
	var pace = object.getAttribute("pace");
	return (isPace(object) || isDuration(object));
}

/**
 * 
 */
function isDuration(object) {
	
	var duration = object.getAttribute("duration");
	return (duration != null && duration == "true");
}

/**
 * 
 */
function isPace(object) {

	var pace = object.getAttribute("pace");
	return (pace != null && pace == "true");	
}



/**
 * 
 */
function isDurationEmpty(hours, minutes, seconds) {
	
	return (!hours || hours && hours.value == "") && 
			(!minutes || minutes && minutes.value == "") &&
			(!seconds || seconds && seconds.value == "");
}

/**
 * 
 */
function isDurationComplete(hours, minutes, seconds) {
	
	var hoursValid = true;
	if ( hours && hours.value == "" )
		hoursValid = false;
		
	var minutesValid = true;
	if ( minutes && minutes.value == "" )
		minutesValid = false;
		
	var secondsValid = true;
	if ( seconds && seconds.value == "" )
		secondsValid = false;
	
	return hoursValid && minutesValid && secondsValid;
}

/**
 * 
 */
function generateDurationString(hoursObj, minutesObj, secondsObj) {
	
	// First check if no duration has been specified
	// Simply return empty strin
	if ( isDurationEmpty(hoursObj, minutesObj, secondsObj) )
		return "";
	
	// Otherwise generate duration string
	var strDuration = "PT";
		
	// First add the hours
	if ( hoursObj && hoursObj.value != null && hoursObj.value != '' )
		strDuration = strDuration + hoursObj.value + "H";
	else 
		strDuration = strDuration + "0H";
		
	// Now add the minutes 
	if ( minutesObj && minutesObj.value != null && minutesObj.value != '')
		strDuration = strDuration + minutesObj.value + "M";
	else 
		strDuration = strDuration + "0M";	
		
	// Now add the seconds 
	if ( secondsObj && secondsObj.value != null && secondsObj.value != '')
		strDuration = strDuration + secondsObj.value + "S";
	else 
		strDuration = strDuration + "0S";
		
	return strDuration;	
}

/**
 * 
 */
function validateTime(form, object) {

	// First check if object is time
	var time = object.getAttribute("time");
	if ( time == null || time == "false" )
		return true;
		
	// Check hour and minute times
	// First get the actual time object
	var objName = object.name;
	
	// Get hours and minutes
	var hours = form[objName + "Hours"];
	var minutes = form[objName + "Minutes"];
	
	if ( isDurationEmpty(hours, minutes, null) )
		return true;
	
	// Check that if one is specified, so is the other
	if ( !isDurationComplete(hours, minutes, null) )
		return false;
			
	// Numeric regular expression
	var numericPattern = /^\d+$/;

	// Check hours: must fall in the range >= 1 && <= 12
	if ( hours && hours.value != '' ) {
		
		// First check specified value is numeric
		if ( !numericPattern.test(hours.value) )
			return false;
			
		// Now check range
		if ( hours.value < 1 || hours.value > 12 )
			return false;
	}
		
	// Check minutes: must fall in the range >= 0 && <= 59
	if ( !isValidMinuteOrSecond(minutes) ) 
		return false;
	
	// Check that AM/PM has been specified
	var amPm = form[objName + "AM"];
	if ( hours.value != "" && minutes.value != "" && getCheckedValue(amPm) == "" )
		return false;
		
	return true;
}

/**
 *
 */
function updateTime(form, object) {

	// First check if object is time
	var time = object.getAttribute("time");
	if ( time == null || time == "false" )
		return true;
		
	// Get hour, minute and am/pm objects
	var objName = object.name;

	var hours = form[objName + "Hours"];
	var minutes = form[objName + "Minutes"];
	
	// Do not proceed if there are no hour and minute
	// values to update
	if ( hours.value == "" && minutes.value == "" )
		return; 
		
	// Update time into 24 hours
	var amPm = form[objName + "AM"];
	var isAm = (getCheckedValue(amPm) == "am");
	
	// Convert time to 24 hours if necessary
	var hours24 = hours.value;
	if ( !isAm ) {
		
		hours24 = parseInt(hours24) + 12;
	}

	// Finally updatethe value of the object with the duration string
	object.value = hours24 + ":" + minutes.value + ":00";	
}

/**
 * 
 */
function validateSeconds(form, object) {
	
	// First check if object is seconds
	var seconds = object.getAttribute("seconds");
	if ( seconds == null || seconds == "false" )
		return true;
		
	// Now make sure it is numeric
	var numericPattern = /^\d+$/;
	if ( object.value != "" && !numericPattern.test(object.value) ) 
		return false;
		
	// Finally check it is less than 59 
	return isValidMinuteOrSecond(object);
}

/**
 * 
 */
function isValidMinuteOrSecond(object) {
	
	// Check minutes: must fall in the range >= 0 && <= 59
	if ( object && object.value != '' ) {
			
		// Now check range
		if ( object.value < 0 || object.value > 59 )
			return false;	
	}
	
	return true;
}

/**
 * 
 */
function validateInt(form, object) {

	// First check if object is time
	var isInt = object.getAttribute("int");
	if ( isInt == null || isInt == "false" )
		return true;
		
	if ( object.value == "" )
		return true;
			
	// Check only numerals have been specified
	var numericPattern = /^\d+$/;
	return numericPattern.test(object.value);
}

/**
 * 
 */
function validateFloat(form, object) {

	// First check if object is float
	var isFloat = object.getAttribute("float");
	if ( isFloat == null || isFloat == "false" )
		return true;
		
	if ( object.value == "" )
		return true;
			
	// Check only numerals have been specified
	var numericPattern = /^\d+(\.\d+)?$/;
	return numericPattern.test(object.value);
}

/**
 * 
 */
function validateFloatOne(form, object) {
	
	// First check if object is time
	var isFloat = object.getAttribute("floatOne");
	if ( isFloat == null || isFloat == "false" )
		return true;
		
	if ( object.value == "" )
		return true;
			
	// Check only numerals have been specified
	var numericPattern = /^\d{0,2}(\.\d{1})?$/;
	return numericPattern.test(object.value);
}

/**
 * 
 */
function highlightErrors(form, object, errorText) {

	// First get the error object
	var errors = document.getElementById(object.name + "Errors");
	if ( !errors )
		errors = document.getElementById(object.id + "Errors");

	// Update error object text
	if ( errors )
		errors.innerHTML = errorText;

	// Update border of object
	highlightErrorBorder(form, object, (errorText != ""));

	// Update border of any display objects
	var display = document.getElementById("Display" + object.name);
	highlightErrorBorder(form, display, (errorText != ""));
}

/**
 * 
 */
function highlightErrorBorder(form, object, markAsError) {

	if ( !object )
		return;
		
	var isDuration = isDurationOrPace(object);

	var time = object.getAttribute("time");
	var isTime = ( time != null && time != "false" );
		
	// First check if object is time or duration
	if ( isDuration || isTime )
		highlightDurationBorder(form, object, markAsError);
	// Otherwise handle all other cases
	else {
		if ( markAsError )
			object.style.border = "1px solid red";
		else
			object.style.borderColor = "";
	}
}

/**
 * 
 */
function highlightDurationBorder(form, object, markAsError) {
	
	var objName = object.name;
	
	highlightErrorBorder(form, form[objName + 'Hours'], markAsError);
	highlightErrorBorder(form, form[objName + 'Minutes'], markAsError);
	highlightErrorBorder(form, form[objName + 'Seconds'], markAsError);	
}

 /**
 * Capture keystroke and check for backspace
 * Ignore event if backspace is selected in body (ie not when input 
 * has focus)
 */
function captureKeystroke(event) {

	if( event.keyCode == 13 ) {

		var target = event.srcElement; // IE
		if(target == null) { // Standard

			target = event.target;
		}

		// Set items per page
		if ( target.id.indexOf("itemsPerPage") != -1 ) {

			var widgetName = document.getElementById("widgetName").value;
			setItemsPerPage(widgetName);
		}

		// Execute search
		if ( target.id.indexOf("query") != -1 
				|| target.id.indexOf("searchType") != -1 )  {

			search();
		}
	}
}

/**
 * 
 */
function  getElementData(node, elementName){

	if ( node.getElementsByTagName(elementName)[0].firstChild == null )
		return "";

	return node.getElementsByTagName(elementName)[0].firstChild.data;
}


/**
 * Sets the option list of the select box with the specified name to the newOptions list
 * this uses selectBox.options[i]=theNewOption as the appendChild to te optios list didnt work
 * in IE.
 */
function setOptionList(optionName, newOptions, clearSelect){

	var selectBox = document.getElementsByAttribute("name", optionName, false);

	// Get the selected value
	var selected;
	if ( !clearSelect )
		selected = getSelectedTypeURI(selectBox);

	if ( selectBox ) {

		//clear the list
		selectBox.options.length = 0;
		
		//add the new elements
		for ( var i = 0; i < newOptions.length ; i++ ){
			var option = newOptions[i];
			var value = getElementData(option, 'value');
			var theNewOption = new Option(getElementData(option, 'name'), value);
			selectBox.options[i] = theNewOption;

			// Set selected if necessary
			if ( !clearSelect && selected == value )
				selectBox.options[i].selected = true;
		}
	}
}


/**
 * 
 */
function updateDropDown(fieldName, display, uri) {

		// Get the dropdown to be updated
		var dropDown = document.getElementsByAttribute("name", fieldName, false);

		// Add new option
		if ( dropDown )
			dropDown.options[dropDown.options.length] = new Option(display, uri, false, true);
}

/**
 * 
 */
function setTimeZoneOffset() {

	// Get the current client timezone
	var date = new Date();
	var offset = date.getTimezoneOffset() * -1;
	createCookie("timeZoneOffset", offset, 365, false);
}


 /****************************************************************************
 * Called by create(uri) and showPicker(uri)
 * Returned selected type URI from type drop down
 ***************************************************************************/
function getSelectedTypeURI(obj) {

	if ( !obj )
		return;

	for ( var i = 0 ; i < obj.options.length ; i++ ) {

		if ( obj.options[i].selected ) {
			
			return obj.options[i].value;
		}
	}
	return "";
}


/**
 * 
 */
function gatherFormData(form) { 

	var formData = ""; 
	var element; 

	// For each form element, extract the name and value 
	for (var i = 0; i < form.elements.length; i++) {

		element = form.elements[i]; 

		// Hidden elements, text boxes, text areas and password fields 
		if (element.type == "hidden" || element.type == "text" 
			|| element.type == "textarea" || element.type == "password" ) { 

			formData += "'" + element.name + "', '" + element.value + "', "; 
		}
		// Drop downs 
		else if (element.tagName =='SELECT') { 
			for (var j = 0; j < element.options.length; j++) { 
			
				if (element.options[j].selected == true) { 

					formData += 
						"'" + element.name + "', '" + element.options[element.selectedIndex].value + "', "; 
				}
			}
		}
		// Check boxes 
		else if (element.type == "checkbox" && element.checked) { 

			formData += "'" + element.name + "', '" + element.value + "', "; 
		}
		// Radio buttons 
		else if (element.type == "radio" && element.checked == true) { 
		
			formData += "'" + element.name + "', '" + element.value + "', "; 
		}
	}

		// Phew! Return that data! 
		return (eval("makeSearchString(" + formData.substring(0, formData.length - 2) + ")")); 
}


/****************************************************************************
* Convert a list of strings into a 'POST' query string
***************************************************************************/
function makeSearchString() { 

	var args = makeSearchString.arguments; 
	var searchString = ""; 
	var pair; 

	for (var i = 0; i < args.length; i++) { 

		pair = escape(args[i++]) + "="; 
		pair += escape(args[i]);
		searchString += pair + "&"; 

	} 
	return searchString.substring(0, searchString.length - 1); 
}


 /****************************************************************************
 * document.getElementsByAttribute([string attributeName],[string attributeValue],[boolean isCommaSeparatedList:false])
 ***************************************************************************/
document.getElementsByAttribute = function(attrN, attrV, multi){

	attrV = 
		attrV.replace(/\|/g,'\\|').replace(/\[/g,'\\[').replace(/\(/g,'\\(').replace(/\+/g,'\\+').replace(/\./g,'\\.').replace(/\*/g,'\\*').replace(/\?/g,'\\?').replace(/\//g,'\\/');
	var
		multi=typeof multi!='undefined'?
			multi:
			false,
		cIterate=typeof document.all!='undefined'?
			document.all:
			document.getElementsByTagName('*'),
		aResponse=[],
		re=new RegExp(multi?
			'\\b'+attrV+'\\b':
			'^'+attrV+'$'),
		i=0,
		elm;
	while((elm=cIterate.item(i++))){
		if(re.test(elm.getAttribute(attrN)||''))
			aResponse[aResponse.length]=elm;
	}
	return aResponse[0];
}

/**
 * 
 */
function isFirstRequestParameter(url) {

	return (url.indexOf("?") == -1 );
}

/**
 * 
 */
function closeWindow() {

	window.close();
}

/**
 * 
 */
function Popup(id, width, className, titleText, defaultText) {

	// Set globals
	this.dom = new DOMManipulator();
	this.id = id;

	// Create the popup div
	var layer = this.dom.appendDiv(document.body, "", id, className);
	this.layer = layer;
	this.layer.style.width = width;

	// Create the title bar of popup, this will be draggable 
	var bar = this.dom.appendDiv(this.layer, "", "", className + "TitleBar");
	bar.style.width = width;
	bar.onmousedown = function(evt){
		getObjectInDrag(layer, evt);
	}

	var leftTitleDiv = this.dom.appendDiv(bar, "", "", className + "TitleBarLeft");
	leftTitleDiv.innerHTML = titleText;

	// Initialise event listeners
	initDragObject();

	// Get popup variable for anon functions
	var popup = this;

	// Create close link
	var rightTitleDiv = this.dom.appendDiv(bar, "", "", className + "TitleBarRight");
	rightTitleDiv.onmousedown = function() {
		popup.close();
		return false;
	}
	var closeLink = this.dom.appendA(rightTitleDiv, "", "", "", "x", "#");
	closeLink.onclick = function () {
		popup.close();
		return false;
	}

	// Add default text 
	if ( !defaultText || defaultText == "" )
		return;

	var text = this.dom.appendP(this.layer, "", "", "");
	text.innerHTML = defaultText;
}


/**
 *
 */
Popup.prototype.appendChild = function(child) {

	if ( child )
		this.layer.appendChild(child);
}


/**
 *
 */
Popup.prototype.close = function() {

	this.layer.style.display = "none";
	document.body.removeChild(this.layer); // does this remove all of its children too?
	popup = null;
};


/**
 *
 */
Popup.prototype.removeChild = function(child) {

	if ( child )
		this.layer.removeChild(child);
}

/**
 *
 */
Popup.prototype.toggleChildDisplay = function(child) {

	if ( child.style.display == "" )
		child.style.display = "none";
	else
		child.style.display = "";
}

/**
 *
 */
Popup.prototype.innerHTML = function(html) {

	if ( !this.layer )
		return;

	var htmlDiv = this.dom.appendDiv(this.layer, "", "", "");
	htmlDiv.innerHTML = html;
}

var dragObject = null;
var mouseOffset = null;


/**
 *
 */
function initDragObject() {

	// Netscape 4 event model 
	if ( document.layers ) {

		document.captureEvents(Event.MOUSEDOWN | Event.MOUSEMOVE | Event.MOUSEUP);
		return;
	}
	// W3C DOM event model
	else if ( document.body && document.body.addEventListener ) {

		document.addEventListener("mousemove", dragTheObject, false);
		document.addEventListener("mouseup", releaseTheObject, false);

		return;
	}

	document.onmousemove = dragTheObject;
	document.onmouseup = releaseTheObject;

	return;
};

/**
 *
 */
function getObjectInDrag(objToDrag, evt) {

	evt = (evt) ? evt : event;

	dragObject = objToDrag;
	mouseOffset = getMouseOffset(objToDrag, evt);

	// Engage event capture in IE
	if ( document.body && document.body.setCapture )
		document.body.setCapture();

	// Prevent event bubble
	if ( evt.stopPropogation )
		evt.stopPropogation();
	if ( evt.preventDefault )
		evt.preventDefault();
}


/**
 *
 */
function dragTheObject(evt) {

	if( !dragObject )
		return;

	evt = (evt) ? evt : event;
	var mousePos = getMouseCoords(evt);

	var units = (typeof dragObject.style.top == "string" ) ? "px" : 0;
	dragObject.style.position = "absolute";
	dragObject.style.top = (mousePos.y - mouseOffset.y) + units;
	dragObject.style.left = (mousePos.x - mouseOffset.x) + units;
	return false;
}


/**
 *
 */
function getMouseCoords(evt) {

	if( evt.pageX || evt.pageY ) {
		var yCoord = evt.pageY;
		if ( yCoord < 0 )
			yCoord = 0;
		return {x:evt.pageX, y:yCoord};
	}

	var yCoord = evt.clientY + document.body.scrollTop  - document.body.clientTop;
	if ( yCoord < 0 )
		yCoord = 0;

	return {
		x:evt.clientX + document.body.scrollLeft - document.body.clientLeft,
		y:yCoord
	};
}

/**
 *
 */
function getMouseOffset(target, evt) {

	var docPos = getPosition(target);
	var mousePos = getMouseCoords(evt);
	return {x:mousePos.x - docPos.x, y:mousePos.y - docPos.y};
}

/**
 *
 */
function getPosition(target) {

	var left = 0;
	var top  = 0;

	while ( target.offsetParent ) {
		left += target.offsetLeft;
		top  += target.offsetTop;
		target = target.offsetParent;
	}

	left += target.offsetLeft;
	top  += target.offsetTop;

	return {x:left, y:top};
}

/**
 *
 */
function releaseTheObject(evt){

	dragObject = null;

	// Stop event capture in IE
	if ( document.body && document.body.releaseCapture ) 
		document.body.releaseCapture();
}

/**
 * 
 */
function DOMManipulator() {

}

/**
 *
 */
DOMManipulator.prototype.appendA = function(parentObj, name, id, className, innerHTML, href) {

	// Create link
	var a = document.createElement("A");

	// Update inner HTML
	a.innerHTML = innerHTML;

	// Update core attributes
	this.initObject(a, name, id, className);

	// Update href
	a.href = href;

	// Append to parent
	parentObj.appendChild(a);

	return a;
};

/**
 *
 */
DOMManipulator.prototype.appendBR = function(parentObj) {

	// Create br and append to parent
	var br = document.createElement("BR");
	parentObj.appendChild(br);
	return br;
};


/**
 *
 */
DOMManipulator.prototype.appendForm = function(parentObj, name, id, className, method, action, enctype) {

	// Create form
	var form = document.createElement("FORM");

	// Update core attributes
	this.initObject(form, name, id, className);

	if ( method )
		form.method = method;

	// Append to parent
	parentObj.appendChild(form);

	// Return form
	return form;
};


/**
 *
 */
DOMManipulator.prototype.appendInput = function(parentObj, type, name, id, className, value, validationRules) {

	// Create input
	var input = document.createElement("INPUT");
	input.type = type;

	// Update core attributes
	this.initObject(input, name, id, className);

	// Update value
	if ( value )
		input.value = value;

	// Append to parent
	parentObj.appendChild(input);

	// Update validation rules
	if ( validationRules && validationRules.length > 0 ) {

		// First create errors span element
		this.appendSpan(parentObj, "", name + "Errors", "errors", "");

		// Now check for the various types of validation
		for ( var i = 0 ; i < validationRules.length ; i++ ) {

			var rule = validationRules[i];
			if ( rule == "required" )
				input.setAttribute("required", "true");
			else if ( rule == "email" ) 
				input.setAttribute("email", "true");
		}
	}

	// Return input
	return input;
};


/**
 *
 */
DOMManipulator.prototype.appendSpan = function(parentObj, name, id, className, innerHTML) {

	// Create span
	var span = document.createElement("SPAN");

	// Update core attributes
	this.initObject(span, name, id, className);

	if ( innerHTML )
		span.innerHTML = innerHTML;

	// Append to parent
	parentObj.appendChild(span);

	// Return span
	return span;
};


/**
 *
 */
DOMManipulator.prototype.appendTable = function(parentObj, name, id, className) {

	// Create table
	var table = document.createElement("TABLE");

	// Update core attributes
	this.initObject(table, name, id, className);

	// Append to parent
	parentObj.appendChild(table);

	// Return table
	return table;
};

/**
 *
 */
DOMManipulator.prototype.appendTBody = function(parentObj, name, id, className) {

	// Create td
	var tbody = document.createElement("TBODY");

	// Update core attributes
	this.initObject(tbody, name, id, className);

	// Append to parent
	parentObj.appendChild(tbody);

	// Return td
	return tbody;
};

/**
 *
 */
DOMManipulator.prototype.appendTD = function(parentObj, name, id, className) {

	// Create td
	var td = document.createElement("TD");

	// Update core attributes
	this.initObject(td, name, id, className);

	// Append to parent
	parentObj.appendChild(td);

	// Return td
	return td;
};

/**
 *
 */
DOMManipulator.prototype.appendTR = function(parentObj, name, id, className) {

	// Create tr and
	var tr = document.createElement("TR");

	// Update core attributes
	this.initObject(tr, name, id, className);

	// Append to parent
	parentObj.appendChild(tr);

	// Return tr
	return tr;
};

/**
 *
 */
DOMManipulator.prototype.appendDiv = function(parentObj, name, id, className) {

	// Create div and append to parent
	var div = document.createElement("DIV");

	// Update core attributes
	this.initObject(div, name, id, className);

	// Append to parent
	parentObj.appendChild(div);

	// Return div
	return div;
};


/**
 *
 */
DOMManipulator.prototype.appendTextArea = function(parentObj, name, id, className, innerHTML) {

	// Create text
	var textArea = document.createElement("TEXTAREA");

	// Update core attributes
	this.initObject(textArea, name, id, className);

	if ( innerHTML )
		textArea.innerHTML = innerHTML;

	// Append to parent
	parentObj.appendChild(textArea);

	// Return text area
	return textArea;
};

/**
 *
 */
DOMManipulator.prototype.appendP = function(parentObj, name, id, className) {

	// Create paragraph
	var p = document.createElement("P");

	// Update core attributes
	this.initObject(p, name, id, className);

	// Append to parent
	parentObj.appendChild(p);

	// Return paragraph
	return p;
};


/**
 *
 */
DOMManipulator.prototype.initObject = function(obj, name, id, className) {

	// Set common attributes
	if ( name )
		obj.name = name;

	if ( id ) 
		obj.id = id;

	if ( className )
		obj.className = className;
};

/**
 *
 */
DOMManipulator.prototype.removeChild = function(parent, child) {

	parent.removeChild(child);
};

/**
 * Copyright 2007 Tim Down.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
/**
 * simpledateformat.js
 *
 * A faithful JavaScript implementation of Java's SimpleDateFormat's format
 * method. All pattern layouts present in the Java implementation are
 * implemented here except for z, the text version of the date's time zone.
 *
 * Thanks to Ash Searle (http://hexmen.com/blog/) for his fix to my
 * misinterpretation of pattern letters h and k.
 * 
 * See the official Sun documentation for the Java version:
 * http://java.sun.com/j2se/1.5.0/docs/api/java/text/SimpleDateFormat.html
 *
 * Author: Tim Down <tim@timdown.co.uk>
 * Last modified: 6/2/2007
 * Website: http://www.timdown.co.uk/code/simpledateformat.php
 */
 
/* ------------------------------------------------------------------------- */

var SimpleDateFormat;

(function() {
	function isUndefined(obj) {
		return typeof obj == "undefined";
	}

	var regex = /('[^']*')|(G+|y+|M+|w+|W+|D+|d+|F+|E+|a+|H+|k+|K+|h+|m+|s+|S+|Z+)|([a-zA-Z]+)|([^a-zA-Z']+)/;
	var monthNames = ["January", "February", "March", "April", "May", "June",
		"July", "August", "September", "October", "November", "December"];
	var dayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
	var TEXT2 = 0, TEXT3 = 1, NUMBER = 2, YEAR = 3, MONTH = 4, TIMEZONE = 5;
	var types = {
		G : TEXT2,
		y : YEAR,
		M : MONTH,
		w : NUMBER,
		W : NUMBER,
		D : NUMBER,
		d : NUMBER,
		F : NUMBER,
		E : TEXT3,
		a : TEXT2,
		H : NUMBER,
		k : NUMBER,
		K : NUMBER,
		h : NUMBER,
		m : NUMBER,
		s : NUMBER,
		S : NUMBER,
		Z : TIMEZONE
	};
	var ONE_DAY = 24 * 60 * 60 * 1000;
	var ONE_WEEK = 7 * ONE_DAY;
	var DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK = 1;

	var newDateAtMidnight = function(year, month, day) {
		var d = new Date(year, month, day, 0, 0, 0);
		d.setMilliseconds(0);
		return d;
	}

	Date.prototype.getDifference = function(date) {
		return this.getTime() - date.getTime();
	};

	Date.prototype.isBefore = function(d) {
		return this.getTime() < d.getTime();
	};

	Date.prototype.getUTCTime = function() {
		return Date.UTC(this.getFullYear(), this.getMonth(), this.getDate(), this.getHours(), this.getMinutes(),
				this.getSeconds(), this.getMilliseconds());
	};

	Date.prototype.getTimeSince = function(d) {
		return this.getUTCTime() - d.getUTCTime();
	};

	Date.prototype.getPreviousSunday = function() {
		// Using midday avoids any possibility of DST messing things up
		var midday = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 12, 0, 0);
		var previousSunday = new Date(midday.getTime() - this.getDay() * ONE_DAY);
		return newDateAtMidnight(previousSunday.getFullYear(), previousSunday.getMonth(),
				previousSunday.getDate());
	}

	Date.prototype.getWeekInYear = function(minimalDaysInFirstWeek) {
		if (isUndefined(this.minimalDaysInFirstWeek)) {
			minimalDaysInFirstWeek = DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK;
		}
		var previousSunday = this.getPreviousSunday();
		var startOfYear = newDateAtMidnight(this.getFullYear(), 0, 1);
		var numberOfSundays = previousSunday.isBefore(startOfYear) ?
			0 : 1 + Math.floor(previousSunday.getTimeSince(startOfYear) / ONE_WEEK);
		var numberOfDaysInFirstWeek =  7 - startOfYear.getDay();
		var weekInYear = numberOfSundays;
		if (numberOfDaysInFirstWeek < minimalDaysInFirstWeek) {
			weekInYear--;
		}
		return weekInYear;
	};

	Date.prototype.getWeekInMonth = function(minimalDaysInFirstWeek) {
		if (isUndefined(this.minimalDaysInFirstWeek)) {
			minimalDaysInFirstWeek = DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK;
		}
		var previousSunday = this.getPreviousSunday();
		var startOfMonth = newDateAtMidnight(this.getFullYear(), this.getMonth(), 1);
		var numberOfSundays = previousSunday.isBefore(startOfMonth) ?
			0 : 1 + Math.floor((previousSunday.getTimeSince(startOfMonth)) / ONE_WEEK);
		var numberOfDaysInFirstWeek =  7 - startOfMonth.getDay();
		var weekInMonth = numberOfSundays;
		if (numberOfDaysInFirstWeek >= minimalDaysInFirstWeek) {
			weekInMonth++;
		}
		return weekInMonth;
	};

	Date.prototype.getDayInYear = function() {
		var startOfYear = newDateAtMidnight(this.getFullYear(), 0, 1);
		return 1 + Math.floor(this.getTimeSince(startOfYear) / ONE_DAY);
	};

	/* ----------------------------------------------------------------- */

	SimpleDateFormat = function(formatString) {
		this.formatString = formatString;
	};

	/**
	 * Sets the minimum number of days in a week in order for that week to
	 * be considered as belonging to a particular month or year
	 */
	SimpleDateFormat.prototype.setMinimalDaysInFirstWeek = function(days) {
		this.minimalDaysInFirstWeek = days;
	};

	SimpleDateFormat.prototype.getMinimalDaysInFirstWeek = function(days) {
		return isUndefined(this.minimalDaysInFirstWeek)	?
			DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK : this.minimalDaysInFirstWeek;
	};

	SimpleDateFormat.prototype.format = function(date) {
		var formattedString = "";
		var result;

		var padWithZeroes = function(str, len) {
			while (str.length < len) {
				str = "0" + str;
			}
			return str;
		};

		var formatText = function(data, numberOfLetters, minLength) {
			return (numberOfLetters >= 4) ? data : data.substr(0, Math.max(minLength, numberOfLetters));
		};

		var formatNumber = function(data, numberOfLetters) {
			var dataString = "" + data;
			// Pad with 0s as necessary
			return padWithZeroes(dataString, numberOfLetters);
		};

		var searchString = this.formatString;
		while ((result = regex.exec(searchString))) {
			var matchedString = result[0];
			var quotedString = result[1];
			var patternLetters = result[2];
			var otherLetters = result[3];
			var otherCharacters = result[4];

			// If the pattern matched is quoted string, output the text between the quotes
			if (quotedString) {
				if (quotedString == "''") {
					formattedString += "'";
				} else {
					formattedString += quotedString.substring(1, quotedString.length - 1);
				}
			} else if (otherLetters) {
				// Swallow non-pattern letters by doing nothing here
			} else if (otherCharacters) {
				// Simply output other characters
				formattedString += otherCharacters;
			} else if (patternLetters) {
				// Replace pattern letters
				var patternLetter = patternLetters.charAt(0);
				var numberOfLetters = patternLetters.length;
				var rawData = "";
				switch (patternLetter) {
					case "G":
						rawData = "AD";
						break;
					case "y":
						rawData = date.getFullYear();
						break;
					case "M":
						rawData = date.getMonth();
						break;
					case "w":
						rawData = date.getWeekInYear(this.getMinimalDaysInFirstWeek());
						break;
					case "W":
						rawData = date.getWeekInMonth(this.getMinimalDaysInFirstWeek());
						break;
					case "D":
						rawData = date.getDayInYear();
						break;
					case "d":
						rawData = date.getDate();
						break;
					case "F":
						rawData = 1 + Math.floor((date.getDate() - 1) / 7);
						break;
					case "E":
						rawData = dayNames[date.getDay()];
						break;
					case "a":
						rawData = (date.getHours() >= 12) ? "PM" : "AM";
						break;
					case "H":
						rawData = date.getHours();
						break;
					case "k":
						rawData = date.getHours() || 24;
						break;
					case "K":
						rawData = date.getHours() % 12;
						break;
					case "h":
						rawData = (date.getHours() % 12) || 12;
						break;
					case "m":
						rawData = date.getMinutes();
						break;
					case "s":
						rawData = date.getSeconds();
						break;
					case "S":
						rawData = date.getMilliseconds();
						break;
					case "Z":
						rawData = date.getTimezoneOffset(); // This is returns the number of minutes since GMT was this time.
						break;
				}
				// Format the raw data depending on the type
				switch (types[patternLetter]) {
					case TEXT2:
						formattedString += formatText(rawData, numberOfLetters, 2);
						break;
					case TEXT3:
						formattedString += formatText(rawData, numberOfLetters, 3);
						break;
					case NUMBER:
						formattedString += formatNumber(rawData, numberOfLetters);
						break;
					case YEAR:
						if (numberOfLetters <= 3) {
							// Output a 2-digit year
							var dataString = "" + rawData;
							formattedString += dataString.substr(2, 2);
						} else {
							formattedString += formatNumber(rawData, numberOfLetters);
						}
						break;
					case MONTH:
						if (numberOfLetters >= 3) {
							formattedString += formatText(monthNames[rawData], numberOfLetters, numberOfLetters);
						} else {
							// NB. Months returned by getMonth are zero-based
							formattedString += formatNumber(rawData + 1, numberOfLetters);
						}
						break;
					case TIMEZONE:
						var isPositive = (rawData > 0);
						// The following line looks like a mistake but isn't
						// because of the way getTimezoneOffset measures.
						var prefix = isPositive ? "-" : "+";
						var absData = Math.abs(rawData);

						// Hours
						var hours = "" + Math.floor(absData / 60);
						hours = padWithZeroes(hours, 2);
						// Minutes
						var minutes = "" + (absData % 60);
						minutes = padWithZeroes(minutes, 2);

						formattedString += prefix + hours + minutes;
						break;
				}
			}
			searchString = searchString.substr(result.index + result[0].length);
		}
		return formattedString;
	};
})();