Skip to main content

Cron Builder

Python 3.13+ Coverage Tests

A fluent, type-safe Python library for building and validating cron expressions with an intuitive API.

Features

✨ Fluent API - Chain methods for readable cron expressions
🔒 Type-Safe - Full type hints and IDE autocomplete support
✅ Validated - Automatic range checking and validation
🎯 Expressive - Use enums for months and weekdays
⚡ Immutable Mode - Optional immutability for functional programming
🔍 Smart Matching - Built-in should_run() for schedule validation
📝 Well-Tested - 100% test coverage with 75 tests

Installation

pip install cron-builder

Or with uv:

uv add cron-builder

Quick Start

from cron_builder import CronBuilder, Weekday, Month

# Every day at 9:30 AM
cron = CronBuilder().at(9, 30)
print(cron)  # 30 9 * * *

# Every 15 minutes
cron = CronBuilder().every_minutes(15)
print(cron)  # */15 * * * *

# Weekdays at 2:30 PM
cron = CronBuilder().at(14, 30).on_weekdays()
print(cron)  # 30 14 * * 1-5

# First day of every month at midnight
cron = CronBuilder().monthly()
print(cron)  # 0 0 1 * *

Usage Guide

Basic Time Settings

from cron_builder import CronBuilder

# Set specific minute (0-59)
CronBuilder().at_minute(30)  # 30 * * * *

# Set multiple minutes
CronBuilder().at_minutes(0, 15, 30, 45)  # 0,15,30,45 * * * *

# Every N minutes
CronBuilder().every_minutes(10)  # */10 * * * *

# Minute range
CronBuilder().minute_range(0, 30)  # 0-30 * * * *

# Set hour (0-23)
CronBuilder().at_hour(14)  # * 14 * * *

# Multiple hours
CronBuilder().at_hours(9, 12, 15, 18)  # * 9,12,15,18 * * *

# Every N hours
CronBuilder().every_hours(6)  # * */6 * * *

# Combine hour and minute
CronBuilder().at(9, 30)  # 30 9 * * *

Day of Month

# Specific day
CronBuilder().on_dom(15)  # * * 15 * *

# Multiple days
CronBuilder().on_doms(1, 15, 30)  # * * 1,15,30 * *

# Day range
CronBuilder().dom_range(1, 7)  # * * 1-7 * *

# Aliases available
CronBuilder().on_day_of_month(15)
CronBuilder().on_days_of_month(1, 15)
CronBuilder().day_of_month_range(10, 20)

Day of Week

from cron_builder import Weekday

# Specific day
CronBuilder().on_dow(Weekday.MONDAY)  # * * * * 1

# Multiple days
CronBuilder().on_dows(Weekday.MON, Weekday.WED, Weekday.FRI)  # * * * * 1,3,5

# Weekdays (Monday-Friday)
CronBuilder().on_weekdays()  # * * * * 1-5

# Weekends (Saturday-Sunday)
CronBuilder().on_weekends()  # * * * * 0,6

# Day range
CronBuilder().dow_range(Weekday.MON, Weekday.FRI)  # * * * * 1-5

# Aliases available
CronBuilder().on_day(Weekday.TUESDAY)
CronBuilder().on_days(Weekday.MON, Weekday.FRI)

Months

from cron_builder import Month

# Specific month
CronBuilder().in_month(Month.JUNE)  # * * * 6 *
CronBuilder().in_month(6)  # Same as above

# Multiple months
CronBuilder().in_months(Month.JAN, Month.JUL, Month.DEC)  # * * * 1,7,12 *

# Month range
CronBuilder().month_range(Month.MARCH, Month.MAY)  # * * * 3-5 *

Convenience Presets

from cron_builder import CronBuilder, Weekday, Month

# Hourly (at minute 0 by default)
CronBuilder().hourly()  # 0 * * * *
CronBuilder().hourly(30)  # 30 * * * *

# Daily (at midnight by default)
CronBuilder().daily()  # 0 0 * * *
CronBuilder().daily(14, 30)  # 30 14 * * *

# Weekly (Monday at midnight by default)
CronBuilder().weekly()  # 0 0 * * 1
CronBuilder().weekly(Weekday.FRIDAY, 18, 0)  # 0 18 * * 5

