API reference

Explain a cron expression

Validate a cron expression, describe it in English, and list its next run times in a zone.

GET POST /api/cron/explain MCP tool cron_explain

Accepts the 5-field crontab form (minute hour day-of-month month day-of-week), a 6-field form whose first field is seconds, and the macros @yearly, @annually, @monthly, @weekly, @daily, @midnight, @hourly. Returns an English description, a field-by-field breakdown with the expanded values, the day-matching rule in effect, and the next run times after from in zone. Day-of-month and day-of-week follow Vixie cron: when both are restricted a day matches if EITHER matches (0 0 13 * 5 runs on every 13th AND every Friday); a field starting with * (such as */2) counts as unrestricted, and then a day must match both. Errors name the offending field.

Browser version (runs locally): Cron Explainer.

Try it

Credentials
Remember these credentials

Not saved: you will paste them again next time.

No credentials yet? Get a client ID and secret. They are sent only to this site, in an Authorization: Basic header.

POST /api/cron/explain with a JSON body

Parameters

Parameter Type Default Description
expression required string (≤ 200 chars) — Cron expression, e.g. */15 9-17 * * 1-5, 0 0 1 * *, 30 0 9 * * MON (6 fields: seconds first), or @daily. Month and weekday names (JAN, MON) are case-insensitive; 0 and 7 are both Sunday.
zone string (≤ 64 chars) UTC Zone in which the schedule is evaluated (the cron daemon's zone, or CRON_TZ / Kubernetes timeZone): IANA name, UTC, or fixed offset.
from string (≤ 100 chars) — List runs strictly after this instant (default: now). ISO 8601, RFC 2822, HTTP-date, Unix timestamp, or now; a time without offset is read in zone.
count integer (1–50) 5 Number of upcoming run times to return (1-50).

Also accepted on every endpoint: format (json | text). Parameters may come from the query string, a JSON object body, an urlencoded or multipart form, or (for input) a text/plain body.

Global parameters
Parameter Type Default Description
format json | text json json returns the documented object. text returns only the primary value as text/plain (see "Text output").

Response 200 application/json

Field Type Description
expression string The expression with whitespace collapsed.
normalized string 5- or 6-field form actually evaluated (macros expanded).
macro string | null The macro used, lower-cased (e.g. @daily), or null.
dialect "five-field" | "six-field-seconds" five-field (standard crontab) or six-field-seconds.
description string English description from cronstrue, 24-hour clock. For the day fields, day_rule is authoritative.
day_rule object
day_rule.rule "every-day" | "day-of-month" | "day-of-week" | "either" | "both" How days are selected: every-day, day-of-month only, day-of-week only, either (OR: both restricted), or both (AND: one starts with * but still narrows).
day_rule.explanation string The rule in plain English for this expression.
fields array of object One entry per field, in expression order (seconds first only for 6-field expressions).
fields[].field "second" | "minute" | "hour" | "day-of-month" | "month" | "day-of-week" Field name.
fields[].raw string The field as written.
fields[].values array of integer Expanded matching values, ascending. Day-of-week uses 0-6 (Sunday = 0). L and # constraints are described in description, not listed.
fields[].unrestricted boolean True for * (and, in the day fields, anything starting with *, or ?), per Vixie cron.
fields[].description string What the field selects, in English.
timezone string Zone the schedule was evaluated in.
from string Start of the search (exclusive), ISO 8601 in timezone.
next array of object Upcoming run times, ascending. Empty when the schedule never fires.
next[].iso string Run time, ISO 8601 with the offset in timezone.
next[].utc string Run time in UTC.
next[].unix_seconds integer Run time as Unix seconds.
next[].local string Wall-clock time in timezone, without offset.
next[].weekday string English weekday in timezone.
next[].dst_shifted boolean True when the scheduled wall-clock time did not exist (DST gap) and the run is shown at the shifted time.
warnings array of string Portability and scheduling warnings: 6-field or Quartz-style syntax, DST shifts, schedules that never run, zone caveats.

Text output (format=text, text/plain): The upcoming run times, one ISO 8601 time per line.

Errors

Errors are JSON: {"error": "<message>", "code": "<code>", "param": "<name>"?}.

Status Code When
400 invalid_parameter zone is unknown or an abbreviation.
422 unprocessable_input The expression has the wrong number of fields, a value out of range (the message names the field), unsupported syntax (H, W, @reboot, unknown macros), or day fields no date can satisfy (0 0 30 2 *); or from cannot be parsed.
400 missing_parameter A required parameter is absent.
400 unknown_parameter A parameter name is not in the table above (typos are rejected, not ignored).
413 payload_too_large The request body exceeds 1 MiB.
405 method_not_allowed A method other than GET, POST or OPTIONS.

Behaviour

  • Not idempotent: the result can differ between calls (time, randomness, or a live network check).
  • Requires a client ID and secret (authentication). Each call is recorded as one metadata row against your client ID; inputs and outputs are never stored or logged. Responses carry cache-control: no-store.
  • CORS: any origin may call this endpoint from a browser, using your credentials.

Examples

Responses below were produced by running each request through this code at build time. Long strings are shortened.

Every 15 minutes in office hours, New York

curl 'https://util.caruana.me/api/cron/explain?expression=*%2F15%209-17%20*%20*%201-5&zone=America%2FNew_York&from=2026-10-13T16%3A50%3A00Z&count=3'

Response 200

{
  "expression": "*/15 9-17 * * 1-5",
  "normalized": "*/15 9-17 * * 1-5",
  "macro": null,
  "dialect": "five-field",
  "description": "Every 15 minutes, between 09:00 and 17:59, Monday through Friday",
  "day_rule": {
    "rule": "day-of-week",
    "explanation": "Only day-of-week (1-5) selects days; day-of-month is \"*\"."
  },
  "fields": [
    {
      "field": "minute",
      "raw": "*/15",
      "values": [
        0,
        15,
        30,
        45
      ],
      "unrestricted": false,
      "description": "every 15 minutes (0, 15, 30, 45)"
    },
    {
      "field": "hour",
      "raw": "9-17",
      "values": [
        9,
        10,
        11,
        12,
        13,
        14,
        15,
        16,
        17
      ],
      "unrestricted": false,
      "description": "hour 9 through 17"
    },
    {
      "field": "day-of-month",
      "raw": "*",
      "values": [
        1,
        2,
        3,
        4,
        5,
        6,
        7,
        8,
        9,
        10,
        11,
        12,
        13,
        14,
        15,
        16,
        17,
        18,
        19,
        20,
        21,
        22,
        23,
        24,
        25,
        26,
        27,
        28,
        29,
        30,
        31
      ],
      "unrestricted": true,
      "description": "any day of the month"
    },
    {
      "field": "month",
      "raw": "*",
      "values": [
        1,
        2,
        3,
        4,
        5,
        6,
        7,
        8,
        9,
        10,
        11,
        12
      ],
      "unrestricted": true,
      "description": "every month"
    },
    {
      "field": "day-of-week",
      "raw": "1-5",
      "values": [
        1,
        2,
        3,
        4,
        5
      ],
      "unrestricted": false,
      "description": "Monday through Friday"
    }
  ],
  "timezone": "America/New_York",
  "from": "2026-10-13T12:50:00-04:00",
  "next": [
    {
      "iso": "2026-10-13T13:00:00-04:00",
      "utc": "2026-10-13T17:00:00Z",
      "unix_seconds": 1791910800,
      "local": "2026-10-13T13:00:00",
      "weekday": "Tuesday",
      "dst_shifted": false
    },
    {
      "iso": "2026-10-13T13:15:00-04:00",
      "utc": "2026-10-13T17:15:00Z",
      "unix_seconds": 1791911700,
      "local": "2026-10-13T13:15:00",
      "weekday": "Tuesday",
      "dst_shifted": false
    },
    {
      "iso": "2026-10-13T13:30:00-04:00",
      "utc": "2026-10-13T17:30:00Z",
      "unix_seconds": 1791912600,
      "local": "2026-10-13T13:30:00",
      "weekday": "Tuesday",
      "dst_shifted": false
    }
  ],
  "warnings": []
}
Same call as an MCP tools/call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "cron_explain",
    "arguments": {
      "expression": "*/15 9-17 * * 1-5",
      "zone": "America/New_York",
      "from": "2026-10-13T16:50:00Z",
      "count": 3
    }
  }
}

