Skip to content

Commit 15adfc7

Browse files
authored
fix ValueError on year zero (#1583)
1 parent 39435c2 commit 15adfc7

File tree

5 files changed

+58
-34
lines changed

5 files changed

+58
-34
lines changed

src/input/datetime.rs

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -47,20 +47,26 @@ pub fn pydate_as_date(py_date: &Bound<'_, PyAny>) -> PyResult<Date> {
4747
})
4848
}
4949

50-
impl<'py> IntoPyObject<'py> for EitherDate<'py> {
51-
type Target = PyDate;
52-
type Output = Bound<'py, PyDate>;
53-
type Error = PyErr;
54-
55-
fn into_pyobject(self, py: Python<'py>) -> PyResult<Self::Output> {
50+
impl<'py> EitherDate<'py> {
51+
pub fn try_into_py(&self, py: Python<'py>, input: &(impl Input<'py> + ?Sized)) -> ValResult<PyObject> {
5652
match self {
57-
Self::Raw(date) => PyDate::new(py, date.year.into(), date.month, date.day),
58-
Self::Py(date) => Ok(date),
53+
Self::Raw(date) => {
54+
if date.year == 0 {
55+
return Err(ValError::new(
56+
ErrorType::DateParsing {
57+
error: Cow::Borrowed("year 0 is out of range"),
58+
context: None,
59+
},
60+
input,
61+
));
62+
};
63+
let py_date = PyDate::new(py, date.year.into(), date.month, date.day)?;
64+
Ok(py_date.into())
65+
}
66+
Self::Py(py_date) => Ok(py_date.clone().into()),
5967
}
6068
}
61-
}
6269

63-
impl EitherDate<'_> {
6470
pub fn as_raw(&self) -> PyResult<Date> {
6571
match self {
6672
Self::Raw(date) => Ok(date.clone()),
@@ -278,30 +284,36 @@ pub fn pydatetime_as_datetime(py_dt: &Bound<'_, PyAny>) -> PyResult<DateTime> {
278284
})
279285
}
280286

281-
impl<'py> IntoPyObject<'py> for EitherDateTime<'py> {
282-
type Target = PyDateTime;
283-
type Output = Bound<'py, PyDateTime>;
284-
type Error = PyErr;
285-
286-
fn into_pyobject(self, py: Python<'py>) -> PyResult<Self::Output> {
287+
impl<'py> EitherDateTime<'py> {
288+
pub fn try_into_py(&self, py: Python<'py>, input: &(impl Input<'py> + ?Sized)) -> ValResult<PyObject> {
287289
match self {
288-
Self::Raw(dt) => PyDateTime::new(
289-
py,
290-
dt.date.year.into(),
291-
dt.date.month,
292-
dt.date.day,
293-
dt.time.hour,
294-
dt.time.minute,
295-
dt.time.second,
296-
dt.time.microsecond,
297-
time_as_tzinfo(py, &dt.time)?.as_ref(),
298-
),
299-
Self::Py(dt) => Ok(dt),
290+
Self::Raw(dt) => {
291+
if dt.date.year == 0 {
292+
return Err(ValError::new(
293+
ErrorType::DatetimeParsing {
294+
error: Cow::Borrowed("year 0 is out of range"),
295+
context: None,
296+
},
297+
input,
298+
));
299+
};
300+
let py_dt = PyDateTime::new(
301+
py,
302+
dt.date.year.into(),
303+
dt.date.month,
304+
dt.date.day,
305+
dt.time.hour,
306+
dt.time.minute,
307+
dt.time.second,
308+
dt.time.microsecond,
309+
time_as_tzinfo(py, &dt.time)?.as_ref(),
310+
)?;
311+
Ok(py_dt.into())
312+
}
313+
Self::Py(py_dt) => Ok(py_dt.clone().into()),
300314
}
301315
}
302-
}
303316

304-
impl EitherDateTime<'_> {
305317
pub fn as_raw(&self) -> PyResult<DateTime> {
306318
match self {
307319
Self::Raw(dt) => Ok(dt.clone()),

src/validators/date.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use pyo3::intern;
22
use pyo3::prelude::*;
33
use pyo3::types::{PyDate, PyDict, PyString};
4-
use pyo3::IntoPyObjectExt;
54
use speedate::{Date, Time};
65
use strum::EnumMessage;
76

@@ -98,7 +97,7 @@ impl Validator for DateValidator {
9897
}
9998
}
10099
}
101-
Ok(date.into_py_any(py)?)
100+
date.try_into_py(py, input)
102101
}
103102

104103
fn get_name(&self) -> &str {

src/validators/datetime.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use pyo3::intern;
22
use pyo3::prelude::*;
33
use pyo3::sync::GILOnceCell;
44
use pyo3::types::{PyDict, PyString};
5-
use pyo3::IntoPyObjectExt;
65
use speedate::{DateTime, Time};
76
use std::cmp::Ordering;
87
use strum::EnumMessage;
@@ -131,7 +130,7 @@ impl Validator for DateTimeValidator {
131130
tz_constraint.tz_check(speedate_dt.time.tz_offset, input)?;
132131
}
133132
}
134-
Ok(datetime.into_py_any(py)?)
133+
datetime.try_into_py(py, input)
135134
}
136135

137136
fn get_name(&self) -> &str {

tests/validators/test_date.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@
6464
),
6565
pytest.param('-', Err('Input should be a valid date or datetime, input is too short'), id='minus'),
6666
pytest.param('+', Err('Input should be a valid date or datetime, input is too short'), id='pus'),
67+
pytest.param('0001-01-01', date(1, 1, 1), id='min-date'),
68+
pytest.param(
69+
'0000-12-31',
70+
Err('Input should be a valid date in the format YYYY-MM-DD, year 0 is out of range [type=date_parsing,'),
71+
id='year-0',
72+
),
6773
],
6874
)
6975
def test_date(input_value, expected):

tests/validators/test_datetime.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@
5353
'Input should be a valid datetime or date, day value is outside expected range [type=datetime_from_date_parsing,'
5454
),
5555
),
56+
(
57+
'0001-01-01T00:00:00.000000Z',
58+
datetime(1, 1, 1, tzinfo=timezone.utc),
59+
),
60+
(
61+
'0000-12-31T23:59:59.999999Z',
62+
Err('Input should be a valid datetime, year 0 is out of range [type=datetime_parsing,'),
63+
),
5664
],
5765
)
5866
def test_datetime(input_value, expected):

0 commit comments

Comments
 (0)