Skip to content

Commit

Permalink
feat: add chrono::Duration type (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
Odonno authored Jan 7, 2025
1 parent 88375b6 commit ccacbd2
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 7 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ members = ["derive"]
default = []

[dependencies]
chrono = { version = "0.4.38", features = ["serde"], optional = true }
chrono = { version = "0.4.39", features = ["serde"], optional = true }
derive = { version = "0.10.0", package = "revision-derive", path = "derive" }
geo = { version = "0.28.0", features = ["use-serde"], optional = true }
ordered-float = { version = "4.2.2", optional = true }
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ compatibility, but where the design of the data format evolves over time.</p>

`Revision` is a framework for revision-tolerant serialization and deserialization with support for schema evolution over time. It allows for easy revisioning of structs and enums for data storage requirements which need to support backwards compatibility, but where the design of the data structures evolve over time. Revision enables data that was serialized at older revisions to be seamlessly deserialized and converted into the latest data structures. It uses [bincode](https://crates.io/crates/bincode) for serialization and deserialization.

The `Revisioned` trait is automatically implemented for the following primitives: `u8`, `u16`, `u32`, `u64`, `u128`, `usize`, `i8`, `i16`, `i32`, `i64`, `i128`, `isize`, `f32`, `f64`, `char`, `String`, `Vec<T>`, Arrays up to 32 elements, `Option<T>`, `Box<T>`, `Bound<T>`, `Wrapping<T>`, `Reverse<T>`, `(A, B)`, `(A, B, C)`, `(A, B, C, D)`, `(A, B, C, D, E)`, `Duration`, `HashMap<K, V>`, `BTreeMap<K, V>`, `HashSet<T>`, `BTreeSet<T>`, `BinaryHeap<T>`, `Result<T, E>`, `Cow<'_, T>`, `Decimal`, `regex::Regex`, `uuid::Uuid`, `chrono::DateTime<Utc>`, `geo::Point`, `geo::LineString` `geo::Polygon`, `geo::MultiPoint`, `geo::MultiLineString`, `geo::MultiPolygon`, and `ordered_float::NotNan`.
The `Revisioned` trait is automatically implemented for the following primitives: `u8`, `u16`, `u32`, `u64`, `u128`, `usize`, `i8`, `i16`, `i32`, `i64`, `i128`, `isize`, `f32`, `f64`, `char`, `String`, `Vec<T>`, Arrays up to 32 elements, `Option<T>`, `Box<T>`, `Bound<T>`, `Wrapping<T>`, `Reverse<T>`, `(A, B)`, `(A, B, C)`, `(A, B, C, D)`, `(A, B, C, D, E)`, `Duration`, `HashMap<K, V>`, `BTreeMap<K, V>`, `HashSet<T>`, `BTreeSet<T>`, `BinaryHeap<T>`, `Result<T, E>`, `Cow<'_, T>`, `Decimal`, `regex::Regex`, `uuid::Uuid`, `chrono::Duration`, `chrono::DateTime<Utc>`, `geo::Point`, `geo::LineString` `geo::Polygon`, `geo::MultiPoint`, `geo::MultiLineString`, `geo::MultiPolygon`, and `ordered_float::NotNan`.

## Inspiration

Expand Down
4 changes: 2 additions & 2 deletions derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
//! String, Vec<T>, Arrays up to 32 elements, Option<T>, Box<T>, Bound<T>, Wrapping<T>,
//! (A, B), (A, B, C), (A, B, C, D), (A, B, C, D, E), Duration, HashMap<K, V>,
//! BTreeMap<K, V>, Result<T, E>, Cow<'_, T>, Decimal, regex::Regex, uuid::Uuid,
//! chrono::DateTime<Utc>, geo::Point, geo::LineString geo::Polygon, geo::MultiPoint,
//! geo::MultiLineString, and geo::MultiPolygon.
//! chrono::Duration, chrono::DateTime<Utc>, geo::Point, geo::LineString geo::Polygon,
//! geo::MultiPoint, geo::MultiLineString, and geo::MultiPolygon.
use proc_macro::TokenStream;

Expand Down
70 changes: 68 additions & 2 deletions src/implementations/chrono.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use super::super::Error;
use super::super::Revisioned;
use chrono::{offset::TimeZone, DateTime, Datelike, NaiveDate, NaiveTime, Timelike, Utc};
use chrono::{offset::TimeZone, DateTime, Datelike, Duration, NaiveDate, NaiveTime, Timelike, Utc};

impl Revisioned for DateTime<Utc> {
#[inline]
Expand Down Expand Up @@ -74,12 +74,48 @@ impl Revisioned for NaiveTime {
}
}

impl Revisioned for Duration {
#[inline]
fn serialize_revisioned<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
let mut secs = self.num_seconds();
let mut nano = self.subsec_nanos();

if nano < 0 {
secs = secs
.checked_sub(1)
.ok_or_else(|| Error::Serialize("invalid duration".to_string()))?;
nano = nano
.checked_add(1_000_000_000)
.ok_or_else(|| Error::Serialize("invalid duration".to_string()))?;
}

secs.serialize_revisioned(writer)?;
nano.serialize_revisioned(writer)?;

Ok(())
}

#[inline]
fn deserialize_revisioned<R: std::io::Read>(reader: &mut R) -> Result<Self, Error> {
let secs = <i64 as Revisioned>::deserialize_revisioned(reader)?;
let nano = <i32 as Revisioned>::deserialize_revisioned(reader)?;
let nano =
u32::try_from(nano).map_err(|_| Error::Deserialize("invalid duration".to_string()))?;

Duration::new(secs, nano).ok_or_else(|| Error::Deserialize("invalid duration".to_string()))
}

fn revision() -> u16 {
1
}
}

#[cfg(test)]
mod tests {
use super::DateTime;
use super::Revisioned;
use super::Utc;
use chrono::{NaiveDate, NaiveTime};
use chrono::{Duration, NaiveDate, NaiveTime};

#[test]
fn test_datetime_min() {
Expand Down Expand Up @@ -142,4 +178,34 @@ mod tests {
let out = <NaiveTime as Revisioned>::deserialize_revisioned(&mut mem.as_slice()).unwrap();
assert_eq!(val, out);
}

#[test]
fn test_duration_min() {
let val = Duration::MIN;
let mut mem: Vec<u8> = vec![];
val.serialize_revisioned(&mut mem).unwrap();
assert_eq!(mem.len(), 14);
let out = <Duration as Revisioned>::deserialize_revisioned(&mut mem.as_slice()).unwrap();
assert_eq!(val, out);
}

#[test]
fn test_duration_zero() {
let val = Duration::zero();
let mut mem: Vec<u8> = vec![];
val.serialize_revisioned(&mut mem).unwrap();
assert_eq!(mem.len(), 2);
let out = <Duration as Revisioned>::deserialize_revisioned(&mut mem.as_slice()).unwrap();
assert_eq!(val, out);
}

#[test]
fn test_duration_max() {
let val = Duration::MAX;
let mut mem: Vec<u8> = vec![];
val.serialize_revisioned(&mut mem).unwrap();
assert_eq!(mem.len(), 14);
let out = <Duration as Revisioned>::deserialize_revisioned(&mut mem.as_slice()).unwrap();
assert_eq!(val, out);
}
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64, char,
//! String, Vec<T>, Arrays up to 32 elements, Option<T>, Box<T>, Bound<T>, Wrapping<T>,
//! (A, B), (A, B, C), (A, B, C, D), (A, B, C, D, E), Duration, HashMap<K, V>,
//! BTreeMap<K, V>, Result<T, E>, Cow<'_, T>, Decimal, regex::Regex, uuid::Uuid,
//! BTreeMap<K, V>, Result<T, E>, Cow<'_, T>, Decimal, regex::Regex, uuid::Uuid, chrono::Duration,
//! chrono::DateTime<Utc>, geo::Point, geo::LineString geo::Polygon, geo::MultiPoint,
//! geo::MultiLineString, and geo::MultiPolygon.
Expand Down

0 comments on commit ccacbd2

Please sign in to comment.