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