Trigger functions and/or evaluate cron expressions in JavaScript. No dependencies. Most features. Node. Deno. Browser.
Try it live on jsfiddle.
Croner
- Trigger functions in JavaScript using Cron syntax.
- Find first date of next month, find date of next tuesday, etc.
- Pause, resume or stop execution after a task is scheduled.
- Works in Node.js >=6.0 (both require and import).
- Works in Deno >=1.16.
- Works in browsers as standalone, UMD or ES-module.
- Schedule using specific target timezones.
- Includes TypeScript typings.
Quick examples:
const job = Cron('*/5 * * * * *', () => {
console.log('This will run every fifth second');
});
const nextSundays = Cron('0 0 0 * * 7').enumerate(100);
console.log(nextSundays);
const msLeft = Cron('59 59 23 24 DEC *').next() - new Date();
console.log(Math.floor(msLeft/1000/3600/24) + " days left to next christmas eve");
Cron('2023-01-23T00:00:00', { timezone: 'Asia/Kolkata' }, () => { console.log('Yay') });
More examples...
Why another javascript cron implementation
Because the existing ones aren't good enough. They have serious bugs, use bloated dependencies, do not work in all environments and/or simply don't work as expected.
Benchmark at 2022-02-20:
> node cron-implementation-test.js
Test: When is 23:00 next 31st march, pattern '0 0 23 31 3 *'
node-schedule: 2022-03-31 23:00:00 in 15.379ms
node-cron: ??? in 0.339ms
Month '3' is limited to '30' days.
cron: 2022-04-01 23:00:00 in 7.785ms
croner (legacy): 2022-03-31 23:00:00 in 2.142ms
croner (default): 2022-03-31 23:00:00 in 0.672ms
More test results
Test: When is next 15th of february, pattern '0 0 0 15 2 *'
node-schedule: 2023-02-15 00:00:00 in 43.875ms
node-cron: ??? in 2.217ms
cron: 2022-03-15 00:00:00 in 4.147ms
croner (legacy): 2023-02-15 00:00:00 in 1.72ms
croner (default): 2023-02-15 00:00:00 in 0.946ms
Test: When is next monday in october, pattern '0 0 0 * 10 1'
node-schedule: 2022-10-03 00:00:00 in 5.594ms
node-cron: ??? in 3.62ms
cron: 2022-11-07 00:00:00 in 1.658ms
croner (legacy): 2022-10-03 00:00:00 in 0.697ms
croner (default): 2022-10-03 00:00:00 in 0.546ms
https://gist.github.com/Hexagon/703f85f2dd86443cc17eef8f5cc6cb70
Installation
Node.js
npm install croner --save
JavaScript
import Cron from "croner";
const Cron = require("croner");
TypeScript
Notes for TypeScript:
- If using strict eslint-rules, specifically
new-cap
combined with no-new
, you need to import and use lower case cron
instead of { Cron }
.
import { Cron } from "croner";
const job : Cron = new Cron("* * * * * *", () => {
console.log("This will run every second.");
});
Deno
JavaScript
import Cron from "https://deno.land/x/croner@5.1.0/src/croner.js";
Cron("* * * * * *", () => {
console.log("This will run every second.");
});
TypeScript
import { Cron } from "https://deno.land/x/croner@5.1.0/src/croner.js";
const _scheduler : Cron = new Cron("* * * * * *", () => {
console.log("This will run every second.");
});
Browser
Manual
- Download latest zipball
- Unpack
- Grab
croner.min.js
(UMD and standalone) or croner.min.mjs
(ES-module) from the dist/ folder
CDN
To use as a UMD-module (stand alone, RequireJS etc.)
<script src="https://cdn.jsdelivr.net/npm/croner@5/dist/croner.min.js"></script>
To use as a ES-module
<script type="module">
import Cron from "https://cdn.jsdelivr.net/npm/croner@5/dist/croner.min.mjs";
</script>
Documentation
Full documentation available at hexagon.github.io/croner.
The short version:
Signature
Cron takes three arguments
const job = Cron("* * * * * *" , { maxRuns: 1 } , () => {} );
job.schedule(( job, context) => {});
const nextRun = job.next( previousRun );
const nextRuns = job.enumerate(10, startFrom );
const prevRun = job.previous( );
const msToNext = job.msToNext( previousRun );
const isRunning = job.running();
job.pause();
job.resume();
job.stop();
Options
Key | Default value | Data type | Remarks |
---|
maxRuns | Infinite | Number | |
catch | false | Boolean | Catch and ignore unhandled errors in triggered function |
timezone | undefined | String | Timezone in Europe/Stockholm format |
startAt | undefined | String | ISO 8601 formatted datetime (2021-10-17T23:43:00) in local time (according to timezone parameter if passed) |
stopAt | undefined | String | ISO 8601 formatted datetime (2021-10-17T23:43:00) in local time (according to timezone parameter if passed) |
interval | 0 | Number | Minimum number of seconds between triggers. |
paused | false | Boolean | If the job should be paused from start. |
context | undefined | Any | Passed as the second parameter to triggered function |
legacyMode | true | boolean | Combine day-of-month and day-of-week using true = OR, false = AND |
Pattern
The expressions of Croner are very similar to the ones of Vixie Cron, with a few additions and changes listed below.
-
In croner, a combination of day-of-week and day-of-month will only trigger when both conditions match. An example: 0 20 1 * MON
will only trigger when monday occur the first day of any month. In Vixie Cron, it would trigger every monday AND the first day of every month. Vixie style can be enabled with legacyMode: true
from version 4.2.0
and is default from 5.0.0
. See issue #53 for more information.
-
Croner expressions support the following additional modifiers
- ? A question mark is substituted with croner initialization time, as an example -
? ? * * * *
would be substituted with 25 8 * * * *
if time is <any hour>:08:25
at the time of new Cron('? ? * * * *', <...>)
. The question mark can be used in any field. - L L can be used in the day of month field, to specify the last day of the month.
-
Croner also allow you to pass a javascript Date object, or a ISO 8601 formatted string, as a pattern. The scheduled function will trigger once at the specified date/time. If you use a timezone different from local, you pass ISO 8601 local time in target location, and specify timezone using the options (2nd parameter).
Field | Required | Allowed values | Allowed special characters | Remarks |
---|
Seconds | Optional | 0-59 | * , - / ? | |
Minutes | Yes | 0-59 | * , - / ? | |
Hours | Yes | 0-23 | * , - / ? | |
Day of Month | Yes | 1-31 | * , - / ? L | |
Month | Yes | 1-12 or JAN-DEC | * , - / ? | |
Day of Week | Yes | 0-7 or SUN-MON | * , - / ? | 0 to 6 are Sunday to Saturday 7 is Sunday, the same as 0 |
Note: Weekday and month names are case insensitive. Both MON and mon works.
It is also possible to use the following "nicknames" as pattern.
Nickname | Description |
---|
@yearly | Run once a year, ie. "0 0 1 1 *". |
@annually | Run once a year, ie. "0 0 1 1 *". |
@monthly | Run once a month, ie. "0 0 1 * *". |
@weekly | Run once a week, ie. "0 0 * * 0". |
@daily | Run once a day, ie. "0 0 * * *". |
@hourly | Run once an hour, ie. "0 * * * *". |
Examples
Expressions
Cron('15-45/10 */5 1,2,3 ? JAN-MAR SAT', { legacyMode: false }, function () {
console.log('This will run every tenth second between second 15-45');
console.log('every fifth minute of hour 1,2 and 3 when day of month');
console.log('is the same as when Cron started, every saturday in January to March.');
});
Interval
Cron('* * * 7-16 * MON-FRI', { interval: 90, legacyMode: false }, function () {
console.log('This will trigger every 90th second at 7-16 on mondays to fridays.');
});
Find dates
const nextMonth = Cron("@monthly").next(),
nextSunday = Cron("@weekly").next(),
nextSat29feb = Cron("0 0 0 29 2 6", { legacyMode: false }).next(),
nextSunLastOfMonth = Cron("0 0 0 L * 7", { legacyMode: false }).next();
console.log("First day of next month: " + nextMonth.toLocaleDateString());
console.log("Next sunday: " + nextSunday.toLocaleDateString());
console.log("Next saturday at 29th of february: " + nextSat29feb.toLocaleDateString());
console.log("Next month ending with a sunday: " + nextSunLastOfMonth.toLocaleDateString());
With options
const job = Cron(
'* * * * *',
{
maxRuns: Infinity,
startAt: "2021-11-01T00:00:00",
stopAt: "2021-12-01T00:00:00",
timezone: "Europe/Stockholm"
},
function() {
console.log('This will run every minute, from 2021-11-01 to 2021-12-01 00:00:00');
}
);
Job controls
const job = Cron('* * * * * *', (self) => {
console.log('This will run every second. Pause on second 10. Resume on 15. And quit on 20.');
console.log('Current second: ', new Date().getSeconds());
console.log('Previous run: ' + self.previous());
console.log('Next run: ' + self.next());
});
Cron('10 * * * * *', {maxRuns: 1}, () => job.pause());
Cron('15 * * * * *', {maxRuns: 1}, () => job.resume());
Cron('20 * * * * *', {maxRuns: 1}, () => job.stop());
Passing a context
const data = {
what: "stuff"
};
Cron('* * * * * *', { context: data }, (_self, context) => {
console.log('This will print stuff: ' + context.what);
});
Cron('*/5 * * * * *', { context: data }, (self, context) => {
console.log('After this, other stuff will be printed instead');
context.what = "other stuff";
self.stop();
});
Fire on a specific date/time
let job = Cron("2025-01-01T23:00:00",{timezone: "Europe/Stockholm"},() => {
console.log('This will run at 2025-01-01 23:00:00 in timezone Europe/Stockholm');
});
if (job.next() === null) {
} else {
console.log("Job will fire at " + job.next());
}
Act at completion
const job = new Cron("0/5 * * * * *", { maxRuns: 3 }, (job) => {
console.log('Job Running');
if (!job.next()) {
console.log('Last execution');
}
});
if (!job.next() && !job.previous()) {
console.log('No executions scheduled');
}
Contributing
See Contribution Guide
License
MIT