// kdt - Ken's DaTe functions
//
// In these routines, {year} is the full year number (e.g. 1997),
// {month} is 1-12 (Jan-Dec), and {day} is the day of month (1-31).
// Weekdays or days-of-the-week are 0-6 (Sun-Sat).
//
// Modification History
// --------------------
// 11/09/97, K. Nellis - converted from other languages.
// 01/12/02, K. Nellis - converted from C to JavaScript.
// 04/28/05, K. Nellis - (1) fixed the old kdtEaster to add century
//						 parameters and two exceptions and renamed
//						 it as kdtEasterOld; (2) implemented new
//						 kdtEaster function.
//----------------------------------------------------------------------

var JANUARY=1, FEBRUARY=2, MARCH=3, APRIL=4, MAY=5, JUNE=6,
    JULY=7, AUGUST=8, SEPTEMBER=9, OCTOBER=10, NOVEMBER=11, DECEMBER=12;

var kdtMonthNames = ["0", "January", "February", "March", "April",
                     "May", "June", "July", "August", "September",
                     "October", "November", "December"];

var SUNDAY=0, MONDAY=1, TUESDAY=2, WEDNESDAY=3, THURSDAY=4, FRIDAY=5,
    SATURDAY=6;

var kdtWeekDayNames = ["Sunday", "Monday", "Tuesday", "Wednesday",
                       "Thursday", "Friday", "Saturday"];

// **** Internal kdt Functions *****


function kdtNormalizeMonth (year, month)
{
	var m = month - 0;	// string-to-number conversion
	var y = year - 0;	// string-to-number conversion
	
	while (m < JANUARY) {
		y--;
		m += 12;
		}
	
	while (m > DECEMBER) {
		y++;
		m -= 12;
		}
		
	return [y, m];
}


// **** Exported kdt Functions *****


function kdtEasterOld (year)
{
	// Returns the month and day in which Easter falls in the given year.
	// This logic was taken from the Honeywell Multics "calendar" program.
	// It appears to be the Gauss algorithm for the years 1900-2099,
	// according to Wikipedia (http://en.wikipedia.org/) article "Computus".
	// Accordingly to the article, there are a few exceptions, which this
	// algorithm doesn't handle. For example, it produces the wrong result
	// for 1981.

	var m, n;
	if (year < 1700)		{m = 22; n = 2;}
	else if (year < 1800)	{m = 23; n = 3;}
	else if (year < 1900)	{m = 23; n = 4;}
	else if (year < 2100)	{m = 24; n = 5;}
	else if (year < 2200)	{m = 24; n = 6;}
	else					{m = 25; n = 0;}

	var a = year % 19;
	var b = year %  4;
	var c = year %  7;
	var d = (19 * a + m) % 30;
	var e = (2 * b + 4 * c + 6 * d + n) % 7;
	var month = MARCH;
	var day = 22 + d + e;
	if (day > 31) {
		month = APRIL;
		day -= 31;
		}
	// Exception #1
	if (month == APRIL && day == 26) day = 19;
	// Exception #2
	if (month == APRIL && day == 25 && d == 28 && e == 6 && a > 10)
		day = 18;
	return [month, day];
}


// function kdtEasterMJB (year)
function kdtEaster (year)
{
	// Returns the month and day in which Easter falls in the given year.
	// This is reproduced from the "Meeus/Jones/Butcher Gregorian algorithm"
	// as given in Wikipedia (http://en.wikipedia.org/) article "Computus"
	// as of 4/27/05.
	
	var a = year % 19;
	var b = Math.floor (year / 100);
	var c = year %  100;
	var d = b >> 2;
	var e = b % 4;
	var f = Math.floor ((b + 8) / 25);
	var g = Math.floor ((b - f + 1) / 3);
	var h = (19*a + b - d - g + 15) % 30;
	var i = c >> 2;
	var k = c % 4;
	var l = (32 + 2*e + 2*i - h - k) % 7;
	var m = Math.floor ((a + 11*h + 22*l) / 451);
	var n = h + l - 7*m + 114;
	var month = Math.floor (n / 31);
	var day = n % 31 + 1;
	return [month, day];
}


function kdtMoDays (year, month)
{
	// Returns the number of days in the given month of the given year.
	
	switch (month) {
		case 1:
		case 3:
		case 5:
		case 7:
		case 8:
		case 10:
		case 12:
			return 31;

		case 4:
		case 6:
		case 9:
		case 11:
			return 30;

		case 2:
			if (year %   4) return 28;
			if (year % 100) return 29;
			if (year % 400) return 28;
			return 29;

		default:
			return 0;
		}
}


function kdtNormalize (year, month, day)
{
	// Normalizes the date so that month and day are within valid ranges.
	// For example, month=13 implies January of the next year.
	// Day=0 implies the last day of the previous month.
	
	var y = year  - 0;	// string-to-number conversion
	var m = month - 0;	// string-to-number conversion
	var d = day   - 0;	// string-to-number conversion
	var n;

	var dt = kdtNormalizeMonth (y, m);
	y = dt[0]; m = dt[1];	

	while (d < 1) {
		dt = kdtNormalizeMonth (y, m-1);
		y = dt[0]; m = dt[1];	
		d += kdtMoDays (y, m);
		}
		
	while (d > (n = kdtMoDays (y, m))) {
		d -= n;
		dt = kdtNormalizeMonth (y, m+1);
		y = dt[0]; m = dt[1];	
		}
	
	return [y, m, d];
}


function kdtRelDay (year, month, dow, offset)
{
	// Returns the day of the specified month and year that
	// is {offset} days past the first {dow} day-of-week.
	// For example, Thanksgiving is the fourth Thursday in November, or
	// is 21 days past the first Thursday. For 1997, Thanksgiving
	// falls on day {kdtRelDay (1997, NOVEMBER, THURSDAY, 21)}.

	var d = dow - 0;		// string-to-number conversion
	var o = offset - 0;		// string-to-number conversion
	return (d + 7 - kdtWkDay(year,month,1)) % 7 + o + 1;
}


function kdtRelEndDay (year, month, dow, offset)
{
	// Returns the day of the specified month and year that
	// is {offset} days prior to the last {dow} day-of-week.
	// For example, Memorial Day is the last Monday in May, or
	// is 0 days prior to the last Monday. For 1997, Memorial Day
	// falls on day {kdtRelEndDay (1997, MAY, MONDAY, 0)}.
	
	var last_day, last_day_dow;
	
	last_day = kdtMoDays (year, month);
	last_day_dow = kdtWkDay (year, month, last_day);
	if (last_day_dow < dow) last_day_dow += 7;
	return last_day + dow - last_day_dow - offset;
}


function kdtToday ()
{
	// Returns today's date
	
	var dt		= new Date ();
	var year	= dt.getFullYear();
	var month	= dt.getMonth() + 1;
	var day		= dt.getDate();

	return [year, month, day];
}


function kdtWkDay (year, month, day)
{
	// Returns the day-of-week of the specified day of the specified month
	// of the specified year.

	var y = year  - 0;		// convert string to number
	var m = month - 0;		// convert string to number
	var d = day   - 0;		// convert string to number
	var no, yprime, mprime, n1, n2, n3, n4, n5;
	
	no = Math.floor (0.6 + (1.0 / m));
	yprime = y - no;
	mprime = m + 12 * no;
	n1 = Math.floor(13 * (mprime + 1) / 5);
	n2 = Math.floor(5 * yprime / 4);
	n3 = Math.floor(yprime / 100);
	n4 = Math.floor(yprime / 400);
	n5 = n1 + n2 - n3 + n4 + d - 1;
	return n5 % 7;	 
}