Both day fields restricted: the 13th OR any Friday

curl 'https://util.caruana.me/api/cron/explain?expression=0%200%2013%20*%205&from=2026-11-01T00%3A00%3A00Z&count=4'

Response 200

{
  "expression": "0 0 13 * 5",
  "normalized": "0 0 13 * 5",
  "macro": null,
  "dialect": "five-field",
  "description": "At 00:00, on day 13 of the month, and on Friday",
  "day_rule": {
    "rule": "either",
    "explanation": "Both day-of-month (13) and day-of-week (5) are restricted, so a day matches if EITHER field matches (the Vixie cron OR rule), not only days that satisfy both."
  },
  "fields": [
    {
      "field": "minute",
      "raw": "0",
      "values": [
        0
      ],
      "unrestricted": false,
      "description": "minute 0"
    },
    {
      "field": "hour",
      "raw": "0",
      "values": [
        0
      ],
      "unrestricted": false,
      "description": "hour 0"
    },
    {
      "field": "day-of-month",
      "raw": "13",
      "values": [
        13
      ],
      "unrestricted": false,
      "description": "day 13"
    },
    {
      "field": "month",
      "raw": "*",
      "values": [
        1,
        2,
        3,
        4,
        5,
        6,
        7,
        8,
        9,
        10,
        11,
        12
      ],
      "unrestricted": true,
      "description": "every month"
    },
    {
      "field": "day-of-week",
      "raw": "5",
      "values": [
        5
      ],
      "unrestricted": false,
      "description": "Friday"
    }
  ],
  "timezone": "UTC",
  "from": "2026-11-01T00:00:00Z",
  "next": [
    {
      "iso": "2026-11-06T00:00:00Z",
      "utc": "2026-11-06T00:00:00Z",
      "unix_seconds": 1793923200,
      "local": "2026-11-06T00:00:00",
      "weekday": "Friday",
      "dst_shifted": false
    },
    {
      "iso": "2026-11-13T00:00:00Z",
      "utc": "2026-11-13T00:00:00Z",
      "unix_seconds": 1794528000,
      "local": "2026-11-13T00:00:00",
      "weekday": "Friday",
      "dst_shifted": false
    },
    {
      "iso": "2026-11-20T00:00:00Z",
      "utc": "2026-11-20T00:00:00Z",
      "unix_seconds": 1795132800,
      "local": "2026-11-20T00:00:00",
      "weekday": "Friday",
      "dst_shifted": false
    },
    {
      "iso": "2026-11-27T00:00:00Z",
      "utc": "2026-11-27T00:00:00Z",
      "unix_seconds": 1795737600,
      "local": "2026-11-27T00:00:00",
      "weekday": "Friday",
      "dst_shifted": false
    }
  ],
  "warnings": []
}
Same call as an MCP tools/call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "cron_explain",
    "arguments": {
      "expression": "0 0 13 * 5",
      "from": "2026-11-01T00:00:00Z",
      "count": 4
    }
  }
}

