//*---------------------------------------------------------------------*//
//*---------------------------------------------------------------------*//
//	dateManip.js														*//
//	Written By		: Ethernet Cowboy									*//
//					: Octoberland										*//
//	Written Using	: PowerMac G3, BBEdit 6.0							*//
//	Copyright		: 2000-2001											*//
//																		*//
//	Version			: 2.3												*//
//																		*//
//	Various date manipulation and formatting functions.  Inspired 		*//
//	by the PERL module dateManip.pm and ColdFusion date functions.		*//
//*---------------------------------------------------------------------*//
//*---------------------------------------------------------------------*//

//*---------------------------------------------------------------------*//
//	Declare Arrays														*//
var daysArray			= new Array("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday");
var monthsArray			= new Array("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December");
var daysInMonthArray	= new Array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);


//*---------------------------------------------------------------------*//
//	Other useful variables and objects
var today			= new Date();

//*---------------------------------------------------------------------*//
//	Add Prototypes to the Date Object for holidays
Date.prototype.isCalendarHoliday	= "false";
Date.prototype.isBusinessHoliday	= "false";
Date.prototype.holidayName			= "N/A";

//*---------------------------------------------------------------------*//
//	Function to verify and adjust all years to four-digit format
function adjustYear(myYear) {

	if ((myYear + "").length == 4) {
		return myYear;
	} else if (myYear <= 30) {
		return (myYear + 2000);
	} else if (myYear > 30) {
		return (myYear + 1900);
	} else {
		return -1;
	}
}


//*---------------------------------------------------------------------*//
//	Function to determine if a given year is a leap year or not.  Returns
//	true for leap years (evenly divisible by 4).  False otherwise.
function isLeapYear(myYear) {

	vYear	= adjustYear(myYear);
	
	if ((vYear % 4) == 0) {
		return true;
	} else {
		return false;
	}
}

//*---------------------------------------------------------------------*//
//	Function to determine if a set of arguments will create a real date.
//	Check to ensure each argument is numeric, whole number, positive, 
//	and not out of range.
function isDate(yr, mo, dy) {
	var yearLow		= 1900;
	var yearHigh	= 2500;
	var maxDays		= 0;
	
//	Check to ensure we have positive integers.
	if (!isPositiveInt(yr) || !isPositiveInt(mo) || !isPositiveInt(dy)) {
		return false;
	}
	
//	Now check out-of-range numbers.
	if ((yr < yearLow) || (yr > yearHigh)) {
		return false;
	}
	if ((mo < 0) || (mo > 11)) {
		return false;
	}
//	Leap year adjustment for February
	if ((isLeapYear(yr)) && (mo == 1)) {
		maxDays = daysInMonthArray[mo] + 1;
	} else {
		maxDays	= daysInMonthArray[mo];
	}
	if ((dy < 1) || (dy > maxDays)) {
		return false;
	}
	
//	If we made it this far, we've got the potential for creating a real date.
	return true;
}


//*---------------------------------------------------------------------*//
//	Function to determine if a set of arguments will create a vaild time.
function isTime(hr, min, sec) {
	
//	check to ensure we have positive integers.
	if (!isPositiveInt(hr) || !isPositiveInt(min) || !isPositiveInt(sec)) {
		return false;
	}
	
//	check for out-of-range arguments.
	if ((hr > 23) || (min > 59) || (sec > 59)) {
		return false;
	}
	
	return true;
}


