/******************************************************************************
 **********                     DateTimePattern                      **********
 ******************************************************************************/

DateTimePattern = {};

DateTimePattern.isValidTimeString = function(string, pattern, anteMeridiem,
		postMeridiem)
{
    var date = DateTimePattern.stringToDateTime(string, pattern, anteMeridiem, postMeridiem);
    var isValid = false;
    
    if (date != null)
    {
        var roundTripString = DateTimePattern.
            dateTimeToString(date, pattern, anteMeridiem, postMeridiem);
            
        isValid = (roundTripString == string);
    }
    
    return isValid;
}

DateTimePattern.isValidDateString = function(string, pattern)
{
    var date = DateTimePattern.stringToDateTime(string, pattern);
    var isValid = false;
    
    if (date != null)
    {
        var roundTripString = DateTimePattern.dateTimeToString(date, pattern);
            
        isValid = (roundTripString == string);
    }
    
    return isValid;
}

DateTimePattern.isValidDateTimeString = function(string, pattern, anteMeridiem,
		postMeridiem)
{
    var date = DateTimePattern.stringToDateTime(string, pattern, anteMeridiem, postMeridiem);
    var isValid = false;
    
    if (date != null)
    {
        var roundTripString = DateTimePattern.
            dateTimeToString(date, pattern, anteMeridiem, postMeridiem);
            
        isValid = (roundTripString == string);
    }
    
    return isValid;
}

// Takes a date formatted in a given pattern and returns a date/time
// object. Works with tokenized dates (1/2/2008) and times times (13:04:05)
// as well as serialized date (20080102) and time (130405) formats. If the
// value cannot be parsed from the string, returns undefined.
// 
// The following format pattern tokens are allowed:
//  
//   yyyy - 4 digit year
//   yy   - 2 digit year
//   mm   - Month with leading zeros
//   m    - Month without leading zeros. Not supported for serialized
//          dates.
//   dd   - Day with leading zeros
//   d    - Day without leading zeros. Not supported for serialized
//          dates.
//   hh   - Hours with leading zeros.
//    h   - Hours without leading zeros. Not supported for serialized
//          times.
//   nn   - Minutes with leading zeros.
//    n   - Minutes without leading zeros. Not supported for serialized
//          times.
//   ss   - Seconds with leading zeros.
//    s   - Seconds without leadig zeros. Not supported for serialized
//          times.
//    t   - Meridiem (AM/PM)
// 
// If the date contains a 2 digit year, the VBScript rules for converting
// a two digit year into a 4 digit year apply (see the Date() function).
// 
// The third and for parameters are the ante and post meridiems. They are
// required if the pattern contains t.
// 
// Examples of date input and output combinations include:
// 
//   1/2/2008     m/d/yyyy    ->  1/2/2008
//   2/1/2008     d/m/yyyy    ->  1/2/2008
//   02.01.2008   dd.mm.yyyy  ->  1/2/2008
//   1/2/08       m/d/yy      ->  1/2/2008
//   20080102     yyyymmdd    ->  1/2/2008
//   080102       yymmdd      ->  1/2/2008
//   02012008     ddmmyyyy    ->  1/2/2008
// 
// Examples of time inputs include:
// 
//   13:04:05      hh:nn:ss
//   1:04:05 PM    h:nn:ss t     AM    PM
//   130405        hhnnss
//   050413        ssnnhh
// 
// Examples of possible date/time inputs include:
// 
//   1/2/2008 01:04:05 PM    m/d/yyyy hh:nn:ss t    AM    PM
//   2/1/2008 01:04:05 PM    d/m/yyyy hh:nn:ss t    AM    PM
//   02.01.2008 13:04:05 PM  dd.mm.yyyy HH:nn:ss
//   1/2/08 1:04:05 PM       m/d/yy h:nn:ss t       AM    PM
//   20080102130405          yyyymmddhhnnss
//   080102130405            yymmddhhnnss
//   05041302012008          ssnnhhddmmyyyy
// 
// Returns void if the string can't be parsed into a date/time.

