croner
Version:
Trigger functions and/or evaluate cron expressions in JavaScript. No dependencies. Most features. All environments.
340 lines (258 loc) • 22.9 kB
Markdown
<p align="center">
<img src="https://cdn.jsdelivr.net/gh/hexagon/croner@master/croner.png" alt="Croner" width="150" height="150"><br>
Trigger functions or evaluate cron expressions in JavaScript or TypeScript. No dependencies. All features. Node. Deno. Bun. Browser. <br><br>Try it live on <a href="https://jsfiddle.net/hexag0n/hoa8kwsb/">jsfiddle</a>, and check out the full documentation on <a href="https://croner.56k.guru">croner.56k.guru</a>.<br>
</p>
# Croner - Cron for JavaScript and TypeScript
[](https://badge.fury.io/js/croner) [](https://jsr.io/@hexagon/croner) [](https://www.npmjs.org/package/croner)
 [](https://github.com/Hexagon/croner/blob/master/LICENSE)
* Trigger functions in JavaScript using [Cron](https://en.wikipedia.org/wiki/Cron#CRON_expression) syntax.
* Evaluate cron expressions and get a list of upcoming run times.
* Supports seconds and year fields, `L` (last), `W` (weekday), `#` (nth occurrence), and `+` (AND logic).
* Works in Node.js >=18.0 (both require and import), Deno >=2.0 and Bun >=1.0.0.
* Works in browsers as standalone, UMD or ES-module.
* Target different [time zones](https://croner.56k.guru/usage/examples/#time-zone).
* Built-in [overrun protection](https://croner.56k.guru/usage/examples/#overrun-protection)
* Built-in [error handling](https://croner.56k.guru/usage/examples/#error-handling)
* Includes [TypeScript](https://www.typescriptlang.org/) typings.
* Support for asynchronous functions.
* Pause, resume, or stop execution after a task is scheduled.
* Operates in-memory, with no need for a database or configuration files.
* Zero dependencies.
Quick examples:
```javascript
// Basic: Run a function at the interval defined by a cron expression
const job = new Cron('*/5 * * * * *', () => {
console.log('This will run every fifth second');
});
// Enumeration: What dates do the next 100 sundays occur on?
const nextSundays = new Cron('0 0 0 * * 7').nextRuns(100);
console.log(nextSundays);
// Days left to a specific date
const msLeft = new Cron('59 59 23 24 DEC *').nextRun() - new Date();
console.log(Math.floor(msLeft/1000/3600/24) + " days left to next christmas eve");
// Run a function at a specific date/time using a non-local timezone (time is ISO 8601 local time)
// This will run 2024-01-23 00:00:00 according to the time in Asia/Kolkata
new Cron('2024-01-23T00:00:00', { timezone: 'Asia/Kolkata' }, () => { console.log('Yay!') });
// Check if a date matches a cron pattern
const mondayCheck = new Cron('0 0 0 * * MON');
console.log(mondayCheck.match('2024-01-01T00:00:00')); // true (Monday)
console.log(mondayCheck.match('2024-01-02T00:00:00')); // false (Tuesday)
```
More [examples](https://croner.56k.guru/usage/examples/)...
## Installation
Full documentation on installation and usage is found at <https://croner.56k.guru>
> **Note**
> If you are migrating from a different library such as `cron` or `node-cron`, or upgrading from a older version of croner, see the [migration section](https://croner.56k.guru/migration/) of the manual.
Install croner using your favorite package manager or CDN, then include it in you project:
Using Node.js or Bun
```javascript
// ESM Import ...
import { Cron } from "croner";
// ... or CommonJS Require, destructure to add type hints
const { Cron } = require("croner");
```
Using Deno
```typescript
// From deno.land/x
import { Cron } from "https://deno.land/x/croner@10.0.0/dist/croner.js";
// ... or jsr.io
import { Cron } from "jsr:@hexagon/croner@10.0.0";
```
In a webpage using the UMD-module
```html
<script src="https://cdn.jsdelivr.net/npm/croner@10/dist/croner.umd.min.js"></script>
```
## Documentation
### Signature
Cron takes three arguments
* [pattern](#pattern)
* [options](#options) (optional)
* scheduled function (optional)
```javascript
// Parameters
// - First: Cron pattern, js date object (fire once), or ISO 8601 time string (fire once)
// - Second: Options (optional)
// - Third: Function run trigger (optional)
const job = new Cron("* * * * * *", { maxRuns: 1 }, () => {} );
// If function is omitted in constructor, it can be scheduled later
job.schedule(job, /* optional */ context) => {});
```
The job will be sceduled to run at next matching time unless you supply option `{ paused: true }`. The `new Cron(...)` constructor will return a Cron instance, later called `job`, which have a couple of methods and properties listed below.
#### Status
```javascript
job.nextRun( /*optional*/ startFromDate ); // Get a Date object representing the next run.
job.nextRuns(10, /*optional*/ startFromDate ); // Get an array of Dates, containing the next n runs.
job.previousRuns(10, /*optional*/ referenceDate ); // Get an array of Dates, containing previous n scheduled runs.
job.msToNext( /*optional*/ startFromDate ); // Get the milliseconds left until the next execution.
job.currentRun(); // Get a Date object showing when the current (or last) run was started.
job.previousRun( ); // Get a Date object showing when the previous job was started.
job.match( date ); // Check if a Date object or date string matches the cron pattern (true or false).
job.isRunning(); // Indicates if the job is scheduled and not paused or killed (true or false).
job.isStopped(); // Indicates if the job is permanently stopped using `stop()` (true or false).
job.isBusy(); // Indicates if the job is currently busy doing work (true or false).
job.getPattern(); // Returns the original pattern string
job.getOnce(); // Returns the original run-once date (Date or null)
```
#### Control functions
```javascript
job.trigger(); // Force a trigger instantly
job.pause(); // Pause trigger
job.resume(); // Resume trigger
job.stop(); // Stop the job completely. It is not possible to resume after this.
// Note that this also removes named jobs from the exported `scheduledJobs` array.
```
#### Properties
```javascript
job.name // Optional job name, populated if a name were passed to options
```
#### Options
| Key | Default value | Data type | Remarks |
|--------------|----------------|----------------|---------------------------------------|
| name | undefined | String | If you specify a name for the job, Croner will keep a reference to the job in the exported array `scheduledJobs`. The reference will be removed on `.stop()`. |
| maxRuns | Infinite | Number | |
| catch | false | Boolean\|Function | Catch unhandled errors in triggered function. Passing `true` will silently ignore errors. Passing a callback function will trigger this callback on error. |
| timezone | undefined | String | Timezone in Europe/Stockholm format |
| startAt | undefined | String | ISO 8601 formatted datetime (2021-10-17T23:43:00)<br>in local time (according to timezone parameter if passed) |
| stopAt | undefined | String | ISO 8601 formatted datetime (2021-10-17T23:43:00)<br>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 |
| domAndDow | false | boolean | Combine day-of-month and day-of-week using true = AND, false = OR (default) |
| legacyMode | (deprecated) | boolean | **Deprecated:** Use `domAndDow` instead. Inverse of `domAndDow` (legacyMode: true = domAndDow: false). |
| unref | false | boolean | Setting this to true unrefs the internal timer, which allows the process to exit even if a cron job is running. |
| utcOffset | undefined | number | Schedule using a specific utc offset in minutes. This does not take care of daylight savings time, you probably want to use option `timezone` instead. |
| protect | undefined | boolean\|Function | Enabled over-run protection. Will block new triggers as long as an old trigger is in progress. Pass either `true` or a callback function to enable |
| alternativeWeekdays | false | boolean | Enable Quartz-style weekday numbering (1=Sunday, 2=Monday, ..., 7=Saturday). When false (default), uses standard cron format (0=Sunday, 1=Monday, ..., 6=Saturday). |
> **Warning**
> Unreferencing timers (option `unref`) is only supported by Node.js and Deno.
> Browsers have not yet implemented this feature, and it does not make sense to use it in a browser environment.
#### Pattern
Croner uses [Vixie Cron](https://en.wikipedia.org/wiki/Cron#CRON_expression) based expressions with the following powerful extensions:
```javascript
// ┌──────────────── (optional) second (0 - 59)
// │ ┌────────────── minute (0 - 59)
// │ │ ┌──────────── hour (0 - 23)
// │ │ │ ┌────────── day of month (1 - 31)
// │ │ │ │ ┌──────── month (1 - 12, JAN-DEC)
// │ │ │ │ │ ┌────── day of week (0 - 6, SUN-Mon)
// │ │ │ │ │ │ (0 to 6 are Sunday to Saturday; 7 is Sunday, the same as 0)
// │ │ │ │ │ │ ┌──── (optional) year (1 - 9999)
// │ │ │ │ │ │ │
// * * * * * * *
```
* **Optional second and year fields** for enhanced precision:
- 6-field format: `SECOND MINUTE HOUR DAY-OF-MONTH MONTH DAY-OF-WEEK`
- 7-field format: `SECOND MINUTE HOUR DAY-OF-MONTH MONTH DAY-OF-WEEK YEAR`
- Supported year range: 1-9999
* **Advanced calendar modifiers**:
- *L*: Last day of month or last occurrence of a weekday. `L` in day-of-month = last day of month; `5#L` or `FRI#L` = last Friday of the month.
- *W*: Nearest weekday. `15W` triggers on the weekday closest to the 15th (moves to Friday if 15th is Saturday, Monday if 15th is Sunday). Won't cross month boundaries.
- *#*: Nth occurrence of a weekday. `5#2` = second Friday; `MON#1` = first Monday of the month.
* **Enhanced logical control**:
- *+*: Explicit AND logic modifier. Prefix the day-of-week field with `+` to require both day-of-month AND day-of-week to match. Example: `0 12 1 * +MON` only triggers when the 1st is also a Monday.
- *?*: Wildcard alias (behaves identically to `*`). **Non-portable**: Its use is discouraged in patterns intended for cross-system use. Supported in all fields for compatibility, but primarily meaningful in day-of-month and day-of-week fields.
- Proper DST handling: Jobs scheduled during DST gaps are skipped; jobs in DST overlaps run once at first occurrence.
* Croner allows you to pass a JavaScript Date object or an ISO 8601 formatted string as a pattern. The scheduled function will trigger at the specified date/time and only once. If you use a timezone different from the local timezone, you should pass the ISO 8601 local time in the target location and specify the timezone using the options (2nd parameter).
* By default, Croner uses OR logic for day-of-month and day-of-week. Example: `0 20 1 * MON` triggers on the 1st of the month OR on Mondays. Use the `+` modifier (`0 20 1 * +MON`) or `{ domAndDow: true }` for AND logic. For more information, see issue [#53](https://github.com/Hexagon/croner/issues/53).
| Field | Required | Allowed values | Allowed special characters | Remarks |
|--------------|----------|----------------|----------------------------|---------------------------------------|
| Seconds | Optional | 0-59 | * , - / ? | Optional, defaults to 0 |
| Minutes | Yes | 0-59 | * , - / ? | |
| Hours | Yes | 0-23 | * , - / ? | |
| Day of Month | Yes | 1-31 | * , - / ? L W | L = last day, W = nearest weekday |
| Month | Yes | 1-12 or JAN-DEC| * , - / ? | |
| Day of Week | Yes | 0-7 or SUN-MON | * , - / ? L # + | 0 and 7 = Sunday (standard mode)<br>1-7 = Sunday-Saturday (Quartz mode with `alternativeWeekdays: true`)<br># = nth occurrence (e.g. MON#2)<br>+ = AND logic modifier |
| Year | Optional | 1-9999 | * , - / | Optional, defaults to * |
> **Note**
> Weekday and month names are case-insensitive. Both `MON` and `mon` work.
> When using `L` in the Day of Week field with a range, it affects all specified weekdays. For example, `5-6#L` means the last Friday and Saturday in the month.
> The `#` character specifies the "nth" weekday of the month. For example, `5#2` = second Friday, `MON#1` = first Monday.
> The `W` character operates within the current month and won't cross month boundaries. If the 1st is a Saturday, `1W` matches Monday the 3rd.
> The `+` modifier (OCPS 1.4) enforces AND logic: `0 12 1 * +MON` only runs when the 1st is also a Monday.
> **Quartz mode**: Enable `alternativeWeekdays: true` to use Quartz-style weekday numbering (1=Sunday, 2=Monday, ..., 7=Saturday) instead of the standard format (0=Sunday, 1=Monday, ..., 6=Saturday). This is useful for compatibility with Quartz cron expressions.
**Predefined schedule nicknames** are supported:
| Nickname | Description |
| -------- | ----------- |
| \ / \ | Run once a year, i.e. "0 0 1 1 *". |
| \ | Run once a month, i.e. "0 0 1 * *". |
| \ | Run once a week, i.e. "0 0 * * 0". |
| \ / \ | Run once a day, i.e. "0 0 * * *". |
| \ | Run once an hour, i.e. "0 * * * *". |
## Why another JavaScript cron implementation
Because the existing ones are not good enough. They have serious bugs, use bloated dependencies, do not work in all environments, and/or simply do not work as expected.
For example, some popular alternatives include large datetime libraries as dependencies, which significantly increases bundle size. Croner has zero dependencies and a much smaller footprint, making it ideal for applications where bundle size matters.
| | croner | cronosjs | node-cron | cron | node-schedule |
|---------------------------|:-------------------:|:-------------------:|:---------:|:-------------------------:|:-------------------:|
| **Platforms** |
| Node.js (CommonJS) | ✓ | ✓ | ✓ | ✓ | ✓ |
| Browser (ESMCommonJS) | ✓ | ✓ | | | |
| Deno (ESM) | ✓ | | | | |
| **Features** |
| Over-run protection | ✓ | | | | |
| Error handling | ✓ | | | | ✓ |
| Typescript typings | ✓ | ✓ | | ✓ | |
| Unref timers (optional) | ✓ | | | ✓ | |
| dom-OR-dow | ✓ | ✓ | ✓ | ✓ | ✓ |
| dom-AND-dow (optional) | ✓ | | | | |
| Next run | ✓ | ✓ | | ✓ | ✓ |
| Next n runs | ✓ | ✓ | | ✓ | |
| Timezone | ✓ | ✓ | ✓ | ✓ | ✓ |
| Minimum interval | ✓ | | | | |
| Controls (stop/resume) | ✓ | ✓ | ✓ | ✓ | ✓ |
| Range (0-13) | ✓ | ✓ | ✓ | ✓ | ✓ |
| Stepping (*/5) | ✓ | ✓ | ✓ | ✓ | ✓ |
| Seconds field | ✓ | | | | |
| Year field | ✓ | | | | |
| Last day of month (L) | ✓ | ✓ | | | |
| Nth weekday of month (#) | ✓ | ✓ | | | |
| Nearest weekday (W) | ✓ | ✓ | | | |
| AND logic modifier (+) | ✓ | | | | |
<details>
<summary>In depth comparison of various libraries</summary>
| | croner | cronosjs | node-cron | cron | node-schedule |
|---------------------------|:-------------------:|:-------------------:|:---------:|:-------------------------:|:-------------------:|
| **Size** |
| Minified size (KB) | 22.7 | 14.9 | 20.1 | 93.7 :warning: | 107.8 :warning: |
| Bundlephobia minzip (KB) | 6.8 | 5.1 | 6.1 | 28.2 | 31.2 :warning: |
| Dependencies | 0 | 0 | 1 | 1 | 3 :warning: |
| **Popularity** |
| Downloads/week [^1] | 2019K | 31K | 447K | 1366K | 1046K |
| **Quality** |
| Issues [^1] | 0 | 2 | 133 :warning: | 13 | 145 :warning: |
| Code coverage | 99% | 98% | 100% | 81% | 94% |
| **Performance** |
| Ops/s `1 2 3 4 5 6` | 160 651 | 55 593 | N/A :x: | 6 313 :warning: | 2 726 :warning: |
| Ops/s `0 0 0 29 2 *` | 176 714 | 67 920 | N/A :x: | 3 104 :warning: | 737 :warning: |
| **Tests** | **11/11** | **10/11** | **0/11** [^4] :question: | **7/11** :warning: | **9/11** |
> **Note**
> * Table last updated at 2023-10-10
> * node-cron has no interface to predict when the function will run, so tests cannot be carried out.
> * All tests and benchmarks were carried out using [https://github.com/Hexagon/cron-comparison](https://github.com/Hexagon/cron-comparison)
[^1]: As of 2023-10-10
[^2]: Requires support for L-modifier
[^3]: In dom-AND-dow mode, only supported by croner at the moment.
[^4]: Node-cron has no way of showing next run time.
</details>
## Development
### Master branch
  
This branch contains the latest stable code, released on npm's default channel `latest`. You can install the latest stable revision by running the command below.
```
npm install croner --save
```
### Dev branch
  
This branch contains code currently being tested, and is released at channel `dev` on npm. You can install the latest revision of the development branch by running the command below.
```
npm install croner
```
> **Warning**
> Expect breaking changes if you do not pin to a specific version.
A list of fixes and features currently released in the `dev` branch is available [here](https://github.com/Hexagon/croner/issues?q=is%3Aopen+is%3Aissue+label%3Areleased-in-dev)
## Contributing & Support
Croner is founded and actively maintained by Hexagon. If you find value in Croner and want to contribute:
- Code Contributions: See our [Contribution Guide](https://croner.56k.guru/contributing/) for details on how to contribute code.
- Sponsorship and Donations: See [github.com/sponsors/hexagon](https://github.com/sponsors/hexagon)
Your trust, support, and contributions drive the project. Every bit, irrespective of its size, is deeply appreciated.
## License
MIT License