/* -----------------------------------------------------------------------------
	FILE:		dateExtension.js
	PURPOSE:	Javascript Date object Extension Library
				Provides a host of extended date manipulation related methods

	Copyright 2007 (c) FlowTech Solutions, Inc.		(www.FlowTech-Solutions.com)

	Permission is hereby granted, free of charge, to any person obtaining
	a copy of this software and associated documentation files (the
	"Software"), to deal in the Software without restriction, including
	without limitation the rights to use, copy, modify, merge, publish,
	distribute, sublicense, and/or sell copies of the Software, and to
	permit persons to whom the Software is furnished to do so, subject to
	the following conditions:

	The above copyright notice and this permission notice shall be
	included in all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
	EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
	MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
	NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
	LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
	OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
	WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


	PUBLIC PROPERTIES		DESCRIPTION
	-----------------		------------------------------------------------
	monthNames				Full month names. Change this for local month names
	monthAbbreviations		Month abbreviations. Change this for local month names
	dayNames				Full day names. Change this for local month names
	dayAbbreviations		Day abbreviations. Change this for local month names
	day2LetterAbbreviations	Day 2 letter abbreviations. Change this for local month names
	day1LetterAbbreviations	Day 1 letter abbreviations. Change this for local month names
	daysInMonth				Number of days in a month (does not account for leap year)


	PUBLIC METHODS						DESCRIPTION
	--------------						---------------------------------------------------
	LZ(1digit)							Utility function to prepend a 0 to single-digit numbers
	getFullYear()						Returns a full 4 digit year
	parseString(datestring, [format])	Parses a date string (given a format) into a Date object
	isValid(datestring, [format])		Validates the date string as a validate date
	isBefore(date2)						Determines if Date is before another date (includes time)
	isAfter(date2)						Determines if Date is after another date (includes time)
	equals(date2)						Determines if Date is equal to another date (includes time)
	equalsIgnoreTime(date2)				Determines if Date is equal to another date (excludes time)
	format(formatstring)				Formats a Date object into the desired string format
	getDayName()						Returns the day name e.g. Sunday, Monday
	getDayAbbreviation()				Returns the day abbreviation name e.g. Sun Mon
	getMonthName()						Returns the month name e.g. January, February
	getMonthAbbreviation()				Returns the month abbreviation name Jan, Feb
	clearTime()							Clears the time element of the Date object
	add(interval, number)				Add an incremental date element e.g. Y-year, M-month, d-Day, h-hour,...


	Additional Date Formata
	--------------------------------------------------------------------------------------------------------------
	U.S.														mm/dd/yyyy
	ANSI														yy.mm.dd
	British/French										dd/mm/yy
	German												dd.mm.yy
	Italian													dd-mm-yy

	Default + milliseconds							mon dd yyyy hh:mi:ss:mmmAM (or PM)		MMM d y h:m:s
	Europe default + milliseconds				dd mon yyyy hh:mi:ss:mmm(24h)
	ODBC canonical									yyyy-mm-dd hh:mi:ss(24h)							y-M-d H:m:s
	ODBC canonical (with milliseconds)		yyyy-mm-dd hh:mi:ss.mmm(24h)					y-M-d H:m:s????
	ISO8601												yyyy-mm-ddThh:mi:ss.mmm (no spaces)		y-M-d?H:m:s????
	ISO8601 with time zone Z.					yyyy-mm-ddThh:mi:ss.mmmZ (no spaces)	y-M-d?H:m:s?????
	Hijri (5)													dd mon yyyy hh:mi:ss:mmmAM					dd MMM y hh:m:s

------------------------------------------------------------------------------ */

