# Croniter Python Source Code - Line-by-Line Analysis

**Date:** 2026-02-07
**Source:** /home/ubuntu-exp/cronproject/croniter/src/croniter/croniter.py (1359 lines)
**Purpose:** Complete API and behavior documentation for Rust drop-in replacement

## Executive Summary

This document provides a comprehensive analysis of the Python croniter library's source code to ensure our Rust implementation (`croniter-rs`) achieves 100% behavioral compatibility.

## Public API Surface

### Main Class: `croniter`

```python
class croniter:
    def __init__(self, expr_format, start_time=None, ret_type=float,
                 day_or=True, max_years_between_matches=50, is_prev=False,
                 hash_id=None, implement_cron_bug=False,
                 second_at_beginning=False, expand_from_start_time=False)
```

**Parameters:**
- `expr_format` (str): Cron expression (5, 6, or 7 fields)
- `start_time` (datetime/float): Starting point for iteration
- `ret_type` (type): Return type (float, datetime, int)
- `day_or` (bool): True=OR logic for DOM/DOW, False=AND logic
- `max_years_between_matches` (int): Safety limit (default 50)
- `is_prev` (bool): Start with previous occurrence
- `hash_id` (str/bytes): Seed for H expressions
- `implement_cron_bug` (bool): Mimic vixie cron bug
- `second_at_beginning` (bool): Seconds field first (6-field)
- `expand_from_start_time` (bool): Expand H from start_time

### Instance Methods

#### `get_next(ret_type=None, start_time=None, update_current=True)`
Returns next occurrence after current time.
- Updates internal state if `update_current=True`
- Can override return type
- Can set new start_time

#### `get_prev(ret_type=None, start_time=None, update_current=True)`
Returns previous occurrence before current time.

#### `get_current(ret_type=None)`
Returns current internal timestamp without advancing.

#### `set_current(start_time, force=True)`
Sets current internal timestamp.
- `force=False`: Only set if start_time > current

#### `all_next(ret_type=None, start_time=None, update_current=True)`
Generator yielding all future occurrences.

#### `all_prev(ret_type=None, start_time=None, update_current=True)`
Generator yielding all past occurrences.

#### `iter(count)`
Returns list of next `count` occurrences.

### Class Methods

#### `expand(expr_format, hash_id=None, second_at_beginning=False, from_timestamp=None)`
Expands H and R expressions to concrete values.
Returns expanded cron expression string.

#### `is_valid(expression, hash_id=None, encoding='UTF-8', second_at_beginning=False)`
Validates cron expression syntax.
Returns True/False.

#### `match(cron_expression, testdate, day_or=True, second_at_beginning=False)`
Tests if datetime matches cron expression.
Returns True/False.

#### `match_range(cron_expression, from_datetime, to_datetime, day_or=True, second_at_beginning=False)`
Tests if any time in range matches cron expression.
Returns True/False.

### Static Methods

#### `datetime_to_timestamp(d)`
Converts datetime to Unix timestamp (float).

#### `timestamp_to_datetime(timestamp, tzinfo=None)`
Converts Unix timestamp to datetime with timezone.

### Module-Level Functions

#### `croniter_range(start, stop, expr_format, ret_type=float, day_or=True, exclude_ends=False, _croniter=None, second_at_beginning=False)`
Generates all times in [start, stop] matching cron expression.

## Field Indices & Constants

```python
MINUTE_FIELD = 0
HOUR_FIELD = 1
DAY_FIELD = 2
MONTH_FIELD = 3
DOW_FIELD = 4
SECOND_FIELD = 5
YEAR_FIELD = 6
```

**Field Ranges:**
- Second: 0-59
- Minute: 0-59
- Hour: 0-23
- Day: 1-31
- Month: 1-12
- DOW: 0-7 (0=Sunday, 7=Sunday)
- Year: 1970-2099

**Special Constants:**
- `OVERFLOW32B_MODE`: Detected at module load for Y2038 handling
- `UTC_DT`: UTC timezone object
- `M_ALPHAS`: Month name mappings (jan-dec)
- `DOW_ALPHAS`: Day name mappings (sun-sat)