DateTimePattern.stringToDateTime = function(string, pattern, anteMeridiem,
		postMeridiem)
{
	
	// We'll be forgiving of white space at the beginning and end of the
	// string.
	
	string = string.trim();
	pattern = pattern.trim();
	
	// If we have what appears to be an SAP null date, we'll convert it
	// into a VBScript friendly null date.
	
	if (DateTimePattern.isSapNullDateString(string, pattern))
	{
		return DateTimePattern.getNullDate();
	}
	
	var regEx;
	
	// First, extract the date parts using our regular expression. We won't
	// know what the matches represent, but we'll know their relative
	// position (first, second, and third).
	
	regEx = DateTimePattern.patternToRegEx(pattern, anteMeridiem,
			postMeridiem);
	
	var dateParts = regEx.exec(string);
	
	// The exec method will return null if no match was found.
	
	if (!dateParts)
	{
		return;
	}
	
	// Now, we'll extract the date parts from the pattern.
	
	regEx = new RegExp(/(yyyy|yy|mm|m|dd|d|hh|h|nn|n|ss|s|t)/ig);
	
	var patternParts = regEx.matches(pattern)
	
	// Loop through our pattern parts. Their order will correspond to
	// the order of the date parts.
	
	var year = 0;
	var month = 0;
	var day = 0;
	var hours = 0;
	var minutes = 0;
	var seconds = 0;
	
	// We may encounter the meridiem. In which case, we know we're using a
	// 12 hour clock. We'll set this value to -1 if it's ante-meridiem, and
	// 1 if it's post-meridiem.
	
	var twelveHourClock = 0;
	
	// NOTE: dateParts is offset by one because the exec method returns an
	// array that looks something like this:
	//  
	//   ["10/23/2008", "10", "23", "2008"]
	// 
	// The first element is the entire match. The rest of the elements are
	// the subexpressions.
	
	for (var i = 0; i < patternParts.length && i < (dateParts.length - 1); i++)
	{
		
		var patternPart = patternParts[i];
		var datePart = dateParts[i + 1];
		var datePartValue = parseInt(datePart, 10);
		
		patternPart = patternPart.toUpperCase();
		
		if (patternPart == "YYYY")
		{
			year = datePartValue;
		}
		else if (patternPart == "YY")
		{
			year = Date.toFullYear(datePartValue);
		}	
		else if (patternPart == "MM" || patternPart == "M")
		{
			month = datePartValue;
		}
		else if (patternPart == "DD" || patternPart == "D")
		{
			day = datePartValue;
		}
		else if (patternPart == "HH" || patternPart == "H")
		{
			hours = datePartValue;
		}
		else if (patternPart == "NN" || patternPart == "N")
		{
			minutes = datePartValue;
		}
		else if (patternPart == "SS" || patternPart == "S")
		{
			seconds = datePartValue;
		}
		else if (patternPart == "T")
		{
			
			// Default to ante-meridiem just in case.
			
			twelveHourClock = -1;
			
			if (postMeridiem)
			{
				if (datePart.toUpperCase() == postMeridiem.toUpperCase())
				{
					twelveHourClock = 1;
				}
			}
			else if (anteMeridiem)
			{
				if (datePart.toUpperCase() == anteMeridiem.toUpperCase())
				{
					twelveHourClock = -1;
				}
			}
			
		}
		else
		{
			throw("Unknown date pattern " + patternPart + ".");
		}
		
	}
	
	// Months in JavaScript are zero based. If we didn't find a month, it
	// will still be set to zero.
	
	if (month != 0)
	{
		month--;
	}
	
	// Check to see if we need to adjust the hours to convert from a 12 hour
	// clock to a 24 hour clock
	
	if (twelveHourClock == -1)
	{
		
		// We have an AM time. 12 AM is 0 hundred hours on a 24 hour clock.
		
		if (hours == 12)
		{
			hours = 0;
		}
		
	}
	else if (twelveHourClock == 1)
	{
		
		// We have a PM time. 12 PM is 12 on a 24 hour clock. Otherwise, we
		// have to add 12 hours.
		
		if (hours != 12)
		{
			hours += 12;
		}
		
	}
	
	var date = DateTimePattern.createDateTime(year, month, day, hours,
			minutes, seconds);
	
	if (isNaN(date))
	{
		return;
	}
	
	return date;
	
};

