zulip/static/third/xdate/xdate.dev.js

861 lines
21 KiB
JavaScript
Raw Normal View History

/** @preserve
Software from "XDate v0.8", a wrapper around JavaScript's Date object, is
Copyright (c) 2010 C. F., Wong and is provided under the following license:
--
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
--
*/
/**
* XDate v0.8
* Docs & Licensing: http://arshaw.com/xdate/
*/
/*
* Internal Architecture
* ---------------------
* An XDate wraps a native Date. The native Date is stored in the '0' property of the object.
* UTC-mode is determined by whether the internal native Date's toString method is set to
* Date.prototype.toUTCString (see getUTCMode).
*
*/
var XDate = (function(Date, Math, Array, undefined) {
/** @const */ var FULLYEAR = 0;
/** @const */ var MONTH = 1;
/** @const */ var DATE = 2;
/** @const */ var HOURS = 3;
/** @const */ var MINUTES = 4;
/** @const */ var SECONDS = 5;
/** @const */ var MILLISECONDS = 6;
/** @const */ var DAY = 7;
/** @const */ var YEAR = 8;
/** @const */ var WEEK = 9;
/** @const */ var DAY_MS = 86400000;
var ISO_FORMAT_STRING = "yyyy-MM-dd'T'HH:mm:ss(.fff)";
var ISO_FORMAT_STRING_TZ = ISO_FORMAT_STRING + "zzz";
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 unitsWithin = [
12, // months in year
31, // days in month (sort of)
24, // hours in day
60, // minutes in hour
60, // seconds in minute
1000, // milliseconds in second
1 //
];
var formatStringRE = new RegExp(
"(([a-zA-Z])\\2*)|" + // 1, 2
"(\\(" + "(('.*?'|\\(.*?\\)|.)*?)" + "\\))|" + // 3, 4, 5 (allows for 1 level of inner quotes or parens)
"('(.*?)')" // 6, 7
);
var UTC = Date.UTC;
var toUTCString = Date.prototype.toUTCString;
var proto = XDate.prototype;
// This makes an XDate look pretty in Firebug and Web Inspector.
// It makes an XDate seem array-like, and displays [ <internal-date>.toString() ]
proto.length = 1;
proto.splice = Array.prototype.splice;
/* Constructor
---------------------------------------------------------------------------------*/
// TODO: in future, I'd change signature for the constructor regarding the `true` utc-mode param. ~ashaw
// I'd move the boolean to be the *first* argument. Still optional. Seems cleaner.
// I'd remove it from the `xdate`, `nativeDate`, and `milliseconds` constructors.
// (because you can simply call .setUTCMode(true) after)
// And I'd only leave it for the y/m/d/h/m/s/m and `dateString` constructors
// (because those are the only constructors that need it for DST-gap data-loss reasons)
// Should do this for 1.0
function XDate() {
return init(
(this instanceof XDate) ? this : new XDate(),
arguments
);
}
function init(xdate, args) {
var len = args.length;
var utcMode;
if (isBoolean(args[len-1])) {
utcMode = args[--len];
args = slice(args, 0, len);
}
if (!len) {
xdate[0] = new Date();
}
else if (len == 1) {
var arg = args[0];
if (arg instanceof Date || isNumber(arg)) {
xdate[0] = new Date(+arg);
}
else if (arg instanceof XDate) {
xdate[0] = _clone(arg);
}
else if (isString(arg)) {
xdate[0] = new Date(0);
xdate = parse(arg, utcMode || false, xdate);
}
}
else {
xdate[0] = new Date(UTC.apply(Date, args));
if (!utcMode) {
xdate[0] = coerceToLocal(xdate[0]);
}
}
if (isBoolean(utcMode)) {
setUTCMode(xdate, utcMode);
}
return xdate;
}
/* UTC Mode Methods
---------------------------------------------------------------------------------*/
proto.getUTCMode = methodize(getUTCMode);
function getUTCMode(xdate) {
return xdate[0].toString === toUTCString;
};
proto.setUTCMode = methodize(setUTCMode);
function setUTCMode(xdate, utcMode, doCoercion) {
if (utcMode) {
if (!getUTCMode(xdate)) {
if (doCoercion) {
xdate[0] = coerceToUTC(xdate[0]);
}
xdate[0].toString = toUTCString;
}
}else{
if (getUTCMode(xdate)) {
if (doCoercion) {
xdate[0] = coerceToLocal(xdate[0]);
}else{
xdate[0] = new Date(+xdate[0]);
}
// toString will have been cleared
}
}
return xdate; // for chaining
}
proto.getTimezoneOffset = function() {
if (getUTCMode(this)) {
return 0;
}else{
return this[0].getTimezoneOffset();
}
};
/* get / set / add / diff Methods (except for week-related)
---------------------------------------------------------------------------------*/
each(methodSubjects, function(subject, fieldIndex) {
proto['get' + subject] = function() {
return _getField(this[0], getUTCMode(this), fieldIndex);
};
if (fieldIndex != YEAR) { // because there is no getUTCYear
proto['getUTC' + subject] = function() {
return _getField(this[0], true, fieldIndex);
};
}
if (fieldIndex != DAY) { // because there is no setDay or setUTCDay
// and the add* and diff* methods use DATE instead
proto['set' + subject] = function(value) {
_set(this, fieldIndex, value, arguments, getUTCMode(this));
return this; // for chaining
};
if (fieldIndex != YEAR) { // because there is no setUTCYear
// and the add* and diff* methods use FULLYEAR instead
proto['setUTC' + subject] = function(value) {
_set(this, fieldIndex, value, arguments, true);
return this; // for chaining
};
proto['add' + (subjectPlurals[fieldIndex] || subject)] = function(delta, preventOverflow) {
_add(this, fieldIndex, delta, preventOverflow);
return this; // for chaining
};
proto['diff' + (subjectPlurals[fieldIndex] || subject)] = function(otherDate) {
return _diff(this, otherDate, fieldIndex);
};
}
}
});
function _set(xdate, fieldIndex, value, args, useUTC) {
var getField = curry(_getField, xdate[0], useUTC);
var setField = curry(_setField, xdate[0], useUTC);
var expectedMonth;
var preventOverflow = false;
if (args.length == 2 && isBoolean(args[1])) {
preventOverflow = args[1];
args = [ value ];
}
if (fieldIndex == MONTH) {
expectedMonth = (value % 12 + 12) % 12;
}else{
expectedMonth = getField(MONTH);
}
setField(fieldIndex, args);
if (preventOverflow && getField(MONTH) != expectedMonth) {
setField(MONTH, [ getField(MONTH) - 1 ]);
setField(DATE, [ getDaysInMonth(getField(FULLYEAR), getField(MONTH)) ]);
}
}
function _add(xdate, fieldIndex, delta, preventOverflow) {
delta = Number(delta);
var intDelta = Math.floor(delta);
xdate['set' + methodSubjects[fieldIndex]](
xdate['get' + methodSubjects[fieldIndex]]() + intDelta,
preventOverflow || false
);
if (intDelta != delta && fieldIndex < MILLISECONDS) {
_add(xdate, fieldIndex+1, (delta-intDelta)*unitsWithin[fieldIndex], preventOverflow);
}
}
function _diff(xdate1, xdate2, fieldIndex) { // fieldIndex=FULLYEAR is for years, fieldIndex=DATE is for days
xdate1 = xdate1.clone().setUTCMode(true, true);
xdate2 = XDate(xdate2).setUTCMode(true, true);
var v = 0;
if (fieldIndex == FULLYEAR || fieldIndex == MONTH) {
for (var i=MILLISECONDS, methodName; i>=fieldIndex; i--) {
v /= unitsWithin[i];
v += _getField(xdate2, false, i) - _getField(xdate1, false, i);
}
if (fieldIndex == MONTH) {
v += (xdate2.getFullYear() - xdate1.getFullYear()) * 12;
}
}
else if (fieldIndex == DATE) {
var clear1 = xdate1.toDate().setUTCHours(0, 0, 0, 0); // returns an ms value
var clear2 = xdate2.toDate().setUTCHours(0, 0, 0, 0); // returns an ms value
v = Math.round((clear2 - clear1) / DAY_MS) + ((xdate2 - clear2) - (xdate1 - clear1)) / DAY_MS;
}
else {
v = (xdate2 - xdate1) / [
3600000, // milliseconds in hour
60000, // milliseconds in minute
1000, // milliseconds in second
1 //
][fieldIndex - 3];
}
return v;
}
/* Week Methods
---------------------------------------------------------------------------------*/
proto.getWeek = function() {
return _getWeek(curry(_getField, this, false));
};
proto.getUTCWeek = function() {
return _getWeek(curry(_getField, this, true));
};
proto.setWeek = function(n, year) {
_setWeek(this, n, year, false);
return this; // for chaining
};
proto.setUTCWeek = function(n, year) {
_setWeek(this, n, year, true);
return this; // for chaining
};
proto.addWeeks = function(delta) {
return this.addDays(Number(delta) * 7);
};
proto.diffWeeks = function(otherDate) {
return _diff(this, otherDate, DATE) / 7;
};
function _getWeek(getField) {
return getWeek(getField(FULLYEAR), getField(MONTH), getField(DATE));
}
function getWeek(year, month, date) {
var d = new Date(UTC(year, month, date));
var week1 = getWeek1(
getWeekYear(year, month, date)
);
return Math.floor(Math.round((d - week1) / DAY_MS) / 7) + 1;
}
function getWeekYear(year, month, date) { // get the year that the date's week # belongs to
var d = new Date(UTC(year, month, date));
if (d < getWeek1(year)) {
return year - 1;
}
else if (d >= getWeek1(year + 1)) {
return year + 1;
}
return year;
}
function getWeek1(year) { // returns Date of first week of year, in UTC
var d = new Date(UTC(year, 0, 4));
d.setUTCDate(d.getUTCDate() - (d.getUTCDay() + 6) % 7); // make it Monday of the week
return d;
}
function _setWeek(xdate, n, year, useUTC) {
var getField = curry(_getField, xdate, useUTC);
var setField = curry(_setField, xdate, useUTC);
if (year === undefined) {
year = getWeekYear(
getField(FULLYEAR),
getField(MONTH),
getField(DATE)
);
}
var week1 = getWeek1(year);
if (!useUTC) {
week1 = coerceToLocal(week1);
}
xdate.setTime(+week1);
setField(DATE, [ getField(DATE) + (n-1) * 7 ]); // would have used xdate.addUTCWeeks :(
// n-1 because n is 1-based
}
/* Parsing
---------------------------------------------------------------------------------*/
XDate.parsers = [
parseISO
];
XDate.parse = function(str) {
return +XDate(''+str);
};
function parse(str, utcMode, xdate) {
var parsers = XDate.parsers;
var i = 0;
var res;
for (; i<parsers.length; i++) {
res = parsers[i](str, utcMode, xdate);
if (res) {
return res;
}
}
xdate[0] = new Date(str);
return xdate;
}
function parseISO(str, utcMode, xdate) {
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) {
var d = new Date(UTC(
m[1],
m[3] ? m[3] - 1 : 0,
m[5] || 1,
m[7] || 0,
m[8] || 0,
m[10] || 0,
m[12] ? Number('0.' + m[12]) * 1000 : 0
));
if (m[13]) { // has gmt offset or Z
if (m[14]) { // has gmt offset
d.setUTCMinutes(
d.getUTCMinutes() +
(m[15] == '-' ? 1 : -1) * (Number(m[16]) * 60 + (m[18] ? Number(m[18]) : 0))
);
}
}else{ // no specified timezone
if (!utcMode) {
d = coerceToLocal(d);
}
}
return xdate.setTime(+d);
}
}
/* Formatting
---------------------------------------------------------------------------------*/
proto.toString = function(formatString, settings, uniqueness) {
if (formatString === undefined || !valid(this)) {
return this[0].toString(); // already accounts for utc-mode (might be toUTCString)
}else{
return format(this, formatString, settings, uniqueness, getUTCMode(this));
}
};
proto.toUTCString = proto.toGMTString = function(formatString, settings, uniqueness) {
if (formatString === undefined || !valid(this)) {
return this[0].toUTCString();
}else{
return format(this, formatString, settings, uniqueness, true);
}
};
proto.toISOString = function() {
return this.toUTCString(ISO_FORMAT_STRING_TZ);
};
XDate.defaultLocale = '';
XDate.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'
}
};
XDate.formatters = {
i: ISO_FORMAT_STRING,
u: ISO_FORMAT_STRING_TZ
};
function format(xdate, formatString, settings, uniqueness, useUTC) {
var locales = XDate.locales;
var defaultLocaleSettings = locales[XDate.defaultLocale] || {};
var getField = curry(_getField, xdate, useUTC);
settings = (isString(settings) ? locales[settings] : settings) || {};
function getSetting(name) {
return settings[name] || defaultLocaleSettings[name];
}
function getFieldAndTrace(fieldIndex) {
if (uniqueness) {
var i = (fieldIndex == DAY ? DATE : fieldIndex) - 1;
for (; i>=0; i--) {
uniqueness.push(getField(i));
}
}
return getField(fieldIndex);
}
return _format(xdate, formatString, getFieldAndTrace, getSetting, useUTC);
}
function _format(xdate, formatString, getField, getSetting, useUTC) {
var m;
var subout;
var out = '';
while (m = formatString.match(formatStringRE)) {
out += formatString.substr(0, m.index);
if (m[1]) { // consecutive alphabetic characters
out += processTokenString(xdate, m[1], getField, getSetting, useUTC);
}
else if (m[3]) { // parenthesis
subout = _format(xdate, m[4], getField, getSetting, useUTC);
if (parseInt(subout.replace(/\D/g, ''), 10)) { // if any of the numbers are non-zero. or no numbers at all
out += subout;
}
}
else { // else if (m[6]) { // single quotes
out += m[7] || "'"; // if inner is blank, meaning 2 consecutive quotes = literal single quote
}
formatString = formatString.substr(m.index + m[0].length);
}
return out + formatString;
}
function processTokenString(xdate, tokenString, getField, getSetting, useUTC) {
var end = tokenString.length;
var replacement;
var out = '';
while (end > 0) {
replacement = getTokenReplacement(xdate, tokenString.substr(0, end), getField, getSetting, useUTC);
if (replacement !== undefined) {
out += replacement;
tokenString = tokenString.substr(end);
end = tokenString.length;
}else{
end--;
}
}
return out + tokenString;
}
function getTokenReplacement(xdate, token, getField, getSetting, useUTC) {
var formatter = XDate.formatters[token];
if (isString(formatter)) {
return _format(xdate, formatter, getField, getSetting, useUTC);
}
else if (isFunction(formatter)) {
return formatter(xdate, useUTC || false, getSetting);
}
switch (token) {
case 'fff' : return zeroPad(getField(MILLISECONDS), 3);
case 's' : return getField(SECONDS);
case 'ss' : return zeroPad(getField(SECONDS));
case 'm' : return getField(MINUTES);
case 'mm' : return zeroPad(getField(MINUTES));
case 'h' : return getField(HOURS) % 12 || 12;
case 'hh' : return zeroPad(getField(HOURS) % 12 || 12);
case 'H' : return getField(HOURS);
case 'HH' : return zeroPad(getField(HOURS));
case 'd' : return getField(DATE);
case 'dd' : return zeroPad(getField(DATE));
case 'ddd' : return getSetting('dayNamesShort')[getField(DAY)] || '';
case 'dddd' : return getSetting('dayNames')[getField(DAY)] || '';
case 'M' : return getField(MONTH) + 1;
case 'MM' : return zeroPad(getField(MONTH) + 1);
case 'MMM' : return getSetting('monthNamesShort')[getField(MONTH)] || '';
case 'MMMM' : return getSetting('monthNames')[getField(MONTH)] || '';
case 'yy' : return (getField(FULLYEAR)+'').substring(2);
case 'yyyy' : return getField(FULLYEAR);
case 't' : return _getDesignator(getField, getSetting).substr(0, 1).toLowerCase();
case 'tt' : return _getDesignator(getField, getSetting).toLowerCase();
case 'T' : return _getDesignator(getField, getSetting).substr(0, 1);
case 'TT' : return _getDesignator(getField, getSetting);
case 'z' :
case 'zz' :
case 'zzz' : return useUTC ? 'Z' : _getTZString(xdate, token);
case 'w' : return _getWeek(getField);
case 'ww' : return zeroPad(_getWeek(getField));
case 'S' :
var d = getField(DATE);
if (d > 10 && d < 20) return 'th';
return ['st', 'nd', 'rd'][d % 10 - 1] || 'th';
}
}
function _getTZString(xdate, token) {
var tzo = xdate.getTimezoneOffset();
var sign = tzo < 0 ? '+' : '-';
var hours = Math.floor(Math.abs(tzo) / 60);
var minutes = Math.abs(tzo) % 60;
var out = hours;
if (token == 'zz') {
out = zeroPad(hours);
}
else if (token == 'zzz') {
out = zeroPad(hours) + ':' + zeroPad(minutes);
}
return sign + out;
}
function _getDesignator(getField, getSetting) {
return getField(HOURS) < 12 ? getSetting('amDesignator') : getSetting('pmDesignator');
}
/* Misc Methods
---------------------------------------------------------------------------------*/
each(
[ // other getters
'getTime',
'valueOf',
'toDateString',
'toTimeString',
'toLocaleString',
'toLocaleDateString',
'toLocaleTimeString',
'toJSON'
],
function(methodName) {
proto[methodName] = function() {
return this[0][methodName]();
};
}
);
proto.setTime = function(t) {
this[0].setTime(t);
return this; // for chaining
};
proto.valid = methodize(valid);
function valid(xdate) {
return !isNaN(+xdate[0]);
}
proto.clone = function() {
return new XDate(this);
};
proto.clearTime = function() {
return this.setHours(0, 0, 0, 0); // will return an XDate for chaining
};
proto.toDate = function() {
return new Date(+this[0]);
};
/* Misc Class Methods
---------------------------------------------------------------------------------*/
XDate.now = function() {
return +new Date();
};
XDate.today = function() {
return new XDate().clearTime();
};
XDate.UTC = UTC;
XDate.getDaysInMonth = getDaysInMonth;
/* Internal Utilities
---------------------------------------------------------------------------------*/
function _clone(xdate) { // returns the internal Date object that should be used
var d = new Date(+xdate[0]);
if (getUTCMode(xdate)) {
d.toString = toUTCString;
}
return d;
}
function _getField(d, useUTC, fieldIndex) {
return d['get' + (useUTC ? 'UTC' : '') + methodSubjects[fieldIndex]]();
}
function _setField(d, useUTC, fieldIndex, args) {
d['set' + (useUTC ? 'UTC' : '') + methodSubjects[fieldIndex]].apply(d, args);
}
/* Date Math Utilities
---------------------------------------------------------------------------------*/
function coerceToUTC(date) {
return new Date(UTC(
date.getFullYear(),
date.getMonth(),
date.getDate(),
date.getHours(),
date.getMinutes(),
date.getSeconds(),
date.getMilliseconds()
));
}
function coerceToLocal(date) {
return new Date(
date.getUTCFullYear(),
date.getUTCMonth(),
date.getUTCDate(),
date.getUTCHours(),
date.getUTCMinutes(),
date.getUTCSeconds(),
date.getUTCMilliseconds()
);
}
function getDaysInMonth(year, month) {
return 32 - new Date(UTC(year, month, 32)).getUTCDate();
}
/* General Utilities
---------------------------------------------------------------------------------*/
function methodize(f) {
return function() {
return f.apply(undefined, [this].concat(slice(arguments)));
};
}
function curry(f) {
var firstArgs = slice(arguments, 1);
return function() {
return f.apply(undefined, firstArgs.concat(slice(arguments)));
};
}
function slice(a, start, end) {
return Array.prototype.slice.call(
a,
start || 0, // start and end cannot be undefined for IE
end===undefined ? a.length : end
);
}
function each(a, f) {
for (var i=0; i<a.length; i++) {
f(a[i], i);
};
}
function isString(arg) {
return typeof arg == 'string';
}
function isNumber(arg) {
return typeof arg == 'number';
}
function isBoolean(arg) {
return typeof arg == 'boolean';
}
function isFunction(arg) {
return typeof arg == 'function';
}
function zeroPad(n, len) {
len = len || 2;
n += '';
while (n.length < len) {
n = '0' + n;
}
return n;
}
// Export for Node.js
if (typeof module !== 'undefined' && module.exports) {
module.exports = XDate;
}
// AMD
if (typeof define === 'function' && define.amd) {
define([], function() {
return XDate;
});
}
return XDate;
})(Date, Math, Array);