fullcalendar/src/util.js
2010-07-03 13:34:43 -07:00

582 lines
13 KiB
JavaScript

/* Date Math
-----------------------------------------------------------------------------*/
var DAY_MS = 86400000,
HOUR_MS = 3600000,
MINUTE_MS = 60000;
function addYears(d, n, keepTime) {
d.setFullYear(d.getFullYear() + n);
if (!keepTime) {
clearTime(d);
}
return d;
}
function addMonths(d, n, keepTime) { // prevents day overflow/underflow
if (+d) { // prevent infinite looping on invalid dates
var m = d.getMonth() + n,
check = cloneDate(d);
check.setDate(1);
check.setMonth(m);
d.setMonth(m);
if (!keepTime) {
clearTime(d);
}
while (d.getMonth() != check.getMonth()) {
d.setDate(d.getDate() + (d < check ? 1 : -1));
}
}
return d;
}
function addDays(d, n, keepTime) { // deals with daylight savings
if (+d) {
var dd = d.getDate() + n,
check = cloneDate(d);
check.setHours(9); // set to middle of day
check.setDate(dd);
d.setDate(dd);
if (!keepTime) {
clearTime(d);
}
fixDate(d, check);
}
return d;
}
fc.addDays = addDays;
function fixDate(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);
}
}
}
function addMinutes(d, n) {
d.setMinutes(d.getMinutes() + n);
return d;
}
function clearTime(d) {
d.setHours(0);
d.setMinutes(0);
d.setSeconds(0);
d.setMilliseconds(0);
return d;
}
function cloneDate(d, dontKeepTime) {
if (dontKeepTime) {
return clearTime(new Date(+d));
}
return new Date(+d);
}
fc.cloneDate = cloneDate;
function zeroDate() { // 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;
}
function skipWeekend(date, inc, excl) {
inc = inc || 1;
while (!date.getDay() || (excl && date.getDay()==1 || !excl && date.getDay()==6)) {
addDays(date, inc);
}
return date;
}
function dayDiff(d1, d2) { // d1 - d2
return Math.round((cloneDate(d1, true) - cloneDate(d2, true)) / DAY_MS);
}
/* Date Parsing
-----------------------------------------------------------------------------*/
var parseDate = fc.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 parseISO8601(s, true) || (s ? new Date(s) : null);
}
// TODO: never return invalid dates (like from new Date(<string>)), return null instead
return null;
};
var parseISO8601 = fc.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]);
}
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);
}
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));
};
var parseTime = fc.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
-----------------------------------------------------------------------------*/
var formatDate = fc.formatDate = function(date, format, options) {
return formatDates(date, null, format, options);
};
var formatDates = fc.formatDates = function(date1, date2, format, options) {
options = options || defaults;
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 = 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 = formatDate(date, subformat, options);
if (subres != 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 = dateFormatters[format.substring(i, i2)]) {
if (date) {
res += formatter(date, options);
}
i = i2 - 1;
break;
}
}
if (i2 == i) {
if (date) {
res += c;
}
}
}
}
return res;
};
var dateFormatters = {
s : function(d) { return d.getSeconds() },
ss : function(d) { return zeroPad(d.getSeconds()) },
m : function(d) { return d.getMinutes() },
mm : function(d) { return zeroPad(d.getMinutes()) },
h : function(d) { return d.getHours() % 12 || 12 },
hh : function(d) { return zeroPad(d.getHours() % 12 || 12) },
H : function(d) { return d.getHours() },
HH : function(d) { return zeroPad(d.getHours()) },
d : function(d) { return d.getDate() },
dd : function(d) { return 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 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 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';
}
};
/* Element Dimensions
-----------------------------------------------------------------------------*/
function setOuterWidth(element, width, includeMargins) {
element.each(function(i, _element) {
_element.style.width = width - hsides(_element, includeMargins) + 'px';
});
}
function setOuterHeight(element, height, includeMargins) {
element.each(function(i, _element) {
_element.style.height = height - vsides(_element, includeMargins) + 'px';
});
}
function hsides(_element, includeMargins) {
return (parseFloat(jQuery.curCSS(_element, 'paddingLeft', true)) || 0) +
(parseFloat(jQuery.curCSS(_element, 'paddingRight', true)) || 0) +
(parseFloat(jQuery.curCSS(_element, 'borderLeftWidth', true)) || 0) +
(parseFloat(jQuery.curCSS(_element, 'borderRightWidth', true)) || 0) +
(includeMargins ? hmargins(_element) : 0);
}
function hmargins(_element) {
return (parseFloat(jQuery.curCSS(_element, 'marginLeft', true)) || 0) +
(parseFloat(jQuery.curCSS(_element, 'marginRight', true)) || 0);
}
function vsides(_element, includeMargins) {
return (parseFloat(jQuery.curCSS(_element, 'paddingTop', true)) || 0) +
(parseFloat(jQuery.curCSS(_element, 'paddingBottom', true)) || 0) +
(parseFloat(jQuery.curCSS(_element, 'borderTopWidth', true)) || 0) +
(parseFloat(jQuery.curCSS(_element, 'borderBottomWidth', true)) || 0) +
(includeMargins ? vmargins(_element) : 0);
}
function vmargins(_element) {
return (parseFloat(jQuery.curCSS(_element, 'marginTop', true)) || 0) +
(parseFloat(jQuery.curCSS(_element, 'marginBottom', true)) || 0);
}
function setMinHeight(element, h) {
h = typeof h == 'number' ? h + 'px' : h;
element[0].style.cssText += ';min-height:' + h + ';_height:' + h;
}
/* Position Calculation
-----------------------------------------------------------------------------*/
// nasty bugs in opera 9.25
// position()'s top returning incorrectly with TR/TD or elements within TD
var topBug;
function topCorrect(tr) { // tr/th/td or anything else
if (topBug !== false) {
var cell;
if (tr.is('th,td')) {
tr = (cell = tr).parent();
}
if (topBug === undefined && tr.is('tr')) {
topBug = tr.position().top != tr.children().position().top;
}
if (topBug) {
return tr.parent().position().top + (cell ? tr.position().top - cell.position().top : 0);
}
}
return 0;
}
/* Coordinate Grid
-----------------------------------------------------------------------------*/
function CoordinateGrid(buildFunc) {
var t = this;
var rows;
var cols;
t.build = function() {
rows = [];
cols = [];
buildFunc(rows, cols);
};
t.cell = function(x, y) {
var rowCnt = rows.length;
var colCnt = cols.length;
var i, r=-1, c=-1;
for (i=0; i<rowCnt; i++) {
if (y >= rows[i][0] && y < rows[i][1]) {
r = i;
break;
}
}
for (i=0; i<colCnt; i++) {
if (x >= cols[i][0] && x < cols[i][1]) {
c = i;
break;
}
}
return (r>=0 && c>=0) ? { row:r, col:c } : null;
};
t.rect = function(row0, col0, row1, col1, originElement) { // row1,col1 is inclusive
var origin = originElement.offset();
return {
top: rows[row0][0] - origin.top,
left: cols[col0][0] - origin.left,
width: cols[col1][1] - cols[col0][0],
height: rows[row1][1] - rows[row0][0]
};
};
}
/* Hover Listener
-----------------------------------------------------------------------------*/
function HoverListener(coordinateGrid) {
var t = this;
var bindType;
var change;
var firstCell;
var cell;
t.start = function(_change, ev, _bindType) {
change = _change;
firstCell = cell = null;
coordinateGrid.build();
mouse(ev);
bindType = _bindType || 'mousemove';
$(document).bind(bindType, mouse);
};
function mouse(ev) {
var newCell = coordinateGrid.cell(ev.pageX, ev.pageY);
if (!newCell != !cell || newCell && (newCell.row != cell.row || newCell.col != cell.col)) {
if (newCell) {
if (!firstCell) {
firstCell = newCell;
}
change(newCell, firstCell, newCell.row-firstCell.row, newCell.col-firstCell.col);
}else{
change(newCell, firstCell);
}
cell = newCell;
}
}
t.stop = function() {
$(document).unbind(bindType, mouse);
return cell;
};
}
/* Misc Utils
-----------------------------------------------------------------------------*/
var dayIDs = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
function zeroPad(n) {
return (n < 10 ? '0' : '') + n;
}
function smartProperty(obj, name) { // get a camel-cased/namespaced property of an object
if (obj[name] !== undefined) {
return obj[name];
}
var parts = name.split(/(?=[A-Z])/),
i=parts.length-1, res;
for (; i>=0; i--) {
res = obj[parts[i].toLowerCase()];
if (res !== undefined) {
return res;
}
}
return obj[''];
}
function htmlEscape(s) {
return s
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/'/g, '&#039;')
.replace(/"/g, '&quot;');
}
function HorizontalPositionCache(getElement) {
var t = this,
elements = {},
lefts = {},
rights = {};
function e(i) {
return elements[i] = elements[i] || getElement(i);
}
t.left = function(i) {
return lefts[i] = lefts[i] === undefined ? e(i).position().left : lefts[i];
};
t.right = function(i) {
return rights[i] = rights[i] === undefined ? t.left(i) + e(i).width() : rights[i];
};
t.clear = function() {
elements = {};
lefts = {};
rights = {};
};
}
function cssKey(_element) {
return _element.id + '/' + _element.className + '/' + _element.style.cssText.replace(/(^|;)\s*(top|left|width|height)\s*:[^;]*/ig, '');
}
function cmp(a, b) {
return a - b;
}
function exclEndDay(event) {
if (event.end) {
return _exclEndDay(event.end, event.allDay);
}else{
return addDays(cloneDate(event.start), 1);
}
}
function _exclEndDay(end, allDay) {
end = cloneDate(end);
return allDay || end.getHours() || end.getMinutes() || end.getSeconds() ? addDays(end, 1) : end;
}
function disableTextSelection(element) {
element
.attr('unselectable', 'on')
.css('MozUserSelect', 'none')
.bind('selectstart.ui', function() { return false; });
}
/*
function enableTextSelection(element) {
element
.attr('unselectable', 'off')
.css('MozUserSelect', '')
.unbind('selectstart.ui');
}
*/