1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4use std::time::{Duration, SystemTime, UNIX_EPOCH};
56/// The maximum number of whole milliseconds that can be represented in
7/// an `f64` without loss of precision.
8const MAX_FLOAT_MILLISECONDS: f64 = ((1u64 << f64::MANTISSA_DIGITS) - 1) as f64;
910/// Typesafe way to manage server timestamps without accidentally mixing them up with
11/// local ones.
12#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Default)]
13pub struct ServerTimestamp(pub i64);
1415impl ServerTimestamp {
16pub fn from_float_seconds(ts: f64) -> Self {
17let rf = (ts * 1000.0).round();
18if !(0.0..=MAX_FLOAT_MILLISECONDS).contains(&rf) {
19error_support::report_error!("sync15-illegal-timestamp", "Illegal timestamp: {}", ts);
20 ServerTimestamp(0)
21 } else {
22 ServerTimestamp(rf as i64)
23 }
24 }
2526pub fn from_millis(ts: i64) -> Self {
27// Catch it in tests, but just complain and replace with 0 otherwise.
28debug_assert!(ts >= 0, "Bad timestamp: {}", ts);
29if ts >= 0 {
30Self(ts)
31 } else {
32error_support::report_error!(
33"sync15-illegal-timestamp",
34"Illegal timestamp, substituting 0: {}",
35 ts
36 );
37Self(0)
38 }
39 }
40}
4142// This lets us use these in hyper header! blocks.
43impl std::str::FromStr for ServerTimestamp {
44type Err = std::num::ParseFloatError;
45fn from_str(s: &str) -> Result<Self, Self::Err> {
46let val = f64::from_str(s)?;
47Ok(Self::from_float_seconds(val))
48 }
49}
5051impl std::fmt::Display for ServerTimestamp {
52fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53write!(f, "{}", self.0 as f64 / 1000.0)
54 }
55}
5657impl ServerTimestamp {
58pub const EPOCH: ServerTimestamp = ServerTimestamp(0);
5960/// Returns None if `other` is later than `self` (Duration may not represent
61 /// negative timespans in rust).
62#[inline]
63pub fn duration_since(self, other: ServerTimestamp) -> Option<Duration> {
64let delta = self.0 - other.0;
65if delta < 0 {
66None
67} else {
68Some(Duration::from_millis(delta as u64))
69 }
70 }
7172/// Get the milliseconds for the timestamp.
73#[inline]
74pub fn as_millis(self) -> i64 {
75self.0
76}
77}
7879impl serde::ser::Serialize for ServerTimestamp {
80fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
81 serializer.serialize_f64(self.0 as f64 / 1000.0)
82 }
83}
8485impl<'de> serde::de::Deserialize<'de> for ServerTimestamp {
86fn deserialize<D: serde::de::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
87 f64::deserialize(d).map(Self::from_float_seconds)
88 }
89}
9091/// Exposed only for tests that need to create a server timestamp from the system time
92/// Please be cautious when constructing the timestamp directly, as constructing the server
93/// timestamp from system time is almost certainly not what you'd want to do for non-test code
94impl TryFrom<SystemTime> for ServerTimestamp {
95type Error = std::time::SystemTimeError;
9697fn try_from(value: SystemTime) -> Result<Self, Self::Error> {
98Ok(Self(value.duration_since(UNIX_EPOCH)?.as_millis() as i64))
99 }
100}
101102#[cfg(test)]
103mod test {
104use super::*;
105106#[test]
107fn test_server_timestamp() {
108let t0 = ServerTimestamp(10_300_150);
109let t1 = ServerTimestamp(10_100_050);
110assert!(t1.duration_since(t0).is_none());
111assert!(t0.duration_since(t1).is_some());
112let dur = t0.duration_since(t1).unwrap();
113assert_eq!(dur.as_secs(), 200);
114assert_eq!(dur.subsec_nanos(), 100_000_000);
115 }
116117#[test]
118fn test_serde() {
119let ts = ServerTimestamp(123_456);
120121// test serialize
122let ser = serde_json::to_string(&ts).unwrap();
123assert_eq!("123.456".to_string(), ser);
124125// test deserialize of float
126let ts: ServerTimestamp = serde_json::from_str(&ser).unwrap();
127assert_eq!(ServerTimestamp(123_456), ts);
128129// test deserialize of whole number
130let ts: ServerTimestamp = serde_json::from_str("123").unwrap();
131assert_eq!(ServerTimestamp(123_000), ts);
132133// test deserialize of negative number
134let ts: ServerTimestamp = serde_json::from_str("-123").unwrap();
135assert_eq!(ServerTimestamp(0), ts);
136 }
137}