Time Utilities

Cron Expression

Parse cron expressions to show next run times, or generate cron from options.

✨AI Generate (Cloudflare Workers AI)
min: 0 min · hour: 9 hour · day: 31 values · month: 12 values · weekday: 7 values
Next 5 runs:
  • Nov 3, 2026, 09:00:00
  • Nov 4, 2026, 09:00:00
  • Nov 5, 2026, 09:00:00
  • Nov 6, 2026, 09:00:00
  • Nov 7, 2026, 09:00:00

What is Cron Expression?

Cron expressions define schedules for recurring tasks. This tool helps create, parse, and validate cron expressions.

Cron Format

  • Standard: minute hour day month weekday
  • Extended: second minute hour day month weekday

Use Cases

  • Scheduled jobs configuration
  • CI/CD pipeline scheduling
  • Backup automation

A cron expression is five whitespace-separated fields — minute, hour, day-of-month, month, day-of-week — that describe a recurring schedule. This tool expands each field into the concrete integers it matches and lists the next runs in your browser's local time, for checking a crontab line, a CI schedule or a container job spec before deploying it.

Fields become integer sets, not patterns

Each field is parsed into an explicit list of integers rather than kept as a pattern: * becomes 0–59 for minutes, 0–23 for hours, 1–31 for days, 1–12 for months and 0–6 for weekdays, and the summary line reports those list lengths instead of the original text. Commas concatenate values, a dash fills an inclusive range, and */15 walks from the field's lower bound. Matching is then five set lookups. Because parsing is positional and has a fallback, any field that yields no valid number collapses silently to the field minimum: a reversed range such as 5-1 in the minute slot becomes minute 0, and a name such as MON in the weekday slot becomes Sunday 0.

Day-of-month OR day-of-week, and the star trap

Classic Vixie cron fires when either day field matches, not when both do, and the implementation here tests exactly that with day || weekday. The catch is that * expands to every legal day, so the OR is satisfied on almost any date: 0 9 * * 1-5 and 30 8 1 * * both list runs on consecutive days, because the unrestricted field keeps the condition true. To make either day field actually bite, constrain both — 0 0 15 * 3 then means the 15th of the month or any Wednesday. Sunday is 0, a 7 in the weekday field is parsed but never matches 0–6, and three-letter names are not recognised at all.

The 365-day scan and silent dead ends

The search begins at the current minute with seconds and milliseconds zeroed, then advances one minute at a time until it collects the requested count or exhausts 525,600 iterations, which is exactly 365 days at 1,440 minutes each. That explains why every listed timestamp ends in :00, and why a schedule whose values fall outside a field's domain — hour 30, month 13, day 0 — returns an empty list instead of an error: no value is rejected, nothing ever matches. Only a string with fewer than five whitespace-separated fields is reported as invalid. A slash after a range is dropped as well, because the range branch is tested first.

Worked example

Expanding a stepped minute field

Input
*/20 9-10 * * 1-5
Output
09:00:00, 09:20:00, 09:40:00, 10:00:00, 10:20:00, 10:40:00 — the same six times on every calendar day

The minute field expands to 0, 20 and 40, the hour field to 9 and 10, so three times two gives six runs per day, each printed with zero seconds. The 1-5 in the weekday slot narrows nothing, because day-of-month is * and the two day fields are joined by OR. Write a day-of-month value instead if either day field should be effective.

Limits and pitfalls

  • The parser reads the first five fields positionally, so a six-field Quartz string is misread: 0 30 9 * * 1-5 is taken as minute 0, hour 30, day 9, and returns an empty list rather than an error.
  • Every run is computed with the browser’s own clock getters (getHours, getDay), so the same expression shows different times for visitors in different zones; there is no UTC switch.
  • When either day field is *, the other day field has no effect — 0 9 * * 1-5 previews daily runs, not weekday runs.
  • The next-run list offers only 3, 5 or 10 entries, and it is capped at 525,600 scanned minutes (365 days); a schedule that never matches inside that window shows an empty list.
  • A step of 0, as in */0, is not guarded: the expander increments by zero and the loop never reaches its upper bound.

Related tools

When it goes wrong

The preview lists Saturday and Sunday even though the crontab on the server only fires Monday to Friday.

CauseMatching is a hard-coded OR of the two day fields, and the day-of-month * expands to all 31 values, so the condition is satisfied on any date. Vixie cron and POSIX do something else: when one of the two day fields is a star it is skipped and the other one alone decides, which is why 0 9 * * 1-5 really is weekday-only in production.

FixTreat the production behaviour as the truth for weekday schedules and read the preview day column as "unconstrained". To watch a date field that genuinely bites, put a number there (0 9 15 * 3 previews the 15th or any Wednesday); to confirm a weekday schedule, run the string through croniter under the server timezone instead of reading this list.

At 09:00:30 the expression 0 9 * * * still shows today 09:00:00 as the next run.

CauseThe scan starts from the current instant with seconds and milliseconds zeroed, and tests that first candidate before advancing, so the minute already in progress is reported as if it were still ahead.

FixRead the first entry as "the minute we are in" rather than "next": if its timestamp is earlier than the wall clock, start from the second entry. Everything after that is exact.

On the morning the clocks go forward the 02:30 job vanishes from the list; on the day they go back 01:30 appears twice.

CauseThe loop advances with whole milliseconds added to the timestamp, so wall-clock labels are a side effect of the zone rules. Spring-forward jumps the local clock from 02:00 to 03:00 and those labels never exist that day; fall-back replays them, and both passes match the same minute set.

FixKeep daily jobs out of the transition window — write 03:30 rather than 02:30 — or run the scheduler on UTC and convert the intended local time once, when you write the expression.

The AI box returns something, and what it pastes into the expression field is either rejected or shows times that are clearly wrong.

CauseThe generated text only has its quote characters removed and is trimmed; nothing checks arity. A model that adds a comment, a UTC suffix, a sixth field or a stray word lands in the input verbatim, and the single validation in the parser is whether five whitespace-separated fields exist — a sixth field is silently folded into the first five positions.

FixAfter generating, count the fields yourself and keep exactly five: drop a leading seconds token, delete trailing comments and zone suffixes. Then re-read the per-field summary line, which reports how many values each field expanded to — a count you did not expect is the fastest sign the string was mistranslated.

Weekday and month names mapped to the numbers this parser reads

NameNumber accepted hereQuartz number
SUN01
MON12
TUE23
WED34
THU45
FRI56
SAT67
JAN11
FEB22
MAR33
APR44
MAY55
JUN66
JUL77
AUG88
SEP99
OCT1010
NOV1111
DEC1212

Each whitespace-separated token goes through parseInt, so a three-letter name yields no number at all and the whole field falls back to its lower bound — digits are the only safe input. Quartz counts weekdays from 1, so its 2-6 (Monday to Friday) has to be written 1-5 here; month numbering is the same in both syntaxes.

Frequently Asked Questions

What do the five fields mean?
Minute, hour, day-of-month, month, day-of-week. */10 in the minute field means "every 10 minutes". This tool also accepts a 6-field form with seconds, common in Quartz and Spring.
Why does my job run at a different time than expected?
Cron is evaluated in the server’s local timezone. 09:00 in UTC+8 fires at 01:00 UTC. Confirm the timezone of the machine or scheduler, then read the expression in that zone.
What is the difference between * and ? in the day fields?
In flavors that support both (Quartz), ? means "unconstrained" and lets you restrict only one of day-of-month / day-of-week. In standard five-field cron, restrict one field and leave the other as *.

Content reviewed: