/*
	Copyright (c) 2004-2006, The Dojo Foundation
	All Rights Reserved.

	Licensed under the Academic Free License version 2.1 or above OR the
	modified BSD license. For more information on Dojo licensing, see:

		http://dojotoolkit.org/community/licensing.shtml
*/

dojo.provide("dojo.date.format");
dojo.require("dojo.date.common");
dojo.require("dojo.date.supplemental");
dojo.require("dojo.lang.array");
dojo.require("dojo.lang.common");
dojo.require("dojo.lang.func");
dojo.require("dojo.string.common");
dojo.require("dojo.i18n.common");
dojo.requireLocalization("dojo.i18n.calendar", "gregorian", null, "de,en,es,fi,fr,hu,ja,it,ko,nl,pt,sv,zh,pt-br,zh-cn,zh-hk,zh-tw,ROOT");
dojo.requireLocalization("dojo.i18n.calendar", "gregorianExtras", null, "ja,zh,ROOT");
(function () {
	dojo.date.format = function (dateObject, options) {
		if (typeof options == "string") {
			dojo.deprecated("dojo.date.format", "To format dates with POSIX-style strings, please use dojo.date.strftime instead", "0.5");
			return dojo.date.strftime(dateObject, options);
		}
		function formatPattern(dateObject, pattern) {
			return pattern.replace(/([a-z])\1*/ig, function (match) {
				var s;
				var c = match.charAt(0);
				var l = match.length;
				var pad;
				var widthList = ["abbr", "wide", "narrow"];
				switch (c) {
				  case "G":
					if (l > 3) {
						dojo.unimplemented("Era format not implemented");
					}
					s = info.eras[dateObject.getFullYear() < 0 ? 1 : 0];
					break;
				  case "y":
					s = dateObject.getFullYear();
					switch (l) {
					  case 1:
						break;
					  case 2:
						s = String(s).substr(-2);
						break;
					  default:
						pad = true;
					}
					break;
				  case "Q":
				  case "q":
					s = Math.ceil((dateObject.getMonth() + 1) / 3);
					switch (l) {
					  case 1:
					  case 2:
						pad = true;
						break;
					  case 3:
					  case 4:
						dojo.unimplemented("Quarter format not implemented");
					}
					break;
				  case "M":
				  case "L":
					var m = dateObject.getMonth();
					var width;
					switch (l) {
					  case 1:
					  case 2:
						s = m + 1;
						pad = true;
						break;
					  case 3:
					  case 4:
					  case 5:
						width = widthList[l - 3];
						break;
					}
					if (width) {
						var type = (c == "L") ? "standalone" : "format";
						var prop = ["months", type, width].join("-");
						s = info[prop][m];
					}
					break;
				  case "w":
					var firstDay = 0;
					s = dojo.date.getWeekOfYear(dateObject, firstDay);
					pad = true;
					break;
				  case "d":
					s = dateObject.getDate();
					pad = true;
					break;
				  case "D":
					s = dojo.date.getDayOfYear(dateObject);
					pad = true;
					break;
				  case "E":
				  case "e":
				  case "c":
					var d = dateObject.getDay();
					var width;
					switch (l) {
					  case 1:
					  case 2:
						if (c == "e") {
							var first = dojo.date.getFirstDayOfWeek(options.locale);
							d = (d - first + 7) % 7;
						}
						if (c != "c") {
							s = d + 1;
							pad = true;
							break;
						}
					  case 3:
					  case 4:
					  case 5:
						width = widthList[l - 3];
						break;
					}
					if (width) {
						var type = (c == "c") ? "standalone" : "format";
						var prop = ["days", type, width].join("-");
						s = info[prop][d];
					}
					break;
				  case "a":
					var timePeriod = (dateObject.getHours() < 12) ? "am" : "pm";
					s = info[timePeriod];
					break;
				  case "h":
				  case "H":
				  case "K":
				  case "k":
					var h = dateObject.getHours();
					switch (c) {
					  case "h":
						s = (h % 12) || 12;
						break;
					  case "H":
						s = h;
						break;
					  case "K":
						s = (h % 12);
						break;
					  case "k":
						s = h || 24;
						break;
					}
					pad = true;
					break;
				  case "m":
					s = dateObject.getMinutes();
					pad = true;
					break;
				  case "s":
					s = dateObject.getSeconds();
					pad = true;
					break;
				  case "S":
					s = Math.round(dateObject.getMilliseconds() * Math.pow(10, l - 3));
					break;
				  case "v":
				  case "z":
					s = dojo.date.getTimezoneName(dateObject);
					if (s) {
						break;
					}
					l = 4;
				  case "Z":
					var offset = dateObject.getTimezoneOffset();
					var tz = [(offset <= 0 ? "+" : "-"), dojo.string.pad(Math.floor(Math.abs(offset) / 60), 2), dojo.string.pad(Math.abs(offset) % 60, 2)];
					if (l == 4) {
						tz.splice(0, 0, "GMT");
						tz.splice(3, 0, ":");
					}
					s = tz.join("");
					break;
				  case "Y":
				  case "u":
				  case "W":
				  case "F":
				  case "g":
				  case "A":
					dojo.debug(match + " modifier not yet implemented");
					s = "?";
					break;
				  default:
					dojo.raise("dojo.date.format: invalid pattern char: " + pattern);
				}
				if (pad) {
					s = dojo.string.pad(s, l);
				}
				return s;
			});
		}
		options = options || {};
		var locale = dojo.hostenv.normalizeLocale(options.locale);
		var formatLength = options.formatLength || "full";
		var info = dojo.date._getGregorianBundle(locale);
		var str = [];
		var sauce = dojo.lang.curry(this, formatPattern, dateObject);
		if (options.selector != "timeOnly") {
			var datePattern = options.datePattern || info["dateFormat-" + formatLength];
			if (datePattern) {
				str.push(_processPattern(datePattern, sauce));
			}
		}
		if (options.selector != "dateOnly") {
			var timePattern = options.timePattern || info["timeFormat-" + formatLength];
			if (timePattern) {
				str.push(_processPattern(timePattern, sauce));
			}
		}
		var result = str.join(" ");
		return result;
	};
	dojo.date.parse = function (value, options) {
		options = options || {};
		var locale = dojo.hostenv.normalizeLocale(options.locale);
		var info = dojo.date._getGregorianBundle(locale);
		var formatLength = options.formatLength || "full";
		if (!options.selector) {
			options.selector = "dateOnly";
		}
		var datePattern = options.datePattern || info["dateFormat-" + formatLength];
		var timePattern = options.timePattern || info["timeFormat-" + formatLength];
		var pattern;
		if (options.selector == "dateOnly") {
			pattern = datePattern;
		} else {
			if (options.selector == "timeOnly") {
				pattern = timePattern;
			} else {
				if (options.selector == "dateTime") {
					pattern = datePattern + " " + timePattern;
				} else {
					var msg = "dojo.date.parse: Unknown selector param passed: '" + options.selector + "'.";
					msg += " Defaulting to date pattern.";
					dojo.debug(msg);
					pattern = datePattern;
				}
			}
		}
		var groups = [];
		var dateREString = _processPattern(pattern, dojo.lang.curry(this, _buildDateTimeRE, groups, info, options));
		var dateRE = new RegExp("^" + dateREString + "$");
		var match = dateRE.exec(value);
		if (!match) {
			return null;
		}
		var widthList = ["abbr", "wide", "narrow"];
		var result = new Date(1972, 0);
		var expected = {};
		for (var i = 1; i < match.length; i++) {
			var grp = groups[i - 1];
			var l = grp.length;
			var v = match[i];
			switch (grp.charAt(0)) {
			  case "y":
				if (l != 2) {
					result.setFullYear(v);
					expected.year = v;
				} else {
					if (v < 100) {
						v = Number(v);
						var year = "" + new Date().getFullYear();
						var century = year.substring(0, 2) * 100;
						var yearPart = Number(year.substring(2, 4));
						var cutoff = Math.min(yearPart + 20, 99);
						var num = (v < cutoff) ? century + v : century - 100 + v;
						result.setFullYear(num);
						expected.year = num;
					} else {
						if (options.strict) {
							return null;
						}
						result.setFullYear(v);
						expected.year = v;
					}
				}
				break;
			  case "M":
				if (l > 2) {
					if (!options.strict) {
						v = v.replace(/\./g, "");
						v = v.toLowerCase();
					}
					var months = info["months-format-" + widthList[l - 3]].concat();
					for (var j = 0; j < months.length; j++) {
						if (!options.strict) {
							months[j] = months[j].toLowerCase();
						}
						if (v == months[j]) {
							result.setMonth(j);
							expected.month = j;
							break;
						}
					}
					if (j == months.length) {
						dojo.debug("dojo.date.parse: Could not parse month name: '" + v + "'.");
						return null;
					}
				} else {
					result.setMonth(v - 1);
					expected.month = v - 1;
				}
				break;
			  case "E":
			  case "e":
				if (!options.strict) {
					v = v.toLowerCase();
				}
				var days = info["days-format-" + widthList[l - 3]].concat();
				for (var j = 0; j < days.length; j++) {
					if (!options.strict) {
						days[j] = days[j].toLowerCase();
					}
					if (v == days[j]) {
						break;
					}
				}
				if (j == days.length) {
					dojo.debug("dojo.date.parse: Could not parse weekday name: '" + v + "'.");
					return null;
				}
				break;
			  case "d":
				result.setDate(v);
				expected.date = v;
				break;
			  case "a":
				var am = options.am || info.am;
				var pm = options.pm || info.pm;
				if (!options.strict) {
					v = v.replace(/\./g, "").toLowerCase();
					am = am.replace(/\./g, "").toLowerCase();
					pm = pm.replace(/\./g, "").toLowerCase();
				}
				if (options.strict && v != am && v != pm) {
					dojo.debug("dojo.date.parse: Could not parse am/pm part.");
					return null;
				}
				var hours = result.getHours();
				if (v == pm && hours < 12) {
					result.setHours(hours + 12);
				} else {
					if (v == am && hours == 12) {
						result.setHours(0);
					}
				}
				break;
			  case "K":
				if (v == 24) {
					v = 0;
				}
			  case "h":
			  case "H":
			  case "k":
				if (v > 23) {
					dojo.debug("dojo.date.parse: Illegal hours value");
					return null;
				}
				result.setHours(v);
				break;
			  case "m":
				result.setMinutes(v);
				break;
			  case "s":
				result.setSeconds(v);
				break;
			  case "S":
				result.setMilliseconds(v);
				break;
			  default:
				dojo.unimplemented("dojo.date.parse: unsupported pattern char=" + grp.charAt(0));
			}
		}
		if (expected.year && result.getFullYear() != expected.year) {
			dojo.debug("Parsed year: '" + result.getFullYear() + "' did not match input year: '" + expected.year + "'.");
			return null;
		}
		if (expected.month && result.getMonth() != expected.month) {
			dojo.debug("Parsed month: '" + result.getMonth() + "' did not match input month: '" + expected.month + "'.");
			return null;
		}
		if (expected.date && result.getDate() != expected.date) {
			dojo.debug("Parsed day of month: '" + result.getDate() + "' did not match input day of month: '" + expected.date + "'.");
			return null;
		}
		return result;
	};
	function _processPattern(pattern, applyPattern, applyLiteral, applyAll) {
		var identity = function (x) {
			return x;
		};
		applyPattern = applyPattern || identity;
		applyLiteral = applyLiteral || identity;
		applyAll = applyAll || identity;
		var chunks = pattern.match(/(''|[^'])+/g);
		var literal = false;
		for (var i = 0; i < chunks.length; i++) {
			if (!chunks[i]) {
				chunks[i] = "";
			} else {
				chunks[i] = (literal ? applyLiteral : applyPattern)(chunks[i]);
				literal = !literal;
			}
		}
		return applyAll(chunks.join(""));
	}
	function _buildDateTimeRE(groups, info, options, pattern) {
		return pattern.replace(/([a-z])\1*/ig, function (match) {
			var s;
			var c = match.charAt(0);
			var l = match.length;
			switch (c) {
			  case "y":
				s = "\\d" + ((l == 2) ? "{2,4}" : "+");
				break;
			  case "M":
				s = (l > 2) ? "\\S+" : "\\d{1,2}";
				break;
			  case "d":
				s = "\\d{1,2}";
				break;
			  case "E":
				s = "\\S+";
				break;
			  case "h":
			  case "H":
			  case "K":
			  case "k":
				s = "\\d{1,2}";
				break;
			  case "m":
			  case "s":
				s = "[0-5]\\d";
				break;
			  case "S":
				s = "\\d{1,3}";
				break;
			  case "a":
				var am = options.am || info.am || "AM";
				var pm = options.pm || info.pm || "PM";
				if (options.strict) {
					s = am + "|" + pm;
				} else {
					s = am;
					s += (am != am.toLowerCase()) ? "|" + am.toLowerCase() : "";
					s += "|";
					s += (pm != pm.toLowerCase()) ? pm + "|" + pm.toLowerCase() : pm;
				}
				break;
			  default:
				dojo.unimplemented("parse of date format, pattern=" + pattern);
			}
			if (groups) {
				groups.push(match);
			}
			return "\\s*(" + s + ")\\s*";
		});
	}
})();
dojo.date.strftime = function (dateObject, format, locale) {
	var padChar = null;
	function _(s, n) {
		return dojo.string.pad(s, n || 2, padChar || "0");
	}
	var info = dojo.date._getGregorianBundle(locale);
	function $(property) {
		switch (property) {
		  case "a":
			return dojo.date.getDayShortName(dateObject, locale);
		  case "A":
			return dojo.date.getDayName(dateObject, locale);
		  case "b":
		  case "h":
			return dojo.date.getMonthShortName(dateObject, locale);
		  case "B":
			return dojo.date.getMonthName(dateObject, locale);
		  case "c":
			return dojo.date.format(dateObject, {locale:locale});
		  case "C":
			return _(Math.floor(dateObject.getFullYear() / 100));
		  case "d":
			return _(dateObject.getDate());
		  case "D":
			return $("m") + "/" + $("d") + "/" + $("y");
		  case "e":
			if (padChar == null) {
				padChar = " ";
			}
			return _(dateObject.getDate());
		  case "f":
			if (padChar == null) {
				padChar = " ";
			}
			return _(dateObject.getMonth() + 1);
		  case "g":
			break;
		  case "G":
			dojo.unimplemented("unimplemented modifier 'G'");
			break;
		  case "F":
			return $("Y") + "-" + $("m") + "-" + $("d");
		  case "H":
			return _(dateObject.getHours());
		  case "I":
			return _(dateObject.getHours() % 12 || 12);
		  case "j":
			return _(dojo.date.getDayOfYear(dateObject), 3);
		  case "k":
			if (padChar == null) {
				padChar = " ";
			}
			return _(dateObject.getHours());
		  case "l":
			if (padChar == null) {
				padChar = " ";
			}
			return _(dateObject.getHours() % 12 || 12);
		  case "m":
			return _(dateObject.getMonth() + 1);
		  case "M":
			return _(dateObject.getMinutes());
		  case "n":
			return "\n";
		  case "p":
			return info[dateObject.getHours() < 12 ? "am" : "pm"];
		  case "r":
			return $("I") + ":" + $("M") + ":" + $("S") + " " + $("p");
		  case "R":
			return $("H") + ":" + $("M");
		  case "S":
			return _(dateObject.getSeconds());
		  case "t":
			return "\t";
		  case "T":
			return $("H") + ":" + $("M") + ":" + $("S");
		  case "u":
			return String(dateObject.getDay() || 7);
		  case "U":
			return _(dojo.date.getWeekOfYear(dateObject));
		  case "V":
			return _(dojo.date.getIsoWeekOfYear(dateObject));
		  case "W":
			return _(dojo.date.getWeekOfYear(dateObject, 1));
		  case "w":
			return String(dateObject.getDay());
		  case "x":
			return dojo.date.format(dateObject, {selector:"dateOnly", locale:locale});
		  case "X":
			return dojo.date.format(dateObject, {selector:"timeOnly", locale:locale});
		  case "y":
			return _(dateObject.getFullYear() % 100);
		  case "Y":
			return String(dateObject.getFullYear());
		  case "z":
			var timezoneOffset = dateObject.getTimezoneOffset();
			return (timezoneOffset > 0 ? "-" : "+") + _(Math.floor(Math.abs(timezoneOffset) / 60)) + ":" + _(Math.abs(timezoneOffset) % 60);
		  case "Z":
			return dojo.date.getTimezoneName(dateObject);
		  case "%":
			return "%";
		}
	}
	var string = "";
	var i = 0;
	var index = 0;
	var switchCase = null;
	while ((index = format.indexOf("%", i)) != -1) {
		string += format.substring(i, index++);
		switch (format.charAt(index++)) {
		  case "_":
			padChar = " ";
			break;
		  case "-":
			padChar = "";
			break;
		  case "0":
			padChar = "0";
			break;
		  case "^":
			switchCase = "upper";
			break;
		  case "*":
			switchCase = "lower";
			break;
		  case "#":
			switchCase = "swap";
			break;
		  default:
			padChar = null;
			index--;
			break;
		}
		var property = $(format.charAt(index++));
		switch (switchCase) {
		  case "upper":
			property = property.toUpperCase();
			break;
		  case "lower":
			property = property.toLowerCase();
			break;
		  case "swap":
			var compareString = property.toLowerCase();
			var swapString = "";
			var j = 0;
			var ch = "";
			while (j < property.length) {
				ch = property.charAt(j);
				swapString += (ch == compareString.charAt(j)) ? ch.toUpperCase() : ch.toLowerCase();
				j++;
			}
			property = swapString;
			break;
		  default:
			break;
		}
		switchCase = null;
		string += property;
		i = index;
	}
	string += format.substring(i);
	return string;
};
(function () {
	var _customFormats = [];
	dojo.date.addCustomFormats = function (packageName, bundleName) {
		_customFormats.push({pkg:packageName, name:bundleName});
	};
	dojo.date._getGregorianBundle = function (locale) {
		var gregorian = {};
		dojo.lang.forEach(_customFormats, function (desc) {
			var bundle = dojo.i18n.getLocalization(desc.pkg, desc.name, locale);
			gregorian = dojo.lang.mixin(gregorian, bundle);
		}, this);
		return gregorian;
	};
})();
dojo.date.addCustomFormats("dojo.i18n.calendar", "gregorian");
dojo.date.addCustomFormats("dojo.i18n.calendar", "gregorianExtras");
dojo.date.getNames = function (item, type, use, locale) {
	var label;
	var lookup = dojo.date._getGregorianBundle(locale);
	var props = [item, use, type];
	if (use == "standAlone") {
		label = lookup[props.join("-")];
	}
	props[1] = "format";
	return (label || lookup[props.join("-")]).concat();
};
dojo.date.getDayName = function (dateObject, locale) {
	return dojo.date.getNames("days", "wide", "format", locale)[dateObject.getDay()];
};
dojo.date.getDayShortName = function (dateObject, locale) {
	return dojo.date.getNames("days", "abbr", "format", locale)[dateObject.getDay()];
};
dojo.date.getMonthName = function (dateObject, locale) {
	return dojo.date.getNames("months", "wide", "format", locale)[dateObject.getMonth()];
};
dojo.date.getMonthShortName = function (dateObject, locale) {
	return dojo.date.getNames("months", "abbr", "format", locale)[dateObject.getMonth()];
};
dojo.date.toRelativeString = function (dateObject) {
	var now = new Date();
	var diff = (now - dateObject) / 1000;
	var end = " ago";
	var future = false;
	if (diff < 0) {
		future = true;
		end = " from now";
		diff = -diff;
	}
	if (diff < 60) {
		diff = Math.round(diff);
		return diff + " second" + (diff == 1 ? "" : "s") + end;
	}
	if (diff < 60 * 60) {
		diff = Math.round(diff / 60);
		return diff + " minute" + (diff == 1 ? "" : "s") + end;
	}
	if (diff < 60 * 60 * 24) {
		diff = Math.round(diff / 3600);
		return diff + " hour" + (diff == 1 ? "" : "s") + end;
	}
	if (diff < 60 * 60 * 24 * 7) {
		diff = Math.round(diff / (3600 * 24));
		if (diff == 1) {
			return future ? "Tomorrow" : "Yesterday";
		} else {
			return diff + " days" + end;
		}
	}
	return dojo.date.format(dateObject);
};
dojo.date.toSql = function (dateObject, noTime) {
	return dojo.date.strftime(dateObject, "%F" + !noTime ? " %T" : "");
};
dojo.date.fromSql = function (sqlDate) {
	var parts = sqlDate.split(/[\- :]/g);
	while (parts.length < 6) {
		parts.push(0);
	}
	return new Date(parts[0], (parseInt(parts[1], 10) - 1), parts[2], parts[3], parts[4], parts[5]);
};