//*---------------------------------------------------------------------*//
function dateFormat(dateObj, formatArg) {
	if (formatArg != "") {
	
//		First we grab the various parts of the Date to use during replacements.
//		Acceptable formats are :
//			m	-	Month as a number.  Single digits are left as is.
//			mm	-	Month as a number.  Displayed as 2 digits (leading 0 as needed)
//			M	-	Month as a string.  Three-character abbreviation is used.
//			MM	-	Month as a string.  Full name of the month.
//			d	-	Day as a number.  Single digits are left as is.
//			dd	-	Day as a number.  Displayed as 2 digits (leading 0 as needed)
//			D	-	Day as a string.  Three-character abbreviation is used.
//			DD	-	Day as a string.  Full name of the day.
//			yyyy-	Year in four digits.
//			yy	-	Year as last two digits.
//			h24	-	Hour as a number (24-hour).  Single digits are left as is.
//			hh24-	Hour as a number (24-hour).  Displayed as 2 digits (leading 0 as needed)
//			h	-	Hour as a number.  Single digits are left as is.
//			hh	-	Hour as a number.  Displayed as 2 digits (leading 0 as needed)
//			mi	-	Minute as a number.  Always 2 digits.
//			ap	-	Show AM or PM.

//		Month elements		
		var m_v		= dateObj.getMonth() + 1;
		var mm_v	= makeTwoDigit(m_v);
		var M_v		= getShortMonthString(dateObj);
		var MM_v	= getMonthString(dateObj);
		
//		Day elements		
		var d_v		= dateObj.getDate();
		var dd_v	= makeTwoDigit(d_v);
		var D_v		= getShortDayString(dateObj);
		var DD_v	= getDayString(dateObj);
		
//		Year elements		
		var yyyy_v	= dateObj.getFullYear();
		var yy_v	= (yyyy_v + "").substring(2,4);
		
//		Hour (24-hour clock) elements		
		var h24_v	= dateObj.getHours();
		var hh24_v	= makeTwoDigit(h24_v);
		
//		Hour (12-hour clock) elements
		var h_v;
		if (h24_v > 12) {
			h_v		= h24_v - 12;
		} else {
			if (h24_v == 0) {
				h_v		= 12;
			} else {
				h_v		= h24_v;
			}
		}
		var hh_v	= makeTwoDigit(h_v);
		
//		Minute element
		var mi_v	= makeTwoDigit(dateObj.getMinutes());
		
//		AM/PM element
		var ap_v;
		if (h24_v > 12) {
			ap_v	= "PM";
		} else {
			ap_v	= "AM";
		}

		var	formattedDate;
		
//		Month replacements
		formattedDate	= formatArg.replace(/\bmm\b/,mm_v);
		formattedDate	= formattedDate.replace(/\bm\b/, m_v);
		formattedDate	= formattedDate.replace(/\bM\b/, M_v);
		formattedDate	= formattedDate.replace(/\bMM\b/, MM_v);
		
//		Day replacements		
		formattedDate	= formattedDate.replace(/\bdd\b/, dd_v);
		formattedDate	= formattedDate.replace(/\bd\b/, d_v);
		formattedDate	= formattedDate.replace(/\bDD\b/, DD_v);
		formattedDate	= formattedDate.replace(/\bD\b/, D_v);
		
//		Year replacements		
		formattedDate	= formattedDate.replace(/\byyyy\b/, yyyy_v);
		formattedDate	= formattedDate.replace(/\byy\b/, yy_v);
		
//		Hour replacements		
		formattedDate	= formattedDate.replace(/\bhh24\b/, hh24_v);
		formattedDate	= formattedDate.replace(/\bh24\b/, h24_v);
		formattedDate	= formattedDate.replace(/\bhh\b/, hh_v);
		formattedDate	= formattedDate.replace(/\bh\b/, h_v);

//		Minute replacement		
		formattedDate	= formattedDate.replace(/\bmi\b/, mi_v);
		
//		AM/PM Replacement
		formattedDate	= formattedDate.replace(/\bap\b/, ap_v);

		return formattedDate;
	} else {
		return "ERROR : Unsupported Date Format";
	}
}

//*---------------------------------------------------------------------*//
//	Function to return the day of the week as a string, given a Date object
function getDayString(dateObj) {
	return dayAsString(dateObj.getDay());
}


//*---------------------------------------------------------------------*//
//	Function to return the month of the year as a string, given a Date object
function getMonthString(dateObj) {
	return monthAsString(dateObj.getMonth());
}


//*---------------------------------------------------------------------*//
//	Function to return the abbreviated day of the week as a string, given a Date object
function getShortDayString(dateObj) {
	return dayAsString(dateObj.getDay()).substring(0,3);
}


