Just File Tools

Cron Expression Builder

Translate cron expressions to plain English and preview next fire times

minute hour day-of-month month day-of-week
In plain English
At 09:00 AM, Monday through Friday
Common presets
Next 5 fire times (local — UTC)
  1. #1Tue May 19 2026 09:00:00
  2. #2Wed May 20 2026 09:00:00
  3. #3Thu May 21 2026 09:00:00
  4. #4Fri May 22 2026 09:00:00
  5. #5Mon May 25 2026 09:00:00

How to Cron Expression Builder Online

Translate cron expressions to plain English and preview their next fire times.

  1. Type or paste a 5-field cron expression. The boxes below explain each field.
  2. Watch the plain-English description update live. If the expression is invalid, the error message includes the field that failed to parse.
  3. Click a preset to jump to a common schedule — every 5 minutes, weekly, monthly, etc.
  4. Read the 'Next 5 fire times' panel to see when the schedule will trigger next, in your local time zone.
  5. Click Copy to put the cron expression on your clipboard for pasting into a scheduler config (crontab, GitHub Actions, Kubernetes CronJob, etc.).

About Cron Expression Builder

Cron is the oldest scheduling DSL still in production use. Vixie cron — the implementation almost every modern cron daemon descends from — was first shipped in 1987. The 5-field syntax (minute, hour, day-of-month, month, day-of-week) is identical in standard Unix cron, in GitHub Actions workflows, in Kubernetes CronJob, in Vercel Cron Jobs, in AWS EventBridge schedule expressions, in Cloudflare Workers cron triggers, and in essentially every job scheduler that took the easy path of "the syntax developers already know." Knowing how to read and write cron is non-optional infrastructure literacy.

The hard part is not the basic syntax — `0 0 * * *` for midnight is easy to memorize — but the corner cases. **Step expressions** (`*/5` for every five minutes) work in most places but have wraparound rules that surprise people. **Combined ranges and steps** (`0-30/5` for every five minutes during the first half of each hour) are powerful but rarely understood. **Day-of-month and day-of-week interaction** is the worst corner — when both are restricted, cron uses OR semantics (`1st of the month OR Sunday`), which is the opposite of what most people guess. **Time zones** are not part of the cron spec at all; the daemon's host zone decides, and getting that wrong is how schedules quietly fire at the wrong hour.

This tool gives you a live translator. The plain-English description comes from **`cronstrue`** — the most-deployed cron-description library, used internally by Hangfire, Quartz, and many cron UIs. It handles the awkward cases gracefully: `0 */2 * * *` becomes "Every 2 hours", `30 9 1-7 * MON` becomes "At 09:30 AM, between day 1 and 7 of the month, and only on Monday", and an invalid expression produces an error pointing at the field that failed. The description updates as you type, so you can verify your intent immediately rather than waiting for a job to fire (or not fire) in production.

Below the description, the **Next 5 fire times** panel uses a small custom evaluator (not cronstrue, which only describes) to step forward from the current moment and find the next five times the expression matches. This is what catches the most common cron mistakes:

- Wrote `0 0 * * 7` thinking 7 was a missing day? Next fire is correct (Sunday) because 7 normalizes to 0, but the panel makes it obvious. - Forgot that `30 9 *` doesn't mean "9:30 once a day" — the day-of-month default `*` means "every day" so it actually fires daily at 9:30 AM. - Wrote `0 9 1 * 1` thinking "9 AM on the 1st AND Mondays" — actually means "9 AM on the 1st OR Mondays" per cron's day-field OR rule, so the panel will show non-consecutive fires.

The time zone displayed above the fire-time list is your browser's local zone, detected from `Intl`. Be careful: **cron daemons in production usually run in UTC or the host's zone, not your zone**. GitHub Actions, AWS EventBridge, and most modern cloud schedulers use UTC by default. Older Linux systems use the system's configured zone. Kubernetes CronJob defaults to the cluster zone. Always verify your target scheduler's zone before deploying a schedule. The fire-time panel here is for *intuition* — match it against your scheduler's zone when you copy the expression.