// ----------------------------  PUBLIC PROPERTIES ---------------------------
// Full day names. Change this for local month names
Date.dayNames = new Array('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday');
// Day abbreviations. Change this for local month names
Date.dayAbbreviations = new Array('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
// Day 2 letter abbreviations. Change this for local month names
Date.day2LetterAbbreviations = new Array('Su','Mo','Tu','We','Th','Fr','Sa');
// Day 1 letter abbreviations. Change this for local month names
Date.day1LetterAbbreviations = new Array('S','M','T','W','T','F','S');
// Number of days in a month (does not account for leap year)
Date.daysInMonth = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
// Full month names. Change this for local month names
Date.monthNames = new Array('January','February','March','April','May','June','July','August','September','October','November','December');
// Month abbreviations. Change this for local month names
Date.monthAbbreviations = new Array('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
// Set to false to prefer 'European' format meaning Feb 1
Date.preferAmericanFormat = true;

// ----------------------------  PUBLIC METHODS ---------------------------

// Utility function to prepend a 0 to single-digit numbers
Date.LZ = function(x) {return(x<0||x>9?"":"0")+x};

// If the getFullYear() method is not defined, create it
if (!Date.prototype.getFullYear)
{
	Date.prototype.getFullYear = function()
	{
		var yy=this.getYear();
		return (yy<1900?yy+1900:yy);
	}
}

// Parse a string and convert it to a Date object.
// If no format is passed, try a list of common formats.
// If string cannot be parsed, return null.
// Avoids regular expressions to be more portable.
Date.parseString = function(val, format)
{
	// If no format is specified, try a few common formats
	if ( typeof(format)=="undefined" || format==null || format=="" || this.isArray(format) )
	{
		var generalFormats=new Array('y-M-d','MMM d, y','MMM d,y','y-MMM-d','d-MMM-y','MMM d','MMM-d','d-MMM');
		var monthFirst=new Array('M/d/y','M-d-y','M.d.y','M/d','M-d');
		var dateFirst =new Array('d/M/y','d-M-y','d.M.y','d/M','d-M');
		var formatsToCheck =  ( typeof(format)!="undefined" && this.isArray(format) ) ? format : (Date.preferAmericanFormat?monthFirst:dateFirst) ;
		var checkList=new Array( formatsToCheck );
		for (var i=0; i<checkList.length; i++)
		{
			var l=checkList[i];
			for (var j=0; j<l.length; j++)
			{
			var d=Date.parseString(val,l[j]);
			if (d!=null)
				{
				return d;
				}
			}
		}
		return null;
	}

	//
	this.isInteger = function(val)
	{
		for (var i=0; i < val.length; i++)
		{
			if ("1234567890".indexOf(val.charAt(i)) == -1) return false;
		}
		return true;
	};

	this.getInt = function(str,i,minlength,maxlength)
	{
		for (var x=maxlength; x>=minlength; x--)
		{
		  var token=str.substring(i,i+x);
		  if (token.length < minlength)
		  {
			return null;
		  }
		  if (this.isInteger(token))
		  {
			return token;
		  }
		}
		return null;
	};

	val=val+"";
	format=format+"";
	var i_val=0;
	var i_format=0;
	var c="";
	var token="";
	var token2="";
	var x,y;
	var year=new Date().getFullYear();
	var month=1;
	var date=1;
	var hh=0;
	var mm=0;
	var ss=0;
	var ampm="";
	while (i_format < format.length)
	{// Get next token from format string
		c=format.charAt(i_format);
		token="";
		while ((format.charAt(i_format)==c) && (i_format < format.length))
		{
	      token += format.charAt(i_format++);
		}
		// Extract contents of value based on format token
		if (token=="yyyy" || token=="yy" || token=="y")
		{
			if (token=="yyyy")	{x=4;y=4;}
			if (token=="yy")	{x=2;y=2;}
			if (token=="y")		{x=2;y=4;}
			year=this.getInt(val,i_val,x,y);
			if (year==null)		return null;
			i_val += year.length;
			if (year.length==2)
			{
				year = (year > 70) ? 1900+(year-0) : 2000+(year-0) ;
			}
		}
		else if (token=="MMM" || token=="NNN")
		{
			month=0;
			var names = (token=="MMM"?(Date.monthNames.concat(Date.monthAbbreviations)):Date.monthAbbreviations);
			for (var i=0; i<names.length; i++)
			{
				var month_name=names[i];
				if (val.substring(i_val,i_val+month_name.length).toLowerCase()==month_name.toLowerCase())
				{
					month=(i%12)+1;
					i_val += month_name.length;
					break;
				}
			}
			if ((month < 1)||(month>12))	return null;
		}
		else if (token=="EE"||token=="E")
		{
			var names = (token=="EE"?Date.dayNames:Date.dayAbbreviations);
			for (var i=0; i<names.length; i++)
			{
				var day_name=names[i];
				if (val.substring(i_val,i_val+day_name.length).toLowerCase()==day_name.toLowerCase())
				{
					i_val += day_name.length;
					break;
				}
			}
		}
		else if (token=="MM"||token=="M")
		{
			month=this.getInt(val,i_val,token.length,2);
			if(month==null||(month<1)||(month>12))	return null;
			i_val+=month.length;
		}
		else if (token=="dd"||token=="d")
		{
			date=this.getInt(val,i_val,token.length,2);
			if(date==null||(date<1)||(date>31))		return null;
			i_val+=date.length;
		}
		else if (token=="hh"||token=="h")
		{
			hh=this.getInt(val,i_val,token.length,2);
			if(hh==null||(hh<1)||(hh>12))			return null;
			i_val+=hh.length;
		}
		else if (token=="HH"||token=="H")
		{
			hh=this.getInt(val,i_val,token.length,2);
			if(hh==null||(hh<0)||(hh>23))			return null;
			i_val+=hh.length;
		}
		else if (token=="KK"||token=="K")
		{
			hh=this.getInt(val,i_val,token.length,2);
			if(hh==null||(hh<0)||(hh>11))			return null;
			i_val+=hh.length;
			hh++;
		}
		else if (token=="kk"||token=="k")
		{
			hh=this.getInt(val,i_val,token.length,2);
			if(hh==null||(hh<1)||(hh>24))			return null;
			i_val+=hh.length;
			hh--;
		}
		else if (token=="mm"||token=="m")
		{
			mm=this.getInt(val,i_val,token.length,2);
			if(mm==null||(mm<0)||(mm>59))			return null;
			i_val+=mm.length;
		}
		else if (token=="ss"||token=="s")
		{
			ss=this.getInt(val,i_val,token.length,2);
			if(ss==null||(ss<0)||(ss>59))			return null;
			i_val+=ss.length;
		}
		else if (token=="a")
		{
			if (val.substring(i_val,i_val+2).toLowerCase()=="am")
			{
				ampm="AM";
			}
			else if (val.substring(i_val,i_val+2).toLowerCase()=="pm")
			{
				ampm="PM";
			}
			else
			{
				return null;
			}
			i_val+=2;
		}
		else if (token=="?" || token=="??" || token=="???" || token=="????" || token=="?????" || token=="??????")
		{
			i_val+=token.length;
		}
		else
		{
			if (val.substring(i_val,i_val+token.length)!=token)		return null;
			else
			{
				i_val+=token.length;
			}
		}
	}
	// If there are any trailing characters left in the value, it doesn't match
	if (i_val != val.length)	return null;
	// Is date valid for month?
	if (month==2)
	{
		// Check for leap year
		if ( ( (year%4==0)&&(year%100 != 0) ) || (year%400==0) )
		{ // leap year
			if (date > 29)		return null;
		}
		else
		{
			if (date > 28)		return null;
		}
	}
	if ((month==4)||(month==6)||(month==9)||(month==11))
	{
		if (date > 30)			return null;
	}
	// Correct hours value
	if (hh<12 && ampm=="PM")
	{
		hh=hh-0+12;
	}
	else if (hh>11 && ampm=="AM")
	{
		hh-=12;
	}
	return new Date(year,month-1,date,hh,mm,ss);
}


Date.isArray = function(object)
{
	return object != null && typeof object == "object" && 'splice' in object && 'join' in object;
};


// Check if a date string is valid
Date.isValid = function(val,format)
{
	return (Date.parseString(val,format) != null);
}


// Check if a date object is before another date object
Date.prototype.isBefore = function(date2)
{
	if (date2==null)		return false;
	return (this.getTime()<date2.getTime());
}


// Check if a date object is after another date object
Date.prototype.isAfter = function(date2)
{
	if (date2==null)		return false;
	return (this.getTime()>date2.getTime());
}


// Check if a date object is less than or equal to another date object
Date.prototype.isLE = function(date2)
{
	if (date2==null)		return false;
	return (this.getTime()<=date2.getTime());
}


// Check if a date object is greater or eual to another date object
Date.prototype.isGE = function(date2)
{
	if (date2==null)		return false;
	return (this.getTime()>=date2.getTime());
}


// Check if two date objects have equal dates and times
Date.prototype.equals = function(date2)
{
	if (date2==null)		return false;
	return (this.getTime()==date2.getTime());
}


// Check if two date objects have equal dates, disregarding times
Date.prototype.equalsIgnoreTime = function(date2)
{
	if (date2==null)		return false;
	var d1 = (new Date(this.getTime())).clearTime();
	var d2 = (new Date(date2.getTime())).clearTime();
	return (d1.getTime()==d2.getTime());
}


// Format a date into a string using a given format string
Date.prototype.format = function(format)
{
	format=format+"";
	var result="";
	var i_format=0;
	var c="";
	var token="";
	var y=this.getYear()+"";
	var M=this.getMonth()+1;
	var d=this.getDate();
	var E=this.getDay();
	var H=this.getHours();
	var m=this.getMinutes();
	var s=this.getSeconds();
	var yyyy,yy,MMM,MM,dd,hh,h,mm,ss,ampm,HH,H,KK,K,kk,k;
	// Convert real date parts into formatted versions
	var value=new Object();
	if (y.length < 4)
	{
		y=""+(+y+1900);
	}
	value["y"]=""+y;
	value["yyyy"]=y;
	value["yy"]=y.substring(2,4);
	value["M"]=M;
	value["MM"]=Date.LZ(M);
	value["MMM"]=Date.monthNames[M-1];
	value["NNN"]=Date.monthAbbreviations[M-1];
	value["d"]=d;
	value["dd"]=Date.LZ(d);
	value["E"]=Date.dayAbbreviations[E];
	value["EE"]=Date.dayNames[E];
	value["H"]=H;
	value["HH"]=Date.LZ(H);
	value["h"] = (H==0) ? 12 : (H>12) ? H-12 : H;
	value["hh"]=Date.LZ(value["h"]);
	value["K"]=value["h"]-1;
	value["k"]=value["H"]+1;
	value["KK"]=Date.LZ(value["K"]);
	value["kk"]=Date.LZ(value["k"]);
	value["a"] = (H > 11) ? "PM" : "AM";
	value["m"]=m;
	value["mm"]=Date.LZ(m);
	value["s"]=s;
	value["ss"]=Date.LZ(s);
	while (i_format < format.length)
	{
		c=format.charAt(i_format);
		token="";
		while ((format.charAt(i_format)==c) && (i_format < format.length))
		{
			token += format.charAt(i_format++);
		}
		result += (value[token] != null) ? value[token] : token ;
	}
	return result;
}



// Get the full name of the day for a date
Date.prototype.getDayName = function()
{
	return Date.dayNames[this.getDay()];
}


// Get the abbreviation of the day for a date
Date.prototype.getDayAbbreviation = function()
{
	return Date.dayAbbreviations[this.getDay()];
}


// Get the full name of the month for a date
Date.prototype.getMonthName = function()
{
	return Date.monthNames[this.getMonth()];
}


// Get the abbreviation of the month for a date
Date.prototype.getMonthAbbreviation = function()
{
	return Date.monthAbbreviations[this.getMonth()];
}


// Clear all time information from the current date object
Date.prototype.clearTime = function()
{
	this.setHours(0,0,0,0);
	return this;
}


// Add an amount of time to a date. Negative numbers can be passed to subtract time.
Date.prototype.add = function(interval, number)
{
	if (typeof(interval)=="undefined" || interval==null || typeof(number)=="undefined" || number==null)
	{
		return this;
	}
	number = +number;
	if (interval=='y')
	{ // year
		this.setFullYear(this.getFullYear()+number);
	}
	else if (interval=='M')
	{ // Month
		this.setMonth(this.getMonth()+number);
	}
	else if (interval=='d')
	{ // Day of the month 1-31
		this.setDate(this.getDate()+number);
	}
	else if (interval=='w')
	{ // Weeks
		var step = (number>0)?1:-1;
		while (number!=0)
		{
			this.add('d',step);
			while(this.getDay()==0 || this.getDay()==6)
			{
				this.add('d',step);
			}
			number -= step;
		}
	}
	else if (interval=='h')
	{ // Hour (from 0-23)
		this.setHours(this.getHours() + number);
	}
	else if (interval=='m')
	{ // Minute (from 0-59)
		this.setMinutes(this.getMinutes() + number);
	}
	else if (interval=='s')
	{ // Second (from 0-59)
		this.setSeconds(this.getSeconds() + number);
	}
	return this;
}


// Retrieves today's date
Date.prototype.today = function()
{
	var dt = new Date();
	return dt;
}

// Retrieves today's date
Date.prototype.now = function()
{
	var dt = new Date();
	return dt;
}


// Adjust for base date-time skew
Date.prototype.fixDate = function()
{
	var	base = new Date(0),
			skew = base.getTime();
  if (skew > 0)
    this.setTime(this.getTime() - skew);
}

// Establish the 1st date of the month (strips time element)
Date.prototype.FirstOfMonth = function(dt)
{
	dt = dt||this;
	var dt2 = (new Date(dt)).clearTime();
	dt2.setDate(1);
	return (dt2);
}

// Establish the last day of the month (strips time element)
Date.prototype.EndOfMonth = function(dt)
{
	dt = dt||this;
	var	dt2 = (new Date(dt)).clearTime(),
			ld = Date.daysInMonth[dt2.getMonth()+1];
	dt2.setDate(ld);
	if (dt2.getDate() != ld) 	dt2.setDate(ld-1);
	return (dt2);
}

// Check if a date object is less than or equal to another date object
Date.prototype.isLE = function(date2)
{
        if (date2==null)                return false;
        return (this.getTime()<=date2.getTime());
}

// Check if a date object is greater or equal to another date object
Date.prototype.isGE = function(date2)
{
        if (date2==null)                return false;
        return (this.getTime()>=date2.getTime());
}

// Check if a date object is greater to another date object (ignore time element)
Date.prototype.GTIgnoreTime = function(date2)
{
        if (date2==null)                return false;
        var d1 = new Date(this.getTime()).clearTime();
        var d2 = new Date(date2.getTime()).clearTime();
        return (d1.getTime()>d2.getTime());
}

// Check if a date object is less than another date object (ignore time element)
Date.prototype.LTIgnoreTime = function(date2)
{
        if (date2==null)                return false;
        var d1 = new Date(this.getTime()).clearTime();
        var d2 = new Date(date2.getTime()).clearTime();
        return (d1.getTime()<d2.getTime());
}

// Check if a date object is greater to another date object (ignore time element)
Date.prototype.GEIgnoreTime = function(date2)
{
        if (date2==null)                return false;
        var d1 = new Date(this.getTime()).clearTime();
        var d2 = new Date(date2.getTime()).clearTime();
        return (d1.getTime()>=d2.getTime());
}

// Check if a date object is less than another date object (ignore time element)
Date.prototype.LEIgnoreTime = function(date2)
{
        if (date2==null)                return false;
        var d1 = new Date(this.getTime()).clearTime();
        var d2 = new Date(date2.getTime()).clearTime();
        return (d1.getTime()<=d2.getTime());
}




