/////////////////////////////////////////////////////////////////////////////
//
// Gladiator Components 
//
// http://www.gladiatorweb.com
//
// (c) 2006 by Edward H. Trager .  All Rights Reserved
//
// NAME: gladiatorDate.js
//
// DESCRIPTION: Implements Gregorian and Julian date handling for
//              the Gladiator components toolkit.
//
// AUTHOR: Edward H. Trager
//
// LAST UPDATE: 2006.03.27
//
// NOTE: SEE "LICENSE" FILE FOR LICENSING TERMS
//
//////////////////////////////////////////////////////////////////////////////

//
// DEPENDENCY CHECKING:
//
if(typeof GLADIATOR_CORE_INCLUDED == 'undefined') alert('NOTE BENE: "gladiatorDate.js" REQUIRES "gladiatorCore.js"!');

//
// DEFINE:
//
var GLADIATOR_DATE_INCLUDED=true;

//
// formatISODate()
//
// Arguments: year, month, day
// Return: Date string in ISO 8601 format for CE years.
//
//         For years BCE, the year is padded to 4 digits
//         and is prefixed with a negative sign.
//         Actual ISO 8601 conformance would require
//         expanding the padding of BCE years
//         beyond 4 digits, which I do not do, and then
//         prefixing with a negative sign.
//  
//         NOTE BENE: If your application really
//         requires accurate representation of dates
//         before 1582, consider modifying this function
//         to return the Julian day number instead.  This
//         would solve the issue of BCE dates as well.
//
function formatISODate( year, month, day, delimiter ){
	
	// Date delimiter:
	if(isUndefined(delimiter) || isNull(delimiter) ){
		delimiter="-";
	}
	if(year<0){
		year= -year; year = "-" + padWithZeros(year,4);
	}else{
		year = padWithZeros(year,4);
	}
	return year + delimiter + padWithZeros(month,2) + delimiter + padWithZeros(day,2);
	
}

//
// isLeapYear()
//
// Every year divisible by 4 is a leap year ...
// ... except years divisible by 100 ...
// ... unless also divisible by 400 ...
//
function isLeapYear(y){
	
	if(y%4==0){
		if(y%100==0){
			if(y%400==0) return true;
			return false;
		}
		return true;
	}
	return false;
}

////////////////////////
//
// global DAYS_IN_MONTH
//
////////////////////////

//
// daysInMonth()
//
function daysInMonth(m,y){
	
	var _daysInMonth = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
	
	if(m==2){
		return (y && isLeapYear(y))?29:28;
	}else{
		return _daysInMonth[m-1];
	}
}

