holochain_timestamp/
lib.rs1#![deny(missing_docs)]
3
4#[allow(missing_docs)]
5mod error;
6#[cfg(feature = "now")]
7mod human;
8
9pub use crate::error::{TimestampError, TimestampResult};
10#[cfg(feature = "now")]
11pub(crate) use chrono_ext::*;
12use core::ops::{Add, Sub};
13#[cfg(feature = "now")]
14pub use human::*;
15use serde::{Deserialize, Serialize};
16use std::convert::{TryFrom, TryInto};
17
18#[cfg(feature = "now")]
19mod chrono_ext;
20
21pub(crate) const MM: i64 = 1_000_000;
23
24#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
43#[cfg_attr(not(feature = "now"), derive(Debug))]
44pub struct Timestamp(
45 pub i64,
47);
48
49impl<D: Into<core::time::Duration>> Add<D> for Timestamp {
53 type Output = TimestampResult<Timestamp>;
54
55 fn add(self, rhs: D) -> Self::Output {
56 self.checked_add(&rhs.into())
57 .ok_or(TimestampError::Overflow)
58 }
59}
60
61impl<D: Into<core::time::Duration>> Add<D> for &Timestamp {
62 type Output = TimestampResult<Timestamp>;
63
64 fn add(self, rhs: D) -> Self::Output {
65 self.to_owned() + rhs
66 }
67}
68
69impl<D: Into<core::time::Duration>> Sub<D> for Timestamp {
71 type Output = TimestampResult<Timestamp>;
72
73 fn sub(self, rhs: D) -> Self::Output {
74 self.checked_sub(&rhs.into())
75 .ok_or(TimestampError::Overflow)
76 }
77}
78
79impl<D: Into<core::time::Duration>> Sub<D> for &Timestamp {
80 type Output = TimestampResult<Timestamp>;
81
82 fn sub(self, rhs: D) -> Self::Output {
83 self.to_owned() - rhs
84 }
85}
86
87impl Timestamp {
88 pub const ZERO: Timestamp = Timestamp(0);
90 pub const MIN: Timestamp = Timestamp(i64::MIN);
92 pub const MAX: Timestamp = Timestamp(i64::MAX);
94 pub const HOLOCHAIN_EPOCH: Timestamp = Timestamp(1640995200000000);
96
97 pub fn max() -> Timestamp {
99 Timestamp(i64::MAX)
100 }
101
102 pub fn from_micros(micros: i64) -> Self {
104 Self(micros)
105 }
106
107 pub fn as_micros(&self) -> i64 {
109 self.0
110 }
111
112 pub fn as_millis(&self) -> i64 {
114 self.0 / 1000
115 }
116
117 pub fn as_seconds_and_nanos(&self) -> (i64, u32) {
119 let secs = self.0 / MM;
120 let nsecs = (self.0 % 1_000_000) * 1000;
121 (secs, nsecs as u32)
122 }
123
124 pub fn checked_add(&self, rhs: &core::time::Duration) -> Option<Timestamp> {
127 let micros = rhs.as_micros();
128 if micros <= i64::MAX as u128 {
129 Some(Self(self.0.checked_add(micros as i64)?))
130 } else {
131 None
132 }
133 }
134
135 pub fn checked_sub(&self, rhs: &core::time::Duration) -> Option<Timestamp> {
137 let micros = rhs.as_micros();
138 if micros <= i64::MAX as u128 {
139 Some(Self(self.0.checked_sub(micros as i64)?))
140 } else {
141 None
142 }
143 }
144
145 pub fn saturating_add(&self, rhs: &core::time::Duration) -> Timestamp {
147 self.checked_add(rhs).unwrap_or(Self::MAX)
148 }
149
150 pub fn saturating_sub(&self, rhs: &core::time::Duration) -> Timestamp {
152 self.checked_sub(rhs).unwrap_or(Self::MIN)
153 }
154
155 pub fn saturating_from_dur(duration: &core::time::Duration) -> Self {
157 Timestamp(std::cmp::min(duration.as_micros(), i64::MAX as u128) as i64)
158 }
159}
160
161impl TryFrom<core::time::Duration> for Timestamp {
162 type Error = error::TimestampError;
163
164 fn try_from(value: core::time::Duration) -> Result<Self, Self::Error> {
165 Ok(Timestamp(
166 value
167 .as_micros()
168 .try_into()
169 .map_err(|_| error::TimestampError::Overflow)?,
170 ))
171 }
172}
173
174#[cfg(feature = "sqlite")]
175impl rusqlite::ToSql for Timestamp {
176 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
177 Ok(rusqlite::types::ToSqlOutput::Owned(self.0.into()))
178 }
179}
180
181#[cfg(feature = "sqlite")]
182impl rusqlite::types::FromSql for Timestamp {
183 fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
184 match value {
185 rusqlite::types::ValueRef::Integer(i) => Ok(Self::from_micros(i)),
189 _ => Err(rusqlite::types::FromSqlError::InvalidType),
190 }
191 }
192}
193
194#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
196pub struct InclusiveTimestampInterval {
197 start: Timestamp,
198 end: Timestamp,
199}
200
201impl InclusiveTimestampInterval {
202 pub fn try_new(start: Timestamp, end: Timestamp) -> TimestampResult<Self> {
204 if start > end {
205 Err(TimestampError::OutOfOrder)
206 } else {
207 Ok(Self { start, end })
208 }
209 }
210
211 pub fn start(&self) -> Timestamp {
213 self.start
214 }
215
216 pub fn end(&self) -> Timestamp {
218 self.end
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use std::convert::TryInto;
226
227 const TEST_TS: &str = "2020-05-05T19:16:04.266431Z";
228
229 #[test]
230 fn timestamp_distance() {
231 let t1 = Timestamp(i64::MAX); let d1: TimestampResult<chrono::DateTime<chrono::Utc>> = t1.try_into();
237 assert_eq!(d1, Err(TimestampError::Overflow));
238
239 let t2 = Timestamp(0) + core::time::Duration::new(0, 1000);
240 assert_eq!(t2, Ok(Timestamp(1)));
241 }
242
243 #[test]
244 fn micros_roundtrip() {
245 for t in [Timestamp(1234567890), Timestamp(987654321)] {
246 let micros = t.clone().as_micros();
247 let r = Timestamp::from_micros(micros);
248 assert_eq!(t.0, r.0);
249 assert_eq!(t, r);
250 }
251 }
252
253 #[test]
254 fn test_timestamp_serialization() {
255 use holochain_serialized_bytes::prelude::*;
256 let t: Timestamp = TEST_TS.try_into().unwrap();
257 let (secs, nsecs) = t.as_seconds_and_nanos();
258 assert_eq!(secs, 1588706164);
259 assert_eq!(nsecs, 266431000);
260 assert_eq!(TEST_TS, &t.to_string());
261
262 #[derive(Debug, serde::Serialize, serde::Deserialize, SerializedBytes)]
263 struct S(Timestamp);
264 let s = S(t);
265 let sb = SerializedBytes::try_from(s).unwrap();
266 let s: S = sb.try_into().unwrap();
267 let t = s.0;
268 assert_eq!(TEST_TS, &t.to_string());
269 }
270
271 #[test]
272 fn test_timestamp_alternate_forms() {
273 use holochain_serialized_bytes::prelude::*;
274
275 decode::<_, Timestamp>(&encode(&(0u64)).unwrap()).unwrap();
276 decode::<_, Timestamp>(&encode(&(i64::MAX as u64)).unwrap()).unwrap();
277 assert!(decode::<_, Timestamp>(&encode(&(i64::MAX as u64 + 1)).unwrap()).is_err());
278 }
279
280 #[test]
281 fn inclusive_timestamp_interval_test_new() {
282 for (start, end) in [(0, 0), (-1, 0), (0, 1), (i64::MIN, i64::MAX)] {
284 InclusiveTimestampInterval::try_new(Timestamp(start), Timestamp(end)).unwrap();
285 }
286
287 for (start, end) in [(0, -1), (1, 0), (i64::MAX, i64::MIN)] {
289 assert!(
290 super::InclusiveTimestampInterval::try_new(Timestamp(start), Timestamp(end))
291 .is_err()
292 );
293 }
294 }
295}