1use std::fmt;
19use std::ops::Deref;
20use std::sync::Arc;
21use std::sync::LazyLock;
22use std::time::SystemTime;
23
24#[cfg(feature = "chrono")]
25use chrono::{DateTime, TimeZone, Utc};
26
27#[cfg(feature = "id_gen")]
28use uuid::{NoContext, Uuid};
29
30pub type EpochMillis = u64;
32
33pub trait AsEpochMillis {
38 fn as_epoch_millis(&self) -> EpochMillis;
40}
41
42#[cfg(feature = "chrono")]
44#[allow(clippy::as_conversions)]
46pub fn to_datetime(millis: EpochMillis) -> DateTime<Utc> {
47 let dur = std::time::Duration::from_millis(millis);
48 match Utc
49 .timestamp_opt(dur.as_secs() as i64, dur.subsec_nanos())
50 .single()
51 {
52 Some(single) => single,
53 None => {
54 panic!("Ambiguous timestamp: {millis} millis")
55 }
56 }
57}
58
59#[cfg(feature = "id_gen")]
61pub fn epoch_to_uuid_v7(epoch: &EpochMillis) -> Uuid {
62 let remainder: u32 = (*epoch % 1000)
63 .try_into()
64 .expect("modulo 1000 of prepared at millis is always within a 32bit unsigned integer.");
65 Uuid::new_v7(uuid::Timestamp::from_unix(
66 NoContext,
67 *epoch / 1000,
68 remainder * 1_000_000,
69 ))
70}
71
72#[derive(Clone)]
77pub struct NowFn<T = EpochMillis>(Arc<dyn Fn() -> T + Send + Sync>);
78
79impl NowFn<EpochMillis> {
80 pub fn as_secs(&self) -> i64 {
82 let millis: u64 = (self)();
83 i64::try_from(millis / 1_000).unwrap()
86 }
87}
88
89impl<T> fmt::Debug for NowFn<T> {
90 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
91 f.write_str("<now_fn>")
92 }
93}
94
95impl<T> Deref for NowFn<T> {
96 type Target = dyn Fn() -> T + Send + Sync;
97
98 fn deref(&self) -> &Self::Target {
99 &(*self.0)
100 }
101}
102
103impl<F, T> From<F> for NowFn<T>
104where
105 F: Fn() -> T + Send + Sync + 'static,
106{
107 fn from(f: F) -> NowFn<T> {
108 NowFn(Arc::new(f))
109 }
110}
111
112fn system_time() -> EpochMillis {
113 SystemTime::now()
114 .duration_since(SystemTime::UNIX_EPOCH)
115 .expect("failed to get millis since epoch")
116 .as_millis()
117 .try_into()
118 .expect("current time did not fit into u64")
119}
120fn now_zero() -> EpochMillis {
121 0
122}
123
124pub static SYSTEM_TIME: LazyLock<NowFn> = LazyLock::new(|| NowFn::from(system_time));
126
127pub static NOW_ZERO: LazyLock<NowFn> = LazyLock::new(|| NowFn::from(now_zero));
131
132#[cfg(feature = "chrono")]
133#[cfg(test)]
134mod tests {
135 use chrono::NaiveDate;
136
137 use super::to_datetime;
138
139 #[crate::test]
140 fn test_to_datetime() {
141 let test_cases = [
142 (
143 0,
144 NaiveDate::from_ymd_opt(1970, 1, 1)
145 .unwrap()
146 .and_hms_nano_opt(0, 0, 0, 0)
147 .unwrap(),
148 ),
149 (
150 1600000000000,
151 NaiveDate::from_ymd_opt(2020, 9, 13)
152 .unwrap()
153 .and_hms_nano_opt(12, 26, 40, 0)
154 .unwrap(),
155 ),
156 (
157 1658323270293,
158 NaiveDate::from_ymd_opt(2022, 7, 20)
159 .unwrap()
160 .and_hms_nano_opt(13, 21, 10, 293_000_000)
161 .unwrap(),
162 ),
163 ];
164 for (millis, datetime) in test_cases.into_iter() {
166 let converted_datetime = to_datetime(millis).naive_utc();
167 assert_eq!(datetime, converted_datetime);
168 assert_eq!(
169 millis,
170 u64::try_from(converted_datetime.and_utc().timestamp_millis()).unwrap()
171 )
172 }
173 }
174}