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.
Metadata
Release files for cronlint-cli 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| cronlint_cli-0.1.0.tar.gz | 14.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| cronlint_cli-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 23.4 kB
Release files / cronlint_cli-0.1.0.tar.gz
| Download URL | cronlint_cli-0.1.0.tar.gz |
|---|---|
| Size | 14.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
38d2a851d773a99ef55e01e8b92284bfefbdb5de3a3a629bfb21a03a00a8b861
|
|
BLAKE2b-256 checksum How to use checksums |
d22ef0407f513bd8b81b8634b3e65d357fa921b88fbb1143f1cd2061f9f364ef
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.15
|
Release files / cronlint_cli-0.1.0-py3-none-any.whl
| Download URL | cronlint_cli-0.1.0-py3-none-any.whl |
|---|---|
| Size | 9.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d26a05665024edb4a2e41af372914aace38cf97aba79844bb44b71cda43c3bfe
|
|
BLAKE2b-256 checksum How to use checksums |
63424f7d291a0800d24bc66128352152646d2b4a70d7723351db7b37fb89dfcc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.15
|