Migration
Upgrading Croner
This section covers upgrading to Croner from previous versions. Croner follows the Semantic Versioning (SemVer) standard. Be mindful that major updates may cause breaking changes.
Upgrading from 4.x to 5.x
If upgrading from version 4.x to 5.x, the most significant change is the way day-of-month and day-of-week are combined. You can read more about this in issue #53. The new mode is called legacyMode and can be disabled using the options.
Upgrading from 5.x to 6.x
For upgrading from version 5.x to 6.x, CommonJS and UMD builds were separated in the dist folder. Several method names were also changed to make them more descriptive:
next()->nextRun()enumerate()->nextRuns()current()->currentRun()previous()->previousRun()running()->isRunning()busy()->isBusy()
Upgrading from 6.x to 7.x
Version 7.x introduces significant changes, including the introduction of the nth weekday specifier #. Furthermore, there's a modification in the way L functions in the day-of-week field. In version 6.x, L had flexibility in its positioning: both LSUN and SUNL were valid expressions to denote the last Sunday of the month. However, starting from version 7.x, L must be used in conjunction with the nth weekday specifier #, like so: SUN#L.
Upgrading from 7.x to 8.x
Version 8.x introduces no significant changes in the API for Croner. However, a major change is the discontinuation of support for Node.js versions prior to 18.0. It is crucial to ensure that your environment is running Node.js 18.0 or higher before upgrading to 8.x. If you rely on Node <= 16 you should stick with 7.x.
Upgrading from 8.x to 9.x
Version 9.x brings several changes to Croner to fix existing issues and ensure consistency in code:
Changes to the API
-
newis no longer optional. Previously, it was possible to instantiate Croner usingCron(/*...*/)now, you need to usenew Cron(/* ... */). -
The default export is removed. Previously, you could
import Cron from, now you need toimport { Cron } from. The same goes for require.
Changes to the file structure
-
Files in the
/distdirectory are always minified, no need to be explicit with that through the file names. As a result,croner.min.jsis nowcroner.js. -
Typings are moved from
/typesto/dist. -
Only the
/srcdirectory is exposed in the JSR module.
Upgrading from 9.x to 10.x
Version 10.x introduces new pattern syntax features and configuration options.
New pattern syntax:
- Year field: 7-field patterns now supported. Example:
0 12 * * * * 2025runs only in 2025. - W modifier: Nearest weekday to a date. Example:
0 12 15W * *runs on weekday closest to the 15th. - + modifier: Explicit AND logic for day matching. Example:
0 12 15 * +FRIruns only when 15th is Friday.
Changed pattern behavior:
- ? character: Now acts as wildcard alias (same as
*). Previously replaced with current time values.
Option renamed (backward compatible):
legacyModeis deprecated but still works. UsedomAndDowinstead.domAndDow: false(default) uses OR logic - matches if day of month OR day of week matches.domAndDow: trueuses AND logic - matches only when both day of month AND day of week match.- Note:
legacyMode: trueequalsdomAndDow: false.
New options:
dayOffset- Offset scheduled dates by a number of days. Positive shifts forward, negative shifts backward. Example:dayOffset: -1schedules one day before the pattern match.mode- Specify the cron pattern mode:"auto"(default),"5-part","6-part","7-part","5-or-6-parts", or"6-or-7-parts". Controls how many fields are expected and how seconds/years are handled.alternativeWeekdays- Enable Quartz-style weekday numbering where Sunday=1, Monday=2, ..., Saturday=7. Whenfalse(default), uses standard cron format where Sunday=0, Monday=1, ..., Saturday=6. Example:new Cron("0 0 0 * * 1", { alternativeWeekdays: true })runs on Sunday in Quartz mode.sloppyRanges- Enable non-standard stepping formats for backward compatibility. Whenfalse(default), only wildcard (*/step) or range (min-max/step) formats are allowed, following strict vixie cron and cronie parsing rules. Whentrue, allows legacy formats like/10,5/5,30/30. This option was added to maintain backward compatibility while enforcing OCPS compliance by default.
Pattern syntax strictness: By default, Croner now follows strict range parsing rules as used by vixie cron and cronie (upcoming OCPS requirement):
- ✅ Valid:
*/10,0-59/10,30-50/10(wildcard or range with stepping) - ❌ Invalid:
/10,0/10,30/30(missing prefix or numeric prefix with stepping) - To allow the old behavior, use
sloppyRanges: trueoption:new Cron("/10 * * * * *", { sloppyRanges: true })
Upgrading from 10.x to 11.x
Version 11.x adds iterator-based schedule traversal (enumerate()), aligns DST handling with
croner-rust, and tightens startAt and dayOffset behavior.
Breaking changes
startAtis inclusive. A pattern match exactly atstartAtnow runs. In10.x, the first run was the following match. This does not apply withintervalset, which works as in10.x. AstartAtbetween two whole seconds counts from the next whole second. A run in the same second before a fractionalstopAtis now included.- With
dayOffset,nextRun()and friends return actual run times.nextRun(),nextRuns(),previousRuns(),msToNext()andenumerate()find the first run after (or before) the reference, as the scheduler does. In10.xthey shifted the next pattern match after the reference, sodayOffset: -1could return a time before the reference, andmsToNext()ignored the offset.startAtandstopAtalso apply to the shifted run time. In10.xthey applied to the unshifted pattern match, so a job could run beforestartAtor afterstopAt. previousRun()returns when the previous run started, as documented. In10.xit returned when the run finished.- DST transitions are handled as in croner-rust, see DST behavior below.
Other behavior changes
- ISO 8601 strings are read in the job's
timezoneorutcOffset, inmsToNext(),nextRun(),match(),startAt,stopAtand one-off dates. In10.xsome of these were read in system local time. Strings withZor an offset, like2026-01-01T00:00:00Z, are read as that exact instant. - Patterns that can never match, like
0 0 30 2 *or0 0 16 * 5#LwithdomAndDow: true, makenextRun()returnnullandnextRuns()/previousRuns()return[]. In10.x,previousRuns()could throw aTypeErrorand the5#Lcase overflowed the stack.
Past date-based jobs
In 10.x, a date-based job whose date had already passed never fired. In 11.x, a date-based
job dated up to 1 second in the past fires immediately, so a job created for "now" is not lost.
To also fire older dates, enable allowPast:
new Cron("2020-01-01T00:00:00Z", { allowPast: true }, () => {
// explicit catch-up behavior
});
New API in 11.x
enumerate(startAt?)returns a statefulCronIteratorfor sequential traversal:- Works with
for...ofand destructuring. - Supports
next(),peek(), andreset(). - Yields runs strictly after the given start date, unlike the inclusive
startAtoption. - Honors
maxRuns. TheCronIteratorclass is exported.
- Works with
allowPastoption to fire date-based jobs more than 1 second in the past.- New type exports:
CatchCallbackFn,CronModeandProtectCallbackFn.
DST behavior aligned with croner-rust
Croner now handles DST transitions the same way as croner-rust and Vixie cron. This deviates from the recommendation in OCPS 1.4 §4.3.1, see Pattern. Jobs are either fixed-time (one value each for seconds, minutes and hours) or interval jobs (anything else).
- Fixed-time jobs in a DST gap (spring forward) run at the first valid time after the gap. In
10.x they ran as much later as the gap was long:
0 30 2 * * *ran at 3:30 AM, now at 3:00 AM. - Interval jobs in a DST overlap (fall back) run on both occurrences of a repeated time, so
* * * * *keeps running through the repeated hour. In 10.x the second pass was skipped, and timers could be re-armed with negative delays throughout it (#399). - Interval jobs in a DST gap skip the times that don't exist. In 10.x,
nextRuns()could go backwards or repeat times around the gap. - Patterns with more than one hour, minute or second, like
0 30 1,2 * * *, are interval jobs, so 1:30 AM runs twice on the day the clock goes back, where 1:00-2:00 repeats (as in America/New_York). - Fixed-time jobs in a DST overlap run once, at the first occurrence, as in 10.x.
nextRun(),previousRuns()andenumerate()move in real time, also when started from inside a repeated hour.dayOffsetshifts the run time by whole days of 24 hours, also when that run time was moved by a DST gap. In America/New_York,0 30 2 * * *withdayOffset: -1moves the March 8 2026 match to 3:00 AM EDT, after the gap, and runs 24 hours earlier: 2:00 AM EST on March 7, not 2:30 AM.
Switching from Cron
If you're currently using the cron package and want to migrate to Croner, the following steps can guide you:
Step 1: Install Croner
To install the croner package, run the following command in your terminal:
npm install croner
Step 2: Update your code to use Croner
The croner package has a different API compared to the cron package. Here's an example of how to create a new CronJob using croner:
// CronJob constructor is called just Cron in Croner
const { Cron } = require('croner');
// If you have a lot of code using the CrobJob constructor, you can re-use the name like this
// const { Cron: CronJob } = require('croner');
const job = new Cron('0 0 12 * * *', { /* options */ }, () => {
console.log('This job will run at 12:00 PM every day.');
});
// No job.start() needed, the job is scheduled immediately (use { paused: true } and job.resume() to start it later)
Step 3: Update your tests
If you have tests for your code, you'll need to update them to use Croner instead of Cron. Make sure to test that your jobs are still running as expected after the migration.
Switching from node-cron
Here's how to migrate from the node-cron package to Croner:
Install croner package:
First, install the croner package in your project:
npm install croner
Replace the import statement:
Next, update the import statement for cron to croner. Replace the line that reads:
const cron = require('node-cron');
with:
const { Cron } = require('croner');
Update your cron job:
Here's an example of how to migrate a cron job:
// node-cron
cron.schedule('* * * * *', () => {
console.log('Running a task every minute');
}, { timezone: "Europe/Oslo" });
// croner
new Cron('* * * * *', { timezone: "Europe/Oslo" }, () => {
console.log('Running a task every minute');
});
By following these steps, you should be able to migrate from node-cron to croner without any issues.