Scheduling

Cron expressions

A schedule is six fields separated by whitespace. A job runs at every moment where all six match.

On this page

The expression above means 09:30:00, Monday to Friday.

Field syntax

Every field accepts the same syntax, and each one can combine forms in a comma-separated list. The examples below are for the seconds field.

Form Example Matches
Any value * Every value in the field
Value 5 5
Range 10-13 10, 11, 12, 13
List 1,15,30 1, 15, 30
Step */15 0, 15, 30, 45
Range with step 10-30/5 10, 15, 20, 25, 30
Start with step 5/10 5, 15, 25, 35, 45, 55
Mixed list 1-5/2,10,20/20 1, 3, 5, 10, 20, 40

Try one

This uses the same parser as the package, so what it accepts is what baker.add() accepts. Click a field to select it in the input.

Second
0at :00
Minute
0at :00
Hour
1212:xx
Day
*every day
Month
*every month
Weekday
1-5Mon to Fri
Next run in--:--:--Your local time
Upcoming runs
  1.  
  2.  
  3.  
  4.  
  5.  

Differences from crontab

If you’re used to a system crontab, four things will catch you out.

Six fields, not five

The seconds field comes first and isn’t optional. A five-field expression is rejected. To convert one, put a 0 in front of it, so */5 * * * * becomes 0 */5 * * * *.

Numbers only

Names such as MON and JAN aren’t supported, and neither are ?, L, W or #. Sunday is 0, and 7 is out of range.

Day of month and day of week must both match

Most cron implementations run when either day field matches. Cronbake requires both, so 0 0 0 13 * 5 only fires on Friday the 13th. On a standard crontab, the same two day fields would fire on every Friday and on every 13th.

Steps restart at each field

A step counts through the values of its field and doesn’t carry over into the next one. */7 in the seconds field matches 0, 7, 14 and so on up to 56, and then the next match is 0 in the following minute, only four seconds later.

A step larger than the field collapses to one value. */90 in the seconds field matches only 0, so it runs once a minute and not every 90 seconds. If you need an exact interval, keep your own timer.

Validation

An invalid expression fails early and loudly.

Cron.isValid("60 * * * * *"); // false

// Throws: Cron field out of range: 60
Cron.parse("60 * * * * *");

// Throws: Invalid cron expression: 60 * * * * *
baker.add({ name: "bad", cron: "60 * * * * *", callback });

TypeScript catches mistakes in presets, but it can’t check six-field strings, so those fail when the job is created.

baker.add({ name: "a", cron: "@every_5_minutes", callback });

// @ts-expect-error "mins" is not a unit, so this doesn't compile
baker.add({ name: "b", cron: "@every_5_mins", callback });

Dates that never come

Validation checks syntax and bounds, not the calendar. 0 0 0 31 2 * asks for the 31st of February, so it passes Cron.isValid() and baker.add(), then throws when the job starts because no matching date exists.

Using expressions without a job

The static methods on Cron don’t need a running job.

Cron.parse("0 */15 9-17 * * 1-5");
// { second: [0], minute: [0, 15, 30, 45], hour: [9, 10, ..., 17], ... }

Cron.getNext("@daily"); // the next midnight
Cron.getPrevious("@daily"); // the last midnight

Reference dates Next release

getNext() and getPrevious() take an optional Date to search from. The search doesn’t include that moment itself, so you can step through a schedule by feeding each result back in.

let run = new Date();
const upcoming: Date[] = [];

for (let i = 0; i < 5; i++) {
  run = Cron.getNext("0 30 9 * * 1-5", run);
  upcoming.push(run);
}

Both methods search up to 400 years in either direction, then throw if nothing matches. They also throw if the reference date is invalid. In 0.4.0 the second argument is ignored.

All of this uses the time zone of the process. See time zones for what that means in practice.

esc

Try overlap, restore or timezone.