1use simple_error::{bail, SimpleError};
2
3use roslibrust_common::RosMessageType;
4
5#[derive(:: serde :: Deserialize, :: serde :: Serialize, Debug, Default, Clone, PartialEq)]
16pub struct Time {
17 #[serde(alias = "sec")]
22 pub secs: i32,
23 #[serde(alias = "nanosec")]
25 pub nsecs: i32,
26}
27
28impl TryFrom<std::time::SystemTime> for Time {
30 type Error = SimpleError;
31 fn try_from(val: std::time::SystemTime) -> Result<Self, Self::Error> {
32 let delta = match val.duration_since(std::time::UNIX_EPOCH) {
33 Ok(delta) => delta,
34 Err(e) => bail!("Failed to convert system time into unix epoch: {}", e),
35 };
36 let downcast_secs = match i32::try_from(delta.as_secs()) {
40 Ok(val) => val,
41 Err(e) => bail!("Failed to convert seconds to i32: {e:?}"),
42 };
43 let downcast_nanos = match i32::try_from(delta.subsec_nanos()) {
44 Ok(val) => val,
45 Err(e) => bail!("Failed to convert nanoseconds to i32: {e:?}"),
46 };
47 Ok(Time {
48 secs: downcast_secs,
49 nsecs: downcast_nanos,
50 })
51 }
52}
53
54impl TryFrom<Time> for std::time::SystemTime {
56 type Error = SimpleError;
57 fn try_from(val: Time) -> Result<Self, Self::Error> {
58 let secs = match u64::try_from(val.secs){
62 Ok(val) => val,
63 Err(e) => bail!( "Failed to convert ROS time to std::time::SystemTime, secs term overflows u64 likely: {val:?}, {e:?}"),
64 };
65 let nsecs = match u64::try_from(val.nsecs) {
66 Ok(val) => val,
67 Err(e) => bail!("Failed to convert ROS time to std::time::SystemTime, nsecs term overflows u64 likely: {val:?}, {e:?}"),
68 };
69 let duration = std::time::Duration::new(secs, nsecs as u32);
70 Ok(std::time::UNIX_EPOCH + duration)
71 }
72}
73
74impl RosMessageType for Time {
75 const ROS_TYPE_NAME: &'static str = "builtin_interfaces/Time";
76 const MD5SUM: &'static str = "";
78 const DEFINITION: &'static str = "";
79}
80
81#[derive(:: serde :: Deserialize, :: serde :: Serialize, Debug, Default, Clone, PartialEq)]
84pub struct Duration {
85 pub sec: i32,
86 pub nsec: i32,
87}
88
89impl TryFrom<std::time::Duration> for Duration {
92 type Error = SimpleError;
93 fn try_from(val: std::time::Duration) -> Result<Self, Self::Error> {
94 let downcast_sec = match i32::try_from(val.as_secs()) {
95 Ok(val) => val,
96 Err(e) => bail!(
97 "Failed to cast tokio duration to ROS duration, secs could not fit in i32: {e:?}"
98 ),
99 };
100 let downcast_nsec = match i32::try_from(val.subsec_nanos()) {
101 Ok(val) => val,
102 Err(e) => bail!(
103 "Failed to cast tokio duration ROS duration, nsecs could not fit in i32: {e:?}"
104 ),
105 };
106 Ok(Duration {
107 sec: downcast_sec,
108 nsec: downcast_nsec,
109 })
110 }
111}
112
113impl TryFrom<Duration> for std::time::Duration {
116 type Error = SimpleError;
117 fn try_from(val: Duration) -> Result<Self, Self::Error> {
118 let upcast_sec = match u64::try_from(val.sec) {
119 Ok(val) => val,
120 Err(e) => bail!(
121 "Failed to cast ROS duration to tokio duration, secs could not fit in u64: {e:?}"
122 ),
123 };
124 let upcast_nsec = match u32::try_from(val.nsec) {
125 Ok(val) => val,
126 Err(e) => bail!(
127 "Failed to cast ROS duration to tokio duration, nsecs could not fit in u64: {e:?}"
128 ),
129 };
130 Ok(std::time::Duration::new(upcast_sec, upcast_nsec))
131 }
132}
133
134#[cfg(feature = "chrono")]
136impl TryFrom<chrono::DateTime<chrono::Utc>> for Time {
137 type Error = SimpleError;
138 fn try_from(val: chrono::DateTime<chrono::Utc>) -> Result<Self, Self::Error> {
139 let downcast_secs = match i32::try_from(val.timestamp()) {
140 Ok(val) => val,
141 Err(e) => {
142 bail!("Failed to convert chrono time to ROS time, secs could not fit in i32: {e:?}")
143 }
144 };
145 let downcast_nanos = match i32::try_from(val.timestamp_subsec_nanos()) {
146 Ok(val) => val,
147 Err(e) => bail!(
148 "Failed to convert chrono time to ROS time, nsecs could not fit in i32: {e:?}"
149 ),
150 };
151 Ok(Time {
152 secs: downcast_secs,
153 nsecs: downcast_nanos,
154 })
155 }
156}
157
158#[cfg(feature = "chrono")]
160impl TryFrom<Time> for chrono::DateTime<chrono::Utc> {
161 type Error = SimpleError;
162 fn try_from(val: Time) -> Result<Self, Self::Error> {
163 let secs = i64::from(val.secs);
164 let nsecs = match u32::try_from(val.nsecs) {
165 Ok(val) => val,
166 Err(e) => bail!(
167 "Failed to convert ROS time to chrono time, nsecs could not fit in u32: {e:?}"
168 ),
169 };
170 match chrono::DateTime::from_timestamp(secs, nsecs) {
171 Some(val) => Ok(val),
172 None => bail!("Failed to convert ROS time to chrono time, secs and nsecs could not fit in chrono::DateTime."),
173 }
174 }
175}
176
177#[cfg(feature = "chrono")]
179impl TryFrom<chrono::Duration> for Duration {
180 type Error = SimpleError;
181 fn try_from(val: chrono::Duration) -> Result<Self, Self::Error> {
182 let downcast_sec = match i32::try_from(val.num_seconds()) {
184 Ok(val) => val,
185 Err(e) => bail!(
186 "Failed to cast chrono duration to ROS duration, secs could not fit in i32: {e:?}"
187 ),
188 };
189 Ok(Duration {
190 sec: downcast_sec,
191 nsec: val.subsec_nanos(),
192 })
193 }
194}
195
196#[cfg(feature = "chrono")]
198impl TryFrom<Duration> for chrono::Duration {
199 type Error = SimpleError;
200 fn try_from(val: Duration) -> Result<Self, Self::Error> {
203 let secs = match chrono::Duration::try_seconds(i64::from(val.sec)) {
204 Some(val) => val,
205 None => bail!("Failed to cast ROS duration to chrono duration, secs could not fit in chrono::Duration."),
206 };
207 let nsecs = chrono::Duration::nanoseconds(i64::from(val.nsec));
209 let total = match secs.checked_add(&nsecs) {
211 Some(val) => val,
212 None => bail!("Failed to cast ROS duration to chrono duration, addition overflowed when combining secs and nsecs."),
213 };
214 Ok(total)
215 }
216}
217
218#[cfg(test)]
219mod test {
220 #[test]
221 fn test_time_conversions() {
222 let time = std::time::SystemTime::now();
224 let ros_time: crate::Time = time.try_into().unwrap();
225 let std_time: std::time::SystemTime = ros_time.try_into().unwrap();
226 assert_eq!(time, std_time);
227
228 let time = std::time::SystemTime::UNIX_EPOCH;
230 let ros_time: crate::Time = time.try_into().unwrap();
231 let std_time: std::time::SystemTime = ros_time.try_into().unwrap();
232 assert_eq!(time, std_time);
233
234 let ros_time = crate::Time {
236 secs: i32::MAX,
237 nsecs: i32::MAX,
238 };
239 let std_time: std::time::SystemTime = ros_time.try_into().unwrap();
240 assert_eq!(
241 std_time,
242 std::time::SystemTime::UNIX_EPOCH
243 + std::time::Duration::new(i32::MAX as u64, i32::MAX as u32)
244 );
245
246 let ros_time = crate::Time {
248 secs: i32::MIN,
249 nsecs: i32::MIN,
250 };
251 let std_time: Result<std::time::SystemTime, _> = ros_time.try_into();
252 assert!(std_time.is_err());
254
255 let ros_time = crate::Time { secs: 1, nsecs: -1 };
257 let std_time: Result<std::time::SystemTime, _> = ros_time.try_into();
258 assert!(std_time.is_err());
261 }
262
263 #[test]
264 fn test_duration_conversions() {
265 let tokio_duration = tokio::time::Duration::from_millis(1000);
267 let ros_duration: crate::Duration = tokio_duration.try_into().unwrap();
268 let roundtrip_duration: tokio::time::Duration = ros_duration.try_into().unwrap();
269 assert_eq!(tokio_duration, roundtrip_duration);
270
271 let std_duration = std::time::Duration::from_millis(1000);
273 let ros_duration: crate::Duration = std_duration.try_into().unwrap();
274 let roundtrip_duration: std::time::Duration = ros_duration.try_into().unwrap();
275 assert_eq!(std_duration, roundtrip_duration);
276
277 let tokio_duration = tokio::time::Duration::from_millis(0);
279 let ros_duration: crate::Duration = tokio_duration.try_into().unwrap();
280 let roundtrip_duration: tokio::time::Duration = ros_duration.try_into().unwrap();
281 assert_eq!(tokio_duration, roundtrip_duration);
282
283 let ros_duration = crate::Duration { sec: -1, nsec: -1 };
285 let tokio_duration: Result<tokio::time::Duration, _> = ros_duration.try_into();
286 assert!(tokio_duration.is_err());
288 }
289
290 #[test]
291 #[cfg(feature = "chrono")]
292 fn test_chrono_duration_conversions() {
293 let chrono_duration = chrono::Duration::seconds(1) + chrono::Duration::nanoseconds(69);
295 let ros_duration: crate::Duration = chrono_duration.try_into().unwrap();
296 let roundtrip_duration: chrono::Duration = ros_duration.try_into().unwrap();
297 assert_eq!(chrono_duration, roundtrip_duration);
298
299 let chrono_duration = chrono::Duration::seconds(0);
301 let ros_duration: crate::Duration = chrono_duration.try_into().unwrap();
302 let roundtrip_duration: chrono::Duration = ros_duration.try_into().unwrap();
303 assert_eq!(chrono_duration, roundtrip_duration);
304
305 let chrono_duration = chrono::Duration::seconds(i64::MAX / 10_000);
307 let ros_duration: Result<crate::Duration, _> = chrono_duration.try_into();
308 assert!(ros_duration.is_err());
309
310 let chrono_duration = chrono::Duration::seconds(-1) + chrono::Duration::nanoseconds(-42);
312 let ros_duration: crate::Duration = chrono_duration.try_into().unwrap();
313 let roundtrip_duration: chrono::Duration = ros_duration.try_into().unwrap();
314 assert_eq!(chrono_duration, roundtrip_duration);
315 }
316
317 #[test]
318 #[cfg(feature = "chrono")]
319 fn test_chrono_time_conversions() {
320 let now = chrono::offset::Utc::now();
322 let ros_time: crate::Time = now.try_into().unwrap();
323 let roundtrip_time: chrono::DateTime<chrono::Utc> = ros_time.try_into().unwrap();
324 assert_eq!(now, roundtrip_time);
325
326 let epoch = chrono::DateTime::<chrono::Utc>::UNIX_EPOCH;
328 let ros_epoch: crate::Time = epoch.try_into().unwrap();
329 let roundtrip_epoch: chrono::DateTime<chrono::Utc> = ros_epoch.try_into().unwrap();
330 assert_eq!(epoch, roundtrip_epoch);
331
332 let too_large = chrono::DateTime::<chrono::Utc>::UNIX_EPOCH
334 + chrono::Duration::seconds(i32::MAX as i64 + 1000);
335 let ros_time: Result<crate::Time, _> = too_large.try_into();
336 assert!(ros_time.is_err());
337 }
338}