//*---------------------------------------------------------------------*//
//	Function to return the abbreviated month of the year as a string, given a Date object
function getShortMonthString(dateObj) {
	return monthAsString(dateObj.getMonth()).substring(0,3);
}


//*---------------------------------------------------------------------*//
//	Given an integer between 0 and 6, return the associated day as a string
function dayAsString(iDay) {
	
	if ((iDay > -1) && (iDay < 7)) {
		return daysArray[iDay];
	} else {
		return "ERROR : Day out of range";
	}
}


//*---------------------------------------------------------------------*//
//	Given the day as a string, return an integer value
function dayAsInteger(sDay)	{

	var	baseDay	= sDay.toLowerCase().substring(0,3);
	var	j		= -1
	
	for (var i=0;i<7;i++) {
		if (daysArray[i].toLowerCase().substring(0,3) == baseDay) {
			j	= i;
			break;
		}
	}
	
	return j;
}


//*---------------------------------------------------------------------*//
//	Function to calculate the number of days in a given month, which is 
//	passed as part of a date object.  Returns integer number of days.
function daysInMonth(dateObj) {

	var mo	= dateObj.getMonth();					// Integer month
	var	yr	= dateObj.getFullYear();				// Want 4-digit year
	
	var numDays	= daysInMonthArray[mo];

	if ((mo == 1) && (isLeapYear(yr))) {			// February Leap Year Adjustment
		numDays++;
	}
	
	return numDays;
}


//*---------------------------------------------------------------------*//
//	Function which returns the date of the first day of the month, as a Date object.
function firstDateOfMonth(dateObj) {

	if (typeof dateObj == "object") {

		var tmpYr	= dateObj.getFullYear();
		var tmpMon	= dateObj.getMonth();
		var	tmpDay	= dateObj.getDate();
		var tmpDate	= new Date(tmpYr, tmpMon, 1);
				
		return tmpDate;
	} else {
		return -1;
	}
}


//*---------------------------------------------------------------------*//
//	Function which returns the first day of the month, as an integer.
//	This can be converted to a string using the dayAsString function.
function firstDayOfMonth(dateObj) {

	if (typeof dateObj == "object") {

		
		var firstDay	= firstDateOfMonth(dateObj).getDay();
		
		return firstDay;
	} else {
		return -1;
	}
}


//*---------------------------------------------------------------------*//
//	Function which returns the last day of the month, as an integer.
//	This can be converted to a string using the dayAsString function.
function lastDayOfMonth(dateObj) {
	if (typeof dateObj == "object") {

		var lastDay	= lastDateOfMonth(dateObj).getDay();
		
		return lastDay;
	} else {
		return -1;
	}
	
}


//*---------------------------------------------------------------------*//
//	Function which returns the date last day of the month, as a Date object.
function lastDateOfMonth(dateObj) {
	if (typeof dateObj == "object") {

		var tmpYr	= dateObj.getFullYear();
		var tmpMon	= dateObj.getMonth();
		var	tmpDay	= daysInMonth(dateObj);
		var tmpDate	= new Date(tmpYr, tmpMon, tmpDay);
		
		return tmpDate;
	} else {
		return -1;
	}
	
}


//*---------------------------------------------------------------------*//
//	Function which returns the first day of the year, as an integer.
//	This can be converted to a string using the dayAsString function.
function firstDayOfYear(dateObj) {

	if (typeof dateObj == "object") {

		var tmpYr	= dateObj.getFullYear();
		var tmpDate	= new Date(tmpYr, 0, 1);
		
		var firstDay	= tmpDate.getDay();
		
		return firstDay;
	} else {
		return -1;
	}
}


//*---------------------------------------------------------------------*//
//	Function which returns the last day of the year, as an integer.
//	This can be converted to a string using the dayAsString function.
function lastDayOfYear(dateObj) {

	if (typeof dateObj == "object") {

		var tmpYr	= dateObj.getFullYear();
		var tmpDate	= new Date(tmpYr, 11, 31);
				
		var lastDay	= tmpDate.getDay();
		
		return lastDay;
	} else {
		return -1;
	}
}


