Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix sub-second negative Duration serialization.
Browse files Browse the repository at this point in the history
vriesk committed Jul 7, 2024
1 parent aca5c36 commit 0cd1f52
Showing 5 changed files with 29 additions and 9 deletions.
8 changes: 8 additions & 0 deletions tests/serde/mod.rs
Original file line number Diff line number Diff line change
@@ -842,6 +842,14 @@ fn duration() {
&Duration::ZERO.readable(),
&[Token::BorrowedStr("0.000000000")],
);
assert_tokens(
&Duration::nanoseconds(123).readable(),
&[Token::BorrowedStr("0.000000123")],
);
assert_tokens(
&Duration::nanoseconds(-123).readable(),
&[Token::BorrowedStr("-0.000000123")],
);
}

#[test]
3 changes: 2 additions & 1 deletion time/src/duration.rs
Original file line number Diff line number Diff line change
@@ -41,7 +41,8 @@ type Nanoseconds =
pub struct Duration {
/// Number of whole seconds.
seconds: i64,
/// Number of nanoseconds within the second. The sign always matches the `seconds` field.
/// Number of nanoseconds within the second. The sign always matches the `seconds` field,
/// except when `seconds` is 0.
// Sign must match that of `seconds` (though this is not a safety requirement).
nanoseconds: Nanoseconds,
#[allow(clippy::missing_docs_in_private_items)]
2 changes: 1 addition & 1 deletion time/src/macros.rs
Original file line number Diff line number Diff line change
@@ -65,7 +65,7 @@ pub use time_macros::datetime;
/// );
/// # Ok::<_, time::Error>(())
/// ```
///
///
/// The syntax accepted by this macro is the same as [`format_description::parse()`], which can
/// be found in [the book](https://time-rs.github.io/book/api/format-description.html).
///
20 changes: 14 additions & 6 deletions time/src/serde/mod.rs
Original file line number Diff line number Diff line change
@@ -102,7 +102,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
/// maybe_dt: Option<OffsetDateTime>,
/// }
/// ```
///
///
/// Define the format separately to be used in multiple places:
/// ```rust,no_run
/// # use time::OffsetDateTime;
@@ -153,7 +153,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
/// let str_ts = OffsetDateTime::now_utc().format(DATE_TIME_FORMAT).unwrap();
/// }
/// ```
///
///
/// Customize the configuration of ISO 8601 formatting/parsing:
/// ```rust,no_run
/// # use time::OffsetDateTime;
@@ -201,7 +201,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
/// }
/// # fn main() {}
/// ```
///
///
/// [`format_description::parse()`]: crate::format_description::parse()
#[cfg(all(feature = "macros", any(feature = "formatting", feature = "parsing"),))]
pub use time_macros::serde_format_description as format_description;
@@ -252,10 +252,18 @@ impl Serialize for Duration {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
#[cfg(feature = "serde-human-readable")]
if serializer.is_human_readable() {
// Simply extracting sign and then calling `self.whole_seconds().abs()` will
// cause overflow on `i64::MIN` seconds, hence this kludge.
let sign_fix = if self.is_negative() && self.whole_seconds() == 0 {
"-"
} else {
""
};

return serializer.collect_str(&format_args!(
"{}.{:>09}",
self.whole_seconds(),
self.subsec_nanoseconds().abs()
"{sign_fix}{seconds}.{nanoseconds:>09}",
seconds = self.whole_seconds(),
nanoseconds = self.subsec_nanoseconds().abs(),
));
}

5 changes: 4 additions & 1 deletion time/src/serde/visitor.rs
Original file line number Diff line number Diff line change
@@ -58,7 +58,10 @@ impl<'a> de::Visitor<'a> for Visitor<Duration> {
de::Error::invalid_value(de::Unexpected::Str(nanoseconds), &"nanoseconds")
})?;

if seconds < 0 {
if seconds < 0
// make sure sign does not disappear when seconds == 0
|| (seconds == 0 && value.starts_with("-"))
{
nanoseconds *= -1;
}

0 comments on commit 0cd1f52

Please sign in to comment.