Comparing version 1.0.4 to 1.0.5
@@ -47,311 +47,348 @@ | ||
------------------------------------------------------------------------------------ */ | ||
(function () { | ||
var root = this, | ||
function raise (err) { | ||
throw new TypeError('Cron parser: ' + err); | ||
} | ||
function partToArray (type, arr, conf, valueIndexOffset) { | ||
// Many JS engines stores the delay as a 32-bit signed integer internally. | ||
// This causes an integer overflow when using delays larger than 2147483647, resulting in the timeout being executed immediately. | ||
// | ||
// All JS engines implements an immediate execution of delays larger that a 32-bit int to keep the behaviour concistent. | ||
maxDelay = Math.pow(2, 32 - 1) - 1; | ||
var i,x, | ||
confParts, | ||
split, | ||
index, | ||
lower, | ||
upper; | ||
// First off, handle wildcard | ||
if (conf === '*' ) { | ||
for (i = 0; i < arr.length; i++) { | ||
arr[i] = 1; | ||
} | ||
return; | ||
function raise (err) { | ||
throw new TypeError('Cron parser: ' + err); | ||
} | ||
// Check if we need to split | ||
confParts = conf.split(','); | ||
// Recurse into comma separated entries | ||
if (confParts.length > 1) { | ||
for (i = 0; i < confParts.length; i++) { | ||
partToArray(type, arr, confParts[i], valueIndexOffset); | ||
} | ||
return; | ||
} | ||
// Didn't need to recurse, determine if this is a range or a number | ||
if (conf.indexOf('-') === -1) { | ||
// Got a number | ||
index = (parseInt(conf, 10) + valueIndexOffset); | ||
function partToArray (type, arr, conf, valueIndexOffset) { | ||
if (index < 0 || index >= arr.length) { | ||
raise(type + ' value out of range: "' + conf + '"'); | ||
var i,x, | ||
confParts, | ||
split, | ||
index, | ||
lower, | ||
upper; | ||
// First off, handle wildcard | ||
if (conf === '*' ) { | ||
for (i = 0; i < arr.length; i++) { | ||
arr[i] = 1; | ||
} | ||
return; | ||
} | ||
// Check if we need to split | ||
confParts = conf.split(','); | ||
// Recurse into comma separated entries | ||
if (confParts.length > 1) { | ||
for (i = 0; i < confParts.length; i++) { | ||
partToArray(type, arr, confParts[i], valueIndexOffset); | ||
} | ||
return; | ||
} | ||
// Didn't need to recurse, determine if this is a range or a number | ||
if (conf.indexOf('-') === -1) { | ||
// Got a number | ||
index = (parseInt(conf, 10) + valueIndexOffset); | ||
arr[index] = 1; | ||
} else { | ||
if (index < 0 || index >= arr.length) { | ||
raise(type + ' value out of range: "' + conf + '"'); | ||
} | ||
// Got a range | ||
split = conf.split('-'); | ||
arr[index] = 1; | ||
} else { | ||
if (split.length !== 2) { | ||
raise('syntax error, illegal range: "' + conf + '"'); | ||
} | ||
// Got a range | ||
split = conf.split('-'); | ||
lower = parseInt(split[0], 10) + valueIndexOffset; | ||
upper = parseInt(split[1], 10) + valueIndexOffset; | ||
if (split.length !== 2) { | ||
raise('syntax error, illegal range: "' + conf + '"'); | ||
} | ||
if (isNaN(lower)) { | ||
raise('syntax error, illegal lower range (NaN)'); | ||
} else if (isNaN(upper)) { | ||
raise('syntax error, illegal upper range (NaN)'); | ||
} | ||
lower = parseInt(split[0], 10) + valueIndexOffset; | ||
upper = parseInt(split[1], 10) + valueIndexOffset; | ||
// | ||
if (lower < 0 || upper >= arr.length) { | ||
raise('value out of range: "' + conf + '"'); | ||
} | ||
if (isNaN(lower)) { | ||
raise('syntax error, illegal lower range (NaN)'); | ||
} else if (isNaN(upper)) { | ||
raise('syntax error, illegal upper range (NaN)'); | ||
} | ||
// | ||
if (lower > upper) { | ||
raise('from value is larger than to value: "' + conf + '"'); | ||
} | ||
// | ||
if (lower < 0 || upper >= arr.length) { | ||
raise('value out of range: "' + conf + '"'); | ||
} | ||
for (x = lower; x <= upper; x++) { | ||
arr[(x + valueIndexOffset)] = 1; | ||
// | ||
if (lower > upper) { | ||
raise('from value is larger than to value: "' + conf + '"'); | ||
} | ||
for (x = lower; x <= upper; x++) { | ||
arr[(x + valueIndexOffset)] = 1; | ||
} | ||
} | ||
} | ||
} | ||
function parsePattern(pattern, target) { | ||
function parsePattern(pattern, target) { | ||
// Sanity check | ||
if (typeof pattern !== 'string') { | ||
raise('invalid configuration string ("' + pattern + '").'); | ||
} | ||
// Sanity check | ||
if (typeof pattern !== 'string') { | ||
raise('invalid configuration string ("' + pattern + '").'); | ||
} | ||
// Split configuration on whitespace | ||
var parts = pattern.trim().replace(/\s+/g, ' ').split(' '), | ||
part, | ||
i, | ||
reValidCron = /[^0-9,-]+/, | ||
hasMonths, | ||
hasDaysOfWeek, | ||
hasDates, | ||
// Split configuration on whitespace | ||
var parts = pattern.trim().replace(/\s+/g, ' ').split(' '), | ||
part, | ||
i, | ||
reValidCron = /[^0-9,-]+/, | ||
hasMonths, | ||
hasDaysOfWeek, | ||
hasDates, | ||
seconds, | ||
minutes, | ||
hours, | ||
days, | ||
months, | ||
daysOfWeek; | ||
seconds, | ||
minutes, | ||
hours, | ||
days, | ||
months, | ||
daysOfWeek; | ||
// Validite number of configuration entries | ||
if (parts.length !== 6) { | ||
raise('invalid configuration format ("' + pattern + '"), exacly five space separated parts required.'); | ||
} | ||
// Validite number of configuration entries | ||
if (parts.length !== 6) { | ||
raise('invalid configuration format ("' + pattern + '"), exacly five space separated parts required.'); | ||
} | ||
// Validate field content | ||
for (i = 0; i < parts.length; i++) { | ||
part = parts[i].trim(); | ||
// Validate field content | ||
for (i = 0; i < parts.length; i++) { | ||
part = parts[i].trim(); | ||
// Check that part only contain legal characters ^[0-9-,]+$ | ||
if (part !== '*' && reValidCron.test(part)) { | ||
raise('configuration entry ' + (i + 1) + ' (' + part + ') contains illegal characters.'); | ||
// Check that part only contain legal characters ^[0-9-,]+$ | ||
if (part !== '*' && reValidCron.test(part)) { | ||
raise('configuration entry ' + (i + 1) + ' (' + part + ') contains illegal characters.'); | ||
} | ||
} | ||
} | ||
// Check that we dont have both months and daysofweek | ||
hasMonths = (parts[4] !== '*'); | ||
hasDaysOfWeek = (parts[5] !== '*'); | ||
hasDates = (parts[3] !== '*'); | ||
// Check that we dont have both months and daysofweek | ||
hasMonths = (parts[4] !== '*'); | ||
hasDaysOfWeek = (parts[5] !== '*'); | ||
hasDates = (parts[3] !== '*'); | ||
// Month/Date and dayofweek is incompatible | ||
if (hasDaysOfWeek && (hasMonths || hasDates)) { | ||
raise('configuration invalid, you can not combine month/date with day of week.'); | ||
} | ||
// Parse parts into arrays, validates as we go | ||
partToArray('seconds', target.seconds, parts[0], 0); | ||
partToArray('minutes', target.minutes, parts[1], 0); | ||
partToArray('hours', target.hours, parts[2], 0); | ||
partToArray('days', target.days, parts[3], -1); | ||
partToArray('months', target.months, parts[4], -1); | ||
partToArray('daysOfWeek', target.daysOfWeek, parts[5], 0); | ||
// 0 = Sunday, 7 = Sunday | ||
if (target.daysOfWeek[0]) { | ||
target.daysOfWeek[7] = 1; | ||
} | ||
// Month/Date and dayofweek is incompatible | ||
if (hasDaysOfWeek && (hasMonths || hasDates)) { | ||
raise('configuration invalid, you can not combine month/date with day of week.'); | ||
} | ||
// Parse parts into arrays, validates as we go | ||
partToArray('seconds', target.seconds, parts[0], 0); | ||
partToArray('minutes', target.minutes, parts[1], 0); | ||
partToArray('hours', target.hours, parts[2], 0); | ||
partToArray('days', target.days, parts[3], -1); | ||
partToArray('months', target.months, parts[4], -1); | ||
partToArray('daysOfWeek', target.daysOfWeek, parts[5], 0); | ||
// 0 = Sunday, 7 = Sunday | ||
if (target.daysOfWeek[0]) { | ||
target.daysOfWeek[7] = 1; | ||
} | ||
if (target.daysOfWeek[7]) { | ||
target.daysOfWeek[0] = 1; | ||
if (target.daysOfWeek[7]) { | ||
target.daysOfWeek[0] = 1; | ||
} | ||
} | ||
} | ||
function Cron (pattern) { | ||
var self = this; | ||
function Cron (pattern) { | ||
var self = this; | ||
self.pattern = pattern; | ||
self.pattern = pattern; | ||
self.seconds = Array(60).fill(0); // 0-59 | ||
self.minutes = Array(60).fill(0); // 0-59 | ||
self.hours = Array(24).fill(0); // 0-23 | ||
self.days = Array(31).fill(0); // 0-30 in array, 1-31 in config | ||
self.months = Array(12).fill(0); // 0-11 in array, 1-12 in config | ||
self.daysOfWeek = Array(8).fill(0); // 0-7 Where 0 = Sunday and 7=Sunday; | ||
self.seconds = Array(60).fill(0); // 0-59 | ||
self.minutes = Array(60).fill(0); // 0-59 | ||
self.hours = Array(24).fill(0); // 0-23 | ||
self.days = Array(31).fill(0); // 0-30 in array, 1-31 in config | ||
self.months = Array(12).fill(0); // 0-11 in array, 1-12 in config | ||
self.daysOfWeek = Array(8).fill(0); // 0-7 Where 0 = Sunday and 7=Sunday; | ||
self.schedulerDefaults = { | ||
stopAt: Infinity, | ||
maxRuns: Infinity, | ||
kill: false | ||
}; | ||
self.schedulerDefaults = { | ||
stopAt: Infinity, | ||
maxRuns: Infinity, | ||
kill: false | ||
}; | ||
parsePattern(pattern, self); | ||
parsePattern(pattern, self); | ||
return this; | ||
} | ||
Cron.prototype.next = function (date) { | ||
return this; | ||
} | ||
Cron.prototype.next = function (date) { | ||
var self = this, | ||
date = date || new Date(), | ||
temp, | ||
var self = this, | ||
date = date || new Date(), | ||
temp, | ||
collection = { | ||
cSecs: date.getSeconds() + 1, | ||
cMins: date.getMinutes(), | ||
cHour: date.getHours(), | ||
cDate: date.getDate(), | ||
cMon: date.getMonth(), | ||
cYear: date.getFullYear(), | ||
}, | ||
collection = { | ||
cSecs: date.getSeconds() + 1, | ||
cMins: date.getMinutes(), | ||
cHour: date.getHours(), | ||
cDate: date.getDate(), | ||
cMon: date.getMonth(), | ||
cYear: date.getFullYear(), | ||
}, | ||
secs = self.seconds, | ||
mins = self.minutes, | ||
hours = self.hours, | ||
days = self.days, | ||
months = self.months, | ||
secs = self.seconds, | ||
mins = self.minutes, | ||
hours = self.hours, | ||
days = self.days, | ||
months = self.months, | ||
hasDays = !(days.filter(Boolean).length==31), | ||
hasMonths = !(months.filter(Boolean).length==12); | ||
function goUp (what, who, current, increment, valueIndexOffset) { | ||
hasDays = !(days.filter(Boolean).length==31), | ||
hasMonths = !(months.filter(Boolean).length==12); | ||
function goUp (what, who, current, increment, valueIndexOffset) { | ||
var i, found = false, dayChanged; | ||
var i, found = false, dayChanged; | ||
if (what[who[current] + valueIndexOffset]) return true; | ||
if (what[who[current] + valueIndexOffset]) return true; | ||
for (i = (who[current] + valueIndexOffset); i < mins.length; i++) { | ||
if (what[i]) { | ||
who[current] = i-valueIndexOffset; | ||
found = true; | ||
break; | ||
for (i = (who[current] + valueIndexOffset); i < mins.length; i++) { | ||
if (what[i]) { | ||
who[current] = i-valueIndexOffset; | ||
found = true; | ||
break; | ||
} | ||
} | ||
} | ||
if (!found) { | ||
who[increment] += 1; | ||
if (!found) { | ||
who[increment] += 1; | ||
for (i = 0; i < who[current] + valueIndexOffset; i++) { | ||
if (what[i]) { | ||
who[current] = i - valueIndexOffset; | ||
break; | ||
for (i = 0; i < who[current] + valueIndexOffset; i++) { | ||
if (what[i]) { | ||
who[current] = i - valueIndexOffset; | ||
break; | ||
} | ||
} | ||
} | ||
return found; | ||
} | ||
// Count up to minute and hour | ||
var upMinHour = function (collection) { | ||
goUp(secs, collection, 'cSecs','cMins', 0); | ||
goUp(mins, collection, 'cMins','cHour', 0); | ||
goUp(hours, collection, 'cHour','cDate', 0); | ||
}; | ||
upMinHour(collection); | ||
dayChanged = false; | ||
return found; | ||
} | ||
// Count up to minute and hour | ||
var upMinHour = function (collection) { | ||
goUp(secs, collection, 'cSecs','cMins', 0); | ||
goUp(mins, collection, 'cMins','cHour', 0); | ||
goUp(hours, collection, 'cHour','cDate', 0); | ||
}; | ||
upMinHour(collection); | ||
dayChanged = false; | ||
if (hasDays || hasMonths) { | ||
// Count up to date and month | ||
dayChanged = goUp(days, collection, 'cDate', 'cMon', -1); | ||
goUp(months, collection, 'cMon', 'cYear', 0); // No need to compensate here as javascript count months 0-11 | ||
if (hasDays || hasMonths) { | ||
// Count up to date and month | ||
dayChanged = goUp(days, collection, 'cDate', 'cMon', -1); | ||
goUp(months, collection, 'cMon', 'cYear', 0); // No need to compensate here as javascript count months 0-11 | ||
return new Date(collection.cYear, collection.cMon, collection.cDate, collection.cHour, collection.cMins, collection.cSecs, 0); | ||
} | ||
while (!self.daysOfWeek[new Date(collection.cYear, collection.cMon, collection.cDate, collection.cHour, collection.cMins, collection.cSecs, 0).getDay()]) { | ||
collection.cDate += 1; | ||
dayChanged = true; | ||
} | ||
// If day changed, we need to re-run hours and minutes | ||
if (dayChanged) { | ||
collection.cMin = collection.cHour = 0; | ||
upMinHour(collection); | ||
} | ||
return new Date(collection.cYear, collection.cMon, collection.cDate, collection.cHour, collection.cMins, collection.cSecs, 0); | ||
} | ||
while (!self.daysOfWeek[new Date(collection.cYear, collection.cMon, collection.cDate, collection.cHour, collection.cMins, collection.cSecs, 0).getDay()]) { | ||
collection.cDate += 1; | ||
dayChanged = true; | ||
Cron.prototype.msToNext = function (prev) { | ||
return (this.next(prev) - new Date().getTime()); | ||
} | ||
// If day changed, we need to re-run hours and minutes | ||
if (dayChanged) { | ||
collection.cMin = collection.cHour = 0; | ||
upMinHour(collection); | ||
} | ||
Cron.prototype.schedule = function (opts, func, recurse) { | ||
var self = this, | ||
waitMs, | ||
return new Date(collection.cYear, collection.cMon, collection.cDate, collection.cHour, collection.cMins, collection.cSecs, 0); | ||
} | ||
// Prioritize context before closure, | ||
// to allow testing of maximum delay. | ||
_maxDelay = self.maxDelay || maxDelay; | ||
// Make opts optional | ||
if (!func) { | ||
func = opts; | ||
opts = {}; | ||
} | ||
Cron.prototype.msToNext = function (prev) { | ||
return (this.next(prev) - new Date().getTime()); | ||
} | ||
// Keep options, or set defaults | ||
opts.paused = (opts.paused === undefined) ? false : opts.paused; | ||
opts.previous = (recurse === false) ? new Date() : opts.startAt || opts.previous; | ||
opts.stopAt = opts.stopAt || this.schedulerDefaults.stopAt; | ||
opts.kill = opts.kill || this.schedulerDefaults.kill; | ||
opts.rest = opts.rest || 0; | ||
if (!opts.maxRuns && opts.maxRuns !== 0) { | ||
opts.maxRuns = this.schedulerDefaults.maxRuns; | ||
} | ||
Cron.prototype.schedule = function (opts, f, recurse) { | ||
var self = this, | ||
waitMs; | ||
// Make opts optional | ||
if ( f === undefined ) { | ||
f = opts; | ||
opts = {}; | ||
} | ||
// One-timer | ||
opts.startAt = undefined; | ||
opts.previous = (recurse === false) ? new Date() : opts.startAt || opts.previous; | ||
opts.stopAt = opts.stopAt || this.schedulerDefaults.stopAt; | ||
opts.kill = opts.kill || this.schedulerDefaults.kill; | ||
opts.rest = opts.rest || 0; | ||
if ( opts.maxRuns === undefined ) opts.maxRuns = this.schedulerDefaults.maxRuns; | ||
// Get ms to next run | ||
waitMs = this.msToNext(opts.previous); | ||
// One-timer | ||
opts.startAt = undefined; | ||
// Check for stop conditions | ||
if (opts.maxRuns <= 0) return; | ||
if (opts.stopAt !== Infinity && opts.previous.getTime() + waitMs/1000 > opts.stopAt.getTime() ) return; | ||
if (opts.kill) return; | ||
// Get ms to next run | ||
waitMs = this.msToNext(opts.previous); | ||
// setTimeout cant handle more than Math.pow(2, 32 - 1) - 1 ms | ||
if (waitMs > _maxDelay) { | ||
waitMs = _maxDelay; | ||
} | ||
// Check for stop conditions | ||
if ( opts.maxRuns <= 0 ) return; | ||
if ( opts.stopAt !== Infinity && opts.previous.getTime() + waitMs/1000 > opts.stopAt.getTime() ) return; | ||
if ( opts.kill ) return; | ||
// All ok, go go! | ||
opts.currentTimeout = setTimeout(function () { | ||
// setTimeout cant handle more than Math.pow(2, 32 - 1) - 1 ms | ||
if ( waitMs > 0x7FFFFFFF) { | ||
waitMs = 0x7FFFFFFF; | ||
} | ||
// Are we running? If waitMs is maxed out, this is a blank run | ||
if ( waitMs !== _maxDelay && !opts.paused) { | ||
opts.maxRuns--; | ||
opts.previous = new Date(); | ||
func(); | ||
} | ||
// All ok, go go! | ||
setTimeout( function() { | ||
// Are we paused? In that case we need to update last run time | ||
if ( opts.paused ) { | ||
opts.previous = new Date(); | ||
} | ||
// Are we running? If waitMs is maxed out, this is a blank run | ||
if ( waitMs !== 0x7FFFFFFF ) { | ||
opts.maxRuns--; | ||
opts.previous = new Date(); | ||
f(); | ||
} | ||
// Recurse | ||
self.schedule(opts, func, true); | ||
}, waitMs ); | ||
// Recurse | ||
self.schedule(opts, f, true); | ||
// First run? Return killer | ||
if ( !recurse ) { | ||
return { | ||
}, waitMs ); | ||
// Return undefined | ||
stop: function() { | ||
opts.kill = true; | ||
// Stop any awaiting call | ||
if ( opts.currentTimeout ) { | ||
clearTimeout( opts.currentTimeout ); | ||
} | ||
}, | ||
// First run? Return killer | ||
if ( !recurse ) { | ||
return { | ||
kill: function() { | ||
opts.kill = true; | ||
// Return if pause were successful | ||
pause: function() { | ||
return (opts.paused = true) && !opts.kill; | ||
}, | ||
// Return if resume were successful | ||
resume: function () { | ||
return !(opts.paused = false) && !opts.kill; | ||
} | ||
} | ||
@@ -361,22 +398,17 @@ } | ||
} | ||
// Expose | ||
if (typeof module != 'undefined' && typeof module.exports === 'object') { | ||
module.exports = Cron; | ||
} else if (typeof define === 'function' && define.amd) { | ||
define([], function () { | ||
return Cron; | ||
}); | ||
} else { | ||
root.cron = Cron; | ||
} | ||
}).call(this); | ||
// Expose to | ||
// ... Node | ||
if (typeof module != 'undefined' && typeof module.exports === 'object') { | ||
module.exports = function (pattern) { return new Cron(pattern); }; | ||
// ... AMD | ||
} else if (typeof define === 'function' && define.amd) { | ||
define([], function () { | ||
return function (pattern) { return new Cron(pattern); }; | ||
}); | ||
// ... Other browser implementations | ||
} else { | ||
this.cron = function (pattern) { return new Cron(pattern); }; | ||
} | ||
// Debug shit - To be removed | ||
if(false) { | ||
if(true) { | ||
var Cron = module.exports; | ||
@@ -387,5 +419,12 @@ var scheduler = new Cron('* * * * * *'); | ||
var job0 = scheduler.schedule(function() { | ||
console.log('I\'m invincible!!'); | ||
console.log('I\'m invincible!!', new Date().getTime()); | ||
}); | ||
// Pause the invicible job between 5 and 15 seconds | ||
setTimeout( function () { job0.pause(); }, 5000); | ||
setTimeout( function () { job0.resume(); }, 15000); | ||
// Kill the infinite job after 20 seconds, HEH! | ||
setTimeout( function () { job0.stop(); }, 20000); | ||
// Start a job that runs from 5 seconds from now, to 10 seconds from now | ||
@@ -407,5 +446,2 @@ var job1 = scheduler.schedule({ | ||
// Kill the infinite job after 20 seconds, HEH! | ||
setTimeout( function () { job0.kill(); }, 20000); | ||
// Start a job that run five times | ||
@@ -416,2 +452,3 @@ scheduler.schedule({maxRuns: 5}, function () { | ||
} | ||
} |
{ | ||
"name": "croner", | ||
"version": "1.0.4", | ||
"version": "1.0.5", | ||
"description": "Isomorphic JavaScript cron parser and scheduler.", | ||
@@ -5,0 +5,0 @@ "author": "Hexagon <github.com/hexagon>", |
@@ -25,3 +25,6 @@ | ||
o.msToNext(); | ||
o.schedule( [ { startAt: <date>, stopAt: <date>, maxRuns: <integer> } ,] callback); | ||
var job = o.schedule( [ { startAt: <date>, stopAt: <date>, maxRuns: <integer> } ,] callback); | ||
job.pause(); | ||
job.resume(); | ||
job.stop(); | ||
@@ -28,0 +31,0 @@ ``` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
21874
487
89