opentalk_types_common/time/
timestamp.rs1use std::{ops::Add, time::SystemTime};
6
7use chrono::{DateTime, TimeZone as _, Timelike as _, Utc};
8use derive_more::{AsRef, Deref, Display, From, FromStr};
9
10use crate::{time::DateTimeTz, utils::ExampleData};
11
12#[derive(
16 AsRef,
17 Deref,
18 Display,
19 From,
20 FromStr,
21 Debug,
22 Default,
23 Copy,
24 Clone,
25 Ord,
26 PartialOrd,
27 Eq,
28 PartialEq,
29 Hash,
30)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
33#[cfg_attr(feature = "typescript", derive(ts_rs::TS), ts(export_to = "common/"))]
34pub struct Timestamp(DateTime<Utc>);
35
36impl Timestamp {
37 pub fn unix_epoch() -> Self {
40 Self(DateTime::from(std::time::UNIX_EPOCH))
41 }
42
43 pub fn now() -> Timestamp {
45 Timestamp(Utc::now())
46 }
47
48 pub fn to_string_for_filename(&self) -> String {
50 self.0.format("%F_%H-%M-%S-UTC").to_string()
54 }
55
56 pub fn rounded_to_seconds(self) -> Timestamp {
58 Timestamp(self.0.with_nanosecond(0).expect("nanoseconds should be 0"))
61 }
62}
63
64impl ExampleData for Timestamp {
65 fn example_data() -> Self {
66 Timestamp(Utc.with_ymd_and_hms(2024, 7, 20, 14, 16, 19).unwrap())
67 }
68}
69
70impl From<SystemTime> for Timestamp {
71 fn from(value: SystemTime) -> Self {
72 Self(value.into())
73 }
74}
75
76impl From<DateTimeTz> for Timestamp {
77 fn from(value: DateTimeTz) -> Self {
78 value.datetime.into()
79 }
80}
81
82impl From<Timestamp> for DateTime<Utc> {
83 fn from(value: Timestamp) -> Self {
84 value.0
85 }
86}
87
88impl Add<chrono::Duration> for Timestamp {
89 type Output = Timestamp;
90
91 fn add(self, rhs: chrono::Duration) -> Self::Output {
92 Timestamp(self.0 + rhs)
93 }
94}
95
96#[cfg(feature = "redis")]
97impl redis::ToRedisArgs for Timestamp {
98 fn write_redis_args<W>(&self, out: &mut W)
99 where
100 W: ?Sized + redis::RedisWrite,
101 {
102 self.0.timestamp().write_redis_args(out)
103 }
104
105 fn describe_numeric_behavior(&self) -> redis::NumericBehavior {
106 redis::NumericBehavior::NumberIsInteger
107 }
108}
109
110#[cfg(feature = "redis")]
111impl redis::ToSingleRedisArg for Timestamp {}
112
113#[cfg(feature = "redis")]
114impl redis::FromRedisValue for Timestamp {
115 fn from_redis_value(v: redis::Value) -> Result<Timestamp, redis::ParsingError> {
116 use chrono::TimeZone as _;
117 let timestamp = Utc
118 .timestamp_opt(i64::from_redis_value(v)?, 0)
119 .latest()
120 .unwrap();
121 Ok(Timestamp(timestamp))
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use chrono::{TimeZone as _, Utc};
128
129 use super::Timestamp;
130
131 #[test]
132 fn to_string_for_filename() {
133 let timestamp = Timestamp::unix_epoch();
134 assert_eq!(
135 "1970-01-01_00-00-00-UTC",
136 timestamp.to_string_for_filename().as_str()
137 );
138
139 let timestamp = Timestamp(Utc.with_ymd_and_hms(2020, 5, 3, 14, 16, 19).unwrap());
140 assert_eq!(
141 "2020-05-03_14-16-19-UTC",
142 timestamp.to_string_for_filename().as_str()
143 );
144 }
145}