1use std::{
2 fmt,
3 ops::{Add, Sub},
4};
5
6use thiserror::Error;
7use time::{
8 format_description::FormatItem, macros::format_description, Duration, OffsetDateTime,
9 PrimitiveDateTime,
10};
11
12#[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord)]
22pub struct Timestamp(time::OffsetDateTime);
23
24const TIMESTAMP_FORMAT: &[FormatItem] =
25 format_description!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]");
26
27impl fmt::Display for Timestamp {
28 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
29 write!(f, "{}", self.0.format(TIMESTAMP_FORMAT).unwrap())
30 }
31}
32
33#[derive(Debug, Error)]
34#[error("Invalid time range: {0}")]
35pub struct OutOfRangeError(String);
36
37impl Timestamp {
38 pub fn now() -> Self {
39 Self(OffsetDateTime::now_utc())
40 }
41
42 pub fn try_from_secs(seconds: i64) -> Result<Self, OutOfRangeError> {
43 let date_time = time::OffsetDateTime::from_unix_timestamp(seconds)
44 .map_err(|err| OutOfRangeError(err.to_string()))?;
45 Ok(Self(date_time))
46 }
47
48 #[deprecated]
49 pub fn from_secs(seconds: i64) -> Self {
50 Self::try_from_secs(seconds).unwrap()
51 }
52
53 pub fn try_from_millis(milliseconds: i64) -> Result<Self, OutOfRangeError> {
54 let nanos = millis_to_nanos(milliseconds);
55 let date_time = time::OffsetDateTime::from_unix_timestamp_nanos(nanos)
56 .map_err(|err| OutOfRangeError(err.to_string()))?;
57 Ok(Self(date_time))
58 }
59
60 #[deprecated]
61 pub fn from_millis(milliseconds: i64) -> Self {
62 Self::try_from_millis(milliseconds).unwrap()
63 }
64
65 pub fn as_secs(self) -> i64 {
66 self.0.unix_timestamp()
67 }
68
69 pub fn as_millis(self) -> i64 {
70 nanos_to_millis(self.0.unix_timestamp_nanos())
71 }
72
73 pub fn format(&self, fmt: &[FormatItem<'_>]) -> String {
74 self.0.format(fmt).unwrap()
75 }
76 pub fn checked_sub(self, duration: Duration) -> Option<Self> {
77 self.0.checked_sub(duration).map(Self)
78 }
79}
80
81fn nanos_to_millis(nanos: i128) -> i64 {
82 (nanos / 1_000_000).try_into().unwrap()
83}
84
85fn millis_to_nanos(millis: i64) -> i128 {
86 i128::from(millis) * 1_000_000
87}
88
89impl From<PrimitiveDateTime> for Timestamp {
90 fn from(ts: PrimitiveDateTime) -> Self {
91 Self(ts.assume_utc())
92 }
93}
94
95impl Add<Duration> for Timestamp {
96 type Output = Self;
97 fn add(self, d: time::Duration) -> Self {
98 Self(self.0.add(d))
99 }
100}
101
102impl Sub<Duration> for Timestamp {
103 type Output = Self;
104 fn sub(self, d: time::Duration) -> Self {
105 Self(self.0.sub(d))
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn format_timestamp() {
115 let ts = Timestamp::try_from_millis(1_658_146_497_321).unwrap();
116 assert_eq!("2022-07-18 12:14:57.321", format!("{ts}"));
117 }
118}