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.
Files changed (47) hide show
  1. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/Cargo.lock +11 -4
  2. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/PKG-INFO +2 -2
  3. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/README.md +1 -1
  4. python_dateutil_rs-0.1.2/crates/dateutil-core/Cargo.toml +30 -0
  5. python_dateutil_rs-0.1.2/crates/dateutil-core/README.md +209 -0
  6. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/benches/benchmarks.rs +8 -8
  7. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/common.rs +13 -40
  8. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/easter.rs +0 -44
  9. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/parser/isoparser.rs +21 -0
  10. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/parser/parserinfo.rs +40 -0
  11. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/parser.rs +180 -82
  12. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/relativedelta.rs +152 -0
  13. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/rrule/parse.rs +30 -0
  14. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/rrule/set.rs +38 -0
  15. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/rrule.rs +221 -0
  16. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/tz/offset.rs +12 -0
  17. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/tz.rs +109 -32
  18. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-py/Cargo.toml +2 -2
  19. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-py/src/py/common.rs +31 -3
  20. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-py/src/py/conv.rs +20 -0
  21. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-py/src/py/easter.rs +1 -1
  22. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-py/src/py/parser.rs +2 -2
  23. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-py/src/py/relativedelta.rs +3 -3
  24. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-py/src/py/rrule.rs +179 -29
  25. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-py/src/py/tz.rs +51 -11
  26. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/pyproject.toml +1 -1
  27. python_dateutil_rs-0.1.2/python/dateutil_rs/easter.py +15 -0
  28. python_dateutil_rs-0.1.2/python/dateutil_rs/relativedelta.py +5 -0
  29. python_dateutil_rs-0.1.2/python/dateutil_rs/rrule.py +27 -0
  30. python_dateutil_rs-0.1.2/python/dateutil_rs/tz.py +23 -0
  31. python_dateutil_rs-0.1.0/crates/dateutil-core/Cargo.toml +0 -25
  32. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/Cargo.toml +0 -0
  33. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/LICENSE +0 -0
  34. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/CLAUDE.md +0 -0
  35. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/error.rs +0 -0
  36. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/lib.rs +0 -0
  37. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/parser/tokenizer.rs +0 -0
  38. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/rrule/iter.rs +0 -0
  39. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/tz/file.rs +0 -0
  40. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/tz/local.rs +0 -0
  41. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-core/src/tz/utc.rs +0 -0
  42. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-py/src/lib.rs +0 -0
  43. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/crates/dateutil-py/src/py.rs +0 -0
  44. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/python/dateutil_rs/__init__.py +0 -0
  45. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/python/dateutil_rs/_native.pyi +0 -0
  46. {python_dateutil_rs-0.1.0 → python_dateutil_rs-0.1.2}/python/dateutil_rs/parser.py +0 -0
  47. {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-core"
223
- version = "0.1.0"
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.0"
237
+ version = "0.1.2"
237
238
  dependencies = [
238
239
  "chrono",
239
- "dateutil-core",
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.0
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
+ [![PyPI](https://img.shields.io/pypi/v/python-dateutil-rs.svg?style=flat-square)](https://pypi.org/project/python-dateutil-rs/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/python-dateutil-rs.svg?style=flat-square)](https://pypi.org/project/python-dateutil-rs/)
5
+ [![License](https://img.shields.io/pypi/l/python-dateutil-rs.svg?style=flat-square)](https://pypi.org/project/python-dateutil-rs/)
6
+ [![CI](https://github.com/wakita181009/dateutil-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/wakita181009/dateutil-rs/actions/workflows/ci.yml)
7
+ [![Coverage](https://codecov.io/gh/wakita181009/dateutil-rs/branch/main/graph/badge.svg)](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)
@@ -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 dateutil_core::common::Weekday;
5
- use dateutil_core::easter::{easter, EasterMethod};
6
- use dateutil_core::parser;
7
- use dateutil_core::parser::tokenizer;
8
- use dateutil_core::relativedelta::RelativeDelta;
9
- use dateutil_core::rrule::{Frequency, Recurrence, RRuleBuilder};
10
- use dateutil_core::rrule::parse::rrulestr;
11
- use dateutil_core::rrule::set::RRuleSet;
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
@@ -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
  }
@@ -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
  }