Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions python/pydantic_core/_pydantic_core.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ def to_json(
exclude_none: bool = False,
round_trip: bool = False,
timedelta_mode: Literal['iso8601', 'float'] = 'iso8601',
temporal_mode: Literal['iso8601', 'seconds', 'milliseconds'] = 'iso8601',
bytes_mode: Literal['utf8', 'base64', 'hex'] = 'utf8',
inf_nan_mode: Literal['null', 'constants', 'strings'] = 'constants',
serialize_unknown: bool = False,
Expand All @@ -425,6 +426,8 @@ def to_json(
exclude_none: Whether to exclude fields that have a value of `None`.
round_trip: Whether to enable serialization and validation round-trip support.
timedelta_mode: How to serialize `timedelta` objects, either `'iso8601'` or `'float'`.
temporal_mode: How to serialize datetime like objects, either
`'iso8601'` or `'seconds'` or `'milliseconds'`.
bytes_mode: How to serialize `bytes` objects, either `'utf8'`, `'base64'`, or `'hex'`.
inf_nan_mode: How to serialize `Infinity`, `-Infinity` and `NaN` values, either `'null'`, `'constants'`, or `'strings'`.
serialize_unknown: Attempt to serialize unknown types, `str(value)` will be used, if that fails
Expand Down Expand Up @@ -483,6 +486,7 @@ def to_jsonable_python(
exclude_none: bool = False,
round_trip: bool = False,
timedelta_mode: Literal['iso8601', 'float'] = 'iso8601',
temporal_mode: Literal['iso8601', 'seconds', 'milliseconds'] = 'iso8601',
bytes_mode: Literal['utf8', 'base64', 'hex'] = 'utf8',
inf_nan_mode: Literal['null', 'constants', 'strings'] = 'constants',
serialize_unknown: bool = False,
Expand All @@ -504,6 +508,8 @@ def to_jsonable_python(
exclude_none: Whether to exclude fields that have a value of `None`.
round_trip: Whether to enable serialization and validation round-trip support.
timedelta_mode: How to serialize `timedelta` objects, either `'iso8601'` or `'float'`.
temporal_mode: How to serialize datetime like objects, either
`'iso8601'` or `'seconds'` or `'milliseconds'`.
bytes_mode: How to serialize `bytes` objects, either `'utf8'`, `'base64'`, or `'hex'`.
inf_nan_mode: How to serialize `Infinity`, `-Infinity` and `NaN` values, either `'null'`, `'constants'`, or `'strings'`.
serialize_unknown: Attempt to serialize unknown types, `str(value)` will be used, if that fails
Expand Down
1 change: 1 addition & 0 deletions python/pydantic_core/core_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ class CoreConfig(TypedDict, total=False):
allow_inf_nan: bool # default: True
# the config options are used to customise serialization to JSON
ser_json_timedelta: Literal['iso8601', 'float'] # default: 'iso8601'
ser_json_temporal: Literal['iso8601', 'seconds', 'milliseconds'] # default: 'iso8601'
ser_json_bytes: Literal['utf8', 'base64', 'hex'] # default: 'utf8'
ser_json_inf_nan: Literal['null', 'constants', 'strings'] # default: 'null'
val_json_bytes: Literal['utf8', 'base64', 'hex'] # default: 'utf8'
Expand Down
2 changes: 1 addition & 1 deletion src/errors/validation_exception.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ impl ValidationError {
include_context: bool,
include_input: bool,
) -> PyResult<Bound<'py, PyString>> {
let state = SerializationState::new("iso8601", "utf8", "constants")?;
let state = SerializationState::new("iso8601", "iso8601", "utf8", "constants")?;
let extra = state.extra(py, &SerMode::Json, None, false, false, true, None, false, None);
let serializer = ValidationErrorSerializer {
py,
Expand Down
87 changes: 87 additions & 0 deletions src/input/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,93 @@ impl EitherTimedelta<'_> {
Self::PySubclass(py_timedelta) => pytimedelta_subclass_as_duration(py_timedelta),
}
}

pub fn total_seconds(&self) -> PyResult<f64> {
match self {
Self::Raw(timedelta) => {
let mut days: i64 = i64::from(timedelta.day);
let mut seconds: i64 = i64::from(timedelta.second);
let mut microseconds = i64::from(timedelta.microsecond);
if !timedelta.positive {
days = -days;
seconds = -seconds;
microseconds = -microseconds;
}

let days_seconds = (86_400 * days) + seconds;
if let Some(days_seconds_as_micros) = days_seconds.checked_mul(1_000_000) {
let total_microseconds = days_seconds_as_micros + microseconds;
Ok(total_microseconds as f64 / 1_000_000.0)
} else {
// Fall back to floating-point operations if the multiplication overflows
let total_seconds = days_seconds as f64 + microseconds as f64 / 1_000_000.0;
Ok(total_seconds)
}
}
Self::PyExact(py_timedelta) => {
let days: i64 = py_timedelta.get_days().into(); // -999999999 to 999999999
let seconds: i64 = py_timedelta.get_seconds().into(); // 0 through 86399
let microseconds = py_timedelta.get_microseconds(); // 0 through 999999
let days_seconds = (86_400 * days) + seconds;
if let Some(days_seconds_as_micros) = days_seconds.checked_mul(1_000_000) {
let total_microseconds = days_seconds_as_micros + i64::from(microseconds);
Ok(total_microseconds as f64 / 1_000_000.0)
} else {
// Fall back to floating-point operations if the multiplication overflows
let total_seconds = days_seconds as f64 + f64::from(microseconds) / 1_000_000.0;
Ok(total_seconds)
}
}
Self::PySubclass(py_timedelta) => py_timedelta
.call_method0(intern!(py_timedelta.py(), "total_seconds"))?
.extract(),
}
}

pub fn total_milliseconds(&self) -> PyResult<f64> {
match self {
Self::Raw(timedelta) => {
let mut days: i64 = i64::from(timedelta.day);
let mut seconds: i64 = i64::from(timedelta.second);
let mut microseconds = i64::from(timedelta.microsecond);
if !timedelta.positive {
days = -days;
seconds = -seconds;
microseconds = -microseconds;
}

let days_seconds = (86_400 * days) + seconds;
if let Some(days_seconds_as_micros) = days_seconds.checked_mul(1_000_000) {
let total_microseconds = days_seconds_as_micros + microseconds;
Ok(total_microseconds as f64 / 1_000.0)
} else {
// Fall back to floating-point operations if the multiplication overflows
let total_seconds = days_seconds as f64 + microseconds as f64 / 1_000.0;
Ok(total_seconds)
}
}
Self::PyExact(py_timedelta) => {
let days: i64 = py_timedelta.get_days().into(); // -999999999 to 999999999
let seconds: i64 = py_timedelta.get_seconds().into(); // 0 through 86399
let microseconds = py_timedelta.get_microseconds(); // 0 through 999999
let days_seconds = (86_400 * days) + seconds;
if let Some(days_seconds_as_micros) = days_seconds.checked_mul(1_000_000) {
let total_microseconds = days_seconds_as_micros + i64::from(microseconds);
Ok(total_microseconds as f64 / 1_000.0)
} else {
// Fall back to floating-point operations if the multiplication overflows
let total_milliseconds = days_seconds as f64 * 1_000.0 + f64::from(microseconds) / 1_000.0;
Ok(total_milliseconds)
}
}
Self::PySubclass(py_timedelta) => {
let total_seconds: f64 = py_timedelta
.call_method0(intern!(py_timedelta.py(), "total_seconds"))?
.extract()?;
Ok(total_seconds / 1000.0)
}
}
}
}

impl<'py> TryFrom<&'_ Bound<'py, PyAny>> for EitherTimedelta<'py> {
Expand Down
Loading
Loading