Skip to content

Commit c959789

Browse files
committed
fix: wip, added a bunch more tests
1 parent 4af4625 commit c959789

File tree

3 files changed

+86
-50
lines changed

3 files changed

+86
-50
lines changed

src/input/datetime.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,40 @@ impl<'a> EitherTimedelta<'a> {
109109
Self::Raw(duration) => duration_as_pytimedelta(py, duration),
110110
}
111111
}
112+
113+
pub fn total_seconds(&self) -> PyResult<f64> {
114+
match self {
115+
Self::Raw(timedelta) => {
116+
let mut days: f64 = timedelta.day as f64;
117+
let mut seconds: f64 = timedelta.second as f64;
118+
let mut microseconds: f64 = timedelta.microsecond as f64;
119+
let mut total_seconds: f64 = if !timedelta.positive {
120+
-1.0 * (86400.0 * days + seconds + microseconds / 1_000_000.0)
121+
} else {
122+
86400.0 * days + seconds + microseconds / 1_000_000.0
123+
};
124+
Ok(total_seconds)
125+
}
126+
Self::PyExact(py_timedelta) => {
127+
let mut days: f64 = py_timedelta.get_days() as f64; // -999999999 to 999999999
128+
let mut seconds: f64 = py_timedelta.get_seconds() as f64; // 0 through 86399
129+
let mut microseconds: f64 = py_timedelta.get_microseconds() as f64; // 0 through 999999
130+
let positive = days >= 0.0;
131+
let total_seconds: f64 = if !positive {
132+
86400.0 * days + seconds + microseconds / 1_000_000.0
133+
} else {
134+
86400.0 * days + seconds + microseconds / 1_000_000.0
135+
};
136+
Ok(total_seconds)
137+
}
138+
Self::PySubclass(py_timedelta) => {
139+
let total_seconds: f64 = py_timedelta
140+
.call_method0(intern!(py_timedelta.py(), "total_seconds"))?
141+
.extract()?;
142+
Ok(total_seconds)
143+
}
144+
}
145+
}
112146
}
113147

