0
# Advanced Features
1
2
Advanced croniter features including Jenkins-style hashed expressions, random expressions, custom start time expansion, specialized cron syntax, and timezone handling capabilities.
3
4
## Capabilities
5
6
### Hashed Expressions (Jenkins-Style)
7
8
Jenkins-style hashed expressions using the "H" keyword provide consistent but distributed scheduling across different job IDs.
9
10
```python { .api }
11
# Hashed expressions in croniter constructor
12
iter = croniter("H H * * *", hash_id="unique-job-id")
13
```
14
15
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.
16
17
### Random Expressions
18
19
Random "R" expressions provide randomized scheduling that remains consistent within a croniter instance but varies between instances.
20
21
```python { .api }
22
# Random expressions in croniter constructor
23
iter = croniter("R R * * *")
24
```
25
26
Random expressions generate different times for each croniter instance but remain consistent for subsequent calls within the same instance.
27
28
### Custom Start Time Expansion
29
30
Control how interval expressions are calculated relative to start time versus calendar boundaries.
31
32
```python { .api }
33
iter = croniter(
34
'0 0 */7 * *', # Every 7 days
35
start_time=datetime(2024, 7, 11),
36
expand_from_start_time=True # Calculate from start_time, not calendar
37
)
38
```
39
40
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).
41
42
### Second-Level Precision
43
44
Support for second-level cron expressions using 6-field format.
45
46
```python { .api }
47
# Seconds as 6th field (default)
48
iter = croniter('* * * * * 15,45', base_time) # At 15 and 45 seconds of each minute
49
50
# Seconds as 1st field (alternative)
51
iter = croniter('15,45 * * * * *', base_time, second_at_beginning=True)
52
```
53
54
### Year Field Support
55
56
Support for year field using 7-field format for precise year-based scheduling.
57
58
```python { .api }
59
# Year as 7th field (seconds=0 for simplicity)
60
iter = croniter('0 0 1 1 * 0 2020/2', base_time) # January 1st every 2 years from 2020
61
```
62
63
Year field supports range 1970-2099 and standard cron syntax (ranges, steps, lists).
64
65
### Special Cron Syntax
66
67
Advanced cron syntax including nth weekday of month and last weekday specifications.
68
69
```python { .api }
70
# Nth weekday of month
71
iter = croniter('0 0 * * sat#1,sun#2', base_time) # 1st Saturday and 2nd Sunday
72
73
# Last weekday of month
74
iter = croniter('0 0 * * L5', base_time) # Last Friday of each month
75
76
# Combined nth and last
77
iter = croniter('0 0 * * 5#3,L5', base_time) # 3rd and last Friday
78
```
79
80
## Usage Examples
81
82
### Hashed Expressions
83
84
```python
85
from croniter import croniter
86
from datetime import datetime
87
88
base = datetime(2021, 4, 10)
89
90
# Same hash_id produces consistent results
91
iter1 = croniter("H H * * *", base, hash_id="hello")
92
iter2 = croniter("H H * * *", base, hash_id="hello")
93
94
print(iter1.get_next(datetime)) # 2021-04-10 11:10:00
95
print(iter2.get_next(datetime)) # 2021-04-10 11:10:00 (same result)
96
97
# Different hash_id produces different results
98
iter3 = croniter("H H * * *", base, hash_id="bonjour")
99
print(iter3.get_next(datetime)) # 2021-04-10 20:52:00 (different result)
100
101
# Hashed ranges
102
iter4 = croniter("H(0-30) H(9-17) * * *", base, hash_id="business-job")
103
print(iter4.get_next(datetime)) # Random minute 0-30, random hour 9-17
104
```
105
106
### Random Expressions
107
108
```python
109
from croniter import croniter
110
from datetime import datetime
111
112
base = datetime(2021, 4, 10)
113
114
# Each instance gets different random values
115
iter1 = croniter("R R * * *", base)
116
iter2 = croniter("R R * * *", base)
117
118
print(iter1.get_next(datetime)) # 2021-04-10 22:56:00
119
print(iter2.get_next(datetime)) # 2021-04-10 07:31:00 (different)
120
121
# But consistent within same instance
122
print(iter1.get_next(datetime)) # 2021-04-11 22:56:00 (same time next day)
123
124
# Random ranges
125
iter3 = croniter("R(0-30) R(9-17) * * *", base)
126
print(iter3.get_next(datetime)) # Random minute 0-30, random hour 9-17
127
```
128
129
### Custom Start Time Expansion
130
131
```python
132
from croniter import croniter
133
from datetime import datetime
134
135
start = datetime(2024, 7, 11) # July 11th
136
137
# Default behavior: every 7 days from calendar (1st, 8th, 15th, 22nd, 29th)
138
iter1 = croniter('0 0 */7 * *', start, expand_from_start_time=False)
139
matches1 = [iter1.get_next(datetime) for _ in range(5)]
140
print("Calendar-based:", [dt.day for dt in matches1])
141
# [15, 22, 29, 1, 8] (calendar boundaries)
142
143
# Custom expansion: every 7 days from start_time (11th, 18th, 25th, 1st, 8th)
144
iter2 = croniter('0 0 */7 * *', start, expand_from_start_time=True)
145
matches2 = [iter2.get_next(datetime) for _ in range(5)]
146
print("Start-time-based:", [dt.day for dt in matches2])
147
# [18, 25, 1, 8, 15] (from start time)
148
```
149
150
### Second-Level Precision
151
152
```python
153
from croniter import croniter
154
from datetime import datetime
155
156
base = datetime(2012, 4, 6, 13, 26, 10)
157
158
# Seconds as 6th field (default)
159
iter1 = croniter('* * * * * 15,25', base)
160
print(iter1.get_next(datetime)) # 2012-04-06 13:26:15
161
print(iter1.get_next(datetime)) # 2012-04-06 13:26:25
162
print(iter1.get_next(datetime)) # 2012-04-06 13:27:15
163
164
# Every second (be careful with performance!)
165
iter2 = croniter('* * * * * *', base)
166
print(iter2.get_next(datetime)) # 2012-04-06 13:26:11
167
168
# Seconds as 1st field
169
iter3 = croniter('15,25 * * * * *', base, second_at_beginning=True)
170
print(iter3.get_next(datetime)) # 2012-04-06 13:26:15
171
172
# Mixed with other precision
173
iter4 = croniter('30 */5 * * * *', base) # Every 5 minutes at 30 seconds
174
print(iter4.get_next(datetime)) # 2012-04-06 13:30:30
175
```
176
177
### Year Field Support
178
179
```python
180
from croniter import croniter
181
from datetime import datetime
182
183
base = datetime(2012, 4, 6, 2, 6, 59)
184
185
# New Year's Day every 2 years starting 2020
186
iter1 = croniter('0 0 1 1 * 0 2020/2', base)
187
print(iter1.get_next(datetime)) # 2020-01-01 00:00:00
188
print(iter1.get_next(datetime)) # 2022-01-01 00:00:00
189
print(iter1.get_next(datetime)) # 2024-01-01 00:00:00
190
191
# Specific years list
192
iter2 = croniter('0 0 1 1 * 0 2025,2030,2035', base)
193
print(iter2.get_next(datetime)) # 2025-01-01 00:00:00
194
195
# Year ranges
196
iter3 = croniter('0 0 1 1 * 0 2025-2030', base)
197
matches = [iter3.get_next(datetime) for _ in range(6)]
198
print([dt.year for dt in matches]) # [2025, 2026, 2027, 2028, 2029, 2030]
199
```
200
201
### Special Weekday Syntax
202
203
```python
204
from croniter import croniter
205
from datetime import datetime
206
207
base = datetime(2010, 1, 1)
208
209
# First Saturday and second Sunday of each month
210
iter1 = croniter('0 0 * * sat#1,sun#2', base)
211
print(iter1.get_next(datetime)) # 2010-01-02 00:00:00 (1st Saturday)
212
print(iter1.get_next(datetime)) # 2010-01-10 00:00:00 (2nd Sunday)
213
214
# Third and last Friday of each month
215
iter2 = croniter('0 0 * * 5#3,L5', base)
216
print(iter2.get_next(datetime)) # 2010-01-15 00:00:00 (3rd Friday)
217
print(iter2.get_next(datetime)) # 2010-01-29 00:00:00 (last Friday)
218
219
# Last day of each month (using L)
220
iter3 = croniter('0 0 L * *', base)
221
print(iter3.get_next(datetime)) # 2010-01-31 00:00:00
222
print(iter3.get_next(datetime)) # 2010-02-28 00:00:00 (handles Feb correctly)
223
```
224
225
### Timezone Handling
226
227
```python
228
import pytz
229
from croniter import croniter
230
from datetime import datetime
231
232
# Create timezone-aware datetime
233
tz = pytz.timezone("Europe/Paris")
234
local_date = tz.localize(datetime(2017, 3, 26)) # Near DST transition
235
236
# Croniter preserves timezone information
237
iter1 = croniter('0 0 * * *', local_date)
238
next_match = iter1.get_next(datetime)
239
print(next_match.tzinfo) # <DstTzInfo 'Europe/Paris' CET+1:00:00 STD>
240
241
# Works with DST transitions
242
iter2 = croniter('0 2 * * *', local_date) # 2 AM daily
243
for i in range(5):
244
match = iter2.get_next(datetime)
245
print(f"{match} - DST: {match.dst()}")
246
247
# Using dateutil timezone
248
import dateutil.tz
249
tz2 = dateutil.tz.gettz('Asia/Tokyo')
250
tokyo_date = datetime(2017, 3, 26, tzinfo=tz2)
251
iter3 = croniter('0 0 * * *', tokyo_date)
252
```
253
254
### Performance Optimization
255
256
```python
257
from croniter import croniter
258
from datetime import datetime
259
260
# Limit search window for sparse expressions
261
iter1 = croniter(
262
"0 4 1 1 fri", # 4 AM on January 1st if it's Friday (very sparse!)
263
datetime(2000, 1, 1),
264
day_or=False,
265
max_years_between_matches=15 # Limit to prevent infinite search
266
)
267
268
# With explicit limit, all_next() won't raise CroniterBadDateError
269
matches = []
270
for match in iter1.all_next(datetime):
271
matches.append(match)
272
if len(matches) >= 5: # Get first 5 matches
273
break
274
275
print(matches)
276
# [2010-01-01 04:00:00, 2016-01-01 04:00:00, 2021-01-01 04:00:00, ...]
277
278
# Without limit might raise CroniterBadDateError for very sparse expressions
279
try:
280
iter2 = croniter("0 4 1 1 fri", datetime(2000, 1, 1), day_or=False)
281
# This might fail if no matches found within default 50-year window
282
match = iter2.get_next(datetime)
283
except Exception as e:
284
print(f"Error: {e}")
285
```
286
287
### Combining Advanced Features
288
289
```python
290
from croniter import croniter
291
from datetime import datetime
292
import pytz
293
294
# Complex example combining multiple advanced features
295
tz = pytz.timezone("US/Eastern")
296
start = tz.localize(datetime(2024, 1, 1))
297
298
# Hashed expression with seconds and custom expansion
299
iter1 = croniter(
300
'H H(9-17) * * 1-5 30', # Random time during business hours, 30 seconds
301
start,
302
hash_id="business-report",
303
expand_from_start_time=True,
304
second_at_beginning=False
305
)
306
307
# Random expression with year field
308
iter2 = croniter(
309
'R R 1 1 * 0 2025-2030', # Random time on New Year's Day 2025-2030
310
start
311
)
312
313
# Special syntax with timezone
314
iter3 = croniter(
315
'0 9 * * L5', # 9 AM on last Friday of each month
316
start
317
)
318
319
# Get next matches
320
print("Business report:", iter1.get_next(datetime))
321
print("New Year random:", iter2.get_next(datetime))
322
print("Monthly meeting:", iter3.get_next(datetime))
323
```