A modern cron expression iterator with full croniter API compatibility, supporting both 5-field (standard) and 6-field (with seconds) cron expressions.
pychronotab was created to solve namespace conflicts when using multiple task schedulers (like django-celery-beat with python-crontab and rq-scheduler with croniter) in the same project. Both python-crontab and older cron libraries expose a top-level crontab module, causing import conflicts.
pychronotab provides:
- ✅ Zero namespace conflicts - all imports under
pychronotab - ✅ Full croniter API compatibility - drop-in replacement for abandoned croniter
- ✅ 6-field cron support - includes seconds field for sub-minute scheduling
- ✅ Modern timezone handling - uses
zoneinfo(Python 3.9+) - ✅ DST-aware - handles daylight saving time transitions correctly
- ✅ Active maintenance - not abandoned
pip install pychronotabfrom datetime import datetime, timezone
from pychronotab import CronExpression
# 5-field cron (standard)
expr = CronExpression("*/5 * * * *", tz=timezone.utc)
now = datetime.now(timezone.utc)
next_run = expr.next(now)
print(f"Next run: {next_run}")
# 6-field cron (with seconds)
expr_seconds = CronExpression("*/30 */5 * * * *", tz=timezone.utc)
next_run = expr_seconds.next(now)
print(f"Next run (with seconds): {next_run}")Drop-in replacement for croniter (just change the import):
from datetime import datetime
from pychronotab import croniter
# Standard 5-field cron
it = croniter("*/5 * * * *", datetime.now())
print(it.get_next(datetime))
print(it.get_next(datetime))
# 6-field cron with seconds
it_seconds = croniter("*/30 */5 * * * *", datetime.now())
print(it_seconds.get_next(datetime))* * * * *
│ │ │ │ │
│ │ │ │ └─── day of week (0-6, SUN-SAT)
│ │ │ └───── month (1-12, JAN-DEC)
│ │ └─────── day of month (1-31)
│ └───────── hour (0-23)
└─────────── minute (0-59)
* * * * * *
│ │ │ │ │ │
│ │ │ │ │ └─── day of week (0-6, SUN-SAT)
│ │ │ │ └───── month (1-12, JAN-DEC)
│ │ │ └─────── day of month (1-31)
│ │ └───────── hour (0-23)
│ └─────────── minute (0-59)
└───────────── second (0-59)
*- any value5- specific value1-5- range of values*/5- step values (every 5)1,3,5- list of values1-5/2- range with stepJAN-DEC,SUN-SAT- month/day names
class CronExpression:
def __init__(self, expr: str, tz: timezone | None = None)
def next(self, base: datetime | None = None, *, inclusive: bool = False) -> datetime
def prev(self, base: datetime | None = None, *, inclusive: bool = False) -> datetime
def iter(self, start: datetime, *, direction: str = "forward", inclusive: bool = False) -> Iterator[datetime]class croniter:
def __init__(self, expr_format: str, start_time: datetime | None = None, day_or: bool = True)
def get_next(self, ret_type: Type = datetime) -> datetime | float
def get_prev(self, ret_type: Type = datetime) -> datetime | float
def get_current(self, ret_type: Type = datetime) -> datetime | float
def all_next(self, ret_type: Type = datetime) -> Iterator[datetime | float]
def all_prev(self, ret_type: Type = datetime) -> Iterator[datetime | float]Simply change your import:
# Old
from croniter import croniter
# New
from pychronotab import croniterEverything else stays the same!
Unlike python-crontab (which exposes crontab module) and old croniter dependencies, pychronotab keeps everything under its own namespace:
# ✅ Safe - no conflicts
from pychronotab import CronExpression, croniter
# ❌ Never exposed - won't conflict with python-crontab
# from crontab import ... # This won't exist in pychronotabThis means you can safely use:
django-celery-beat(usespython-crontab)rq-scheduler(usespychronotabinstead of abandonedcroniter)- Any other
crontab-based library
…all in the same project without import conflicts!
MIT License - see LICENSE file for details.
Contributions welcome! Please open an issue or PR on GitHub.