114148
impl<'a> TryFrom<&'_ Bound<'a, PyAny>> for EitherTimedelta<'a> {

src/serializers/config.rs

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,6 @@ serialization_mode! {
110110
}
111111

112112
impl TimedeltaMode {
113-
fn total_seconds<'py>(py_timedelta: &Bound<'py, PyDelta>) -> PyResult<Bound<'py, PyAny>> {
114-
py_timedelta.call_method0(intern!(py_timedelta.py(), "total_seconds"))
115-
}
116-
117113
pub fn either_delta_to_json(self, py: Python, either_delta: &EitherTimedelta) -> PyResult<PyObject> {
118114
match self {
119115
Self::Iso8601 => {
@@ -123,15 +119,13 @@ impl TimedeltaMode {
123119
Self::Float | Self::SecondsFloat => {
124120
// convert to int via a py timedelta not duration since we know this this case the input would have
125121
// been a py timedelta
126-
let py_timedelta = either_delta.try_into_py(py)?;
127-
let seconds = Self::total_seconds(&py_timedelta)?;
122+
let seconds: f64 = either_delta.total_seconds()?;
128123
Ok(seconds.into_py(py))
129124
}
130125
Self::MillisecondsFloat => {
131126
// convert to int via a py timedelta not duration since we know this this case the input would have
132127
// been a py timedelta
133-
let py_timedelta = either_delta.try_into_py(py)?;
134-
let seconds: f64 = Self::total_seconds(&py_timedelta)?.extract()?;
128+
let seconds: f64 = either_delta.total_seconds()?;
135129
let object: Bound<PyFloat> = PyFloat::new_bound(py, seconds * 1000.0);
136130
Ok(object.into_py(py))
137131
}
@@ -145,13 +139,11 @@ impl TimedeltaMode {
145139
Ok(d.to_string().into())
146140
}
147141
Self::Float | Self::SecondsFloat => {
148-
let py_timedelta = either_delta.try_into_py(py)?;
149-
let seconds: f64 = Self::total_seconds(&py_timedelta)?.extract()?;
142+
let seconds: f64 = either_delta.total_seconds()?;
150143
Ok(seconds.to_string().into())
151144
}
152145
Self::MillisecondsFloat => {
153-
let py_timedelta = either_delta.try_into_py(py)?;
154-
let seconds: f64 = Self::total_seconds(&py_timedelta)?.extract()?;
146+
let seconds: f64 = either_delta.total_seconds()?;
155147
let milliseconds: f64 = seconds * 1000.0;
156148
Ok(milliseconds.to_string().into())
157149
}
@@ -170,15 +162,11 @@ impl TimedeltaMode {
170162
serializer.serialize_str(&d.to_string())
171163
}
172164
Self::Float | Self::SecondsFloat => {
173-
let py_timedelta = either_delta.try_into_py(py).map_err(py_err_se_err)?;
174-
let seconds = Self::total_seconds(&py_timedelta).map_err(py_err_se_err)?;
175-
let seconds: f64 = seconds.extract().map_err(py_err_se_err)?;
165+
let seconds: f64 = either_delta.total_seconds().map_err(py_err_se_err)?;
176166
serializer.serialize_f64(seconds)
177167
}
178168
Self::MillisecondsFloat => {
179-
let py_timedelta = either_delta.try_into_py(py).map_err(py_err_se_err)?;
180-
let seconds = Self::total_seconds(&py_timedelta).map_err(py_err_se_err)?;
181-
let seconds: f64 = seconds.extract().map_err(py_err_se_err)?;
169+
let seconds: f64 = either_delta.total_seconds().map_err(py_err_se_err)?;
182170
let milliseconds: f64 = seconds * 1000.0;
183171
serializer.serialize_f64(milliseconds)
184172
}

tests/serializers/test_any.py

Lines changed: 46 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -216,40 +216,54 @@ def test_any_config_timedelta_float_negative(mode):
216216
assert s.to_json({one_half_s: 'foo'}) == b'{"-1.5":"foo"}'
217217

218218

219-
def test_any_config_timedelta_millisecond():
220-
s = SchemaSerializer(core_schema.any_schema(), config={'ser_json_timedelta': 'milliseconds_float'})
221-
h2 = timedelta(hours=2)
222-
assert s.to_python(h2) == h2
223-
assert s.to_python(h2, mode='json') == 7200000.0
224-
assert s.to_json(h2) == b'7200000.0'
225-
226-
assert s.to_python({h2: 'foo'}) == {h2: 'foo'}
227-
assert s.to_python({h2: 'foo'}, mode='json') == {'7200000': 'foo'}
228-
assert s.to_json({h2: 'foo'}) == b'{"7200000":"foo"}'
229-
230-
231-
def test_any_config_timedelta_millisecond_fraction():
232-
s = SchemaSerializer(core_schema.any_schema(), config={'ser_json_timedelta': 'milliseconds_float'})
233-
h2 = timedelta(seconds=1.5)
234-
assert s.to_python(h2) == h2
235-
assert s.to_python(h2, mode='json') == 1500.0
236-
assert s.to_json(h2) == b'1500.0'
237-
238-
assert s.to_python({h2: 'foo'}) == {h2: 'foo'}
239-
assert s.to_python({h2: 'foo'}, mode='json') == {'1500': 'foo'}
240-
assert s.to_json({h2: 'foo'}) == b'{"1500":"foo"}'
241-
242-
243-
def test_any_config_timedelta_millisecond_negative():
219+
@pytest.mark.parametrize(
220+
'td,expected_to_python,expected_to_json,expected_to_python_dict,expected_to_json_dict',
221+
[
222+
(timedelta(hours=2), 7200000.0, b'7200000.0', {'7200000': 'foo'}, b'{"7200000":"foo"}'),
223+
(timedelta(hours=-2), -7200000.0, b'-7200000.0', {'-7200000': 'foo'}, b'{"-7200000":"foo"}'),
224+
(timedelta(seconds=1.5), 1500.0, b'1500.0', {'1500': 'foo'}, b'{"1500":"foo"}'),
225+
(timedelta(seconds=-1.5), -1500.0, b'-1500.0', {'-1500': 'foo'}, b'{"-1500":"foo"}'),
226+
(timedelta(microseconds=1), 0.001, b'0.001', {'0.001': 'foo'}, b'{"0.001":"foo"}'),
227+
(timedelta(microseconds=-1), -0.001, b'-0.001', {'-0.001': 'foo'}, b'{"-0.001":"foo"}'),
228+
(timedelta(days=1), 86400000.0, b'86400000.0', {'86400000': 'foo'}, b'{"86400000":"foo"}'),
229+
(timedelta(days=-1), -86400000.0, b'-86400000.0', {'-86400000': 'foo'}, b'{"-86400000":"foo"}'),
230+
(timedelta(days=1, seconds=1), 86401000.0, b'86401000.0', {'86401000': 'foo'}, b'{"86401000":"foo"}'),
231+
(timedelta(days=-1, seconds=-1), -86401000.0, b'-86401000.0', {'-86401000': 'foo'}, b'{"-86401000":"foo"}'),
232+
(timedelta(days=1, seconds=-1), 86399000.0, b'86399000.0', {'86399000': 'foo'}, b'{"86399000":"foo"}'),
233+
(
234+
timedelta(days=1, seconds=1, microseconds=1),
235+
86401000.001,
236+
b'86401000.001',
237+
{'86401000.001': 'foo'},
238+
b'{"86401000.001":"foo"}',
239+
),
240+
(
241+
timedelta(days=-1, seconds=-1, microseconds=-1),
242+
-86401000.001,
243+
b'-86401000.001',
244+
{'-86401000.001': 'foo'},
245+
b'{"-86401000.001":"foo"}',
246+
),
247+
(
248+
timedelta(days=-1, seconds=-1, microseconds=-1),
249+
-86401000.001,
250+
b'-86401000.001',
251+
{'-86401000.001': 'foo'},
252+
b'{"-86401000.001":"foo"}',
253+
),
254+
],
255+
)
256+
def test_any_config_timedelta_millisecond(
257+
td: timedelta, expected_to_python, expected_to_json, expected_to_python_dict, expected_to_json_dict
258+
):
244259
s = SchemaSerializer(core_schema.any_schema(), config={'ser_json_timedelta': 'milliseconds_float'})
245-
h2 = timedelta(seconds=-1.5)
246-
assert s.to_python(h2) == h2
247-
assert s.to_python(h2, mode='json') == -1500.0
248-
assert s.to_json(h2) == b'-1500.0'
260+
assert s.to_python(td) == td
261+
assert s.to_python(td, mode='json') == expected_to_python
262+
assert s.to_json(td) == expected_to_json
249263

250-
assert s.to_python({h2: 'foo'}) == {h2: 'foo'}
251-
assert s.to_python({h2: 'foo'}, mode='json') == {'-1500': 'foo'}
252-
assert s.to_json({h2: 'foo'}) == b'{"-1500":"foo"}'
264+
assert s.to_python({td: 'foo'}) == {td: 'foo'}
265+
assert s.to_python({td: 'foo'}, mode='json') == expected_to_python_dict
266+
assert s.to_json({td: 'foo'}) == expected_to_json_dict
253267

254268

255269
def test_recursion(any_serializer):

0 commit comments

Comments
 (0)