Metadata-Version: 2.4
Name: cron-parser-py
Version: 1.0.0
Summary: Cron expression parser with human-readable descriptions and next-run calculation
Project-URL: Homepage, https://sarmkadan.com
Project-URL: Repository, https://github.com/Sarmkadan/cron-parser-py
Author-email: Vladyslav Zaiets <rutova2@gmail.com>
License: MIT
License-File: LICENSE
Keywords: cron,expression,human-readable,job,parser,scheduler,timer
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Systems Administration
Classifier: Topic :: Utilities
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# cron-parser-py

> Cron expression parser with human-readable descriptions and next-run calculation.

Pure Python · Zero dependencies · Python 3.9+

```python
from cron_parser_py import CronParser

expr = CronParser.parse("0 9 * * MON-FRI")
print(expr.description())   # At 9:00 AM, Monday through Friday
print(expr.next_run())       # 2024-01-15 09:00:00
print(expr.next_runs(3))     # [datetime, datetime, datetime]
```

---

## Installation

```bash
pip install cron-parser-py
```

---

## Features

| Capability | Details |
|---|---|
| **5-field standard cron** | `MIN HOUR DOM MON DOW` |
| **6-field (with seconds)** | `SEC MIN HOUR DOM MON DOW` |
| **7-field (with year)** | `SEC MIN HOUR DOM MON DOW YEAR` |
| **Predefined macros** | `@yearly`, `@monthly`, `@weekly`, `@daily`, `@hourly`, `@reboot` |
| **Special characters** | `*` `/` `,` `-` `?` `L` `W` `#` |
| **Name aliases** | `JAN–DEC`, `SUN–SAT` (case-insensitive) |
| **Human descriptions** | Natural English for any expression |
| **Next / previous run** | Forward and backward time search |
| **`is_due` check** | Determine if an expression fires right now |
| **Validation** | Detailed error messages with field/value context |

---

## Quick start

### Module-level helpers

```python
import cron_parser_py as cron

# Human-readable description
cron.describe("*/5 * * * *")          # "Every 5 minutes"
cron.describe("0 9 * * MON-FRI")      # "At 9:00 AM, Monday through Friday"
cron.describe("0 0 1 1 *")            # "At midnight, on the 1st, in January"

# Validate without raising
cron.is_valid("0 9 * * MON-FRI")      # True
cron.is_valid("99 9 * * *")           # False

# Next run time
cron.next_run("0 * * * *")            # next top-of-hour datetime

# Multiple next runs
cron.next_runs("*/15 * * * *", 5)     # list of 5 datetimes

# Previous run
cron.previous_run("0 0 * * *")        # most recent midnight

# Is due right now?
cron.is_due("* * * * *")              # True (every minute)
```

### CronExpression object

```python
from cron_parser_py import CronParser

expr = CronParser.parse("30 8 * * 1-5")

expr.description()           # "At 8:30 AM, Monday through Friday"
expr.next_run()              # next scheduled datetime
expr.next_runs(count=10)     # list of next 10 datetimes
expr.previous_run()          # most recent past run
expr.is_due()                # True/False right now
str(expr)                    # "30 8 * * 1-5"
```

---

## Supported syntax

### Field positions

**Standard 5-field:**

```
┌──────── minute        (0–59)
│ ┌────── hour          (0–23)
│ │ ┌──── day-of-month  (1–31)
│ │ │ ┌── month         (1–12)
│ │ │ │ ┌ day-of-week   (0–7, 0 and 7 = Sunday)
* * * * *
```

**6-field (add seconds at the front):**

```
┌──────────── second       (0–59)
│ ┌────────── minute       (0–59)
│ │ ┌──────── hour         (0–23)
│ │ │ ┌────── day-of-month (1–31)
│ │ │ │ ┌──── month        (1–12)
│ │ │ │ │ ┌── day-of-week  (0–6)
* * * * * *
```

### Special characters

| Character | Meaning | Example |
|---|---|---|
| `*` | every value | `* * * * *` — every minute |
| `?` | any (day fields only) | `0 0 ? * 1` — every Monday |
| `/` | step | `*/15 * * * *` — every 15 min |
| `-` | range | `MON-FRI` |
| `,` | list | `1,15 * * *` — 1st and 15th |
| `L` | last | `L` in DOM — last day of month |
| `W` | nearest weekday | `15W` — nearest weekday to 15th |
| `LW` | last weekday | last weekday of month |
| `#` | Nth weekday | `5#2` — second Friday |
| `nL` | last Nth weekday | `5L` — last Friday |

### Month and weekday names

```
JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC
SUN MON TUE WED THU FRI SAT
```
Names are case-insensitive and can be used in ranges: `MON-FRI`.

### Predefined macros

