Chart Options
+
+
+
+
diff --git a/src/app/services/graphite/graphiteSrv.js b/src/app/services/graphite/graphiteSrv.js
index c11efaa1a72..65a9279de92 100644
--- a/src/app/services/graphite/graphiteSrv.js
+++ b/src/app/services/graphite/graphiteSrv.js
@@ -52,7 +52,13 @@ function (angular, _, $, config, kbn) {
date = kbn.parseDate(date);
}
- return $.plot.formatDate(date, '%H%:%M_%Y%m%d');
+ date = moment.utc(date).local();
+
+ if (config.timezoneOffset) {
+ date = date.zone(config.timezoneOffset)
+ }
+
+ return date.format('HH:mm_YYYYMMDD');
};
this.match = function(targets, graphiteTargetStr) {
diff --git a/src/config.js b/src/config.js
index 35abc565831..5babff20a62 100644
--- a/src/config.js
+++ b/src/config.js
@@ -20,6 +20,12 @@ function (Settings) {
default_route: '/dashboard/file/default.json',
+ /**
+ * If your graphite server has another timezone than you & users browsers specify the offset here
+ * Example: "-0500" (for UTC - 5 hours)
+ */
+ timezoneOffset: null,
+
grafana_index: "grafana-dash",
panel_names: [
diff --git a/src/vendor/moment.js b/src/vendor/moment.js
index a5fa92f7ab2..4efd7d50b94 100644
--- a/src/vendor/moment.js
+++ b/src/vendor/moment.js
@@ -1,8 +1,8 @@
-// moment.js
-// version : 2.1.0
-// author : Tim Wood
-// license : MIT
-// momentjs.com
+//! moment.js
+//! version : 2.5.1
+//! authors : Tim Wood, Iskren Chernev, Moment.js contributors
+//! license : MIT
+//! momentjs.com
(function (undefined) {
@@ -11,41 +11,86 @@
************************************/
var moment,
- VERSION = "2.1.0",
- round = Math.round, i,
+ VERSION = "2.5.1",
+ global = this,
+ round = Math.round,
+ i,
+
+ YEAR = 0,
+ MONTH = 1,
+ DATE = 2,
+ HOUR = 3,
+ MINUTE = 4,
+ SECOND = 5,
+ MILLISECOND = 6,
+
// internal storage for language config files
languages = {},
+ // moment internal properties
+ momentProperties = {
+ _isAMomentObject: null,
+ _i : null,
+ _f : null,
+ _l : null,
+ _strict : null,
+ _isUTC : null,
+ _offset : null, // optional. Combine with _isUTC
+ _pf : null,
+ _lang : null // optional
+ },
+
// check for nodeJS
- hasModule = (typeof module !== 'undefined' && module.exports),
+ hasModule = (typeof module !== 'undefined' && module.exports && typeof require !== 'undefined'),
// ASP.NET json date format regex
aspNetJsonRegex = /^\/?Date\((\-?\d+)/i,
- aspNetTimeSpanJsonRegex = /(\-)?(\d*)?\.?(\d+)\:(\d+)\:(\d+)\.?(\d{3})?/,
+ aspNetTimeSpanJsonRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,
+
+ // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
+ // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
+ isoDurationRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,
// format tokens
- formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|SS?S?|X|zz?|ZZ?|.)/g,
+ formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g,
localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,
// parsing token regexes
parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99
parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999
- parseTokenThreeDigits = /\d{3}/, // 000 - 999
- parseTokenFourDigits = /\d{1,4}/, // 0 - 9999
- parseTokenSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999
+ parseTokenOneToFourDigits = /\d{1,4}/, // 0 - 9999
+ parseTokenOneToSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999
+ parseTokenDigits = /\d+/, // nonzero number of digits
parseTokenWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i, // any word (or two) characters or numbers including two/three word month in arabic.
- parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/i, // +00:00 -00:00 +0000 -0000 or Z
- parseTokenT = /T/i, // T (ISO seperator)
+ parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z
+ parseTokenT = /T/i, // T (ISO separator)
parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123
- // preliminary iso regex
- // 0000-00-00 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000
- isoRegex = /^\s*\d{4}-\d\d-\d\d((T| )(\d\d(:\d\d(:\d\d(\.\d\d?\d?)?)?)?)?([\+\-]\d\d:?\d\d)?)?/,
+ //strict parsing regexes
+ parseTokenOneDigit = /\d/, // 0 - 9
+ parseTokenTwoDigits = /\d\d/, // 00 - 99
+ parseTokenThreeDigits = /\d{3}/, // 000 - 999
+ parseTokenFourDigits = /\d{4}/, // 0000 - 9999
+ parseTokenSixDigits = /[+-]?\d{6}/, // -999,999 - 999,999
+ parseTokenSignedNumber = /[+-]?\d+/, // -inf - inf
+
+ // iso 8601 regex
+ // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
+ isoRegex = /^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,
+
isoFormat = 'YYYY-MM-DDTHH:mm:ssZ',
+ isoDates = [
+ ['YYYYYY-MM-DD', /[+-]\d{6}-\d{2}-\d{2}/],
+ ['YYYY-MM-DD', /\d{4}-\d{2}-\d{2}/],
+ ['GGGG-[W]WW-E', /\d{4}-W\d{2}-\d/],
+ ['GGGG-[W]WW', /\d{4}-W\d{2}/],
+ ['YYYY-DDD', /\d{4}-\d{3}/]
+ ],
+
// iso time formats and regexes
isoTimes = [
- ['HH:mm:ss.S', /(T| )\d\d:\d\d:\d\d\.\d{1,3}/],
+ ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d{1,3}/],
['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/],
['HH:mm', /(T| )\d\d:\d\d/],
['HH', /(T| )\d\d/]
@@ -72,9 +117,24 @@
m : 'minute',
h : 'hour',
d : 'day',
+ D : 'date',
w : 'week',
+ W : 'isoWeek',
M : 'month',
- y : 'year'
+ y : 'year',
+ DDD : 'dayOfYear',
+ e : 'weekday',
+ E : 'isoWeekday',
+ gg: 'weekYear',
+ GG: 'isoWeekYear'
+ },
+
+ camelFunctions = {
+ dayofyear : 'dayOfYear',
+ isoweekday : 'isoWeekday',
+ isoweek : 'isoWeek',
+ weekyear : 'weekYear',
+ isoweekyear : 'isoWeekYear'
},
// format function strings
@@ -127,11 +187,15 @@
YYYYY : function () {
return leftZeroFill(this.year(), 5);
},
+ YYYYYY : function () {
+ var y = this.year(), sign = y >= 0 ? '+' : '-';
+ return sign + leftZeroFill(Math.abs(y), 6);
+ },
gg : function () {
return leftZeroFill(this.weekYear() % 100, 2);
},
gggg : function () {
- return this.weekYear();
+ return leftZeroFill(this.weekYear(), 4);
},
ggggg : function () {
return leftZeroFill(this.weekYear(), 5);
@@ -140,7 +204,7 @@
return leftZeroFill(this.isoWeekYear() % 100, 2);
},
GGGG : function () {
- return this.isoWeekYear();
+ return leftZeroFill(this.isoWeekYear(), 4);
},
GGGGG : function () {
return leftZeroFill(this.isoWeekYear(), 5);
@@ -170,14 +234,17 @@
return this.seconds();
},
S : function () {
- return ~~(this.milliseconds() / 100);
+ return toInt(this.milliseconds() / 100);
},
SS : function () {
- return leftZeroFill(~~(this.milliseconds() / 10), 2);
+ return leftZeroFill(toInt(this.milliseconds() / 10), 2);
},
SSS : function () {
return leftZeroFill(this.milliseconds(), 3);
},
+ SSSS : function () {
+ return leftZeroFill(this.milliseconds(), 3);
+ },
Z : function () {
var a = -this.zone(),
b = "+";
@@ -185,7 +252,7 @@
a = -a;
b = "-";
}
- return b + leftZeroFill(~~(a / 60), 2) + ":" + leftZeroFill(~~a % 60, 2);
+ return b + leftZeroFill(toInt(a / 60), 2) + ":" + leftZeroFill(toInt(a) % 60, 2);
},
ZZ : function () {
var a = -this.zone(),
@@ -194,7 +261,7 @@
a = -a;
b = "-";
}
- return b + leftZeroFill(~~(10 * a / 6), 4);
+ return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2);
},
z : function () {
return this.zoneAbbr();
@@ -204,8 +271,30 @@
},
X : function () {
return this.unix();
+ },
+ Q : function () {
+ return this.quarter();
}
+ },
+
+ lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin'];
+
+ function defaultParsingFlags() {
+ // We need to deep clone this object, and es5 standard is not very
+ // helpful.
+ return {
+ empty : false,
+ unusedTokens : [],
+ unusedInput : [],
+ overflow : -2,
+ charsLeftOver : 0,
+ nullInput : false,
+ invalidMonth : null,
+ invalidFormat : false,
+ userInvalidated : false,
+ iso: false
};
+ }
function padToken(func, count) {
return function (a) {
@@ -239,36 +328,35 @@
// Moment prototype object
function Moment(config) {
+ checkOverflow(config);
extend(this, config);
}
// Duration Constructor
function Duration(duration) {
- var years = duration.years || duration.year || duration.y || 0,
- months = duration.months || duration.month || duration.M || 0,
- weeks = duration.weeks || duration.week || duration.w || 0,
- days = duration.days || duration.day || duration.d || 0,
- hours = duration.hours || duration.hour || duration.h || 0,
- minutes = duration.minutes || duration.minute || duration.m || 0,
- seconds = duration.seconds || duration.second || duration.s || 0,
- milliseconds = duration.milliseconds || duration.millisecond || duration.ms || 0;
-
- // store reference to input for deterministic cloning
- this._input = duration;
+ var normalizedInput = normalizeObjectUnits(duration),
+ years = normalizedInput.year || 0,
+ months = normalizedInput.month || 0,
+ weeks = normalizedInput.week || 0,
+ days = normalizedInput.day || 0,
+ hours = normalizedInput.hour || 0,
+ minutes = normalizedInput.minute || 0,
+ seconds = normalizedInput.second || 0,
+ milliseconds = normalizedInput.millisecond || 0;
// representation for dateAddRemove
- this._milliseconds = milliseconds +
+ this._milliseconds = +milliseconds +
seconds * 1e3 + // 1000
minutes * 6e4 + // 1000 * 60
hours * 36e5; // 1000 * 60 * 60
// Because of dateAddRemove treats 24 hours as different from a
// day when working around DST, we need to store them separately
- this._days = days +
+ this._days = +days +
weeks * 7;
// It is impossible translate months into days without knowing
// which months you are are talking about, so we have to store
// it separately.
- this._months = months +
+ this._months = +months +
years * 12;
this._data = {};
@@ -276,7 +364,6 @@
this._bubble();
}
-
/************************************
Helpers
************************************/
@@ -288,9 +375,29 @@
a[i] = b[i];
}
}
+
+ if (b.hasOwnProperty("toString")) {
+ a.toString = b.toString;
+ }
+
+ if (b.hasOwnProperty("valueOf")) {
+ a.valueOf = b.valueOf;
+ }
+
return a;
}
+ function cloneMoment(m) {
+ var result = {}, i;
+ for (i in m) {
+ if (m.hasOwnProperty(i) && momentProperties.hasOwnProperty(i)) {
+ result[i] = m[i];
+ }
+ }
+
+ return result;
+ }
+
function absRound(number) {
if (number < 0) {
return Math.ceil(number);
@@ -301,12 +408,14 @@
// left zero fill a number
// see http://jsperf.com/left-zero-filling for performance comparison
- function leftZeroFill(number, targetLength) {
- var output = number + '';
+ function leftZeroFill(number, targetLength, forceSign) {
+ var output = '' + Math.abs(number),
+ sign = number >= 0;
+
while (output.length < targetLength) {
output = '0' + output;
}
- return output;
+ return (sign ? (forceSign ? '+' : '') : '-') + output;
}
// helper function for _.addTime and _.subtractTime
@@ -315,8 +424,7 @@
days = duration._days,
months = duration._months,
minutes,
- hours,
- currentDate;
+ hours;
if (milliseconds) {
mom._d.setTime(+mom._d + milliseconds * isAdding);
@@ -347,14 +455,20 @@
return Object.prototype.toString.call(input) === '[object Array]';
}
+ function isDate(input) {
+ return Object.prototype.toString.call(input) === '[object Date]' ||
+ input instanceof Date;
+ }
+
// compare two arrays, return the number of differences
- function compareArrays(array1, array2) {
+ function compareArrays(array1, array2, dontConvert) {
var len = Math.min(array1.length, array2.length),
lengthDiff = Math.abs(array1.length - array2.length),
diffs = 0,
i;
for (i = 0; i < len; i++) {
- if (~~array1[i] !== ~~array2[i]) {
+ if ((dontConvert && array1[i] !== array2[i]) ||
+ (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) {
diffs++;
}
}
@@ -362,16 +476,155 @@
}
function normalizeUnits(units) {
- return units ? unitAliases[units] || units.toLowerCase().replace(/(.)s$/, '$1') : units;
+ if (units) {
+ var lowered = units.toLowerCase().replace(/(.)s$/, '$1');
+ units = unitAliases[units] || camelFunctions[lowered] || lowered;
+ }
+ return units;
}
+ function normalizeObjectUnits(inputObject) {
+ var normalizedInput = {},
+ normalizedProp,
+ prop;
+
+ for (prop in inputObject) {
+ if (inputObject.hasOwnProperty(prop)) {
+ normalizedProp = normalizeUnits(prop);
+ if (normalizedProp) {
+ normalizedInput[normalizedProp] = inputObject[prop];
+ }
+ }
+ }
+
+ return normalizedInput;
+ }
+
+ function makeList(field) {
+ var count, setter;
+
+ if (field.indexOf('week') === 0) {
+ count = 7;
+ setter = 'day';
+ }
+ else if (field.indexOf('month') === 0) {
+ count = 12;
+ setter = 'month';
+ }
+ else {
+ return;
+ }
+
+ moment[field] = function (format, index) {
+ var i, getter,
+ method = moment.fn._lang[field],
+ results = [];
+
+ if (typeof format === 'number') {
+ index = format;
+ format = undefined;
+ }
+
+ getter = function (i) {
+ var m = moment().utc().set(setter, i);
+ return method.call(moment.fn._lang, m, format || '');
+ };
+
+ if (index != null) {
+ return getter(index);
+ }
+ else {
+ for (i = 0; i < count; i++) {
+ results.push(getter(i));
+ }
+ return results;
+ }
+ };
+ }
+
+ function toInt(argumentForCoercion) {
+ var coercedNumber = +argumentForCoercion,
+ value = 0;
+
+ if (coercedNumber !== 0 && isFinite(coercedNumber)) {
+ if (coercedNumber >= 0) {
+ value = Math.floor(coercedNumber);
+ } else {
+ value = Math.ceil(coercedNumber);
+ }
+ }
+
+ return value;
+ }
+
+ function daysInMonth(year, month) {
+ return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
+ }
+
+ function daysInYear(year) {
+ return isLeapYear(year) ? 366 : 365;
+ }
+
+ function isLeapYear(year) {
+ return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+ }
+
+ function checkOverflow(m) {
+ var overflow;
+ if (m._a && m._pf.overflow === -2) {
+ overflow =
+ m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH :
+ m._a[DATE] < 1 || m._a[DATE] > daysInMonth(m._a[YEAR], m._a[MONTH]) ? DATE :
+ m._a[HOUR] < 0 || m._a[HOUR] > 23 ? HOUR :
+ m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE :
+ m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND :
+ m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND :
+ -1;
+
+ if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
+ overflow = DATE;
+ }
+
+ m._pf.overflow = overflow;
+ }
+ }
+
+ function isValid(m) {
+ if (m._isValid == null) {
+ m._isValid = !isNaN(m._d.getTime()) &&
+ m._pf.overflow < 0 &&
+ !m._pf.empty &&
+ !m._pf.invalidMonth &&
+ !m._pf.nullInput &&
+ !m._pf.invalidFormat &&
+ !m._pf.userInvalidated;
+
+ if (m._strict) {
+ m._isValid = m._isValid &&
+ m._pf.charsLeftOver === 0 &&
+ m._pf.unusedTokens.length === 0;
+ }
+ }
+ return m._isValid;
+ }
+
+ function normalizeLanguage(key) {
+ return key ? key.toLowerCase().replace('_', '-') : key;
+ }
+
+ // Return a moment from input, that is local/utc/zone equivalent to model.
+ function makeAs(input, model) {
+ return model._isUTC ? moment(input).zone(model._offset || 0) :
+ moment(input).local();
+ }
/************************************
Languages
************************************/
- Language.prototype = {
+ extend(Language.prototype, {
+
set : function (config) {
var prop, i;
for (i in config) {
@@ -404,7 +657,7 @@
for (i = 0; i < 12; i++) {
// make the regex if we don't have it already
if (!this._monthsParse[i]) {
- mom = moment([2000, i]);
+ mom = moment.utc([2000, i]);
regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
}
@@ -470,7 +723,9 @@
},
isPM : function (input) {
- return ((input + '').toLowerCase()[0] === 'p');
+ // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
+ // Using charAt should be more compatible.
+ return ((input + '').toLowerCase().charAt(0) === 'p');
},
_meridiemParse : /[ap]\.?m?\.?/i,
@@ -537,11 +792,17 @@
week : function (mom) {
return weekOfYear(mom, this._week.dow, this._week.doy).week;
},
+
_week : {
dow : 0, // Sunday is the first day of the week.
doy : 6 // The week that contains Jan 1st is the first week of the year.
+ },
+
+ _invalidDate: 'Invalid date',
+ invalidDate: function () {
+ return this._invalidDate;
}
- };
+ });
// Loads a language definition into the `languages` cache. The function
// takes a key and optionally values. If not in the browser and no values
@@ -556,6 +817,11 @@
return languages[key];
}
+ // Remove a language from the `languages` cache. Mostly useful in tests.
+ function unloadLang(key) {
+ delete languages[key];
+ }
+
// Determines which language definition to use and returns it.
//
// With no parameters, it will return the global language. If you
@@ -563,20 +829,52 @@
// definition for 'en', so long as 'en' has already been loaded using
// moment.lang.
function getLangDefinition(key) {
+ var i = 0, j, lang, next, split,
+ get = function (k) {
+ if (!languages[k] && hasModule) {
+ try {
+ require('./lang/' + k);
+ } catch (e) { }
+ }
+ return languages[k];
+ };
+
if (!key) {
return moment.fn._lang;
}
- if (!languages[key] && hasModule) {
- try {
- require('./lang/' + key);
- } catch (e) {
- // call with no params to set to default
- return moment.fn._lang;
- }
- }
- return languages[key];
- }
+ if (!isArray(key)) {
+ //short-circuit everything else
+ lang = get(key);
+ if (lang) {
+ return lang;
+ }
+ key = [key];
+ }
+
+ //pick the language from the array
+ //try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
+ //substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
+ while (i < key.length) {
+ split = normalizeLanguage(key[i]).split('-');
+ j = split.length;
+ next = normalizeLanguage(key[i + 1]);
+ next = next ? next.split('-') : null;
+ while (j > 0) {
+ lang = get(split.slice(0, j).join('-'));
+ if (lang) {
+ return lang;
+ }
+ if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
+ //the next array item is better than a shallower substring of this one
+ break;
+ }
+ j--;
+ }
+ i++;
+ }
+ return moment.fn._lang;
+ }
/************************************
Formatting
@@ -584,7 +882,7 @@
function removeFormattingTokens(input) {
- if (input.match(/\[.*\]/)) {
+ if (input.match(/\[[\s\S]/)) {
return input.replace(/^\[|\]$/g, "");
}
return input.replace(/\\/g, "");
@@ -612,15 +910,12 @@
// format date using native date object
function formatMoment(m, format) {
- var i = 5;
- function replaceLongDateFormatTokens(input) {
- return m.lang().longDateFormat(input) || input;
+ if (!m.isValid()) {
+ return m.lang().invalidDate();
}
- while (i-- && localFormattingTokens.test(format)) {
- format = format.replace(localFormattingTokens, replaceLongDateFormatTokens);
- }
+ format = expandFormat(format, m.lang());
if (!formatFunctions[format]) {
formatFunctions[format] = makeFormatFunction(format);
@@ -629,6 +924,23 @@
return formatFunctions[format](m);
}
+ function expandFormat(format, lang) {
+ var i = 5;
+
+ function replaceLongDateFormatTokens(input) {
+ return lang.longDateFormat(input) || input;
+ }
+
+ localFormattingTokens.lastIndex = 0;
+ while (i >= 0 && localFormattingTokens.test(format)) {
+ format = format.replace(localFormattingTokens, replaceLongDateFormatTokens);
+ localFormattingTokens.lastIndex = 0;
+ i -= 1;
+ }
+
+ return format;
+ }
+
/************************************
Parsing
@@ -637,16 +949,32 @@
// get the regex to find the next token
function getParseRegexForToken(token, config) {
+ var a, strict = config._strict;
switch (token) {
case 'DDDD':
return parseTokenThreeDigits;
case 'YYYY':
- return parseTokenFourDigits;
+ case 'GGGG':
+ case 'gggg':
+ return strict ? parseTokenFourDigits : parseTokenOneToFourDigits;
+ case 'Y':
+ case 'G':
+ case 'g':
+ return parseTokenSignedNumber;
+ case 'YYYYYY':
case 'YYYYY':
- return parseTokenSixDigits;
+ case 'GGGGG':
+ case 'ggggg':
+ return strict ? parseTokenSixDigits : parseTokenOneToSixDigits;
case 'S':
+ if (strict) { return parseTokenOneDigit; }
+ /* falls through */
case 'SS':
+ if (strict) { return parseTokenTwoDigits; }
+ /* falls through */
case 'SSS':
+ if (strict) { return parseTokenThreeDigits; }
+ /* falls through */
case 'DDD':
return parseTokenOneToThreeDigits;
case 'MMM':
@@ -665,13 +993,20 @@
return parseTokenTimezone;
case 'T':
return parseTokenT;
+ case 'SSSS':
+ return parseTokenDigits;
case 'MM':
case 'DD':
case 'YY':
+ case 'GG':
+ case 'gg':
case 'HH':
case 'hh':
case 'mm':
case 'ss':
+ case 'ww':
+ case 'WW':
+ return strict ? parseTokenTwoDigits : parseTokenOneOrTwoDigits;
case 'M':
case 'D':
case 'd':
@@ -679,16 +1014,23 @@
case 'h':
case 'm':
case 's':
+ case 'w':
+ case 'W':
+ case 'e':
+ case 'E':
return parseTokenOneOrTwoDigits;
default :
- return new RegExp(token.replace('\\', ''));
+ a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), "i"));
+ return a;
}
}
function timezoneMinutesFromString(string) {
- var tzchunk = (parseTokenTimezone.exec(string) || [])[0],
- parts = (tzchunk + '').match(parseTimezoneChunker) || ['-', 0, 0],
- minutes = +(parts[1] * 60) + ~~parts[2];
+ string = string || "";
+ var possibleTzMatches = (string.match(parseTokenTimezone) || []),
+ tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [],
+ parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0],
+ minutes = +(parts[1] * 60) + toInt(parts[2]);
return parts[0] === '+' ? -minutes : minutes;
}
@@ -701,34 +1043,43 @@
// MONTH
case 'M' : // fall through to MM
case 'MM' :
- datePartArray[1] = (input == null) ? 0 : ~~input - 1;
+ if (input != null) {
+ datePartArray[MONTH] = toInt(input) - 1;
+ }
break;
case 'MMM' : // fall through to MMMM
case 'MMMM' :
a = getLangDefinition(config._l).monthsParse(input);
// if we didn't find a month name, mark the date as invalid.
if (a != null) {
- datePartArray[1] = a;
+ datePartArray[MONTH] = a;
} else {
- config._isValid = false;
+ config._pf.invalidMonth = input;
}
break;
// DAY OF MONTH
- case 'D' : // fall through to DDDD
- case 'DD' : // fall through to DDDD
+ case 'D' : // fall through to DD
+ case 'DD' :
+ if (input != null) {
+ datePartArray[DATE] = toInt(input);
+ }
+ break;
+ // DAY OF YEAR
case 'DDD' : // fall through to DDDD
case 'DDDD' :
if (input != null) {
- datePartArray[2] = ~~input;
+ config._dayOfYear = toInt(input);
}
+
break;
// YEAR
case 'YY' :
- datePartArray[0] = ~~input + (~~input > 68 ? 1900 : 2000);
+ datePartArray[YEAR] = toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
break;
case 'YYYY' :
case 'YYYYY' :
- datePartArray[0] = ~~input;
+ case 'YYYYYY' :
+ datePartArray[YEAR] = toInt(input);
break;
// AM / PM
case 'a' : // fall through to A
@@ -740,23 +1091,24 @@
case 'HH' : // fall through to hh
case 'h' : // fall through to hh
case 'hh' :
- datePartArray[3] = ~~input;
+ datePartArray[HOUR] = toInt(input);
break;
// MINUTE
case 'm' : // fall through to mm
case 'mm' :
- datePartArray[4] = ~~input;
+ datePartArray[MINUTE] = toInt(input);
break;
// SECOND
case 's' : // fall through to ss
case 'ss' :
- datePartArray[5] = ~~input;
+ datePartArray[SECOND] = toInt(input);
break;
// MILLISECOND
case 'S' :
case 'SS' :
case 'SSS' :
- datePartArray[6] = ~~ (('0.' + input) * 1000);
+ case 'SSSS' :
+ datePartArray[MILLISECOND] = toInt(('0.' + input) * 1000);
break;
// UNIX TIMESTAMP WITH MS
case 'X':
@@ -768,11 +1120,29 @@
config._useUTC = true;
config._tzm = timezoneMinutesFromString(input);
break;
- }
-
- // if the input is null, the date is not valid
- if (input == null) {
- config._isValid = false;
+ case 'w':
+ case 'ww':
+ case 'W':
+ case 'WW':
+ case 'd':
+ case 'dd':
+ case 'ddd':
+ case 'dddd':
+ case 'e':
+ case 'E':
+ token = token.substr(0, 1);
+ /* falls through */
+ case 'gg':
+ case 'gggg':
+ case 'GG':
+ case 'GGGG':
+ case 'GGGGG':
+ token = token.substr(0, 2);
+ if (input) {
+ config._w = config._w || {};
+ config._w[token] = input;
+ }
+ break;
}
}
@@ -780,124 +1150,257 @@
// the array should mirror the parameters below
// note: all values past the year are optional and will default to the lowest possible value.
// [year, month, day , hour, minute, second, millisecond]
- function dateFromArray(config) {
- var i, date, input = [];
+ function dateFromConfig(config) {
+ var i, date, input = [], currentDate,
+ yearToUse, fixYear, w, temp, lang, weekday, week;
if (config._d) {
return;
}
- for (i = 0; i < 7; i++) {
+ currentDate = currentDateArray(config);
+
+ //compute day of the year from weeks and weekdays
+ if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
+ fixYear = function (val) {
+ var int_val = parseInt(val, 10);
+ return val ?
+ (val.length < 3 ? (int_val > 68 ? 1900 + int_val : 2000 + int_val) : int_val) :
+ (config._a[YEAR] == null ? moment().weekYear() : config._a[YEAR]);
+ };
+
+ w = config._w;
+ if (w.GG != null || w.W != null || w.E != null) {
+ temp = dayOfYearFromWeeks(fixYear(w.GG), w.W || 1, w.E, 4, 1);
+ }
+ else {
+ lang = getLangDefinition(config._l);
+ weekday = w.d != null ? parseWeekday(w.d, lang) :
+ (w.e != null ? parseInt(w.e, 10) + lang._week.dow : 0);
+
+ week = parseInt(w.w, 10) || 1;
+
+ //if we're parsing 'd', then the low day numbers may be next week
+ if (w.d != null && weekday < lang._week.dow) {
+ week++;
+ }
+
+ temp = dayOfYearFromWeeks(fixYear(w.gg), week, weekday, lang._week.doy, lang._week.dow);
+ }
+
+ config._a[YEAR] = temp.year;
+ config._dayOfYear = temp.dayOfYear;
+ }
+
+ //if the day of the year is set, figure out what it is
+ if (config._dayOfYear) {
+ yearToUse = config._a[YEAR] == null ? currentDate[YEAR] : config._a[YEAR];
+
+ if (config._dayOfYear > daysInYear(yearToUse)) {
+ config._pf._overflowDayOfYear = true;
+ }
+
+ date = makeUTCDate(yearToUse, 0, config._dayOfYear);
+ config._a[MONTH] = date.getUTCMonth();
+ config._a[DATE] = date.getUTCDate();
+ }
+
+ // Default to current date.
+ // * if no year, month, day of month are given, default to today
+ // * if day of month is given, default month and year
+ // * if month is given, default only year
+ // * if year is given, don't default anything
+ for (i = 0; i < 3 && config._a[i] == null; ++i) {
+ config._a[i] = input[i] = currentDate[i];
+ }
+
+ // Zero out whatever was not defaulted, including time
+ for (; i < 7; i++) {
config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
}
// add the offsets to the time to be parsed so that we can have a clean array for checking isValid
- input[3] += ~~((config._tzm || 0) / 60);
- input[4] += ~~((config._tzm || 0) % 60);
+ input[HOUR] += toInt((config._tzm || 0) / 60);
+ input[MINUTE] += toInt((config._tzm || 0) % 60);
- date = new Date(0);
+ config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input);
+ }
- if (config._useUTC) {
- date.setUTCFullYear(input[0], input[1], input[2]);
- date.setUTCHours(input[3], input[4], input[5], input[6]);
- } else {
- date.setFullYear(input[0], input[1], input[2]);
- date.setHours(input[3], input[4], input[5], input[6]);
+ function dateFromObject(config) {
+ var normalizedInput;
+
+ if (config._d) {
+ return;
}
- config._d = date;
+ normalizedInput = normalizeObjectUnits(config._i);
+ config._a = [
+ normalizedInput.year,
+ normalizedInput.month,
+ normalizedInput.day,
+ normalizedInput.hour,
+ normalizedInput.minute,
+ normalizedInput.second,
+ normalizedInput.millisecond
+ ];
+
+ dateFromConfig(config);
+ }
+
+ function currentDateArray(config) {
+ var now = new Date();
+ if (config._useUTC) {
+ return [
+ now.getUTCFullYear(),
+ now.getUTCMonth(),
+ now.getUTCDate()
+ ];
+ } else {
+ return [now.getFullYear(), now.getMonth(), now.getDate()];
+ }
}
// date from string and format string
function makeDateFromStringAndFormat(config) {
- // This array is used to make a Date, either with `new Date` or `Date.UTC`
- var tokens = config._f.match(formattingTokens),
- string = config._i,
- i, parsedInput;
config._a = [];
+ config._pf.empty = true;
+
+ // This array is used to make a Date, either with `new Date` or `Date.UTC`
+ var lang = getLangDefinition(config._l),
+ string = '' + config._i,
+ i, parsedInput, tokens, token, skipped,
+ stringLength = string.length,
+ totalParsedInputLength = 0;
+
+ tokens = expandFormat(config._f, lang).match(formattingTokens) || [];
for (i = 0; i < tokens.length; i++) {
- parsedInput = (getParseRegexForToken(tokens[i], config).exec(string) || [])[0];
+ token = tokens[i];
+ parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
if (parsedInput) {
+ skipped = string.substr(0, string.indexOf(parsedInput));
+ if (skipped.length > 0) {
+ config._pf.unusedInput.push(skipped);
+ }
string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
+ totalParsedInputLength += parsedInput.length;
}
- // don't parse if its not a known token
- if (formatTokenFunctions[tokens[i]]) {
- addTimeToArrayFromToken(tokens[i], parsedInput, config);
+ // don't parse if it's not a known token
+ if (formatTokenFunctions[token]) {
+ if (parsedInput) {
+ config._pf.empty = false;
+ }
+ else {
+ config._pf.unusedTokens.push(token);
+ }
+ addTimeToArrayFromToken(token, parsedInput, config);
+ }
+ else if (config._strict && !parsedInput) {
+ config._pf.unusedTokens.push(token);
}
}
- // add remaining unparsed input to the string
- if (string) {
- config._il = string;
+ // add remaining unparsed input length to the string
+ config._pf.charsLeftOver = stringLength - totalParsedInputLength;
+ if (string.length > 0) {
+ config._pf.unusedInput.push(string);
}
// handle am pm
- if (config._isPm && config._a[3] < 12) {
- config._a[3] += 12;
+ if (config._isPm && config._a[HOUR] < 12) {
+ config._a[HOUR] += 12;
}
// if is 12 am, change hours to 0
- if (config._isPm === false && config._a[3] === 12) {
- config._a[3] = 0;
+ if (config._isPm === false && config._a[HOUR] === 12) {
+ config._a[HOUR] = 0;
}
- // return
- dateFromArray(config);
+
+ dateFromConfig(config);
+ checkOverflow(config);
+ }
+
+ function unescapeFormat(s) {
+ return s.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
+ return p1 || p2 || p3 || p4;
+ });
+ }
+
+ // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
+ function regexpEscape(s) {
+ return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}
// date from string and array of format strings
function makeDateFromStringAndArray(config) {
var tempConfig,
- tempMoment,
bestMoment,
- scoreToBeat = 99,
+ scoreToBeat,
i,
currentScore;
+ if (config._f.length === 0) {
+ config._pf.invalidFormat = true;
+ config._d = new Date(NaN);
+ return;
+ }
+
for (i = 0; i < config._f.length; i++) {
+ currentScore = 0;
tempConfig = extend({}, config);
+ tempConfig._pf = defaultParsingFlags();
tempConfig._f = config._f[i];
makeDateFromStringAndFormat(tempConfig);
- tempMoment = new Moment(tempConfig);
- currentScore = compareArrays(tempConfig._a, tempMoment.toArray());
-
- // if there is any input that was not parsed
- // add a penalty for that format
- if (tempMoment._il) {
- currentScore += tempMoment._il.length;
+ if (!isValid(tempConfig)) {
+ continue;
}
- if (currentScore < scoreToBeat) {
+ // if there is any input that was not parsed add a penalty for that format
+ currentScore += tempConfig._pf.charsLeftOver;
+
+ //or tokens
+ currentScore += tempConfig._pf.unusedTokens.length * 10;
+
+ tempConfig._pf.score = currentScore;
+
+ if (scoreToBeat == null || currentScore < scoreToBeat) {
scoreToBeat = currentScore;
- bestMoment = tempMoment;
+ bestMoment = tempConfig;
}
}
- extend(config, bestMoment);
+ extend(config, bestMoment || tempConfig);
}
// date from iso format
function makeDateFromString(config) {
- var i,
+ var i, l,
string = config._i,
match = isoRegex.exec(string);
if (match) {
- // match[2] should be "T" or undefined
- config._f = 'YYYY-MM-DD' + (match[2] || " ");
- for (i = 0; i < 4; i++) {
+ config._pf.iso = true;
+ for (i = 0, l = isoDates.length; i < l; i++) {
+ if (isoDates[i][1].exec(string)) {
+ // match[5] should be "T" or undefined
+ config._f = isoDates[i][0] + (match[6] || " ");
+ break;
+ }
+ }
+ for (i = 0, l = isoTimes.length; i < l; i++) {
if (isoTimes[i][1].exec(string)) {
config._f += isoTimes[i][0];
break;
}
}
- if (parseTokenTimezone.exec(string)) {
- config._f += " Z";
+ if (string.match(parseTokenTimezone)) {
+ config._f += "Z";
}
makeDateFromStringAndFormat(config);
- } else {
+ }
+ else {
config._d = new Date(string);
}
}
@@ -914,12 +1417,50 @@
makeDateFromString(config);
} else if (isArray(input)) {
config._a = input.slice(0);
- dateFromArray(config);
+ dateFromConfig(config);
+ } else if (isDate(input)) {
+ config._d = new Date(+input);
+ } else if (typeof(input) === 'object') {
+ dateFromObject(config);
} else {
- config._d = input instanceof Date ? new Date(+input) : new Date(input);
+ config._d = new Date(input);
}
}
+ function makeDate(y, m, d, h, M, s, ms) {
+ //can't just apply() to create a date:
+ //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply
+ var date = new Date(y, m, d, h, M, s, ms);
+
+ //the date constructor doesn't accept years < 1970
+ if (y < 1970) {
+ date.setFullYear(y);
+ }
+ return date;
+ }
+
+ function makeUTCDate(y) {
+ var date = new Date(Date.UTC.apply(null, arguments));
+ if (y < 1970) {
+ date.setUTCFullYear(y);
+ }
+ return date;
+ }
+
+ function parseWeekday(input, language) {
+ if (typeof input === 'string') {
+ if (!isNaN(input)) {
+ input = parseInt(input, 10);
+ }
+ else {
+ input = language.weekdaysParse(input);
+ if (typeof input !== 'number') {
+ return null;
+ }
+ }
+ }
+ return input;
+ }
/************************************
Relative Time
@@ -987,6 +1528,19 @@
};
}
+ //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
+ function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) {
+ var d = makeUTCDate(year, 0, 1).getUTCDay(), daysToAdd, dayOfYear;
+
+ weekday = weekday != null ? weekday : firstDayOfWeek;
+ daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0);
+ dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1;
+
+ return {
+ year: dayOfYear > 0 ? year : year - 1,
+ dayOfYear: dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear
+ };
+ }
/************************************
Top Level Functions
@@ -996,8 +1550,8 @@
var input = config._i,
format = config._f;
- if (input === null || input === '') {
- return null;
+ if (input === null) {
+ return moment.invalid({nullInput: true});
}
if (typeof input === 'string') {
@@ -1005,7 +1559,8 @@
}
if (moment.isMoment(input)) {
- config = extend({}, input);
+ config = cloneMoment(input);
+
config._d = new Date(+input._d);
} else if (format) {
if (isArray(format)) {
@@ -1020,24 +1575,48 @@
return new Moment(config);
}
- moment = function (input, format, lang) {
- return makeMoment({
- _i : input,
- _f : format,
- _l : lang,
- _isUTC : false
- });
+ moment = function (input, format, lang, strict) {
+ var c;
+
+ if (typeof(lang) === "boolean") {
+ strict = lang;
+ lang = undefined;
+ }
+ // object construction must be done this way.
+ // https://github.com/moment/moment/issues/1423
+ c = {};
+ c._isAMomentObject = true;
+ c._i = input;
+ c._f = format;
+ c._l = lang;
+ c._strict = strict;
+ c._isUTC = false;
+ c._pf = defaultParsingFlags();
+
+ return makeMoment(c);
};
// creating with utc
- moment.utc = function (input, format, lang) {
- return makeMoment({
- _useUTC : true,
- _isUTC : true,
- _l : lang,
- _i : input,
- _f : format
- });
+ moment.utc = function (input, format, lang, strict) {
+ var c;
+
+ if (typeof(lang) === "boolean") {
+ strict = lang;
+ lang = undefined;
+ }
+ // object construction must be done this way.
+ // https://github.com/moment/moment/issues/1423
+ c = {};
+ c._isAMomentObject = true;
+ c._useUTC = true;
+ c._isUTC = true;
+ c._l = lang;
+ c._i = input;
+ c._f = format;
+ c._strict = strict;
+ c._pf = defaultParsingFlags();
+
+ return makeMoment(c).utc();
};
// creating with unix timestamp (in seconds)
@@ -1047,34 +1626,60 @@
// duration
moment.duration = function (input, key) {
- var isDuration = moment.isDuration(input),
- isNumber = (typeof input === 'number'),
- duration = (isDuration ? input._input : (isNumber ? {} : input)),
- matched = aspNetTimeSpanJsonRegex.exec(input),
+ var duration = input,
+ // matching against regexp is expensive, do it on demand
+ match = null,
sign,
- ret;
+ ret,
+ parseIso;
- if (isNumber) {
+ if (moment.isDuration(input)) {
+ duration = {
+ ms: input._milliseconds,
+ d: input._days,
+ M: input._months
+ };
+ } else if (typeof input === 'number') {
+ duration = {};
if (key) {
duration[key] = input;
} else {
duration.milliseconds = input;
}
- } else if (matched) {
- sign = (matched[1] === "-") ? -1 : 1;
+ } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) {
+ sign = (match[1] === "-") ? -1 : 1;
duration = {
y: 0,
- d: ~~matched[2] * sign,
- h: ~~matched[3] * sign,
- m: ~~matched[4] * sign,
- s: ~~matched[5] * sign,
- ms: ~~matched[6] * sign
+ d: toInt(match[DATE]) * sign,
+ h: toInt(match[HOUR]) * sign,
+ m: toInt(match[MINUTE]) * sign,
+ s: toInt(match[SECOND]) * sign,
+ ms: toInt(match[MILLISECOND]) * sign
+ };
+ } else if (!!(match = isoDurationRegex.exec(input))) {
+ sign = (match[1] === "-") ? -1 : 1;
+ parseIso = function (inp) {
+ // We'd normally use ~~inp for this, but unfortunately it also
+ // converts floats to ints.
+ // inp may be undefined, so careful calling replace on it.
+ var res = inp && parseFloat(inp.replace(',', '.'));
+ // apply sign while we're at it
+ return (isNaN(res) ? 0 : res) * sign;
+ };
+ duration = {
+ y: parseIso(match[2]),
+ M: parseIso(match[3]),
+ d: parseIso(match[4]),
+ h: parseIso(match[5]),
+ m: parseIso(match[6]),
+ s: parseIso(match[7]),
+ w: parseIso(match[8])
};
}
ret = new Duration(duration);
- if (isDuration && input.hasOwnProperty('_lang')) {
+ if (moment.isDuration(input) && input.hasOwnProperty('_lang')) {
ret._lang = input._lang;
}
@@ -1095,15 +1700,20 @@
// no arguments are passed in, it will simply return the current global
// language key.
moment.lang = function (key, values) {
+ var r;
if (!key) {
return moment.fn._lang._abbr;
}
if (values) {
- loadLang(key, values);
+ loadLang(normalizeLanguage(key), values);
+ } else if (values === null) {
+ unloadLang(key);
+ key = 'en';
} else if (!languages[key]) {
getLangDefinition(key);
}
- moment.duration.fn._lang = moment.fn._lang = getLangDefinition(key);
+ r = moment.duration.fn._lang = moment.fn._lang = getLangDefinition(key);
+ return r._abbr;
};
// returns language data
@@ -1116,7 +1726,8 @@
// compare moment object
moment.isMoment = function (obj) {
- return obj instanceof Moment;
+ return obj instanceof Moment ||
+ (obj != null && obj.hasOwnProperty('_isAMomentObject'));
};
// for typechecking Duration objects
@@ -1124,13 +1735,36 @@
return obj instanceof Duration;
};
+ for (i = lists.length - 1; i >= 0; --i) {
+ makeList(lists[i]);
+ }
+
+ moment.normalizeUnits = function (units) {
+ return normalizeUnits(units);
+ };
+
+ moment.invalid = function (flags) {
+ var m = moment.utc(NaN);
+ if (flags != null) {
+ extend(m._pf, flags);
+ }
+ else {
+ m._pf.userInvalidated = true;
+ }
+
+ return m;
+ };
+
+ moment.parseZone = function (input) {
+ return moment(input).parseZone();
+ };
/************************************
Moment Prototype
************************************/
- moment.fn = Moment.prototype = {
+ extend(moment.fn = Moment.prototype, {
clone : function () {
return moment(this);
@@ -1145,7 +1779,7 @@
},
toString : function () {
- return this.format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ");
+ return this.clone().lang('en').format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ");
},
toDate : function () {
@@ -1153,7 +1787,12 @@
},
toISOString : function () {
- return formatMoment(moment(this).utc(), 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
+ var m = moment(this).utc();
+ if (0 < m.year() && m.year() <= 9999) {
+ return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
+ } else {
+ return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
+ }
},
toArray : function () {
@@ -1170,14 +1809,24 @@
},
isValid : function () {
- if (this._isValid == null) {
- if (this._a) {
- this._isValid = !compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray());
- } else {
- this._isValid = !isNaN(this._d.getTime());
- }
+ return isValid(this);
+ },
+
+ isDSTShifted : function () {
+
+ if (this._a) {
+ return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0;
}
- return !!this._isValid;
+
+ return false;
+ },
+
+ parsingFlags : function () {
+ return extend({}, this._pf);
+ },
+
+ invalidAt: function () {
+ return this._pf.overflow;
},
utc : function () {
@@ -1220,7 +1869,7 @@
},
diff : function (input, units, asFloat) {
- var that = this._isUTC ? moment(input).zone(this._offset || 0) : moment(input).local(),
+ var that = makeAs(input, this),
zoneDiff = (this.zone() - that.zone()) * 6e4,
diff, output;
@@ -1262,19 +1911,21 @@
},
calendar : function () {
- var diff = this.diff(moment().startOf('day'), 'days', true),
+ // We want to compare the start of today, vs this.
+ // Getting start-of-today depends on whether we're zone'd or not.
+ var sod = makeAs(moment(), this).startOf('day'),
+ diff = this.diff(sod, 'days', true),
format = diff < -6 ? 'sameElse' :
- diff < -1 ? 'lastWeek' :
- diff < 0 ? 'lastDay' :
- diff < 1 ? 'sameDay' :
- diff < 2 ? 'nextDay' :
- diff < 7 ? 'nextWeek' : 'sameElse';
+ diff < -1 ? 'lastWeek' :
+ diff < 0 ? 'lastDay' :
+ diff < 1 ? 'sameDay' :
+ diff < 2 ? 'nextDay' :
+ diff < 7 ? 'nextWeek' : 'sameElse';
return this.format(this.lang().calendar(format, this));
},
isLeapYear : function () {
- var year = this.year();
- return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+ return isLeapYear(this.year());
},
isDST : function () {
@@ -1285,12 +1936,7 @@
day : function (input) {
var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
if (input != null) {
- if (typeof input === 'string') {
- input = this.lang().weekdaysParse(input);
- if (typeof input !== 'number') {
- return this;
- }
- }
+ input = parseWeekday(input, this.lang());
return this.add({ d : input - day });
} else {
return day;
@@ -1299,8 +1945,7 @@
month : function (input) {
var utc = this._isUTC ? 'UTC' : '',
- dayOfMonth,
- daysInMonth;
+ dayOfMonth;
if (input != null) {
if (typeof input === 'string') {
@@ -1334,6 +1979,7 @@
this.date(1);
/* falls through */
case 'week':
+ case 'isoWeek':
case 'day':
this.hours(0);
/* falls through */
@@ -1351,13 +1997,16 @@
// weeks are a special case
if (units === 'week') {
this.weekday(0);
+ } else if (units === 'isoWeek') {
+ this.isoWeekday(1);
}
return this;
},
endOf: function (units) {
- return this.startOf(units).add(units, 1).subtract('ms', 1);
+ units = normalizeUnits(units);
+ return this.startOf(units).add((units === 'isoWeek' ? 'week' : units), 1).subtract('ms', 1);
},
isAfter: function (input, units) {
@@ -1371,8 +2020,8 @@
},
isSame: function (input, units) {
- units = typeof units !== 'undefined' ? units : 'millisecond';
- return +this.clone().startOf(units) === +moment(input).startOf(units);
+ units = units || 'ms';
+ return +this.clone().startOf(units) === +makeAs(input, this).startOf(units);
},
min: function (other) {
@@ -1413,8 +2062,28 @@
return this._isUTC ? "Coordinated Universal Time" : "";
},
+ parseZone : function () {
+ if (this._tzm) {
+ this.zone(this._tzm);
+ } else if (typeof this._i === 'string') {
+ this.zone(this._i);
+ }
+ return this;
+ },
+
+ hasAlignedHourOffset : function (input) {
+ if (!input) {
+ input = 0;
+ }
+ else {
+ input = moment(input).zone();
+ }
+
+ return (this.zone() - input) % 60 === 0;
+ },
+
daysInMonth : function () {
- return moment.utc([this.year(), this.month() + 1, 0]).date();
+ return daysInMonth(this.year(), this.month());
},
dayOfYear : function (input) {
@@ -1422,6 +2091,10 @@
return input == null ? dayOfYear : this.add("d", (input - dayOfYear));
},
+ quarter : function () {
+ return Math.ceil((this.month() + 1.0) / 3.0);
+ },
+
weekYear : function (input) {
var year = weekOfYear(this, this.lang()._week.dow, this.lang()._week.doy).year;
return input == null ? year : this.add("y", (input - year));
@@ -1443,7 +2116,7 @@
},
weekday : function (input) {
- var weekday = (this._d.getDay() + 7 - this.lang()._week.dow) % 7;
+ var weekday = (this.day() + 7 - this.lang()._week.dow) % 7;
return input == null ? weekday : this.add("d", input - weekday);
},
@@ -1454,6 +2127,19 @@
return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7);
},
+ get : function (units) {
+ units = normalizeUnits(units);
+ return this[units]();
+ },
+
+ set : function (units, value) {
+ units = normalizeUnits(units);
+ if (typeof this[units] === 'function') {
+ this[units](value);
+ }
+ return this;
+ },
+
// If passed a language key, it will set the language for this
// instance. Otherwise, it will return the language configuration
// variables for this instance.
@@ -1465,7 +2151,7 @@
return this;
}
}
- };
+ });
// helper for adding shortcuts
function makeGetterAndSetter(name, key) {
@@ -1503,7 +2189,8 @@
************************************/
- moment.duration.fn = Duration.prototype = {
+ extend(moment.duration.fn = Duration.prototype, {
+
_bubble : function () {
var milliseconds = this._milliseconds,
days = this._days,
@@ -1542,7 +2229,7 @@
return this._milliseconds +
this._days * 864e5 +
(this._months % 12) * 2592e6 +
- ~~(this._months / 12) * 31536e6;
+ toInt(this._months / 12) * 31536e6;
},
humanize : function (withSuffix) {
@@ -1591,8 +2278,34 @@
return this['as' + units.charAt(0).toUpperCase() + units.slice(1) + 's']();
},
- lang : moment.fn.lang
- };
+ lang : moment.fn.lang,
+
+ toIsoString : function () {
+ // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
+ var years = Math.abs(this.years()),
+ months = Math.abs(this.months()),
+ days = Math.abs(this.days()),
+ hours = Math.abs(this.hours()),
+ minutes = Math.abs(this.minutes()),
+ seconds = Math.abs(this.seconds() + this.milliseconds() / 1000);
+
+ if (!this.asSeconds()) {
+ // this is the same as C#'s (Noda) and python (isodate)...
+ // but not other JS (goog.date)
+ return 'P0D';
+ }
+
+ return (this.asSeconds() < 0 ? '-' : '') +
+ 'P' +
+ (years ? years + 'Y' : '') +
+ (months ? months + 'M' : '') +
+ (days ? days + 'D' : '') +
+ ((hours || minutes || seconds) ? 'T' : '') +
+ (hours ? hours + 'H' : '') +
+ (minutes ? minutes + 'M' : '') +
+ (seconds ? seconds + 'S' : '');
+ }
+ });
function makeDurationGetter(name) {
moment.duration.fn[name] = function () {
@@ -1628,7 +2341,7 @@
moment.lang('en', {
ordinal : function (number) {
var b = number % 10,
- output = (~~ (number % 100 / 10) === 1) ? 'th' :
+ output = (toInt(number % 100 / 10) === 1) ? 'th' :
(b === 1) ? 'st' :
(b === 2) ? 'nd' :
(b === 3) ? 'rd' : 'th';
@@ -1636,27 +2349,52 @@
}
});
+ /* EMBED_LANGUAGES */
/************************************
Exposing Moment
************************************/
+ function makeGlobal(deprecate) {
+ var warned = false, local_moment = moment;
+ /*global ender:false */
+ if (typeof ender !== 'undefined') {
+ return;
+ }
+ // here, `this` means `window` in the browser, or `global` on the server
+ // add `moment` as a global object via a string identifier,
+ // for Closure Compiler "advanced" mode
+ if (deprecate) {
+ global.moment = function () {
+ if (!warned && console && console.warn) {
+ warned = true;
+ console.warn(
+ "Accessing Moment through the global scope is " +
+ "deprecated, and will be removed in an upcoming " +
+ "release.");
+ }
+ return local_moment.apply(null, arguments);
+ };
+ extend(global.moment, local_moment);
+ } else {
+ global['moment'] = moment;
+ }
+ }
// CommonJS module is defined
if (hasModule) {
module.exports = moment;
- }
- /*global ender:false */
- if (typeof ender === 'undefined') {
- // here, `this` means `window` in the browser, or `global` on the server
- // add `moment` as a global object via a string identifier,
- // for Closure Compiler "advanced" mode
- this['moment'] = moment;
- }
- /*global define:false */
- if (typeof define === "function" && define.amd) {
- define("moment", [], function () {
+ makeGlobal(true);
+ } else if (typeof define === "function" && define.amd) {
+ define("moment", function (require, exports, module) {
+ if (module.config && module.config() && module.config().noGlobal !== true) {
+ // If user provided noGlobal, he is aware of global
+ makeGlobal(module.config().noGlobal === undefined);
+ }
+
return moment;
});
+ } else {
+ makeGlobal();
}
}).call(this);
\ No newline at end of file