﻿Namespace("vc");

vc.DateTime = function() {
	// References
	var Char = vc.Char;

	var DateTime = new Class({
		statics: {
			addYears: function(d, n, keepTime) {
				d.setFullYear(d.getFullYear() + n);
				if (!keepTime) {
					DateTime.clearTime(d);
				}
				return d;
			},

			addMonths: function(d, n, keepTime) { // prevents day overflow/underflow
				if (+d) { // prevent infinite looping on invalid dates
					var m = d.getMonth() + n,
						check = DateTime.cloneDate(d);
					check.setDate(1);
					check.setMonth(m);
					d.setMonth(m);
					if (!keepTime) {
						DateTime.clearTime(d);
					}
					while (d.getMonth() != check.getMonth()) {
						d.setDate(d.getDate() + (d < check ? 1 : -1));
					}
				}
				return d;
			},

			addDays: function(d, n, keepTime) { // deals with daylight savings
				if (+d) {
					var dd = d.getDate() + n,
						check = DateTime.cloneDate(d);
					check.setHours(9); // set to middle of day
					check.setDate(dd);
					d.setDate(dd);
					if (!keepTime) {
						DateTime.clearTime(d);
					}
					DateTime.fixDate(d, check);
				}
				return d;
			},

			fixDate: function(d, check) { // force d to be on check's YMD, for daylight savings purposes
				if (+d) { // prevent infinite looping on invalid dates
					while (d.getDate() != check.getDate()) {
						d.setTime(+d + (d < check ? 1 : -1) * HOUR_MS);
					}
				}
			},

			addMinutes: function(d, n) {
				d.setMinutes(d.getMinutes() + n);
				return d;
			},

			addSeconds: function(d, n) {
				d.setSeconds(d.getSeconds() + n);
				return d;
			},

			addMilliseconds: function(d, n) {
				d.setMilliseconds(d.getMilliseconds() + n);
				return d;
			},

			clearTime: function(d) {
				d.setHours(0);
				d.setMinutes(0);
				d.setSeconds(0);
				d.setMilliseconds(0);
				return d;
			},

			cloneDate: function(d, dontKeepTime) {
				if (dontKeepTime) {
					return DateTime.clearTime(new Date(+d));
				}
				return new Date(+d);
			},

			zeroDate: function() { // returns a Date with time 00:00:00 and dateOfMonth=1
				var i = 0, d;
				do {
					d = new Date(1970, i++, 1);
				} while (d.getHours()); // != 0
				return d;
			},

			skipWeekend: function(date, inc, excl) {
				inc = inc || 1;
				while (!date.getDay() || (excl && date.getDay() == 1 || !excl && date.getDay() == 6)) {
					DateTime.addDays(date, inc);
				}
				return date;
			},

			dayDiff: function(d1, d2) { // d1 - d2
				return Math.round((DateTime.cloneDate(d1, true) - DateTime.cloneDate(d2, true)) / DAY_MS);
			},

			setYMD: function(date, y, m, d) {
				var mo = m - 1;
				if (y !== undefined && y != date.getFullYear()) {
					date.setDate(1);
					date.setMonth(0);
					date.setFullYear(y);
				}
				if (mo !== undefined && mo != date.getMonth()) {
					date.setDate(1);
					date.setMonth(mo);
				}
				if (d !== undefined) {
					date.setDate(d);
				}

				return date;
			},

			daysInMonth: function(year, month) {
				var d = new Date(year, month, 1); //setYMD(new Date(), year, month);
				DateTime.addMonths(d, 1);
				DateTime.addDays(d, -1);
				return d.getDate();
			},

			/* Date Parsing
			-----------------------------------------------------------------------------*/

			parseDate: function(s) {
				if (typeof s == 'object') { // already a Date object
					return s;
				}
				if (typeof s == 'number') { // a UNIX timestamp
					return new Date(s * 1000);
				}
				if (typeof s == 'string') {
					if (s.match(/^\d+$/)) { // a UNIX timestamp
						return new Date(parseInt(s) * 1000);
					}
					return DateTime.parseISO8601(s, true) || (s ? new Date(s) : null);
				}
				// TODO: never return invalid dates (like from new Date(<string>)), return null instead
				return null;
			},

			parseISO8601: function(s, ignoreTimezone) {
				// derived from http://delete.me.uk/2005/03/iso8601.html
				// TODO: for a know glitch/feature, read tests/issue_206_parseDate_dst.html
				var m = s.match(/^([0-9]{4})(-([0-9]{2})(-([0-9]{2})([T ]([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?$/);
				if (!m) {
					return null;
				}
				var date = new Date(m[1], 0, 1),
				check = new Date(m[1], 0, 1, 9, 0),
				offset = 0;
				if (m[3]) {
					date.setMonth(m[3] - 1);
					check.setMonth(m[3] - 1);
				}
				if (m[5]) {
					date.setDate(m[5]);
					check.setDate(m[5]);
				}
				DateTime.fixDate(date, check);
				if (m[7]) {
					date.setHours(m[7]);
				}
				if (m[8]) {
					date.setMinutes(m[8]);
				}
				if (m[10]) {
					date.setSeconds(m[10]);
				}
				if (m[12]) {
					date.setMilliseconds(Number("0." + m[12]) * 1000);
				}
				DateTime.fixDate(date, check);
				if (!ignoreTimezone) {
					if (m[14]) {
						offset = Number(m[16]) * 60 + Number(m[17]);
						offset *= m[15] == '-' ? 1 : -1;
					}
					offset -= date.getTimezoneOffset();
				}
				return new Date(+date + (offset * 60 * 1000));
			},

			parseTime: function(s) { // returns minutes since start of day
				if (typeof s == 'number') { // an hour
					return s * 60;
				}
				if (typeof s == 'object') { // a Date object
					return s.getHours() * 60 + s.getMinutes();
				}
				var m = s.match(/(\d+)(?::(\d+))?\s*(\w+)?/);
				if (m) {
					var h = parseInt(m[1]);
					if (m[3]) {
						h %= 12;
						if (m[3].toLowerCase().charAt(0) == 'p') {
							h += 12;
						}
					}
					return h * 60 + (m[2] ? parseInt(m[2]) : 0);
				}
			},

			/* Date Formatting
			-----------------------------------------------------------------------------*/

			formatDate: function(date, format, options) {
				return DateTime.formatDates(date, null, format, options);
			},

			formatDates: function(date1, date2, format, options) {
				options = options || DateTime.config;
				var date = date1,
				otherDate = date2,
				i, len = format.length, c,
				i2, formatter,
				res = '';
				for (i = 0; i < len; i++) {
					c = format.charAt(i);
					if (c == "'") {
						for (i2 = i + 1; i2 < len; i2++) {
							if (format.charAt(i2) == "'") {
								if (date) {
									if (i2 == i + 1) {
										res += "'";
									} else {
										res += format.substring(i + 1, i2);
									}
									i = i2;
								}
								break;
							}
						}
					}
					else if (c == '(') {
						for (i2 = i + 1; i2 < len; i2++) {
							if (format.charAt(i2) == ')') {
								var subres = DateTime.formatDate(date, format.substring(i + 1, i2), options);
								if (parseInt(subres.replace(/\D/, ''))) {
									res += subres;
								}
								i = i2;
								break;
							}
						}
					}
					else if (c == '[') {
						for (i2 = i + 1; i2 < len; i2++) {
							if (format.charAt(i2) == ']') {
								var subformat = format.substring(i + 1, i2);
								var subres = DateTime.formatDate(date, subformat, options);
								if (subres != DateTime.formatDate(otherDate, subformat, options)) {
									res += subres;
								}
								i = i2;
								break;
							}
						}
					}
					else if (c == '{') {
						date = date2;
						otherDate = date1;
					}
					else if (c == '}') {
						date = date1;
						otherDate = date2;
					}
					else {
						for (i2 = len; i2 > i; i2--) {
							if (formatter = DateTime.dateFormatters[format.substring(i, i2)]) {
								if (date) {
									res += formatter(date, options);
								}
								i = i2 - 1;
								break;
							}
						}
						if (i2 == i) {
							if (date) {
								res += c;
							}
						}
					}
				}
				return res;
			},

			dateFormatters: {
				s: function(d) { return d.getSeconds() },
				ss: function(d) { return DateTime.zeroPad(d.getSeconds()) },
				m: function(d) { return d.getMinutes() },
				mm: function(d) { return DateTime.zeroPad(d.getMinutes()) },
				h: function(d) { return d.getHours() % 12 || 12 },
				hh: function(d) { return DateTime.zeroPad(d.getHours() % 12 || 12) },
				H: function(d) { return d.getHours() },
				HH: function(d) { return DateTime.zeroPad(d.getHours()) },
				d: function(d) { return d.getDate() },
				dd: function(d) { return DateTime.zeroPad(d.getDate()) },
				ddd: function(d, o) { return o.dayNamesShort[d.getDay()] },
				dddd: function(d, o) { return o.dayNames[d.getDay()] },
				M: function(d) { return d.getMonth() + 1 },
				MM: function(d) { return DateTime.zeroPad(d.getMonth() + 1) },
				MMM: function(d, o) { return o.monthNamesShort[d.getMonth()] },
				MMMM: function(d, o) { return o.monthNames[d.getMonth()] },
				yy: function(d) { return (d.getFullYear() + '').substring(2) },
				yyyy: function(d) { return d.getFullYear() },
				t: function(d) { return d.getHours() < 12 ? 'a' : 'p' },
				tt: function(d) { return d.getHours() < 12 ? 'am' : 'pm' },
				T: function(d) { return d.getHours() < 12 ? 'A' : 'P' },
				TT: function(d) { return d.getHours() < 12 ? 'AM' : 'PM' },
				u: function(d) { return DateTime.formatDate(d, "yyyy-MM-dd'T'HH:mm:ss'Z'") },
				S: function(d) {
					var date = d.getDate();
					if (date > 10 && date < 20) {
						return 'th';
					}
					return ['st', 'nd', 'rd'][date % 10 - 1] || 'th';
				}
			},

			dayOfWeekToString: function(dayOfWeek, dayOfWeekFormat) {
				var i,
					s = "",
					formatLength = dayOfWeekFormat.length,
					baseDay = DateTime.addDays(DateTime.setYMD(new Date(), 2010, 8, 1), dayOfWeek),
					longDayName = DateTime.config.dayNames[baseDay.getDay()],
					longDayNameLength = longDayName.length;

				if (dayOfWeekFormat == "ddd")
					return DateTime.config.dayNamesShort[baseDay.getDay()];
				else if (dayOfWeekFormat == "dddd")
					return longDayName;

				if (formatLength <= 3) {
					// For this conversion we don't care what actual letters were 
					// specified in the dayOfWeekFormat, only if they are upper or 
					// lower case
					for (i = 0; i < formatLength; i++) {
						if (i >= longDayNameLength)
							break;

						var ch = dayOfWeekFormat[i];

						if (!vc.IsLetter(ch))
							throw new Error("dayOfWeekFormat does not contain a valid format pattern.");

						if (vc.IsLower(ch))
							s += ch.toLowerCase();
						else
							s += ch.toUpperCase();
					}
				}
				else
					return longDayName;

				return s;
			},

			isToday: function(date) {
				var today = new Date();
				return (date.getFullYear() == today.getFullYear()) && (date.getMonth() == today.getMonth()) && (date.getDate() == today.getDate());
			},

			zeroPad: function(n) {
				return (n < 10 ? '0' : '') + n;
			},

			now: function() {
				return new Date();
			},

			today: function() {
				var now = new Date();
				return new Date(now.getFullYear(), now.getMonth(), now.getDate());
			},

			config: {},
			setConfig: function(newConfig) {
				DateTime.config = newConfig;
			}
		},
		constructor: function(config) {
			debugger;
			this.config = config;
			this.dateFormatters = DateTime.dateFormatters;
		},

		// Public
		config: {},

		addYears: function(d, n, keepTime) {
			return DateTime.addYears(d, n, keepTime);
		},

		addMonths: function(d, n, keepTime) { // prevents day overflow/underflow
			return DateTime.addMonths(d, n, keepTime);
		},

		addDays: function(d, n, keepTime) { // deals with daylight savings
			return DateTime.addDays(d, n, keepTime);
		},

		fixDate: function(d, check) { // force d to be on check's YMD, for daylight savings purposes
			DateTime.fixDate(d, check);
		},

		addMinutes: function(d, n) {
			return DateTime.addMinutes(d, n);
		},

		addSeconds: function(d, n) {
			return DateTime.addSeconds(d, n);
		},

		addMilliseconds: function(d, n) {
			return DateTime.addMilliseconds(d, n);
		},

		clearTime: function(d) {
			return DateTime.clearTime(d);
		},

		cloneDate: function(d, dontKeepTime) {
			return DateTime.cloneDate(d, dontKeepTime);
		},

		zeroDate: function() { // returns a Date with time 00:00:00 and dateOfMonth=1
			return DateTime.zeroDate();
		},

		skipWeekend: function(date, inc, excl) {
			return DateTime.skipWeekend(date, inc, excl);
		},

		dayDiff: function(d1, d2) { // d1 - d2
			return DateTime.dayDiff(d1, d2);
		},

		setYMD: function(date, y, m, d) {
			return DateTime.setYMD(date, y, m, d);
		},

		daysInMonth: function(year, month) {
			return DateTime.daysInMonth(year, month);
		},

		/* Date Parsing
		-----------------------------------------------------------------------------*/

		parseDate: function(s) {
			return DateTime.parseDate(s);
		},

		parseISO8601: function(s, ignoreTimezone) {
			return DateTime.pasrseISO8601(s, ignoreTimezone);
		},

		parseTime: function(s) {
			return DateTime.parseTime(s);
		},

		/* Date Formatting
		-----------------------------------------------------------------------------*/

		formatDate: function(date, format, options) {
			return DateTime.formatDate(date, format, options);
		},

		formatDates: function(date1, date2, format, options) {
			DateTime.formatDates(date1, date2, format, options);
		},

		dateFormatters: {},

		dayOfWeekToString: function(dayOfWeek, dayOfWeekFormat) {
			var i,
				s = "",
				formatLength = dayOfWeekFormat.length,
				baseDay = addDays(setYMD(new Date(), 2010, 8, 1), dayOfWeek),
				longDayName = DateTime.config.dayNames[baseDay.getDay()],
				longDayNameLength = longDayName.length;

			if (dayOfWeekFormat == "ddd")
				return DateTime.config.dayNamesShort[baseDay.getDay()];
			else if (dayOfWeekFormat == "dddd")
				return longDayName;

			if (formatLength <= 3) {
				// For this conversion we don't care what actual letters were 
				// specified in the dayOfWeekFormat, only if they are upper or 
				// lower case
				for (i = 0; i < formatLength; i++) {
					if (i >= longDayNameLength)
						break;

					var ch = dayOfWeekFormat[i];

					if (!Char.IsLetter(ch))
						throw new Error("dayOfWeekFormat does not contain a valid format pattern.");

					if (Char.IsLower(ch))
						s += ch.toLowerCase();
					else
						s += ch.toUpperCase();
				}
			}
			else
				return longDayName;

			return s;
		},

		isToday: function(date) {
			return DateTime.isToday(date);
		},

		now: function() {
			return DateTime.now();
		},

		today: function() {
			return DateTime.today();
		}
	});

	return DateTime;
} ();