The **presets** below the description cover the eleven schedules most teams actually use: every minute (testing only — please do not deploy this), every 5 minutes, every 15 minutes, hourly, daily at midnight, daily at 9 AM, weekly on Sunday midnight, weekdays at 9 AM, monthly on the 1st, quarterly, and yearly. Click any preset to jump there; this is faster than typing the expression and reduces the risk of off-by-one mistakes (writing `*/30 * * * *` instead of `*/30 * * * 0-6` and accidentally including or excluding weekends).

A note on **what this does not support**. The `@`-prefix shortcuts — `@daily`, `@hourly`, `@reboot` — are commonly accepted by Unix cron but are not part of any spec and are not understood by GitHub Actions or Kubernetes CronJob. The Quartz extensions (`L` for last day of month, `W` for nearest weekday, `#` for nth occurrence of a weekday) are Quartz-specific. The 6-field (seconds) and 7-field (year) variants used by some Java-based schedulers are also not supported. This tool sticks to the canonical 5-field syntax that works in the largest number of schedulers.

Everything runs in your browser. `cronstrue` is ~30 KB compressed; the next-fire-time evaluator is a few hundred lines of pure TypeScript with vitest coverage including the OR-semantics edge case. No network requests during description or fire-time computation — your scheduled jobs' expressions stay in your tab.

Frequently Asked Questions

What are the five fields of a cron expression?

In order: minute (0–59), hour (0–23), day of month (1–31), month (1–12 or JAN–DEC), day of week (0–6 or SUN–SAT). A `*` means 'every value'. So `0 9 * * 1-5` means 'minute 0, hour 9, any day of month, any month, Monday through Friday' — i.e. weekdays at 9 AM. Most cron daemons also accept a 6th 'seconds' field at the start, but standard Unix cron and most modern schedulers (GitHub Actions, Kubernetes CronJob, AWS EventBridge) use 5 fields.

Why does `*/15` give me `0, 15, 30, 45` instead of `0, 15, 30, 45, 60`?

Because minute 60 does not exist — minutes range 0–59. The step expression `*/15` means 'start at 0 and go in steps of 15 within the valid range'. After 45 the next step would be 60, which is out of range, so the next value is the start of the next hour. This matches Vixie cron and every modern implementation.

What is the 'OR semantics' when both day-of-month and day-of-week are specified?

When both day fields are restricted (neither is `*`), cron treats the match as OR. So `0 0 1 * SUN` means 'midnight on the 1st of any month OR midnight on any Sunday' — fires on both. This is the historical Vixie cron behavior and what most schedulers do. When only one day field is restricted, that one must match. When both are `*`, every day matches.

Why is day-of-week 0 and 7 both Sunday?

Historical accident. The original cron used 0–6 with 0=Sunday. Some later implementations added 7=Sunday for users who think of the week as Mon–Sun. Most modern cron daemons accept both, and this tool normalizes 7 to 0. To avoid the ambiguity, use the abbreviations: `MON`, `TUE`, `WED`, `THU`, `FRI`, `SAT`, `SUN`.

What time zone are the next-fire times in?

Your local time zone as detected by `Intl.DateTimeFormat().resolvedOptions().timeZone`. The zone is shown above the list. Cron daemons themselves run in a specific time zone — Unix cron uses the system zone, GitHub Actions runs in UTC, Kubernetes CronJob defaults to the cluster's zone. Verify your target scheduler's zone before relying on a schedule.

Does this support the extended syntax (`@daily`, `@hourly`, `L`, `W`, `#`)?

Not yet. This tool parses the standard 5-field POSIX-ish syntax: numbers, names, lists (`1,2,3`), ranges (`1-5`), steps (`*/5` and `1-10/2`), and the `*` wildcard. The `@`-prefix shortcuts (`@daily`, `@hourly`, `@reboot`) and Quartz extensions (`L` for last day, `W` for weekday, `#` for nth weekday) are not supported. For those, use the underlying scheduler's documentation.

How is 'every minute' described?

As 'Every minute' — both the `cronstrue` library output and human convention agree. Other tricky cases the description handles well: `0 */2 * * *` is 'Every 2 hours', `30 9 1-7 * MON` is 'At 09:30 AM, between day 1 and 7 of the month, and only on Monday', `*/5 0 * * *` is 'Every 5 minutes, between 00:00 AM and 12:59 AM'.