Croniter
Parses cron schedules to iterate over datetime objects.
Install / Use
/learn @pallets-eco/CroniterREADME
Introduction
.. contents::
croniter provides iteration for the datetime object with a cron like format.
::
_ _
___ _ __ ___ _ __ (_) |_ ___ _ __
/ __| '__/ _ \| '_ \| | __/ _ \ '__|
| (__| | | (_) | | | | | || __/ |
\___|_| \___/|_| |_|_|\__\___|_|
Website: https://github.com/pallets-eco/croniter
Build Badge
.. image:: https://github.com/pallets-eco/croniter/actions/workflows/cicd.yml/badge.svg :target: https://github.com/pallets-eco/croniter/actions/workflows/cicd.yml
Pallets Community Ecosystem
.. important::
This project is part of the Pallets Community Ecosystem. Pallets is the open
source organization that maintains Flask; Pallets-Eco enables community
maintenance of Flask extensions. If you are interested in helping maintain
this project, please reach out on the Pallets Discord server <https://discord.gg/pallets>_.
Usage
A simple example::
>>> from croniter import croniter
>>> from datetime import datetime
>>> base = datetime(2010, 1, 25, 4, 46)
>>> iter = croniter('*/5 * * * *', base) # every 5 minutes
>>> print(iter.get_next(datetime)) # 2010-01-25 04:50:00
>>> print(iter.get_next(datetime)) # 2010-01-25 04:55:00
>>> print(iter.get_next(datetime)) # 2010-01-25 05:00:00
>>>
>>> iter = croniter('2 4 * * mon,fri', base) # 04:02 on every Monday and Friday
>>> print(iter.get_next(datetime)) # 2010-01-26 04:02:00
>>> print(iter.get_next(datetime)) # 2010-01-30 04:02:00
>>> print(iter.get_next(datetime)) # 2010-02-02 04:02:00
>>>
>>> iter = croniter('2 4 1 * wed', base) # 04:02 on every Wednesday OR on 1st day of month
>>> print(iter.get_next(datetime)) # 2010-01-27 04:02:00
>>> print(iter.get_next(datetime)) # 2010-02-01 04:02:00
>>> print(iter.get_next(datetime)) # 2010-02-03 04:02:00
>>>
>>> iter = croniter('2 4 1 * wed', base, day_or=False) # 04:02 on every 1st day of the month if it is a Wednesday
>>> print(iter.get_next(datetime)) # 2010-09-01 04:02:00
>>> print(iter.get_next(datetime)) # 2010-12-01 04:02:00
>>> print(iter.get_next(datetime)) # 2011-06-01 04:02:00
>>>
>>> iter = croniter('0 0 * * sat#1,sun#2', base) # 1st Saturday, and 2nd Sunday of the month
>>> print(iter.get_next(datetime)) # 2010-02-06 00:00:00
>>>
>>> iter = croniter('0 0 * * 5#3,L5', base) # 3rd and last Friday of the month
>>> print(iter.get_next(datetime)) # 2010-01-29 00:00:00
>>> print(iter.get_next(datetime)) # 2010-02-19 00:00:00
All you need to know is how to use the constructor and the get_next
method, the signature of these methods are listed below::
>>> def __init__(self, cron_format, start_time=time.time(), day_or=True)
croniter iterates along with cron_format from start_time.
cron_format is min hour day month day_of_week, you can refer to
http://en.wikipedia.org/wiki/Cron for more details. The day_or
switch is used to control how croniter handles day and day_of_week
entries. Default option is the cron behaviour, which connects those
values using OR. If the switch is set to False, the values are connected
using AND. This behaves like fcron and enables you to e.g. define a job that
executes each 2nd Friday of a month by setting the days of month and the
weekday.
::
>>> def get_next(self, ret_type=float)
get_next calculates the next value according to the cron expression and
returns an object of type ret_type. ret_type should be a float or a
datetime object.
Supported added for get_prev method. (>= 0.2.0)::
>>> base = datetime(2010, 8, 25)
>>> itr = croniter('0 0 1 * *', base)
>>> print(itr.get_prev(datetime)) # 2010-08-01 00:00:00
>>> print(itr.get_prev(datetime)) # 2010-07-01 00:00:00
>>> print(itr.get_prev(datetime)) # 2010-06-01 00:00:00
You can validate your crons using is_valid class method. (>= 0.3.18)::
>>> croniter.is_valid('0 0 1 * *') # True
>>> croniter.is_valid('0 wrong_value 1 * *') # False
Strict validation
By default, is_valid and expand only check that each field is within its own range
(e.g. day 1-31, month 1-12). They do not cross-validate fields, so expressions like
0 0 31 2 * (February 31st) are considered valid even though they can never match a real date.
Use strict=True to enable cross-field validation that rejects impossible day/month
combinations::
>>> croniter.is_valid('0 0 31 2 *') # True (default: syntax only)
>>> croniter.is_valid('0 0 31 2 *', strict=True) # False (Feb has at most 29 days)
>>> croniter.is_valid('0 0 31 4 *', strict=True) # False (Apr has 30 days)
>>> croniter.is_valid('0 0 30 1,2 *', strict=True) # True (day 30 is valid in Jan)
When strict=True and the expression does not include a year field, February is assumed to
have 29 days (since leap years exist)::
>>> croniter.is_valid('0 0 29 2 *', strict=True) # True (leap years exist)
If the expression includes a year field (7-field format), leap year status is determined from the specified years::
>>> croniter.is_valid('0 0 29 2 * 0 2024', strict=True) # True (2024 is a leap year)
>>> croniter.is_valid('0 0 29 2 * 0 2023', strict=True) # False (2023 is not)
>>> croniter.is_valid('0 0 29 2 * 0 2023-2025', strict=True) # True (2024 in range is a leap year)
For 5- or 6-field expressions, you can pass strict_year to provide the year(s) for
leap year checking without adding a year field to the expression::
>>> croniter.is_valid('0 0 29 2 *', strict=True, strict_year=2024) # True (leap year)
>>> croniter.is_valid('0 0 29 2 *', strict=True, strict_year=2023) # False (not a leap year)
>>> croniter.is_valid('0 0 29 2 *', strict=True, strict_year=[2023, 2024]) # True (2024 is a leap year)
The strict and strict_year parameters are also available on expand()::
>>> croniter.expand('0 0 31 2 *', strict=True) # raises CroniterBadCronError
Last day of month (l)
You can use l in the day-of-month field to mean "the last day of the month". This
automatically respects the number of days in each month, including February and leap years::
>>> croniter('0 0 l * *', datetime(2024, 1, 30)).get_next(datetime)
datetime.datetime(2024, 1, 31, 0, 0)
>>> croniter('0 0 l * *', datetime(2024, 2, 1)).get_next(datetime) # leap year
datetime.datetime(2024, 2, 29, 0, 0)
>>> croniter('0 0 l * *', datetime(2023, 2, 1)).get_next(datetime) # non-leap year
datetime.datetime(2023, 2, 28, 0, 0)
Nearest weekday (W)
The W character is supported in the day-of-month field to specify the nearest weekday
(Monday-Friday) to the given day. Both nW and Wn formats are accepted::
>>> base = datetime(2024, 6, 1)
>>> itr = croniter('0 9 15W * *', base)
>>> itr.get_next(datetime) # Jun 15 2024 is Saturday -> fires on Friday 14th
datetime.datetime(2024, 6, 14, 9, 0)
Rules:
- If the specified day falls on a weekday, the trigger fires on that day.
- If the specified day falls on Saturday, the trigger fires on the preceding Friday.
- If the specified day falls on Sunday, the trigger fires on the following Monday.
- The nearest weekday never crosses month boundaries. If the 1st is a Saturday, the trigger fires on Monday the 3rd. If the last day of the month is a Sunday, the trigger fires on the preceding Friday.
Wcan only be used with a single day value, not in a range or list.
Examples::
>>> croniter('0 9 1W * *', datetime(2024, 5, 31)).get_next(datetime) # Jun 1 is Sat -> Mon 3rd
datetime.datetime(2024, 6, 3, 9, 0)
>>> croniter('0 9 W15 * *', datetime(2024, 1, 1)).get_next(datetime) # Wn format also works
datetime.datetime(2024, 1, 15, 9, 0)
Day-of-month and day-of-week
When both the day-of-month and day-of-week fields are restricted (not *),
the default POSIX cron behavior is to match when either field matches (OR).
This is controlled by the day_or parameter::
>>> # OR (default): fires on every Wednesday OR on the 1st of the month
>>> iter = croniter('2 4 1 * wed', datetime(2010, 1, 25))
>>> print(iter.get_next(datetime)) # 2010-01-27 04:02:00 (Wed)
>>> print(iter.get_next(datetime)) # 2010-02-01 04:02:00 (1st)
>>> # AND: fires only on the 1st of the month IF it is a Wednesday
>>> iter = croniter('2 4 1 * wed', datetime(2010, 1, 25), day_or=False)
>>> print(iter.get_next(datetime)) # 2010-09-01 04:02:00 (Wed AND 1st)
This can be used to express patterns like "the first Tuesday of the month" by combining a day range with a weekday::
>>> # First Tuesday of each month: days 1-7 AND Tuesday
>>> iter = croniter('1 1 1-7 * 2', datetime(2024, 7, 12), day_or=False)
>>> print(iter.get_next(datetime)) # 2024-08-06 01:01:00
>>> print(iter.get_next(datetime)) # 2024-09-03 01:01:00
>>> print(iter.get_next(datetime)) # 2024-10-01 01:01:00
>>> print(iter.get_next(datetime)) # 2024-11-05 01:01:00
Vixie cron bug compatibility
Some vixie/ISC cron implementations have a known bug <https://crontab.guru/cron-bug.html>_ where expressions that start with
* in the day-of-month or day-of-week field (e.g. */32,1-7) use AND
logic instead of OR, even though those fields are technically restricted.
If you need to replicate this behavior (e.g. */32,1-7 as a hack for days
1-7), use implement_cron_bug=True::
>>> iter = croniter('1 1 */32,1-7 * 2', datetime(2024, 7, 12), implement_cron_bug=True)
>>> print(iter.get_next(datetime)) # 2024-08-06 01:01:00
>>> print(iter.get_next(datetime)) # 2024-09-03 01:01:00
About DST
Be sure to init your croniter instance wit
Related Skills
node-connect
335.8kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
claude-opus-4-5-migration
82.7kMigrate prompts and code from Claude Sonnet 4.0, Sonnet 4.5, or Opus 4.1 to Opus 4.5
frontend-design
82.7kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
model-usage
335.8kUse CodexBar CLI local cost usage to summarize per-model usage for Codex or Claude, including the current (most recent) model or a full model breakdown. Trigger when asked for model-level usage/cost data from codexbar, or when you need a scriptable per-model summary from codexbar cost JSON.
