Skip to main content

Croner - Cron for Python

Trigger functions or evaluate cron expressions in Python. No dependencies. Most features. Python 3.9+.

This is the Python flavor of croner (JavaScript/TypeScript) and croner-rust. All three parse the same patterns and schedule the same runs, DST included.

  • Schedule functions, or just evaluate patterns: next runs, previous runs, matching.
  • Works in any time zone, or a fixed UTC offset, with well defined DST behavior.
  • Supports the Open Cron Pattern Standard (OCPS) 1.0 - 1.4, with optional seconds and years, L, W, #, + and ?.
  • Over-run protection, error handling, pausing, max runs, intervals, start and stop dates, day offsets.
  • Human readable descriptions of patterns.
  • No dependencies, only the Python standard library. Fully typed.

Quick examples:

from croner import Cron

# Basic: run a function at the fifth second of every minute
job = Cron("5 * * * * *", lambda: print("This will run every fifth second"))

# Enumeration: what dates do the next 100 sundays occur on?
next_sundays = Cron("0 0 0 * * 7").next_runs(100)

# Days left to a specific date
ms_left = Cron("59 59 23 24 DEC *").ms_to_next()
print(f"{ms_left // 86_400_000} days left to next christmas eve")

# Run a function once, at a specific date and time
job = Cron("2027-01-23T00:00:00", lambda: print("Yay!"), timezone="Asia/Kolkata")

# As a decorator, with over-run protection
@Cron("*/5 * * * * *", protect=True)
def every_five_seconds(job):
    print("Every five seconds, never overlapping. Next run:", job.next_run())

# Describe a pattern
print(Cron("0 12 * * MON-FRI").describe())  # At 12:00, on Monday, Tuesday, Wednesday, Thursday, and Friday.

Installation

pip install croner

On Windows, which has no system time zone database, also install tzdata to use the timezone option: pip install croner[tzdata].

Documentation

Signature

Cron takes a pattern, an optional function, and keyword options:

# - pattern: Cron pattern, datetime (run once), or ISO 8601 string (run once)
# - fn: Optional function to schedule
# - options: Keyword options (optional)
job = Cron("* * * * * *", max_runs=1)

# If the function is omitted, it can be scheduled later
job.schedule(lambda job, context: None)

The function is called with the job and options.context, fn(job, context). A function that takes fewer arguments gets fewer: fn(job) or fn(). Coroutine functions (async def) are run to completion.

Each run happens in a thread of its own, so a slow run does not delay the schedule (use protect to prevent overlapping runs). The scheduler keeps the process alive while a job is scheduled, like a JavaScript timer, unless you pass unref=True.

Status

job.next_run(start=None)              # The next run (datetime), or None.
job.next_runs(10, start=None)         # The next 10 runs.
job.previous_runs(10, reference=None) # The 10 previous scheduled runs, most recent first.
job.enumerate(start=None)             # A stateful iterator, for loops: for run in job.enumerate(): ...
job.ms_to_next(start=None)            # Milliseconds to the next run, or None.
job.current_run()                     # When the current (or last) run started.
job.previous_run()                    # When the previous run started.
job.runs_left()                       # Runs left, None for unlimited.

job.match(date)                       # Whether a datetime or ISO 8601 string matches the pattern.

job.is_running()                      # Scheduled, and not paused or stopped.
job.is_stopped()                      # Stopped with stop().
job.is_busy()                         # The function is running right now.

job.get_pattern()                     # The original pattern, or None for one-off jobs.
job.get_once()                        # The date of a one-off job, or None.
job.describe()                        # Human readable description of the pattern.

Control functions

job.trigger()   # Run the function now, in the calling thread.
job.pause()     # Pause.
job.resume()    # Resume.
job.stop()      # Stop permanently. Also removes a named job from scheduled_jobs.

Dates and time zones

Every method that takes a date accepts a datetime, a date or an ISO 8601 string.

  • Aware datetimes, and strings with Z or an offset, are exact instants.
  • Naive datetimes, dates, and strings without an offset are wall clock times in the job's time zone (the timezone or utc_offset option), or in system local time if neither is set.

Returned datetimes are aware, in the job's time zone (zoneinfo.ZoneInfo), so they print in local time and compare correctly as instants.

Note Python subtracts two aware datetimes that share a tzinfo as wall clock times, ignoring DST. Convert to UTC first, a.astimezone(timezone.utc) - b.astimezone(timezone.utc), to get the real time between two runs across a DST change.

Options

Key Default Type Remarks
name None str Keep a reference to the job in croner.scheduled_jobs. The reference is removed on stop().
max_runs None int Maximum number of runs, None for unlimited.
catch False bool | callable Catch errors raised by the function. True silently ignores them, a callable is called as catch(error, job).
timezone None str | tzinfo Time zone in Europe/Stockholm format, or a tzinfo.
utc_offset None int Schedule with a fixed UTC offset in minutes. This does not take DST into account, you probably want timezone.
start_at None datetime | str Earliest run. Inclusive, a run exactly at start_at is included (except when interval is set).
stop_at None datetime | str Stop before this date. Exclusive, a run exactly at stop_at is not included.
interval 0 int Minimum number of seconds between runs.
paused False bool Start paused.
context None Any Passed as the second argument to the function.
dom_and_dow False bool Combine day-of-month and day-of-week with AND (True) or OR (False).
legacy_mode - bool Deprecated: use dom_and_dow. Inverse of dom_and_dow.
unref False bool Run the scheduler in daemon threads, so the process can exit while the job is scheduled.
protect False bool | callable Over-run protection: skip runs while a previous run is still going. A callable is called as protect(job) for each skipped run.
alternative_weekdays False bool Quartz-style weekday numbering (1 = Sunday, 2 = Monday, ..., 7 = Saturday).
allow_past False bool Let a one-off job dated in the past run immediately. Otherwise only one dated within the last second does.
day_offset 0 int Shift every run by a number of days, e.g. -1 for the day before each match. All methods and start_at / stop_at refer to the shifted time.
mode "auto" str How many fields the pattern has: "auto", "5-part", "6-part", "7-part", "5-or-6-parts" or "6-or-7-parts".
sloppy_ranges False bool Accept non-standard steps, like /10, 5/5 or 30/30. By default only */step and min-max/step are allowed.

