Skip to content

Commit

Permalink
Merge pull request #137 from santigimeno/tz
Browse files Browse the repository at this point in the history
WIP: Timezone support
  • Loading branch information
tejasmanohar committed Apr 29, 2015
2 parents fae30c0 + e5a00d3 commit 03a2dfb
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 19 deletions.
56 changes: 45 additions & 11 deletions lib/schedule.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,27 @@
var events = require('events'),
util = require('util'),
cronParser = require('cron-parser'),
lt = require('long-timeout');
lt = require('long-timeout'),
time = require('time');

/* Extend Date to add timezone support */
time(Date);

/* Job object */
var anonJobCounter = 0;

/* Jobs names corresponding to Jobs indexes */
var jobsIndexName = {};

function newDate(opts) {
var args = opts ? opts.args : null;
var tz = opts ? opts.tz : null;
tz = tz || time.currentTimezone;
var date = args ? new Date(args) : new Date();
date.setTimezone(tz);
return date;
}

function Job() {
var name;

Expand Down Expand Up @@ -136,8 +149,9 @@ Job.prototype.runOnDate = function(date) {
return this.schedule(date);
};

Job.prototype.schedule = function(spec) {
Job.prototype.schedule = function(spec, tz) {
var self = this;
this.tz = tz;
var success = false;
var inv;
try {
Expand All @@ -154,6 +168,10 @@ Job.prototype.schedule = function(spec) {
}

if (spec instanceof Date) {
if (tz) {
spec.setTimezone(tz);
}

inv = new Invocation(self, spec);
scheduleInvocation(inv);
success = self.trackInvocation(inv);
Expand Down Expand Up @@ -267,18 +285,19 @@ function RecurrenceRule(year, month, date, dayOfWeek, hour, minute, second) {
this.second = (second == null) ? 0 : second;
}

RecurrenceRule.prototype.nextInvocationDate = function(base) {
base = (base instanceof Date) ? base : (new Date());
RecurrenceRule.prototype.nextInvocationDate = function(base, tz) {
var now = newDate({ tz: tz });

base = (base instanceof Date) ? base : now;
if (!this.recurs) {
return null;
}

var now = new Date();
if (this.year !== null && (typeof this.year == 'number') && this.year < now.getFullYear()) {
return null;
}

var next = new Date(base.getTime());
var next = newDate({ args: base.getTime(), tz: tz });
next.addSecond();

while (true) {
Expand Down Expand Up @@ -357,7 +376,8 @@ function recurMatch(val, matcher) {

/* Date-based scheduler */
function runOnDate(date, job) {
var now = (new Date()).getTime();
var d = newDate({ tz: job.tz });
var now = d.getTime();
var then = date.getTime();

if (then < now) {
Expand Down Expand Up @@ -433,9 +453,9 @@ function cancelInvocation(invocation) {

/* Recurrence scheduler */
function scheduleNextRecurrence(rule, job, prevDate) {
prevDate = (prevDate instanceof Date) ? prevDate : (new Date());
prevDate = (prevDate instanceof Date) ? prevDate : newDate({ tz: job.tz });

var date = (rule instanceof RecurrenceRule) ? rule.nextInvocationDate(prevDate) : rule.next();
var date = (rule instanceof RecurrenceRule) ? rule.nextInvocationDate(prevDate, job.tz) : rule.next();
if (date === null) {
return null;
}
Expand Down Expand Up @@ -473,13 +493,27 @@ function scheduleJob() {
return null;
}

var name = (arguments.length >= 3) ? arguments[0] : null;
var name;
var tz;

var options = (arguments.length >= 3) ? arguments[0] : null;
var spec = (arguments.length >= 3) ? arguments[1] : arguments[0];
var method = (arguments.length >= 3) ? arguments[2] : arguments[1];
if (options) {
switch (typeof options) {
case 'string':
name = options;
break;
case 'object':
name = options.name;
tz = options.tz;
break;
}
}

var job = new Job(name, method);

if (job.schedule(spec)) {
if (job.schedule(spec, tz)) {
// Generate a id
var key = genJobId();

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
},
"dependencies": {
"cron-parser": "0.6.1",
"long-timeout": "0.0.2"
"long-timeout": "0.0.2",
"time": "^0.11.2"
},
"devDependencies": {
"coveralls": "^2.11.2",
Expand Down
9 changes: 4 additions & 5 deletions test/recurrence-rule-test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
var main = require('../package.json').main;
var schedule = require('../' + main);
var schedule = require('../');

// 12:30:15 pm Thursday 29 April 2010 in the timezone this code is being run in
var base = new Date(2010, 3, 29, 12, 30, 15, 0);
Expand Down Expand Up @@ -123,13 +122,13 @@ module.exports = {
test.deepEqual(new Date(2011, 1, 1, 0, 0, 0, 0), next);
test.done();
},
"in the year 2040": function(test) {
"in the year 2038": function(test) {
var rule = new schedule.RecurrenceRule();
rule.year = 2040;
rule.year = 2038;

var next = rule.nextInvocationDate(base);

test.deepEqual(new Date(2040, 0, 1, 0, 0, 0, 0), next);
test.deepEqual(new Date(2038, 0, 1, 0, 0, 0, 0), next);
test.done();
},
"using past year": function(test) {
Expand Down
3 changes: 1 addition & 2 deletions test/schedule-cron-jobs.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
var schedule = require('../');
var sinon = require('sinon');
var main = require('../package.json').main;
var schedule = require('../' + main);

var clock;

Expand Down
117 changes: 117 additions & 0 deletions test/timezone.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
delete require.cache[require.resolve('../')];
delete require.cache[require.resolve('time')];
delete require.cache[require.resolve('sinon')];

var schedule = require('../');
var time = require('time');
var sinon = require('sinon');

var clock;
var currentTz = time.currentTimezone;

module.exports = {
setUp: function(cb) {
/*
* Setting the current date to Sun, 29 Mar 2015 01:59:59 in Europe/Madrid timezone
* just 1 second before the time switch to summertime: it ticks to 03:00:00 instead of
* 02:00:00
*/
time.tzset('Europe/Madrid');
var date = new Date('Sun, 29 Mar 2015 01:59:59 +0100');
clock = sinon.useFakeTimers(date.getTime());
cb();
},
"Schedule job at 3AM with no timezone set": {
"Should fire just 1 after 2.5 secs": function(test) {
test.expect(1);

var job = schedule.scheduleJob({ hour : 3, minute : 0 }, function() {
test.ok(true);
});

setTimeout(function() {
job.cancel();
test.done();
}, 2500);

clock.tick(2500);
}
},
"Schedule job at 2AM with no timezone set": {
"Should not fire after 2.5 secs": function(test) {
test.expect(0);

var job = schedule.scheduleJob({ hour : 2, minute : 0 }, function() {
test.ok(true);
});

setTimeout(function() {
job.cancel();
test.done();
}, 2500);

clock.tick(2500);
}
},
"Schedule job at 1AM with tz = UTC": {
"Should fire just 1 after 2.5 secs": function(test) {
test.expect(1);

var job = schedule.scheduleJob({ tz : 'UTC' }, { hour : 1, minute : 0 }, function() {
test.ok(true);
});

setTimeout(function() {
job.cancel();
test.done();
}, 2500);

clock.tick(2500);
}
},
"Schedule multiple jobs in different timezones": {
"Should work correctly independently": function(test) {
var local; // at Madrid should fire at 3 AM
var utc; // should fire at 1AM
var dublin; // should fire at 2 AM
var athens; // should fire at 4 AM

test.expect(4);

var job1 = schedule.scheduleJob({ hour : 3, minute : 0 }, function() {
local = true;
});

var job2 = schedule.scheduleJob({ tz : 'UTC' }, { hour : 1, minute : 0 }, function() {
utc = true;
});

var job3 = schedule.scheduleJob({ tz : 'Europe/Dublin' }, { hour : 2, minute : 0 }, function() {
dublin = true;
});

var job4 = schedule.scheduleJob({ tz : 'Europe/Athens' }, { hour : 4, minute : 0 }, function() {
athens = true;
});

setTimeout(function() {
job1.cancel();
job2.cancel();
job3.cancel();
job4.cancel();
test.ok(local);
test.ok(utc);
test.ok(dublin);
test.ok(athens);
test.done();
}, 2500);

clock.tick(2500);
}
},
tearDown: function(cb) {
clock.restore();
time.tzset(currentTz);
cb();
}
};

0 comments on commit 03a2dfb

Please sign in to comment.