The fields, in order
A Unix cron expression has five fields: minute, hour, day of month, month, day of week. Quartz (and Spring's scheduler) adds a seconds field at the front and an optional year at the end, so the same intention is written with six fields. Reading left to right is the whole of the basic syntax; the difficulty is in the values each field accepts and in how the two day fields interact.
| Field | Unix range | Quartz range | Notes |
|---|---|---|---|
| Second | — | 0–59 | Quartz only; Unix fires at second 0 |
| Minute | 0–59 | 0–59 | *, lists, ranges, steps |
| Hour | 0–23 | 0–23 | 24-hour clock |
| Day of month | 1–31 | 1–31 | L = last day (Quartz) |
| Month | 1–12 | 1–12 | Names JAN–DEC accepted |
| Day of week | 0–7 (0 or 7 = Sunday) | 1–7 (1 = Sunday) | The numbering is the classic porting bug |
| Year | — | 1970–2099 (optional) | Quartz only |
Stars, lists, ranges and steps
* means every value. A comma is a list (1,15), a hyphen is an inclusive range
(1-5), and a slash is a step (*/15). A step may follow a range
(9-17/2), and in Quartz, and in most modern Unix implementations, it may follow a single
value (0/15) meaning “start there and go to the end of the field by that step”. That last
form is worth checking in the scheduler you actually use: a parser that treats 0/15 as
just 0 turns an every-fifteen-minutes job into an hourly one, and nothing reports an
error.
The step is not a frequency cap. */15 in the minute field means minutes 0, 15, 30 and 45
of every hour; it does not mean “no more than four times an hour” in a way that interacts with other
fields. Cron matches a wall-clock time against the fields; it does not schedule intervals.
The day-of-month OR day-of-week rule
In Unix cron, if both day-of-month and day-of-week are restricted (neither is
*), a run happens when either matches. 0 0 1 * MON fires on the
first of the month and on every Monday — an OR that catches people who expect AND. If one of
the fields is *, the other governs alone.
Quartz removes the ambiguity by requiring a ? in one of the two day fields to mean “no
constraint here”. 0 0 12 ? * MON is every Monday at noon; 0 0 12 1 * ? is the
first of every month. A ? anywhere else is invalid. If you copy a Quartz expression into a
Unix crontab, the ? is a field value Unix does not understand — and if you copy a Unix
expression into Quartz, the OR rule is gone, so the meaning can change silently.
Measured next-run times
The table below is the output of the scheduler engine this site's generator uses, evaluated from 2026-10-24 00:00 UTC. The dates are UTC; in another zone the same expressions produce the same wall-clock times, shifted.
| Expression | Dialect | Next runs (UTC) |
|---|---|---|
*/5 * * * * | Unix | 00:05, 00:10, 00:15 on 2026-10-24 |
0 9 * * 1-5 | Unix | 09:00 on Mon 2026-10-26, Tue 27, Wed 28 |
0 0 1 * * | Unix | 00:00 on 2026-11-01, 2026-12-01, 2027-01-01 |
30 2 * * * | Unix | 02:30 daily |
0 0/15 * * * ? | Quartz | 00:15, 00:30, 00:45 on 2026-10-24 |
0 0 12 ? * MON | Quartz | 12:00 on Mon 2026-10-26, 11-02, 11-09 |
Note the fourth row: 2026-10-24 is a Saturday, so 0 9 * * 1-5 does not run that day and
the first run is Monday. The last row shows the Quartz weekday numbering — MON is
unambiguous, which is exactly why names are worth using.
Daylight saving is where schedules break
A cron expression names a wall-clock time, and wall clocks move twice a year. In a zone that
springs forward at 02:00, a job scheduled for 30 2 * * * has no 02:30 that day: the local
time does not exist. Different implementations handle that differently — some skip the run, some run
it at 03:00, some run it at 01:30 — and the same job then runs twice in the autumn, when 01:30 occurs
twice. A scheduler that constructs a local time and re-checks the fields (as the engine here does)
skips the nonexistent time rather than silently firing at the wrong hour.
The practical consequence is that a “daily at 02:30” job cannot be assumed to run 365 times a year. If the job must run exactly once per day regardless of the clock, schedule it in UTC — but then its local time shifts by an hour for half the year. There is no schedule that is both fixed in local time and guaranteed once per local day across a DST change; decide which property matters.
Expressions that look right and are not
0 0 31 2 *— 31 February. The expression is valid and never fires. Cron does not warn about impossible calendar combinations.*/15 9-17 * * 1-5— every 15 minutes from 09:00 to 17:45 on weekdays. The range is inclusive; 17:45 is inside it.0 0 * * 7— Sunday in Unix, where 0 and 7 both mean Sunday. In Quartz, 7 means Saturday; the same number, a different day.0 0 1 * MON— first of the month OR every Monday in Unix, not both. Use Quartz with?if you need one or the other explicitly.@reboot— not an expression. It runs when the daemon starts, which may be several times a day or not for months; it has no schedule to compute.