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)]
49pub struct Instant {
50 secs: i64,
51 subsec_nanos: i32,
52}
53
54impl Instant {
55 pub fn new(secs: i64, subsec_nanos: i32) -> Instant {
61 if secs > 0 && subsec_nanos < 0 {
62 panic!("invalid instant: secs was positive but subsec_nanos was negative");
63 }
64 if secs < 0 && subsec_nanos > 0 {
65 panic!("invalid instant: secs was negative but subsec_nanos was positive");
66 }
67 Instant { secs, subsec_nanos }
68 }
69
70 #[cfg(feature = "std")]
83 pub fn now() -> Self {
84 match time::SystemTime::now().duration_since(time::UNIX_EPOCH) {
85 Ok(duration) => {
86 let secs = duration.as_secs() as i64;
87 let subsec_nanos = duration.subsec_nanos() as i32;
88 Instant::new(secs, subsec_nanos)
89 }
90 Err(sys_time_err) => {
91 let duration_pre_unix_epoch = sys_time_err.duration();
92 let secs = -(duration_pre_unix_epoch.as_secs() as i64);
93 let subsec_nanos = -(duration_pre_unix_epoch.subsec_nanos() as i32);
94 Instant::new(secs, subsec_nanos)
95 }
96 }
97 }
98
99 pub fn secs(&self) -> i64 {
101 self.secs
102 }
103
104 pub fn subsec_nanos(&self) -> i32 {
106 self.subsec_nanos
107 }
108}
109
110fn era_aware_ntp_seconds(raw_seconds: u32, pivot: &Instant) -> i64 {
117 let pivot_ntp = pivot.secs + EPOCH_DELTA;
118 let raw = raw_seconds as i64;
119
120 let pivot_era = pivot_ntp.div_euclid(ERA_SECONDS);
122 let candidate = pivot_era * ERA_SECONDS + raw;
123
124 let diff = candidate - pivot_ntp;
127 if diff > ERA_SECONDS / 2 {
128 candidate - ERA_SECONDS
129 } else if diff < -(ERA_SECONDS / 2) {
130 candidate + ERA_SECONDS
131 } else {
132 candidate
133 }
134}
135
136pub fn timestamp_to_instant(ts: protocol::TimestampFormat, pivot: &Instant) -> Instant {
146 let ntp_secs = era_aware_ntp_seconds(ts.seconds, pivot);
147 let secs = ntp_secs - EPOCH_DELTA;
148 let subsec_nanos = (ts.fraction as f64 / NTP_SCALE * 1e9) as i32;
149 Instant::new(secs, subsec_nanos)
150}
151
152impl From<protocol::ShortFormat> for Instant {
155 fn from(t: protocol::ShortFormat) -> Self {
156 let secs = t.seconds as i64 - EPOCH_DELTA;
157 let subsec_nanos = (t.fraction as f64 / NTP_SCALE * 1e9) as i32;
158 Instant::new(secs, subsec_nanos)
159 }
160}
161
162#[cfg(feature = "std")]
163impl From<protocol::TimestampFormat> for Instant {
164 fn from(t: protocol::TimestampFormat) -> Self {
170 timestamp_to_instant(t, &Instant::now())
171 }
172}
173
174impl From<Instant> for protocol::ShortFormat {
175 fn from(t: Instant) -> Self {
176 let sec = t.secs() + EPOCH_DELTA;
177 let frac = t.subsec_nanos() as f64 * NTP_SCALE / 1e9;
178 protocol::ShortFormat {
179 seconds: sec as u16,
180 fraction: frac as u16,
181 }
182 }
183}
184
185impl From<Instant> for protocol::TimestampFormat {
186 fn from(t: Instant) -> Self {
192 let sec = t.secs() + EPOCH_DELTA;
193 let frac = t.subsec_nanos() as f64 * NTP_SCALE / 1e9;
194 protocol::TimestampFormat {
195 seconds: sec as u32,
196 fraction: frac as u32,
197 }
198 }
199}
200
201impl From<protocol::DateFormat> for Instant {
202 fn from(d: protocol::DateFormat) -> Self {
206 let ntp_secs = d.era_number as i64 * ERA_SECONDS + d.era_offset as i64;
207 let secs = ntp_secs - EPOCH_DELTA;
208 let subsec_nanos = (d.fraction as f64 / NTP_SCALE_64 * 1e9) as i32;
209 Instant::new(secs, subsec_nanos)
210 }
211}
212
213impl From<Instant> for protocol::DateFormat {
214 fn from(t: Instant) -> Self {
218 let ntp_secs = t.secs() + EPOCH_DELTA;
219 let era_number = ntp_secs.div_euclid(ERA_SECONDS) as i32;
220 let era_offset = ntp_secs.rem_euclid(ERA_SECONDS) as u32;
221 let fraction = (t.subsec_nanos().unsigned_abs() as f64 / 1e9 * NTP_SCALE_64) as u64;
222 protocol::DateFormat {
223 era_number,
224 era_offset,
225 fraction,
226 }
227 }
228}
229
230#[cfg(all(test, feature = "std"))]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn era0_timestamp_to_instant() {
236 let ts = protocol::TimestampFormat {
238 seconds: 3_913_056_000,
239 fraction: 0,
240 };
241 let pivot = Instant::new(1_704_067_200, 0);
242 let result = timestamp_to_instant(ts, &pivot);
243 assert_eq!(result.secs(), 1_704_067_200);
244 }
245
246 #[test]
247 fn era1_timestamp_with_era1_pivot() {
248 let ts = protocol::TimestampFormat {
251 seconds: 100_000_000,
252 fraction: 0,
253 };
254 let pivot = Instant::new(2_185_978_496, 0);
255 let result = timestamp_to_instant(ts, &pivot);
256 assert_eq!(result.secs(), 2_185_978_496);
257 }
258
259 #[test]
260 fn era_boundary_pivot_before_ts_after() {
261 let pivot = Instant::new(2_082_758_400, 0); let ts = protocol::TimestampFormat {
264 seconds: 1000,
265 fraction: 0,
266 };
267 let result = timestamp_to_instant(ts, &pivot);
268 let expected = ERA_SECONDS + 1000 - EPOCH_DELTA;
269 assert_eq!(result.secs(), expected);
270 }
271
272 #[test]
273 fn era_boundary_pivot_after_ts_before() {
274 let pivot = Instant::new(2_087_942_400, 0); let ts = protocol::TimestampFormat {
277 seconds: u32::MAX,
278 fraction: 0,
279 };
280 let result = timestamp_to_instant(ts, &pivot);
281 let expected = u32::MAX as i64 - EPOCH_DELTA;
282 assert_eq!(result.secs(), expected);
283 }
284
285 #[test]
286 fn date_format_roundtrip_era0() {
287 let instant = Instant::new(1_704_067_200, 500_000_000);
288 let date: protocol::DateFormat = instant.into();
289 assert_eq!(date.era_number, 0);
290 let back: Instant = date.into();
291 assert_eq!(back.secs(), instant.secs());
292 assert!((back.subsec_nanos() - instant.subsec_nanos()).abs() <= 1);
293 }
294
295 #[test]
296 fn date_format_roundtrip_era1() {
297 let instant = Instant::new(2_185_978_496, 0); let date: protocol::DateFormat = instant.into();
299 assert_eq!(date.era_number, 1);
300 let back: Instant = date.into();
301 assert_eq!(back.secs(), instant.secs());
302 }
303
304 #[test]
305 fn timestamp_format_roundtrip_with_pivot() {
306 let original = Instant::new(1_704_067_200, 0);
307 let ts: protocol::TimestampFormat = original.into();
308 let restored = timestamp_to_instant(ts, &original);
309 assert_eq!(restored.secs(), original.secs());
310 }
311
312 #[test]
313 fn date_format_negative_era() {
314 let instant = Instant::new(-2_300_000_000, 0);
316 let date: protocol::DateFormat = instant.into();
317 assert_eq!(date.era_number, -1);
318 let back: Instant = date.into();
319 assert_eq!(back.secs(), instant.secs());
320 }
321}