| Macro | Equivalent | Description |
|---|---|---|
| `@yearly` / `@annually` | `0 0 1 1 *` | Once a year, on Jan 1st at midnight |
| `@monthly` | `0 0 1 * *` | Once a month, on the 1st at midnight |
| `@weekly` | `0 0 * * 0` | Once a week, on Sunday at midnight |
| `@daily` / `@midnight` | `0 0 * * *` | Every day at midnight |
| `@hourly` | `0 * * * *` | Every hour |
| `@reboot` | — | At system startup |

---

## API Reference

### `CronParser`

```python
CronParser.parse(expression: str) -> CronExpression
```
Parse a cron expression string. Raises `CronParseError` on failure.

```python
CronParser.is_valid(expression: str) -> bool
```
Return `True` if the expression is parseable without errors.

---

### `CronExpression`

| Method | Returns | Description |
|---|---|---|
| `.description()` | `str` | Human-readable English description |
| `.next_run(from_time=None)` | `datetime` | Next run after `from_time` (default: now) |
| `.next_runs(count=5, from_time=None)` | `list[datetime]` | Next N run times |
| `.previous_run(from_time=None)` | `datetime` | Most recent run before `from_time` |
| `.is_due(at_time=None, tolerance_seconds=0)` | `bool` | True if expression fires now |
| `str(expr)` | `str` | Original expression string |

---

### `CronScheduler`

```python
from cron_parser_py import CronParser, CronScheduler

expr = CronParser.parse("0 9 * * *")

CronScheduler.next_run(expr)
CronScheduler.next_runs(expr, count=10)
CronScheduler.previous_run(expr)
CronScheduler.is_due(expr, tolerance_seconds=30)
CronScheduler.time_until_next_run(expr)   # → timedelta
```

---

### `CronValidator`

```python
from cron_parser_py import CronParser, CronValidator

expr = CronParser.parse("0 0 30 2 *")

errors = CronValidator.validate(expr, strict=True)
# ["The combination of month(s) and day(s) can never produce a valid date ..."]

CronValidator.assert_valid(expr, strict=True)   # raises CronValidationError

warnings = CronValidator.get_warnings(expr)
```

---

### `CronDescriptor`

```python
from cron_parser_py import CronParser, CronDescriptor

expr = CronParser.parse("0 9 * * MON-FRI")
CronDescriptor.describe(expr)   # "At 9:00 AM, Monday through Friday"

# Describe a single field
from cron_parser_py import FieldType
from cron_parser_py.parser import parse_field
field = parse_field("MON-FRI", FieldType.DAY_OF_WEEK)
CronDescriptor.describe_field(field)   # "Monday through Friday"
```

---

### Exceptions

```python
from cron_parser_py import (
    CronParseError,       # base class
    CronValidationError,  # invalid expression structure
    CronFieldError,       # invalid value in a specific field
    CronScheduleError,    # cannot calculate run time
)
```

---

## Examples

```python
from datetime import datetime
import cron_parser_py as cron

# -- Descriptions --
cron.describe("* * * * *")            # "Every minute"
cron.describe("*/5 * * * *")          # "Every 5 minutes"
cron.describe("0 * * * *")            # "Every hour"
cron.describe("0 */2 * * *")          # "Every 2 hours"
cron.describe("0 9 * * *")            # "At 9:00 AM"
cron.describe("30 14 * * *")          # "At 2:30 PM"
cron.describe("0 9,17 * * *")         # "At 9:00 AM and 5:00 PM"
cron.describe("0 9 * * 1-5")          # "At 9:00 AM, Monday through Friday"
cron.describe("0 9 * * 1,3,5")        # "At 9:00 AM, on Monday, Wednesday, and Friday"
cron.describe("0 0 1 * *")            # "At midnight, on the 1st"
cron.describe("0 0 L * *")            # "At midnight, on the last day of the month"
cron.describe("0 0 ? * 5#2")          # "At midnight, on the second Friday of the month"
cron.describe("0 0 1 1 *")            # "At midnight, on the 1st, in January"
cron.describe("@weekly")              # "Weekly, at midnight on Sunday"

# -- Scheduling --
ref = datetime(2024, 6, 10, 8, 0)     # Monday 08:00

nxt = cron.next_run("0 9 * * MON-FRI", ref)
# datetime(2024, 6, 10, 9, 0)  — same day at 09:00

runs = cron.next_runs("*/30 8-18 * * 1-5", count=4, from_time=ref)
# Four 30-minute slots on weekdays between 08:00 and 18:00

prev = cron.previous_run("0 0 * * *", ref)
# datetime(2024, 6, 10, 0, 0)  — midnight that day

td = cron.parse("0 9 * * *").next_run(ref) - ref
print(td)   # 1:00:00  (1 hour)
```

---

## License

MIT © [Vladyslav Zaiets](https://sarmkadan.com)