//*---------------------------------------------------------------------*//
//	Given the month as a string, return an integer value
function monthAsInteger(sMonth) {
	var baseMonth	= sMonth.toLowerCase().substring(0,3);
	var j			= -1;
	
	for (var i=0;i<12;i++) {
		if (monthsArray[i].toLowerCase().substring(0,3) == baseMonth) {
			j	= i;
			break;
		}
	}
	
	return j;
}


//*---------------------------------------------------------------------*//
//	Given an integer between 0 and 11, return the associated month as a string
function monthAsString(iMonth) {

	if ((iMonth > -1) && (iMonth < 12)) {
		return monthsArray[iMonth];
	} else {
		return "ERROR : Month out of range";
	}
}


//*---------------------------------------------------------------------*//
//	Function to add/subtract months from a given date object
function monthAdd(dateObj, numMonths) {
	var adjYrs		= dateObj.getYear();
	var	adjMos		= dateObj.getMonth();
	var tempDate	= dateObj;
	
	var mos, yrs;
	
	mos			= (adjMos + numMonths) % 12;
	if (adjMos + numMonths < 0) {										// Need to round the proper direction
		yrs			= Math.ceil((adjMos + numMonths) / 12);
	} else {
		yrs			= Math.floor((adjMos + numMonths) / 12);
	}
		
	date1		= new Date(tempDate.setMonth(mos));
	date2		= yearAdd(date1, yrs);
	
	return date2;
}

//*---------------------------------------------------------------------*//
//	Function to add/subtract months from a given date object
function monthAddSimple(dateObj, numMonths) {
	var origYrs		= dateObj.getFullYear();
	var	origMos		= dateObj.getMonth();
		
	var addMos, addYrs;
	
	rValue			= new Array();
	
	addMos			= numMonths % 12;
	if (numMonths < 0) {										// Need to round the proper direction
		addYrs		= Math.ceil(numMonths / 12);
	} else {
		addYrs		= Math.floor(numMonths / 12);
	}

	var mos			= origMos + addMos;
	var yrs			= origYrs + addYrs;
	
	if (mos < 0) {
		rValue[0]		= mos + 12;
		rValue[1]		= yrs - 1;
	} else if (mos > 11) {
		rValue[0]		= mos - 12;
		rValue[1]		= yrs + 1;		
	} else {
		rValue[0]		= mos;
		rValue[1]		= yrs;
	}

	return rValue;
}


//*---------------------------------------------------------------------*//
//	Function to add years to a given date
function yearAdd(dObj, numYears) {
	var adjYrs		= dObj.getYear();
	var tmpDate		= dObj;
	
	var	totalYears	= adjustYear(adjYrs + numYears);

	var yDate		= new Date(tmpDate.setYear(totalYears));
	
	return yDate;
}


//*---------------------------------------------------------------------*//
//	Function to add business days to a given date.
//	First we check to see if we're lucky enough for the date to fall w/in
//	the same week we're in.  If its greater, then we need to see how many
//	weekends we span.
function businessAdd(dateObj, numDays) {

	var finalDate;
	var dayInt			= dateObj.getDay();
	var weekendsSpanned	= 0;
	var weekendDays		= 0;
	var remainingDays	= 0;
	
//		We have to figure out how many weekend days to add to this total.
//		First check to see how many weekends this number would span.
//		Remembering that there might be one extra weekend in here, depending
//		on what day of the week we're counting from.  Of course, is always possible
//		that we don't span any weekends.  This is taken care of as well.
		weekendsSpanned	= Math.floor(numDays / 5);
		remainingDays	= (numDays % 5);
		
//		Check to see if there's one additional weekend to span
		if (dayInt + remainingDays > 5) {
			weekendsSpanned++;
		}
		
//		Total number of weekend days
		weekendDays	= weekendsSpanned * 2;
	
		finalDate		= dateAdd(dateObj, (numDays + weekendDays), "d");

	return finalDate;
}


