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