---
name: cron-expression-conventions
source: https://app.decimal.ai/s/cron-expression-conventions@1/SKILL.md
source_sha256: 13a307a047a2
---

# Crontab expression conventions

## Contract

Write a five-field crontab expression to the standard form: the fields in the fixed order minute,
hour, day-of-month, month, day-of-week; `*` for anything unconstrained; the `*/n` step form for a
regular interval; only one of the two day fields restricted unless a union is genuinely wanted
(cron ORs them); 0 and 7 both meaning Sunday; and a leading comment naming the schedule in words.
Apply when producing or reviewing a crontab line; not for systemd timers, for parsing a cron
string, or for non-cron scheduling.

## Rules

1. **Five fields, fixed order.** Exactly five space-separated fields, in this order:

   ```
   ┌ minute (0–59)
   │ ┌ hour (0–23)
   │ │ ┌ day-of-month (1–31)
   │ │ │ ┌ month (1–12)
   │ │ │ │ ┌ day-of-week (0–7)
   * * * * *
   ```

   The minute is first and the day-of-week is last — not the other way around.

2. **`*` means every.** A field you are not constraining is `*` — every minute, every day, every
   month. Do not enumerate all values (`1,2,3,…,12`) to mean "every".

3. **Operators.** `,` for a list (`0,30`), `-` for an inclusive range (`1-5`), `/` for a step.
   A step over the whole field is `*/n` (`*/15`); a step over a range is `start-end/n` (`9-17/2`).

4. **Regular intervals use `*/n`.** "Every 15 minutes" is `*/15`, NOT the list `0,15,30,45` and
   NOT a range like `0-59/15` or `0-45/15`. Reach for the step form whenever the interval is
   regular.

5. **The day-of-month / day-of-week trap.** If BOTH the day-of-month field and the day-of-week
   field are restricted (neither is `*`), cron runs the job when EITHER matches — a union, not an
   intersection. So constrain only ONE of them and leave the other `*`, unless you deliberately
   want the union. "First of the month at 2am" is `0 2 1 * *` (day-of-week `*`), never
   `0 2 1 * 1`.

6. **0 and 7 are both Sunday.** In the day-of-week field, `0` and `7` both mean Sunday; `1` is
   Monday … `6` is Saturday.

7. **Lead with a comment.** Put a comment line stating the schedule in plain words directly above
   the expression, so a reader does not have to decode five fields:

   ```
   # every day at 03:30
   30 3 * * *
   ```

## Worked examples

Interval — enumerated list vs. the step form:

```
BEFORE   0,20,40 * * * *          # "every 20 minutes"
AFTER    */20 * * * *
```

Field order — a fixed daily time:

```
# 2:30 PM every day
30 14 * * *                        # minute=30 first, hour=14 second
```

Unconstrained fields stay `*`:

```
BEFORE   0 12 1-31 1-12 0-6        # "noon every day", every field spelled out
AFTER    0 12 * * *
```

Single day condition — leave the other day field `*`:

```
# every Tuesday at 08:00
0 8 * * 2                          # day-of-month is *, only day-of-week is set
```

The day trap — a union you DID want, both fields set:

```
# 1st of the month and every Friday, at midnight
0 0 1 * 5                          # runs on the 1st OR any Friday (cron ORs the two)
```

Sunday, either spelling:

```
# every Sunday at midnight
0 0 * * 0                          # 0 and 7 both mean Sunday
```

## Edge cases & exceptions

- "Every N hours" fixes the minute so it fires once per mark: `0 */6 * * *`, not `*/6 * * * *`.
- Two specific times that are not a clean interval (6am and 6pm) are a list, not a step:
  `0 6,18 * * *`.
- A business-hours window uses a range in the hour field: `0 9-17 * * 1-5`.
- Wanting the 1st ONLY when it is a weekday is an INTERSECTION cron cannot express with both day
  fields — restrict day-of-month and guard the weekday in the command, or accept the union.
- Month and day-of-week accept names (`JAN`, `MON`), but numbers are the portable default.
- `@daily`, `@hourly` macros exist but the explicit five fields are the reviewable form.

## Do / Don't

- Do keep the five fields in minute-hour-dom-month-dow order. Don't put the hour before the minute.
- Do use `*` for an unconstrained field. Don't enumerate every value to mean "every".
- Do write a regular interval as `*/n`. Don't spell it as a list or an equivalent range.
- Do restrict only one day field unless a union is intended. Don't set both day-of-month and
  day-of-week by reflex.
- Do lead with a plain-words comment. Don't ship a bare five-field line with no explanation.

## Common mistakes

- Hour and minute swapped — `14 30 * * *` for 2:30 PM instead of `30 14 * * *`.
- "Every 15 minutes" written as `0,15,30,45 * * * *` or `0-45/15 * * * *` instead of `*/15`.
- Both day fields pinned (`0 9 1 * 1`) expecting "1st AND Monday" — cron runs it on the 1st OR any
  Monday.
- Every field spelled out (`0-59 0-23 1-31 1-12 0-6`) instead of `* * * * *`.
- Forgetting that `0` and `7` both mean Sunday.
- No leading comment, so the schedule is unreadable at a glance.

## Quick checklist

- Five fields, order minute · hour · day-of-month · month · day-of-week.
- `*` for unconstrained; `*/n` for a regular interval.
- Only one day field set unless the union is intended.
- 0 or 7 for Sunday; a leading plain-words comment.