//*---------------------------------------------------------------------*//
//	Function to add/subtract a given amount of time from a Date object
//	Valid arguments for dateUnits are :
//	s - Seconds
//	m - Minutes
//	h - Hours
//	d - Days
//	w - Weeks
//	M - months
//	y - years
//	b - business days
function dateAdd(originalDate, dateAmount, dateUnits) {
	var adjDate;
	var baseDate	= cloneDate(originalDate);

	if (dateUnits == "M") {								// Months - special case
		adjDate	= monthAdd(baseDate, dateAmount);
		
	} else if (dateUnits == "y") {						// Years - special case
		adjDate	= yearAdd(baseDate, dateAmount);
		
	} else if (dateUnits == "b") {						// Business days - special case
		adjDate	= businessAdd(baseDate, dateAmount);
		
	} else {											// Other standard additions

		var	baseDateM	= baseDate.getTime();
		var adj			= getMilliseconds(dateUnits);
		
		if (adj != -1) {
			var adjDateM	= baseDateM + (dateAmount * adj);
			adjDate		= new Date(adjDateM);			
		} else {
			adjDate	=  "ERROR : Function dateAdd, date units unknown";
		}
	}
	return adjDate;

}


//*---------------------------------------------------------------------*//
//	Calculate the difference between date1 and date2.  Return difference 
//	in desired units.  Default is milliseconds.
//	Valid arguments for dateUnits are :
//	s - Seconds
//	m - Minutes
//	h - Hours
//	d - Days
//	w - Weeks
function dateDiff(date1, date2, dateUnits) {
	var date1M	= date1.getTime();
	var date2M	= date2.getTime();
	
	var diff	= date1M - date2M;
	
	var	adj		= getMilliseconds(dateUnits);
	
	if (adj !=	-1) {
		return (diff / adj);
	} else {
		return diff;
	}
}


//*---------------------------------------------------------------------*//
//	Function to determine if a given year, month, day is "today".  Returns
//	true or false.  Requires integer year, month, and day arguments.
function isToday(yr, mo, dy) {
	var todayYear	= today.getFullYear();
	var todayMonth	= today.getMonth();
	var	todayDay	= today.getDate();
	
	if ((yr == todayYear) && (mo == todayMonth) && (dy == todayDay)) {
		return true;
	} else {
		return false;
	}

}


//*---------------------------------------------------------------------*//
//	Function to determine if a given date is a business day.
//	Simple definition of a business day is any day that isn't Saturday
//	or Sunday.  Holidays are not taken into account.
function isBusinessDay(dateObj) {
	var testDay	= dateObj.getDay();
	
	if ((testDay == 0) || (testDay == 6)) {
		return false;
	} else {
		return true;
	}
}


//*---------------------------------------------------------------------*//
//	Function used to return "now" as a date object.
//	This is for simplicity and allowing for ColdFusion-like syntax.
function now() {
	return new Date();
}


//*---------------------------------------------------------------------*//
//	Function to determine if a given date object is a holiday.
//	Standard US "calendar" holidays are used.  Other definitions can
//	be added.  There are two types of holidays:
//	Defined Holiday - Falls on same date every year (Haloween, Christmas)
//	Calculated Holiday - Some formula is applied, such as "last Thursday of month"
//							(Thanksgiving, Easter)
//	Sets the given Date Object's prototypes for the following:
//	isCalendarHoliday flag (is this a calendar holiday)
//	isBusinessHoliday flag (is this a business holiday - determines business days)
//	holidayName string (proper name of holiday)
function isHoliday(dateObj) {
	var dDay	= dateObj.getDay();
	var dDate	= dateObj.getDate();
	var dMonth	= dateObj.getMonth();
	var	cflag	= false;
	var	bflag	= false;
	var	holiday	= "N/A";
		
	if ((dMonth	== 0) && (dDate == 1)) {
		holiday	= "New Year's Day";
		cflag	= true;
		bflag	= true;
		
	} else if ((dMonth	== 1) && (dDate == 14)) {
		holiday	= "Valentine's Day";
		cflag	= true;
		bflag	= false;
		
	} else if ((dMonth	== 3) && (dDate == 1)) {
		holiday	= "April Fool's Day";
		cflag	= true;
		bflag	= false;
		
	} else if ((dMonth	== 6) && (dDate == 4)) {
		holiday	= "Fourth Of July";
		cflag	= true;
		bflag	= true;
		
	} else if ((dMonth	== 9) && (dDate == 31)) {
		holiday	= "Haloween";
		cflag	= true;
		bflag	= false;

	} else if ((dMonth == 10) && (dDate == findLastDayInMonth('Thursday', dateObj).getDate())) {
		holiday	= "Thanksgiving";
		cflag	= true;
		bflag	= true;
			
	} else if ((dMonth	== 11) && (dDate == 24)) {
		holiday	= "Christmas Eve";
		cflag	= true;
		bflag	= false;
		
	} else if ((dMonth	== 11) && (dDate == 25)) {
		holiday	= "Christmas";
		cflag	= true;
		bflag	= true;
		
	} else if ((dMonth	== 11) && (dDate == 31)) {
		holiday	= "New Year's Eve";
		cflag	= true;
		bflag	= true;
		
	}
	
	dateObj.isCalendarHoliday	= cflag;
	dateObj.isBusinessHoliday	= bflag;
	dateObj.holidayName			= holiday;
}