# Monthly (1st at midnight by default)
CronBuilder().monthly()  # 0 0 1 * *
CronBuilder().monthly(15, 9, 30)  # 30 9 15 * *

# Yearly (Jan 1st at midnight by default)
CronBuilder().yearly()  # 0 0 1 1 *
CronBuilder().yearly(Month.JULY, 4, 12, 0)  # 0 12 4 7 *

Advanced: Conjunctions

Handle complex schedules that require both day-of-month AND day-of-week conditions:

from cron_builder import CronBuilder, Weekday
from datetime import datetime

# First Monday of every month
cron = CronBuilder().at(9, 0).dom_range(1, 7).and_dow(Weekday.MONDAY)
print(cron)  # 0 9 1-7 * *

# Check if it should run
if cron.should_run(datetime(2024, 1, 1)):  # Jan 1, 2024 is a Monday
    print("Task runs!")

# Or use callable syntax
if cron():  # Checks current time
    run_task()

⚠️ Note: Standard cron uses OR logic for day fields, but this library provides should_run() to validate AND conditions.

Immutable Mode

For functional programming or when you want to avoid mutations:

# Mutable mode (default) - modifies in place
builder = CronBuilder()
builder.at_hour(9)
builder.at_hour(14)  # Overwrites previous value (shows warning)
print(builder)  # * 14 * * *

# Immutable mode - returns new instances
builder = CronBuilder(immutable=True)
morning = builder.at_hour(9)
afternoon = builder.at_hour(14)

print(builder)    # * * * * * (unchanged)
print(morning)    # * 9 * * *
print(afternoon)  # * 14 * * *

Real-World Examples

from cron_builder import CronBuilder, Weekday, Month

# Daily backup at 2 AM
backup_cron = CronBuilder().daily(2, 0)
# 0 2 * * *

# Business hours notifications (9 AM - 5 PM, every 30 minutes, weekdays)
notifications = CronBuilder().at_minutes(0, 30).hour_range(9, 17).on_weekdays()
# 0,30 9-17 * * 1-5

# Quarterly reports (first day of Jan, Apr, Jul, Oct at 8 AM)
reports = CronBuilder().at(8, 0).on_dom(1).in_months(Month.JAN, Month.APR, Month.JUL, Month.OCT)
# 0 8 1 1,4,7,10 *

# Weekly deployment window (Sunday 2 AM)
deployment = CronBuilder().weekly(Weekday.SUNDAY, 2, 0)
# 0 2 * * 0

# Holiday reminder (Christmas morning)
holiday = CronBuilder().yearly(Month.DECEMBER, 25, 8, 0)
# 0 8 25 12 *

API Reference

Enums

Weekday

class Weekday(IntEnum):
    SUNDAY = 0
    MONDAY = 1
    TUESDAY = 2
    WEDNESDAY = 3
    THURSDAY = 4
    FRIDAY = 5
    SATURDAY = 6
    # Aliases: SUN, MON, TUE, WED, THU, FRI, SAT

Month

class Month(IntEnum):
    JANUARY = 1
    FEBRUARY = 2
    # ... through DECEMBER = 12
    # Aliases: JAN, FEB, MAR, APR, JUN, JUL, AUG, SEP, OCT, NOV, DEC

CronBuilder Methods

