Cron Atlas

Cron expression for every two weeks

Every other Monday at 09:00, with a date check.

Can’t be written in Cron. The runs below follow the workaround.

Next 10 runs

In Cron, worked out in your browser from the current time.

At a glance

Runs
26 in 2027
Gap between runs
Always 14 days
Expressible in
None directly; tested workarounds for all six
Clock changes
No effect in the zones checked

Open in the tester

In each dialect

Every expression here is generated by the tester’s own engine and checked run for run against the others, so “Exact” means the same clock times in the same zone. Each one links to the tester.

Can’t be written in Cron

Cron fields cannot count weeks. Day of week repeats every seven days and day of month restarts each month, so there is no way to skip alternate weeks.

Workaround

Schedule 0 9 * * 1 (at 09:00, on Mondays) and let a shell test go ahead only when the number of whole weeks since 1 January 1970 is even. In a crontab, % must be written \%, or cron cuts the line there.

0 9 * * 1 [ $(( $(date +\%s) / 604800 \% 2 )) -eq 0 ] && /path/to/command
No equivalent
Can’t be written in EventBridge

Cron fields cannot count weeks. Day of week repeats every seven days and day of month restarts each month, so there is no way to skip alternate weeks.

Workaround

rate(14 days) repeats every 14 days, counted from when the rule or schedule is created, so create it at the moment you want the first run. A day here is 24 hours, so with a Scheduler time zone the run time moves by an hour across a clock change. For a fixed Monday 09:00, schedule 0 9 ? * MON * and have the target skip alternate weeks with the same check as below.

aws events put-rule --name every-two-weeks --schedule-expression "rate(14 days)"
No equivalent
Can’t be written in Quartz

Cron fields cannot count weeks. Day of week repeats every seven days and day of month restarts each month, so there is no way to skip alternate weeks.

Workaround

Quartz cron cannot count weeks, but Quartz has a trigger that can. A CalendarIntervalTrigger repeats every two weeks from its start time, so start it on a Monday at 09:00. preserveHourOfDayAcrossDaylightSavings(true) keeps it at 09:00 local when the clocks change.

TriggerBuilder.newTrigger()
    .startAt(firstMondayAt9)
    .withSchedule(CalendarIntervalScheduleBuilder.calendarIntervalSchedule()
        .withIntervalInWeeks(2)
        .preserveHourOfDayAcrossDaylightSavings(true))
    .build();
No equivalent
Can’t be written in Kubernetes

Cron fields cannot count weeks. Day of week repeats every seven days and day of month restarts each month, so there is no way to skip alternate weeks.

Workaround

Schedule 0 9 * * 1 and put the test in front of the container’s command, so the Job exits straight away unless the number of whole weeks since 1 January 1970 is even. The test uses GNU date, so Alpine and other BusyBox images need coreutils. The container’s clock is UTC unless you set TZ; match it to timeZone.

schedule: "0 9 * * 1"
# in the container:
command: ["sh", "-c", "[ $(( $(date +%s) / 604800 % 2 )) -eq 0 ] && exec /app/job"]
No equivalent
Can’t be written in GitHub

Cron fields cannot count weeks. Day of week repeats every seven days and day of month restarts each month, so there is no way to skip alternate weeks.

Workaround

Schedule 0 9 * * 1 and make the test the first line of the step that does the work, so the step ends early unless the number of whole weeks since 1 January 1970 is even. The runner’s clock is UTC; if you set timezone, give the step the same zone with env: TZ.

- cron: '0 9 * * 1'
# in the job:
- run: |
    [ $(( $(date +%s) / 604800 % 2 )) -eq 0 ] || exit 0
    ./your-script
No equivalent
Can’t be written in Azure

Cron fields cannot count weeks. Day of week repeats every seven days and day of month restarts each month, so there is no way to skip alternate weeks.

Workaround

Schedule 0 0 9 * * Mon and return early unless the number of whole weeks since 1 January 1970 is even. UtcNow matches the default UTC schedule; if you set WEBSITE_TIME_ZONE, compare local time instead.

[TimerTrigger("0 0 9 * * Mon")]
// first line of the function:
if (DateTimeOffset.UtcNow.ToUnixTimeSeconds() / 604800 % 2 != 0) return;
No equivalent

About this schedule

Cron fields have no idea of week parity: day of week repeats every week and day of month restarts every month, so no dialect here can say “every other Monday”. The reliable fix is to run every Monday and let a date check skip alternate weeks. Counting whole weeks since 1 January 1970 keeps the rhythm unbroken across year ends, which ISO week numbers do not, because some years have 53 weeks.

Clock changes

None of these times falls in the hour that clocks skip or repeat in New York, London, Berlin, Helsinki or Sydney, so every dialect runs it normally all year there. Zones that change clocks at other hours can still differ.

Questions

What is the cron expression for every two weeks?
None of the six dialects can say it in one expression. Schedule 0 9 * * 1 (at 09:00, on Mondays) and let a shell test go ahead only when the number of whole weeks since 1 January 1970 is even. In a crontab, % must be written \%, or cron cuts the line there.
Can I use ISO week numbers for every other week?
You can, but years with 53 weeks break the pattern: week 53 and week 1 are both odd, so the rhythm slips at that year end. Counting weeks since 1970, as the check on this page does, never slips.

More schedules