or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-features.mdcore-iteration.mdindex.mdrange-operations.mdvalidation-matching.md

advanced-features.mddocs/

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

```