# croniter-rs

[![CI](https://github.com/Souls-R/croniter-rs/actions/workflows/CI.yml/badge.svg)](https://github.com/Souls-R/croniter-rs/actions/workflows/CI.yml)
[![PyPI version](https://img.shields.io/pypi/v/croniter-rs.svg)](https://pypi.org/project/croniter-rs/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

A high-performance Rust implementation of [croniter](https://github.com/pallets-eco/croniter), providing up to **50x faster** cron expression parsing and datetime iteration. Drop-in compatible with the original Python API.

**Validated against 2.3 million differential fuzz tests with zero correctness bugs.**

## Why croniter-rs?

- **Up to 50x faster** — Rust-powered cron iteration, exposed to Python via PyO3
- **Drop-in compatible** — same API as [croniter](https://pypi.org/project/croniter/), swap one import line
- **Rigorously tested** — 2.3M differential fuzz tests, 5,200 real-world GitHub Actions expressions, 301 Rust unit tests, 95%+ code coverage
- **Independently maintained** — not a fork; a clean-room Rust reimplementation with its own test suite

## Installation

```bash
pip install croniter-rs
```

## Quick Migration

```python
# Before
from croniter import croniter

# After — no other code changes needed
from croniter_rs import croniter
```

```python
# Graceful fallback (recommended)
try:
    from croniter_rs import croniter  # Fast Rust version
except ImportError:
    from croniter import croniter      # Fallback to original
```

## Performance

| Operation | croniter (Python) | croniter-rs (Rust) | Speedup |
|-----------|-------------------|---------------------|---------|
| Constructor | 83.79 μs | 6.09 μs | **13.8x** |
| get_next() | 24.62 μs | 0.52 μs | **47.0x** |
| get_prev() | 24.37 μs | 0.49 μs | **49.9x** |
| is_valid() | 79.77 μs | 5.27 μs | **15.1x** |
| match() | 114.97 μs | 5.85 μs | **19.7x** |
| 1000 iterations | 24.93 ms | 0.45 ms | **55.3x** |

*Benchmarks: Linux 6.8.0, Python 3.10, 10,000 iterations per operation.*

## Usage

```python
from croniter_rs import croniter
from datetime import datetime

base = datetime(2024, 1, 1, 0, 0)
cron = croniter('*/5 * * * *', base)

# Get next/previous occurrence
print(cron.get_next(datetime))  # 2024-01-01 00:05:00
print(cron.get_prev(datetime))  # 2024-01-01 00:00:00

# Iterate
for _ in range(5):
    print(cron.get_next(datetime))

# Validate expression
print(croniter.is_valid('0 0 * * *'))  # True

# Check if datetime matches pattern
print(croniter.match('0 12 * * *', datetime(2024, 1, 1, 12, 0)))  # True
```

## API Reference

### Constructor

```python
croniter(expr, start_time=None, ret_type=None, day_or=True,
         hash_id=None, second_at_beginning=False)
```

### Instance Methods

| Method | Description |
|--------|-------------|
| `get_next(ret_type)` | Get next occurrence |
| `get_prev(ret_type)` | Get previous occurrence |
| `get_current(ret_type)` | Get current time |
| `set_current(time)` | Set current time |
| `all_next(ret_type)` | Iterator for forward iteration |
| `all_prev(ret_type)` | Iterator for backward iteration |

### Class Methods

| Method | Description |
|--------|-------------|
| `is_valid(expr)` | Validate expression |
| `match(expr, dt)` | Check if datetime matches |
| `match_range(expr, start, end)` | Check if match exists in range |
| `expand(expr)` | Expand expression fields |

## Supported Syntax

### Standard Cron (5 fields)

```
┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12 or JAN-DEC)
│ │ │ │ ┌───────────── day of week (0-6 or SUN-SAT)
│ │ │ │ │
* * * * *
```

### Extended Formats

- **6-field**: Adds seconds (`minute hour day month weekday second`, or `second minute hour day month weekday` with `second_at_beginning=True`)
- **7-field**: Adds year (1970–2099)

### Expressions

| Pattern | Example | Description |
|---------|---------|-------------|
| Wildcard | `*` | Every value |
| Value | `5` | Specific value |
| Range | `1-5` | Values 1 through 5 |
| Step | `*/5` | Every 5th value |
| List | `1,3,5` | Values 1, 3, and 5 |
| Last | `L` | Last day of month |
| Nth weekday | `MON#1` | First Monday |
| Last weekday | `L5` | Last Friday |
| Hash | `H` | Jenkins-style hash (requires `hash_id`) |
| Random | `R` | Random value |
| Placeholder | `?` | Wildcard for day/weekday fields |

### Keyword Expressions

| Keyword | Equivalent |
|---------|------------|
| `@yearly` / `@annually` | `0 0 1 1 *` |
| `@monthly` | `0 0 1 * *` |
| `@weekly` | `0 0 * * 0` |
| `@daily` / `@midnight` | `0 0 * * *` |
| `@hourly` | `0 * * * *` |

## croniter_range()

```python
from croniter_rs import croniter_range
from datetime import datetime

start = datetime(2024, 1, 1, 0, 0)
stop = datetime(2024, 1, 1, 1, 0)

for dt in croniter_range(start, stop, "*/15 * * * *"):
    print(dt)
```

## Module Constants

```python
from croniter_rs import (
    MINUTE_FIELD,  # 0
    HOUR_FIELD,    # 1
    DAY_FIELD,     # 2
    MONTH_FIELD,   # 3
    DOW_FIELD,     # 4
    SECOND_FIELD,  # 5
    YEAR_FIELD,    # 6
    UTC_DT,        # datetime.timezone.utc
)
```

## Exception Types

All exception types match the original croniter for compatibility:

- `CroniterError` — Base exception
- `CroniterBadCronError` — Invalid cron expression
- `CroniterBadDateError` — Failed to find matching date
- `CroniterNotAlphaError` — Invalid alphabetic value
- `CroniterUnsupportedSyntaxError` — Unsupported syntax
- `CroniterBadTypeRangeError` — Type mismatch in range

## Not Yet Implemented

| Feature | Status |
|---------|--------|
| Timezone-aware datetime (full DST support) | Planned |

## Testing

This project uses a multi-layered testing strategy:

- **301 Rust unit tests** — covering parser, scheduler, and edge cases
- **2.3M differential fuzz tests** — comparing output against Python croniter
- **5,200 real-world expressions** — sourced from GitHub Actions workflows
- **95%+ code coverage** — remaining uncovered lines are unreachable defensive code

See [FUZZING_REPORT.md](FUZZING_REPORT.md) for the full methodology and results.

## Contributing

```bash
git clone https://github.com/Souls-R/croniter-rs.git
cd croniter-rs
pip install maturin pytest croniter
maturin develop
cargo test           # Rust tests
pytest tests/ -v     # Python integration tests
```

## License

MIT License. See [LICENSE](LICENSE).

## Acknowledgments

Thanks to [kiorky](https://github.com/kiorky) and all contributors to the original [croniter](https://github.com/pallets-eco/croniter) library for the design and specification that this implementation follows.
