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))]
33pub struct Timestamp(DateTime<Utc>);
34
35impl Timestamp {
36 pub fn unix_epoch() -> Self {
39 Self(DateTime::from(std::time::UNIX_EPOCH))
40 }
41
42 pub fn now() -> Timestamp {
44 Timestamp(Utc::now())
45 }
46
47 pub fn to_string_for_filename(&self) -> String {
49 self.0.format("%F_%H-%M-%S-UTC").to_string()
53 }
54
55 pub fn rounded_to_seconds(self) -> Timestamp {
57 Timestamp(self.0.with_nanosecond(0).expect("nanoseconds should be 0"))
60 }
61}
62
63impl ExampleData for Timestamp {
64 fn example_data() -> Self {
65 Timestamp(Utc.with_ymd_and_hms(2024, 7, 20, 14, 16, 19).unwrap())
66 }
67}
68
69impl From<SystemTime> for Timestamp {
70 fn from(value: SystemTime) -> Self {
71 Self(value.into())
72 }
73}
74
75impl From<DateTimeTz> for Timestamp {
76 fn from(value: DateTimeTz) -> Self {
77 value.datetime.into()
78 }
79}
80
81impl From<Timestamp> for DateTime<Utc> {
82 fn from(value: Timestamp) -> Self {
83 value.0
84 }
85}
86
87impl Add<chrono::Duration> for Timestamp {
88 type Output = Timestamp;
89
90 fn add(self, rhs: chrono::Duration) -> Self::Output {
91 Timestamp(self.0 + rhs)
92 }
93}
94
95#[cfg(feature = "redis")]
96impl redis::ToRedisArgs for Timestamp {
97 fn write_redis_args<W>(&self, out: &mut W)
98 where
99 W: ?Sized + redis::RedisWrite,
100 {
101 self.0.timestamp().write_redis_args(out)
102 }
103
104 fn describe_numeric_behavior(&self) -> redis::NumericBehavior {
105 redis::NumericBehavior::NumberIsInteger
106 }
107}
108
109#[cfg(feature = "redis")]
110impl redis::ToSingleRedisArg for Timestamp {}
111
112#[cfg(feature = "redis")]
113impl redis::FromRedisValue for Timestamp {
114 fn from_redis_value(v: redis::Value) -> Result<Timestamp, redis::ParsingError> {
115 use chrono::TimeZone as _;
116 let timestamp = Utc
117 .timestamp_opt(i64::from_redis_value(v)?, 0)
118 .latest()
119 .unwrap();
120 Ok(Timestamp(timestamp))
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use chrono::{TimeZone as _, Utc};
127
128 use super::Timestamp;
129
130 #[test]
131 fn to_string_for_filename() {
132 let timestamp = Timestamp::unix_epoch();
133 assert_eq!(
134 "1970-01-01_00-00-00-UTC",
135 timestamp.to_string_for_filename().as_str()
136 );
137
138 let timestamp = Timestamp(Utc.with_ymd_and_hms(2020, 5, 3, 14, 16, 19).unwrap());
139 assert_eq!(
140 "2020-05-03_14-16-19-UTC",
141 timestamp.to_string_for_filename().as_str()
142 );
143 }
144}