//
// gDate class : Stores a date
//
// Julian <--> Gregorian conversion algorithms adapted 
// from Press et al., "Numerical Recipes in C" 
// (Cambridge Univ. Press).  The Javascript adaptation
// presented here also borrows a bit from Marc A. Murison's
// Javascript implementation shown on the U.S. Naval Observatory's
// Astronomical Applications Dept. web page, "Julian Date Converter"
// at http://aa.usno.navy.mil/data/docs/JulianDate.html
//
// year : --> If "year" is a string, then it is assumed to be in ISO "yyyy-mm-dd" format.
//        --> Else, if year is undefined, then the date is set to today's date.
//        --> Else, if year is a number, then the number becomes the year.
//
// month: --> If undefined, month 01 (January) is used.
//
// day:   --> If undefined, day 01 is used. 
//
function gDate(year,month,day){
	
	////////////////////////////
	//
	// PRIVATE MEMBER VARIABLES
	//
	////////////////////////////
	var _GREGORIAN_START  = 2299161; // Julian day number for Oct. 15, 1582;
	var _julian;                     // Julian day number
	var _year;
	var _month;
	var _day;
	var _daysInMonth = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
	//
	// _self provides access to "this" within class methods:
	//
	var _self=this;
	
	/////////////////////////
	//
	// PRIVATE METHODS
	//
	/////////////////////////
	
	//
	// Private method _setJulian()
	//
	// --> Pass year (y), month (m), and day (d) as arguments:
	//
	function _setJulian(y,m,d){
		
		var jy,ja,jm;
		
		// Negative years represent years before the Common Era (BCE).
		// Correct for the fact that there was no year zero:
		if(y<0) y++;
		
		if(m>2){
			jy=y;
			jm=m+1;
		}else{
			jy=y-1;
			jm=m+13;
		}
		
		_julian = Math.floor( Math.floor(365.25*jy) + Math.floor(30.6001*jm) + d + 1720995 );
		
		// Check for the switch to the Gregorian calendar:
		var gregorianStart = 15 + 31*( 10 + 12*1582 );
		
		if( d + 31*(m + 12*y) >= gregorianStart ){
			ja = Math.floor(0.01*jy);
			_julian += 2 - ja + Math.floor(0.25*ja);
		}
	}
	
	//
	// PRIVATE _julianToGregorian() : 
	//
	// Sets the _year, _month, and _day members
	// based on the value of _julian
	//
	function _julianToGregorian(){
		
		var j1, j2, j3, j4, j5;
		
		if( _julian >= _GREGORIAN_START ){
			var tmp = Math.floor( ( (_julian - 1867216) - 0.25 ) / 36524.25 );
			j1 = _julian + 1 + tmp - Math.floor(0.25*tmp);
		}else{
			j1 = _julian;
		}
		
		j2 = j1 + 1524;
		j3 = Math.floor( 6680.0 + ( (j2 - 2439870) - 122.1 )/365.25 );
		j4 = Math.floor(j3*365.25);
		j5 = Math.floor( (j2 - j4)/30.6001 );
		
		_day   = Math.floor(j2 - j4 - Math.floor(j5*30.6001));
		_month = Math.floor(j5 - 1);
		if( _month > 12 ) _month -= 12;
		_year = Math.floor(j3 - 4715);
		if( _month > 2 ) _year--;
		if( _year <= 0 ) _year--;
		
	}
	
	//
	// PRIVATE _checkYear();
	//
	function _checkYear(){
		if(_year==0){
			alert("The year has been set to 0001 CE because there was no year 0 in the Julian calendar.");
			_year = 1;
		}
		if(_year<-4713){
			alert("The year has been set to -4713 BCE because the Julian cycle started on -4713.01.01.");
			_year = -4713;
		}
	}
	
	//////////////////
	//
	// PUBLIC METHODS
	//
	//////////////////
	
	//
	// setToday(): Sets date to today
	//           
	this.setToday = function(){
		
		var today = new Date();
		_year  = today.getFullYear();
		_month = today.getMonth()+1;
		_day   = today.getDate();
		_setJulian(_year,_month,_day);
		
	}
	
	//
	// setDate()
	//
	this.setDate = function(year,month,day){
		
		// Set YEAR (month, day ...)
		if(isUndefined(year)){
			this.setToday();
			return;
		}else if(isString(year)){
			//
			// Assume string is in an ISO-compatible form "yyyy-mm-dd":
			//
			var parts = new Array();
			parts = year.split("-");
			_year = parseInt(parts[0]);
			_checkYear();
			month=parts[1];
			day=parts[2];
		}else{
			_year = parseInt(year,10);
			_checkYear();
		}
		
		// Set MONTH:
		if(isUndefined(month)){
			_month = 01;
		}else{
			_month = parseInt(month,10);
			if(_month<01) _month=01;
			if(_month>12) _month=12;
		}
		// Set DAY:
		if(isUndefined(day)){
			_day = 01;
		}else{
			_day = parseInt(day,10);
			if(_day<01){
				_day = 01;
				alert("The day of month has been set to "+_day);
			}else{
				var dim=daysInMonth(_month,_year);
				if(_day>dim){
					_day = dim;
					alert("The day of month has been set to "+_day);
				}
			}
			//
			// Finally, don't permit the missing 10 days 
			// in October of 1582:
			//
			if( _year==1582 && _month==10 && _day>4 && _day<15 ){
				alert("The day of the month has been set to the 15th because the 5th to 14th were skipped in October of 1582\nwhen Pope Gregory XIII implemented the calendar reforms\nof the commission headed by the Jesuit mathematician and astronomer\nChristoph Clavius.");
				_day=15;
			}
		}
		////////////////
		//
		// Set JULIAN:
		//
		////////////////
		_setJulian(_year,_month,_day);
	}
	
	//////////////////////////////////////////////
	//
	// Just call setDate() to construct the date:
	//
	//////////////////////////////////////////////
	this.setDate(year,month,day);
	
	//
	// getJulianDayNumber()
	//
	this.getJulianDayNumber = function(){
		
		return _julian;
		
	}
	
	//
	// dayOfWeek(): Returns the day of the week
	//
	this.dayOfWeek = function(){
		
		return (_julian+1)%7;
		
	}
	
	//
	// getYear()
	//
	this.getYear = function(){
		
		return _year;
		
	}
	
	//
	// getMonth()
	//
	this.getMonth = function(){
		
		return _month;
		
	}
	
	//
	// getDay()
	//
	this.getDay = function(){
		
		return _day;
		
	}
	
	//
	// getISODateString()
	//
	this.getISODateString = function(){
		
		return formatISODate(_year,_month,_day);
		
	}
	
	//
	// s() : Return the date as an ISO string -- identical to getISODateString()
	//
	this.s = function(){
		
		return formatISODate(_year,_month,_day);
		
	}
	
	//
	// increment
	//
	this.increment = function(){
		
		_julian++;
		_julianToGregorian();
		
	}
	
	//
	// decrement()
	//
	this.decrement = function(){
		
		_julian--;
		_julianToGregorian();
		
	}
	
	//
	// add(days)
	//
	this.add = function(daysToAdd){
		
		_julian+=daysToAdd;
		_julianToGregorian();
		
	}
	
	//
	// Special case of October, 1582 where 10 days are missing
	// from 5th to 14th:
	this.isInOctober1582 = function(){
		return (_year==1582 && _month==10);
	}
	
}

