diff --git a/crates/ruma-common/src/lib.rs b/crates/ruma-common/src/lib.rs index 873c9039..0626c8d4 100644 --- a/crates/ruma-common/src/lib.rs +++ b/crates/ruma-common/src/lib.rs @@ -13,3 +13,6 @@ pub mod presence; pub mod push; pub mod receipt; pub mod thirdparty; +mod time; + +pub use time::{MilliSecondsSinceUnixEpoch, SecondsSinceUnixEpoch}; diff --git a/crates/ruma-common/src/time.rs b/crates/ruma-common/src/time.rs new file mode 100644 index 00000000..d5443b4e --- /dev/null +++ b/crates/ruma-common/src/time.rs @@ -0,0 +1,94 @@ +use js_int::UInt; +use serde::{Deserialize, Serialize}; +use std::{ + convert::TryInto, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; + +/// A timestamp represented as the number of milliseconds since the unix epoch. +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] +#[serde(transparent)] +pub struct MilliSecondsSinceUnixEpoch(pub UInt); + +impl MilliSecondsSinceUnixEpoch { + /// Creates a `MilliSecondsSinceUnixEpoch` from the given `SystemTime`, if it is not before the + /// unix epoch, or too large to be represented. + pub fn from_system_time(time: SystemTime) -> Option { + let duration = time.duration_since(UNIX_EPOCH).ok()?; + let millis = duration.as_millis().try_into().ok()?; + Some(Self(millis)) + } + + /// The current milliseconds since the unix epoch. + pub fn now() -> Self { + Self::from_system_time(SystemTime::now()).unwrap() + } + + /// Creates a `SystemTime` from `self`, if it can be represented. + pub fn to_system_time(self) -> Option { + UNIX_EPOCH.checked_add(Duration::from_millis(self.0.into())) + } +} + +/// A timestamp represented as the number of seconds since the unix epoch. +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] +#[serde(transparent)] +pub struct SecondsSinceUnixEpoch(pub UInt); + +impl SecondsSinceUnixEpoch { + /// Creates a `MilliSecondsSinceUnixEpoch` from the given `SystemTime`, if it is not before the + /// unix epoch, or too large to be represented. + pub fn from_system_time(time: SystemTime) -> Option { + let duration = time.duration_since(UNIX_EPOCH).ok()?; + let millis = duration.as_secs().try_into().ok()?; + Some(Self(millis)) + } + + /// Creates a `SystemTime` from `self`, if it can be represented. + pub fn to_system_time(self) -> Option { + UNIX_EPOCH.checked_add(Duration::from_secs(self.0.into())) + } +} + +#[cfg(test)] +mod tests { + use std::time::{Duration, UNIX_EPOCH}; + + use js_int::uint; + use matches::assert_matches; + use serde::{Deserialize, Serialize}; + use serde_json::json; + + use super::{MilliSecondsSinceUnixEpoch, SecondsSinceUnixEpoch}; + + #[derive(Clone, Debug, Deserialize, Serialize)] + struct SystemTimeTest { + millis: MilliSecondsSinceUnixEpoch, + secs: SecondsSinceUnixEpoch, + } + + #[test] + fn deserialize() { + let json = json!({ "millis": 3000, "secs": 60 }); + + assert_matches!( + serde_json::from_value::(json), + Ok(SystemTimeTest { millis, secs }) + if millis.to_system_time() == Some(UNIX_EPOCH + Duration::from_millis(3000)) + && secs.to_system_time() == Some(UNIX_EPOCH + Duration::from_secs(60)) + ); + } + + #[test] + fn serialize() { + let request = SystemTimeTest { + millis: MilliSecondsSinceUnixEpoch::from_system_time(UNIX_EPOCH + Duration::new(2, 0)) + .unwrap(), + secs: SecondsSinceUnixEpoch(uint!(0)), + }; + assert_matches!( + serde_json::to_value(&request), + Ok(value) if value == json!({ "millis": 2000, "secs": 0 }) + ); + } +}