//*---------------------------------------------------------------------*//
//	Function to find the last specific day in a month.  For example, to
//	find the last Thursday in November (Thanksgiving).  Returns a Date 
//	object set to the desired day of the month.
function findLastDayInMonth(findDay, dateObj) {
	var intDay		= dayAsInteger(findDay);
	var lastDay		= lastDayOfMonth(dateObj);
	var lastDate	= lastDateOfMonth(dateObj)
	var diffDays;
		
	if ((intDay != -1) || (lastDay != -1)) {
		if (intDay > lastDay) {
			diffDays	= (lastDay + 7) - intDay;
		} else {
			diffDays	= lastDay - intDay;
		}

		retDate		= dateAdd(lastDate, -diffDays, 'd');
		return retDate;
	} else {
		return "ERROR.";
	}
}


//*---------------------------------------------------------------------*//
//	Function to "clone" a date object for manipulation.  This will allow
//	the original date to be preserved
function cloneDate(dateObj) {
	var mDate	= dateObj.getTime();
	var cDate	= new Date(mDate);
	
	return cDate;

}

//*---------------------------------------------------------------------*//
//	Function to return the number of milliseconds in various lengths of time
//	Months and Years are not standard arguments, as months can contain anywhere
//	from 28 to 31 days.  Years contain 365 or 366 days.  Special handling is done
//	in these cases.
function getMilliseconds(timeUnit) {
	
	var	adj;
	
	if (timeUnit == "s") {				// SECONDS
		adj	= 1000;
	} else if (timeUnit == "m") {		// MINUTES
		adj	= 60 * 1000;
	} else if (timeUnit == "h") {		// HOURS
		adj	= 60 * 60 * 1000;
	} else if (timeUnit == "d") {		// DAYS
		adj	= 24 * 60 * 60 * 1000;
	} else if (timeUnit == "w") {		// WEEKS
		adj	= 7 * 24 * 60 * 60 * 1000;
	} else {							// BAD ARGUMENT
		adj	= -1;
	}
	
	return adj;
}


//*---------------------------------------------------------------------*//
//*---------------------------------------------------------------------*//
//*							UTILITY FUNCTIONS							*//
//*---------------------------------------------------------------------*//
//*---------------------------------------------------------------------*//

//*---------------------------------------------------------------------*//
//	Function to ensure an argument is a positive integer
function isPositiveInt(arg) {

	if (isNaN(arg)) {
		return false;
	}
	if (Math.ceil(arg) != Math.floor(arg)) {
		return false;
	}
	if (arg < 0) {
		return false;
	}
	
//	Passed all tests, must be OK
	return true;
}

//*---------------------------------------------------------------------*//
//	Function to add leading zero to any one-digit date component.
function makeTwoDigit(numCheck) {
	if ((numCheck + "").length < 2) {
		return "0" + numCheck;
	} else {
		return numCheck;
	}
}