## Exception Hierarchy

```
CroniterError (base exception)
├── CroniterBadCronError (invalid cron syntax)
│   ├── CroniterUnsupportedSyntaxError
│   └── CroniterNotAlphaError
└── CroniterBadDateError (invalid date/time)

CroniterBadTypeRangeError (TypeError subclass)
```

## Cron Expression Syntax

### Field Formats

**5-field (Unix cron):**
```
MIN HOUR DAY MONTH DOW
```

**6-field (with seconds):**
```
MIN HOUR DAY MONTH DOW SEC
or (second_at_beginning=True)
SEC MIN HOUR DAY MONTH DOW
```

**7-field (with year):**
```
MIN HOUR DAY MONTH DOW SEC YEAR
```

### Operators

1. **Wildcard (`*`)**: All values
   - `* * * * *` = every minute

2. **Range (`-`)**: Inclusive range
   - `1-5 * * * *` = minutes 1,2,3,4,5
   - Supports wraparound: `10-2` in month = Oct,Nov,Dec,Jan,Feb

3. **Step (`/`)**: Every nth value
   - `*/5 * * * *` = every 5 minutes
   - `1-10/2 * * * *` = 1,3,5,7,9

4. **List (`,`)**: Multiple values
   - `1,3,5 * * * *` = minutes 1,3,5
   - Can mix: `1,3-5,10 * * * *`

5. **Last (`L`)**: Last day of month
   - `* * L * *` = last day of each month
   - `L` in DOW: last occurrence (e.g., `L5` = last Friday)

6. **Nth weekday (`#`)**: Nth occurrence
   - `* * * * 2#3` = 3rd Tuesday
   - `* * * * 5#1` = 1st Friday

7. **No specific value (`?`)**: Wildcard for day fields
   - `* * ? * 1` = every Monday (day of month ignored)

8. **Hash (`H`)**: Deterministic pseudo-random
   - `H * * * *` = consistent minute based on hash_id
   - `H(0-29) * * * *` = hash within range
   - `H/15 * * * *` = hash with step
   - `H(30-59)/10 * * * *` = hash in range with step

9. **Random (`R`)**: True random
   - `R * * * *` = random minute
   - `R(0-29) * * * *` = random in range

### Expression Aliases

```python
@yearly    = 0 0 1 1 *
@annually  = 0 0 1 1 *
@monthly   = 0 0 1 * *
@weekly    = 0 0 * * 0
@daily     = 0 0 * * *
@midnight  = 0 0 * * *
@hourly    = 0 * * * *
```

### Month/Day Names

**Months (case-insensitive):**
- jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec
- Can use in ranges: `* * * jan-mar *`

**Days (case-insensitive):**
- sun, mon, tue, wed, thu, fri, sat
- Can use in ranges: `* * * * mon-fri`

## Day/DOW Logic

### `day_or=True` (Default, Standard Cron)
DOM and DOW are OR'd (union).
- `15 * * * 1` = 15th of month OR every Monday
- If either DOM or DOW is `*`, only the other matters
- If both are specific, match either

### `day_or=False` (AND Logic)
DOM and DOW are AND'd (intersection).
- `15 * * * 1` = 15th of month AND it's Monday
- Both conditions must be true

### `implement_cron_bug=True`
Mimics vixie cron bug where `*` in DOM makes AND instead of OR.
- `* * * * 1` with bug = every day that is Monday (same as without bug)
- But internal logic differs

## Timezone Handling

### Timezone Preservation
- Croniter preserves tzinfo from start_time
- All returned datetimes have same tzinfo
- Naive datetimes treated as local time

### DST Transitions

**Spring Forward (non-existent times):**
- Skip forward to next valid time
- Example: 2:30 AM doesn't exist → 3:00 AM

**Fall Back (ambiguous times):**
- Choose time closer to previous but still successor
- Uses PEP 495 `fold` attribute
- Prefers fold=0 (first occurrence) when advancing forward