// Takes a date and formats it given the specified pattern. Supports the
// following tokens:
// 
//   yyyy - 4 digit year
//   yy   - 2 digit year
//   mm   - Month with leading zeros
//   m    - Month without leading zeros.
//   dd   - Day with leading zeros
//   d    - Day without leading zeros.
//   hh   - Hours with leading zeros.
//    h   - Hours without leading zeros.
//   nn   - Alternate for minutes. If a string contains both a date and
//          and a time, use mm for months and nn for minutes.
//    n   - Minutes without a leading zero.
//   ss   - Seconds with leading zeros.
//    s   - Seconds without a leading zero.
//    t   - Meridiem (AM/PM).
// 
// Examples of date input include:
// 
//   {date}    m/d/yyyy
//   {date}    d/m/yyyy
//   {date}    dd.mm.yyyy
//   {date}    m/d/yy
//   {date}    yyyymmdd
//   {date}    yymmdd
//   {date}    ddmmyyyy
// 
// Examples of time inputs include:
// 
//   {date}    hh:nn:ss
//   {date}    h:nn:ss t     AM    PM
//   {date}    hhnnss
//   {date}    ssnnhh
// 
// Examples of possible date/time inputs include:
// 
//   {date}    m/d/yyyy hh:nn:ss t    AM    PM
//   {date}    d/m/yyyy hh:nn:ss t    AM    PM
//   {date}    dd.mm.yyyy HH:nn:ss
//   {date}    m/d/yy h:nn:ss t       AM    PM
//   {date}    yyyymmddhhnnss
//   {date}    yymmddhhnnss
//   {date}    ssnnhhddmmyyyy
// 
// The third and for parameters are the ante and post meridiems. They are
// required if the pattern contains t.

DateTimePattern.dateTimeToString = function(date, pattern, anteMeridiem,
		postMeridiem)
{
	
	// SAP null dates are a special case. They can't be stored in a
	// VBScript date object so they get converted to a null date. If we have
	// a null date and the pattern matches the SAP date pattern, we'll
	// assume that we should render the value as an SAP null date.
	
	if (DateTimePattern.isNullDate(date))
	{
		if (DateTimePattern.isSapDatePattern(pattern))
		{
			return DateTimePattern.SAP_NULL_DATE_VALUE;
		}
	}
	
	var string = pattern;
	
	// Replace a 4 digit year first.
	
	var year = new String(date.getFullYear());
	
	year = year.padLeft(4, "0");
	
	string = string.replace(/yyyy/ig, year);
	
	// Now look for a 2 digit year.
	
	year = year.substring(2, 4);
	
	string = string.replace(/yy/ig, year);
	
	// Month
	
	// NOTE: We have to add 1 because months in JavaScript are zero based.
	
	var month = new String(date.getMonth() + 1);
	
	var monthPadded = month.padLeft(2, "0");
	
	string = string.replace(/mm/ig, monthPadded);
	
	string = string.replace(/m/ig, month);
	
	// Day
	
	var day = new String(date.getDate());
	
	var dayPadded = day.padLeft(2, "0");
	
	string = string.replace(/dd/ig, dayPadded);
	
	string = string.replace(/d/ig, day);
	
	// If the string contains an ante-meridiem or a post-meridiem, then 
	// we're using a 12 hour clock.
	
	var useTwelveHourClock = false;
	
	if (pattern.toUpperCase().indexOf("T") != -1)
	{
		useTwelveHourClock = true;
	}
	
	// Hours
	
	var hours = date.getHours();
	
	// If we're using a 12 hour clock, we need to adjust our hours.
	
	var twelveHourClock = 0;
	
	if (useTwelveHourClock)
	{
		if (hours < 12)
		{
			
			// We have an AM time. 0 hundred hours is 12 AM.
			
			twelveHourClock = -1;
			
			if (hours == 0)
			{
				hours = 12;
			}
		}
		else
		{
			
			// We have a PM time. 12 PM is 12 hundred hours. Otherwise, hours
			// 13 through 23 need to drop by 12.
			
			twelveHourClock = 1;
			
			if (hours != 12)
			{
				hours -= 12;			
			}
			
		}
	}
	
	hours = new String(hours);
	
	var hoursPadded = hours.padLeft(2, "0");
	
	string = string.replace(/hh/ig, hoursPadded);
	
	string = string.replace(/h/ig, hours);
	
	// Minutes
	
	var minutes = new String(date.getMinutes());
	
	var minutesPadded = minutes.padLeft(2, "0");
	
	string = string.replace(/nn/ig, minutesPadded);
	
	string = string.replace(/n/ig, minutes);
	
	// Seconds
	
	var seconds = new String(date.getSeconds());
	
	seconds = seconds.padLeft(2, "0");
	
	string = string.replace(/ss/ig, seconds);
	
	// Meridiem
	
	// NOTE: We're dealing with the meridiem last because it may contain
	// characters that conflict with the other patterns.
	
	if (twelveHourClock == -1)
	{
		string = string.replace(/t/ig, anteMeridiem);
	}
	else if (twelveHourClock == 1)
	{
		string = string.replace(/t/ig, postMeridiem);
	}
	
	return string;
	
};


/******************************************************************************
 **********                          Helpers                         **********
 ******************************************************************************/

