1use crate::protocol;
2#[cfg(feature = "std")]
3use std::time;
4
5pub const EPOCH_DELTA: i64 = 2_208_988_800;
7
8pub const ERA_SECONDS: i64 = 4_294_967_296; const NTP_SCALE: f64 = u32::MAX as f64;
16
17const NTP_SCALE_64: f64 = u64::MAX as f64;
19
20#[derive(Copy, Clone, Debug)]
44pub struct Instant {
45 secs: i64,
46 subsec_nanos: i32,
47}
48
49impl Instant {
50 pub fn new(secs: i64, subsec_nanos: i32) -> Instant {
56 if secs > 0 && subsec_nanos < 0 {
57 panic!("invalid instant: secs was positive but subsec_nanos was negative");
58 }
59 if secs < 0 && subsec_nanos > 0 {
60 panic!("invalid instant: secs was negative but subsec_nanos was positive");
61 }
62 Instant { secs, subsec_nanos }
63 }
64
65 #[cfg(feature = "std")]
74 pub fn now() -> Self {
75 match time::SystemTime::now().duration_since(time::UNIX_EPOCH) {
76 Ok(duration) => {
77 let secs = duration.as_secs() as i64;
78 let subsec_nanos = duration.subsec_nanos() as i32;
79 Instant::new(secs, subsec_nanos)
80 }
81 Err(sys_time_err) => {
82 let duration_pre_unix_epoch = sys_time_err.duration();
83 let secs = -(duration_pre_unix_epoch.as_secs() as i64);
84 let subsec_nanos = -(duration_pre_unix_epoch.subsec_nanos() as i32);
85 Instant::new(secs, subsec_nanos)
86 }
87 }
88 }
89
90 pub fn secs(&self) -> i64 {
92 self.secs
93 }
94
95 pub fn subsec_nanos(&self) -> i32 {
97 self.subsec_nanos
98 }
99}
100
101fn era_aware_ntp_seconds(raw_seconds: u32, pivot: &Instant) -> i64 {
108 let pivot_ntp = pivot.secs + EPOCH_DELTA;
109 let raw = raw_seconds as i64;
110
111 let pivot_era = pivot_ntp.div_euclid(ERA_SECONDS);
113 let candidate = pivot_era * ERA_SECONDS + raw;
114
115 let diff = candidate - pivot_ntp;
118 if diff > ERA_SECONDS / 2 {
119 candidate - ERA_SECONDS
120 } else if diff < -(ERA_SECONDS / 2) {
121 candidate + ERA_SECONDS
122 } else {
123 candidate
124 }
125}
126
127pub fn timestamp_to_instant(ts: protocol::TimestampFormat, pivot: &Instant) -> Instant {
137 let ntp_secs = era_aware_ntp_seconds(ts.seconds, pivot);
138 let secs = ntp_secs - EPOCH_DELTA;
139 let subsec_nanos = (ts.fraction as f64 / NTP_SCALE * 1e9) as i32;
140 Instant::new(secs, subsec_nanos)
141}
142
143impl From<protocol::ShortFormat> for Instant {
146 fn from(t: protocol::ShortFormat) -> Self {
147 let secs = t.seconds as i64 - EPOCH_DELTA;
148 let subsec_nanos = (t.fraction as f64 / NTP_SCALE * 1e9) as i32;
149 Instant::new(secs, subsec_nanos)
150 }
151}
152
153#[cfg(feature = "std")]
154impl From<protocol::TimestampFormat> for Instant {
155 fn from(t: protocol::TimestampFormat) -> Self {
161 timestamp_to_instant(t, &Instant::now())
162 }
163}
164
165impl From<Instant> for protocol::ShortFormat {
166 fn from(t: Instant) -> Self {
167 let sec = t.secs() + EPOCH_DELTA;
168 let frac = t.subsec_nanos() as f64 * NTP_SCALE / 1e9;
169 protocol::ShortFormat {
170 seconds: sec as u16,
171 fraction: frac as u16,
172 }
173 }
174}
175
176impl From<Instant> for protocol::TimestampFormat {
177 fn from(t: Instant) -> Self {
183 let sec = t.secs() + EPOCH_DELTA;
184 let frac = t.subsec_nanos() as f64 * NTP_SCALE / 1e9;
185 protocol::TimestampFormat {
186 seconds: sec as u32,
187 fraction: frac as u32,
188 }
189 }
190}
191
192impl From<protocol::DateFormat> for Instant {
193 fn from(d: protocol::DateFormat) -> Self {
197 let ntp_secs = d.era_number as i64 * ERA_SECONDS + d.era_offset as i64;
198 let secs = ntp_secs - EPOCH_DELTA;
199 let subsec_nanos = (d.fraction as f64 / NTP_SCALE_64 * 1e9) as i32;
200 Instant::new(secs, subsec_nanos)
201 }
202}
203
204impl From<Instant> for protocol::DateFormat {
205 fn from(t: Instant) -> Self {
209 let ntp_secs = t.secs() + EPOCH_DELTA;
210 let era_number = ntp_secs.div_euclid(ERA_SECONDS) as i32;
211 let era_offset = ntp_secs.rem_euclid(ERA_SECONDS) as u32;
212 let fraction = (t.subsec_nanos().unsigned_abs() as f64 / 1e9 * NTP_SCALE_64) as u64;
213 protocol::DateFormat {
214 era_number,
215 era_offset,
216 fraction,
217 }
218 }
219}
220
221#[cfg(all(test, feature = "std"))]
222mod tests {
223 use super::*;
224
225 #[test]
226 fn era0_timestamp_to_instant() {
227 let ts = protocol::TimestampFormat {
229 seconds: 3_913_056_000,
230 fraction: 0,
231 };
232 let pivot = Instant::new(1_704_067_200, 0);
233 let result = timestamp_to_instant(ts, &pivot);
234 assert_eq!(result.secs(), 1_704_067_200);
235 }
236
237 #[test]
238 fn era1_timestamp_with_era1_pivot() {
239 let ts = protocol::TimestampFormat {
242 seconds: 100_000_000,
243 fraction: 0,
244 };
245 let pivot = Instant::new(2_185_978_496, 0);
246 let result = timestamp_to_instant(ts, &pivot);
247 assert_eq!(result.secs(), 2_185_978_496);
248 }
249
250 #[test]
251 fn era_boundary_pivot_before_ts_after() {
252 let pivot = Instant::new(2_082_758_400, 0); let ts = protocol::TimestampFormat {
255 seconds: 1000,
256 fraction: 0,
257 };
258 let result = timestamp_to_instant(ts, &pivot);
259 let expected = ERA_SECONDS + 1000 - EPOCH_DELTA;
260 assert_eq!(result.secs(), expected);
261 }
262
263 #[test]
264 fn era_boundary_pivot_after_ts_before() {
265 let pivot = Instant::new(2_087_942_400, 0); let ts = protocol::TimestampFormat {
268 seconds: u32::MAX,
269 fraction: 0,
270 };
271 let result = timestamp_to_instant(ts, &pivot);
272 let expected = u32::MAX as i64 - EPOCH_DELTA;
273 assert_eq!(result.secs(), expected);
274 }
275
276 #[test]
277 fn date_format_roundtrip_era0() {
278 let instant = Instant::new(1_704_067_200, 500_000_000);
279 let date: protocol::DateFormat = instant.into();
280 assert_eq!(date.era_number, 0);
281 let back: Instant = date.into();
282 assert_eq!(back.secs(), instant.secs());
283 assert!((back.subsec_nanos() - instant.subsec_nanos()).abs() <= 1);
284 }
285
286 #[test]
287 fn date_format_roundtrip_era1() {
288 let instant = Instant::new(2_185_978_496, 0); let date: protocol::DateFormat = instant.into();
290 assert_eq!(date.era_number, 1);
291 let back: Instant = date.into();
292 assert_eq!(back.secs(), instant.secs());
293 }
294
295 #[test]
296 fn timestamp_format_roundtrip_with_pivot() {
297 let original = Instant::new(1_704_067_200, 0);
298 let ts: protocol::TimestampFormat = original.into();
299 let restored = timestamp_to_instant(ts, &original);
300 assert_eq!(restored.secs(), original.secs());
301 }
302
303 #[test]
304 fn date_format_negative_era() {
305 let instant = Instant::new(-2_300_000_000, 0);
307 let date: protocol::DateFormat = instant.into();
308 assert_eq!(date.era_number, -1);
309 let back: Instant = date.into();
310 assert_eq!(back.secs(), instant.secs());
311 }
312}