diff --git a/app/index.html b/app/index.html
index 55dfcf5e..0c56105f 100644
--- a/app/index.html
+++ b/app/index.html
@@ -36,6 +36,7 @@
+
diff --git a/app/js/app.js b/app/js/app.js
index da882392..990be82a 100755
--- a/app/js/app.js
+++ b/app/js/app.js
@@ -99,6 +99,13 @@ var ExplorerApp = Backbone.View.extend({
model: dataset
})
},
+ {
+ id: 'timeline',
+ label: 'Timeline',
+ view: new recline.View.Timeline({
+ model: dataset
+ })
+ }
];
this.dataExplorer = new recline.View.DataExplorer({
diff --git a/src/model.js b/src/model.js
index 9d4c690a..a32e5521 100644
--- a/src/model.js
+++ b/src/model.js
@@ -198,6 +198,16 @@ my.Document = Backbone.Model.extend({
val = field.renderer(val, field, this);
}
return val;
+ },
+
+ summary: function(fields) {
+ var html = '';
+ for (key in this.attributes) {
+ if (key != 'id') {
+ html += '
' + key + ': '+ this.attributes[key] + '
';
+ }
+ }
+ return html;
}
});
diff --git a/src/view-graph.js b/src/view-graph.js
index 6b5374c4..ff3cf3b9 100644
--- a/src/view-graph.js
+++ b/src/view-graph.js
@@ -345,7 +345,7 @@ my.Graph = Backbone.View.extend({
// time series
var isDateTime = xfield.get('type') === 'date';
if (isDateTime) {
- x = new Date(x);
+ x = moment(x).toDate();
}
var yfield = self.model.fields.get(field);
var y = doc.getFieldValue(yfield);
diff --git a/src/view-slickgrid.js b/src/view-slickgrid.js
index 6635e256..f42e905e 100644
--- a/src/view-slickgrid.js
+++ b/src/view-slickgrid.js
@@ -108,8 +108,15 @@ my.SlickGrid = Backbone.View.extend({
}
columns = columns.concat(tempHiddenColumns);
+ var data = [];
- var data = this.model.currentDocuments.toJSON();
+ this.model.currentDocuments.each(function(doc){
+ var row = {};
+ self.model.fields.each(function(field){
+ row[field.id] = doc.getFieldValue(field);
+ });
+ data.push(row);
+ });
this.grid = new Slick.Grid(this.el, data, visibleColumns, options);
diff --git a/src/view-timeline.js b/src/view-timeline.js
index e3faca04..18ad35ef 100644
--- a/src/view-timeline.js
+++ b/src/view-timeline.js
@@ -28,7 +28,7 @@ my.Timeline = Backbone.View.extend({
self._initTimeline();
}
});
- this.model.fields.bind('change', function() {
+ this.model.fields.bind('reset', function() {
self._setupTemporalField();
});
this.model.currentDocuments.bind('all', function() {
@@ -79,13 +79,16 @@ my.Timeline = Backbone.View.extend({
}
};
this.model.currentDocuments.each(function(doc) {
- var tlEntry = {
- "startDate": doc.get(self.state.get('startField')),
- "endDate": doc.get(self.state.get('endField')) || null,
- "headline": String(doc.get(self.model.fields.models[0].id)),
- "text": ''
- };
- if (tlEntry.startDate) {
+ var start = doc.get(self.state.get('startField'));
+ if (start) {
+ var end = moment(doc.get(self.state.get('endField')));
+ end = end ? end.toDate() : null;
+ var tlEntry = {
+ "startDate": moment(start).toDate(),
+ "endDate": end,
+ "headline": String(doc.get('title') || ''),
+ "text": doc.summary()
+ };
out.timeline.date.push(tlEntry);
}
});
diff --git a/test/index.html b/test/index.html
index 826971da..1b9e301a 100644
--- a/test/index.html
+++ b/test/index.html
@@ -11,6 +11,7 @@
+
diff --git a/test/view-slickgrid.test.js b/test/view-slickgrid.test.js
index d43d2d1b..56b5a457 100644
--- a/test/view-slickgrid.test.js
+++ b/test/view-slickgrid.test.js
@@ -42,7 +42,7 @@ test('state', function () {
var visibleColumns = _.filter(_.pluck(dataset.fields.toArray(),'id'),function(f){
return (_.indexOf(view.state.get('hiddenColumns'),f) == -1)
});
-
+
// Hidden columns
assertPresent('.slick-header-column[title="y"]');
assertNotPresent('.slick-header-column[title="x"]');
@@ -61,4 +61,31 @@ test('state', function () {
view.remove();
});
+test('renderers', function () {
+ var dataset = Fixture.getDataset();
+
+ dataset.fields.get('country').renderer = function(val, field, doc){
+ return 'Country: ' + val;
+ };
+
+ var deriver = function(val, field, doc){
+ return doc.get('x') * 10;
+ }
+ dataset.fields.add(new recline.Model.Field({id:'computed'},{deriver:deriver}));
+
+
+ var view = new recline.View.SlickGrid({
+ model: dataset
+ });
+ $('.fixtures .test-datatable').append(view.el);
+ view.render();
+
+ // Render the grid manually
+ view.grid.init();
+
+ equal($(view.grid.getCellNode(0,view.grid.getColumnIndex('country'))).text(),'Country: DE');
+ equal($(view.grid.getCellNode(0,view.grid.getColumnIndex('computed'))).text(),'10');
+ view.remove();
+});
+
})(this.jQuery);
diff --git a/vendor/moment/1.6.2/moment.js b/vendor/moment/1.6.2/moment.js
new file mode 100644
index 00000000..c7901d27
--- /dev/null
+++ b/vendor/moment/1.6.2/moment.js
@@ -0,0 +1,918 @@
+// moment.js
+// version : 1.6.2
+// author : Tim Wood
+// license : MIT
+// momentjs.com
+
+(function (Date, undefined) {
+
+ var moment,
+ VERSION = "1.6.2",
+ round = Math.round, i,
+ // internal storage for language config files
+ languages = {},
+ currentLanguage = 'en',
+
+ // check for nodeJS
+ hasModule = (typeof module !== 'undefined'),
+
+ // parameters to check for on the lang config
+ langConfigProperties = 'months|monthsShort|monthsParse|weekdays|weekdaysShort|longDateFormat|calendar|relativeTime|ordinal|meridiem'.split('|'),
+
+ // ASP.NET json date format regex
+ aspNetJsonRegex = /^\/?Date\((\-?\d+)/i,
+
+ // format tokens
+ formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|dddd?|do?|w[o|w]?|YYYY|YY|a|A|hh?|HH?|mm?|ss?|SS?S?|zz?|ZZ?|LT|LL?L?L?)/g,
+
+ // parsing tokens
+ parseMultipleFormatChunker = /([0-9a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+)/gi,
+
+ // parsing token regexes
+ parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99
+ parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999
+ parseTokenThreeDigits = /\d{3}/, // 000 - 999
+ parseTokenFourDigits = /\d{4}/, // 0000 - 9999
+ parseTokenWord = /[0-9a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+/i, // any word characters or numbers
+ parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/i, // +00:00 -00:00 +0000 -0000 or Z
+ parseTokenT = /T/i, // T (ISO seperator)
+
+ // 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)?)?/,
+ isoFormat = 'YYYY-MM-DDTHH:mm:ssZ',
+
+ // iso time formats and regexes
+ isoTimes = [
+ ['HH:mm:ss.S', /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/]
+ ],
+
+ // timezone chunker "+10:00" > ["10", "00"] or "-1530" > ["-15", "30"]
+ parseTimezoneChunker = /([\+\-]|\d\d)/gi,
+
+ // getter and setter names
+ proxyGettersAndSetters = 'Month|Date|Hours|Minutes|Seconds|Milliseconds'.split('|'),
+ unitMillisecondFactors = {
+ 'Milliseconds' : 1,
+ 'Seconds' : 1e3,
+ 'Minutes' : 6e4,
+ 'Hours' : 36e5,
+ 'Days' : 864e5,
+ 'Months' : 2592e6,
+ 'Years' : 31536e6
+ };
+
+ // Moment prototype object
+ function Moment(date, isUTC) {
+ this._d = date;
+ this._isUTC = !!isUTC;
+ }
+
+ function absRound(number) {
+ if (number < 0) {
+ return Math.ceil(number);
+ } else {
+ return Math.floor(number);
+ }
+ }
+
+ // Duration Constructor
+ function Duration(duration) {
+ var data = this._data = {},
+ years = duration.years || duration.y || 0,
+ months = duration.months || duration.M || 0,
+ weeks = duration.weeks || duration.w || 0,
+ days = duration.days || duration.d || 0,
+ hours = duration.hours || duration.h || 0,
+ minutes = duration.minutes || duration.m || 0,
+ seconds = duration.seconds || duration.s || 0,
+ milliseconds = duration.milliseconds || duration.ms || 0;
+
+ // representation for dateAddRemove
+ 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 +
+ 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 +
+ years * 12;
+
+ // The following code bubbles up values, see the tests for
+ // examples of what that means.
+ data.milliseconds = milliseconds % 1000;
+ seconds += absRound(milliseconds / 1000);
+
+ data.seconds = seconds % 60;
+ minutes += absRound(seconds / 60);
+
+ data.minutes = minutes % 60;
+ hours += absRound(minutes / 60);
+
+ data.hours = hours % 24;
+ days += absRound(hours / 24);
+
+ days += weeks * 7;
+ data.days = days % 30;
+
+ months += absRound(days / 30);
+
+ data.months = months % 12;
+ years += absRound(months / 12);
+
+ data.years = years;
+ }
+
+ // left zero fill a number
+ // see http://jsperf.com/left-zero-filling for performance comparison
+ function leftZeroFill(number, targetLength) {
+ var output = number + '';
+ while (output.length < targetLength) {
+ output = '0' + output;
+ }
+ return output;
+ }
+
+ // helper function for _.addTime and _.subtractTime
+ function addOrSubtractDurationFromMoment(mom, duration, isAdding) {
+ var ms = duration._milliseconds,
+ d = duration._days,
+ M = duration._months,
+ currentDate;
+
+ if (ms) {
+ mom._d.setTime(+mom + ms * isAdding);
+ }
+ if (d) {
+ mom.date(mom.date() + d * isAdding);
+ }
+ if (M) {
+ currentDate = mom.date();
+ mom.date(1)
+ .month(mom.month() + M * isAdding)
+ .date(Math.min(currentDate, mom.daysInMonth()));
+ }
+ }
+
+ // check if is an array
+ function isArray(input) {
+ return Object.prototype.toString.call(input) === '[object Array]';
+ }
+
+ // convert an array to a date.
+ // 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(input) {
+ return new Date(input[0], input[1] || 0, input[2] || 1, input[3] || 0, input[4] || 0, input[5] || 0, input[6] || 0);
+ }
+
+ // format date using native date object
+ function formatMoment(m, inputString) {
+ var currentMonth = m.month(),
+ currentDate = m.date(),
+ currentYear = m.year(),
+ currentDay = m.day(),
+ currentHours = m.hours(),
+ currentMinutes = m.minutes(),
+ currentSeconds = m.seconds(),
+ currentMilliseconds = m.milliseconds(),
+ currentZone = -m.zone(),
+ ordinal = moment.ordinal,
+ meridiem = moment.meridiem;
+ // check if the character is a format
+ // return formatted string or non string.
+ //
+ // uses switch/case instead of an object of named functions (like http://phpjs.org/functions/date:380)
+ // for minification and performance
+ // see http://jsperf.com/object-of-functions-vs-switch for performance comparison
+ function replaceFunction(input) {
+ // create a couple variables to be used later inside one of the cases.
+ var a, b;
+ switch (input) {
+ // MONTH
+ case 'M' :
+ return currentMonth + 1;
+ case 'Mo' :
+ return (currentMonth + 1) + ordinal(currentMonth + 1);
+ case 'MM' :
+ return leftZeroFill(currentMonth + 1, 2);
+ case 'MMM' :
+ return moment.monthsShort[currentMonth];
+ case 'MMMM' :
+ return moment.months[currentMonth];
+ // DAY OF MONTH
+ case 'D' :
+ return currentDate;
+ case 'Do' :
+ return currentDate + ordinal(currentDate);
+ case 'DD' :
+ return leftZeroFill(currentDate, 2);
+ // DAY OF YEAR
+ case 'DDD' :
+ a = new Date(currentYear, currentMonth, currentDate);
+ b = new Date(currentYear, 0, 1);
+ return ~~ (((a - b) / 864e5) + 1.5);
+ case 'DDDo' :
+ a = replaceFunction('DDD');
+ return a + ordinal(a);
+ case 'DDDD' :
+ return leftZeroFill(replaceFunction('DDD'), 3);
+ // WEEKDAY
+ case 'd' :
+ return currentDay;
+ case 'do' :
+ return currentDay + ordinal(currentDay);
+ case 'ddd' :
+ return moment.weekdaysShort[currentDay];
+ case 'dddd' :
+ return moment.weekdays[currentDay];
+ // WEEK OF YEAR
+ case 'w' :
+ a = new Date(currentYear, currentMonth, currentDate - currentDay + 5);
+ b = new Date(a.getFullYear(), 0, 4);
+ return ~~ ((a - b) / 864e5 / 7 + 1.5);
+ case 'wo' :
+ a = replaceFunction('w');
+ return a + ordinal(a);
+ case 'ww' :
+ return leftZeroFill(replaceFunction('w'), 2);
+ // YEAR
+ case 'YY' :
+ return leftZeroFill(currentYear % 100, 2);
+ case 'YYYY' :
+ return currentYear;
+ // AM / PM
+ case 'a' :
+ return meridiem ? meridiem(currentHours, currentMinutes, false) : (currentHours > 11 ? 'pm' : 'am');
+ case 'A' :
+ return meridiem ? meridiem(currentHours, currentMinutes, true) : (currentHours > 11 ? 'PM' : 'AM');
+ // 24 HOUR
+ case 'H' :
+ return currentHours;
+ case 'HH' :
+ return leftZeroFill(currentHours, 2);
+ // 12 HOUR
+ case 'h' :
+ return currentHours % 12 || 12;
+ case 'hh' :
+ return leftZeroFill(currentHours % 12 || 12, 2);
+ // MINUTE
+ case 'm' :
+ return currentMinutes;
+ case 'mm' :
+ return leftZeroFill(currentMinutes, 2);
+ // SECOND
+ case 's' :
+ return currentSeconds;
+ case 'ss' :
+ return leftZeroFill(currentSeconds, 2);
+ // MILLISECONDS
+ case 'S' :
+ return ~~ (currentMilliseconds / 100);
+ case 'SS' :
+ return leftZeroFill(~~(currentMilliseconds / 10), 2);
+ case 'SSS' :
+ return leftZeroFill(currentMilliseconds, 3);
+ // TIMEZONE
+ case 'Z' :
+ return (currentZone < 0 ? '-' : '+') + leftZeroFill(~~(Math.abs(currentZone) / 60), 2) + ':' + leftZeroFill(~~(Math.abs(currentZone) % 60), 2);
+ case 'ZZ' :
+ return (currentZone < 0 ? '-' : '+') + leftZeroFill(~~(10 * Math.abs(currentZone) / 6), 4);
+ // LONG DATES
+ case 'L' :
+ case 'LL' :
+ case 'LLL' :
+ case 'LLLL' :
+ case 'LT' :
+ return formatMoment(m, moment.longDateFormat[input]);
+ // DEFAULT
+ default :
+ return input.replace(/(^\[)|(\\)|\]$/g, "");
+ }
+ }
+ return inputString.replace(formattingTokens, replaceFunction);
+ }
+
+ // get the regex to find the next token
+ function getParseRegexForToken(token) {
+ switch (token) {
+ case 'DDDD':
+ return parseTokenThreeDigits;
+ case 'YYYY':
+ return parseTokenFourDigits;
+ case 'S':
+ case 'SS':
+ case 'SSS':
+ case 'DDD':
+ return parseTokenOneToThreeDigits;
+ case 'MMM':
+ case 'MMMM':
+ case 'ddd':
+ case 'dddd':
+ case 'a':
+ case 'A':
+ return parseTokenWord;
+ case 'Z':
+ case 'ZZ':
+ return parseTokenTimezone;
+ case 'T':
+ return parseTokenT;
+ case 'MM':
+ case 'DD':
+ case 'dd':
+ case 'YY':
+ case 'HH':
+ case 'hh':
+ case 'mm':
+ case 'ss':
+ case 'M':
+ case 'D':
+ case 'd':
+ case 'H':
+ case 'h':
+ case 'm':
+ case 's':
+ return parseTokenOneOrTwoDigits;
+ default :
+ return new RegExp(token.replace('\\', ''));
+ }
+ }
+
+ // function to convert string input to date
+ function addTimeToArrayFromToken(token, input, datePartArray, config) {
+ var a;
+ //console.log('addTime', format, input);
+ switch (token) {
+ // MONTH
+ case 'M' : // fall through to MM
+ case 'MM' :
+ datePartArray[1] = (input == null) ? 0 : ~~input - 1;
+ break;
+ case 'MMM' : // fall through to MMMM
+ case 'MMMM' :
+ for (a = 0; a < 12; a++) {
+ if (moment.monthsParse[a].test(input)) {
+ datePartArray[1] = a;
+ break;
+ }
+ }
+ break;
+ // DAY OF MONTH
+ case 'D' : // fall through to DDDD
+ case 'DD' : // fall through to DDDD
+ case 'DDD' : // fall through to DDDD
+ case 'DDDD' :
+ datePartArray[2] = ~~input;
+ break;
+ // YEAR
+ case 'YY' :
+ input = ~~input;
+ datePartArray[0] = input + (input > 70 ? 1900 : 2000);
+ break;
+ case 'YYYY' :
+ datePartArray[0] = ~~Math.abs(input);
+ break;
+ // AM / PM
+ case 'a' : // fall through to A
+ case 'A' :
+ config.isPm = ((input + '').toLowerCase() === 'pm');
+ break;
+ // 24 HOUR
+ case 'H' : // fall through to hh
+ case 'HH' : // fall through to hh
+ case 'h' : // fall through to hh
+ case 'hh' :
+ datePartArray[3] = ~~input;
+ break;
+ // MINUTE
+ case 'm' : // fall through to mm
+ case 'mm' :
+ datePartArray[4] = ~~input;
+ break;
+ // SECOND
+ case 's' : // fall through to ss
+ case 'ss' :
+ datePartArray[5] = ~~input;
+ break;
+ // MILLISECOND
+ case 'S' :
+ case 'SS' :
+ case 'SSS' :
+ datePartArray[6] = ~~ (('0.' + input) * 1000);
+ break;
+ // TIMEZONE
+ case 'Z' : // fall through to ZZ
+ case 'ZZ' :
+ config.isUTC = true;
+ a = (input + '').match(parseTimezoneChunker);
+ if (a && a[1]) {
+ config.tzh = ~~a[1];
+ }
+ if (a && a[2]) {
+ config.tzm = ~~a[2];
+ }
+ // reverse offsets
+ if (a && a[0] === '+') {
+ config.tzh = -config.tzh;
+ config.tzm = -config.tzm;
+ }
+ break;
+ }
+ }
+
+ // date from string and format string
+ function makeDateFromStringAndFormat(string, format) {
+ var datePartArray = [0, 0, 1, 0, 0, 0, 0],
+ config = {
+ tzh : 0, // timezone hour offset
+ tzm : 0 // timezone minute offset
+ },
+ tokens = format.match(formattingTokens),
+ i, parsedInput;
+
+ for (i = 0; i < tokens.length; i++) {
+ parsedInput = (getParseRegexForToken(tokens[i]).exec(string) || [])[0];
+ string = string.replace(getParseRegexForToken(tokens[i]), '');
+ addTimeToArrayFromToken(tokens[i], parsedInput, datePartArray, config);
+ }
+ // handle am pm
+ if (config.isPm && datePartArray[3] < 12) {
+ datePartArray[3] += 12;
+ }
+ // if is 12 am, change hours to 0
+ if (config.isPm === false && datePartArray[3] === 12) {
+ datePartArray[3] = 0;
+ }
+ // handle timezone
+ datePartArray[3] += config.tzh;
+ datePartArray[4] += config.tzm;
+ // return
+ return config.isUTC ? new Date(Date.UTC.apply({}, datePartArray)) : dateFromArray(datePartArray);
+ }
+
+ // compare two arrays, return the number of differences
+ function compareArrays(array1, array2) {
+ 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]) {
+ diffs++;
+ }
+ }
+ return diffs + lengthDiff;
+ }
+
+ // date from string and array of format strings
+ function makeDateFromStringAndArray(string, formats) {
+ var output,
+ inputParts = string.match(parseMultipleFormatChunker) || [],
+ formattedInputParts,
+ scoreToBeat = 99,
+ i,
+ currentDate,
+ currentScore;
+ for (i = 0; i < formats.length; i++) {
+ currentDate = makeDateFromStringAndFormat(string, formats[i]);
+ formattedInputParts = formatMoment(new Moment(currentDate), formats[i]).match(parseMultipleFormatChunker) || [];
+ currentScore = compareArrays(inputParts, formattedInputParts);
+ if (currentScore < scoreToBeat) {
+ scoreToBeat = currentScore;
+ output = currentDate;
+ }
+ }
+ return output;
+ }
+
+ // date from iso format
+ function makeDateFromString(string) {
+ var format = 'YYYY-MM-DDT',
+ i;
+ if (isoRegex.exec(string)) {
+ for (i = 0; i < 4; i++) {
+ if (isoTimes[i][1].exec(string)) {
+ format += isoTimes[i][0];
+ break;
+ }
+ }
+ return parseTokenTimezone.exec(string) ?
+ makeDateFromStringAndFormat(string, format + ' Z') :
+ makeDateFromStringAndFormat(string, format);
+ }
+ return new Date(string);
+ }
+
+ // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
+ function substituteTimeAgo(string, number, withoutSuffix, isFuture) {
+ var rt = moment.relativeTime[string];
+ return (typeof rt === 'function') ?
+ rt(number || 1, !!withoutSuffix, string, isFuture) :
+ rt.replace(/%d/i, number || 1);
+ }
+
+ function relativeTime(milliseconds, withoutSuffix) {
+ var seconds = round(Math.abs(milliseconds) / 1000),
+ minutes = round(seconds / 60),
+ hours = round(minutes / 60),
+ days = round(hours / 24),
+ years = round(days / 365),
+ args = seconds < 45 && ['s', seconds] ||
+ minutes === 1 && ['m'] ||
+ minutes < 45 && ['mm', minutes] ||
+ hours === 1 && ['h'] ||
+ hours < 22 && ['hh', hours] ||
+ days === 1 && ['d'] ||
+ days <= 25 && ['dd', days] ||
+ days <= 45 && ['M'] ||
+ days < 345 && ['MM', round(days / 30)] ||
+ years === 1 && ['y'] || ['yy', years];
+ args[2] = withoutSuffix;
+ args[3] = milliseconds > 0;
+ return substituteTimeAgo.apply({}, args);
+ }
+
+ moment = function (input, format) {
+ if (input === null || input === '') {
+ return null;
+ }
+ var date,
+ matched,
+ isUTC;
+ // parse Moment object
+ if (moment.isMoment(input)) {
+ date = new Date(+input._d);
+ isUTC = input._isUTC;
+ // parse string and format
+ } else if (format) {
+ if (isArray(format)) {
+ date = makeDateFromStringAndArray(input, format);
+ } else {
+ date = makeDateFromStringAndFormat(input, format);
+ }
+ // evaluate it as a JSON-encoded date
+ } else {
+ matched = aspNetJsonRegex.exec(input);
+ date = input === undefined ? new Date() :
+ matched ? new Date(+matched[1]) :
+ input instanceof Date ? input :
+ isArray(input) ? dateFromArray(input) :
+ typeof input === 'string' ? makeDateFromString(input) :
+ new Date(input);
+ }
+ return new Moment(date, isUTC);
+ };
+
+ // creating with utc
+ moment.utc = function (input, format) {
+ if (isArray(input)) {
+ return new Moment(new Date(Date.UTC.apply({}, input)), true);
+ }
+ return (format && input) ?
+ moment(input + ' +0000', format + ' Z').utc() :
+ moment(input && !parseTokenTimezone.exec(input) ? input + '+0000' : input).utc();
+ };
+
+ // creating with unix timestamp (in seconds)
+ moment.unix = function (input) {
+ return moment(input * 1000);
+ };
+
+ // duration
+ moment.duration = function (input, key) {
+ var isDuration = moment.isDuration(input),
+ isNumber = (typeof input === 'number'),
+ duration = (isDuration ? input._data : (isNumber ? {} : input));
+
+ if (isNumber) {
+ if (key) {
+ duration[key] = input;
+ } else {
+ duration.milliseconds = input;
+ }
+ }
+
+ return new Duration(duration);
+ };
+
+ // humanizeDuration
+ // This method is deprecated in favor of the new Duration object. Please
+ // see the moment.duration method.
+ moment.humanizeDuration = function (num, type, withSuffix) {
+ return moment.duration(num, type === true ? null : type).humanize(type === true ? true : withSuffix);
+ };
+
+ // version number
+ moment.version = VERSION;
+
+ // default format
+ moment.defaultFormat = isoFormat;
+
+ // language switching and caching
+ moment.lang = function (key, values) {
+ var i, req,
+ parse = [];
+ if (!key) {
+ return currentLanguage;
+ }
+ if (values) {
+ for (i = 0; i < 12; i++) {
+ parse[i] = new RegExp('^' + values.months[i] + '|^' + values.monthsShort[i].replace('.', ''), 'i');
+ }
+ values.monthsParse = values.monthsParse || parse;
+ languages[key] = values;
+ }
+ if (languages[key]) {
+ for (i = 0; i < langConfigProperties.length; i++) {
+ moment[langConfigProperties[i]] = languages[key][langConfigProperties[i]] ||
+ languages.en[langConfigProperties[i]];
+ }
+ currentLanguage = key;
+ } else {
+ if (hasModule) {
+ req = require('./lang/' + key);
+ moment.lang(key, req);
+ }
+ }
+ };
+
+ // set default language
+ moment.lang('en', {
+ months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"),
+ monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),
+ weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),
+ weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),
+ longDateFormat : {
+ LT : "h:mm A",
+ L : "MM/DD/YYYY",
+ LL : "MMMM D YYYY",
+ LLL : "MMMM D YYYY LT",
+ LLLL : "dddd, MMMM D YYYY LT"
+ },
+ meridiem : false,
+ calendar : {
+ sameDay : '[Today at] LT',
+ nextDay : '[Tomorrow at] LT',
+ nextWeek : 'dddd [at] LT',
+ lastDay : '[Yesterday at] LT',
+ lastWeek : '[last] dddd [at] LT',
+ sameElse : 'L'
+ },
+ relativeTime : {
+ future : "in %s",
+ past : "%s ago",
+ s : "a few seconds",
+ m : "a minute",
+ mm : "%d minutes",
+ h : "an hour",
+ hh : "%d hours",
+ d : "a day",
+ dd : "%d days",
+ M : "a month",
+ MM : "%d months",
+ y : "a year",
+ yy : "%d years"
+ },
+ ordinal : function (number) {
+ var b = number % 10;
+ return (~~ (number % 100 / 10) === 1) ? 'th' :
+ (b === 1) ? 'st' :
+ (b === 2) ? 'nd' :
+ (b === 3) ? 'rd' : 'th';
+ }
+ });
+
+ // compare moment object
+ moment.isMoment = function (obj) {
+ return obj instanceof Moment;
+ };
+
+ // for typechecking Duration objects
+ moment.isDuration = function (obj) {
+ return obj instanceof Duration;
+ };
+
+ // shortcut for prototype
+ moment.fn = Moment.prototype = {
+
+ clone : function () {
+ return moment(this);
+ },
+
+ valueOf : function () {
+ return +this._d;
+ },
+
+ unix : function () {
+ return Math.floor(+this._d / 1000);
+ },
+
+ toString : function () {
+ return this._d.toString();
+ },
+
+ toDate : function () {
+ return this._d;
+ },
+
+ utc : function () {
+ this._isUTC = true;
+ return this;
+ },
+
+ local : function () {
+ this._isUTC = false;
+ return this;
+ },
+
+ format : function (inputString) {
+ return formatMoment(this, inputString ? inputString : moment.defaultFormat);
+ },
+
+ add : function (input, val) {
+ var dur = val ? moment.duration(+val, input) : moment.duration(input);
+ addOrSubtractDurationFromMoment(this, dur, 1);
+ return this;
+ },
+
+ subtract : function (input, val) {
+ var dur = val ? moment.duration(+val, input) : moment.duration(input);
+ addOrSubtractDurationFromMoment(this, dur, -1);
+ return this;
+ },
+
+ diff : function (input, val, asFloat) {
+ var inputMoment = this._isUTC ? moment(input).utc() : moment(input).local(),
+ zoneDiff = (this.zone() - inputMoment.zone()) * 6e4,
+ diff = this._d - inputMoment._d - zoneDiff,
+ year = this.year() - inputMoment.year(),
+ month = this.month() - inputMoment.month(),
+ date = this.date() - inputMoment.date(),
+ output;
+ if (val === 'months') {
+ output = year * 12 + month + date / 30;
+ } else if (val === 'years') {
+ output = year + (month + date / 30) / 12;
+ } else {
+ output = val === 'seconds' ? diff / 1e3 : // 1000
+ val === 'minutes' ? diff / 6e4 : // 1000 * 60
+ val === 'hours' ? diff / 36e5 : // 1000 * 60 * 60
+ val === 'days' ? diff / 864e5 : // 1000 * 60 * 60 * 24
+ val === 'weeks' ? diff / 6048e5 : // 1000 * 60 * 60 * 24 * 7
+ diff;
+ }
+ return asFloat ? output : round(output);
+ },
+
+ from : function (time, withoutSuffix) {
+ return moment.duration(this.diff(time)).humanize(!withoutSuffix);
+ },
+
+ fromNow : function (withoutSuffix) {
+ return this.from(moment(), withoutSuffix);
+ },
+
+ calendar : function () {
+ var diff = this.diff(moment().sod(), 'days', true),
+ calendar = moment.calendar,
+ allElse = calendar.sameElse,
+ format = diff < -6 ? allElse :
+ diff < -1 ? calendar.lastWeek :
+ diff < 0 ? calendar.lastDay :
+ diff < 1 ? calendar.sameDay :
+ diff < 2 ? calendar.nextDay :
+ diff < 7 ? calendar.nextWeek : allElse;
+ return this.format(typeof format === 'function' ? format.apply(this) : format);
+ },
+
+ isLeapYear : function () {
+ var year = this.year();
+ return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+ },
+
+ isDST : function () {
+ return (this.zone() < moment([this.year()]).zone() ||
+ this.zone() < moment([this.year(), 5]).zone());
+ },
+
+ day : function (input) {
+ var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
+ return input == null ? day :
+ this.add({ d : input - day });
+ },
+
+ sod: function () {
+ return moment(this)
+ .hours(0)
+ .minutes(0)
+ .seconds(0)
+ .milliseconds(0);
+ },
+
+ eod: function () {
+ // end of day = start of day plus 1 day, minus 1 millisecond
+ return this.sod().add({
+ d : 1,
+ ms : -1
+ });
+ },
+
+ zone : function () {
+ return this._isUTC ? 0 : this._d.getTimezoneOffset();
+ },
+
+ daysInMonth : function () {
+ return moment(this).month(this.month() + 1).date(0).date();
+ }
+ };
+
+ // helper for adding shortcuts
+ function makeGetterAndSetter(name, key) {
+ moment.fn[name] = function (input) {
+ var utc = this._isUTC ? 'UTC' : '';
+ if (input != null) {
+ this._d['set' + utc + key](input);
+ return this;
+ } else {
+ return this._d['get' + utc + key]();
+ }
+ };
+ }
+
+ // loop through and add shortcuts (Month, Date, Hours, Minutes, Seconds, Milliseconds)
+ for (i = 0; i < proxyGettersAndSetters.length; i ++) {
+ makeGetterAndSetter(proxyGettersAndSetters[i].toLowerCase(), proxyGettersAndSetters[i]);
+ }
+
+ // add shortcut for year (uses different syntax than the getter/setter 'year' == 'FullYear')
+ makeGetterAndSetter('year', 'FullYear');
+
+ moment.duration.fn = Duration.prototype = {
+ weeks : function () {
+ return absRound(this.days() / 7);
+ },
+
+ valueOf : function () {
+ return this._milliseconds +
+ this._days * 864e5 +
+ this._months * 2592e6;
+ },
+
+ humanize : function (withSuffix) {
+ var difference = +this,
+ rel = moment.relativeTime,
+ output = relativeTime(difference, !withSuffix);
+
+ if (withSuffix) {
+ output = (difference <= 0 ? rel.past : rel.future).replace(/%s/i, output);
+ }
+
+ return output;
+ }
+ };
+
+ function makeDurationGetter(name) {
+ moment.duration.fn[name] = function () {
+ return this._data[name];
+ };
+ }
+
+ function makeDurationAsGetter(name, factor) {
+ moment.duration.fn['as' + name] = function () {
+ return +this / factor;
+ };
+ }
+
+ for (i in unitMillisecondFactors) {
+ if (unitMillisecondFactors.hasOwnProperty(i)) {
+ makeDurationAsGetter(i, unitMillisecondFactors[i]);
+ makeDurationGetter(i.toLowerCase());
+ }
+ }
+
+ makeDurationAsGetter('Weeks', 6048e5);
+
+ // CommonJS module is defined
+ if (hasModule) {
+ module.exports = moment;
+ }
+ /*global ender:false */
+ if (typeof window !== 'undefined' && typeof ender === 'undefined') {
+ window.moment = moment;
+ }
+ /*global define:false */
+ if (typeof define === "function" && define.amd) {
+ define("moment", [], function () {
+ return moment;
+ });
+ }
+})(Date);