### 32-bit Overflow Mode
Detected at module load. If timestamp > 2^31-1 fails:
- Uses timedelta arithmetic instead of fromtimestamp
- Workaround for Y2038 problem on 32-bit systems

## Iteration Logic

### Forward Iteration (`get_next`)
1. Start from current timestamp
2. Increment to next valid time
3. Check all field constraints
4. Handle month/year boundaries
5. Apply max_years_between_matches limit
6. Update current timestamp

### Backward Iteration (`get_prev`)
1. Start from current timestamp
2. Decrement to previous valid time
3. Check all field constraints
4. Handle month/year boundaries
5. Apply max_years_between_matches limit
6. Update current timestamp

### Boundary Handling
- Month wraparound: Dec → Jan (increment year)
- Year wraparound: Checked against max_years_between_matches
- Leap year: Feb 29 handled correctly
- Last day of month: Varies by month (28-31)

## Edge Cases & Special Behaviors

### DOW 0 vs 7
Both 0 and 7 represent Sunday.
- `* * * * 0` = Sunday
- `* * * * 7` = Sunday
- `* * * * 0,7` = Sunday (deduplicated)

### Month/DOW 0 Conversion
Classical cron doesn't support 0 for month/DOW in some contexts.
- Month 0 → Month 1 (January)
- DOW 0 → Sunday (valid)

### Leap Year Handling
- `* * 29 2 *` matches Feb 29 only in leap years
- Non-leap years: skips to next valid date
- Leap year calculation: divisible by 4, except centuries unless divisible by 400

### Last Day of Month (`L`)
- Jan: 31, Feb: 28/29, Mar: 31, Apr: 30, May: 31, Jun: 30
- Jul: 31, Aug: 31, Sep: 30, Oct: 31, Nov: 30, Dec: 31

### Nth Weekday
- `2#3` = 3rd Tuesday of month
- `5#1` = 1st Friday of month
- `1#5` = 5th Monday (may not exist in all months)
- If nth occurrence doesn't exist, skip to next month

### Last Occurrence (`L` in DOW)
- `L5` = last Friday of month
- `L0` = last Sunday of month

### Wraparound Ranges
- `10-2` in month = Oct, Nov, Dec, Jan, Feb
- `20-5` in hour = 20, 21, 22, 23, 0, 1, 2, 3, 4, 5
- `fri-mon` = Fri, Sat, Sun, Mon

### Hash Expression Expansion
- Uses CRC32 of hash_id (or empty string if None)
- Deterministic: same hash_id → same value
- `H` → hash % range_size
- `H(10-20)` → 10 + (hash % 11)
- `H/15` → (hash % (60/15)) * 15 = 0, 15, 30, or 45
- `expand_from_start_time=True`: Uses start_time in hash

### Random Expression Expansion
- Uses random.randint
- Non-deterministic
- Same syntax as H but with R

## Validation Rules

### Expression Validation
1. Must have 5, 6, or 7 fields
2. Each field must be valid syntax
3. Ranges must be low-high
4. Steps must be positive
5. Values must be in valid range
6. No negative numbers
7. Month/day names must be valid

### Field Range Validation
- Second: 0-59
- Minute: 0-59
- Hour: 0-23
- Day: 1-31 (validated against month)
- Month: 1-12
- DOW: 0-7
- Year: 1970-2099

### Special Syntax Validation
- `#` only in DOW field
- `L` only in day or DOW field
- `?` only in day or DOW field
- Cannot mix `#` with literal DOW in same expression
- Cannot have both day and DOW as `?`

## Test Requirements

### 1. Basic Expressions
- [ ] Every minute: `* * * * *`
- [ ] Every hour: `0 * * * *`
- [ ] Every day: `0 0 * * *`
- [ ] Every week: `0 0 * * 0`
- [ ] Every month: `0 0 1 * *`
- [ ] Every year: `0 0 1 1 *`

### 2. Ranges
- [ ] Simple range: `1-5 * * * *`
- [ ] Wraparound: `10-2 * * * *` (month)
- [ ] Wraparound: `20-5 * * * *` (hour)
- [ ] Month names: `* * * jan-mar *`
- [ ] Day names: `* * * * mon-fri`

