python-dateutil-rs 0.1.0__tar.gz → 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/Cargo.lock +11 -4
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/PKG-INFO +2 -2
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/README.md +1 -1
- python_dateutil_rs-0.1.2/crates/dateutil-core/Cargo.toml +30 -0
- python_dateutil_rs-0.1.2/crates/dateutil-core/README.md +209 -0
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/benches/benchmarks.rs +8 -8
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/common.rs +13 -40
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/easter.rs +0 -44
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/parser/isoparser.rs +21 -0
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/parser/parserinfo.rs +40 -0
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/parser.rs +180 -82
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/relativedelta.rs +152 -0
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/rrule/parse.rs +30 -0
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/rrule/set.rs +38 -0
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/rrule.rs +221 -0
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/tz/offset.rs +12 -0
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/tz.rs +109 -32
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-py/Cargo.toml +2 -2
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-py/src/py/common.rs +31 -3
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-py/src/py/conv.rs +20 -0
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-py/src/py/easter.rs +1 -1
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-py/src/py/parser.rs +2 -2
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-py/src/py/relativedelta.rs +3 -3
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-py/src/py/rrule.rs +179 -29
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-py/src/py/tz.rs +51 -11
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/pyproject.toml +1 -1
- python_dateutil_rs-0.1.2/python/dateutil_rs/easter.py +15 -0
- python_dateutil_rs-0.1.2/python/dateutil_rs/relativedelta.py +5 -0
- python_dateutil_rs-0.1.2/python/dateutil_rs/rrule.py +27 -0
- python_dateutil_rs-0.1.2/python/dateutil_rs/tz.py +23 -0
- python_dateutil_rs-0.1.0/crates/dateutil-core/Cargo.toml +0 -25
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/Cargo.toml +0 -0
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/LICENSE +0 -0
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/CLAUDE.md +0 -0
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/error.rs +0 -0
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/lib.rs +0 -0
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/parser/tokenizer.rs +0 -0
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/rrule/iter.rs +0 -0
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/tz/file.rs +0 -0
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/tz/local.rs +0 -0
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/tz/utc.rs +0 -0
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-py/src/lib.rs +0 -0
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-py/src/py.rs +0 -0
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/python/dateutil_rs/__init__.py +0 -0
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/python/dateutil_rs/_native.pyi +0 -0
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/python/dateutil_rs/parser.py +0 -0
- {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/python/dateutil_rs/py.typed +0 -0
|
@@ -219,24 +219,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
219
219
|
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
|
220
220
|
|
|
221
221
|
[[package]]
|
|
222
|
-
name = "dateutil
|
|
223
|
-
version = "0.1.
|
|
222
|
+
name = "dateutil"
|
|
223
|
+
version = "0.1.2"
|
|
224
224
|
dependencies = [
|
|
225
225
|
"bitflags",
|
|
226
226
|
"chrono",
|
|
227
227
|
"criterion",
|
|
228
228
|
"iana-time-zone",
|
|
229
229
|
"phf",
|
|
230
|
+
"rustc-hash",
|
|
230
231
|
"smallvec",
|
|
231
232
|
"thiserror",
|
|
232
233
|
]
|
|
233
234
|
|
|
234
235
|
[[package]]
|
|
235
236
|
name = "dateutil-py"
|
|
236
|
-
version = "0.1.
|
|
237
|
+
version = "0.1.2"
|
|
237
238
|
dependencies = [
|
|
238
239
|
"chrono",
|
|
239
|
-
"dateutil
|
|
240
|
+
"dateutil",
|
|
240
241
|
"pyo3",
|
|
241
242
|
]
|
|
242
243
|
|
|
@@ -576,6 +577,12 @@ version = "0.8.10"
|
|
|
576
577
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
577
578
|
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
|
578
579
|
|
|
580
|
+
[[package]]
|
|
581
|
+
name = "rustc-hash"
|
|
582
|
+
version = "2.1.2"
|
|
583
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
584
|
+
checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
|
|
585
|
+
|
|
579
586
|
[[package]]
|
|
580
587
|
name = "rustversion"
|
|
581
588
|
version = "1.0.22"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-dateutil-rs
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Classifier: Programming Language :: Python :: 3.10
|
|
5
5
|
Classifier: Programming Language :: Python :: 3.11
|
|
6
6
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -142,7 +142,7 @@ Benchmarks compare the original `python-dateutil` (PyPI) and the Rust extension
|
|
|
142
142
|
| Parser (parse) | **19.5x-36.0x** |
|
|
143
143
|
| Parser (isoparse) | **13.0x-38.4x** |
|
|
144
144
|
| RRule | **5.9x-63.7x** |
|
|
145
|
-
| Timezone | **1.0x-896.7x**
|
|
145
|
+
| Timezone | **1.0x-896.7x** |
|
|
146
146
|
| RelativeDelta | **2.0x-28.1x** |
|
|
147
147
|
| Easter | **5.0x-7.3x** |
|
|
148
148
|
|
|
@@ -120,7 +120,7 @@ Benchmarks compare the original `python-dateutil` (PyPI) and the Rust extension
|
|
|
120
120
|
| Parser (parse) | **19.5x-36.0x** |
|
|
121
121
|
| Parser (isoparse) | **13.0x-38.4x** |
|
|
122
122
|
| RRule | **5.9x-63.7x** |
|
|
123
|
-
| Timezone | **1.0x-896.7x**
|
|
123
|
+
| Timezone | **1.0x-896.7x** |
|
|
124
124
|
| RelativeDelta | **2.0x-28.1x** |
|
|
125
125
|
| Easter | **5.0x-7.3x** |
|
|
126
126
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "dateutil"
|
|
3
|
+
version = "0.1.2"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
license = "MIT"
|
|
6
|
+
description = "Fast date utility library — parser, relativedelta, rrule, timezone (Rust reimplementation of python-dateutil)"
|
|
7
|
+
repository = "https://github.com/wakita181009/dateutil-rs"
|
|
8
|
+
keywords = ["date", "datetime", "parser", "rrule", "timezone"]
|
|
9
|
+
categories = ["date-and-time", "parser-implementations"]
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
|
|
12
|
+
[lib]
|
|
13
|
+
name = "dateutil"
|
|
14
|
+
crate-type = ["rlib"]
|
|
15
|
+
|
|
16
|
+
[dependencies]
|
|
17
|
+
bitflags = "2"
|
|
18
|
+
chrono = "0.4"
|
|
19
|
+
phf = { version = "0.13", features = ["macros"] }
|
|
20
|
+
rustc-hash = "2"
|
|
21
|
+
smallvec = "1.15"
|
|
22
|
+
thiserror = "2"
|
|
23
|
+
iana-time-zone = "0.1"
|
|
24
|
+
|
|
25
|
+
[dev-dependencies]
|
|
26
|
+
criterion = { version = "0.8", features = ["html_reports"] }
|
|
27
|
+
|
|
28
|
+
[[bench]]
|
|
29
|
+
name = "benchmarks"
|
|
30
|
+
harness = false
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# python-dateutil-rs
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/python-dateutil-rs/)
|
|
4
|
+
[](https://pypi.org/project/python-dateutil-rs/)
|
|
5
|
+
[](https://pypi.org/project/python-dateutil-rs/)
|
|
6
|
+
[](https://github.com/wakita181009/dateutil-rs/actions/workflows/ci.yml)
|
|
7
|
+
[](https://codecov.io/gh/wakita181009/dateutil-rs)
|
|
8
|
+
|
|
9
|
+
A high-performance Rust-backed port of [python-dateutil](https://github.com/dateutil/dateutil) (v2.9.0).
|
|
10
|
+
|
|
11
|
+
> **Status:** All core modules (easter, relativedelta, parser, rrule, tz) are rewritten in Rust via PyO3/maturin. The optimized `dateutil-core` + `dateutil-py` architecture delivers **2x-897x** speedups over python-dateutil.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **Drop-in replacement** for `python-dateutil` — same API, same behavior
|
|
16
|
+
- **Rust-accelerated:** easter, relativedelta, parser (`parse` / `isoparse`), rrule, tz, weekday
|
|
17
|
+
- **Optimized core:** zero-copy parser, PHF lookup tables, bitflag filters, buffer-reusing rrule
|
|
18
|
+
- **Comprehensive test suite** inherited from the original project
|
|
19
|
+
- **Benchmark infrastructure** for side-by-side performance comparison
|
|
20
|
+
- **Python 3.10-3.14** supported on Linux and macOS
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install python-dateutil-rs
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from dateutil_rs import (
|
|
32
|
+
parse, isoparse, relativedelta, rrule, rruleset, rrulestr,
|
|
33
|
+
easter, gettz, tzutc, tzoffset,
|
|
34
|
+
MONTHLY, MO, TU, WE, TH, FR, SA, SU,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# Parse date strings (zero-copy tokenizer)
|
|
38
|
+
dt = parse("2026-01-15T10:30:00+09:00")
|
|
39
|
+
|
|
40
|
+
# ISO-8601 strict parsing
|
|
41
|
+
dt = isoparse("2026-01-15T10:30:00")
|
|
42
|
+
|
|
43
|
+
# Relative deltas
|
|
44
|
+
next_month = dt + relativedelta(months=+1)
|
|
45
|
+
|
|
46
|
+
# Recurrence rules (buffer-reusing iterator)
|
|
47
|
+
monthly = rrule(MONTHLY, count=5, dtstart=parse("2026-01-01"))
|
|
48
|
+
dates = monthly.all()
|
|
49
|
+
dates = list(monthly) # also iterable
|
|
50
|
+
first = monthly[0] # indexing
|
|
51
|
+
subset = monthly[1:3] # slicing
|
|
52
|
+
n = monthly.count() # total occurrences
|
|
53
|
+
dt in monthly # membership test
|
|
54
|
+
|
|
55
|
+
# Timezones
|
|
56
|
+
tokyo = gettz("Asia/Tokyo")
|
|
57
|
+
utc = tzutc()
|
|
58
|
+
|
|
59
|
+
# Easter
|
|
60
|
+
easter_date = easter(2026)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Development
|
|
64
|
+
|
|
65
|
+
### Prerequisites
|
|
66
|
+
|
|
67
|
+
- Python 3.10+
|
|
68
|
+
- Rust toolchain
|
|
69
|
+
- [uv](https://github.com/astral-sh/uv) (recommended) or pip
|
|
70
|
+
|
|
71
|
+
### Setup
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
git clone https://github.com/wakita181009/dateutil-rs.git
|
|
75
|
+
cd dateutil-rs
|
|
76
|
+
uv sync --extra dev
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Building
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# Build the native extension
|
|
83
|
+
maturin develop --release
|
|
84
|
+
|
|
85
|
+
# Development build (faster compilation)
|
|
86
|
+
maturin develop -F python
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Running Tests
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Run the test suite
|
|
93
|
+
uv run pytest tests/ -x -q
|
|
94
|
+
|
|
95
|
+
# Run with coverage
|
|
96
|
+
uv run pytest tests/ --cov=dateutil_rs
|
|
97
|
+
|
|
98
|
+
# Run Rust tests
|
|
99
|
+
cargo test -p dateutil-core
|
|
100
|
+
cargo test --workspace
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Linting
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
uv run ruff check tests/ python/
|
|
107
|
+
uv run ruff format --check tests/ python/
|
|
108
|
+
uv run mypy python/
|
|
109
|
+
cargo clippy --workspace
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Benchmarks
|
|
113
|
+
|
|
114
|
+
Benchmarks compare the original `python-dateutil` (PyPI) and the Rust extension (`dateutil_rs`) using pytest-benchmark.
|
|
115
|
+
|
|
116
|
+
#### Summary (vs python-dateutil)
|
|
117
|
+
|
|
118
|
+
| Module | Speedup |
|
|
119
|
+
|--------|---------|
|
|
120
|
+
| Parser (parse) | **19.5x-36.0x** |
|
|
121
|
+
| Parser (isoparse) | **13.0x-38.4x** |
|
|
122
|
+
| RRule | **5.9x-63.7x** |
|
|
123
|
+
| Timezone | **1.0x-896.7x** |
|
|
124
|
+
| RelativeDelta | **2.0x-28.1x** |
|
|
125
|
+
| Easter | **5.0x-7.3x** |
|
|
126
|
+
|
|
127
|
+
> Measured on Apple Silicon (M-series), Python 3.13, release build. Full results: [benchmarks/RESULTS.md](benchmarks/RESULTS.md)
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# Install the original python-dateutil for comparison
|
|
131
|
+
uv pip install python-dateutil
|
|
132
|
+
|
|
133
|
+
# Run benchmarks
|
|
134
|
+
make bench
|
|
135
|
+
|
|
136
|
+
# Run and save results as JSON
|
|
137
|
+
make bench-save
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Project Structure
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
dateutil-rs/
|
|
144
|
+
├── Cargo.toml # Workspace root
|
|
145
|
+
├── pyproject.toml # Python project config (maturin)
|
|
146
|
+
├── crates/
|
|
147
|
+
│ ├── dateutil-core/ # Pure Rust optimized core (crates.io)
|
|
148
|
+
│ │ └── src/
|
|
149
|
+
│ │ ├── lib.rs # Crate root, public API
|
|
150
|
+
│ │ ├── common.rs # Weekday (MO-SU with N-th occurrence)
|
|
151
|
+
│ │ ├── easter.rs # Easter date calculations
|
|
152
|
+
│ │ ├── error.rs # Shared error types
|
|
153
|
+
│ │ ├── relativedelta.rs
|
|
154
|
+
│ │ ├── parser.rs # parse() entry point
|
|
155
|
+
│ │ ├── parser/ # tokenizer, parserinfo, isoparser
|
|
156
|
+
│ │ ├── rrule.rs # RRule entry point
|
|
157
|
+
│ │ ├── rrule/ # iter, parse (rrulestr), set
|
|
158
|
+
│ │ └── tz/ # tzutc, tzoffset, tzfile, tzlocal
|
|
159
|
+
│ └── dateutil-py/ # PyO3 binding layer → PyPI package
|
|
160
|
+
│ └── src/
|
|
161
|
+
│ ├── lib.rs # Module registration
|
|
162
|
+
│ ├── py.rs # Binding root + #[pymodule]
|
|
163
|
+
│ └── py/ # Per-module bindings (common, conv, easter, parser, relativedelta, rrule, tz)
|
|
164
|
+
├── python/dateutil_rs/ # Python package (maturin mixed layout)
|
|
165
|
+
│ ├── __init__.py # Re-exports from Rust native module
|
|
166
|
+
│ ├── _native.pyi # Type stubs for native module
|
|
167
|
+
│ ├── py.typed # PEP 561 marker
|
|
168
|
+
│ └── parser.py # parserinfo (Python subclass support)
|
|
169
|
+
├── tests/ # Python test suite
|
|
170
|
+
├── benchmarks/ # pytest-benchmark comparisons
|
|
171
|
+
├── .github/workflows/ # CI (ci.yml, publish.yml)
|
|
172
|
+
├── Makefile
|
|
173
|
+
└── LICENSE
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Crate Roles
|
|
177
|
+
|
|
178
|
+
| Crate | Purpose | PyO3 | Publish To |
|
|
179
|
+
|-------|---------|------|------------|
|
|
180
|
+
| `dateutil-core` | Pure Rust optimized core | No | crates.io |
|
|
181
|
+
| `dateutil-py` | PyO3 binding layer | Yes | PyPI (`python-dateutil-rs`) |
|
|
182
|
+
|
|
183
|
+
## Implementation Status
|
|
184
|
+
|
|
185
|
+
| Module | Status | Notes |
|
|
186
|
+
|--------|:------:|-------|
|
|
187
|
+
| common (Weekday) | ✅ | MO-SU constants with N-th occurrence |
|
|
188
|
+
| easter | ✅ | 5.0x-7.3x faster, 3 calendar methods |
|
|
189
|
+
| relativedelta | ✅ | 2.0x-28.1x faster |
|
|
190
|
+
| parser (parse) | ✅ | 19.5x-36.0x faster, zero-copy tokenizer, PHF lookups |
|
|
191
|
+
| parser (isoparse) | ✅ | 13.0x-38.4x faster |
|
|
192
|
+
| parser (parserinfo) | ✅ | Customizable via Python subclass |
|
|
193
|
+
| rrule / rruleset | ✅ | 5.9x-63.7x faster, bitflag filters, buffer reuse |
|
|
194
|
+
| rrulestr | ✅ | RFC 5545 string parsing |
|
|
195
|
+
| tz (tzutc, tzoffset, tzfile, tzlocal) | ✅ | 1.0x-896.7x faster |
|
|
196
|
+
| tz utilities (gettz, datetime_exists, etc.) | ✅ | gettz with caching |
|
|
197
|
+
|
|
198
|
+
## Roadmap
|
|
199
|
+
|
|
200
|
+
1. ~~Python-only phase — Pure Python port with full test coverage~~ ✅
|
|
201
|
+
2. ~~Rust core + PyO3 bindings — easter, relativedelta, parser, weekday~~ ✅
|
|
202
|
+
3. ~~Rust rrule — Rewrite recurrence rules in Rust~~ ✅
|
|
203
|
+
4. ~~Rust tz — Rewrite timezone support in Rust~~ ✅
|
|
204
|
+
5. ~~Optimized core — zero-copy parser, buffer-reusing rrule, consolidated architecture~~ ✅
|
|
205
|
+
6. **Release** — Publish dateutil-core to crates.io and python-dateutil-rs 1.0 to PyPI
|
|
206
|
+
|
|
207
|
+
## License
|
|
208
|
+
|
|
209
|
+
[MIT](LICENSE)
|
{python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/benches/benchmarks.rs
RENAMED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
use criterion::{criterion_group, criterion_main, Criterion};
|
|
2
2
|
use std::hint::black_box;
|
|
3
3
|
use chrono::NaiveDate;
|
|
4
|
-
use
|
|
5
|
-
use
|
|
6
|
-
use
|
|
7
|
-
use
|
|
8
|
-
use
|
|
9
|
-
use
|
|
10
|
-
use
|
|
11
|
-
use
|
|
4
|
+
use dateutil::common::Weekday;
|
|
5
|
+
use dateutil::easter::{easter, EasterMethod};
|
|
6
|
+
use dateutil::parser;
|
|
7
|
+
use dateutil::parser::tokenizer;
|
|
8
|
+
use dateutil::relativedelta::RelativeDelta;
|
|
9
|
+
use dateutil::rrule::{Frequency, Recurrence, RRuleBuilder};
|
|
10
|
+
use dateutil::rrule::parse::rrulestr;
|
|
11
|
+
use dateutil::rrule::set::RRuleSet;
|
|
12
12
|
|
|
13
13
|
fn bench_tokenizer(c: &mut Criterion) {
|
|
14
14
|
c.bench_function("tokenize_simple_date", |b| {
|
|
@@ -189,30 +189,6 @@ mod tests {
|
|
|
189
189
|
assert_eq!(wd_neg.to_string(), "SU(-100)");
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
#[test]
|
|
193
|
-
fn test_weekday_negative_one_n() {
|
|
194
|
-
// Last occurrence (e.g., last Friday of month)
|
|
195
|
-
let wd = FR.with_n(Some(-1));
|
|
196
|
-
assert_eq!(wd.n(), Some(-1));
|
|
197
|
-
assert_eq!(wd.to_string(), "FR(-1)");
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
#[test]
|
|
201
|
-
fn test_weekday_clone_copy() {
|
|
202
|
-
let wd = MO.with_n(Some(2));
|
|
203
|
-
let cloned = wd;
|
|
204
|
-
assert_eq!(wd, cloned); // Copy semantics — both usable
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
#[test]
|
|
208
|
-
fn test_weekday_boundary_values() {
|
|
209
|
-
// Weekday 0 (Monday) and 6 (Sunday) are boundaries
|
|
210
|
-
let mon = Weekday::new(0, Some(1)).unwrap();
|
|
211
|
-
let sun = Weekday::new(6, Some(-1)).unwrap();
|
|
212
|
-
assert_eq!(mon.weekday(), 0);
|
|
213
|
-
assert_eq!(sun.weekday(), 6);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
192
|
#[test]
|
|
217
193
|
fn test_weekday_all_invalid() {
|
|
218
194
|
for i in 7..=255 {
|
|
@@ -227,14 +203,6 @@ mod tests {
|
|
|
227
203
|
assert_ne!(a, b);
|
|
228
204
|
}
|
|
229
205
|
|
|
230
|
-
#[test]
|
|
231
|
-
fn test_weekday_eq_none_vs_zero() {
|
|
232
|
-
// n=0 is now rejected at construction time (Weekday::new).
|
|
233
|
-
// with_n() bypasses validation for internal use, so test display.
|
|
234
|
-
let a = MO.with_n(None);
|
|
235
|
-
assert_eq!(a.to_string(), "MO");
|
|
236
|
-
}
|
|
237
|
-
|
|
238
206
|
#[test]
|
|
239
207
|
fn test_weekday_hash_with_n() {
|
|
240
208
|
use std::collections::HashSet;
|
|
@@ -281,14 +249,6 @@ mod tests {
|
|
|
281
249
|
}
|
|
282
250
|
}
|
|
283
251
|
|
|
284
|
-
#[test]
|
|
285
|
-
fn test_weekday_equality_ignores_n_for_same_display() {
|
|
286
|
-
let a = MO.with_n(Some(1));
|
|
287
|
-
let b = MO.with_n(Some(-1));
|
|
288
|
-
assert_ne!(a, b);
|
|
289
|
-
assert_eq!(a.weekday(), b.weekday());
|
|
290
|
-
}
|
|
291
|
-
|
|
292
252
|
#[test]
|
|
293
253
|
fn test_weekday_hash_set_none() {
|
|
294
254
|
use std::collections::HashSet;
|
|
@@ -297,4 +257,17 @@ mod tests {
|
|
|
297
257
|
set.insert(MO.with_n(Some(1)));
|
|
298
258
|
assert_eq!(set.len(), 2);
|
|
299
259
|
}
|
|
260
|
+
|
|
261
|
+
#[test]
|
|
262
|
+
fn test_weekday_try_from_valid() {
|
|
263
|
+
let wd: Weekday = 3u8.try_into().unwrap();
|
|
264
|
+
assert_eq!(wd.weekday(), 3);
|
|
265
|
+
assert_eq!(wd.n(), None);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
#[test]
|
|
269
|
+
fn test_weekday_try_from_invalid() {
|
|
270
|
+
let result: Result<Weekday, _> = 7u8.try_into();
|
|
271
|
+
assert!(result.is_err());
|
|
272
|
+
}
|
|
300
273
|
}
|
|
@@ -71,30 +71,6 @@ mod tests {
|
|
|
71
71
|
use super::*;
|
|
72
72
|
use chrono::Datelike;
|
|
73
73
|
|
|
74
|
-
#[test]
|
|
75
|
-
fn test_western_2024() {
|
|
76
|
-
assert_eq!(
|
|
77
|
-
easter(2024, EasterMethod::Western).unwrap(),
|
|
78
|
-
NaiveDate::from_ymd_opt(2024, 3, 31).unwrap()
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
#[test]
|
|
83
|
-
fn test_orthodox_2024() {
|
|
84
|
-
assert_eq!(
|
|
85
|
-
easter(2024, EasterMethod::Orthodox).unwrap(),
|
|
86
|
-
NaiveDate::from_ymd_opt(2024, 5, 5).unwrap()
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
#[test]
|
|
91
|
-
fn test_julian_326() {
|
|
92
|
-
assert_eq!(
|
|
93
|
-
easter(326, EasterMethod::Julian).unwrap(),
|
|
94
|
-
NaiveDate::from_ymd_opt(326, 4, 3).unwrap()
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
74
|
#[test]
|
|
99
75
|
fn test_invalid_method_from_i32() {
|
|
100
76
|
assert!(matches!(
|
|
@@ -107,18 +83,6 @@ mod tests {
|
|
|
107
83
|
));
|
|
108
84
|
}
|
|
109
85
|
|
|
110
|
-
#[test]
|
|
111
|
-
fn test_invalid_year() {
|
|
112
|
-
assert!(matches!(
|
|
113
|
-
easter(0, EasterMethod::Western),
|
|
114
|
-
Err(EasterError::InvalidYear(0))
|
|
115
|
-
));
|
|
116
|
-
assert!(matches!(
|
|
117
|
-
easter(-1, EasterMethod::Western),
|
|
118
|
-
Err(EasterError::InvalidYear(-1))
|
|
119
|
-
));
|
|
120
|
-
}
|
|
121
|
-
|
|
122
86
|
#[test]
|
|
123
87
|
fn test_western_range_1990_2050() {
|
|
124
88
|
let expected: Vec<(i32, u32, u32)> = vec![
|
|
@@ -347,14 +311,6 @@ mod tests {
|
|
|
347
311
|
);
|
|
348
312
|
}
|
|
349
313
|
|
|
350
|
-
#[test]
|
|
351
|
-
fn test_orthodox_boundary_exact_1600() {
|
|
352
|
-
let d1600 = easter(1600, EasterMethod::Orthodox).unwrap();
|
|
353
|
-
let d1601 = easter(1601, EasterMethod::Orthodox).unwrap();
|
|
354
|
-
assert!((3..=5).contains(&d1600.month()));
|
|
355
|
-
assert!((3..=5).contains(&d1601.month()));
|
|
356
|
-
}
|
|
357
|
-
|
|
358
314
|
#[test]
|
|
359
315
|
fn test_easter_always_sunday_western_wide_range() {
|
|
360
316
|
// Western (Gregorian) Easter is always Sunday across a wide year range
|
{python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/parser/isoparser.rs
RENAMED
|
@@ -591,4 +591,25 @@ mod tests {
|
|
|
591
591
|
fn test_iso_leap_second_rejected() {
|
|
592
592
|
assert!(isoparse("2024-01-15T23:59:60").is_err());
|
|
593
593
|
}
|
|
594
|
+
|
|
595
|
+
#[test]
|
|
596
|
+
fn test_iso_hhmm_colon_format() {
|
|
597
|
+
let dt = isoparse("2024-01-15T10:30").unwrap();
|
|
598
|
+
assert_eq!(dt.hour(), 10);
|
|
599
|
+
assert_eq!(dt.minute(), 30);
|
|
600
|
+
assert_eq!(dt.second(), 0);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
#[test]
|
|
604
|
+
fn test_iso_unrecognized_time_format() {
|
|
605
|
+
let result = isoparse("2024-01-15TX");
|
|
606
|
+
assert!(result.is_err());
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
#[test]
|
|
610
|
+
fn test_iso_fractional_dot_only() {
|
|
611
|
+
let dt = isoparse("2024-01-15T10:30:45.").unwrap();
|
|
612
|
+
assert_eq!(dt.second(), 45);
|
|
613
|
+
assert_eq!(dt.nanosecond(), 0);
|
|
614
|
+
}
|
|
594
615
|
}
|
{python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/parser/parserinfo.rs
RENAMED
|
@@ -315,4 +315,44 @@ mod tests {
|
|
|
315
315
|
assert_eq!(do_tzoffset("est", Some(&info)), Some(-18000));
|
|
316
316
|
assert_eq!(do_month("January", Some(&info)), Some(1));
|
|
317
317
|
}
|
|
318
|
+
|
|
319
|
+
#[test]
|
|
320
|
+
fn test_parserinfo_hms() {
|
|
321
|
+
let info = ParserInfo::default();
|
|
322
|
+
assert_eq!(info.hms("hour"), Some(0));
|
|
323
|
+
assert_eq!(info.hms("HOURS"), Some(0));
|
|
324
|
+
assert_eq!(info.hms("minute"), Some(1));
|
|
325
|
+
assert_eq!(info.hms("s"), Some(2));
|
|
326
|
+
assert_eq!(info.hms("xyz"), None);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
#[test]
|
|
330
|
+
fn test_parserinfo_ampm() {
|
|
331
|
+
let info = ParserInfo::default();
|
|
332
|
+
assert_eq!(info.ampm("am"), Some(0));
|
|
333
|
+
assert_eq!(info.ampm("AM"), Some(0));
|
|
334
|
+
assert_eq!(info.ampm("p"), Some(1));
|
|
335
|
+
assert_eq!(info.ampm("PM"), Some(1));
|
|
336
|
+
assert_eq!(info.ampm("xyz"), None);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
#[test]
|
|
340
|
+
fn test_parserinfo_pertain() {
|
|
341
|
+
let info = ParserInfo::default();
|
|
342
|
+
assert!(info.pertain("of"));
|
|
343
|
+
assert!(info.pertain("OF"));
|
|
344
|
+
assert!(!info.pertain("xyz"));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
#[test]
|
|
348
|
+
fn test_dispatch_with_some_info() {
|
|
349
|
+
let info = ParserInfo::default();
|
|
350
|
+
assert!(do_jump(",", Some(&info)));
|
|
351
|
+
assert_eq!(do_weekday("Monday", Some(&info)), Some(0));
|
|
352
|
+
assert_eq!(do_month("January", Some(&info)), Some(1));
|
|
353
|
+
assert_eq!(do_hms("hour", Some(&info)), Some(0));
|
|
354
|
+
assert_eq!(do_ampm("AM", Some(&info)), Some(0));
|
|
355
|
+
assert!(do_pertain("of", Some(&info)));
|
|
356
|
+
assert!(do_utczone("UTC", Some(&info)));
|
|
357
|
+
}
|
|
318
358
|
}
|