Method Description Example
at(hour, minute) Set specific time at(9, 30) → 30 9 * * *
at_minute(min) Set minute at_minute(30) → 30 * * * *
at_minutes(*mins) Set multiple minutes at_minutes(0, 30) → 0,30 * * * *
every_minutes(n) Every N minutes every_minutes(15) → */15 * * * *
minute_range(start, end) Minute range minute_range(0, 30) → 0-30 * * * *
at_hour(hour) Set hour at_hour(14) → * 14 * * *
at_hours(*hours) Set multiple hours at_hours(9, 17) → * 9,17 * * *
every_hours(n) Every N hours every_hours(6) → * */6 * * *
hour_range(start, end) Hour range hour_range(9, 17) → * 9-17 * * *
on_dom(day) Day of month on_dom(15) → * * 15 * *
on_doms(*days) Multiple days of month on_doms(1, 15) → * * 1,15 * *
dom_range(start, end) Day of month range dom_range(1, 7) → * * 1-7 * *
on_dow(day) Day of week on_dow(Weekday.MON) → * * * * 1
on_dows(*days) Multiple days of week on_dows(1, 3, 5) → * * * * 1,3,5
on_weekdays() Monday-Friday on_weekdays() → * * * * 1-5
on_weekends() Saturday-Sunday on_weekends() → * * * * 0,6
dow_range(start, end) Day of week range dow_range(1, 5) → * * * * 1-5
in_month(month) Specific month in_month(Month.JUN) → * * * 6 *
in_months(*months) Multiple months in_months(1, 6, 12) → * * * 1,6,12 *
month_range(start, end) Month range month_range(3, 5) → * * * 3-5 *
hourly(minute=0) Every hour hourly(30) → 30 * * * *
daily(hour=0, minute=0) Every day daily(14, 30) → 30 14 * * *
weekly(day=MON, h=0, m=0) Every week weekly(Weekday.FRI) → 0 0 * * 5
monthly(day=1, h=0, m=0) Every month monthly(15) → 0 0 15 * *
yearly(month=JAN, day=1, h=0, m=0) Every year yearly(Month.JUL, 4) → 0 0 4 7 *
and_dow(day) Add DOW conjunction on_dom(1).and_dow(Weekday.MON)
and_dom(day) Add DOM conjunction on_dow(Weekday.MON).and_dom(1)
should_run(dt=None) Check if should run Returns bool

Validation

All methods automatically validate input ranges:

# These will raise ValueError with helpful messages
CronBuilder().at_minute(60)    # minute must be between 0 and 59
CronBuilder().at_hour(24)      # hour must be between 0 and 23
CronBuilder().on_dom(32)       # day_of_month must be between 1 and 31
CronBuilder().in_month(13)     # month must be between 1 and 12
CronBuilder().on_dow(7)        # day_of_week must be between 0 and 6
CronBuilder().every_minutes(0) # Interval must be positive

Development

Setup

# Clone the repository
git clone https://github.com/yourusername/cron-builder.git
cd cron-builder

# Install dependencies with uv
uv sync

Running Tests

# Run all tests
pytest cron-builder/cron_builder.py -v

# Run with coverage
pytest cron-builder/cron_builder.py --cov=cron-builder --cov-report=html

# Open coverage report
open htmlcov/index.html

Test Coverage

This project maintains 100% test coverage with 75 comprehensive tests covering:

  • ✅ All cron expression types (value, list, range, step, any)
  • ✅ Validation and error handling
  • ✅ Convenience methods and presets
  • ✅ Immutable and mutable modes
  • ✅ Conjunction logic
  • ✅ Method aliases
  • ✅ Edge cases and fallbacks

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT License - see LICENSE file for details

Credits

Built with ❤️ for developers who want type-safe, validated cron expressions in Python.

Metadata

Release files for cron-builder 0.1.1

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

Source distribution (sdist)

Source distribution for cron-builder 0.1.1
File Size Uploaded Provenance
cron_builder-0.1.1.tar.gz 28.8 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for cron-builder 0.1.1
File Interpreter ABI Platform
cron_builder-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 37.3 kB

Release files / cron_builder-0.1.1.tar.gz

Download URL cron_builder-0.1.1.tar.gz
Size 28.8 kB
Tags Source
SHA-256 checksum
How to use checksums
66fdabcdc628afafbfcacabd6d0e94dcfd2356ab976e78f8cf4dcfdd628f7ed7
BLAKE2b-256 checksum
How to use checksums
1fa423d5673a0b745ae82540a8fec4942e4c3e93dd816ccab2215e000d46c1b1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.2

Release files / cron_builder-0.1.1-py3-none-any.whl

Download URL cron_builder-0.1.1-py3-none-any.whl
Size 8.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0328107abccb01140f6635bcc52440c6cbf557d4e725a66361540449eb905163
BLAKE2b-256 checksum
How to use checksums
ae4b51a66006bae575ab7bb1c3520ab7edbf49dae428058f5e331cbd2f5d9007
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.2

Security for cron-builder 0.1.1

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

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.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files No provenance

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