croniter provides iteration for datetime object with cron like format
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Advanced croniter features including Jenkins-style hashed expressions, random expressions, custom start time expansion, specialized cron syntax, and timezone handling capabilities.
Jenkins-style hashed expressions using the "H" keyword provide consistent but distributed scheduling across different job IDs.
# Hashed expressions in croniter constructor
iter = croniter("H H * * *", hash_id="unique-job-id")Hashed expressions remain consistent for the same hash_id but distribute differently across different hash_id values, enabling even distribution of jobs without manual coordination.
Random "R" expressions provide randomized scheduling that remains consistent within a croniter instance but varies between instances.
# Random expressions in croniter constructor
iter = croniter("R R * * *")Random expressions generate different times for each croniter instance but remain consistent for subsequent calls within the same instance.
Control how interval expressions are calculated relative to start time versus calendar boundaries.
iter = croniter(
'0 0 */7 * *', # Every 7 days
start_time=datetime(2024, 7, 11),
expand_from_start_time=True # Calculate from start_time, not calendar
)When expand_from_start_time=True, intervals are calculated from the specified start time rather than calendar boundaries (e.g., every 7 days from July 11th rather than from the 1st of each month).
Support for second-level cron expressions using 6-field format.
# Seconds as 6th field (default)
iter = croniter('* * * * * 15,45', base_time) # At 15 and 45 seconds of each minute
# Seconds as 1st field (alternative)
iter = croniter('15,45 * * * * *', base_time, second_at_beginning=True)Support for year field using 7-field format for precise year-based scheduling.
# Year as 7th field (seconds=0 for simplicity)
iter = croniter('0 0 1 1 * 0 2020/2', base_time) # January 1st every 2 years from 2020Year field supports range 1970-2099 and standard cron syntax (ranges, steps, lists).
Advanced cron syntax including nth weekday of month and last weekday specifications.
# Nth weekday of month
iter = croniter('0 0 * * sat#1,sun#2', base_time) # 1st Saturday and 2nd Sunday
# Last weekday of month
iter = croniter('0 0 * * L5', base_time) # Last Friday of each month
# Combined nth and last
iter = croniter('0 0 * * 5#3,L5', base_time) # 3rd and last Fridayfrom croniter import croniter
from datetime import datetime
base = datetime(2021, 4, 10)
# Same hash_id produces consistent results
iter1 = croniter("H H * * *", base, hash_id="hello")
iter2 = croniter("H H * * *", base, hash_id="hello")
print(iter1.get_next(datetime)) # 2021-04-10 11:10:00
print(iter2.get_next(datetime)) # 2021-04-10 11:10:00 (same result)
# Different hash_id produces different results
iter3 = croniter("H H * * *", base, hash_id="bonjour")
print(iter3.get_next(datetime)) # 2021-04-10 20:52:00 (different result)
# Hashed ranges
iter4 = croniter("H(0-30) H(9-17) * * *", base, hash_id="business-job")
print(iter4.get_next(datetime)) # Random minute 0-30, random hour 9-17from croniter import croniter
from datetime import datetime
base = datetime(2021, 4, 10)
# Each instance gets different random values
iter1 = croniter("R R * * *", base)
iter2 = croniter("R R * * *", base)
print(iter1.get_next(datetime)) # 2021-04-10 22:56:00
print(iter2.get_next(datetime)) # 2021-04-10 07:31:00 (different)
# But consistent within same instance
print(iter1.get_next(datetime)) # 2021-04-11 22:56:00 (same time next day)
# Random ranges
iter3 = croniter("R(0-30) R(9-17) * * *", base)
print(iter3.get_next(datetime)) # Random minute 0-30, random hour 9-17from croniter import croniter
from datetime import datetime
start = datetime(2024, 7, 11) # July 11th
# Default behavior: every 7 days from calendar (1st, 8th, 15th, 22nd, 29th)
iter1 = croniter('0 0 */7 * *', start, expand_from_start_time=False)
matches1 = [iter1.get_next(datetime) for _ in range(5)]
print("Calendar-based:", [dt.day for dt in matches1])
# [15, 22, 29, 1, 8] (calendar boundaries)
# Custom expansion: every 7 days from start_time (11th, 18th, 25th, 1st, 8th)
iter2 = croniter('0 0 */7 * *', start, expand_from_start_time=True)
matches2 = [iter2.get_next(datetime) for _ in range(5)]
print("Start-time-based:", [dt.day for dt in matches2])
# [18, 25, 1, 8, 15] (from start time)from croniter import croniter
from datetime import datetime
base = datetime(2012, 4, 6, 13, 26, 10)
# Seconds as 6th field (default)
iter1 = croniter('* * * * * 15,25', base)
print(iter1.get_next(datetime)) # 2012-04-06 13:26:15
print(iter1.get_next(datetime)) # 2012-04-06 13:26:25
print(iter1.get_next(datetime)) # 2012-04-06 13:27:15
# Every second (be careful with performance!)
iter2 = croniter('* * * * * *', base)
print(iter2.get_next(datetime)) # 2012-04-06 13:26:11
# Seconds as 1st field
iter3 = croniter('15,25 * * * * *', base, second_at_beginning=True)
print(iter3.get_next(datetime)) # 2012-04-06 13:26:15
# Mixed with other precision
iter4 = croniter('30 */5 * * * *', base) # Every 5 minutes at 30 seconds
print(iter4.get_next(datetime)) # 2012-04-06 13:30:30from croniter import croniter
from datetime import datetime
base = datetime(2012, 4, 6, 2, 6, 59)
# New Year's Day every 2 years starting 2020
iter1 = croniter('0 0 1 1 * 0 2020/2', base)
print(iter1.get_next(datetime)) # 2020-01-01 00:00:00
print(iter1.get_next(datetime)) # 2022-01-01 00:00:00
print(iter1.get_next(datetime)) # 2024-01-01 00:00:00
# Specific years list
iter2 = croniter('0 0 1 1 * 0 2025,2030,2035', base)
print(iter2.get_next(datetime)) # 2025-01-01 00:00:00
# Year ranges
iter3 = croniter('0 0 1 1 * 0 2025-2030', base)
matches = [iter3.get_next(datetime) for _ in range(6)]
print([dt.year for dt in matches]) # [2025, 2026, 2027, 2028, 2029, 2030]from croniter import croniter
from datetime import datetime
base = datetime(2010, 1, 1)
# First Saturday and second Sunday of each month
iter1 = croniter('0 0 * * sat#1,sun#2', base)
print(iter1.get_next(datetime)) # 2010-01-02 00:00:00 (1st Saturday)
print(iter1.get_next(datetime)) # 2010-01-10 00:00:00 (2nd Sunday)
# Third and last Friday of each month
iter2 = croniter('0 0 * * 5#3,L5', base)
print(iter2.get_next(datetime)) # 2010-01-15 00:00:00 (3rd Friday)
print(iter2.get_next(datetime)) # 2010-01-29 00:00:00 (last Friday)
# Last day of each month (using L)
iter3 = croniter('0 0 L * *', base)
print(iter3.get_next(datetime)) # 2010-01-31 00:00:00
print(iter3.get_next(datetime)) # 2010-02-28 00:00:00 (handles Feb correctly)import pytz
from croniter import croniter
from datetime import datetime
# Create timezone-aware datetime
tz = pytz.timezone("Europe/Paris")
local_date = tz.localize(datetime(2017, 3, 26)) # Near DST transition
# Croniter preserves timezone information
iter1 = croniter('0 0 * * *', local_date)
next_match = iter1.get_next(datetime)
print(next_match.tzinfo) # <DstTzInfo 'Europe/Paris' CET+1:00:00 STD>
# Works with DST transitions
iter2 = croniter('0 2 * * *', local_date) # 2 AM daily
for i in range(5):
match = iter2.get_next(datetime)
print(f"{match} - DST: {match.dst()}")
# Using dateutil timezone
import dateutil.tz
tz2 = dateutil.tz.gettz('Asia/Tokyo')
tokyo_date = datetime(2017, 3, 26, tzinfo=tz2)
iter3 = croniter('0 0 * * *', tokyo_date)from croniter import croniter
from datetime import datetime
# Limit search window for sparse expressions
iter1 = croniter(
"0 4 1 1 fri", # 4 AM on January 1st if it's Friday (very sparse!)
datetime(2000, 1, 1),
day_or=False,
max_years_between_matches=15 # Limit to prevent infinite search
)
# With explicit limit, all_next() won't raise CroniterBadDateError
matches = []
for match in iter1.all_next(datetime):
matches.append(match)
if len(matches) >= 5: # Get first 5 matches
break
print(matches)
# [2010-01-01 04:00:00, 2016-01-01 04:00:00, 2021-01-01 04:00:00, ...]
# Without limit might raise CroniterBadDateError for very sparse expressions
try:
iter2 = croniter("0 4 1 1 fri", datetime(2000, 1, 1), day_or=False)
# This might fail if no matches found within default 50-year window
match = iter2.get_next(datetime)
except Exception as e:
print(f"Error: {e}")from croniter import croniter
from datetime import datetime
import pytz
# Complex example combining multiple advanced features
tz = pytz.timezone("US/Eastern")
start = tz.localize(datetime(2024, 1, 1))
# Hashed expression with seconds and custom expansion
iter1 = croniter(
'H H(9-17) * * 1-5 30', # Random time during business hours, 30 seconds
start,
hash_id="business-report",
expand_from_start_time=True,
second_at_beginning=False
)
# Random expression with year field
iter2 = croniter(
'R R 1 1 * 0 2025-2030', # Random time on New Year's Day 2025-2030
start
)
# Special syntax with timezone
iter3 = croniter(
'0 9 * * L5', # 9 AM on last Friday of each month
start
)
# Get next matches
print("Business report:", iter1.get_next(datetime))
print("New Year random:", iter2.get_next(datetime))
print("Monthly meeting:", iter3.get_next(datetime))Install with Tessl CLI
npx tessl i tessl/pypi-croniter