use simple_error::{bail, SimpleError};
use roslibrust_common::RosMessageType;
#[derive(:: serde :: Deserialize, :: serde :: Serialize, Debug, Default, Clone, PartialEq)]
pub struct Time {
#[serde(alias = "sec")]
pub secs: i32,
#[serde(alias = "nanosec")]
pub nsecs: i32,
}
impl TryFrom<std::time::SystemTime> for Time {
type Error = SimpleError;
fn try_from(val: std::time::SystemTime) -> Result<Self, Self::Error> {
let delta = match val.duration_since(std::time::UNIX_EPOCH) {
Ok(delta) => delta,
Err(e) => bail!("Failed to convert system time into unix epoch: {}", e),
};
let downcast_secs = match i32::try_from(delta.as_secs()) {
Ok(val) => val,
Err(e) => bail!("Failed to convert seconds to i32: {e:?}"),
};
let downcast_nanos = match i32::try_from(delta.subsec_nanos()) {
Ok(val) => val,
Err(e) => bail!("Failed to convert nanoseconds to i32: {e:?}"),
};
Ok(Time {
secs: downcast_secs,
nsecs: downcast_nanos,
})
}
}
impl TryFrom<Time> for std::time::SystemTime {
type Error = SimpleError;
fn try_from(val: Time) -> Result<Self, Self::Error> {
let secs = match u64::try_from(val.secs){
Ok(val) => val,
Err(e) => bail!( "Failed to convert ROS time to std::time::SystemTime, secs term overflows u64 likely: {val:?}, {e:?}"),
};
let nsecs = match u64::try_from(val.nsecs) {
Ok(val) => val,
Err(e) => bail!("Failed to convert ROS time to std::time::SystemTime, nsecs term overflows u64 likely: {val:?}, {e:?}"),
};
let duration = std::time::Duration::new(secs, nsecs as u32);
Ok(std::time::UNIX_EPOCH + duration)
}
}
impl RosMessageType for Time {
const ROS_TYPE_NAME: &'static str = "builtin_interfaces/Time";
const MD5SUM: &'static str = "";
const DEFINITION: &'static str = "";
}
#[derive(:: serde :: Deserialize, :: serde :: Serialize, Debug, Default, Clone, PartialEq)]
pub struct Duration {
pub sec: i32,
pub nsec: i32,
}
impl TryFrom<std::time::Duration> for Duration {
type Error = SimpleError;
fn try_from(val: std::time::Duration) -> Result<Self, Self::Error> {
let downcast_sec = match i32::try_from(val.as_secs()) {
Ok(val) => val,
Err(e) => bail!(
"Failed to cast tokio duration to ROS duration, secs could not fit in i32: {e:?}"
),
};
let downcast_nsec = match i32::try_from(val.subsec_nanos()) {
Ok(val) => val,
Err(e) => bail!(
"Failed to cast tokio duration ROS duration, nsecs could not fit in i32: {e:?}"
),
};
Ok(Duration {
sec: downcast_sec,
nsec: downcast_nsec,
})
}
}
impl TryFrom<Duration> for std::time::Duration {
type Error = SimpleError;
fn try_from(val: Duration) -> Result<Self, Self::Error> {
let upcast_sec = match u64::try_from(val.sec) {
Ok(val) => val,
Err(e) => bail!(
"Failed to cast ROS duration to tokio duration, secs could not fit in u64: {e:?}"
),
};
let upcast_nsec = match u32::try_from(val.nsec) {
Ok(val) => val,
Err(e) => bail!(
"Failed to cast ROS duration to tokio duration, nsecs could not fit in u64: {e:?}"
),
};
Ok(std::time::Duration::new(upcast_sec, upcast_nsec))
}
}
#[cfg(feature = "chrono")]
impl TryFrom<chrono::DateTime<chrono::Utc>> for Time {
type Error = SimpleError;
fn try_from(val: chrono::DateTime<chrono::Utc>) -> Result<Self, Self::Error> {
let downcast_secs = match i32::try_from(val.timestamp()) {
Ok(val) => val,
Err(e) => {
bail!("Failed to convert chrono time to ROS time, secs could not fit in i32: {e:?}")
}
};
let downcast_nanos = match i32::try_from(val.timestamp_subsec_nanos()) {
Ok(val) => val,
Err(e) => bail!(
"Failed to convert chrono time to ROS time, nsecs could not fit in i32: {e:?}"
),
};
Ok(Time {
secs: downcast_secs,
nsecs: downcast_nanos,
})
}
}
#[cfg(feature = "chrono")]
impl TryFrom<Time> for chrono::DateTime<chrono::Utc> {
type Error = SimpleError;
fn try_from(val: Time) -> Result<Self, Self::Error> {
let secs = i64::from(val.secs);
let nsecs = match u32::try_from(val.nsecs) {
Ok(val) => val,
Err(e) => bail!(
"Failed to convert ROS time to chrono time, nsecs could not fit in u32: {e:?}"
),
};
match chrono::DateTime::from_timestamp(secs, nsecs) {
Some(val) => Ok(val),
None => bail!("Failed to convert ROS time to chrono time, secs and nsecs could not fit in chrono::DateTime."),
}
}
}
#[cfg(feature = "chrono")]
impl TryFrom<chrono::Duration> for Duration {
type Error = SimpleError;
fn try_from(val: chrono::Duration) -> Result<Self, Self::Error> {
let downcast_sec = match i32::try_from(val.num_seconds()) {
Ok(val) => val,
Err(e) => bail!(
"Failed to cast chrono duration to ROS duration, secs could not fit in i32: {e:?}"
),
};
Ok(Duration {
sec: downcast_sec,
nsec: val.subsec_nanos(),
})
}
}
#[cfg(feature = "chrono")]
impl TryFrom<Duration> for chrono::Duration {
type Error = SimpleError;
fn try_from(val: Duration) -> Result<Self, Self::Error> {
let secs = match chrono::Duration::try_seconds(i64::from(val.sec)) {
Some(val) => val,
None => bail!("Failed to cast ROS duration to chrono duration, secs could not fit in chrono::Duration."),
};
let nsecs = chrono::Duration::nanoseconds(i64::from(val.nsec));
let total = match secs.checked_add(&nsecs) {
Some(val) => val,
None => bail!("Failed to cast ROS duration to chrono duration, addition overflowed when combining secs and nsecs."),
};
Ok(total)
}
}
#[cfg(test)]
mod test {
#[test]
fn test_time_conversions() {
let time = std::time::SystemTime::now();
let ros_time: crate::Time = time.try_into().unwrap();
let std_time: std::time::SystemTime = ros_time.try_into().unwrap();
assert_eq!(time, std_time);
let time = std::time::SystemTime::UNIX_EPOCH;
let ros_time: crate::Time = time.try_into().unwrap();
let std_time: std::time::SystemTime = ros_time.try_into().unwrap();
assert_eq!(time, std_time);
let ros_time = crate::Time {
secs: i32::MAX,
nsecs: i32::MAX,
};
let std_time: std::time::SystemTime = ros_time.try_into().unwrap();
assert_eq!(
std_time,
std::time::SystemTime::UNIX_EPOCH
+ std::time::Duration::new(i32::MAX as u64, i32::MAX as u32)
);
let ros_time = crate::Time {
secs: i32::MIN,
nsecs: i32::MIN,
};
let std_time: Result<std::time::SystemTime, _> = ros_time.try_into();
assert!(std_time.is_err());
let ros_time = crate::Time { secs: 1, nsecs: -1 };
let std_time: Result<std::time::SystemTime, _> = ros_time.try_into();
assert!(std_time.is_err());
}
#[test]
fn test_duration_conversions() {
let tokio_duration = tokio::time::Duration::from_millis(1000);
let ros_duration: crate::Duration = tokio_duration.try_into().unwrap();
let roundtrip_duration: tokio::time::Duration = ros_duration.try_into().unwrap();
assert_eq!(tokio_duration, roundtrip_duration);
let std_duration = std::time::Duration::from_millis(1000);
let ros_duration: crate::Duration = std_duration.try_into().unwrap();
let roundtrip_duration: std::time::Duration = ros_duration.try_into().unwrap();
assert_eq!(std_duration, roundtrip_duration);
let tokio_duration = tokio::time::Duration::from_millis(0);
let ros_duration: crate::Duration = tokio_duration.try_into().unwrap();
let roundtrip_duration: tokio::time::Duration = ros_duration.try_into().unwrap();
assert_eq!(tokio_duration, roundtrip_duration);
let ros_duration = crate::Duration { sec: -1, nsec: -1 };
let tokio_duration: Result<tokio::time::Duration, _> = ros_duration.try_into();
assert!(tokio_duration.is_err());
}
#[test]
#[cfg(feature = "chrono")]
fn test_chrono_duration_conversions() {
let chrono_duration = chrono::Duration::seconds(1) + chrono::Duration::nanoseconds(69);
let ros_duration: crate::Duration = chrono_duration.try_into().unwrap();
let roundtrip_duration: chrono::Duration = ros_duration.try_into().unwrap();
assert_eq!(chrono_duration, roundtrip_duration);
let chrono_duration = chrono::Duration::seconds(0);
let ros_duration: crate::Duration = chrono_duration.try_into().unwrap();
let roundtrip_duration: chrono::Duration = ros_duration.try_into().unwrap();
assert_eq!(chrono_duration, roundtrip_duration);
let chrono_duration = chrono::Duration::seconds(i64::MAX / 10_000);
let ros_duration: Result<crate::Duration, _> = chrono_duration.try_into();
assert!(ros_duration.is_err());
let chrono_duration = chrono::Duration::seconds(-1) + chrono::Duration::nanoseconds(-42);
let ros_duration: crate::Duration = chrono_duration.try_into().unwrap();
let roundtrip_duration: chrono::Duration = ros_duration.try_into().unwrap();
assert_eq!(chrono_duration, roundtrip_duration);
}
#[test]
#[cfg(feature = "chrono")]
fn test_chrono_time_conversions() {
let now = chrono::offset::Utc::now();
let ros_time: crate::Time = now.try_into().unwrap();
let roundtrip_time: chrono::DateTime<chrono::Utc> = ros_time.try_into().unwrap();
assert_eq!(now, roundtrip_time);
let epoch = chrono::DateTime::<chrono::Utc>::UNIX_EPOCH;
let ros_epoch: crate::Time = epoch.try_into().unwrap();
let roundtrip_epoch: chrono::DateTime<chrono::Utc> = ros_epoch.try_into().unwrap();
assert_eq!(epoch, roundtrip_epoch);
let too_large = chrono::DateTime::<chrono::Utc>::UNIX_EPOCH
+ chrono::Duration::seconds(i32::MAX as i64 + 1000);
let ros_time: Result<crate::Time, _> = too_large.try_into();
assert!(ros_time.is_err());
}
}