├── .gitignore ├── .travis.yml ├── package.json ├── test └── crontab.js ├── lib └── crontab.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.11" 4 | - "0.10" 5 | - "0.8" -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-crontab", 3 | "version": "0.0.8", 4 | "description": "Crontab-based task scheduler for node.", 5 | "main": "lib/crontab.js", 6 | "scripts": { 7 | "test": "mocha test/* --reporter spec" 8 | }, 9 | "keywords": [ 10 | "cron", 11 | "crontab", 12 | "scheduler" 13 | ], 14 | "author": "NineCollective", 15 | "license": "MIT", 16 | "dependencies": { 17 | "cron-parser": "~0.3.3" 18 | }, 19 | "devDependencies": { 20 | "mocha": "~1.12", 21 | "expect.js": "~0.2.0" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git://github.com/NineCollective/node-crontab.git" 26 | }, 27 | "bugs": { 28 | "url": "https://github.com/NineCollective/node-crontab/issues" 29 | }, 30 | "homepage": "https://github.com/NineCollective/node-crontab" 31 | } 32 | -------------------------------------------------------------------------------- /test/crontab.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect.js'), 2 | crontab = require('../lib/crontab.js'); 3 | 4 | describe('Crontab', function(){ 5 | it('creates normal task', function(done){ 6 | //Creates a task 7 | var jobId = crontab.scheduleJob("* * * * *", function(){ 8 | console.log("Hello world"); 9 | }); 10 | expect(typeof(jobId)).to.be(typeof(new Date().getTime())); 11 | done(); 12 | }); 13 | 14 | it('creates long-term task', function(done){ 15 | //This test schedules a function for yesterday of this month (or the 28th of the previous month if today is the first), which should make the difference be greater than MAX_SET_TIMEOUT, meaning this should be scheduled in the delayed queue 16 | var date = new Date(); 17 | var day = date.getDate()-1; 18 | var month = date.getMonth()+1; 19 | if(day === 0){ 20 | day = 28; 21 | month -= 1; 22 | } 23 | if(month === 0){ 24 | month = 12; 25 | } 26 | var yesterdayOfNextYear = "* * " + (day) + " " + (month) + " *"; 27 | var jobId = crontab.scheduleJob(yesterdayOfNextYear, function(){ 28 | console.log("Hello world"); 29 | }); 30 | expect(typeof(jobId)).to.be(typeof(new Date().getTime())); 31 | done(); 32 | }); 33 | 34 | it('executes task', function(done){ 35 | //This job relies on cron-parser's support for second-based parsing, schedules a task that should fire on the next second 36 | var jobId = crontab.scheduleJob("* * * * * *", function(){ 37 | done(); 38 | }, null, null, false); 39 | setTimeout(function(){ 40 | done("Didn't execute callback"); 41 | }, 2000); 42 | }); 43 | 44 | it('cancels task', function(done){ 45 | //Schedules a task, then cancels it and expects canceled to be true 46 | var jobId = crontab.scheduleJob("* * * * * *", function(){ 47 | console.log("Hello world."); 48 | }); 49 | var canceled = crontab.cancelJob(jobId); 50 | expect(canceled).to.be(true); 51 | done(); 52 | }); 53 | 54 | it('should break with invalid cron', function(done){ 55 | //Trying to schedule a task with an invalid cron time, cron-parser is actually responsible for the exception we're receiving 56 | try { 57 | crontab.scheduleJob("this should fail", function(){ 58 | console.log("Should never make it here"); 59 | }); 60 | done("Didn't throw an exception"); 61 | }catch(e){ 62 | expect(e.message).to.be("Invalid characters, got value: this"); 63 | done(); 64 | } 65 | }); 66 | 67 | it('fails to cancel invalid task id', function(done){ 68 | //Tries to cancel a task that shouldn't exist. 69 | var result = crontab.cancelJob((new Date()).getTime()); 70 | expect(result).to.be(false); 71 | done(); 72 | }); 73 | }); -------------------------------------------------------------------------------- /lib/crontab.js: -------------------------------------------------------------------------------- 1 | var parser = require('cron-parser'); 2 | 3 | var MAX_SET_TIMEOUT = 2147483647; //Also known as the maximum 32bit integer, the highest number setTimeout/setInterval will allow 4 | var scheduledJobs = {}; 5 | var delayedQueue = {}; 6 | var delayedQueueIntervalId = null; 7 | 8 | var scheduleJob = function(cronTime, callback, args, context, repeating, previousIndex){ 9 | if(repeating == null) repeating = true; 10 | var interval = parser.parseExpressionSync(cronTime), 11 | difference = interval.next() - (new Date()), 12 | timeout = null, 13 | scheduledJobIndex = previousIndex; 14 | 15 | if(scheduledJobIndex == null){ 16 | //Just a cheap way to get a unique ID. Used later to cancel jobs. 17 | scheduledJobIndex = (new Date).getTime(); 18 | } 19 | if(difference > MAX_SET_TIMEOUT){ 20 | delayedQueue[scheduledJobIndex] = [cronTime, callback, args, context, repeating, scheduledJobIndex]; 21 | if(delayedQueueIntervalId == null){ 22 | setUpDelayedQueueInterval(); 23 | } 24 | }else{ 25 | timeout = setTimeout(executeJob.bind(this, cronTime, callback, args, context, repeating, scheduledJobIndex), difference); 26 | scheduledJobs[scheduledJobIndex] = timeout; 27 | } 28 | return scheduledJobIndex; 29 | }; 30 | 31 | var setUpDelayedQueueInterval = function(){ 32 | delayedQueueIntervalId = setInterval(function(){ 33 | var keys = Object.keys(delayedQueue); 34 | for(var i = 0, ii = keys.length; i < ii; i++){ 35 | var task = delayedQueue[keys[i]]; 36 | delete delayedQueue[keys[i]]; 37 | scheduleJob.apply(this, task); 38 | } 39 | if(Object.keys(delayedQueue).length === 0){ 40 | tearDownDelayedQueueInterval(); 41 | } 42 | }, MAX_SET_TIMEOUT); 43 | }; 44 | 45 | var tearDownDelayedQueueInterval = function(){ 46 | if(delayedQueueIntervalId != null){ 47 | clearInterval(delayedQueueIntervalId); 48 | delayedQueueIntervalId = null; 49 | } 50 | }; 51 | 52 | var cancelJob = function(id){ 53 | var job = scheduledJobs[id]; 54 | var delayedJob = delayedQueue[id]; 55 | var deleted = false; 56 | if(job != null){ 57 | clearTimeout(job); 58 | delete scheduledJobs[id]; 59 | deleted = true; 60 | } 61 | if(delayedJob != null){ 62 | delete delayedQueue[id]; 63 | if(Object.keys(delayedQueue).length === 0){ 64 | tearDownDelayedQueueInterval(); 65 | } 66 | deleted = true; 67 | } 68 | return deleted; 69 | }; 70 | 71 | var executeJob = function(cronTime, callback, args, context, repeating, previousIndex){ 72 | if(repeating){ 73 | //I do this before the callback so jobs could actually cancel themselves if they'd like. 74 | scheduleJob.apply(this, arguments); 75 | } 76 | callback.apply(context, args); 77 | }; 78 | 79 | module.exports = { 80 | scheduleJob: scheduleJob, 81 | cancelJob: cancelJob 82 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-crontab 2 | 3 | A task scheduler for node that uses the crontab syntax 4 | 5 | 6 | Travis CI build status 7 | *** 8 | 9 | **Why node-crontab?** 10 | 11 | 12 | Need to schedule tasks in node and you already have the cron syntax? Then you need node-crontab! We take care of the task scheduling and timing and all you have to do is tell us what to do! 13 | 14 | With node-crontab, you can pass in your callback, arguments for the callback, and even the context of the callback function! We also take care of times that are normally too long for setInterval/setTimeout to handle automatically! 15 | 16 | So if this sounds like something you need, keep reading! 17 | 18 | **Setup** 19 | 20 | All you have to do is run this command in the directory of your node project: 21 | 22 | `npm install node-crontab` 23 | 24 | npm will take care of the rest! 25 | 26 | **Example Usage** 27 | 28 | 29 | *Regular job* 30 | ```javascript 31 | var crontab = require('node-crontab'); 32 | var jobId = crontab.scheduleJob("*/2 * * * *", function(){ //This will call this function every 2 minutes 33 | console.log("It's been 2 minutes!"); 34 | }); 35 | ``` 36 | 37 | *Arguments* 38 | ```javascript 39 | var crontab = require('node-crontab'); 40 | var jobId = crontab.scheduleJob("* * * * *", function(a){ 41 | console.log("Hello " + a + "! It's been a minute!"); 42 | }, ["World"]); 43 | ``` 44 | 45 | *Context* 46 | ```javascript 47 | var crontab = require('node-crontab'); 48 | var obj = {a: "World"}; 49 | var jobId = crontab.scheduleJob("* * * * *", function(){ 50 | console.log("Hello " + this.a + "! It's been a minute!"); 51 | }, null, obj); 52 | ``` 53 | 54 | *Non-repeating* 55 | ```javascript 56 | var crontab = require('node-crontab'); 57 | var jobId = crontab.scheduleJob("* * * * *", function(){ 58 | console.log("Hello world! It's been a minute, but this will be the only time I run."); 59 | }, null, null, false); 60 | ``` 61 | 62 | 63 | *Killing a job* 64 | ```javascript 65 | var crontab = require('node-crontab'); 66 | var jobId = crontab.scheduleJob("* * * * *", function(){ 67 | console.log("It's been a minute!"); 68 | }); 69 | crontab.cancelJob(jobId); //Should cancel the job immediately 70 | ``` 71 | 72 | 73 | *Suicidal jobs* 74 | ```javascript 75 | var crontab = require('node-crontab'); 76 | var jobId = crontab.scheduleJob("* * * * *", function(){ 77 | console.log("It's been a minute, but this is the last time I run."); 78 | crontab.cancelJob(jobId); // Jobs can cancel themselves, too! 79 | }); 80 | ``` 81 | 82 | 83 | **API** 84 | 85 | int crontab.**scheduleJob**(cronTime, callback, [args], [context], [repeating = true]) 86 | - **cronTime** - the crontab format of the schedule. 87 | - Example: "\*/2 \* \* \* \*" would run every 2 minutes of every hour of every day 88 | - cronTime is parsed by cron-parser, full support information can be found [on the cron-parser github page](https://github.com/harrisiirak/cron-parser). 89 | - **callback** - The function you'd like to run 90 | - **args** - Arguments you'd like node-crontab to pass into your callback, in array notation 91 | - **context** - The "this" of the function you'd like node-crontab to call 92 | - **repeating** - Declare whether this task should repeat or not. Defaults to true, since usually you use the crontab format for repeating tasks. 93 | - Returns an ID that you pass into cancelJob in order to kill a job after it's been scheduled 94 | 95 | 96 | bool crontab.**cancelJob**(jobId) 97 | - **jobId** - The ID of the job you'd like to cancel 98 | - Returns true/false, indicates whether it successfully canceled the job or not (will only be false if a job with that ID did not exist) 99 | 100 | 101 | **To Do** 102 | 103 | - Create better tests. I've created basic tests, but they're not as good as they could be, probably. 104 | 105 | 106 | **Wrap-up** 107 | 108 | 109 | Have an issue? Feel free to open an issue in the issues page. If you want to help, feel free to fork and make pull requests! --------------------------------------------------------------------------------