cronlint
Validate cron expressions from the CLI or as a Python library.
cronlint checks whether a cron expression is well-formed before you commit it
to a crontab or embed it in a deployment. It runs anywhere Python runs — no
system cron daemon required, so it slots cleanly into CI pipelines.
Why this exists
Cron's grammar is simple but unforgiving: a stray 60 in the minute field or a
typo like @dailly will silently never fire. The official way to validate a
cron expression is to install it on a real cron daemon and wait — useless in
CI. Existing validators are mostly JavaScript (cron-validator) or tied to a
specific daemon. cronlint is the Python option that fits a static check into
your build.
Install
pip install git+https://github.com/prasadabhishek/cronlint.git
From a clone:
pip install -e ".[test]"
CLI usage
cronlint "*/5 * * * *" # exit 0, valid
cronlint "60 * * * *" # exit 1, writes error to stderr
cronlint --no-nicknames "@daily" # exit 1, nicknames disabled
cronlint --file expression.txt # read expression from file
Exit codes: 0 if valid, 1 if invalid, 2 on usage error (missing
arguments or unreadable --file).
Python API
from cronlint import validate_cron
ok, err = validate_cron("*/5 * * * *")
assert ok and err is None
ok, err = validate_cron("60 * * * *")
assert not ok and "minute" in err.lower()
The function takes an optional allow_nicknames flag (default True):
ok, _ = validate_cron("@daily", allow_nicknames=False)
assert not ok
Return shape
validate_cron always returns a tuple[bool, str | None]:
(True, None)— the expression is well-formed(False, "<reason>")— the expression is malformed; the string names the offending field so the caller can fix the input without guessing
Exposed constants
| Name | Purpose |
|---|---|
FIELD_BOUNDS |
{field: (lo, hi)} — per-field numeric bounds |
MONTH_NAMES |
JAN..DEC -> 1..12 (uppercase keys) |
DAY_NAMES |
SUN..SAT -> 0..6 (uppercase keys) |
NICKNAMES |
nickname -> canonical 5-field expansion |
parse_field |
expand a single field token to its matching values |
Supported syntax
Five space-separated fields:
[minute] [hour] [day of month] [month] [day of week]
| Field | Range | Names |
|---|---|---|
| minute | 0-59 | |
| hour | 0-23 | |
| day of month | 1-31 | |
| month | 1-12 | JAN-DEC (case-insensitive) |
| day of week | 0-6 | SUN-SAT (case-insensitive); 7 is also accepted as Sunday |
Special characters per field:
| Char | Meaning | Example |
|---|---|---|
* |
any value in range | * |
, |
list | 1,3,5 |
- |
range (inclusive) | 1-5 |
/ |
step | */5 |
| combination | mix of range + step | 1-10/2 |
Nicknames (when not disabled):
| Nickname | Expands to |
|---|---|
@yearly |
0 0 1 1 * |
@annually |
0 0 1 1 * |
@monthly |
0 0 1 * * |
@weekly |
0 0 * * 0 |
@daily |
0 0 * * * |
@midnight |
0 0 * * * |
@hourly |
0 * * * * |
@reboot |
always valid |
Examples
$ cronlint "*/15 * * * *"
$ echo $?
0
$ cronlint "60 * * * *"
cronlint: minute value 60 out of bounds [0, 59]
$ echo $?
1
$ cronlint "@DAILY"
$ echo $?
0
$ cronlint --no-nicknames "@daily"
cronlint: nicknames are disabled (--no-nicknames)
$ echo $?
1
$ cronlint --file /tmp/job.cron
$ echo $?
0
Limitations
cronlintis a static validator. It checks structure and per-field ranges; it does not compute the next firing time, and it does not catch calendar impossibilities like "Feb 30" (no mainstream cron daemon does either).@rebootis accepted as valid but cannot be statically proven to fire on any particular schedule — that requires runtime context.- Leap-second and timezone-aware validation are out of scope.
- Only single-line expressions are supported; multi-line crontabs are
iterated line-by-line by the caller (or via
--file, which trims to the first non-empty line).
Non-goals
- Parsing crontab files with environment-variable assignments, comments, or user definitions — only the expression itself is validated.
- Computing firing schedules (
cronitercovers that). - Daemon-side validation that requires
cron(8)orcrond(8). - Becoming an LLM wrapper around another library. This is pure stdlib.
Development
git clone https://github.com/prasad-a-abhishek/cronlint
cd cronlint
python3 -m venv .venv
.venv/bin/pip install -e ".[test]"
.venv/bin/pytest # 134 tests
.venv/bin/cronlint "*/5 * * * *"
License
MIT. See LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cronlint_cli-0.1.0.tar.gz.
File metadata
- Download URL: cronlint_cli-0.1.0.tar.gz
- Upload date:
- Size: 14.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
38d2a851d773a99ef55e01e8b92284bfefbdb5de3a3a629bfb21a03a00a8b861
|
|
| MD5 |
d5fd0cfcc091ae2e66d304e7992db784
|
|
| BLAKE2b-256 |
d22ef0407f513bd8b81b8634b3e65d357fa921b88fbb1143f1cd2061f9f364ef
|
File details
Details for the file cronlint_cli-0.1.0-py3-none-any.whl.
File metadata
- Download URL: cronlint_cli-0.1.0-py3-none-any.whl
- Upload date:
- Size: 9.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d26a05665024edb4a2e41af372914aace38cf97aba79844bb44b71cda43c3bfe
|
|
| MD5 |
bfe35b52ef0c4207e0e8de263e525b49
|
|
| BLAKE2b-256 |
63424f7d291a0800d24bc66128352152646d2b4a70d7723351db7b37fb89dfcc
|