### 3. Steps
- [ ] Every 5 minutes: `*/5 * * * *`
- [ ] Every 2 hours: `0 */2 * * *`
- [ ] Range with step: `1-10/2 * * * *`
- [ ] Wraparound with step: `10-2/2 * * * *`

### 4. Lists
- [ ] Simple list: `1,3,5 * * * *`
- [ ] Mixed list: `1,3-5,10 * * * *`
- [ ] Month names: `* * * jan,mar,may *`
- [ ] Day names: `* * * * mon,wed,fri`

### 5. Special Characters
- [ ] Last day: `* * L * *`
- [ ] Nth weekday: `* * * * 2#3`
- [ ] Last occurrence: `* * * * L5`
- [ ] Wildcard: `* * ? * 1`

### 6. Aliases
- [ ] @yearly
- [ ] @annually
- [ ] @monthly
- [ ] @weekly
- [ ] @daily
- [ ] @midnight
- [ ] @hourly

### 7. Hash Expressions
- [ ] Simple: `H * * * *`
- [ ] Range: `H(0-29) * * * *`
- [ ] Step: `H/15 * * * *`
- [ ] Range+step: `H(30-59)/10 * * * *`
- [ ] Deterministic: same hash_id → same value
- [ ] Different hash_id → different value

### 8. Random Expressions
- [ ] Simple: `R * * * *`
- [ ] Range: `R(0-29) * * * *`
- [ ] Non-deterministic: multiple calls → different values

### 9. Timezone Handling
- [ ] UTC timezone
- [ ] Fixed offset (+05:00)
- [ ] DST spring forward (skip non-existent)
- [ ] DST fall back (handle ambiguous)
- [ ] Naive datetime (local time)
- [ ] Timezone preservation

### 10. Day/DOW Logic
- [ ] day_or=True: OR logic
- [ ] day_or=False: AND logic
- [ ] implement_cron_bug=True
- [ ] Both DOM and DOW specific
- [ ] One wildcard, one specific
- [ ] Both wildcards

### 11. Edge Cases
- [ ] Leap year: `* * 29 2 *`
- [ ] Non-leap year: skip Feb 29
- [ ] DOW 0 = Sunday
- [ ] DOW 7 = Sunday
- [ ] Month boundaries (28, 29, 30, 31 days)
- [ ] Year boundaries
- [ ] Nth weekday doesn't exist (5th Monday)
- [ ] Last occurrence

### 12. Field Formats
- [ ] 5-field: `MIN HOUR DAY MONTH DOW`
- [ ] 6-field: `MIN HOUR DAY MONTH DOW SEC`
- [ ] 6-field (second_at_beginning): `SEC MIN HOUR DAY MONTH DOW`
- [ ] 7-field: `MIN HOUR DAY MONTH DOW SEC YEAR`

### 13. Iteration
- [ ] Forward: get_next()
- [ ] Backward: get_prev()
- [ ] Generator: all_next()
- [ ] Generator: all_prev()
- [ ] Range: croniter_range()
- [ ] max_years_between_matches limit
- [ ] Iterator protocol: __iter__, __next__

### 14. Return Types
- [ ] float (Unix timestamp)
- [ ] datetime
- [ ] int (Unix timestamp)

### 15. Validation
- [ ] Valid expressions
- [ ] Invalid expressions
- [ ] Out of range values
- [ ] Invalid steps (zero, negative)
- [ ] Invalid syntax
- [ ] Mixed nth weekday syntax

### 16. Boundary Values
- [ ] Minimum values (0, 1)
- [ ] Maximum values (59, 23, 31, 12, 7)
- [ ] Timestamp limits (1970, 2099)
- [ ] 32-bit overflow mode

### 17. API Methods
- [ ] get_next()
- [ ] get_prev()
- [ ] get_current()
- [ ] set_current()
- [ ] all_next()
- [ ] all_prev()
- [ ] iter()
- [ ] expand()
- [ ] is_valid()
- [ ] match()
- [ ] match_range()
- [ ] datetime_to_timestamp()
- [ ] timestamp_to_datetime()