DateTimePattern.patternToRegEx = function(pattern, anteMeridiem,
		postMeridiem)
{
	
	// var expression = "(\\d+)";
	
	var expression = DateTimePattern.regExEscape(pattern);
	
	// Swap d for z since d represents the digit class.
	
	expression = expression.replace(/d/gi, "z");
	
	// Date parts.
	
	expression = expression.replace(/yyyy/gi, "(\\d{4,4})");
	expression = expression.replace(/yy/gi, "(\\d{2,2})");
	expression = expression.replace(/mm/gi, "(\\d{2,2})");
	expression = expression.replace(/m/gi, "(\\d{1,2})");
	expression = expression.replace(/zz/gi, "(\\d{2,2})");
	expression = expression.replace(/z/gi, "(\\d{1,2})");
	
	// Time parts.
	
	expression = expression.replace(/hh/gi, "(\\d{2,2})");
	expression = expression.replace(/h/gi, "(\\d{1,2})");
	expression = expression.replace(/nn/gi, "(\\d{2,2})");
	expression = expression.replace(/n/gi, "(\\d{1,2})");
	expression = expression.replace(/ss/gi, "(\\d{2,2})");
	expression = expression.replace(/s/gi, "(\\d{1,2})");
	
	// Because meridiems are arbitrary strings, we can only really match
	// them if we know what they are. Fortunately, there's only two
	// possibilities for any locale.
	
	if (anteMeridiem && postMeridiem)
	{
		
		var meridiems = DateTimePattern.meridiemsToRegExString(anteMeridiem,
				postMeridiem);
		
		// We're ignoring case to catch AM and am. Hopefully, there's no
		// locale whose meridiems differ by case.
		
		expression = expression.replace(/t/gi, meridiems);
		
	}
	
	// Don't allow for any other characters at the beginning or end of the
	// line.
	
	expression = "^" + expression + "$";
	
	var regEx = new RegExp(expression, "ig");
	
	return regEx;
	
};

DateTimePattern.meridiemsToRegExString = function(anteMeridiem,
		postMeridiem)
{
	var meridiems = "";
	
	meridiems += "(";
	meridiems += DateTimePattern.regExEscape(anteMeridiem)
	meridiems += "|"
	meridiems += DateTimePattern.regExEscape(postMeridiem);
	meridiems += "){1,1}";
	
	return meridiems;
	
};

DateTimePattern.regExEscape = function(expression)
{
	
	expression = expression.replace(/\\/gi, "\\\\");
	expression = expression.replace(/\./gi, "\\.");
	
	return expression;
	
};

// SAP uses 00010101 as a null date. This function will check a given
// pattern and value to see if they correspond to an SAP null date.

DateTimePattern.isSapNullDateString = function(string, pattern)
{
	
	if (string.matches(DateTimePattern.SAP_NULL_DATE_VALUE))
	{
		if (DateTimePattern.isSapDatePattern(pattern))
		{
			return true;
		}
	}
	
	return false;
	
};

// SAP uses YYYMMDD to store dates. This function will check a given
// pattern and see if it corresponds to an SAP date pattern.

DateTimePattern.isSapDatePattern = function(pattern)
{
	if (pattern.matches(DateTimePattern.SAP_DATE_PATTERN))
	{
		return true;
	}
	else
	{
		return false;
	}
};

// Checks a given year month and day to see if it corresponds to an SAP null
// date.

DateTimePattern.isSapNullDate = function(year, month, day)
{
	if (year == 1 && month == 0 && day == 1)
	{
		return true;
	}
	else
	{
		return false;
	}
};


// The range for a JavaScript date is -100000000 to 100000000 days. The
// oldest date is 86400000 * -100000000. This spans the oldest VBScript
// date, which is Jan 1st 100. Since we have to interoperate with VBScript
// on the server side, we'll use the VBScript null date.

DateTimePattern.getNullDate = function()
{
	return new Date(100, 0, 1);
};


DateTimePattern.isNullDate = function(date)
{
	if (date.getFullYear() == 100 && date.getMonth() == 0 &&
			date.getDate() == 1)
	{
		return true;
	}
	else
	{
		return false;
	}
};


DateTimePattern.SAP_NULL_DATE_VALUE = "00010101";
DateTimePattern.SAP_DATE_PATTERN = "yyyymmdd";


// Helper method for creating a date for a given year, month and day.
// Converts SAP null dates into a JavaScript null date.

DateTimePattern.createDateTime = function(year, month, day, hours, minutes,
		seconds)
{
	
	if (DateTimePattern.isSapNullDate(year, month, day))
	{
		return DateTimePattern.getNullDate();
	}
	
	return new Date(year, month, day, hours, minutes, seconds);
	
};