Date math parser and tests

This commit is contained in:
Rashid Khan 2014-04-16 11:11:29 -07:00
parent 5918df6a80
commit 00844d67bf
3 changed files with 236 additions and 2 deletions

View file

@ -0,0 +1,91 @@
define(function (require) {
var _ = require('lodash');
var moment = require('moment');
/* This is a simplified version of elasticsearch's date parser */
var parse = function (text, roundUp) {
if (moment.isMoment(text)) return text;
if (_.isDate(text)) return moment(text);
var time,
mathString = '',
index,
parseString;
if (text.substring(0, 3) === 'now') {
time = moment();
mathString = text.substring('now'.length);
} else {
index = text.indexOf('||');
if (index === -1) {
parseString = text;
mathString = ''; // nothing else
} else {
parseString = text.substring(0, index);
mathString = text.substring(index + 2);
}
// We're going to just require ISO8601 timestamps, k?
time = moment(parseString);
}
if (!mathString.length) {
return time;
}
return parseDateMath(mathString, time, roundUp);
};
var parseDateMath = function (mathString, time, roundUp) {
var dateTime = time,
spans = ['s', 'm', 'h', 'd', 'w', 'M', 'y'];
for (var i = 0; i < mathString.length;) {
var c = mathString.charAt(i++),
type,
num,
unit;
if (c === '/') {
type = 0;
} else if (c === '+') {
type = 1;
} else if (c === '-') {
type = 2;
} else {
return undefined;
}
if (isNaN(mathString.charAt(i))) {
num = 1;
} else {
var numFrom = i;
while (!isNaN(mathString.charAt(i))) {
i++;
}
num = parseInt(mathString.substring(numFrom, i), 10);
}
if (type === 0) {
// rounding is only allowed on whole, single, units (eg M or 1M, not 0.5M or 2M)
if (num !== 1) {
return undefined;
}
}
unit = mathString.charAt(i++);
if (!_.contains(spans, unit)) {
return undefined;
} else {
if (type === 0) {
roundUp ? dateTime.endOf(unit) : dateTime.startOf(unit);
} else if (type === 1) {
dateTime.add(unit, num);
} else if (type === 2) {
dateTime.subtract(unit, num);
}
}
}
return dateTime;
};
return {
parse: parse
};
});

View file

@ -48,9 +48,8 @@
function runTests() {
require([
'sinon/sinon',
'specs/apps/dashboard/index',
'specs/apps/dashboard/directives/panel',
'specs/apps/dashboard/directives/grid',
'specs/utils/datemath',
'specs/services/state',
'specs/courier/index'
], function (sinon) {

View file

@ -0,0 +1,144 @@
define(function (require) {
var datemath = require('utils/datemath');
var moment = require('moment');
var _ = require('lodash');
var sinon = require('sinon/sinon');
describe('datemath', function () {
// Test each of these intervals when testing relative time
var spans = ['s', 'm', 'h', 'd', 'w', 'M', 'y'],
anchor = '2014-01-01T06:06:06.666Z',
unix = moment(anchor).valueOf(),
format = 'YYYY-MM-DDTHH:mm:ss.SSSZ',
clock;
describe('errors', function () {
it('should return undefined if I pass an operator besides [+-/]', function () {
expect(datemath.parse('now&1d')).to.be(undefined);
});
it('should return undefined if I pass a unit besides' + spans.toString(), function () {
expect(datemath.parse('now+5f')).to.be(undefined);
});
it('should return undefined if rounding unit is not 1', function () {
expect(datemath.parse('now/2y')).to.be(undefined);
expect(datemath.parse('now/0.5y')).to.be(undefined);
});
});
describe('objects and strings', function () {
var mmnt, date, string, now;
beforeEach(function () {
clock = sinon.useFakeTimers(unix);
now = moment();
mmnt = moment(anchor);
date = mmnt.toDate();
string = mmnt.format(format);
});
afterEach(function () {
clock.restore();
});
it('should return the same moment if passed a moment', function () {
expect(datemath.parse(mmnt)).to.eql(mmnt);
});
it('should return a moment if passed a date', function () {
expect(datemath.parse(date).format(format)).to.eql(mmnt.format(format));
});
it('should return a moment if passed an ISO8601 string', function () {
expect(datemath.parse(string).format(format)).to.eql(mmnt.format(format));
});
it('should return the current time if passed now', function () {
expect(datemath.parse('now').format(format)).to.eql(now.format(format));
});
});
describe('subtraction', function () {
var now, anchored;
beforeEach(function () {
clock = sinon.useFakeTimers(unix);
now = moment();
anchored = moment(anchor);
});
afterEach(function () {
clock.restore();
});
_.each(spans, function (span) {
var nowEx = 'now-5' + span;
var thenEx = anchor + '||-5' + span;
it('should return 5' + span + ' ago', function () {
expect(datemath.parse(nowEx).format(format)).to.eql(now.subtract(5, span).format(format));
});
it('should return 5' + span + ' before ' + anchor, function () {
expect(datemath.parse(thenEx).format(format)).to.eql(anchored.subtract(5, span).format(format));
});
});
});
describe('addition', function () {
var now, anchored;
beforeEach(function () {
clock = sinon.useFakeTimers(unix);
now = moment();
anchored = moment(anchor);
});
afterEach(function () {
clock.restore();
});
_.each(spans, function (span) {
var nowEx = 'now+5' + span;
var thenEx = anchor + '||+5' + span;
it('should return 5' + span + ' from now', function () {
expect(datemath.parse(nowEx).format()).to.eql(now.add(5, span).format());
});
it('should return 5' + span + ' after ' + anchor, function () {
expect(datemath.parse(thenEx).format()).to.eql(anchored.add(5, span).format());
});
});
});
describe('rounding', function () {
var now, anchored;
beforeEach(function () {
clock = sinon.useFakeTimers(unix);
now = moment();
anchored = moment(anchor);
});
afterEach(function () {
clock.restore();
});
_.each(spans, function (span) {
it('should round now to the beginning of the ' + span, function () {
expect(datemath.parse('now/' + span).format(format)).to.eql(now.startOf(span).format(format));
});
it('should round now to the end of the ' + span, function () {
expect(datemath.parse('now/' + span, true).format(format)).to.eql(now.endOf(span).format(format));
});
});
});
});
});