### 18. Error Handling
- [ ] CroniterBadCronError
- [ ] CroniterBadDateError
- [ ] CroniterUnsupportedSyntaxError
- [ ] CroniterNotAlphaError
- [ ] CroniterBadTypeRangeError

## Differential Testing Strategy

### Test Data Generation
1. **Equivalence Classes:**
   - Valid expressions (all operators)
   - Invalid expressions (all error types)
   - Edge cases (boundaries, special values)
   - Timezone scenarios (UTC, fixed, DST)
   - Field formats (5, 6, 7 fields)

2. **Boundary Value Analysis:**
   - Min/max for each field
   - Wraparound boundaries
   - Month boundaries (28-31 days)
   - Year boundaries
   - Timestamp limits

3. **Combinatorial Testing:**
   - All operators combined
   - All special characters
   - All field formats
   - All return types
   - All day_or combinations

### Differential Test Execution
1. Generate test case (expression, start_time, parameters)
2. Execute Python croniter
3. Execute Rust croniter_rs
4. Compare results:
   - Next/prev occurrences
   - Validation results
   - Error types
   - Edge case handling
5. Log any discrepancies
6. Analyze and fix

### Coverage Metrics
- **Code Coverage:** 100% (cargo-tarpaulin/cargo-llvm-cov)
- **Branch Coverage:** 100%
- **Data Coverage:** All equivalence classes
- **API Coverage:** All public methods
- **Error Coverage:** All exception types

## Implementation Checklist

### Core Parsing
- [ ] 5/6/7 field format detection
- [ ] Wildcard (*) parsing
- [ ] Range (-) parsing with wraparound
- [ ] Step (/) parsing
- [ ] List (,) parsing
- [ ] Last (L) parsing
- [ ] Nth weekday (#) parsing
- [ ] Wildcard (?) parsing
- [ ] Hash (H) expansion
- [ ] Random (R) expansion
- [ ] Month name parsing
- [ ] Day name parsing
- [ ] Alias expansion

### Iteration Logic
- [ ] Forward iteration (get_next)
- [ ] Backward iteration (get_prev)
- [ ] Month boundary handling
- [ ] Year boundary handling
- [ ] Leap year handling
- [ ] max_years_between_matches limit

### Day/DOW Logic
- [ ] day_or=True (OR)
- [ ] day_or=False (AND)
- [ ] implement_cron_bug
- [ ] DOW 0/7 = Sunday
- [ ] Nth weekday calculation
- [ ] Last occurrence calculation

### Timezone Support
- [ ] Timezone preservation
- [ ] DST spring forward
- [ ] DST fall back
- [ ] Naive datetime handling
- [ ] 32-bit overflow mode

### Validation
- [ ] Expression syntax validation
- [ ] Field range validation
- [ ] Special syntax validation
- [ ] Error type mapping

### API Surface
- [ ] croniter class
- [ ] All instance methods
- [ ] All class methods
- [ ] All static methods
- [ ] croniter_range function
- [ ] Exception hierarchy

### Python Interop
- [ ] PyO3 bindings
- [ ] Type conversions (datetime, float, int)
- [ ] Exception mapping
- [ ] Iterator protocol
- [ ] Module structure

## Notes for Rust Implementation

1. **Timezone Library:** Use `chrono-tz` for timezone support
2. **Hash Function:** Use CRC32 for H expressions
3. **Random:** Use `rand` crate for R expressions
4. **Error Handling:** Map to Python exception types
5. **Performance:** Optimize hot paths (iteration, parsing)
6. **Memory:** Minimize allocations in iteration
7. **Safety:** No unsafe code unless absolutely necessary
8. **Testing:** Comprehensive differential tests against Python

## Conclusion

This analysis provides a complete specification for implementing a drop-in replacement for Python croniter in Rust. Every public API, behavior, edge case, and validation rule has been documented. The test requirements ensure 100% compatibility with the original library.
