fullcalendar/src/mightydate.js

796 lines
18 KiB
JavaScript

/**
* @preserve MightyDate v0.4
* http://github.com/arshaw/mightydate
*
* Copyright 2011, Adam Shaw
* Dual licensed under the MIT or GPL Version 2 licenses.
*
* Date: Sun May 8 00:19:07 2011 -0700
*/
var MightyDate = (function(Date, undefined) {
/** @const */ var FULLYEAR_INDEX = 0;
/** @const */ var MONTH_INDEX = 1;
/** @const */ var DATE_INDEX = 2;
/** @const */ var HOURS_INDEX = 3;
/** @const */ var MINUTES_INDEX = 4;
/** @const */ var SECONDS_INDEX = 5;
/** @const */ var MILLISECONDS_INDEX = 6;
/** @const */ var DAY_INDEX = 7;
/** @const */ var YEAR_INDEX = 8;
/** @const */ var WEEK_INDEX = 9;
/** @const */ var DAY_MS = 86400000;
var methodSubjects = [
'FullYear', // 0
'Month', // 1
'Date', // 2
'Hours', // 3
'Minutes', // 4
'Seconds', // 5
'Milliseconds', // 6
'Day', // 7
'Year' // 8
];
var subjectPlurals = [
'Years', // 0
'Months', // 1
'Days' // 2
];
var otherGetters = [
'getTimezoneOffset',
'getTime',
'valueOf',
'toDateString',
'toTimeString',
'toLocaleString',
'toLocaleDateString',
'toLocaleTimeString',
'toJSON'
];
var formatStringRE = new RegExp(
"(" + // 1
"\\((" + "('.*?'|\\(.*?\\)|.)*?" + ")\\)" + "|" + // 2 3
"\\{(" + "('.*?'|.)*?" + ")\\}" + "|" + // 4 5
"\\[(" + "('.*?'|.)*?" + ")\\]" + "|" + // 6 7
"'(.*?)'" + "|" + // 8
"(.[^({[']*)" + // 9
")" +
"(.*)" // 10
);
var UTC = Date.UTC;
var proto = MightyDate.prototype;
var i;
var noopSignal = {};
/* Constructor
------------------------------------------------------------------------------*/
function MightyDate() {
if (!(this instanceof MightyDate)) {
// when MightyDate is called as a method. TODO: write test
return init(new MightyDate(noopSignal), arguments);
}
init(this, arguments);
}
function init(mightyDate, args) {
var len = args.length;
if (!len) {
// set to now
setLocalDate(mightyDate, new Date());
}
else if (len == 1) {
var arg = args[0];
if (isNumber(arg)) {
setLocalDate(mightyDate, new Date(arg));
}
else if (isString(arg)) {
_setUniversalDate(mightyDate, new Date(0));
parse(mightyDate, arg);
}
else if (arg instanceof Date) {
setLocalDate(mightyDate, new Date(+arg));
}
else if (arg instanceof MightyDate) {
_setUniversalDate(mightyDate, new Date(+getUniversalDate(arg)));
}
else if (arg != noopSignal) {
_setUniversalDate(mightyDate, NaN); // make it an Invalid Date. TODO: write test
}
}
else {
_setUniversalDate(mightyDate, new Date(UTC.apply(Date, args)));
}
return mightyDate;
}
/* Standard Methods + Adding/Diffing Methods
-----------------------------------------------------------------------------*/
for (i=0; i<WEEK_INDEX; i++) (function(subject, i) { // TODO: rename i to fieldIndex
if (i != YEAR_INDEX) { // there is no getUTCYear
// getFullYear
// getMonth
// getDate
// getHours
// getMinutes
// getSeconds
// getMilliseconds
// getDay
proto['get' + subject] = function() {
return getUniversalDate(this)['getUTC' + subject]();
};
}
// getFullYear
// getMonth
// getDate
// getHours
// getMinutes
// getSeconds
// getMilliseconds
// getDay
// getYear
proto['getUTC' + subject] = function() {
return getLocalDate(this)['getUTC' + subject]();
};
if (i != DAY_INDEX) { // there is no setDay or setUTCDay methods
// setFullYear
// setMonth
// setDate
// setHours
// setMinutes
// setSeconds
// setMilliseconds
// setYear
proto['set' + subject] = function(value, preventOverflow) {
value = parseInt(value); // TODO: write tests
var month = i == MONTH_INDEX ? value % 12 : this.getMonth();
var args = arguments;
if (args.length == 2 && (isBoolean(preventOverflow) || preventOverflow === undefined)) {
args = [value];
}
getUniversalDate(this)['setUTC' + subject].apply(
getUniversalDate(this),
args
);
clearLocalDate(this);
// TODO: write tests for year overflow
if (i < DATE_INDEX && preventOverflow === true && this.getMonth() != month) { // fullyear or month
this.addMonths(-1)
.setDate(
getDaysInMonth(this.getFullYear(), this.getMonth())
);
}
return this; // for chaining
};
if (i != YEAR_INDEX) { // there is no setUTCYear method
// setUTCFullYear
// setUTCMonth
// setUTCDate
// setUTCHours
// setUTCMinutes
// setUTCMilliseconds
proto['setUTC' + subject] = function() {
var localDate = getLocalDate(this);
localDate['setUTC' + subject].apply(
localDate,
arguments
);
setUniversalDate(this, localToUniversal(localDate));
return this; // for chaining
};
}
}
if (i < DAY_INDEX) { // not day and not year
// addYears
// addMonths
// addDays
// addHours
// addMinutes
// addSeconds
// addMilliseconds
proto['add' + (subjectPlurals[i] || subject)] = function(delta, preventOverflow) {
delta = parseInt(delta);
this['set' + subject](
this['get' + subject]() + delta,
preventOverflow
);
return this; // for chaining
};
// diffYears
// diffMonths
// diffDays
// diffHours
// diffMinutes
// diffSeconds
// diffMilliseconds
proto['diff' + (subjectPlurals[i] || subject)] = function(otherDate, exact) {
return diff(i, this, otherDate, exact);
};
}
})(methodSubjects[i], i);
for (i=0; i<otherGetters.length; i++) (function(getter) {
proto[getter] = function() {
return getLocalDate(this)[getter]();
};
})(otherGetters[i]);
proto.getYear = function() {
return getUniversalDate(this).getUTCFullYear() - 1900;
};
proto.setTime = function(t) {
setLocalDate(this, new Date(t));
};
function diff(fieldIndex, date, otherDate, exact) { // TODO: write tests
var d1 = getUniversalDate(date);
var d2 = extractUniversalDate(otherDate);
var v;
if (fieldIndex < DATE_INDEX) { // fullyear or month
v = (d2.getUTCFullYear() * 12 + d2.getUTCMonth()) -
(d1.getUTCFullYear() * 12 + d1.getUTCMonth()) +
((d2 - UTC(d2.getUTCFullYear(), d2.getUTCMonth(), 1)) -
(d1 - UTC(d1.getUTCFullYear(), d1.getUTCMonth(), 1))) / DAY_MS / 31;
if (fieldIndex == FULLYEAR_INDEX) {
v /= 12;
}
}else{
v = (d2 - d1) / [ // milliseconds in a...
DAY_MS, // day
3600000, // hour
60000, // minute
1000, // second
1, // millisecond
0,
0,
DAY_MS * 7 // week
][fieldIndex - 2];
}
if (!exact) {
v = Math.floor(v);
}
return v;
}
/* Week (TODO: write tests)
----------------------------------------------------------------------------*/
proto.diffWeeks = function(otherDate, exact) {
return diff(WEEK_INDEX, this, otherDate, exact);
};
proto.addWeeks = function(delta) {
return this.addDays(delta * 7);
};
proto.getWeek = function(d) {
return _getWeek(this.getFullYear(), this.getMonth(), this.getDate());
};
proto.getUTCWeek = function(d) {
return _getWeek(this.getUTCFullYear(), this.getUTCMonth(), this.getUTCDate());
};
function _getWeek(year, month, date) {
var d = new Date(UTC(year, month, date));
var currentWeek1 = getWeek1(year);
var week1 = currentWeek1;
if (d < currentWeek1) {
week1 = getWeek1(year-1);
}else{
var nextWeek1 = getWeek1(year+1);
if (d >= nextWeek1) {
week1 = nextWeek1;
}
}
return Math.floor(Math.round((d - week1) / DAY_MS) / 7) + 1;
}
function getWeek1(year) {
var d = new Date(UTC(year, 0, 4));
d.setUTCDate(d.getUTCDate() - (d.getUTCDay() + 6) % 7);
return d;
}
/* Non-Standard Methods
-----------------------------------------------------------------------------*/
proto.equals = function(date) {
return +getUniversalDate(this) == +extractUniversalDate(date);
};
proto.before = function(date) {
return getUniversalDate(this) < extractUniversalDate(date);
};
proto.after = function(date) {
return getUniversalDate(this) > extractUniversalDate(date);
};
proto.valid = function() {
return !isNaN(+getUniversalDate(this));
};
proto.clearTime = function() {
return this.setHours(0, 0, 0, 0);
};
proto.clone = function() {
return new MightyDate(this);
};
proto.toDate = function() {
return new Date(+this);
};
/* Parsing
-----------------------------------------------------------------------------*/
MightyDate.parsers = [
parseISO
];
MightyDate.parse = function(str) {
return +new MightyDate(''+str);
};
function parse(date, str) {
var parsers = MightyDate.parsers;
for (var i=0; i<parsers.length; i++) {
if (parsers[i](date, str)) {
return;
}
}
setLocalDate(date, new Date(str));
}
function parseISO(date, str) {
var m = str.match(/^(\d{4})(-(\d{2})(-(\d{2})([T ](\d{2}):(\d{2})(:(\d{2})(\.(\d+))?)?(Z|(([-+])(\d{2})(:?(\d{2}))?))?)?)?)?$/);
if (!m) {
return false;
}
var args1 = [
m[1],
m[3] ? m[3] - 1 : 0,
m[5] || 1
];
var args2 = [
m[7] || 0,
m[8] || 0,
m[10] || 0,
m[12] ? Number('0.' + m[12]) * 1000 : 0
];
if (m[14]) {
date.setUTCFullYear.apply(date, args1);
date.setUTCHours.apply(date, args2);
var offset = Number(m[16]) * 60 + (m[18] ? Number(m[18]) : 0);
offset *= m[15] == '-' ? 1 : -1;
date.addMinutes(offset);
}else{
date.setFullYear.apply(date, args1);
date.setHours.apply(date, args2);
}
return true;
}
/* Formatting
---------------------------------------------------------------------------------------*/
MightyDate.defaultLocale = '';
MightyDate.locales = {
'': {
monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'],
monthNamesShort: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
dayNames: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
dayNamesShort: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],
amDesignator: 'AM',
pmDesignator: 'PM'
}
};
MightyDate.formatters = {
i: "yyyy-MM-dd'T'HH:mm:ss",
u: "yyyy-MM-dd'T'HH:mm:ssK"
};
proto.toString = function(otherDate, formatString, settings) {
if (!arguments.length || !this.valid()) {
return getLocalDate(this).toString();
}
return format(false, this, otherDate, formatString, settings);
};
proto.toUTCString = proto.toGMTString = function(otherDate, formatString, settings) {
if (!arguments.length || !this.valid()) {
return getLocalDate(this).toUTCString();
}
return format(true, this, otherDate, formatString, settings);
};
function format(isUTC, date, otherDate, formatString, settings) {
if (isString(otherDate)) {
settings = formatString;
formatString = otherDate;
otherDate = null;
}
var locales = MightyDate.locales;
var localeSettings = locales[MightyDate.defaultLocale] || {};
if (isString(settings)) {
localeSettings = locales[settings] || localeSettings;
settings = {};
}else{
settings = settings || {};
}
function getSetting(name) {
return settings[name] || localeSettings[name];
}
return _format(isUTC, date, otherDate, formatString, getSetting);
}
function _format(isUTC, date, otherDate, formatString, getSetting, uniqueness) {
var match;
var tokenReplacement;
var subOutput;
var output = '';
var _getField = isUTC ? getUTCField : getLocalField;
function getField(i) {
if (uniqueness) {
for (var j=i; j>=0; j--) {
uniqueness.push(_getField(date, j));
}
}
return _getField(date, i);
}
while (formatString) {
match = formatStringRE.exec(formatString);
if (match[9] !== undefined) {
// tokens
var tokenSoup = match[9];
var tokenSoupLength = tokenSoup.length;
var tokenStart = 0;
var tokenEnd = tokenSoupLength;
while (tokenStart < tokenSoupLength) {
tokenReplacement = undefined;
// find the longest formatter starting at tokenStart
while (tokenEnd > tokenStart) {
tokenReplacement = getTokenReplacement(
tokenSoup.substring(tokenStart, tokenEnd), // the potential token
isUTC, date, getField, getSetting, uniqueness
);
if (tokenReplacement !== undefined) {
output += tokenReplacement;
tokenStart = tokenEnd; // continue looking after
break;
}
tokenEnd--;
}
if (tokenReplacement === undefined) {
// swallow single character and continue after
output += formatString.charAt(tokenStart);
tokenStart++;
}
tokenEnd = tokenSoupLength;
}
}
else if (match[2] !== undefined) {
// only if non-zero
subOutput = _format(isUTC, date, otherDate, match[2], getSetting);
if (Number(subOutput.replace(/\D/g, ''))) {
output += subOutput;
}
}
else if (match[4] !== undefined) {
// switch to other date
if (otherDate) {
output += _format(isUTC, otherDate, date, match[4], getSetting);
}
}
else if (match[6] !== undefined) {
// only if different
var otherSubUniqueness = [];
var otherSubOutput = _format(isUTC, otherDate, date, match[6], getSetting, otherSubUniqueness);
var subUniqueness = [];
subOutput = _format(isUTC, date, otherDate, match[6], getSetting, subUniqueness);
if (subOutput != otherSubOutput || subUniqueness.join() != otherSubUniqueness.join()) {
output += subOutput;
}
}
else if (match[8] !== undefined) {
if (match[8]) {
output += match[8]; // quoted text
}else{
output += "'"; // escaped quote
}
}
formatString = match[10];
}
return output;
}
function getTokenReplacement(token, isUTC, date, getField, getSetting, uniqueness) {
var formatter = MightyDate.formatters[token];
if (formatter) {
if (isString(formatter)) {
return _format(isUTC, date, null, formatter, getSetting, uniqueness);
}
return formatter(date, getSetting, isUTC);
}
switch (token) {
case 's' : return getField(SECONDS_INDEX);
case 'ss' : return zeroPad(getField(SECONDS_INDEX));
case 'm' : return getField(MINUTES_INDEX);
case 'mm' : return zeroPad(getField(MINUTES_INDEX));
case 'h' : return getField(HOURS_INDEX) % 12 || 12;
case 'hh' : return zeroPad(getField(HOURS_INDEX) % 12 || 12);
case 'H' : return getField(HOURS_INDEX);
case 'HH' : return zeroPad(getField(HOURS_INDEX));
case 'd' : return getField(DATE_INDEX);
case 'dd' : return zeroPad(getField(DATE_INDEX));
case 'ddd' : return getSetting('dayNamesShort')[getField(DAY_INDEX)];
case 'dddd' : return getSetting('dayNames')[getField(DAY_INDEX)];
case 'M' : return getField(MONTH_INDEX) + 1;
case 'MM' : return zeroPad(getField(MONTH_INDEX) + 1);
case 'MMM' : return getSetting('monthNamesShort')[getField(MONTH_INDEX)];
case 'MMMM' : return getSetting('monthNames')[getField(MONTH_INDEX)];
case 'yy' : return (getField(FULLYEAR_INDEX)+'').substring(2);
case 'yyyy' : return getField(FULLYEAR_INDEX);
case 't' :
case 'tt' :
case 'T' :
case 'TT' :
var s = getField(HOURS_INDEX) < 12 ? getSetting('amDesignator') : getSetting('pmDesignator');
if (token.length == 1) s = s.substr(0, 1);
if (token > 'TT') s = s.toLowerCase(); // token == 't' || token == 'tt'
return s;
case 'K' : if (isUTC) return 'Z';
case 'z' :
case 'zz' :
case 'zzz' :
var tzo = date.getTimezoneOffset();
var sign = tzo < 0 ? '+' : '-';
var hours = Math.floor((tzo = Math.abs(tzo)) / 60);
var minutes = tzo % 60;
return sign +
(token == 'z' ? hours : zeroPad(hours)) +
((token == 'z' || token == 'zz') ? '' : ':' + zeroPad(minutes));
case 'S' :
var d = getField(DATE_INDEX);
if (d > 10 && d < 20) return 'th';
return ['st', 'nd', 'rd'][d % 10 - 1] || 'th';
case 'W' :
return _getWeek(
getField(FULLYEAR_INDEX),
getField(MONTH_INDEX),
getField(DATE_INDEX)
);
case 'WW' :
return zeroPad(
_getWeek(
getField(FULLYEAR_INDEX),
getField(MONTH_INDEX),
getField(DATE_INDEX)
)
);
}
}
function getLocalField(date, i) {
return date['get' + methodSubjects[i]]();
}
function getUTCField(date, i) {
return date['getUTC' + methodSubjects[i]]();
}
/* Misc Class Methods
-----------------------------------------------------------------------------*/
MightyDate.now = Date.now;
MightyDate.UTC = UTC;
MightyDate.getDaysInMonth = getDaysInMonth;
/* Date Utilities
--------------------------------------------------------------------------------*/
function getDaysInMonth(year, month) {
return 32 - new Date(Date.UTC(year, month, 32)).getUTCDate();
}
function extractUniversalDate(date) {
if (date instanceof MightyDate) {
return getUniversalDate(date);
}
else if (date instanceof Date) {
return localToUniversal(date);
}
return getUniversalDate(new MightyDate(date));
}
function localToUniversal(localDate) {
return new Date(UTC(
localDate.getFullYear(),
localDate.getMonth(),
localDate.getDate(),
localDate.getHours(),
localDate.getMinutes(),
localDate.getSeconds(),
localDate.getMilliseconds()
));
}
function universalToLocal(universalDate) {
return new Date(
universalDate.getUTCFullYear(),
universalDate.getUTCMonth(),
universalDate.getUTCDate(),
universalDate.getUTCHours(),
universalDate.getUTCMinutes(),
universalDate.getUTCSeconds(),
universalDate.getUTCMilliseconds()
);
}
/* General Utilities
--------------------------------------------------------------------------------*/
function zeroPad(n) {
return (n < 10 ? '0' : '') + n;
}
function isString(x) {
return typeof x == 'string';
}
function isNumber(x) {
return typeof x == 'number';
}
function isBoolean(x) {
return typeof x == 'boolean';
}
/* Internal Utilities
---------------------------------------------------------------------------------*/
// _ is universal date
// $ is local date
function getUniversalDate(mightyDate) {
return mightyDate._;
}
function setUniversalDate(mightyDate, universalDate) {
_setUniversalDate(mightyDate, universalDate);
clearLocalDate(mightyDate);
}
function _setUniversalDate(mightyDate, universalDate) {
mightyDate._ = universalDate;
}
function getLocalDate(mightyDate) {
if (mightyDate.$ === undefined) {
mightyDate.$ = universalToLocal(mightyDate._);
}
return mightyDate.$;
}
function setLocalDate(mightyDate, localDate) {
mightyDate.$ = localDate;
mightyDate._ = localToUniversal(localDate);
}
function clearLocalDate(mightyDate) {
delete mightyDate.$;
}
return MightyDate;
})(Date);