1use super::{ID, NTP64};
12use alloc::string::String;
13use core::{fmt, time::Duration};
14use serde::{Deserialize, Serialize};
15
16#[cfg(feature = "std")]
17use core::str::FromStr;
18
19#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
34#[cfg_attr(feature = "defmt", derive(defmt::Format))]
35pub struct Timestamp {
36 time: NTP64,
37 id: ID,
38}
39
40impl Timestamp {
41 #[inline]
43 pub fn new(time: NTP64, id: ID) -> Timestamp {
44 Timestamp { time, id }
45 }
46
47 #[inline]
49 pub fn get_time(&self) -> &NTP64 {
50 &self.time
51 }
52
53 #[inline]
55 pub fn get_id(&self) -> &ID {
56 &self.id
57 }
58
59 #[inline]
61 pub fn get_diff_duration(&self, other: &Timestamp) -> Duration {
62 (self.time - other.time).to_duration()
63 }
64
65 #[cfg(feature = "std")]
68 pub fn to_string_rfc3339_lossy(&self) -> String {
69 #[cfg(feature = "std")]
70 return format!("{:#}", self);
71 #[cfg(not(feature = "std"))]
72 return self.to_string();
73 }
74
75 #[cfg(feature = "std")]
77 pub fn parse_rfc3339(s: &str) -> Result<Self, ParseTimestampError> {
78 match s.find('/') {
79 Some(i) => {
80 let (stime, srem) = s.split_at(i);
81 let time = NTP64::parse_rfc3339(stime)
82 .map_err(|e| ParseTimestampError { cause: e.cause })?;
83 let id =
84 ID::from_str(&srem[1..]).map_err(|e| ParseTimestampError { cause: e.cause })?;
85 Ok(Timestamp::new(time, id))
86 }
87 None => Err(ParseTimestampError {
88 cause: "No '/' found in String".into(),
89 }),
90 }
91 }
92}
93
94impl fmt::Display for Timestamp {
95 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
109 if f.alternate() {
110 write!(f, "{:#}/{}", self.time, self.id)
111 } else {
112 write!(f, "{}/{}", self.time, self.id)
113 }
114 }
115}
116
117impl fmt::Debug for Timestamp {
118 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
119 write!(f, "{:?}/{:?}", self.time, self.id)
120 }
121}
122
123#[cfg(feature = "std")]
124impl FromStr for Timestamp {
125 type Err = ParseTimestampError;
126
127 fn from_str(s: &str) -> Result<Self, Self::Err> {
128 match s.find('/') {
129 Some(i) => {
130 let (stime, srem) = s.split_at(i);
131 let time =
132 NTP64::from_str(stime).map_err(|e| ParseTimestampError { cause: e.cause })?;
133 let id =
134 ID::from_str(&srem[1..]).map_err(|e| ParseTimestampError { cause: e.cause })?;
135 Ok(Timestamp::new(time, id))
136 }
137 None => Err(ParseTimestampError {
138 cause: "No '/' found in String".into(),
139 }),
140 }
141 }
142}
143
144#[derive(Debug, Clone, PartialEq, Eq)]
145#[cfg_attr(feature = "defmt", derive(defmt::Format))]
146pub struct ParseTimestampError {
147 pub cause: String,
148}
149
150#[cfg(test)]
151mod tests {
152 use crate::*;
153 use core::convert::TryFrom;
154
155 #[test]
156 fn test_timestamp() {
157 let id1: ID = ID::try_from([0x01]).unwrap();
158 let id2: ID = ID::try_from([0x02]).unwrap();
159
160 let ts1_epoch = Timestamp::new(Default::default(), id1);
161 #[cfg(feature = "std")]
162 assert_eq!(ts1_epoch.get_time().to_system_time(), std::time::UNIX_EPOCH);
163 #[cfg(not(feature = "std"))]
164 assert_eq!(ts1_epoch.get_time().as_u64(), 0);
165 assert_eq!(ts1_epoch.get_id(), &id1);
166
167 let ts2_epoch = Timestamp::new(Default::default(), id2);
168 #[cfg(feature = "std")]
169 assert_eq!(ts2_epoch.get_time().to_system_time(), std::time::UNIX_EPOCH);
170 #[cfg(not(feature = "std"))]
171 assert_eq!(ts2_epoch.get_time().as_u64(), 0);
172 assert_eq!(ts2_epoch.get_id(), &id2);
173
174 assert_ne!(ts1_epoch, ts2_epoch);
176 assert!(ts1_epoch < ts2_epoch);
177
178 #[cfg(feature = "std")]
179 let now = system_time_clock();
180 #[cfg(not(feature = "std"))]
181 let now = zero_clock();
182 let ts1_now = Timestamp::new(now, id1);
183 let ts2_now = Timestamp::new(now, id2);
184 assert_ne!(ts1_now, ts2_now);
185 assert!(ts1_now < ts2_now);
186
187 #[cfg(feature = "std")]
188 {
189 assert!(ts1_epoch < ts1_now);
191 assert!(ts2_epoch < ts2_now);
192 }
193
194 #[cfg(feature = "std")]
195 {
196 let s = ts1_now.to_string();
198 assert_eq!(ts1_now, s.parse().unwrap());
199 }
200
201 let diff = ts1_now.get_diff_duration(&ts2_now);
202 assert_eq!(diff, Duration::from_secs(0));
203 }
204
205 #[test]
206 fn bijective_to_string() {
207 use crate::*;
208 use std::str::FromStr;
209
210 let hlc = HLCBuilder::new().with_id(ID::rand()).build();
211 for _ in 1..10000 {
212 let now_ts = hlc.new_timestamp();
213 assert_eq!(now_ts, Timestamp::from_str(&now_ts.to_string()).unwrap());
214 }
215 }
216}