Daily 02:30 across the spring DST gap (New York)

curl 'https://util.caruana.me/api/cron/explain?expression=30%202%20*%20*%20*&zone=America%2FNew_York&from=2026-03-07T00%3A00%3A00-05%3A00&count=3'

Response 200

{
  "expression": "30 2 * * *",
  "normalized": "30 2 * * *",
  "macro": null,
  "dialect": "five-field",
  "description": "At 02:30",
  "day_rule": {
    "rule": "every-day",
    "explanation": "Both day fields are \"*\" or \"?\": every day matches."
  },
  "fields": [
    {
      "field": "minute",
      "raw": "30",
      "values": [
        30
      ],
      "unrestricted": false,
      "description": "minute 30"
    },
    {
      "field": "hour",
      "raw": "2",
      "values": [
        2
      ],
      "unrestricted": false,
      "description": "hour 2"
    },
    {
      "field": "day-of-month",
      "raw": "*",
      "values": [
        1,
        2,
        3,
        4,
        5,
        6,
        7,
        8,
        9,
        10,
        11,
        12,
        13,
        14,
        15,
        16,
        17,
        18,
        19,
        20,
        21,
        22,
        23,
        24,
        25,
        26,
        27,
        28,
        29,
        30,
        31
      ],
      "unrestricted": true,
      "description": "any day of the month"
    },
    {
      "field": "month",
      "raw": "*",
      "values": [
        1,
        2,
        3,
        4,
        5,
        6,
        7,
        8,
        9,
        10,
        11,
        12
      ],
      "unrestricted": true,
      "description": "every month"
    },
    {
      "field": "day-of-week",
      "raw": "*",
      "values": [
        0,
        1,
        2,
        3,
        4,
        5,
        6
      ],
      "unrestricted": true,
      "description": "any day of the week"
    }
  ],
  "timezone": "America/New_York",
  "from": "2026-03-07T00:00:00-05:00",
  "next": [
    {
      "iso": "2026-03-07T02:30:00-05:00",
      "utc": "2026-03-07T07:30:00Z",
      "unix_seconds": 1772868600,
      "local": "2026-03-07T02:30:00",
      "weekday": "Saturday",
      "dst_shifted": false
    },
    {
      "iso": "2026-03-08T03:30:00-04:00",
      "utc": "2026-03-08T07:30:00Z",
      "unix_seconds": 1772955000,
      "local": "2026-03-08T03:30:00",
      "weekday": "Sunday",
      "dst_shifted": true
    },
    {
      "iso": "2026-03-09T02:30:00-04:00",
      "utc": "2026-03-09T06:30:00Z",
      "unix_seconds": 1773037800,
      "local": "2026-03-09T02:30:00",
      "weekday": "Monday",
      "dst_shifted": false
    }
  ],
  "warnings": [
    "Some run times fall in a DST gap in America/New_York; they are shown at the shifted wall-clock time (dst_shifted: true)."
  ]
}
Same call as an MCP tools/call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "cron_explain",
    "arguments": {
      "expression": "30 2 * * *",
      "zone": "America/New_York",
      "from": "2026-03-07T00:00:00-05:00",
      "count": 3
    }
  }
}