Invalid patterns and options raise croner.CronError, a subclass of ValueError.

Pattern

Croner uses Vixie cron based expressions, with these extensions:

┌──────────────── (optional) second (0 - 59)
│ ┌────────────── minute (0 - 59)
│ │ ┌──────────── hour (0 - 23)
│ │ │ ┌────────── day of month (1 - 31)
│ │ │ │ ┌──────── month (1 - 12, JAN-DEC)
│ │ │ │ │ ┌────── day of week (0 - 6, SUN-SAT)
│ │ │ │ │ │       (0 to 6 are Sunday to Saturday; 7 is Sunday, the same as 0)
│ │ │ │ │ │ ┌──── (optional) year (1 - 9999)
│ │ │ │ │ │ │
* * * * * * *
  • Optional second and year fields: 6 fields add seconds first, 7 fields also add a year last.
  • L: Last day of month, or last occurrence of a weekday. L in day-of-month is the last day of the month, LW the last weekday (Mon-Fri) of the month, and 5#L, 5L or FRI#L the last Friday of the month.
  • W: Nearest weekday. 15W is the weekday closest to the 15th, never crossing into another month.
  • #: Nth occurrence of a weekday. 5#2 is the second Friday, MON#1 the first Monday of the month.
  • +: Prefix the day-of-week field to combine it with day-of-month using AND: 0 12 1 * +MON only runs when the 1st is a Monday.
  • ?: An alias for *. Non-portable, avoid it in patterns meant for other systems.
  • DST, the same as croner (JS), croner-rust and Vixie cron, deviating from OCPS 1.4 §4.3.1:
    • Fixed-time jobs, with a single value in each of second, minute and hour (e.g. 0 30 2 * * *): a time that occurs twice (fall back) runs once, at the first occurrence. A time that doesn't exist (spring forward) runs at the first valid time after the gap, on the same day.
    • Interval jobs, with a wildcard, step, range or list in second, minute or hour (e.g. */5 * * * * or 0 30 1,2 * * *): a time that occurs twice runs on both occurrences. A time that doesn't exist is skipped.
    • next_run(), next_runs(), previous_runs() and enumerate() move in real time. They never return a time they have passed, and cover the first pass through a repeated hour before the second.

By default, day-of-month and day-of-week are combined with OR: 0 20 1 * MON runs on the 1st of the month and on Mondays. Use the + modifier or dom_and_dow=True for AND.

Field Required Allowed values Special characters Remarks
Seconds Optional 0-59 * , - / ? Defaults to 0
Minutes Yes 0-59 * , - / ?
Hours Yes 0-23 * , - / ?
Day of Month Yes 1-31 * , - / ? L W L = last day, W = nearest weekday
Month Yes 1-12 or JAN-DEC * , - / ?
Day of Week Yes 0-7 or SUN-SAT * , - / ? L # + 0 and 7 = Sunday, 1-7 = Sunday-Saturday with alternative_weekdays
Year Optional 1-9999 * , - / Defaults to *

Weekday and month names are case-insensitive.

Nicknames:

Nickname Same as
@yearly / @annually 0 0 1 1 *
@monthly 0 0 1 * *
@weekly 0 0 * * 0
@daily / @midnight 0 0 * * *
@hourly 0 * * * *
@reboot Recognized, but raises CronError, as croner can't detect system startup.

Pattern objects

croner.CronPattern parses a pattern on its own. job.pattern is the parsed pattern of a job.

from croner import CronPattern

pattern = CronPattern("0 9-11 * * 1-5")
pattern.values("hour")         # [9, 10, 11]
pattern.values("day_of_week")  # [1, 2, 3, 4, 5]
pattern.is_fixed_time          # False

Development

pip install -e . pytest
python -m pytest

tools/compare_with_js.py compares random patterns with croner (JavaScript), run from source with Node.js, see the script for details.

License

MIT License

Metadata

Release files for croner 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for croner 1.0.0
File Size Uploaded Provenance Actions
croner-1.0.0.tar.gz 29.2 kB With provenance Details for croner-1.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for croner 1.0.0
File Interpreter ABI Platform Actions
croner-1.0.0-py3-none-any.whl Python 3 none any Details for croner-1.0.0-py3-none-any.whl

Total release size: 55.2 kB

Release files / croner-1.0.0.tar.gz

Download URL croner-1.0.0.tar.gz
Size 29.2 kB
Tags Source
SHA-256 checksum
How to use checksums
b3421b26b568a25d8de1989642cd9b692343f4e48d0b0930245e6dca2cdb068a
BLAKE2b-256 checksum
How to use checksums
5be9734371adb3232b2ab624350431a1741661e364b67d6d4c22b6d3cd15e0a0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 7, 2026.

Transparency log

Release files / croner-1.0.0-py3-none-any.whl

Download URL croner-1.0.0-py3-none-any.whl
Size 26.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f9c0049ba4933f4a10046a3ec9758d8f314fb600e4b0ac45b79ae5fd3c2b2d4f
BLAKE2b-256 checksum
How to use checksums
7dd77ac1cf882819179814f46026c33aeaa537f07e5929919fec645f0ccc9447
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 7, 2026.

Transparency log

Security for croner 1.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for croner-1.0.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 7, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page